net/sched: act_ife: Fix metalist update behavior

Whenever an ife action replace changes the metalist, instead of
replacing the old data on the metalist, the current ife code is appending
the new metadata. Aside from being innapropriate behavior, this may lead
to an unbounded addition of metadata to the metalist which might cause an
out of bounds error when running the encode op:

[  138.423369][    C1] ==================================================================
[  138.424317][    C1] BUG: KASAN: slab-out-of-bounds in ife_tlv_meta_encode (net/ife/ife.c:168)
[  138.424906][    C1] Write of size 4 at addr ffff8880077f4ffe by task ife_out_out_bou/255
[  138.425778][    C1] CPU: 1 UID: 0 PID: 255 Comm: ife_out_out_bou Not tainted 7.0.0-rc1-00169-gfbdfa8da05b6 #624 PREEMPT(full)
[  138.425795][    C1] Hardware name: Bochs Bochs, BIOS Bochs 01/01/2011
[  138.425800][    C1] Call Trace:
[  138.425804][    C1]  <IRQ>
[  138.425808][    C1]  dump_stack_lvl (lib/dump_stack.c:122)
[  138.425828][    C1]  print_report (mm/kasan/report.c:379 mm/kasan/report.c:482)
[  138.425839][    C1]  ? srso_alias_return_thunk (arch/x86/lib/retpoline.S:221)
[  138.425844][    C1]  ? __virt_addr_valid (./arch/x86/include/asm/preempt.h:95 (discriminator 1) ./include/linux/rcupdate.h:975 (discriminator 1) ./include/linux/mmzone.h:2207 (discriminator 1) arch/x86/mm/physaddr.c:54 (discriminator 1))
[  138.425853][    C1]  ? ife_tlv_meta_encode (net/ife/ife.c:168)
[  138.425859][    C1]  kasan_report (mm/kasan/report.c:221 mm/kasan/report.c:597)
[  138.425868][    C1]  ? ife_tlv_meta_encode (net/ife/ife.c:168)
[  138.425878][    C1]  kasan_check_range (mm/kasan/generic.c:186 (discriminator 1) mm/kasan/generic.c:200 (discriminator 1))
[  138.425884][    C1]  __asan_memset (mm/kasan/shadow.c:84 (discriminator 2))
[  138.425889][    C1]  ife_tlv_meta_encode (net/ife/ife.c:168)
[  138.425893][    C1]  ? ife_tlv_meta_encode (net/ife/ife.c:171)
[  138.425898][    C1]  ? srso_alias_return_thunk (arch/x86/lib/retpoline.S:221)
[  138.425903][    C1]  ife_encode_meta_u16 (net/sched/act_ife.c:57)
[  138.425910][    C1]  ? __pfx_do_raw_spin_lock (kernel/locking/spinlock_debug.c:114)
[  138.425916][    C1]  ? __asan_memcpy (mm/kasan/shadow.c:105 (discriminator 3))
[  138.425921][    C1]  ? __pfx_ife_encode_meta_u16 (net/sched/act_ife.c:45)
[  138.425927][    C1]  ? srso_alias_return_thunk (arch/x86/lib/retpoline.S:221)
[  138.425931][    C1]  tcf_ife_act (net/sched/act_ife.c:847 net/sched/act_ife.c:879)

To solve this issue, fix the replace behavior by adding the metalist to
the ife rcu data structure.

Fixes: aa9fd9a325 ("sched: act: ife: update parameters via rcu handling")
Reported-by: Ruitong Liu <cnitlrt@gmail.com>
Tested-by: Ruitong Liu <cnitlrt@gmail.com>
Co-developed-by: Victor Nogueira <victor@mojatatu.com>
Signed-off-by: Victor Nogueira <victor@mojatatu.com>
Signed-off-by: Jamal Hadi Salim <jhs@mojatatu.com>
Link: https://patch.msgid.link/20260304140603.76500-1-jhs@mojatatu.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Jamal Hadi Salim 2026-03-04 09:06:02 -05:00 committed by Jakub Kicinski
parent 4517b74cc0
commit e2cedd400c
2 changed files with 45 additions and 52 deletions

View file

