Skip to content

Commit 623859a

Browse files
committed
Merge branch 'net-sched-race-fix'
Cong Wang says: ==================== net_sched: close the race between call_rcu() and cleanup_net() This patchset tries to fix the race between call_rcu() and cleanup_net() again. Without holding the netns refcnt the tc_action_net_exit() in netns workqueue could be called before filter destroy works in tc filter workqueue. This patchset moves the netns refcnt from tc actions to tcf_exts, without breaking per-netns tc actions. Patch 1 reverts the previous fix, patch 2 introduces two new API's to help to address the bug and the rest patches switch to the new API's. Please see each patch for details. I was not able to reproduce this bug, but now after adding some delay in filter destroy work I manage to trigger the crash. After this patchset, the crash is not reproducible any more and the debugging printk's show the order is expected too. ==================== Fixes: ddf97cc ("net_sched: add network namespace support for tc actions") Reported-by: Lucas Bates <[email protected]> Cc: Lucas Bates <[email protected]> Cc: Jamal Hadi Salim <[email protected]> Cc: Jiri Pirko <[email protected]> Signed-off-by: Cong Wang <[email protected]> Signed-off-by: David S. Miller <[email protected]>
2 parents 8f56246 + 35c55fc commit 623859a

31 files changed

+198
-63
lines changed

include/net/act_api.h

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
struct tcf_idrinfo {
1515
spinlock_t lock;
1616
struct idr action_idr;
17-
struct net *net;
1817
};
1918

