Skip to content

Commit

Permalink
Merge branch 'net-sched-validate-the-control-action-with-all-the-othe…
Browse files Browse the repository at this point in the history
…r-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 <davem@davemloft.net>
  • Loading branch information
David S. Miller committed Mar 21, 2019
2 parents cd5afa9 + ee3bbfe commit 1ea186e
Show file tree
Hide file tree
Showing 36 changed files with 749 additions and 121 deletions.
9 changes: 7 additions & 2 deletions include/net/act_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -90,7 +90,7 @@ struct tc_action_ops {
int (*lookup)(struct net *net, struct tc_action **a, u32 index);
int (*init)(struct net *net, struct nlattr *nla,
struct nlattr *est, struct tc_action **act, int ovr,
int bind, bool rtnl_held,
int bind, bool rtnl_held, struct tcf_proto *tp,
struct netlink_ext_ack *extack);
int (*walk)(struct net *, struct sk_buff *,
struct netlink_callback *, int,
Expand Down Expand Up @@ -181,6 +181,11 @@ int tcf_action_dump_old(struct sk_buff *skb, struct tc_action *a, int, int);
int tcf_action_dump_1(struct sk_buff *skb, struct tc_action *a, int, int);
int tcf_action_copy_stats(struct sk_buff *, struct tc_action *, int);

int tcf_action_check_ctrlact(int action, struct tcf_proto *tp,
struct tcf_chain **handle,
struct netlink_ext_ack *newchain);
struct tcf_chain *tcf_action_set_ctrlact(struct tc_action *a, int action,
struct tcf_chain *newchain);
#endif /* CONFIG_NET_CLS_ACT */

static inline void tcf_action_stats_update(struct tc_action *a, u64 bytes,
Expand Down
1 change: 1 addition & 0 deletions include/net/sch_generic.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion include/net/tc_act/tc_gact.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ static inline bool is_tcf_gact_goto_chain(const struct tc_action *a)

static inline u32 tcf_gact_goto_chain_index(const struct tc_action *a)
{
return a->goto_chain->index;
return READ_ONCE(a->tcfa_action) & TC_ACT_EXT_VAL_MASK;
}

#endif /* __NET_TC_GACT_H */
101 changes: 59 additions & 42 deletions net/sched/act_api.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,27 +28,10 @@
#include <net/act_api.h>
#include <net/netlink.h>

static int tcf_action_goto_chain_init(struct tc_action *a, struct tcf_proto *tp)
{
u32 chain_index = a->tcfa_action & TC_ACT_EXT_VAL_MASK;

if (!tp)
return -EINVAL;
a->goto_chain = tcf_chain_get_by_act(tp->chain->block, chain_index);
if (!a->goto_chain)
return -ENOMEM;
return 0;
}

static void tcf_action_goto_chain_fini(struct tc_action *a)
{
tcf_chain_put_by_act(a->goto_chain);
}

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);
}
Expand All @@ -71,20 +54,67 @@ static void tcf_set_action_cookie(struct tc_cookie __rcu **old_cookie,
call_rcu(&old->rcu, tcf_free_cookie_rcu);
}

int tcf_action_check_ctrlact(int action, struct tcf_proto *tp,
struct tcf_chain **newchain,
struct netlink_ext_ack *extack)
{
int opcode = TC_ACT_EXT_OPCODE(action), ret = -EINVAL;
u32 chain_index;

if (!opcode)
ret = action > TC_ACT_VALUE_MAX ? -EINVAL : 0;
else if (opcode <= TC_ACT_EXT_OPCODE_MAX || action == TC_ACT_UNSPEC)
ret = 0;
if (ret) {
NL_SET_ERR_MSG(extack, "invalid control action");
goto end;
}

if (TC_ACT_EXT_CMP(action, TC_ACT_GOTO_CHAIN)) {
chain_index = action & TC_ACT_EXT_VAL_MASK;
if (!tp || !newchain) {
ret = -EINVAL;
NL_SET_ERR_MSG(extack,
"can't goto NULL proto/chain");
goto end;
}
*newchain = tcf_chain_get_by_act(tp->chain->block, chain_index);
if (!*newchain) {
ret = -ENOMEM;
NL_SET_ERR_MSG(extack,
"can't allocate goto_chain");
}
}
end:
return ret;
}
EXPORT_SYMBOL(tcf_action_check_ctrlact);

struct tcf_chain *tcf_action_set_ctrlact(struct tc_action *a, int action,
struct tcf_chain *goto_chain)
{
a->tcfa_action = action;
rcu_swap_protected(a->goto_chain, goto_chain, 1);
return goto_chain;
}
EXPORT_SYMBOL(tcf_action_set_ctrlact);

/* XXX: For standalone actions, we don't need a RCU grace period either, because
* actions are always connected to filters and filters are already destroyed in
* RCU callbacks, so after a RCU grace period actions are already disconnected
* from filters. Readers later can not find us.
*/
static void free_tcf(struct tc_action *p)
{
struct tcf_chain *chain = rcu_dereference_protected(p->goto_chain, 1);

free_percpu(p->cpu_bstats);
free_percpu(p->cpu_bstats_hw);
free_percpu(p->cpu_qstats);

tcf_set_action_cookie(&p->act_cookie, NULL);
if (p->goto_chain)
tcf_action_goto_chain_fini(p);
if (chain)
tcf_chain_put_by_act(chain);

kfree(p);
}
Expand Down Expand Up @@ -654,6 +684,10 @@ int tcf_action_exec(struct sk_buff *skb, struct tc_action **actions,
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);
}

