Skip to content

Commit 1ea186e

Browse files
committed
Merge branch 'net-sched-validate-the-control-action-with-all-the-other-parameters'
Davide Caratti says: ==================== net/sched: validate the control action with all the other parameters currently, the kernel checks for bad values of the control action in tcf_action_init_1(), after a successful call to the action's init() function. When the control action is 'goto chain', this causes two undesired behaviors: 1. "misconfigured action after replace that causes kernel crash": if users replace a valid TC action with another one having invalid control action, all the new configuration data (including the bad control action) are applied successfully, even if the kernel returned an error. As a consequence, it's possible to trigger a NULL pointer dereference in the traffic path of every TC action (1), replacing the control action with 'goto chain x', when chain <x> doesn't exist. 2. "refcount leak that makes kmemleak complain" when a valid 'goto chain' action is overwritten with another action, the kernel forgets to decrease refcounts in the chain. The above problems can be fixed if we validate the control action in each action's init() function, the same way as we are already doing for all the other configuration parameters. Now that chains can be released after an action is replaced, we need to care about concurrent access of 'goto_chain' pointer: ensure we access it through RCU, like we did with most action-specific configuration parameters. - Patch 1 removes the wrong checks and provides functions that can be used to properly validate control actions in individual actions - Patch 2 to 16 fix individual actions, and add TDC selftest code to verify the correct behavior (2) - Patch 17 and 18 fix concurrent access issues on 'goto_chain', that can be observed after the chain refcount leak is fixed. Changes since v1: - reword the cover letter - condense the extack message in case tc_action_check_ctrlact() is called with invalid parameters. - add tcf_action_set_ctrlact() to avoid code duplication an make the RCU-ification of 'goto_chain' easier. - fix errors in act_ife, act_simple, act_skbedit, and avoid useless 'goto end' in act_connmark, thanks a lot to Vlad Buslov. - avoid dereferencing 'goto_chain' in tcf_gact_goto_chain_index(), so we don't have to care about the grace period there. - let actions respect the grace period when they release chains, thanks to Cong Wang and Vlad Buslov. Changes since RFC: - include a fix for all TC actions - add a selftest for each TC action - squash fix for refcount leaks into a single patch, the first in the series, thanks to Cong Wang - ensure that chain refcount is released without tcfa_lock held, thanks to Vlad Buslov Notes: (1) act_ipt didn't need any fix, as the control action is constantly equal to TC_ACT_OK. (2) the selftest for act_simple fails because userspace tc backend for 'simple' does not parse the control action correctly (and hardcodes it to TC_ACT_PIPE). ==================== Signed-off-by: David S. Miller <[email protected]>
2 parents cd5afa9 + ee3bbfe commit 1ea186e

36 files changed

+749
-121
lines changed

