aboutsummaryrefslogtreecommitdiffstats
path: root/net/sched/cls_basic.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_basic.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_basic.c')
-rw-r--r--net/sched/cls_basic.c10
1 files changed, 4 insertions, 6 deletions
diff --git a/net/sched/cls_basic.c b/net/sched/cls_basic.c
index 422414f16b38..c4fd63a068f9 100644
--- a/net/sched/cls_basic.c
+++ b/net/sched/cls_basic.c
@@ -93,30 +93,28 @@ static void basic_delete_filter(struct rcu_head *head)
93 kfree(f); 93 kfree(f);
94} 94}
95 95
96static bool basic_destroy(struct tcf_proto *tp, bool force) 96static void basic_destroy(struct tcf_proto *tp)
97{ 97{
98 struct basic_head *head = rtnl_dereference(tp->root); 98 struct basic_head *head = rtnl_dereference(tp->root);
99 struct basic_filter *f, *n; 99 struct basic_filter *f, *n;
100 100
101 if (!force && !list_empty(&head->flist))
102 return false;
103
104 list_for_each_entry_safe(f, n, &head->flist, link) { 101 list_for_each_entry_safe(f, n, &head->flist, link) {
105 list_del_rcu(&f->link); 102 list_del_rcu(&f->link);
106 tcf_unbind_filter(tp, &f->res); 103 tcf_unbind_filter(tp, &f->res);
107 call_rcu(&f->rcu, basic_delete_filter); 104 call_rcu(&f->rcu, basic_delete_filter);
108 } 105 }
109 kfree_rcu(head, rcu); 106 kfree_rcu(head, rcu);
110 return true;
111} 107}
112 108
113static int basic_delete(struct tcf_proto *tp, unsigned long arg) 109static int basic_delete(struct tcf_proto *tp, unsigned long arg, bool *last)
114{ 110{
111 struct basic_head *head = rtnl_dereference(tp->root);
115 struct basic_filter *f = (struct basic_filter *) arg; 112 struct basic_filter *f = (struct basic_filter *) arg;
116 113
117 list_del_rcu(&f->link); 114 list_del_rcu(&f->link);
118 tcf_unbind_filter(tp, &f->res); 115 tcf_unbind_filter(tp, &f->res);
119 call_rcu(&f->rcu, basic_delete_filter); 116 call_rcu(&f->rcu, basic_delete_filter);
117 *last = list_empty(&head->flist);
120 return 0; 118 return 0;
121} 119}
122 120