--- /dev/null
+From 0fedc63fadf0404a729e73a35349481c8009c02f Mon Sep 17 00:00:00 2001
+From: Cong Wang <xiyou.wangcong@gmail.com>
+Date: Tue, 22 Sep 2020 20:56:24 -0700
+Subject: net_sched: commit action insertions together
+
+From: Cong Wang <xiyou.wangcong@gmail.com>
+
+commit 0fedc63fadf0404a729e73a35349481c8009c02f upstream.
+
+syzbot is able to trigger a failure case inside the loop in
+tcf_action_init(), and when this happens we clean up with
+tcf_action_destroy(). But, as these actions are already inserted
+into the global IDR, other parallel process could free them
+before tcf_action_destroy(), then we will trigger a use-after-free.
+
+Fix this by deferring the insertions even later, after the loop,
+and committing all the insertions in a separate loop, so we will
+never fail in the middle of the insertions any more.
+
+One side effect is that the window between alloction and final
+insertion becomes larger, now it is more likely that the loop in
+tcf_del_walker() sees the placeholder -EBUSY pointer. So we have
+to check for error pointer in tcf_del_walker().
+
+Reported-and-tested-by: syzbot+2287853d392e4b42374a@syzkaller.appspotmail.com
+Fixes: 0190c1d452a9 ("net: sched: atomically check-allocate action")
+Cc: Vlad Buslov <vladbu@mellanox.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>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+
+---
+ net/sched/act_api.c | 32 +++++++++++++++++++++++---------
+ 1 file changed, 23 insertions(+), 9 deletions(-)
+
+--- a/net/sched/act_api.c
++++ b/net/sched/act_api.c
+@@ -303,6 +303,8 @@ static int tcf_del_walker(struct tcf_idr
+
+ mutex_lock(&idrinfo->lock);
+ idr_for_each_entry_ul(idr, p, tmp, id) {
++ if (IS_ERR(p))
++ continue;
+ ret = tcf_idr_release_unsafe(p);
+ if (ret == ACT_P_DELETED) {
+ module_put(ops->owner);
+@@ -828,14 +830,24 @@ static const struct nla_policy tcf_actio
+ [TCA_ACT_OPTIONS] = { .type = NLA_NESTED },
+ };
+
+-static void tcf_idr_insert(struct tc_action *a)
++static void tcf_idr_insert_many(struct tc_action *actions[])
+ {
+- struct tcf_idrinfo *idrinfo = a->idrinfo;
++ int i;
+
+- mutex_lock(&idrinfo->lock);
+- /* Replace ERR_PTR(-EBUSY) allocated by tcf_idr_check_alloc */
+- WARN_ON(!IS_ERR(idr_replace(&idrinfo->action_idr, a, a->tcfa_index)));
+- mutex_unlock(&idrinfo->lock);
++ for (i = 0; i < TCA_ACT_MAX_PRIO; i++) {
++ struct tc_action *a = actions[i];
++ struct tcf_idrinfo *idrinfo;
++
++ if (!a)
++ continue;
++ idrinfo = a->idrinfo;
++ mutex_lock(&idrinfo->lock);
++ /* Replace ERR_PTR(-EBUSY) allocated by tcf_idr_check_alloc if
++ * it is just created, otherwise this is just a nop.
++ */
++ idr_replace(&idrinfo->action_idr, a, a->tcfa_index);
++ mutex_unlock(&idrinfo->lock);
++ }
+ }
+
+ struct tc_action *tcf_action_init_1(struct net *net, struct tcf_proto *tp,
+@@ -927,9 +939,6 @@ struct tc_action *tcf_action_init_1(stru
+ return ERR_PTR(-EINVAL);
+ }
+
+- if (err == ACT_P_CREATED)
+- tcf_idr_insert(a);
+-
+ if (!name && tb[TCA_ACT_COOKIE])
+ tcf_set_action_cookie(&a->act_cookie, cookie);
+
+@@ -983,6 +992,11 @@ int tcf_action_init(struct net *net, str
+ actions[i - 1] = act;
+ }
+
++ /* We have to commit them all together, because if any error happened in
++ * between, we could not handle the failure gracefully.
++ */
++ tcf_idr_insert_many(actions);
++
+ *attr_size = tcf_action_full_attrs_size(sz);
+ return i - 1;
+
--- /dev/null
+From e49d8c22f1261c43a986a7fdbf677ac309682a07 Mon Sep 17 00:00:00 2001
+From: Cong Wang <xiyou.wangcong@gmail.com>
+Date: Tue, 22 Sep 2020 20:56:23 -0700
+Subject: net_sched: defer tcf_idr_insert() in tcf_action_init_1()
+
+From: Cong Wang <xiyou.wangcong@gmail.com>
+
+commit e49d8c22f1261c43a986a7fdbf677ac309682a07 upstream.
+
+All TC actions call tcf_idr_insert() for new action at the end
+of their ->init(), so we can actually move it to a central place
+in tcf_action_init_1().
+
+And once the action is inserted into the global IDR, other parallel
+process could free it immediately as its refcnt is still 1, so we can
+not fail after this, we need to move it after the goto action
+validation to avoid handling the failure case after insertion.
+
+This is found during code review, is not directly triggered by syzbot.
+And this prepares for the next patch.
+
+Cc: Vlad Buslov <vladbu@mellanox.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>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+
+---
+ include/net/act_api.h | 2 --
+ net/sched/act_api.c | 38 ++++++++++++++++++++------------------
+ net/sched/act_bpf.c | 4 +---
+ net/sched/act_connmark.c | 1 -
+ net/sched/act_csum.c | 3 ---
+ net/sched/act_ct.c | 2 --
+ net/sched/act_ctinfo.c | 3 ---
+ net/sched/act_gact.c | 2 --
+ net/sched/act_ife.c | 3 ---
+ net/sched/act_ipt.c | 2 --
+ net/sched/act_mirred.c | 2 --
+ net/sched/act_mpls.c | 2 --
+ net/sched/act_nat.c | 3 ---
+ net/sched/act_pedit.c | 2 --
+ net/sched/act_police.c | 2 --
+ net/sched/act_sample.c | 2 --
+ net/sched/act_simple.c | 2 --
+ net/sched/act_skbedit.c | 2 --
+ net/sched/act_skbmod.c | 2 --
+ net/sched/act_tunnel_key.c | 3 ---
+ net/sched/act_vlan.c | 2 --
+ 21 files changed, 21 insertions(+), 63 deletions(-)
+
+--- a/include/net/act_api.h
++++ b/include/net/act_api.h
+@@ -156,8 +156,6 @@ int tcf_idr_search(struct tc_action_net
+ int tcf_idr_create(struct tc_action_net *tn, u32 index, struct nlattr *est,
+ struct tc_action **a, const struct tc_action_ops *ops,
+ int bind, bool cpustats);
+-void tcf_idr_insert(struct tc_action_net *tn, struct tc_action *a);
+-
+ void tcf_idr_cleanup(struct tc_action_net *tn, u32 index);
+ int tcf_idr_check_alloc(struct tc_action_net *tn, u32 *index,
+ struct tc_action **a, int bind);
+--- a/net/sched/act_api.c
++++ b/net/sched/act_api.c
+@@ -451,17 +451,6 @@ err1:
+ }
+ EXPORT_SYMBOL(tcf_idr_create);
+
+-void tcf_idr_insert(struct tc_action_net *tn, struct tc_action *a)
+-{
+- struct tcf_idrinfo *idrinfo = tn->idrinfo;
+-
+- mutex_lock(&idrinfo->lock);
+- /* Replace ERR_PTR(-EBUSY) allocated by tcf_idr_check_alloc */
+- WARN_ON(!IS_ERR(idr_replace(&idrinfo->action_idr, a, a->tcfa_index)));
+- mutex_unlock(&idrinfo->lock);
+-}
+-EXPORT_SYMBOL(tcf_idr_insert);
+-
+ /* Cleanup idr index that was allocated but not initialized. */
+
+ void tcf_idr_cleanup(struct tc_action_net *tn, u32 index)
+@@ -839,6 +828,16 @@ static const struct nla_policy tcf_actio
+ [TCA_ACT_OPTIONS] = { .type = NLA_NESTED },
+ };
+
++static void tcf_idr_insert(struct tc_action *a)
++{
++ struct tcf_idrinfo *idrinfo = a->idrinfo;
++
++ mutex_lock(&idrinfo->lock);
++ /* Replace ERR_PTR(-EBUSY) allocated by tcf_idr_check_alloc */
++ WARN_ON(!IS_ERR(idr_replace(&idrinfo->action_idr, a, a->tcfa_index)));
++ mutex_unlock(&idrinfo->lock);
++}
++
+ struct tc_action *tcf_action_init_1(struct net *net, struct tcf_proto *tp,
+ struct nlattr *nla, struct nlattr *est,
+ char *name, int ovr, int bind,
+@@ -921,6 +920,16 @@ struct tc_action *tcf_action_init_1(stru
+ if (err < 0)
+ goto err_mod;
+
++ if (TC_ACT_EXT_CMP(a->tcfa_action, TC_ACT_GOTO_CHAIN) &&
++ !rcu_access_pointer(a->goto_chain)) {
++ tcf_action_destroy_1(a, bind);
++ NL_SET_ERR_MSG(extack, "can't use goto chain with NULL chain");
++ return ERR_PTR(-EINVAL);
++ }
++
++ if (err == ACT_P_CREATED)
++ tcf_idr_insert(a);
++
+ if (!name && tb[TCA_ACT_COOKIE])
+ tcf_set_action_cookie(&a->act_cookie, cookie);
+
+@@ -931,13 +940,6 @@ struct tc_action *tcf_action_init_1(stru
+ if (err != ACT_P_CREATED)
+ module_put(a_o->owner);
+
+- if (TC_ACT_EXT_CMP(a->tcfa_action, TC_ACT_GOTO_CHAIN) &&
+- !rcu_access_pointer(a->goto_chain)) {
+- tcf_action_destroy_1(a, bind);
+- NL_SET_ERR_MSG(extack, "can't use goto chain with NULL chain");
+- return ERR_PTR(-EINVAL);
+- }
+-
+ return a;
+
+ err_mod:
+--- a/net/sched/act_bpf.c
++++ b/net/sched/act_bpf.c
+@@ -361,9 +361,7 @@ static int tcf_bpf_init(struct net *net,
+ if (goto_ch)
+ tcf_chain_put_by_act(goto_ch);
+
+- if (res == ACT_P_CREATED) {
+- tcf_idr_insert(tn, *act);
+- } else {
++ if (res != ACT_P_CREATED) {
+ /* make sure the program being replaced is no longer executing */
+ synchronize_rcu();
+ tcf_bpf_cfg_cleanup(&old);
+--- a/net/sched/act_connmark.c
++++ b/net/sched/act_connmark.c
+@@ -139,7 +139,6 @@ static int tcf_connmark_init(struct net
+ ci->net = net;
+ ci->zone = parm->zone;
+
+- tcf_idr_insert(tn, *a);
+ ret = ACT_P_CREATED;
+ } else if (ret > 0) {
+ ci = to_connmark(*a);
+--- a/net/sched/act_csum.c
++++ b/net/sched/act_csum.c
+@@ -110,9 +110,6 @@ static int tcf_csum_init(struct net *net
+ if (params_new)
+ kfree_rcu(params_new, rcu);
+
+- if (ret == ACT_P_CREATED)
+- tcf_idr_insert(tn, *a);
+-
+ return ret;
+ put_chain:
+ if (goto_ch)
+--- a/net/sched/act_ct.c
++++ b/net/sched/act_ct.c
+@@ -740,8 +740,6 @@ static int tcf_ct_init(struct net *net,
+ tcf_chain_put_by_act(goto_ch);
+ if (params)
+ call_rcu(¶ms->rcu, tcf_ct_params_free);
+- if (res == ACT_P_CREATED)
+- tcf_idr_insert(tn, *a);
+
+ return res;
+
+--- a/net/sched/act_ctinfo.c
++++ b/net/sched/act_ctinfo.c
+@@ -269,9 +269,6 @@ static int tcf_ctinfo_init(struct net *n
+ if (cp_new)
+ kfree_rcu(cp_new, rcu);
+
+- if (ret == ACT_P_CREATED)
+- tcf_idr_insert(tn, *a);
+-
+ return ret;
+
+ put_chain:
+--- a/net/sched/act_gact.c
++++ b/net/sched/act_gact.c
+@@ -139,8 +139,6 @@ static int tcf_gact_init(struct net *net
+ if (goto_ch)
+ tcf_chain_put_by_act(goto_ch);
+
+- if (ret == ACT_P_CREATED)
+- tcf_idr_insert(tn, *a);
+ return ret;
+ release_idr:
+ tcf_idr_release(*a, bind);
+--- a/net/sched/act_ife.c
++++ b/net/sched/act_ife.c
+@@ -626,9 +626,6 @@ static int tcf_ife_init(struct net *net,
+ if (p)
+ kfree_rcu(p, rcu);
+
+- if (ret == ACT_P_CREATED)
+- tcf_idr_insert(tn, *a);
+-
+ return ret;
+ metadata_parse_err:
+ if (goto_ch)
+--- a/net/sched/act_ipt.c
++++ b/net/sched/act_ipt.c
+@@ -189,8 +189,6 @@ static int __tcf_ipt_init(struct net *ne
+ ipt->tcfi_t = t;
+ ipt->tcfi_hook = hook;
+ spin_unlock_bh(&ipt->tcf_lock);
+- if (ret == ACT_P_CREATED)
+- tcf_idr_insert(tn, *a);
+ return ret;
+
+ err3:
+--- a/net/sched/act_mirred.c
++++ b/net/sched/act_mirred.c
+@@ -194,8 +194,6 @@ static int tcf_mirred_init(struct net *n
+ spin_lock(&mirred_list_lock);
+ list_add(&m->tcfm_list, &mirred_list);
+ spin_unlock(&mirred_list_lock);
+-
+- tcf_idr_insert(tn, *a);
+ }
+
+ return ret;
+--- a/net/sched/act_mpls.c
++++ b/net/sched/act_mpls.c
+@@ -273,8 +273,6 @@ static int tcf_mpls_init(struct net *net
+ if (p)
+ kfree_rcu(p, rcu);
+
+- if (ret == ACT_P_CREATED)
+- tcf_idr_insert(tn, *a);
+ return ret;
+ put_chain:
+ if (goto_ch)
+--- a/net/sched/act_nat.c
++++ b/net/sched/act_nat.c
+@@ -93,9 +93,6 @@ static int tcf_nat_init(struct net *net,
+ if (goto_ch)
+ tcf_chain_put_by_act(goto_ch);
+
+- if (ret == ACT_P_CREATED)
+- tcf_idr_insert(tn, *a);
+-
+ return ret;
+ release_idr:
+ tcf_idr_release(*a, bind);
+--- a/net/sched/act_pedit.c
++++ b/net/sched/act_pedit.c
+@@ -237,8 +237,6 @@ static int tcf_pedit_init(struct net *ne
+ spin_unlock_bh(&p->tcf_lock);
+ if (goto_ch)
+ tcf_chain_put_by_act(goto_ch);
+- if (ret == ACT_P_CREATED)
+- tcf_idr_insert(tn, *a);
+ return ret;
+
+ put_chain:
+--- a/net/sched/act_police.c
++++ b/net/sched/act_police.c
+@@ -201,8 +201,6 @@ static int tcf_police_init(struct net *n
+ if (new)
+ kfree_rcu(new, rcu);
+
+- if (ret == ACT_P_CREATED)
+- tcf_idr_insert(tn, *a);
+ return ret;
+
+ failure:
+--- a/net/sched/act_sample.c
++++ b/net/sched/act_sample.c
+@@ -116,8 +116,6 @@ static int tcf_sample_init(struct net *n
+ if (goto_ch)
+ tcf_chain_put_by_act(goto_ch);
+
+- if (ret == ACT_P_CREATED)
+- tcf_idr_insert(tn, *a);
+ return ret;
+ put_chain:
+ if (goto_ch)
+--- a/net/sched/act_simple.c
++++ b/net/sched/act_simple.c
+@@ -156,8 +156,6 @@ static int tcf_simp_init(struct net *net
+ goto release_idr;
+ }
+
+- if (ret == ACT_P_CREATED)
+- tcf_idr_insert(tn, *a);
+ return ret;
+ put_chain:
+ if (goto_ch)
+--- a/net/sched/act_skbedit.c
++++ b/net/sched/act_skbedit.c
+@@ -214,8 +214,6 @@ static int tcf_skbedit_init(struct net *
+ if (goto_ch)
+ tcf_chain_put_by_act(goto_ch);
+
+- if (ret == ACT_P_CREATED)
+- tcf_idr_insert(tn, *a);
+ return ret;
+ put_chain:
+ if (goto_ch)
+--- a/net/sched/act_skbmod.c
++++ b/net/sched/act_skbmod.c
+@@ -190,8 +190,6 @@ static int tcf_skbmod_init(struct net *n
+ if (goto_ch)
+ tcf_chain_put_by_act(goto_ch);
+
+- if (ret == ACT_P_CREATED)
+- tcf_idr_insert(tn, *a);
+ return ret;
+ put_chain:
+ if (goto_ch)
+--- a/net/sched/act_tunnel_key.c
++++ b/net/sched/act_tunnel_key.c
+@@ -392,9 +392,6 @@ static int tunnel_key_init(struct net *n
+ if (goto_ch)
+ tcf_chain_put_by_act(goto_ch);
+
+- if (ret == ACT_P_CREATED)
+- tcf_idr_insert(tn, *a);
+-
+ return ret;
+
+ put_chain:
+--- a/net/sched/act_vlan.c
++++ b/net/sched/act_vlan.c
+@@ -228,8 +228,6 @@ static int tcf_vlan_init(struct net *net
+ if (p)
+ kfree_rcu(p, rcu);
+
+- if (ret == ACT_P_CREATED)
+- tcf_idr_insert(tn, *a);
+ return ret;
+ put_chain:
+ if (goto_ch)