2019
struct tc_action_ops;
@@ -106,15 +105,14 @@ struct tc_action_net {
106105

107106
static inline
108107
int tc_action_net_init(struct tc_action_net *tn,
109-
const struct tc_action_ops *ops, struct net *net)
108+
const struct tc_action_ops *ops)
110109
{
111110
int err = 0;
112111

113112
tn->idrinfo = kmalloc(sizeof(*tn->idrinfo), GFP_KERNEL);
114113
if (!tn->idrinfo)
115114
return -ENOMEM;
116115
tn->ops = ops;
117-
tn->idrinfo->net = net;
118116
spin_lock_init(&tn->idrinfo->lock);
119117
idr_init(&tn->idrinfo->action_idr);
120118
return err;

include/net/pkt_cls.h

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ struct tcf_exts {
9494
__u32 type; /* for backward compat(TCA_OLD_COMPAT) */
9595
int nr_actions;
9696
struct tc_action **actions;
97+
struct net *net;
9798
#endif
9899
/* Map to export classifier specific extension TLV types to the
99100
* generic extensions API. Unsupported extensions must be set to 0.
@@ -107,6 +108,7 @@ static inline int tcf_exts_init(struct tcf_exts *exts, int action, int police)
107108
#ifdef CONFIG_NET_CLS_ACT
108109
exts->type = 0;
109110
exts->nr_actions = 0;
111+
exts->net = NULL;
110112
exts->actions = kcalloc(TCA_ACT_MAX_PRIO, sizeof(struct tc_action *),
111113
GFP_KERNEL);
112114
if (!exts->actions)
@@ -117,6 +119,28 @@ static inline int tcf_exts_init(struct tcf_exts *exts, int action, int police)
117119
return 0;
118120
}
119121

122+
/* Return false if the netns is being destroyed in cleanup_net(). Callers
123+
* need to do cleanup synchronously in this case, otherwise may race with
124+
* tc_action_net_exit(). Return true for other cases.
125+
*/
126+
static inline bool tcf_exts_get_net(struct tcf_exts *exts)
127+
{
128+
#ifdef CONFIG_NET_CLS_ACT
129+
exts->net = maybe_get_net(exts->net);
130+
return exts->net != NULL;
131+
#else
132+
return true;
133+
#endif
134+
}
135+
136+
static inline void tcf_exts_put_net(struct tcf_exts *exts)
137+
{
138+
#ifdef CONFIG_NET_CLS_ACT
139+
if (exts->net)
140+
put_net(exts->net);
141+
#endif
142+
}
143+
120144
static inline void tcf_exts_to_list(const struct tcf_exts *exts,
121145
struct list_head *actions)
122146
{

net/sched/act_api.c

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,6 @@ static void tcf_idr_remove(struct tcf_idrinfo *idrinfo, struct tc_action *p)
7878
spin_lock_bh(&idrinfo->lock);
7979
idr_remove_ext(&idrinfo->action_idr, p->tcfa_index);
8080
spin_unlock_bh(&idrinfo->lock);
81-
put_net(idrinfo->net);
8281
gen_kill_estimator(&p->tcfa_rate_est);
8382
free_tcf(p);
8483
}
@@ -337,7 +336,6 @@ int tcf_idr_create(struct tc_action_net *tn, u32 index, struct nlattr *est,
337336
p->idrinfo = idrinfo;
338337
p->ops = ops;
339338
INIT_LIST_HEAD(&p->list);
340-
get_net(idrinfo->net);
341339
*a = p;
342340
return 0;
343341
}

net/sched/act_bpf.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -398,7 +398,7 @@ static __net_init int bpf_init_net(struct net *net)
398398
{
399399
struct tc_action_net *tn = net_generic(net, bpf_net_id);
400400

401-
return tc_action_net_init(tn, &act_bpf_ops, net);
401+
return tc_action_net_init(tn, &act_bpf_ops);
402402
}
403403

404404
static void __net_exit bpf_exit_net(struct net *net)

net/sched/act_connmark.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ static __net_init int connmark_init_net(struct net *net)
206206
{
207207
struct tc_action_net *tn = net_generic(net, connmark_net_id);
208208

209-
return tc_action_net_init(tn, &act_connmark_ops, net);
209+
return tc_action_net_init(tn, &act_connmark_ops);
210210
}
211211

212212
static void __net_exit connmark_exit_net(struct net *net)

net/sched/act_csum.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -626,7 +626,7 @@ static __net_init int csum_init_net(struct net *net)
626626
{
627627
struct tc_action_net *tn = net_generic(net, csum_net_id);
628628

629-
return tc_action_net_init(tn, &act_csum_ops, net);
629+
return tc_action_net_init(tn, &act_csum_ops);
630630
}
631631

632632
static void __net_exit csum_exit_net(struct net *net)

net/sched/act_gact.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ static __net_init int gact_init_net(struct net *net)
232232
{
233233
struct tc_action_net *tn = net_generic(net, gact_net_id);
234234

235-
return tc_action_net_init(tn, &act_gact_ops, net);
235+
return tc_action_net_init(tn, &act_gact_ops);
236236
}
237237

238238
static void __net_exit gact_exit_net(struct net *net)

net/sched/act_ife.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -818,7 +818,7 @@ static __net_init int ife_init_net(struct net *net)
818818
{
819819
struct tc_action_net *tn = net_generic(net, ife_net_id);
820820

821-
return tc_action_net_init(tn, &act_ife_ops, net);
821+
return tc_action_net_init(tn, &act_ife_ops);
822822
}
823823

824824
static void __net_exit ife_exit_net(struct net *net)

net/sched/act_ipt.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,7 @@ static __net_init int ipt_init_net(struct net *net)
334334
{
335335
struct tc_action_net *tn = net_generic(net, ipt_net_id);
336336

337-
return tc_action_net_init(tn, &act_ipt_ops, net);
337+
return tc_action_net_init(tn, &act_ipt_ops);
338338
}
339339

340340
static void __net_exit ipt_exit_net(struct net *net)
@@ -384,7 +384,7 @@ static __net_init int xt_init_net(struct net *net)
384384
{
385385
struct tc_action_net *tn = net_generic(net, xt_net_id);
386386

387-
return tc_action_net_init(tn, &act_xt_ops, net);
387+
return tc_action_net_init(tn, &act_xt_ops);
388388
}
389389

390390
static void __net_exit xt_exit_net(struct net *net)

net/sched/act_mirred.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -343,7 +343,7 @@ static __net_init int mirred_init_net(struct net *net)
343343
{
344344
struct tc_action_net *tn = net_generic(net, mirred_net_id);
345345

346-
return tc_action_net_init(tn, &act_mirred_ops, net);
346+
return tc_action_net_init(tn, &act_mirred_ops);
347347
}
348348

349349
static void __net_exit mirred_exit_net(struct net *net)

net/sched/act_nat.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,7 @@ static __net_init int nat_init_net(struct net *net)
307307
{
308308
struct tc_action_net *tn = net_generic(net, nat_net_id);
309309

310-
return tc_action_net_init(tn, &act_nat_ops, net);
310+
return tc_action_net_init(tn, &act_nat_ops);
311311
}
312312

313313
static void __net_exit nat_exit_net(struct net *net)

net/sched/act_pedit.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -450,7 +450,7 @@ static __net_init int pedit_init_net(struct net *net)
450450
{
451451
struct tc_action_net *tn = net_generic(net, pedit_net_id);
452452

453-
return tc_action_net_init(tn, &act_pedit_ops, net);
453+
return tc_action_net_init(tn, &act_pedit_ops);
454454
}
455455

456456
static void __net_exit pedit_exit_net(struct net *net)

net/sched/act_police.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,7 @@ static __net_init int police_init_net(struct net *net)
331331
{
332332
struct tc_action_net *tn = net_generic(net, police_net_id);
333333

334-
return tc_action_net_init(tn, &act_police_ops, net);
334+
return tc_action_net_init(tn, &act_police_ops);
335335
}
336336

337337
static void __net_exit police_exit_net(struct net *net)

net/sched/act_sample.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ static __net_init int sample_init_net(struct net *net)
240240
{
241241
struct tc_action_net *tn = net_generic(net, sample_net_id);
242242

243-
return tc_action_net_init(tn, &act_sample_ops, net);
243+
return tc_action_net_init(tn, &act_sample_ops);
244244
}
245245

246246
static void __net_exit sample_exit_net(struct net *net)

net/sched/act_simple.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ static __net_init int simp_init_net(struct net *net)
201201
{
202202
struct tc_action_net *tn = net_generic(net, simp_net_id);
203203

204-
return tc_action_net_init(tn, &act_simp_ops, net);
204+
return tc_action_net_init(tn, &act_simp_ops);
205205
}
206206

207207
static void __net_exit simp_exit_net(struct net *net)

net/sched/act_skbedit.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ static __net_init int skbedit_init_net(struct net *net)
238238
{
239239
struct tc_action_net *tn = net_generic(net, skbedit_net_id);
240240

241-
return tc_action_net_init(tn, &act_skbedit_ops, net);
241+
return tc_action_net_init(tn, &act_skbedit_ops);
242242
}
243243

244244
static void __net_exit skbedit_exit_net(struct net *net)

net/sched/act_skbmod.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,7 @@ static __net_init int skbmod_init_net(struct net *net)
263263
{
264264
struct tc_action_net *tn = net_generic(net, skbmod_net_id);
265265

266-
return tc_action_net_init(tn, &act_skbmod_ops, net);
266+
return tc_action_net_init(tn, &act_skbmod_ops);
267267
}
268268

269269
static void __net_exit skbmod_exit_net(struct net *net)

net/sched/act_tunnel_key.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,7 @@ static __net_init int tunnel_key_init_net(struct net *net)
322322
{
323323
struct tc_action_net *tn = net_generic(net, tunnel_key_net_id);
324324

325-
return tc_action_net_init(tn, &act_tunnel_key_ops, net);
325+
return tc_action_net_init(tn, &act_tunnel_key_ops);
326326
}
327327

328328
static void __net_exit tunnel_key_exit_net(struct net *net)

net/sched/act_vlan.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ static __net_init int vlan_init_net(struct net *net)
269269
{
270270
struct tc_action_net *tn = net_generic(net, vlan_net_id);
271271

272-
return tc_action_net_init(tn, &act_vlan_ops, net);
272+
return tc_action_net_init(tn, &act_vlan_ops);
273273
}
274274

275275
static void __net_exit vlan_exit_net(struct net *net)

net/sched/cls_api.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -927,6 +927,7 @@ int tcf_exts_validate(struct net *net, struct tcf_proto *tp, struct nlattr **tb,
927927
exts->actions[i++] = act;
928928
exts->nr_actions = i;
929929
}
930+
exts->net = net;
930931
}
931932
#else
932933
if ((exts->action && tb[exts->action]) ||

net/sched/cls_basic.c

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -85,16 +85,21 @@ static int basic_init(struct tcf_proto *tp)
8585
return 0;
8686
}
8787

88+
static void __basic_delete_filter(struct basic_filter *f)
89+
{
90+
tcf_exts_destroy(&f->exts);
91+
tcf_em_tree_destroy(&f->ematches);
92+
tcf_exts_put_net(&f->exts);
93+
kfree(f);
94+
}
95+
8896
static void basic_delete_filter_work(struct work_struct *work)
8997
{
9098
struct basic_filter *f = container_of(work, struct basic_filter, work);
9199

92100
rtnl_lock();
93-
tcf_exts_destroy(&f->exts);
94-
tcf_em_tree_destroy(&f->ematches);
101+
__basic_delete_filter(f);
95102
rtnl_unlock();
96-
97-
kfree(f);
98103
}
99104

100105
static void basic_delete_filter(struct rcu_head *head)
@@ -113,7 +118,10 @@ static void basic_destroy(struct tcf_proto *tp)
113118
list_for_each_entry_safe(f, n, &head->flist, link) {
114119
list_del_rcu(&f->link);
115120
tcf_unbind_filter(tp, &f->res);
116-
call_rcu(&f->rcu, basic_delete_filter);
121+
if (tcf_exts_get_net(&f->exts))
122+
call_rcu(&f->rcu, basic_delete_filter);
123+
else
124+
__basic_delete_filter(f);
117125
}
118126
kfree_rcu(head, rcu);
119127
}
@@ -125,6 +133,7 @@ static int basic_delete(struct tcf_proto *tp, void *arg, bool *last)
125133