include/net/act_api.h

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ struct tc_action {
3939
struct gnet_stats_basic_cpu __percpu *cpu_bstats_hw;
4040
struct gnet_stats_queue __percpu *cpu_qstats;
4141
struct tc_cookie __rcu *act_cookie;
42-
struct tcf_chain *goto_chain;
42+
struct tcf_chain __rcu *goto_chain;
4343
};
4444
#define tcf_index common.tcfa_index
4545
#define tcf_refcnt common.tcfa_refcnt
@@ -90,7 +90,7 @@ struct tc_action_ops {
9090
int (*lookup)(struct net *net, struct tc_action **a, u32 index);
9191
int (*init)(struct net *net, struct nlattr *nla,
9292
struct nlattr *est, struct tc_action **act, int ovr,
93-
int bind, bool rtnl_held,
93+
int bind, bool rtnl_held, struct tcf_proto *tp,
9494
struct netlink_ext_ack *extack);
9595
int (*walk)(struct net *, struct sk_buff *,
9696
struct netlink_callback *, int,
@@ -181,6 +181,11 @@ int tcf_action_dump_old(struct sk_buff *skb, struct tc_action *a, int, int);
181181
int tcf_action_dump_1(struct sk_buff *skb, struct tc_action *a, int, int);
182182
int tcf_action_copy_stats(struct sk_buff *, struct tc_action *, int);
183183

184+
int tcf_action_check_ctrlact(int action, struct tcf_proto *tp,
185+
struct tcf_chain **handle,
186+
struct netlink_ext_ack *newchain);
187+
struct tcf_chain *tcf_action_set_ctrlact(struct tc_action *a, int action,
188+
struct tcf_chain *newchain);
184189
#endif /* CONFIG_NET_CLS_ACT */
185190

186191
static inline void tcf_action_stats_update(struct tc_action *a, u64 bytes,

include/net/sch_generic.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,7 @@ struct tcf_chain {
378378
bool flushing;
379379
const struct tcf_proto_ops *tmplt_ops;
380380
void *tmplt_priv;
381+
struct rcu_head rcu;
381382
};
382383

383384
struct tcf_block {

include/net/tc_act/tc_gact.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ static inline bool is_tcf_gact_goto_chain(const struct tc_action *a)
5656

5757
static inline u32 tcf_gact_goto_chain_index(const struct tc_action *a)
5858
{
59-
return a->goto_chain->index;
59+
return READ_ONCE(a->tcfa_action) & TC_ACT_EXT_VAL_MASK;
6060
}
6161

6262
#endif /* __NET_TC_GACT_H */

net/sched/act_api.c

Lines changed: 59 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -28,27 +28,10 @@
2828
#include <net/act_api.h>
2929
#include <net/netlink.h>
3030

31-
static int tcf_action_goto_chain_init(struct tc_action *a, struct tcf_proto *tp)
32-
{
33-
u32 chain_index = a->tcfa_action & TC_ACT_EXT_VAL_MASK;
34-
35-
if (!tp)
36-
return -EINVAL;
37-
a->goto_chain = tcf_chain_get_by_act(tp->chain->block, chain_index);
38-
if (!a->goto_chain)
39-
return -ENOMEM;
40-
return 0;
41-
}
42-
43-
static void tcf_action_goto_chain_fini(struct tc_action *a)
44-
{
45-
tcf_chain_put_by_act(a->goto_chain);
46-
}
47-
4831
static void tcf_action_goto_chain_exec(const struct tc_action *a,
4932
struct tcf_result *res)
5033
{
51-
const struct tcf_chain *chain = a->goto_chain;
34+
const struct tcf_chain *chain = rcu_dereference_bh(a->goto_chain);
5235

5336
res->goto_tp = rcu_dereference_bh(chain->filter_chain);
5437
}
@@ -71,20 +54,67 @@ static void tcf_set_action_cookie(struct tc_cookie __rcu **old_cookie,
7154
call_rcu(&old->rcu, tcf_free_cookie_rcu);
7255
}
7356

57+
int tcf_action_check_ctrlact(int action, struct tcf_proto *tp,
58+
struct tcf_chain **newchain,
59+
struct netlink_ext_ack *extack)
60+
{
61+
int opcode = TC_ACT_EXT_OPCODE(action), ret = -EINVAL;
62+
u32 chain_index;
63+
64+
if (!opcode)
65+
ret = action > TC_ACT_VALUE_MAX ? -EINVAL : 0;
66+
else if (opcode <= TC_ACT_EXT_OPCODE_MAX || action == TC_ACT_UNSPEC)
67+
ret = 0;
68+
if (ret) {
69+
NL_SET_ERR_MSG(extack, "invalid control action");
70+
goto end;
71+
}
72+
73+
if (TC_ACT_EXT_CMP(action, TC_ACT_GOTO_CHAIN)) {
74+
chain_index = action & TC_ACT_EXT_VAL_MASK;
75+
if (!tp || !newchain) {
76+
ret = -EINVAL;
77+
NL_SET_ERR_MSG(extack,
78+
"can't goto NULL proto/chain");
79+
goto end;
80+
}
81+
*newchain = tcf_chain_get_by_act(tp->chain->block, chain_index);
82+
if (!*newchain) {
83+
ret = -ENOMEM;
84+
NL_SET_ERR_MSG(extack,
85+
"can't allocate goto_chain");
86+
}
87+
}
88+
end:
89+
return ret;
90+
}
91+
EXPORT_SYMBOL(tcf_action_check_ctrlact);
92+
93+
struct tcf_chain *tcf_action_set_ctrlact(struct tc_action *a, int action,
94+
struct tcf_chain *goto_chain)
95+
{
96+
a->tcfa_action = action;
97+
rcu_swap_protected(a->goto_chain, goto_chain, 1);
98+
return goto_chain;
99+
}
100+
EXPORT_SYMBOL(tcf_action_set_ctrlact);
101+
74102
/* XXX: For standalone actions, we don't need a RCU grace period either, because
75103
* actions are always connected to filters and filters are already destroyed in
76104
* RCU callbacks, so after a RCU grace period actions are already disconnected
77105
* from filters. Readers later can not find us.
78106
*/
79107
static void free_tcf(struct tc_action *p)
80108
{
109+
struct tcf_chain *chain = rcu_dereference_protected(p->goto_chain, 1);
110+
81111
free_percpu(p->cpu_bstats);
82112
free_percpu(p->cpu_bstats_hw);
83113
free_percpu(p->cpu_qstats);
84114

85115
tcf_set_action_cookie(&p->act_cookie, NULL);
86-
if (p->goto_chain)
87-
tcf_action_goto_chain_fini(p);
116+
if (chain)
117+
tcf_chain_put_by_act(chain);
88118

89119
kfree(p);
90120
}
@@ -654,6 +684,10 @@ int tcf_action_exec(struct sk_buff *skb, struct tc_action **actions,
654684
return TC_ACT_OK;
655685
}
656686
} else if (TC_ACT_EXT_CMP(ret, TC_ACT_GOTO_CHAIN)) {
687+
if (unlikely(!rcu_access_pointer(a->goto_chain))) {
688+
net_warn_ratelimited("can't go to NULL chain!\n");
689+
return TC_ACT_SHOT;
690+
}
657691
tcf_action_goto_chain_exec(a, res);
658692
}
659693

@@ -800,15 +834,6 @@ static struct tc_cookie *nla_memdup_cookie(struct nlattr **tb)
800834
return c;
801835
}
802836

803-
static bool tcf_action_valid(int action)
804-
{
805-
int opcode = TC_ACT_EXT_OPCODE(action);
806-
807-
if (!opcode)
808-
return action <= TC_ACT_VALUE_MAX;
809-
return opcode <= TC_ACT_EXT_OPCODE_MAX || action == TC_ACT_UNSPEC;
810-
}
811-
812837
struct tc_action *tcf_action_init_1(struct net *net, struct tcf_proto *tp,
813838
struct nlattr *nla, struct nlattr *est,
814839
char *name, int ovr, int bind,
@@ -890,10 +915,10 @@ struct tc_action *tcf_action_init_1(struct net *net, struct tcf_proto *tp,
890915
/* backward compatibility for policer */
891916
if (name == NULL)
892917
err = a_o->init(net, tb[TCA_ACT_OPTIONS], est, &a, ovr, bind,
893-
rtnl_held, extack);
918+
rtnl_held, tp, extack);
894919
else
895920
err = a_o->init(net, nla, est, &a, ovr, bind, rtnl_held,
896-
extack);
921+
tp, extack);
897922
if (err < 0)
898923
goto err_mod;
899924

@@ -907,18 +932,10 @@ struct tc_action *tcf_action_init_1(struct net *net, struct tcf_proto *tp,
907932
if (err != ACT_P_CREATED)
908933
module_put(a_o->owner);
909934

910-
if (TC_ACT_EXT_CMP(a->tcfa_action, TC_ACT_GOTO_CHAIN)) {
911-
err = tcf_action_goto_chain_init(a, tp);
912-
if (err) {
913-
tcf_action_destroy_1(a, bind);
914-
NL_SET_ERR_MSG(extack, "Failed to init TC action chain");
915-
return ERR_PTR(err);
916-
}
917-
}
918-
919-
if (!tcf_action_valid(a->tcfa_action)) {
935+
if (TC_ACT_EXT_CMP(a->tcfa_action, TC_ACT_GOTO_CHAIN) &&
936+
!rcu_access_pointer(a->goto_chain)) {
920937
tcf_action_destroy_1(a, bind);
921-
NL_SET_ERR_MSG(extack, "Invalid control action value");
938+
NL_SET_ERR_MSG(extack, "can't use goto chain with NULL chain");
922939
return ERR_PTR(-EINVAL);
923940
}
924941

net/sched/act_bpf.c

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
#include <net/netlink.h>
1919
#include <net/pkt_sched.h>
20+
#include <net/pkt_cls.h>
2021

2122
#include <linux/tc_act/tc_bpf.h>
2223
#include <net/tc_act/tc_bpf.h>
@@ -278,10 +279,11 @@ static void tcf_bpf_prog_fill_cfg(const struct tcf_bpf *prog,
278279
static int tcf_bpf_init(struct net *net, struct nlattr *nla,
279280
struct nlattr *est, struct tc_action **act,
280281
int replace, int bind, bool rtnl_held,
281-
struct netlink_ext_ack *extack)
282+
struct tcf_proto *tp, struct netlink_ext_ack *extack)
282283
{
283284
struct tc_action_net *tn = net_generic(net, bpf_net_id);
284285
struct nlattr *tb[TCA_ACT_BPF_MAX + 1];
286+
struct tcf_chain *goto_ch = NULL;
285287
struct tcf_bpf_cfg cfg, old;
286288
struct tc_act_bpf *parm;
287289
struct tcf_bpf *prog;
@@ -323,20 +325,24 @@ static int tcf_bpf_init(struct net *net, struct nlattr *nla,
323325
return ret;
324326
}
325327

328+
ret = tcf_action_check_ctrlact(parm->action, tp, &goto_ch, extack);
329+
if (ret < 0)
330+
goto release_idr;
331+
326332
is_bpf = tb[TCA_ACT_BPF_OPS_LEN] && tb[TCA_ACT_BPF_OPS];
327333
is_ebpf = tb[TCA_ACT_BPF_FD];
328334

329335
if ((!is_bpf && !is_ebpf) || (is_bpf && is_ebpf)) {
330336
ret = -EINVAL;
331-
goto out;
337+
goto put_chain;
332338
}
333339

334340
memset(&cfg, 0, sizeof(cfg));
335341

336342
ret = is_bpf ? tcf_bpf_init_from_ops(tb, &cfg) :
337343
tcf_bpf_init_from_efd(tb, &cfg);
338344
if (ret < 0)
339-
goto out;
345+
goto put_chain;
340346

341347
prog = to_bpf(*act);
342348

@@ -350,10 +356,13 @@ static int tcf_bpf_init(struct net *net, struct nlattr *nla,
350356
if (cfg.bpf_num_ops)
351357
prog->bpf_num_ops = cfg.bpf_num_ops;
352358

353-
prog->tcf_action = parm->action;
359+
goto_ch = tcf_action_set_ctrlact(*act, parm->action, goto_ch);
354360
rcu_assign_pointer(prog->filter, cfg.filter);
355361
spin_unlock_bh(&prog->tcf_lock);
356362

363+
if (goto_ch)
364+
tcf_chain_put_by_act(goto_ch);
365+
357366
if (res == ACT_P_CREATED) {
358367
tcf_idr_insert(tn, *act);
359368
} else {
@@ -363,9 +372,13 @@ static int tcf_bpf_init(struct net *net, struct nlattr *nla,
363372
}
364373

365374
return res;
366-
out:
367-
tcf_idr_release(*act, bind);
368375

376+
put_chain:
377+
if (goto_ch)
378+
tcf_chain_put_by_act(goto_ch);
379+
380+
release_idr:
381+
tcf_idr_release(*act, bind);
369382
return ret;
370383
}
371384

net/sched/act_connmark.c

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include <net/netlink.h>
2222
#include <net/pkt_sched.h>
2323
#include <net/act_api.h>
24+
#include <net/pkt_cls.h>
2425
#include <uapi/linux/tc_act/tc_connmark.h>
2526
#include <net/tc_act/tc_connmark.h>
2627

@@ -97,13 +98,15 @@ static const struct nla_policy connmark_policy[TCA_CONNMARK_MAX + 1] = {
9798
static int tcf_connmark_init(struct net *net, struct nlattr *nla,
9899
struct nlattr *est, struct tc_action **a,
99100
int ovr, int bind, bool rtnl_held,
101+
struct tcf_proto *tp,
100102
struct netlink_ext_ack *extack)
101103
{
102104
struct tc_action_net *tn = net_generic(net, connmark_net_id);
103105
struct nlattr *tb[TCA_CONNMARK_MAX + 1];
106+
struct tcf_chain *goto_ch = NULL;
104107
struct tcf_connmark_info *ci;
105108
struct tc_connmark *parm;
106-
int ret = 0;
109+
int ret = 0, err;
107110

108111
if (!nla)
109112
return -EINVAL;
@@ -128,7 +131,11 @@ static int tcf_connmark_init(struct net *net, struct nlattr *nla,
128131
}
129132

130133
ci = to_connmark(*a);
131-
ci->tcf_action = parm->action;
134+
err = tcf_action_check_ctrlact(parm->action, tp, &goto_ch,
135+
extack);
136+
if (err < 0)
137+
goto release_idr;
138+
tcf_action_set_ctrlact(*a, parm->action, goto_ch);
132139
ci->net = net;
133140
ci->zone = parm->zone;
134141

@@ -142,15 +149,24 @@ static int tcf_connmark_init(struct net *net, struct nlattr *nla,
142149
tcf_idr_release(*a, bind);
143150
return -EEXIST;
144151
}
152+
err = tcf_action_check_ctrlact(parm->action, tp, &goto_ch,
153+
extack);
154+
if (err < 0)
155+
goto release_idr;
145156
/* replacing action and zone */
146157
spin_lock_bh(&ci->tcf_lock);
147-
ci->tcf_action = parm->action;
158+
goto_ch = tcf_action_set_ctrlact(*a, parm->action, goto_ch);
148159
ci->zone = parm->zone;
149160
spin_unlock_bh(&ci->tcf_lock);
161+
if (goto_ch)
162+
tcf_chain_put_by_act(goto_ch);
150163
ret = 0;
151164
}
152165

153166
return ret;
167+
release_idr:
168+
tcf_idr_release(*a, bind);
169+
return err;
154170
}
155171

156172
static inline int tcf_connmark_dump(struct sk_buff *skb, struct tc_action *a,

0 commit comments

Comments
 (0)