batman-adv: Fix use-after-free/double-free of tt_req_node
[pandora-kernel.git] / net / batman-adv / translation-table.c
index c7aafc7..d2d85a6 100644 (file)
@@ -245,9 +245,11 @@ void tt_local_add(struct net_device *soft_iface, const uint8_t *addr,
        if (tt_global_entry) {
                /* This node is probably going to update its tt table */
                tt_global_entry->orig_node->tt_poss_change = true;
-               /* The global entry has to be marked as PENDING and has to be
+               /* The global entry has to be marked as ROAMING and has to be
                 * kept for consistency purpose */
-               tt_global_entry->flags |= TT_CLIENT_PENDING;
+               tt_global_entry->flags |= TT_CLIENT_ROAM;
+               tt_global_entry->roam_at = jiffies;
+
                send_roam_adv(bat_priv, tt_global_entry->addr,
                              tt_global_entry->orig_node);
        }
@@ -694,6 +696,7 @@ void tt_global_del(struct bat_priv *bat_priv,
                   const char *message, bool roaming)
 {
        struct tt_global_entry *tt_global_entry = NULL;
+       struct tt_local_entry *tt_local_entry = NULL;
 
        tt_global_entry = tt_global_hash_find(bat_priv, addr);
        if (!tt_global_entry)
@@ -701,15 +704,29 @@ void tt_global_del(struct bat_priv *bat_priv,
 
        if (tt_global_entry->orig_node == orig_node) {
                if (roaming) {
-                       tt_global_entry->flags |= TT_CLIENT_ROAM;
-                       tt_global_entry->roam_at = jiffies;
-                       goto out;
+                       /* if we are deleting a global entry due to a roam
+                        * event, there are two possibilities:
+                        * 1) the client roamed from node A to node B => we mark
+                        *    it with TT_CLIENT_ROAM, we start a timer and we
+                        *    wait for node B to claim it. In case of timeout
+                        *    the entry is purged.
+                        * 2) the client roamed to us => we can directly delete
+                        *    the global entry, since it is useless now. */
+                       tt_local_entry = tt_local_hash_find(bat_priv,
+                                                       tt_global_entry->addr);
+                       if (!tt_local_entry) {
+                               tt_global_entry->flags |= TT_CLIENT_ROAM;
+                               tt_global_entry->roam_at = jiffies;
+                               goto out;
+                       }
                }
                _tt_global_del(bat_priv, tt_global_entry, message);
        }
 out:
        if (tt_global_entry)
                tt_global_entry_free_ref(tt_global_entry);
+       if (tt_local_entry)
+               tt_local_entry_free_ref(tt_local_entry);
 }
 
 void tt_global_del_orig(struct bat_priv *bat_priv,
@@ -935,6 +952,29 @@ uint16_t tt_local_crc(struct bat_priv *bat_priv)
        return total;
 }
 
+/**
+ * batadv_tt_req_node_release - free tt_req node entry
+ * @ref: kref pointer of the tt req_node entry
+ */
+static void batadv_tt_req_node_release(struct kref *ref)
+{
+       struct tt_req_node *tt_req_node;
+
+       tt_req_node = container_of(ref, struct tt_req_node, refcount);
+
+       kfree(tt_req_node);
+}
+
+/**
+ * batadv_tt_req_node_put - decrement the tt_req_node refcounter and
+ *  possibly release it
+ * @tt_req_node: tt_req_node to be free'd
+ */
+static void batadv_tt_req_node_put(struct tt_req_node *tt_req_node)
+{
+       kref_put(&tt_req_node->refcount, batadv_tt_req_node_release);
+}
+
 static void tt_req_list_free(struct bat_priv *bat_priv)
 {
        struct tt_req_node *node, *safe;
@@ -943,7 +983,7 @@ static void tt_req_list_free(struct bat_priv *bat_priv)
 
        list_for_each_entry_safe(node, safe, &bat_priv->tt_req_list, list) {
                list_del(&node->list);
-               kfree(node);
+               batadv_tt_req_node_put(node);
        }
 
        spin_unlock_bh(&bat_priv->tt_req_list_lock);
@@ -978,7 +1018,7 @@ static void tt_req_purge(struct bat_priv *bat_priv)
                if (is_out_of_time(node->issued_at,
                    TT_REQUEST_TIMEOUT * 1000)) {
                        list_del(&node->list);
-                       kfree(node);
+                       batadv_tt_req_node_put(node);
                }
        }
        spin_unlock_bh(&bat_priv->tt_req_list_lock);
@@ -1003,9 +1043,11 @@ static struct tt_req_node *new_tt_req_node(struct bat_priv *bat_priv,
        if (!tt_req_node)
                goto unlock;
 
+       kref_init(&tt_req_node->refcount);
        memcpy(tt_req_node->addr, orig_node->orig, ETH_ALEN);
        tt_req_node->issued_at = jiffies;
 
+       kref_get(&tt_req_node->refcount);
        list_add(&tt_req_node->list, &bat_priv->tt_req_list);
 unlock:
        spin_unlock_bh(&bat_priv->tt_req_list_lock);
@@ -1157,12 +1199,19 @@ out:
                hardif_free_ref(primary_if);
        if (ret)
                kfree_skb(skb);
+
        if (ret && tt_req_node) {
                spin_lock_bh(&bat_priv->tt_req_list_lock);
-               list_del(&tt_req_node->list);
+               if (!list_empty(&tt_req_node->list)) {
+                       list_del(&tt_req_node->list);
+                       batadv_tt_req_node_put(tt_req_node);
+               }
                spin_unlock_bh(&bat_priv->tt_req_list_lock);
-               kfree(tt_req_node);
        }
+
+       if (tt_req_node)
+               batadv_tt_req_node_put(tt_req_node);
+
        return ret;
 }
 
@@ -1535,7 +1584,7 @@ void handle_tt_response(struct bat_priv *bat_priv,
                if (!compare_eth(node->addr, tt_response->src))
                        continue;
                list_del(&node->list);
-               kfree(node);
+               batadv_tt_req_node_put(node);
        }
        spin_unlock_bh(&bat_priv->tt_req_list_lock);
 
@@ -1799,10 +1848,10 @@ bool is_ap_isolated(struct bat_priv *bat_priv, uint8_t *src, uint8_t *dst)
 {
        struct tt_local_entry *tt_local_entry = NULL;
        struct tt_global_entry *tt_global_entry = NULL;
-       bool ret = true;
+       bool ret = false;
 
        if (!atomic_read(&bat_priv->ap_isolation))
-               return false;
+               goto out;
 
        tt_local_entry = tt_local_hash_find(bat_priv, dst);
        if (!tt_local_entry)
@@ -1812,10 +1861,10 @@ bool is_ap_isolated(struct bat_priv *bat_priv, uint8_t *src, uint8_t *dst)
        if (!tt_global_entry)
                goto out;
 
-       if (_is_ap_isolated(tt_local_entry, tt_global_entry))
+       if (!_is_ap_isolated(tt_local_entry, tt_global_entry))
                goto out;
 
-       ret = false;
+       ret = true;
 
 out:
        if (tt_global_entry)