126134
list_del_rcu(&f->link);
127135
tcf_unbind_filter(tp, &f->res);
136+
tcf_exts_get_net(&f->exts);
128137
call_rcu(&f->rcu, basic_delete_filter);
129138
*last = list_empty(&head->flist);
130139
return 0;
@@ -219,6 +228,7 @@ static int basic_change(struct net *net, struct sk_buff *in_skb,
219228
if (fold) {
220229
list_replace_rcu(&fold->link, &fnew->link);
221230
tcf_unbind_filter(tp, &fold->res);
231+
tcf_exts_get_net(&fold->exts);
222232
call_rcu(&fold->rcu, basic_delete_filter);
223233
} else {
224234
list_add_rcu(&fnew->link, &head->flist);

net/sched/cls_bpf.c

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,7 @@ static int cls_bpf_init(struct tcf_proto *tp)
249249
static void __cls_bpf_delete_prog(struct cls_bpf_prog *prog)
250250
{
251251
tcf_exts_destroy(&prog->exts);
252+
tcf_exts_put_net(&prog->exts);
252253

253254
if (cls_bpf_is_ebpf(prog))
254255
bpf_prog_put(prog->filter);
@@ -282,7 +283,10 @@ static void __cls_bpf_delete(struct tcf_proto *tp, struct cls_bpf_prog *prog)
282283
cls_bpf_stop_offload(tp, prog);
283284
list_del_rcu(&prog->link);
284285
tcf_unbind_filter(tp, &prog->res);
285-
call_rcu(&prog->rcu, cls_bpf_delete_prog_rcu);
286+
if (tcf_exts_get_net(&prog->exts))
287+
call_rcu(&prog->rcu, cls_bpf_delete_prog_rcu);
288+
else
289+
__cls_bpf_delete_prog(prog);
286290
}
287291

288292
static int cls_bpf_delete(struct tcf_proto *tp, void *arg, bool *last)
@@ -516,6 +520,7 @@ static int cls_bpf_change(struct net *net, struct sk_buff *in_skb,
516520
if (oldprog) {
517521
list_replace_rcu(&oldprog->link, &prog->link);
518522
tcf_unbind_filter(tp, &oldprog->res);
523+
tcf_exts_get_net(&oldprog->exts);
519524
call_rcu(&oldprog->rcu, cls_bpf_delete_prog_rcu);
520525
} else {
521526
list_add_rcu(&prog->link, &head->plist);

net/sched/cls_cgroup.c

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,15 +60,21 @@ static const struct nla_policy cgroup_policy[TCA_CGROUP_MAX + 1] = {
6060
[TCA_CGROUP_EMATCHES] = { .type = NLA_NESTED },
6161
};
6262

63+
static void __cls_cgroup_destroy(struct cls_cgroup_head *head)
64+
{
65+
tcf_exts_destroy(&head->exts);
66+
tcf_em_tree_destroy(&head->ematches);
67+
tcf_exts_put_net(&head->exts);
68+
kfree(head);
69+
}
70+
6371
static void cls_cgroup_destroy_work(struct work_struct *work)
6472
{
6573
struct cls_cgroup_head *head = container_of(work,
6674
struct cls_cgroup_head,
6775
work);
6876
rtnl_lock();
69-
tcf_exts_destroy(&head->exts);
70-
tcf_em_tree_destroy(&head->ematches);
71-
kfree(head);
77+
__cls_cgroup_destroy(head);
7278
rtnl_unlock();
7379
}
7480

@@ -124,8 +130,10 @@ static int cls_cgroup_change(struct net *net, struct sk_buff *in_skb,
124130
goto errout;
125131

126132
rcu_assign_pointer(tp->root, new);
127-
if (head)
133+
if (head) {
134+
tcf_exts_get_net(&head->exts);
128135
call_rcu(&head->rcu, cls_cgroup_destroy_rcu);
136+
}
129137
return 0;
130138
errout:
131139
tcf_exts_destroy(&new->exts);
@@ -138,8 +146,12 @@ static void cls_cgroup_destroy(struct tcf_proto *tp)
138146
struct cls_cgroup_head *head = rtnl_dereference(tp->root);
139147

140148
/* Head can still be NULL due to cls_cgroup_init(). */
141-
if (head)
142-
call_rcu(&head->rcu, cls_cgroup_destroy_rcu);
149+
if (head) {
150+
if (tcf_exts_get_net(&head->exts))
151+
call_rcu(&head->rcu, cls_cgroup_destroy_rcu);
152+
else
153+
__cls_cgroup_destroy(head);
154+
}
143155
}
144156

145157
static int cls_cgroup_delete(struct tcf_proto *tp, void *arg, bool *last)

0 commit comments

Comments
 (0)