aboutsummaryrefslogtreecommitdiffstats
path: root/net/sched/cls_route.c
diff options
context:
space:
mode:
authorWANG Cong <xiyou.wangcong@gmail.com>2017-04-19 17:21:21 -0400
committerDavid S. Miller <davem@davemloft.net>2017-04-21 13:58:15 -0400
commit763dbf6328e41de7a55851baf5ee49e367552531 (patch)
tree22138b66b3576d4368ba99c3ea1b9c88fb1bdeaa /net/sched/cls_route.c
parentb1d9fc41aab11f9520b2e0d57ae872e2ec5d6f32 (diff)
net_sched: move the empty tp check from ->destroy() to ->delete()
We could have a race condition where in ->classify() path we dereference tp->root and meanwhile a parallel ->destroy() makes it a NULL. Daniel cured this bug in commit d936377414fa ("net, sched: respect rcu grace period on cls destruction"). This happens when ->destroy() is called for deleting a filter to check if we are the last one in tp, this tp is still linked and visible at that time. The root cause of this problem is the semantic of ->destroy(), it does two things (for non-force case): 1) check if tp is empty 2) if tp is empty we could really destroy it and its caller, if cares, needs to check its return value to see if it is really destroyed. Therefore we can't unlink tp unless we know it is empty. As suggested by Daniel, we could actually move the test logic to ->delete() so that we can safely unlink tp after ->delete() tells us the last one is just deleted and before ->destroy(). Fixes: 1e052be69d04 ("net_sched: destroy proto tp when all filters are gone") Cc: Roi Dayan <roid@mellanox.com> Cc: Daniel Borkmann <daniel@iogearbox.net> Cc: John Fastabend <john.fastabend@gmail.com> Cc: Jamal Hadi Salim <jhs@mojatatu.com> Signed-off-by: Cong Wang <xiyou.wangcong@gmail.com> Acked-by: Daniel Borkmann <daniel@iogearbox.net> Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'net/sched/cls_route.c')
-rw-r--r--net/sched/cls_route.c29
1 files changed, 15 insertions, 14 deletions
diff --git a/net/sched/cls_route.c b/net/sched/cls_route.c
index a371075c1d7a..f4d687e04240 100644
--- a/net/sched/cls_route.c
+++ b/net/sched/cls_route.c
@@ -276,20 +276,13 @@ static void route4_delete_filter(struct rcu_head *head)
276 kfree(f); 276 kfree(f);
277} 277}
278 278
279static bool route4_destroy(struct tcf_proto *tp, bool force) 279static void route4_destroy(struct tcf_proto *tp)
280{ 280{
281 struct route4_head *head = rtnl_dereference(tp->root); 281 struct route4_head *head = rtnl_dereference(tp->root);
282 int h1, h2; 282 int h1, h2;
283 283
284 if (head == NULL) 284 if (head == NULL)
285 return true; 285 return;
286
287 if (!force) {
288 for (h1 = 0; h1 <= 256; h1++) {
289 if (rcu_access_pointer(head->table[h1]))
290 return false;
291 }
292 }
293 286
294 for (h1 = 0; h1 <= 256; h1++) { 287 for (h1 = 0; h1 <= 256; h1++) {
295 struct route4_bucket *b; 288 struct route4_bucket *b;
@@ -314,10 +307,9 @@ static bool route4_destroy(struct tcf_proto *tp, bool force)
314 } 307 }
315 RCU_INIT_POINTER(tp->root, NULL); 308 RCU_INIT_POINTER(tp->root, NULL);
316 kfree_rcu(head, rcu); 309 kfree_rcu(head, rcu);
317 return true;
318} 310}
319 311
320static int route4_delete(struct tcf_proto *tp, unsigned long arg) 312static int route4_delete(struct tcf_proto *tp, unsigned long arg, bool *last)
321{ 313{
322 struct route4_head *head = rtnl_dereference(tp->root); 314 struct route4_head *head = rtnl_dereference(tp->root);
323 struct route4_filter *f = (struct route4_filter *)arg; 315 struct route4_filter *f = (struct route4_filter *)arg;
@@ -325,7 +317,7 @@ static int route4_delete(struct tcf_proto *tp, unsigned long arg)
325 struct route4_filter *nf; 317 struct route4_filter *nf;
326 struct route4_bucket *b; 318 struct route4_bucket *b;
327 unsigned int h = 0; 319 unsigned int h = 0;
328 int i; 320 int i, h1;
329 321
330 if (!head || !f) 322 if (!head || !f)
331 return -EINVAL; 323 return -EINVAL;
@@ -356,16 +348,25 @@ static int route4_delete(struct tcf_proto *tp, unsigned long arg)
356 348
357 rt = rtnl_dereference(b->ht[i]); 349 rt = rtnl_dereference(b->ht[i]);
358 if (rt) 350 if (rt)
359 return 0; 351 goto out;
360 } 352 }
361 353
362 /* OK, session has no flows */ 354 /* OK, session has no flows */
363 RCU_INIT_POINTER(head->table[to_hash(h)], NULL); 355 RCU_INIT_POINTER(head->table[to_hash(h)], NULL);
364 kfree_rcu(b, rcu); 356 kfree_rcu(b, rcu);
357 break;
358 }
359 }
365 360
366 return 0; 361out:
362 *last = true;
363 for (h1 = 0; h1 <= 256; h1++) {
364 if (rcu_access_pointer(head->table[h1])) {
365 *last = false;
366 break;
367 } 367 }
368 } 368 }
369
369 return 0; 370 return 0;
370} 371}
371 372