net/sched: let actions use RCU to access 'goto_chain'
authorDavide Caratti <dcaratti@redhat.com>
Wed, 20 Mar 2019 14:00:16 +0000 (15:00 +0100)
committerDavid S. Miller <davem@davemloft.net>
Thu, 21 Mar 2019 20:26:42 +0000 (13:26 -0700)
use RCU when accessing the action chain, to avoid use after free in the
traffic path when 'goto chain' is replaced on existing TC actions (see
script below). Since the control action is read in the traffic path
without holding the action spinlock, we need to explicitly ensure that
a->goto_chain is not NULL before dereferencing (i.e it's not sufficient
to rely on the value of TC_ACT_GOTO_CHAIN bits). Not doing so caused NULL
dereferences in tcf_action_goto_chain_exec() when the following script:

 # tc chain add dev dd0 chain 42 ingress protocol ip flower \
 > ip_proto udp action pass index 4
 # tc filter add dev dd0 ingress protocol ip flower \
 > ip_proto udp action csum udp goto chain 42 index 66
 # tc chain del dev dd0 chain 42 ingress
 (start UDP traffic towards dd0)
 # tc action replace action csum udp pass index 66

was run repeatedly for several hours.

Suggested-by: Cong Wang <xiyou.wangcong@gmail.com>
Suggested-by: Vlad Buslov <vladbu@mellanox.com>
Signed-off-by: Davide Caratti <dcaratti@redhat.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
include/net/act_api.h
include/net/sch_generic.h
net/sched/act_api.c
net/sched/cls_api.c

index 54fbb49bd08a9d833c049a980d769ceff7458452..c61a1bf4e3de544dd41886e9126b7ff20eb829c3 100644 (file)
@@ -39,7 +39,7 @@ struct tc_action {
        struct gnet_stats_basic_cpu __percpu *cpu_bstats_hw;
        struct gnet_stats_queue __percpu *cpu_qstats;
        struct tc_cookie        __rcu *act_cookie;
-       struct tcf_chain        *goto_chain;
+       struct tcf_chain        __rcu *goto_chain;
 };
 #define tcf_index      common.tcfa_index
 #define tcf_refcnt     common.tcfa_refcnt
index 31284c078d06b6bb426fec08e4776a1e67ada41b..7d1a0483a17ba01b94643fd78acd72095a2e0adb 100644 (file)
@@ -378,6 +378,7 @@ struct tcf_chain {
        bool flushing;
        const struct tcf_proto_ops *tmplt_ops;
        void *tmplt_priv;
+       struct rcu_head rcu;
 };
 
 struct tcf_block {
index fe67b98ac64114a4df97eb9173ba40b341d7c6ae..5a87e271d35a2416b3589888bcfadee0c31b2142 100644 (file)
@@ -31,7 +31,7 @@
 static void tcf_action_goto_chain_exec(const struct tc_action *a,
                                       struct tcf_result *res)
 {
-       const struct tcf_chain *chain = a->goto_chain;
+       const struct tcf_chain *chain = rcu_dereference_bh(a->goto_chain);
 
        res->goto_tp = rcu_dereference_bh(chain->filter_chain);
 }
@@ -91,13 +91,11 @@ end:
 EXPORT_SYMBOL(tcf_action_check_ctrlact);
 
 struct tcf_chain *tcf_action_set_ctrlact(struct tc_action *a, int action,
-                                        struct tcf_chain *newchain)
+                                        struct tcf_chain *goto_chain)
 {
-       struct tcf_chain *oldchain = a->goto_chain;
-
        a->tcfa_action = action;
-       a->goto_chain = newchain;
-       return oldchain;
+       rcu_swap_protected(a->goto_chain, goto_chain, 1);
+       return goto_chain;
 }
 EXPORT_SYMBOL(tcf_action_set_ctrlact);
 
@@ -108,7 +106,7 @@ EXPORT_SYMBOL(tcf_action_set_ctrlact);
  */
 static void free_tcf(struct tc_action *p)
 {
-       struct tcf_chain *chain = p->goto_chain;
+       struct tcf_chain *chain = rcu_dereference_protected(p->goto_chain, 1);
 
        free_percpu(p->cpu_bstats);
        free_percpu(p->cpu_bstats_hw);
@@ -686,6 +684,10 @@ repeat:
                                        return TC_ACT_OK;
                        }
                } else if (TC_ACT_EXT_CMP(ret, TC_ACT_GOTO_CHAIN)) {
+                       if (unlikely(!rcu_access_pointer(a->goto_chain))) {
+                               net_warn_ratelimited("can't go to NULL chain!\n");
+                               return TC_ACT_SHOT;
+                       }
                        tcf_action_goto_chain_exec(a, res);
                }
 
@@ -931,7 +933,7 @@ struct tc_action *tcf_action_init_1(struct net *net, struct tcf_proto *tp,
                module_put(a_o->owner);
 
        if (TC_ACT_EXT_CMP(a->tcfa_action, TC_ACT_GOTO_CHAIN) &&
-           !a->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);
index dc10525e90e7073563f9a3b220fc092323946a19..99ae30c177c76783dae71bf7955f4d4d0bb3b639 100644 (file)
@@ -367,7 +367,7 @@ static void tcf_chain_destroy(struct tcf_chain *chain, bool free_block)
        struct tcf_block *block = chain->block;
 
        mutex_destroy(&chain->filter_chain_lock);
-       kfree(chain);
+       kfree_rcu(chain, rcu);
        if (free_block)
                tcf_block_destroy(block);
 }