summaryrefslogtreecommitdiff
path: root/net/batman-adv/vis.c
diff options
context:
space:
mode:
authorMarek Lindner <lindner_marek@yahoo.de>2011-02-10 14:33:53 +0000
committerMarek Lindner <lindner_marek@yahoo.de>2011-03-05 12:50:03 +0100
commit44524fcdf6ca19b58c24f7622c4af1d8d8fe59f8 (patch)
tree297c76f80d68d56e3c65a23c70de645a1c93df47 /net/batman-adv/vis.c
parenta4c135c561106c397bae33455acfca4aa8065a30 (diff)
downloadlinux-3.10-44524fcdf6ca19b58c24f7622c4af1d8d8fe59f8.tar.gz
linux-3.10-44524fcdf6ca19b58c24f7622c4af1d8d8fe59f8.tar.bz2
linux-3.10-44524fcdf6ca19b58c24f7622c4af1d8d8fe59f8.zip
batman-adv: Correct rcu refcounting for neigh_node
It might be possible that 2 threads access the same data in the same rcu grace period. The first thread calls call_rcu() to decrement the refcount and free the data while the second thread increases the refcount to use the data. To avoid this race condition all refcount operations have to be atomic. Reported-by: Sven Eckelmann <sven@narfation.org> Signed-off-by: Marek Lindner <lindner_marek@yahoo.de>
Diffstat (limited to 'net/batman-adv/vis.c')
-rw-r--r--net/batman-adv/vis.c33
1 files changed, 27 insertions, 6 deletions
diff --git a/net/batman-adv/vis.c b/net/batman-adv/vis.c
index 8092eadcbde..9832d8f9ed4 100644
--- a/net/batman-adv/vis.c
+++ b/net/batman-adv/vis.c
@@ -764,21 +764,35 @@ static void unicast_vis_packet(struct bat_priv *bat_priv,
struct vis_info *info)
{
struct orig_node *orig_node;
+ struct neigh_node *neigh_node = NULL;
struct sk_buff *skb;
struct vis_packet *packet;
struct batman_if *batman_if;
uint8_t dstaddr[ETH_ALEN];
- spin_lock_bh(&bat_priv->orig_hash_lock);
packet = (struct vis_packet *)info->skb_packet->data;
+
+ spin_lock_bh(&bat_priv->orig_hash_lock);
rcu_read_lock();
orig_node = ((struct orig_node *)hash_find(bat_priv->orig_hash,
compare_orig, choose_orig,
packet->target_orig));
- rcu_read_unlock();
- if ((!orig_node) || (!orig_node->router))
- goto out;
+ if (!orig_node)
+ goto unlock;
+
+ kref_get(&orig_node->refcount);
+ neigh_node = orig_node->router;
+
+ if (!neigh_node)
+ goto unlock;
+
+ if (!atomic_inc_not_zero(&neigh_node->refcount)) {
+ neigh_node = NULL;
+ goto unlock;
+ }
+
+ rcu_read_unlock();
/* don't lock while sending the packets ... we therefore
* copy the required data before sending */
@@ -790,10 +804,17 @@ static void unicast_vis_packet(struct bat_priv *bat_priv,
if (skb)
send_skb_packet(skb, batman_if, dstaddr);
- return;
+ goto out;
-out:
+unlock:
+ rcu_read_unlock();
spin_unlock_bh(&bat_priv->orig_hash_lock);
+out:
+ if (neigh_node)
+ neigh_node_free_ref(neigh_node);
+ if (orig_node)
+ kref_put(&orig_node->refcount, orig_node_free_ref);
+ return;
}
/* only send one vis packet. called from send_vis_packets() */