@ -13,15 +13,13 @@ struct tcf_ife_params {
u8 eth_src[ETH_ALEN]; u8 eth_src[ETH_ALEN];
u16 eth_type; u16 eth_type;
u16 flags; u16 flags;
struct list_head metalist;
struct rcu_head rcu; struct rcu_head rcu;
}; };
struct tcf_ife_info { struct tcf_ife_info {
struct tc_action common; struct tc_action common;
struct tcf_ife_params __rcu *params; struct tcf_ife_params __rcu *params;
/* list of metaids allowed */
struct list_head metalist;
}; };
#define to_ife(a) ((struct tcf_ife_info *)a) #define to_ife(a) ((struct tcf_ife_info *)a)

View file

@ -293,8 +293,8 @@ static int load_metaops_and_vet(u32 metaid, void *val, int len, bool rtnl_held)
/* called when adding new meta information /* called when adding new meta information
*/ */
static int __add_metainfo(const struct tcf_meta_ops *ops, static int __add_metainfo(const struct tcf_meta_ops *ops,
struct tcf_ife_info *ife, u32 metaid, void *metaval, struct tcf_ife_params *p, u32 metaid, void *metaval,
int len, bool atomic, bool exists) int len, bool atomic)
{ {
struct tcf_meta_info *mi = NULL; struct tcf_meta_info *mi = NULL;
int ret = 0; int ret = 0;
@ -313,45 +313,40 @@ static int __add_metainfo(const struct tcf_meta_ops *ops,
} }
} }
if (exists) list_add_tail(&mi->metalist, &p->metalist);
spin_lock_bh(&ife->tcf_lock);
list_add_tail(&mi->metalist, &ife->metalist);
if (exists)
spin_unlock_bh(&ife->tcf_lock);
return ret; return ret;
} }
static int add_metainfo_and_get_ops(const struct tcf_meta_ops *ops, static int add_metainfo_and_get_ops(const struct tcf_meta_ops *ops,
struct tcf_ife_info *ife, u32 metaid, struct tcf_ife_params *p, u32 metaid)
bool exists)
{ {
int ret; int ret;
if (!try_module_get(ops->owner)) if (!try_module_get(ops->owner))
return -ENOENT; return -ENOENT;
ret = __add_metainfo(ops, ife, metaid, NULL, 0, true, exists); ret = __add_metainfo(ops, p, metaid, NULL, 0, true);
if (ret) if (ret)
module_put(ops->owner); module_put(ops->owner);
return ret; return ret;
} }
static int add_metainfo(struct tcf_ife_info *ife, u32 metaid, void *metaval, static int add_metainfo(struct tcf_ife_params *p, u32 metaid, void *metaval,
int len, bool exists) int len)
{ {
const struct tcf_meta_ops *ops = find_ife_oplist(metaid); const struct tcf_meta_ops *ops = find_ife_oplist(metaid);
int ret; int ret;
if (!ops) if (!ops)
return -ENOENT; return -ENOENT;
ret = __add_metainfo(ops, ife, metaid, metaval, len, false, exists); ret = __add_metainfo(ops, p, metaid, metaval, len, false);
if (ret) if (ret)
/*put back what find_ife_oplist took */ /*put back what find_ife_oplist took */
module_put(ops->owner); module_put(ops->owner);
return ret; return ret;
} }
static int use_all_metadata(struct tcf_ife_info *ife, bool exists) static int use_all_metadata(struct tcf_ife_params *p)
{ {
struct tcf_meta_ops *o; struct tcf_meta_ops *o;
int rc = 0; int rc = 0;
@ -359,7 +354,7 @@ static int use_all_metadata(struct tcf_ife_info *ife, bool exists)
read_lock(&ife_mod_lock); read_lock(&ife_mod_lock);
list_for_each_entry(o, &ifeoplist, list) { list_for_each_entry(o, &ifeoplist, list) {
rc = add_metainfo_and_get_ops(o, ife, o->metaid, exists); rc = add_metainfo_and_get_ops(o, p, o->metaid);
if (rc == 0) if (rc == 0)
installed += 1; installed += 1;
} }
@ -371,7 +366,7 @@ static int use_all_metadata(struct tcf_ife_info *ife, bool exists)
return -EINVAL; return -EINVAL;
} }
static int dump_metalist(struct sk_buff *skb, struct tcf_ife_info *ife) static int dump_metalist(struct sk_buff *skb, struct tcf_ife_params *p)
{ {
struct tcf_meta_info *e; struct tcf_meta_info *e;
struct nlattr *nest; struct nlattr *nest;
@ -379,14 +374,14 @@ static int dump_metalist(struct sk_buff *skb, struct tcf_ife_info *ife)
int total_encoded = 0; int total_encoded = 0;
/*can only happen on decode */ /*can only happen on decode */
if (list_empty(&ife->metalist)) if (list_empty(&p->metalist))
return 0; return 0;
nest = nla_nest_start_noflag(skb, TCA_IFE_METALST); nest = nla_nest_start_noflag(skb, TCA_IFE_METALST);
if (!nest) if (!nest)
goto out_nlmsg_trim; goto out_nlmsg_trim;
list_for_each_entry(e, &ife->metalist, metalist) { list_for_each_entry(e, &p->metalist, metalist) {
if (!e->ops->get(skb, e)) if (!e->ops->get(skb, e))
total_encoded += 1; total_encoded += 1;
} }
@ -403,13 +398,11 @@ out_nlmsg_trim:
return -1; return -1;
} }
/* under ife->tcf_lock */ static void __tcf_ife_cleanup(struct tcf_ife_params *p)
static void _tcf_ife_cleanup(struct tc_action *a)
{ {
struct tcf_ife_info *ife = to_ife(a);
struct tcf_meta_info *e, *n; struct tcf_meta_info *e, *n;
list_for_each_entry_safe(e, n, &ife->metalist, metalist) { list_for_each_entry_safe(e, n, &p->metalist, metalist) {
list_del(&e->metalist); list_del(&e->metalist);
if (e->metaval) { if (e->metaval) {
if (e->ops->release) if (e->ops->release)
@ -422,18 +415,23 @@ static void _tcf_ife_cleanup(struct tc_action *a)
} }
} }
static void tcf_ife_cleanup_params(struct rcu_head *head)
{
struct tcf_ife_params *p = container_of(head, struct tcf_ife_params,
rcu);
__tcf_ife_cleanup(p);
kfree(p);
}
static void tcf_ife_cleanup(struct tc_action *a) static void tcf_ife_cleanup(struct tc_action *a)
{ {
struct tcf_ife_info *ife = to_ife(a); struct tcf_ife_info *ife = to_ife(a);
struct tcf_ife_params *p; struct tcf_ife_params *p;
spin_lock_bh(&ife->tcf_lock);
_tcf_ife_cleanup(a);
spin_unlock_bh(&ife->tcf_lock);
p = rcu_dereference_protected(ife->params, 1); p = rcu_dereference_protected(ife->params, 1);
if (p) if (p)
kfree_rcu(p, rcu); call_rcu(&p->rcu, tcf_ife_cleanup_params);
} }
static int load_metalist(struct nlattr **tb, bool rtnl_held) static int load_metalist(struct nlattr **tb, bool rtnl_held)
@ -455,8 +453,7 @@ static int load_metalist(struct nlattr **tb, bool rtnl_held)
return 0; return 0;
} }
static int populate_metalist(struct tcf_ife_info *ife, struct nlattr **tb, static int populate_metalist(struct tcf_ife_params *p, struct nlattr **tb)
bool exists, bool rtnl_held)
{ {
int len = 0; int len = 0;
int rc = 0; int rc = 0;
@ -468,7 +465,7 @@ static int populate_metalist(struct tcf_ife_info *ife, struct nlattr **tb,
val = nla_data(tb[i]); val = nla_data(tb[i]);
len = nla_len(tb[i]); len = nla_len(tb[i]);
rc = add_metainfo(ife, i, val, len, exists); rc = add_metainfo(p, i, val, len);
if (rc) if (rc)
return rc; return rc;
} }
@ -523,6 +520,7 @@ static int tcf_ife_init(struct net *net, struct nlattr *nla,
p = kzalloc_obj(*p); p = kzalloc_obj(*p);
if (!p) if (!p)
return -ENOMEM; return -ENOMEM;
INIT_LIST_HEAD(&p->metalist);
if (tb[TCA_IFE_METALST]) { if (tb[TCA_IFE_METALST]) {
err = nla_parse_nested_deprecated(tb2, IFE_META_MAX, err = nla_parse_nested_deprecated(tb2, IFE_META_MAX,
@ -567,8 +565,6 @@ static int tcf_ife_init(struct net *net, struct nlattr *nla,
} }
ife = to_ife(*a); ife = to_ife(*a);
if (ret == ACT_P_CREATED)
INIT_LIST_HEAD(&ife->metalist);
err = tcf_action_check_ctrlact(parm->action, tp, &goto_ch, extack); err = tcf_action_check_ctrlact(parm->action, tp, &goto_ch, extack);
if (err < 0) if (err < 0)
@ -600,8 +596,7 @@ static int tcf_ife_init(struct net *net, struct nlattr *nla,
} }
if (tb[TCA_IFE_METALST]) { if (tb[TCA_IFE_METALST]) {
err = populate_metalist(ife, tb2, exists, err = populate_metalist(p, tb2);
!(flags & TCA_ACT_FLAGS_NO_RTNL));
if (err) if (err)
goto metadata_parse_err; goto metadata_parse_err;
} else { } else {
@ -610,7 +605,7 @@ static int tcf_ife_init(struct net *net, struct nlattr *nla,
* as we can. You better have at least one else we are * as we can. You better have at least one else we are
* going to bail out * going to bail out
*/ */
err = use_all_metadata(ife, exists); err = use_all_metadata(p);
if (err) if (err)
goto metadata_parse_err; goto metadata_parse_err;
} }
@ -626,13 +621,14 @@ static int tcf_ife_init(struct net *net, struct nlattr *nla,
if (goto_ch) if (goto_ch)
tcf_chain_put_by_act(goto_ch); tcf_chain_put_by_act(goto_ch);
if (p) if (p)
kfree_rcu(p, rcu); call_rcu(&p->rcu, tcf_ife_cleanup_params);
return ret; return ret;
metadata_parse_err: metadata_parse_err:
if (goto_ch) if (goto_ch)
tcf_chain_put_by_act(goto_ch); tcf_chain_put_by_act(goto_ch);
release_idr: release_idr:
__tcf_ife_cleanup(p);
kfree(p); kfree(p);
tcf_idr_release(*a, bind); tcf_idr_release(*a, bind);
return err; return err;
@ -679,7 +675,7 @@ static int tcf_ife_dump(struct sk_buff *skb, struct tc_action *a, int bind,
if (nla_put(skb, TCA_IFE_TYPE, 2, &p->eth_type)) if (nla_put(skb, TCA_IFE_TYPE, 2, &p->eth_type))
goto nla_put_failure; goto nla_put_failure;
if (dump_metalist(skb, ife)) { if (dump_metalist(skb, p)) {
/*ignore failure to dump metalist */ /*ignore failure to dump metalist */
pr_info("Failed to dump metalist\n"); pr_info("Failed to dump metalist\n");
} }
@ -693,13 +689,13 @@ nla_put_failure:
return -1; return -1;
} }
static int find_decode_metaid(struct sk_buff *skb, struct tcf_ife_info *ife, static int find_decode_metaid(struct sk_buff *skb, struct tcf_ife_params *p,
u16 metaid, u16 mlen, void *mdata) u16 metaid, u16 mlen, void *mdata)
{ {
struct tcf_meta_info *e; struct tcf_meta_info *e;
/* XXX: use hash to speed up */ /* XXX: use hash to speed up */
list_for_each_entry(e, &ife->metalist, metalist) { list_for_each_entry_rcu(e, &p->metalist, metalist) {
if (metaid == e->metaid) { if (metaid == e->metaid) {
if (e->ops) { if (e->ops) {
/* We check for decode presence already */ /* We check for decode presence already */
@ -716,10 +712,13 @@ static int tcf_ife_decode(struct sk_buff *skb, const struct tc_action *a,
{ {
struct tcf_ife_info *ife = to_ife(a); struct tcf_ife_info *ife = to_ife(a);
int action = ife->tcf_action; int action = ife->tcf_action;
struct tcf_ife_params *p;
u8 *ifehdr_end; u8 *ifehdr_end;
u8 *tlv_data; u8 *tlv_data;
u16 metalen; u16 metalen;
p = rcu_dereference_bh(ife->params);
bstats_update(this_cpu_ptr(ife->common.cpu_bstats), skb); bstats_update(this_cpu_ptr(ife->common.cpu_bstats), skb);
tcf_lastuse_update(&ife->tcf_tm); tcf_lastuse_update(&ife->tcf_tm);
@ -745,7 +744,7 @@ static int tcf_ife_decode(struct sk_buff *skb, const struct tc_action *a,
return TC_ACT_SHOT; return TC_ACT_SHOT;
} }
if (find_decode_metaid(skb, ife, mtype, dlen, curr_data)) { if (find_decode_metaid(skb, p, mtype, dlen, curr_data)) {
/* abuse overlimits to count when we receive metadata /* abuse overlimits to count when we receive metadata
* but dont have an ops for it * but dont have an ops for it
*/ */
@ -769,12 +768,12 @@ static int tcf_ife_decode(struct sk_buff *skb, const struct tc_action *a,
/*XXX: check if we can do this at install time instead of current /*XXX: check if we can do this at install time instead of current
* send data path * send data path
**/ **/
static int ife_get_sz(struct sk_buff *skb, struct tcf_ife_info *ife) static int ife_get_sz(struct sk_buff *skb, struct tcf_ife_params *p)
{ {
struct tcf_meta_info *e, *n; struct tcf_meta_info *e;
int tot_run_sz = 0, run_sz = 0; int tot_run_sz = 0, run_sz = 0;
list_for_each_entry_safe(e, n, &ife->metalist, metalist) { list_for_each_entry_rcu(e, &p->metalist, metalist) {
if (e->ops->check_presence) { if (e->ops->check_presence) {
run_sz = e->ops->check_presence(skb, e); run_sz = e->ops->check_presence(skb, e);
tot_run_sz += run_sz; tot_run_sz += run_sz;
@ -795,7 +794,7 @@ static int tcf_ife_encode(struct sk_buff *skb, const struct tc_action *a,
OUTERHDR:TOTMETALEN:{TLVHDR:Metadatum:TLVHDR..}:ORIGDATA OUTERHDR:TOTMETALEN:{TLVHDR:Metadatum:TLVHDR..}:ORIGDATA
where ORIGDATA = original ethernet header ... where ORIGDATA = original ethernet header ...
*/ */
u16 metalen = ife_get_sz(skb, ife); u16 metalen = ife_get_sz(skb, p);
int hdrm = metalen + skb->dev->hard_header_len + IFE_METAHDRLEN; int hdrm = metalen + skb->dev->hard_header_len + IFE_METAHDRLEN;
unsigned int skboff = 0; unsigned int skboff = 0;
int new_len = skb->len + hdrm; int new_len = skb->len + hdrm;
@ -833,25 +832,21 @@ drop:
if (!ife_meta) if (!ife_meta)
goto drop; goto drop;
spin_lock(&ife->tcf_lock);
/* XXX: we dont have a clever way of telling encode to /* XXX: we dont have a clever way of telling encode to
* not repeat some of the computations that are done by * not repeat some of the computations that are done by
* ops->presence_check... * ops->presence_check...
*/ */
list_for_each_entry(e, &ife->metalist, metalist) { list_for_each_entry_rcu(e, &p->metalist, metalist) {
if (e->ops->encode) { if (e->ops->encode) {
err = e->ops->encode(skb, (void *)(ife_meta + skboff), err = e->ops->encode(skb, (void *)(ife_meta + skboff),
e); e);
} }
if (err < 0) { if (err < 0) {
/* too corrupt to keep around if overwritten */ /* too corrupt to keep around if overwritten */
spin_unlock(&ife->tcf_lock);
goto drop; goto drop;
} }
skboff += err; skboff += err;
} }
spin_unlock(&ife->tcf_lock);
oethh = (struct ethhdr *)skb->data; oethh = (struct ethhdr *)skb->data;
if (!is_zero_ether_addr(p->eth_src)) if (!is_zero_ether_addr(p->eth_src))