Expand Down Expand Up @@ -800,15 +834,6 @@ static struct tc_cookie *nla_memdup_cookie(struct nlattr **tb)
return c;
}

static bool tcf_action_valid(int action)
{
int opcode = TC_ACT_EXT_OPCODE(action);

if (!opcode)
return action <= TC_ACT_VALUE_MAX;
return opcode <= TC_ACT_EXT_OPCODE_MAX || action == TC_ACT_UNSPEC;
}

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,
Expand Down Expand Up @@ -890,10 +915,10 @@ struct tc_action *tcf_action_init_1(struct net *net, struct tcf_proto *tp,
/* backward compatibility for policer */
if (name == NULL)
err = a_o->init(net, tb[TCA_ACT_OPTIONS], est, &a, ovr, bind,
rtnl_held, extack);
rtnl_held, tp, extack);
else
err = a_o->init(net, nla, est, &a, ovr, bind, rtnl_held,
extack);
tp, extack);
if (err < 0)
goto err_mod;

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

if (TC_ACT_EXT_CMP(a->tcfa_action, TC_ACT_GOTO_CHAIN)) {
err = tcf_action_goto_chain_init(a, tp);
if (err) {
tcf_action_destroy_1(a, bind);
NL_SET_ERR_MSG(extack, "Failed to init TC action chain");
return ERR_PTR(err);
}
}

if (!tcf_action_valid(a->tcfa_action)) {
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, "Invalid control action value");
NL_SET_ERR_MSG(extack, "can't use goto chain with NULL chain");
return ERR_PTR(-EINVAL);
}

Expand Down
25 changes: 19 additions & 6 deletions net/sched/act_bpf.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

#include <net/netlink.h>
#include <net/pkt_sched.h>
#include <net/pkt_cls.h>

