Skip to content

Commit

Permalink
batman-adv: Correct rcu refcounting for neigh_node
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Marek Lindner committed Mar 5, 2011
1 parent a4c135c commit 44524fc
Show file tree
Hide file tree
Showing 7 changed files with 313 additions and 174 deletions.
27 changes: 20 additions & 7 deletions net/batman-adv/icmp_socket.c
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,8 @@ static ssize_t bat_socket_write(struct file *file, const char __user *buff,
struct sk_buff *skb;
struct icmp_packet_rr *icmp_packet;

struct orig_node *orig_node;
struct orig_node *orig_node = NULL;
struct neigh_node *neigh_node = NULL;
struct batman_if *batman_if;
size_t packet_len = sizeof(struct icmp_packet);
uint8_t dstaddr[ETH_ALEN];
Expand Down Expand Up @@ -224,17 +225,25 @@ static ssize_t bat_socket_write(struct file *file, const char __user *buff,
orig_node = ((struct orig_node *)hash_find(bat_priv->orig_hash,
compare_orig, choose_orig,
icmp_packet->dst));
rcu_read_unlock();

if (!orig_node)
goto unlock;

if (!orig_node->router)
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();

batman_if = orig_node->router->if_incoming;
memcpy(dstaddr, orig_node->router->addr, ETH_ALEN);

spin_unlock_bh(&bat_priv->orig_hash_lock);

if (!batman_if)
Expand All @@ -247,21 +256,25 @@ static ssize_t bat_socket_write(struct file *file, const char __user *buff,
bat_priv->primary_if->net_dev->dev_addr, ETH_ALEN);

if (packet_len == sizeof(struct icmp_packet_rr))
memcpy(icmp_packet->rr, batman_if->net_dev->dev_addr, ETH_ALEN);

memcpy(icmp_packet->rr,
batman_if->net_dev->dev_addr, ETH_ALEN);

send_skb_packet(skb, batman_if, dstaddr);

goto out;

unlock:
rcu_read_unlock();
spin_unlock_bh(&bat_priv->orig_hash_lock);
dst_unreach:
icmp_packet->msg_type = DESTINATION_UNREACHABLE;
bat_socket_add_packet(socket_client, icmp_packet, packet_len);
free_skb:
kfree_skb(skb);
out:
if (neigh_node)
neigh_node_free_ref(neigh_node);
if (orig_node)
kref_put(&orig_node->refcount, orig_node_free_ref);
return len;
}

Expand Down
26 changes: 8 additions & 18 deletions net/batman-adv/originator.c
Original file line number Diff line number Diff line change
Expand Up @@ -59,28 +59,18 @@ int originator_init(struct bat_priv *bat_priv)
return 0;
}

void neigh_node_free_ref(struct kref *refcount)
{
struct neigh_node *neigh_node;

neigh_node = container_of(refcount, struct neigh_node, refcount);
kfree(neigh_node);
}

static void neigh_node_free_rcu(struct rcu_head *rcu)
{
struct neigh_node *neigh_node;

neigh_node = container_of(rcu, struct neigh_node, rcu);
kref_put(&neigh_node->refcount, neigh_node_free_ref);
kfree(neigh_node);
}

void neigh_node_free_rcu_bond(struct rcu_head *rcu)
void neigh_node_free_ref(struct neigh_node *neigh_node)
{
struct neigh_node *neigh_node;

neigh_node = container_of(rcu, struct neigh_node, rcu_bond);
kref_put(&neigh_node->refcount, neigh_node_free_ref);
if (atomic_dec_and_test(&neigh_node->refcount))
call_rcu(&neigh_node->rcu, neigh_node_free_rcu);
}

struct neigh_node *create_neighbor(struct orig_node *orig_node,
Expand All @@ -104,7 +94,7 @@ struct neigh_node *create_neighbor(struct orig_node *orig_node,
memcpy(neigh_node->addr, neigh, ETH_ALEN);
neigh_node->orig_node = orig_neigh_node;
neigh_node->if_incoming = if_incoming;
kref_init(&neigh_node->refcount);
atomic_set(&neigh_node->refcount, 1);

spin_lock_bh(&orig_node->neigh_list_lock);
hlist_add_head_rcu(&neigh_node->list, &orig_node->neigh_list);
Expand All @@ -126,14 +116,14 @@ void orig_node_free_ref(struct kref *refcount)
list_for_each_entry_safe(neigh_node, tmp_neigh_node,
&orig_node->bond_list, bonding_list) {
list_del_rcu(&neigh_node->bonding_list);
call_rcu(&neigh_node->rcu_bond, neigh_node_free_rcu_bond);
neigh_node_free_ref(neigh_node);
}

/* for all neighbors towards this originator ... */
hlist_for_each_entry_safe(neigh_node, node, node_tmp,
&orig_node->neigh_list, list) {
hlist_del_rcu(&neigh_node->list);
call_rcu(&neigh_node->rcu, neigh_node_free_rcu);
neigh_node_free_ref(neigh_node);
}

spin_unlock_bh(&orig_node->neigh_list_lock);
Expand Down Expand Up @@ -315,7 +305,7 @@ static bool purge_orig_neighbors(struct bat_priv *bat_priv,

hlist_del_rcu(&neigh_node->list);
bonding_candidate_del(orig_node, neigh_node);
call_rcu(&neigh_node->rcu, neigh_node_free_rcu);
neigh_node_free_ref(neigh_node);
} else {
if ((!*best_neigh_node) ||
(neigh_node->tq_avg > (*best_neigh_node)->tq_avg))
Expand Down
3 changes: 1 addition & 2 deletions net/batman-adv/originator.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,12 @@ int originator_init(struct bat_priv *bat_priv);
void originator_free(struct bat_priv *bat_priv);
void purge_orig_ref(struct bat_priv *bat_priv);
void orig_node_free_ref(struct kref *refcount);
void neigh_node_free_rcu_bond(struct rcu_head *rcu);
struct orig_node *get_orig_node(struct bat_priv *bat_priv, uint8_t *addr);
struct neigh_node *create_neighbor(struct orig_node *orig_node,
struct orig_node *orig_neigh_node,
uint8_t *neigh,
struct batman_if *if_incoming);
void neigh_node_free_ref(struct kref *refcount);
void neigh_node_free_ref(struct neigh_node *neigh_node);
int orig_seq_print_text(struct seq_file *seq, void *offset);
int orig_hash_add_if(struct batman_if *batman_if, int max_if_num);
int orig_hash_del_if(struct batman_if *batman_if, int max_if_num);
Expand Down
Loading

0 comments on commit 44524fc

Please sign in to comment.