Skip to content

Commit

Permalink
Merge branch 'net-sched-race-fix'
Browse files Browse the repository at this point in the history
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 <lucasb@mojatatu.com>
Cc: Lucas Bates <lucasb@mojatatu.com>
Cc: Jamal Hadi Salim <jhs@mojatatu.com>
Cc: Jiri Pirko <jiri@resnulli.us>
Signed-off-by: Cong Wang <xiyou.wangcong@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
  • Loading branch information
David S. Miller committed Nov 9, 2017
2 parents 8f56246 + 35c55fc commit 623859a
Show file tree
Hide file tree
Showing 31 changed files with 198 additions and 63 deletions.
4 changes: 1 addition & 3 deletions include/net/act_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
struct tcf_idrinfo {
spinlock_t lock;
struct idr action_idr;
struct net *net;
};

struct tc_action_ops;
Expand Down Expand Up @@ -106,15 +105,14 @@ struct tc_action_net {

static inline
int tc_action_net_init(struct tc_action_net *tn,
const struct tc_action_ops *ops, struct net *net)
const struct tc_action_ops *ops)
{
int err = 0;

tn->idrinfo = kmalloc(sizeof(*tn->idrinfo), GFP_KERNEL);
if (!tn->idrinfo)
return -ENOMEM;
tn->ops = ops;
tn->idrinfo->net = net;
spin_lock_init(&tn->idrinfo->lock);
idr_init(&tn->idrinfo->action_idr);
return err;
Expand Down
24 changes: 24 additions & 0 deletions include/net/pkt_cls.h
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ struct tcf_exts {
__u32 type; /* for backward compat(TCA_OLD_COMPAT) */
int nr_actions;
struct tc_action **actions;
struct net *net;
#endif
/* Map to export classifier specific extension TLV types to the
* generic extensions API. Unsupported extensions must be set to 0.
Expand All @@ -107,6 +108,7 @@ static inline int tcf_exts_init(struct tcf_exts *exts, int action, int police)
#ifdef CONFIG_NET_CLS_ACT
exts->type = 0;
exts->nr_actions = 0;
exts->net = NULL;
exts->actions = kcalloc(TCA_ACT_MAX_PRIO, sizeof(struct tc_action *),
GFP_KERNEL);
if (!exts->actions)
Expand All @@ -117,6 +119,28 @@ static inline int tcf_exts_init(struct tcf_exts *exts, int action, int police)
return 0;
}

/* Return false if the netns is being destroyed in cleanup_net(). Callers
* need to do cleanup synchronously in this case, otherwise may race with
* tc_action_net_exit(). Return true for other cases.
*/
static inline bool tcf_exts_get_net(struct tcf_exts *exts)
{
#ifdef CONFIG_NET_CLS_ACT
exts->net = maybe_get_net(exts->net);
return exts->net != NULL;
#else
return true;
#endif
}

static inline void tcf_exts_put_net(struct tcf_exts *exts)
{
#ifdef CONFIG_NET_CLS_ACT
if (exts->net)
put_net(exts->net);
#endif
}

static inline void tcf_exts_to_list(const struct tcf_exts *exts,
struct list_head *actions)
{
Expand Down
2 changes: 0 additions & 2 deletions net/sched/act_api.c
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,6 @@ static void tcf_idr_remove(struct tcf_idrinfo *idrinfo, struct tc_action *p)
spin_lock_bh(&idrinfo->lock);
idr_remove_ext(&idrinfo->action_idr, p->tcfa_index);
spin_unlock_bh(&idrinfo->lock);
put_net(idrinfo->net);
gen_kill_estimator(&p->tcfa_rate_est);
free_tcf(p);
}
Expand Down Expand Up @@ -337,7 +336,6 @@ int tcf_idr_create(struct tc_action_net *tn, u32 index, struct nlattr *est,
p->idrinfo = idrinfo;
p->ops = ops;
INIT_LIST_HEAD(&p->list);
get_net(idrinfo->net);
*a = p;
return 0;
}
Expand Down
2 changes: 1 addition & 1 deletion net/sched/act_bpf.c
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,7 @@ static __net_init int bpf_init_net(struct net *net)
{
struct tc_action_net *tn = net_generic(net, bpf_net_id);

return tc_action_net_init(tn, &act_bpf_ops, net);
return tc_action_net_init(tn, &act_bpf_ops);
}

static void __net_exit bpf_exit_net(struct net *net)
Expand Down
2 changes: 1 addition & 1 deletion net/sched/act_connmark.c
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ static __net_init int connmark_init_net(struct net *net)
{
struct tc_action_net *tn = net_generic(net, connmark_net_id);

return tc_action_net_init(tn, &act_connmark_ops, net);
return tc_action_net_init(tn, &act_connmark_ops);
}

static void __net_exit connmark_exit_net(struct net *net)
Expand Down
2 changes: 1 addition & 1 deletion net/sched/act_csum.c
Original file line number Diff line number Diff line change
Expand Up @@ -626,7 +626,7 @@ static __net_init int csum_init_net(struct net *net)
{
struct tc_action_net *tn = net_generic(net, csum_net_id);

return tc_action_net_init(tn, &act_csum_ops, net);
return tc_action_net_init(tn, &act_csum_ops);
}

static void __net_exit csum_exit_net(struct net *net)
Expand Down
2 changes: 1 addition & 1 deletion net/sched/act_gact.c
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ static __net_init int gact_init_net(struct net *net)
{
struct tc_action_net *tn = net_generic(net, gact_net_id);

return tc_action_net_init(tn, &act_gact_ops, net);
return tc_action_net_init(tn, &act_gact_ops);
}

static void __net_exit gact_exit_net(struct net *net)
Expand Down
2 changes: 1 addition & 1 deletion net/sched/act_ife.c
Original file line number Diff line number Diff line change
Expand Up @@ -818,7 +818,7 @@ static __net_init int ife_init_net(struct net *net)
{
struct tc_action_net *tn = net_generic(net, ife_net_id);

return tc_action_net_init(tn, &act_ife_ops, net);
return tc_action_net_init(tn, &act_ife_ops);
}

static void __net_exit ife_exit_net(struct net *net)
Expand Down
4 changes: 2 additions & 2 deletions net/sched/act_ipt.c
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,7 @@ static __net_init int ipt_init_net(struct net *net)
{
struct tc_action_net *tn = net_generic(net, ipt_net_id);

return tc_action_net_init(tn, &act_ipt_ops, net);
return tc_action_net_init(tn, &act_ipt_ops);
}

static void __net_exit ipt_exit_net(struct net *net)
Expand Down Expand Up @@ -384,7 +384,7 @@ static __net_init int xt_init_net(struct net *net)
{
struct tc_action_net *tn = net_generic(net, xt_net_id);

return tc_action_net_init(tn, &act_xt_ops, net);
return tc_action_net_init(tn, &act_xt_ops);
}

static void __net_exit xt_exit_net(struct net *net)
Expand Down
2 changes: 1 addition & 1 deletion net/sched/act_mirred.c
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,7 @@ static __net_init int mirred_init_net(struct net *net)
{
struct tc_action_net *tn = net_generic(net, mirred_net_id);

return tc_action_net_init(tn, &act_mirred_ops, net);
return tc_action_net_init(tn, &act_mirred_ops);
}

static void __net_exit mirred_exit_net(struct net *net)
Expand Down
2 changes: 1 addition & 1 deletion net/sched/act_nat.c
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,7 @@ static __net_init int nat_init_net(struct net *net)
{
struct tc_action_net *tn = net_generic(net, nat_net_id);

return tc_action_net_init(tn, &act_nat_ops, net);
return tc_action_net_init(tn, &act_nat_ops);
}

static void __net_exit nat_exit_net(struct net *net)
Expand Down
2 changes: 1 addition & 1 deletion net/sched/act_pedit.c
Original file line number Diff line number Diff line change
Expand Up @@ -450,7 +450,7 @@ static __net_init int pedit_init_net(struct net *net)
{
struct tc_action_net *tn = net_generic(net, pedit_net_id);

return tc_action_net_init(tn, &act_pedit_ops, net);
return tc_action_net_init(tn, &act_pedit_ops);
}

static void __net_exit pedit_exit_net(struct net *net)
Expand Down
2 changes: 1 addition & 1 deletion net/sched/act_police.c
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,7 @@ static __net_init int police_init_net(struct net *net)
{
struct tc_action_net *tn = net_generic(net, police_net_id);

return tc_action_net_init(tn, &act_police_ops, net);
return tc_action_net_init(tn, &act_police_ops);
}

static void __net_exit police_exit_net(struct net *net)
Expand Down
2 changes: 1 addition & 1 deletion net/sched/act_sample.c
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ static __net_init int sample_init_net(struct net *net)
{
struct tc_action_net *tn = net_generic(net, sample_net_id);

return tc_action_net_init(tn, &act_sample_ops, net);
return tc_action_net_init(tn, &act_sample_ops);
}

static void __net_exit sample_exit_net(struct net *net)
Expand Down
2 changes: 1 addition & 1 deletion net/sched/act_simple.c
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ static __net_init int simp_init_net(struct net *net)
{
struct tc_action_net *tn = net_generic(net, simp_net_id);

return tc_action_net_init(tn, &act_simp_ops, net);
return tc_action_net_init(tn, &act_simp_ops);
}

static void __net_exit simp_exit_net(struct net *net)
Expand Down
2 changes: 1 addition & 1 deletion net/sched/act_skbedit.c
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ static __net_init int skbedit_init_net(struct net *net)
{
struct tc_action_net *tn = net_generic(net, skbedit_net_id);

return tc_action_net_init(tn, &act_skbedit_ops, net);
return tc_action_net_init(tn, &act_skbedit_ops);
}

static void __net_exit skbedit_exit_net(struct net *net)
Expand Down
2 changes: 1 addition & 1 deletion net/sched/act_skbmod.c
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ static __net_init int skbmod_init_net(struct net *net)
{
struct tc_action_net *tn = net_generic(net, skbmod_net_id);

return tc_action_net_init(tn, &act_skbmod_ops, net);
return tc_action_net_init(tn, &act_skbmod_ops);
}

static void __net_exit skbmod_exit_net(struct net *net)
Expand Down
2 changes: 1 addition & 1 deletion net/sched/act_tunnel_key.c
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,7 @@ static __net_init int tunnel_key_init_net(struct net *net)
{
struct tc_action_net *tn = net_generic(net, tunnel_key_net_id);

return tc_action_net_init(tn, &act_tunnel_key_ops, net);
return tc_action_net_init(tn, &act_tunnel_key_ops);
}

static void __net_exit tunnel_key_exit_net(struct net *net)
Expand Down
2 changes: 1 addition & 1 deletion net/sched/act_vlan.c
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ static __net_init int vlan_init_net(struct net *net)
{
struct tc_action_net *tn = net_generic(net, vlan_net_id);

return tc_action_net_init(tn, &act_vlan_ops, net);
return tc_action_net_init(tn, &act_vlan_ops);
}

static void __net_exit vlan_exit_net(struct net *net)
Expand Down
1 change: 1 addition & 0 deletions net/sched/cls_api.c
Original file line number Diff line number Diff line change
Expand Up @@ -927,6 +927,7 @@ int tcf_exts_validate(struct net *net, struct tcf_proto *tp, struct nlattr **tb,
exts->actions[i++] = act;
exts->nr_actions = i;
}
exts->net = net;
}
#else
if ((exts->action && tb[exts->action]) ||
Expand Down
20 changes: 15 additions & 5 deletions net/sched/cls_basic.c
Original file line number Diff line number Diff line change
Expand Up @@ -85,16 +85,21 @@ static int basic_init(struct tcf_proto *tp)
return 0;
}

static void __basic_delete_filter(struct basic_filter *f)
{
tcf_exts_destroy(&f->exts);
tcf_em_tree_destroy(&f->ematches);
tcf_exts_put_net(&f->exts);
kfree(f);
}

static void basic_delete_filter_work(struct work_struct *work)
{
struct basic_filter *f = container_of(work, struct basic_filter, work);

rtnl_lock();
tcf_exts_destroy(&f->exts);
tcf_em_tree_destroy(&f->ematches);
__basic_delete_filter(f);
rtnl_unlock();

kfree(f);
}

static void basic_delete_filter(struct rcu_head *head)
Expand All @@ -113,7 +118,10 @@ static void basic_destroy(struct tcf_proto *tp)
list_for_each_entry_safe(f, n, &head->flist, link) {
list_del_rcu(&f->link);
tcf_unbind_filter(tp, &f->res);
call_rcu(&f->rcu, basic_delete_filter);
if (tcf_exts_get_net(&f->exts))
call_rcu(&f->rcu, basic_delete_filter);
else
__basic_delete_filter(f);
}
kfree_rcu(head, rcu);
}
Expand All @@ -125,6 +133,7 @@ static int basic_delete(struct tcf_proto *tp, void *arg, bool *last)

