aboutsummaryrefslogtreecommitdiffstats
path: root/net/batman-adv
diff options
context:
space:
mode:
authorMarek Lindner <lindner_marek@yahoo.de>2011-02-10 09:33:49 -0500
committerMarek Lindner <lindner_marek@yahoo.de>2011-03-05 06:50:04 -0500
commit25b6d3c17eaa92ae9700eb8235bc79782613354a (patch)
tree1c9949a6adf8144f77b91e9f3a785d0557525f39 /net/batman-adv
parent44524fcdf6ca19b58c24f7622c4af1d8d8fe59f8 (diff)
batman-adv: Correct rcu refcounting for gw_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')
-rw-r--r--net/batman-adv/gateway_client.c37
-rw-r--r--net/batman-adv/types.h2
2 files changed, 17 insertions, 22 deletions
diff --git a/net/batman-adv/gateway_client.c b/net/batman-adv/gateway_client.c
index 429a013d2e0a..517e001605d9 100644
--- a/net/batman-adv/gateway_client.c
+++ b/net/batman-adv/gateway_client.c
@@ -28,20 +28,18 @@
28#include <linux/udp.h> 28#include <linux/udp.h>
29#include <linux/if_vlan.h> 29#include <linux/if_vlan.h>
30 30
31static void gw_node_free_ref(struct kref *refcount) 31static void gw_node_free_rcu(struct rcu_head *rcu)
32{ 32{
33 struct gw_node *gw_node; 33 struct gw_node *gw_node;
34 34
35 gw_node = container_of(refcount, struct gw_node, refcount); 35 gw_node = container_of(rcu, struct gw_node, rcu);
36 kfree(gw_node); 36 kfree(gw_node);
37} 37}
38 38
39static void gw_node_free_rcu(struct rcu_head *rcu) 39static void gw_node_free_ref(struct gw_node *gw_node)
40{ 40{
41 struct gw_node *gw_node; 41 if (atomic_dec_and_test(&gw_node->refcount))
42 42 call_rcu(&gw_node->rcu, gw_node_free_rcu);
43 gw_node = container_of(rcu, struct gw_node, rcu);
44 kref_put(&gw_node->refcount, gw_node_free_ref);
45} 43}
46 44
47void *gw_get_selected(struct bat_priv *bat_priv) 45void *gw_get_selected(struct bat_priv *bat_priv)
@@ -61,25 +59,26 @@ void gw_deselect(struct bat_priv *bat_priv)
61 bat_priv->curr_gw = NULL; 59 bat_priv->curr_gw = NULL;
62 60
63 if (gw_node) 61 if (gw_node)
64 kref_put(&gw_node->refcount, gw_node_free_ref); 62 gw_node_free_ref(gw_node);
65} 63}
66 64
67static struct gw_node *gw_select(struct bat_priv *bat_priv, 65static void gw_select(struct bat_priv *bat_priv, struct gw_node *new_gw_node)
68 struct gw_node *new_gw_node)
69{ 66{
70 struct gw_node *curr_gw_node = bat_priv->curr_gw; 67 struct gw_node *curr_gw_node = bat_priv->curr_gw;
71 68
72 if (new_gw_node) 69 if (new_gw_node && !atomic_inc_not_zero(&new_gw_node->refcount))
73 kref_get(&new_gw_node->refcount); 70 new_gw_node = NULL;
74 71
75 bat_priv->curr_gw = new_gw_node; 72 bat_priv->curr_gw = new_gw_node;
76 return curr_gw_node; 73
74 if (curr_gw_node)
75 gw_node_free_ref(curr_gw_node);
77} 76}
78 77
79void gw_election(struct bat_priv *bat_priv) 78void gw_election(struct bat_priv *bat_priv)
80{ 79{
81 struct hlist_node *node; 80 struct hlist_node *node;
82 struct gw_node *gw_node, *curr_gw_tmp = NULL, *old_gw_node = NULL; 81 struct gw_node *gw_node, *curr_gw_tmp = NULL;
83 uint8_t max_tq = 0; 82 uint8_t max_tq = 0;
84 uint32_t max_gw_factor = 0, tmp_gw_factor = 0; 83 uint32_t max_gw_factor = 0, tmp_gw_factor = 0;
85 int down, up; 84 int down, up;
@@ -174,14 +173,10 @@ void gw_election(struct bat_priv *bat_priv)
174 curr_gw_tmp->orig_node->gw_flags, 173 curr_gw_tmp->orig_node->gw_flags,
175 curr_gw_tmp->orig_node->router->tq_avg); 174 curr_gw_tmp->orig_node->router->tq_avg);
176 175
177 old_gw_node = gw_select(bat_priv, curr_gw_tmp); 176 gw_select(bat_priv, curr_gw_tmp);
178 } 177 }
179 178
180 rcu_read_unlock(); 179 rcu_read_unlock();
181
182 /* the kfree() has to be outside of the rcu lock */
183 if (old_gw_node)
184 kref_put(&old_gw_node->refcount, gw_node_free_ref);
185} 180}
186 181
187void gw_check_election(struct bat_priv *bat_priv, struct orig_node *orig_node) 182void gw_check_election(struct bat_priv *bat_priv, struct orig_node *orig_node)
@@ -242,7 +237,7 @@ static void gw_node_add(struct bat_priv *bat_priv,
242 memset(gw_node, 0, sizeof(struct gw_node)); 237 memset(gw_node, 0, sizeof(struct gw_node));
243 INIT_HLIST_NODE(&gw_node->list); 238 INIT_HLIST_NODE(&gw_node->list);
244 gw_node->orig_node = orig_node; 239 gw_node->orig_node = orig_node;
245 kref_init(&gw_node->refcount); 240 atomic_set(&gw_node->refcount, 1);
246 241
247 spin_lock_bh(&bat_priv->gw_list_lock); 242 spin_lock_bh(&bat_priv->gw_list_lock);
248 hlist_add_head_rcu(&gw_node->list, &bat_priv->gw_list); 243 hlist_add_head_rcu(&gw_node->list, &bat_priv->gw_list);
@@ -325,7 +320,7 @@ void gw_node_purge(struct bat_priv *bat_priv)
325 gw_deselect(bat_priv); 320 gw_deselect(bat_priv);
326 321
327 hlist_del_rcu(&gw_node->list); 322 hlist_del_rcu(&gw_node->list);
328 call_rcu(&gw_node->rcu, gw_node_free_rcu); 323 gw_node_free_ref(gw_node);
329 } 324 }
330 325
331 326
diff --git a/net/batman-adv/types.h b/net/batman-adv/types.h
index 084604a6dcf1..cfbeb45cd9b3 100644
--- a/net/batman-adv/types.h
+++ b/net/batman-adv/types.h
@@ -98,7 +98,7 @@ struct gw_node {
98 struct hlist_node list; 98 struct hlist_node list;
99 struct orig_node *orig_node; 99 struct orig_node *orig_node;
100 unsigned long deleted; 100 unsigned long deleted;
101 struct kref refcount; 101 atomic_t refcount;
102 struct rcu_head rcu; 102 struct rcu_head rcu;
103}; 103};
104 104