| From foo@baz Thu Feb 27 20:11:26 PST 2014 |
| From: Antonio Quartulli <antonio@open-mesh.com> |
| Date: Wed, 29 Jan 2014 11:25:12 +0100 |
| Subject: batman-adv: avoid potential race condition when adding a new neighbour |
| |
| From: Antonio Quartulli <antonio@open-mesh.com> |
| |
| [ Upstream commit 08bf0ed29c7ded45c477d08618220dd200c3524a ] |
| |
| When adding a new neighbour it is important to atomically |
| perform the following: |
| - check if the neighbour already exists |
| - append the neighbour to the proper list |
| |
| If the two operations are not performed in an atomic context |
| it is possible that two concurrent insertions add the same |
| neighbour twice. |
| |
| Signed-off-by: Antonio Quartulli <antonio@open-mesh.com> |
| Signed-off-by: Marek Lindner <mareklindner@neomailbox.ch> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| --- |
| net/batman-adv/bat_iv_ogm.c | 22 ++++++++++++++++------ |
| net/batman-adv/originator.c | 36 ++++++++++++++++++++++++++++++++++++ |
| net/batman-adv/originator.h | 4 ++++ |
| 3 files changed, 56 insertions(+), 6 deletions(-) |
| |
| --- a/net/batman-adv/bat_iv_ogm.c |
| +++ b/net/batman-adv/bat_iv_ogm.c |
| @@ -268,7 +268,7 @@ batadv_iv_ogm_neigh_new(struct batadv_ha |
| struct batadv_orig_node *orig_neigh) |
| { |
| struct batadv_priv *bat_priv = netdev_priv(hard_iface->soft_iface); |
| - struct batadv_neigh_node *neigh_node; |
| + struct batadv_neigh_node *neigh_node, *tmp_neigh_node; |
| |
| neigh_node = batadv_neigh_node_new(hard_iface, neigh_addr, orig_node); |
| if (!neigh_node) |
| @@ -276,14 +276,24 @@ batadv_iv_ogm_neigh_new(struct batadv_ha |
| |
| spin_lock_init(&neigh_node->bat_iv.lq_update_lock); |
| |
| - batadv_dbg(BATADV_DBG_BATMAN, bat_priv, |
| - "Creating new neighbor %pM for orig_node %pM on interface %s\n", |
| - neigh_addr, orig_node->orig, hard_iface->net_dev->name); |
| - |
| spin_lock_bh(&orig_node->neigh_list_lock); |
| - hlist_add_head_rcu(&neigh_node->list, &orig_node->neigh_list); |
| + tmp_neigh_node = batadv_neigh_node_get(orig_node, hard_iface, |
| + neigh_addr); |
| + if (!tmp_neigh_node) { |
| + hlist_add_head_rcu(&neigh_node->list, &orig_node->neigh_list); |
| + } else { |
| + kfree(neigh_node); |
| + batadv_hardif_free_ref(hard_iface); |
| + neigh_node = tmp_neigh_node; |
| + } |
| spin_unlock_bh(&orig_node->neigh_list_lock); |
| |
| + if (!tmp_neigh_node) |
| + batadv_dbg(BATADV_DBG_BATMAN, bat_priv, |
| + "Creating new neighbor %pM for orig_node %pM on interface %s\n", |
| + neigh_addr, orig_node->orig, |
| + hard_iface->net_dev->name); |
| + |
| out: |
| return neigh_node; |
| } |
| --- a/net/batman-adv/originator.c |
| +++ b/net/batman-adv/originator.c |
| @@ -511,6 +511,42 @@ void batadv_purge_orig_ref(struct batadv |
| _batadv_purge_orig(bat_priv); |
| } |
| |
| +/** |
| + * batadv_neigh_node_get - retrieve a neighbour from the list |
| + * @orig_node: originator which the neighbour belongs to |
| + * @hard_iface: the interface where this neighbour is connected to |
| + * @addr: the address of the neighbour |
| + * |
| + * Looks for and possibly returns a neighbour belonging to this originator list |
| + * which is connected through the provided hard interface. |
| + * Returns NULL if the neighbour is not found. |
| + */ |
| +struct batadv_neigh_node * |
| +batadv_neigh_node_get(const struct batadv_orig_node *orig_node, |
| + const struct batadv_hard_iface *hard_iface, |
| + const uint8_t *addr) |
| +{ |
| + struct batadv_neigh_node *tmp_neigh_node, *res = NULL; |
| + |
| + rcu_read_lock(); |
| + hlist_for_each_entry_rcu(tmp_neigh_node, &orig_node->neigh_list, list) { |
| + if (!batadv_compare_eth(tmp_neigh_node->addr, addr)) |
| + continue; |
| + |
| + if (tmp_neigh_node->if_incoming != hard_iface) |
| + continue; |
| + |
| + if (!atomic_inc_not_zero(&tmp_neigh_node->refcount)) |
| + continue; |
| + |
| + res = tmp_neigh_node; |
| + break; |
| + } |
| + rcu_read_unlock(); |
| + |
| + return res; |
| +} |
| + |
| int batadv_orig_seq_print_text(struct seq_file *seq, void *offset) |
| { |
| struct net_device *net_dev = (struct net_device *)seq->private; |
| --- a/net/batman-adv/originator.h |
| +++ b/net/batman-adv/originator.h |
| @@ -31,6 +31,10 @@ void batadv_orig_node_free_ref_now(struc |
| struct batadv_orig_node *batadv_orig_node_new(struct batadv_priv *bat_priv, |
| const uint8_t *addr); |
| struct batadv_neigh_node * |
| +batadv_neigh_node_get(const struct batadv_orig_node *orig_node, |
| + const struct batadv_hard_iface *hard_iface, |
| + const uint8_t *addr); |
| +struct batadv_neigh_node * |
| batadv_neigh_node_new(struct batadv_hard_iface *hard_iface, |
| const uint8_t *neigh_addr, |
| struct batadv_orig_node *orig_node); |