#include <linux/tc_act/tc_bpf.h>
#include <net/tc_act/tc_bpf.h>
Expand Down Expand Up @@ -278,10 +279,11 @@ static void tcf_bpf_prog_fill_cfg(const struct tcf_bpf *prog,
static int tcf_bpf_init(struct net *net, struct nlattr *nla,
struct nlattr *est, struct tc_action **act,
int replace, int bind, bool rtnl_held,
struct netlink_ext_ack *extack)
struct tcf_proto *tp, struct netlink_ext_ack *extack)
{
struct tc_action_net *tn = net_generic(net, bpf_net_id);
struct nlattr *tb[TCA_ACT_BPF_MAX + 1];
struct tcf_chain *goto_ch = NULL;
struct tcf_bpf_cfg cfg, old;
struct tc_act_bpf *parm;
struct tcf_bpf *prog;
Expand Down Expand Up @@ -323,20 +325,24 @@ static int tcf_bpf_init(struct net *net, struct nlattr *nla,
return ret;
}

ret = tcf_action_check_ctrlact(parm->action, tp, &goto_ch, extack);
if (ret < 0)
goto release_idr;

is_bpf = tb[TCA_ACT_BPF_OPS_LEN] && tb[TCA_ACT_BPF_OPS];
is_ebpf = tb[TCA_ACT_BPF_FD];

if ((!is_bpf && !is_ebpf) || (is_bpf && is_ebpf)) {
ret = -EINVAL;
goto out;
goto put_chain;
}

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

ret = is_bpf ? tcf_bpf_init_from_ops(tb, &cfg) :
tcf_bpf_init_from_efd(tb, &cfg);
if (ret < 0)
goto out;
goto put_chain;

prog = to_bpf(*act);

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

prog->tcf_action = parm->action;
goto_ch = tcf_action_set_ctrlact(*act, parm->action, goto_ch);
rcu_assign_pointer(prog->filter, cfg.filter);
spin_unlock_bh(&prog->tcf_lock);

if (goto_ch)
tcf_chain_put_by_act(goto_ch);

if (res == ACT_P_CREATED) {
tcf_idr_insert(tn, *act);
} else {
Expand All @@ -363,9 +372,13 @@ static int tcf_bpf_init(struct net *net, struct nlattr *nla,
}

return res;
out:
tcf_idr_release(*act, bind);

put_chain:
if (goto_ch)
tcf_chain_put_by_act(goto_ch);

release_idr:
tcf_idr_release(*act, bind);
return ret;
}

Expand Down
22 changes: 19 additions & 3 deletions net/sched/act_connmark.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include <net/netlink.h>
#include <net/pkt_sched.h>
#include <net/act_api.h>
#include <net/pkt_cls.h>
#include <uapi/linux/tc_act/tc_connmark.h>
#include <net/tc_act/tc_connmark.h>

Expand Down Expand Up @@ -97,13 +98,15 @@ static const struct nla_policy connmark_policy[TCA_CONNMARK_MAX + 1] = {
static int tcf_connmark_init(struct net *net, struct nlattr *nla,
struct nlattr *est, struct tc_action **a,
int ovr, int bind, bool rtnl_held,
struct tcf_proto *tp,
struct netlink_ext_ack *extack)
{
struct tc_action_net *tn = net_generic(net, connmark_net_id);
struct nlattr *tb[TCA_CONNMARK_MAX + 1];
struct tcf_chain *goto_ch = NULL;
struct tcf_connmark_info *ci;
struct tc_connmark *parm;
int ret = 0;
int ret = 0, err;

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

ci = to_connmark(*a);
ci->tcf_action = parm->action;
err = tcf_action_check_ctrlact(parm->action, tp, &goto_ch,
extack);
if (err < 0)
goto release_idr;
tcf_action_set_ctrlact(*a, parm->action, goto_ch);
ci->net = net;
ci->zone = parm->zone;

Expand All @@ -142,15 +149,24 @@ static int tcf_connmark_init(struct net *net, struct nlattr *nla,
tcf_idr_release(*a, bind);
return -EEXIST;
}
err = tcf_action_check_ctrlact(parm->action, tp, &goto_ch,
extack);
if (err < 0)
goto release_idr;
/* replacing action and zone */
spin_lock_bh(&ci->tcf_lock);
ci->tcf_action = parm->action;
goto_ch = tcf_action_set_ctrlact(*a, parm->action, goto_ch);
ci->zone = parm->zone;
spin_unlock_bh(&ci->tcf_lock);
if (goto_ch)
tcf_chain_put_by_act(goto_ch);
ret = 0;
}

return ret;
release_idr:
tcf_idr_release(*a, bind);
return err;
}

static inline int tcf_connmark_dump(struct sk_buff *skb, struct tc_action *a,
Expand Down
Loading

0 comments on commit 1ea186e

Please sign in to comment.