From 44524fcdf6ca19b58c24f7622c4af1d8d8fe59f8 Mon Sep 17 00:00:00 2001 From: Marek Lindner Date: Thu, 10 Feb 2011 14:33:53 +0000 Subject: 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 Signed-off-by: Marek Lindner --- net/batman-adv/unicast.c | 57 +++++++++++++++++++++++++++++------------------- 1 file changed, 34 insertions(+), 23 deletions(-) (limited to 'net/batman-adv/unicast.c') 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; } -- cgit v1.2.2