list_del_rcu(&f->link);
tcf_unbind_filter(tp, &f->res);
tcf_exts_get_net(&f->exts);
call_rcu(&f->rcu, basic_delete_filter);
*last = list_empty(&head->flist);
return 0;
Expand Down Expand Up @@ -219,6 +228,7 @@ static int basic_change(struct net *net, struct sk_buff *in_skb,
if (fold) {
list_replace_rcu(&fold->link, &fnew->link);
tcf_unbind_filter(tp, &fold->res);
tcf_exts_get_net(&fold->exts);
call_rcu(&fold->rcu, basic_delete_filter);
} else {
list_add_rcu(&fnew->link, &head->flist);
Expand Down
7 changes: 6 additions & 1 deletion net/sched/cls_bpf.c
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@ static int cls_bpf_init(struct tcf_proto *tp)
static void __cls_bpf_delete_prog(struct cls_bpf_prog *prog)
{
tcf_exts_destroy(&prog->exts);
tcf_exts_put_net(&prog->exts);

if (cls_bpf_is_ebpf(prog))
bpf_prog_put(prog->filter);
Expand Down Expand Up @@ -282,7 +283,10 @@ static void __cls_bpf_delete(struct tcf_proto *tp, struct cls_bpf_prog *prog)
cls_bpf_stop_offload(tp, prog);
list_del_rcu(&prog->link);
tcf_unbind_filter(tp, &prog->res);
call_rcu(&prog->rcu, cls_bpf_delete_prog_rcu);
if (tcf_exts_get_net(&prog->exts))
call_rcu(&prog->rcu, cls_bpf_delete_prog_rcu);
else
__cls_bpf_delete_prog(prog);
}

static int cls_bpf_delete(struct tcf_proto *tp, void *arg, bool *last)
Expand Down Expand Up @@ -516,6 +520,7 @@ static int cls_bpf_change(struct net *net, struct sk_buff *in_skb,
if (oldprog) {
list_replace_rcu(&oldprog->link, &prog->link);
tcf_unbind_filter(tp, &oldprog->res);
tcf_exts_get_net(&oldprog->exts);
call_rcu(&oldprog->rcu, cls_bpf_delete_prog_rcu);
} else {
list_add_rcu(&prog->link, &head->plist);
Expand Down
24 changes: 18 additions & 6 deletions net/sched/cls_cgroup.c
Original file line number Diff line number Diff line change
Expand Up @@ -60,15 +60,21 @@ static const struct nla_policy cgroup_policy[TCA_CGROUP_MAX + 1] = {
[TCA_CGROUP_EMATCHES] = { .type = NLA_NESTED },
};

static void __cls_cgroup_destroy(struct cls_cgroup_head *head)
{
tcf_exts_destroy(&head->exts);
tcf_em_tree_destroy(&head->ematches);
tcf_exts_put_net(&head->exts);
kfree(head);
}

static void cls_cgroup_destroy_work(struct work_struct *work)
{
struct cls_cgroup_head *head = container_of(work,
struct cls_cgroup_head,
work);
rtnl_lock();
tcf_exts_destroy(&head->exts);
tcf_em_tree_destroy(&head->ematches);
kfree(head);
__cls_cgroup_destroy(head);
rtnl_unlock();
}

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

rcu_assign_pointer(tp->root, new);
if (head)
if (head) {
tcf_exts_get_net(&head->exts);
call_rcu(&head->rcu, cls_cgroup_destroy_rcu);
}
return 0;
errout:
tcf_exts_destroy(&new->exts);
Expand All @@ -138,8 +146,12 @@ static void cls_cgroup_destroy(struct tcf_proto *tp)
struct cls_cgroup_head *head = rtnl_dereference(tp->root);

/* Head can still be NULL due to cls_cgroup_init(). */
if (head)
call_rcu(&head->rcu, cls_cgroup_destroy_rcu);
if (head) {
if (tcf_exts_get_net(&head->exts))
call_rcu(&head->rcu, cls_cgroup_destroy_rcu);
else
__cls_cgroup_destroy(head);
}
}

static int cls_cgroup_delete(struct tcf_proto *tp, void *arg, bool *last)
Expand Down
Loading

0 comments on commit 623859a

Please sign in to comment.