Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

net/sched: let actions use RCU to access 'goto_chain'

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>

authored by

Davide Caratti and committed by
David S. Miller
ee3bbfe8 fe384e2f

+13 -10
+1 -1
include/net/act_api.h
··· 39 39 struct gnet_stats_basic_cpu __percpu *cpu_bstats_hw; 40 40 struct gnet_stats_queue __percpu *cpu_qstats; 41 41 struct tc_cookie __rcu *act_cookie; 42 - struct tcf_chain *goto_chain; 42 + struct tcf_chain __rcu *goto_chain; 43 43 }; 44 44 #define tcf_index common.tcfa_index 45 45 #define tcf_refcnt common.tcfa_refcnt
+1
include/net/sch_generic.h
··· 378 378 bool flushing; 379 379 const struct tcf_proto_ops *tmplt_ops; 380 380 void *tmplt_priv; 381 + struct rcu_head rcu; 381 382 }; 382 383 383 384 struct tcf_block {
+10 -8
net/sched/act_api.c
··· 31 31 static void tcf_action_goto_chain_exec(const struct tc_action *a, 32 32 struct tcf_result *res) 33 33 { 34 - const struct tcf_chain *chain = a->goto_chain; 34 + const struct tcf_chain *chain = rcu_dereference_bh(a->goto_chain); 35 35 36 36 res->goto_tp = rcu_dereference_bh(chain->filter_chain); 37 37 } ··· 91 91 EXPORT_SYMBOL(tcf_action_check_ctrlact); 92 92 93 93 struct tcf_chain *tcf_action_set_ctrlact(struct tc_action *a, int action, 94 - struct tcf_chain *newchain) 94 + struct tcf_chain *goto_chain) 95 95 { 96 - struct tcf_chain *oldchain = a->goto_chain; 97 - 98 96 a->tcfa_action = action; 99 - a->goto_chain = newchain; 100 - return oldchain; 97 + rcu_swap_protected(a->goto_chain, goto_chain, 1); 98 + return goto_chain; 101 99 } 102 100 EXPORT_SYMBOL(tcf_action_set_ctrlact); 103 101 ··· 106 108 */ 107 109 static void free_tcf(struct tc_action *p) 108 110 { 109 - struct tcf_chain *chain = p->goto_chain; 111 + struct tcf_chain *chain = rcu_dereference_protected(p->goto_chain, 1); 110 112 111 113 free_percpu(p->cpu_bstats); 112 114 free_percpu(p->cpu_bstats_hw); ··· 684 686 return TC_ACT_OK; 685 687 } 686 688 } else if (TC_ACT_EXT_CMP(ret, TC_ACT_GOTO_CHAIN)) { 689 + if (unlikely(!rcu_access_pointer(a->goto_chain))) { 690 + net_warn_ratelimited("can't go to NULL chain!\n"); 691 + return TC_ACT_SHOT; 692 + } 687 693 tcf_action_goto_chain_exec(a, res); 688 694 } 689 695 ··· 933 931 module_put(a_o->owner); 934 932 935 933 if (TC_ACT_EXT_CMP(a->tcfa_action, TC_ACT_GOTO_CHAIN) && 936 - !a->goto_chain) { 934 + !rcu_access_pointer(a->goto_chain)) { 937 935 tcf_action_destroy_1(a, bind); 938 936 NL_SET_ERR_MSG(extack, "can't use goto chain with NULL chain"); 939 937 return ERR_PTR(-EINVAL);
+1 -1
net/sched/cls_api.c
··· 367 367 struct tcf_block *block = chain->block; 368 368 369 369 mutex_destroy(&chain->filter_chain_lock); 370 - kfree(chain); 370 + kfree_rcu(chain, rcu); 371 371 if (free_block) 372 372 tcf_block_destroy(block); 373 373 }