diff options
author | Marek Lindner <lindner_marek@yahoo.de> | 2011-02-10 17:33:53 +0300 |
---|---|---|
committer | Marek Lindner <lindner_marek@yahoo.de> | 2011-03-05 14:50:03 +0300 |
commit | 44524fcdf6ca19b58c24f7622c4af1d8d8fe59f8 (patch) | |
tree | 297c76f80d68d56e3c65a23c70de645a1c93df47 /net/batman-adv/unicast.c | |
parent | a4c135c561106c397bae33455acfca4aa8065a30 (diff) | |
download | linux-44524fcdf6ca19b58c24f7622c4af1d8d8fe59f8.tar.xz |
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/unicast.c')
-rw-r--r-- | net/batman-adv/unicast.c | 57 |
1 files changed, 34 insertions, 23 deletions
diff --git a/net/batman-adv/unicast.c b/net/batman-adv/unicast.c index 00bfeaf9ece3..7ca994ccac1d 100644 --- a/net/batman-adv/unicast.c +++ b/net/batman-adv/unicast.c @@ -285,38 +285,42 @@ int unicast_send_skb(struct sk_buff *skb, struct bat_priv *bat_priv) struct unicast_packet *unicast_packet; struct orig_node *orig_node = NULL; struct batman_if *batman_if; - struct neigh_node *router; + struct neigh_node *neigh_node; int data_len = skb->len; uint8_t dstaddr[6]; + int ret = 1; spin_lock_bh(&bat_priv->orig_hash_lock); /* get routing information */ if (is_multicast_ether_addr(ethhdr->h_dest)) orig_node = (struct orig_node *)gw_get_selected(bat_priv); + if (orig_node) { + kref_get(&orig_node->refcount); + goto find_router; + } - /* check for hna host */ - if (!orig_node) - orig_node = transtable_search(bat_priv, ethhdr->h_dest); + /* check for hna host - increases orig_node refcount */ + orig_node = transtable_search(bat_priv, ethhdr->h_dest); +find_router: /* find_router() increases neigh_nodes refcount if found. */ - router = find_router(bat_priv, orig_node, NULL); + neigh_node = find_router(bat_priv, orig_node, NULL); - if (!router) + if (!neigh_node) goto unlock; - /* don't lock while sending the packets ... we therefore - * copy the required data before sending */ - batman_if = router->if_incoming; - memcpy(dstaddr, router->addr, ETH_ALEN); - - spin_unlock_bh(&bat_priv->orig_hash_lock); - - if (batman_if->if_status != IF_ACTIVE) - goto dropped; + if (neigh_node->if_incoming->if_status != IF_ACTIVE) + goto unlock; if (my_skb_head_push(skb, sizeof(struct unicast_packet)) < 0) - goto dropped; + goto unlock; + + /* don't lock while sending the packets ... we therefore + * copy the required data before sending */ + batman_if = neigh_node->if_incoming; + memcpy(dstaddr, neigh_node->addr, ETH_ALEN); + spin_unlock_bh(&bat_priv->orig_hash_lock); unicast_packet = (struct unicast_packet *)skb->data; @@ -330,18 +334,25 @@ int unicast_send_skb(struct sk_buff *skb, struct bat_priv *bat_priv) if (atomic_read(&bat_priv->fragmentation) && data_len + sizeof(struct unicast_packet) > - batman_if->net_dev->mtu) { + batman_if->net_dev->mtu) { /* send frag skb decreases ttl */ unicast_packet->ttl++; - return frag_send_skb(skb, bat_priv, batman_if, - dstaddr); + ret = frag_send_skb(skb, bat_priv, batman_if, dstaddr); + goto out; } + send_skb_packet(skb, batman_if, dstaddr); - return 0; + ret = 0; + goto out; unlock: spin_unlock_bh(&bat_priv->orig_hash_lock); -dropped: - kfree_skb(skb); - return 1; +out: + if (neigh_node) + neigh_node_free_ref(neigh_node); + if (orig_node) + kref_put(&orig_node->refcount, orig_node_free_ref); + if (ret == 1) + kfree_skb(skb); + return ret; } |