diff options
author | John Fastabend <john.fastabend@gmail.com> | 2014-10-06 00:28:52 -0400 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2014-10-06 18:02:33 -0400 |
commit | 18cdb37ebf4c986d9502405cbd16b0ac29770c25 (patch) | |
tree | 2bf659bf5d527447c11845ca06d15d1b69b9ab31 /net | |
parent | 13990f8156862fe945a1a226850a6550c8988a33 (diff) |
net: sched: do not use tcf_proto 'tp' argument from call_rcu
Using the tcf_proto pointer 'tp' from inside the classifiers callback
is not valid because it may have been cleaned up by another call_rcu
occuring on another CPU.
'tp' is currently being used by tcf_unbind_filter() in this patch we
move instances of tcf_unbind_filter outside of the call_rcu() context.
This is safe to do because any running schedulers will either read the
valid class field or it will be zeroed.
And all schedulers today when the class is 0 do a lookup using the
same call used by the tcf_exts_bind(). So even if we have a running
classifier hit the null class pointer it will do a lookup and get
to the same result. This is particularly fragile at the moment because
the only way to verify this is to audit the schedulers call sites.
Reported-by: Cong Wang <xiyou.wangconf@gmail.com>
Signed-off-by: John Fastabend <john.r.fastabend@intel.com>
Acked-by: Cong Wang <cwang@twopensource.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'net')
-rw-r--r-- | net/sched/cls_basic.c | 5 | ||||
-rw-r--r-- | net/sched/cls_bpf.c | 4 | ||||
-rw-r--r-- | net/sched/cls_fw.c | 5 | ||||
-rw-r--r-- | net/sched/cls_route.c | 8 |
4 files changed, 14 insertions, 8 deletions
diff --git a/net/sched/cls_basic.c b/net/sched/cls_basic.c index 90647a8af8ca..cd61280941e5 100644 --- a/net/sched/cls_basic.c +++ b/net/sched/cls_basic.c | |||
@@ -91,9 +91,7 @@ static int basic_init(struct tcf_proto *tp) | |||
91 | static void basic_delete_filter(struct rcu_head *head) | 91 | static void basic_delete_filter(struct rcu_head *head) |
92 | { | 92 | { |
93 | struct basic_filter *f = container_of(head, struct basic_filter, rcu); | 93 | struct basic_filter *f = container_of(head, struct basic_filter, rcu); |
94 | struct tcf_proto *tp = f->tp; | ||
95 | 94 | ||
96 | tcf_unbind_filter(tp, &f->res); | ||
97 | tcf_exts_destroy(&f->exts); | 95 | tcf_exts_destroy(&f->exts); |
98 | tcf_em_tree_destroy(&f->ematches); | 96 | tcf_em_tree_destroy(&f->ematches); |
99 | kfree(f); | 97 | kfree(f); |
@@ -106,6 +104,7 @@ static void basic_destroy(struct tcf_proto *tp) | |||
106 | 104 | ||
107 | list_for_each_entry_safe(f, n, &head->flist, link) { | 105 | list_for_each_entry_safe(f, n, &head->flist, link) { |
108 | list_del_rcu(&f->link); | 106 | list_del_rcu(&f->link); |
107 | tcf_unbind_filter(tp, &f->res); | ||
109 | call_rcu(&f->rcu, basic_delete_filter); | 108 | call_rcu(&f->rcu, basic_delete_filter); |
110 | } | 109 | } |
111 | RCU_INIT_POINTER(tp->root, NULL); | 110 | RCU_INIT_POINTER(tp->root, NULL); |
@@ -120,6 +119,7 @@ static int basic_delete(struct tcf_proto *tp, unsigned long arg) | |||
120 | list_for_each_entry(t, &head->flist, link) | 119 | list_for_each_entry(t, &head->flist, link) |
121 | if (t == f) { | 120 | if (t == f) { |
122 | list_del_rcu(&t->link); | 121 | list_del_rcu(&t->link); |
122 | tcf_unbind_filter(tp, &t->res); | ||
123 | call_rcu(&t->rcu, basic_delete_filter); | 123 | call_rcu(&t->rcu, basic_delete_filter); |
124 | return 0; | 124 | return 0; |
125 | } | 125 | } |
@@ -222,6 +222,7 @@ static int basic_change(struct net *net, struct sk_buff *in_skb, | |||
222 | 222 | ||
223 | if (fold) { | 223 | if (fold) { |
224 | list_replace_rcu(&fold->link, &fnew->link); | 224 | list_replace_rcu(&fold->link, &fnew->link); |
225 | tcf_unbind_filter(tp, &fold->res); | ||
225 | call_rcu(&fold->rcu, basic_delete_filter); | 226 | call_rcu(&fold->rcu, basic_delete_filter); |
226 | } else { | 227 | } else { |
227 | list_add_rcu(&fnew->link, &head->flist); | 228 | list_add_rcu(&fnew->link, &head->flist); |
diff --git a/net/sched/cls_bpf.c b/net/sched/cls_bpf.c index 4318d067b0a0..eed49d1d0878 100644 --- a/net/sched/cls_bpf.c +++ b/net/sched/cls_bpf.c | |||
@@ -92,7 +92,6 @@ static int cls_bpf_init(struct tcf_proto *tp) | |||
92 | 92 | ||
93 | static void cls_bpf_delete_prog(struct tcf_proto *tp, struct cls_bpf_prog *prog) | 93 | static void cls_bpf_delete_prog(struct tcf_proto *tp, struct cls_bpf_prog *prog) |
94 | { | 94 | { |
95 | tcf_unbind_filter(tp, &prog->res); | ||
96 | tcf_exts_destroy(&prog->exts); | 95 | tcf_exts_destroy(&prog->exts); |
97 | 96 | ||
98 | bpf_prog_destroy(prog->filter); | 97 | bpf_prog_destroy(prog->filter); |
@@ -116,6 +115,7 @@ static int cls_bpf_delete(struct tcf_proto *tp, unsigned long arg) | |||
116 | list_for_each_entry(prog, &head->plist, link) { | 115 | list_for_each_entry(prog, &head->plist, link) { |
117 | if (prog == todel) { | 116 | if (prog == todel) { |
118 | list_del_rcu(&prog->link); | 117 | list_del_rcu(&prog->link); |
118 | tcf_unbind_filter(tp, &prog->res); | ||
119 | call_rcu(&prog->rcu, __cls_bpf_delete_prog); | 119 | call_rcu(&prog->rcu, __cls_bpf_delete_prog); |
120 | return 0; | 120 | return 0; |
121 | } | 121 | } |
@@ -131,6 +131,7 @@ static void cls_bpf_destroy(struct tcf_proto *tp) | |||
131 | 131 | ||
132 | list_for_each_entry_safe(prog, tmp, &head->plist, link) { | 132 | list_for_each_entry_safe(prog, tmp, &head->plist, link) { |
133 | list_del_rcu(&prog->link); | 133 | list_del_rcu(&prog->link); |
134 | tcf_unbind_filter(tp, &prog->res); | ||
134 | call_rcu(&prog->rcu, __cls_bpf_delete_prog); | 135 | call_rcu(&prog->rcu, __cls_bpf_delete_prog); |
135 | } | 136 | } |
136 | 137 | ||
@@ -282,6 +283,7 @@ static int cls_bpf_change(struct net *net, struct sk_buff *in_skb, | |||
282 | 283 | ||
283 | if (oldprog) { | 284 | if (oldprog) { |
284 | list_replace_rcu(&prog->link, &oldprog->link); | 285 | list_replace_rcu(&prog->link, &oldprog->link); |
286 | tcf_unbind_filter(tp, &oldprog->res); | ||
285 | call_rcu(&oldprog->rcu, __cls_bpf_delete_prog); | 287 | call_rcu(&oldprog->rcu, __cls_bpf_delete_prog); |
286 | } else { | 288 | } else { |
287 | list_add_rcu(&prog->link, &head->plist); | 289 | list_add_rcu(&prog->link, &head->plist); |
diff --git a/net/sched/cls_fw.c b/net/sched/cls_fw.c index da805aeeb65c..dbfdfd1f1a9f 100644 --- a/net/sched/cls_fw.c +++ b/net/sched/cls_fw.c | |||
@@ -123,9 +123,7 @@ static int fw_init(struct tcf_proto *tp) | |||
123 | static void fw_delete_filter(struct rcu_head *head) | 123 | static void fw_delete_filter(struct rcu_head *head) |
124 | { | 124 | { |
125 | struct fw_filter *f = container_of(head, struct fw_filter, rcu); | 125 | struct fw_filter *f = container_of(head, struct fw_filter, rcu); |
126 | struct tcf_proto *tp = f->tp; | ||
127 | 126 | ||
128 | tcf_unbind_filter(tp, &f->res); | ||
129 | tcf_exts_destroy(&f->exts); | 127 | tcf_exts_destroy(&f->exts); |
130 | kfree(f); | 128 | kfree(f); |
131 | } | 129 | } |
@@ -143,6 +141,7 @@ static void fw_destroy(struct tcf_proto *tp) | |||
143 | while ((f = rtnl_dereference(head->ht[h])) != NULL) { | 141 | while ((f = rtnl_dereference(head->ht[h])) != NULL) { |
144 | RCU_INIT_POINTER(head->ht[h], | 142 | RCU_INIT_POINTER(head->ht[h], |
145 | rtnl_dereference(f->next)); | 143 | rtnl_dereference(f->next)); |
144 | tcf_unbind_filter(tp, &f->res); | ||
146 | call_rcu(&f->rcu, fw_delete_filter); | 145 | call_rcu(&f->rcu, fw_delete_filter); |
147 | } | 146 | } |
148 | } | 147 | } |
@@ -166,6 +165,7 @@ static int fw_delete(struct tcf_proto *tp, unsigned long arg) | |||
166 | fp = &pfp->next, pfp = rtnl_dereference(*fp)) { | 165 | fp = &pfp->next, pfp = rtnl_dereference(*fp)) { |
167 | if (pfp == f) { | 166 | if (pfp == f) { |
168 | RCU_INIT_POINTER(*fp, rtnl_dereference(f->next)); | 167 | RCU_INIT_POINTER(*fp, rtnl_dereference(f->next)); |
168 | tcf_unbind_filter(tp, &f->res); | ||
169 | call_rcu(&f->rcu, fw_delete_filter); | 169 | call_rcu(&f->rcu, fw_delete_filter); |
170 | return 0; | 170 | return 0; |
171 | } | 171 | } |
@@ -280,6 +280,7 @@ static int fw_change(struct net *net, struct sk_buff *in_skb, | |||
280 | 280 | ||
281 | RCU_INIT_POINTER(fnew->next, rtnl_dereference(pfp->next)); | 281 | RCU_INIT_POINTER(fnew->next, rtnl_dereference(pfp->next)); |
282 | rcu_assign_pointer(*fp, fnew); | 282 | rcu_assign_pointer(*fp, fnew); |
283 | tcf_unbind_filter(tp, &f->res); | ||
283 | call_rcu(&f->rcu, fw_delete_filter); | 284 | call_rcu(&f->rcu, fw_delete_filter); |
284 | 285 | ||
285 | *arg = (unsigned long)fnew; | 286 | *arg = (unsigned long)fnew; |
diff --git a/net/sched/cls_route.c b/net/sched/cls_route.c index b665aee661f7..6f22baae0afa 100644 --- a/net/sched/cls_route.c +++ b/net/sched/cls_route.c | |||
@@ -269,9 +269,7 @@ static void | |||
269 | route4_delete_filter(struct rcu_head *head) | 269 | route4_delete_filter(struct rcu_head *head) |
270 | { | 270 | { |
271 | struct route4_filter *f = container_of(head, struct route4_filter, rcu); | 271 | struct route4_filter *f = container_of(head, struct route4_filter, rcu); |
272 | struct tcf_proto *tp = f->tp; | ||
273 | 272 | ||
274 | tcf_unbind_filter(tp, &f->res); | ||
275 | tcf_exts_destroy(&f->exts); | 273 | tcf_exts_destroy(&f->exts); |
276 | kfree(f); | 274 | kfree(f); |
277 | } | 275 | } |
@@ -297,6 +295,7 @@ static void route4_destroy(struct tcf_proto *tp) | |||
297 | 295 | ||
298 | next = rtnl_dereference(f->next); | 296 | next = rtnl_dereference(f->next); |
299 | RCU_INIT_POINTER(b->ht[h2], next); | 297 | RCU_INIT_POINTER(b->ht[h2], next); |
298 | tcf_unbind_filter(tp, &f->res); | ||
300 | call_rcu(&f->rcu, route4_delete_filter); | 299 | call_rcu(&f->rcu, route4_delete_filter); |
301 | } | 300 | } |
302 | } | 301 | } |
@@ -338,6 +337,7 @@ static int route4_delete(struct tcf_proto *tp, unsigned long arg) | |||
338 | route4_reset_fastmap(head); | 337 | route4_reset_fastmap(head); |
339 | 338 | ||
340 | /* Delete it */ | 339 | /* Delete it */ |
340 | tcf_unbind_filter(tp, &f->res); | ||
341 | call_rcu(&f->rcu, route4_delete_filter); | 341 | call_rcu(&f->rcu, route4_delete_filter); |
342 | 342 | ||
343 | /* Strip RTNL protected tree */ | 343 | /* Strip RTNL protected tree */ |
@@ -545,8 +545,10 @@ static int route4_change(struct net *net, struct sk_buff *in_skb, | |||
545 | 545 | ||
546 | route4_reset_fastmap(head); | 546 | route4_reset_fastmap(head); |
547 | *arg = (unsigned long)f; | 547 | *arg = (unsigned long)f; |
548 | if (fold) | 548 | if (fold) { |
549 | tcf_unbind_filter(tp, &fold->res); | ||
549 | call_rcu(&fold->rcu, route4_delete_filter); | 550 | call_rcu(&fold->rcu, route4_delete_filter); |
551 | } | ||
550 | return 0; | 552 | return 0; |
551 | 553 | ||
552 | errout: | 554 | errout: |