mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-08-31 09:20:13 -04:00
net/sched: account classifier filter allocations to memcg
Allocations in the tc classifier *_change() paths (filter objects,
per-CPU counters, and per-filter aux data) use plain GFP_KERNEL without
__GFP_ACCOUNT, allowing unprivileged users to pin kernel memory outside
memcg charging. The shared tcf_exts_init_ex() action array allocation in
cls_api.c was also uncharged; this patch closes it along with the
per-classifier filter-object/percpu/aux allocations that remain
unaccounted.
Add GFP_KERNEL_ACCOUNT to:
- the shared tcf_exts_init_ex() action array (cls_api.c), common to every
filter of every classifier (32 pointers, 256 bytes);
- the filter-object, per-CPU-counter, and per-filter aux allocations in
cls_basic, cls_bpf, cls_cgroup, cls_flow, cls_flower, cls_fw,
cls_matchall, cls_route and cls_u32;
- the u32_init_knode() replace-path knode allocation (cls_u32.c), which
allocates the same struct tc_u_knode + sel.keys on every replace of an
existing knode and was missed by the create-path-only conversion.
Also fix the cls_basic error path: basic_change() inserts fnew into the
IDR before allocating the per-CPU counter. If alloc_percpu() fails the
errout path kfree'd fnew without idr_remove, leaving a dangling pointer
in the IDR. With GFP_KERNEL_ACCOUNT the percpu alloc becomes failable
on demand (memcg at memory.max), making the dead path attacker-reachable
and burning the handle permanently. Add the idr_remove on the percpu
failure path, matching the basic_set_parms failure-path pattern.
Note: vega@nebusec.ai provided a poc for basic_cls, but it was easy to
extend to the other classifiers.
Conditions to recreate the bug:
- CONFIG_NET_SCHED, CONFIG_NET_CLS_* (the classifier being used),
CONFIG_NET_CLS_ACT, CONFIG_MEMCG, CONFIG_USER_NS, CONFIG_NET_NS.
- Unprivileged user in a fresh user+network namespace (unshare -Urn),
or root with CAP_NET_ADMIN.
- Create a large number of tc filters (e.g. tc filter add dev lo
ingress ... <classifier> ...) while watching a memcg-limited cgroup:
system slab grows far faster than memory.current, pinning kernel
memory outside memcg charging.
Fixes: 0da974f4f3 ("[NET]: Conversions from kmalloc+memset to k(z|c)alloc.")
Reported-by: vega@nebusec.ai
Signed-off-by: Jamal Hadi Salim <jhs@mojatatu.com>
Reviewed-by: Breno Leitao <leitao@debian.org>
Link: https://patch.msgid.link/20260819143733.57538-1-jhs@mojatatu.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
committed by
Jakub Kicinski
parent
e755276c9f
commit
1beb81947e
@@ -3372,7 +3372,8 @@ int tcf_exts_init_ex(struct tcf_exts *exts, struct net *net, int action,
|
||||
* This reference might be taken later from tcf_exts_get_net().
|
||||
*/
|
||||
exts->net = net;
|
||||
exts->actions = kzalloc_objs(struct tc_action *, TCA_ACT_MAX_PRIO);
|
||||
exts->actions = kzalloc_objs(struct tc_action *, TCA_ACT_MAX_PRIO,
|
||||
GFP_KERNEL_ACCOUNT);
|
||||
if (!exts->actions)
|
||||
return -ENOMEM;
|
||||
#endif
|
||||
|
||||
@@ -193,7 +193,7 @@ static int basic_change(struct net *net, struct sk_buff *in_skb,
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
fnew = kzalloc_obj(*fnew);
|
||||
fnew = kzalloc_obj(*fnew, GFP_KERNEL_ACCOUNT);
|
||||
if (!fnew)
|
||||
return -ENOBUFS;
|
||||
|
||||
@@ -212,9 +212,11 @@ static int basic_change(struct net *net, struct sk_buff *in_skb,
|
||||
if (err)
|
||||
goto errout;
|
||||
fnew->handle = handle;
|
||||
fnew->pf = alloc_percpu(struct tc_basic_pcnt);
|
||||
fnew->pf = alloc_percpu_gfp(struct tc_basic_pcnt, GFP_KERNEL_ACCOUNT);
|
||||
if (!fnew->pf) {
|
||||
err = -ENOMEM;
|
||||
if (!fold)
|
||||
idr_remove(&head->handle_idr, fnew->handle);
|
||||
goto errout;
|
||||
}
|
||||
|
||||
|
||||
@@ -352,7 +352,7 @@ static int cls_bpf_prog_from_ops(struct nlattr **tb, struct cls_bpf_prog *prog)
|
||||
if (bpf_size != nla_len(tb[TCA_BPF_OPS]))
|
||||
return -EINVAL;
|
||||
|
||||
bpf_ops = kmemdup(nla_data(tb[TCA_BPF_OPS]), bpf_size, GFP_KERNEL);
|
||||
bpf_ops = kmemdup(nla_data(tb[TCA_BPF_OPS]), bpf_size, GFP_KERNEL_ACCOUNT);
|
||||
if (bpf_ops == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
@@ -403,7 +403,7 @@ static int cls_bpf_prog_from_efd(struct nlattr **tb, struct cls_bpf_prog *prog,
|
||||
}
|
||||
|
||||
if (tb[TCA_BPF_NAME]) {
|
||||
name = nla_memdup(tb[TCA_BPF_NAME], GFP_KERNEL);
|
||||
name = nla_memdup(tb[TCA_BPF_NAME], GFP_KERNEL_ACCOUNT);
|
||||
if (!name) {
|
||||
bpf_prog_put(fp);
|
||||
return -ENOMEM;
|
||||
@@ -443,7 +443,7 @@ static int cls_bpf_change(struct net *net, struct sk_buff *in_skb,
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
prog = kzalloc_obj(*prog);
|
||||
prog = kzalloc_obj(*prog, GFP_KERNEL_ACCOUNT);
|
||||
if (!prog)
|
||||
return -ENOBUFS;
|
||||
|
||||
|
||||
@@ -95,7 +95,7 @@ static int cls_cgroup_change(struct net *net, struct sk_buff *in_skb,
|
||||
if (head && handle != head->handle)
|
||||
return -ENOENT;
|
||||
|
||||
new = kzalloc_obj(*head);
|
||||
new = kzalloc_obj(*head, GFP_KERNEL_ACCOUNT);
|
||||
if (!new)
|
||||
return -ENOBUFS;
|
||||
|
||||
|
||||
@@ -438,7 +438,7 @@ static int flow_change(struct net *net, struct sk_buff *in_skb,
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
fnew = kzalloc_obj(*fnew);
|
||||
fnew = kzalloc_obj(*fnew, GFP_KERNEL_ACCOUNT);
|
||||
if (!fnew)
|
||||
return -ENOBUFS;
|
||||
|
||||
|
||||
@@ -2233,7 +2233,7 @@ static struct fl_flow_mask *fl_create_new_mask(struct cls_fl_head *head,
|
||||
struct fl_flow_mask *newmask;
|
||||
int err;
|
||||
|
||||
newmask = kzalloc_obj(*newmask);
|
||||
newmask = kzalloc_obj(*newmask, GFP_KERNEL_ACCOUNT);
|
||||
if (!newmask)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
@@ -2394,7 +2394,7 @@ static int fl_change(struct net *net, struct sk_buff *in_skb,
|
||||
goto errout_tb;
|
||||
}
|
||||
|
||||
fnew = kzalloc_obj(*fnew);
|
||||
fnew = kzalloc_obj(*fnew, GFP_KERNEL_ACCOUNT);
|
||||
if (!fnew) {
|
||||
err = -ENOBUFS;
|
||||
goto errout_tb;
|
||||
|
||||
@@ -276,7 +276,7 @@ static int fw_change(struct net *net, struct sk_buff *in_skb,
|
||||
if (f->id != handle && handle)
|
||||
return -EINVAL;
|
||||
|
||||
fnew = kzalloc_obj(struct fw_filter);
|
||||
fnew = kzalloc_obj(struct fw_filter, GFP_KERNEL_ACCOUNT);
|
||||
if (!fnew)
|
||||
return -ENOBUFS;
|
||||
|
||||
@@ -330,7 +330,7 @@ static int fw_change(struct net *net, struct sk_buff *in_skb,
|
||||
rcu_assign_pointer(tp->root, head);
|
||||
}
|
||||
|
||||
f = kzalloc_obj(struct fw_filter);
|
||||
f = kzalloc_obj(struct fw_filter, GFP_KERNEL_ACCOUNT);
|
||||
if (f == NULL)
|
||||
return -ENOBUFS;
|
||||
|
||||
|
||||
@@ -189,7 +189,7 @@ static int mall_change(struct net *net, struct sk_buff *in_skb,
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
new = kzalloc_obj(*new);
|
||||
new = kzalloc_obj(*new, GFP_KERNEL_ACCOUNT);
|
||||
if (!new)
|
||||
return -ENOBUFS;
|
||||
|
||||
@@ -201,7 +201,7 @@ static int mall_change(struct net *net, struct sk_buff *in_skb,
|
||||
handle = 1;
|
||||
new->handle = handle;
|
||||
new->flags = userflags;
|
||||
new->pf = alloc_percpu(struct tc_matchall_pcnt);
|
||||
new->pf = alloc_percpu_gfp(struct tc_matchall_pcnt, GFP_KERNEL_ACCOUNT);
|
||||
if (!new->pf) {
|
||||
err = -ENOMEM;
|
||||
goto err_alloc_percpu;
|
||||
|
||||
@@ -455,7 +455,7 @@ static int route4_set_parms(struct net *net, struct tcf_proto *tp,
|
||||
h1 = to_hash(nhandle);
|
||||
b = rtnl_dereference(head->table[h1]);
|
||||
if (!b) {
|
||||
b = kzalloc_obj(struct route4_bucket);
|
||||
b = kzalloc_obj(struct route4_bucket, GFP_KERNEL_ACCOUNT);
|
||||
if (b == NULL)
|
||||
return -ENOBUFS;
|
||||
|
||||
@@ -524,7 +524,7 @@ static int route4_change(struct net *net, struct sk_buff *in_skb,
|
||||
return -EINVAL;
|
||||
|
||||
err = -ENOBUFS;
|
||||
f = kzalloc_obj(struct route4_filter);
|
||||
f = kzalloc_obj(struct route4_filter, GFP_KERNEL_ACCOUNT);
|
||||
if (!f)
|
||||
goto errout;
|
||||
|
||||
|
||||
@@ -825,7 +825,7 @@ static struct tc_u_knode *u32_init_knode(struct net *net, struct tcf_proto *tp,
|
||||
struct tc_u32_sel *s = &n->sel;
|
||||
struct tc_u_knode *new;
|
||||
|
||||
new = kzalloc_flex(*new, sel.keys, s->nkeys);
|
||||
new = kzalloc_flex(*new, sel.keys, s->nkeys, GFP_KERNEL_ACCOUNT);
|
||||
if (!new)
|
||||
return NULL;
|
||||
|
||||
@@ -1114,15 +1114,16 @@ static int u32_change(struct net *net, struct sk_buff *in_skb,
|
||||
goto erridr;
|
||||
}
|
||||
|
||||
n = kzalloc_flex(*n, sel.keys, s->nkeys);
|
||||
n = kzalloc_flex(*n, sel.keys, s->nkeys, GFP_KERNEL_ACCOUNT);
|
||||
if (n == NULL) {
|
||||
err = -ENOBUFS;
|
||||
goto erridr;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_CLS_U32_PERF
|
||||
n->pf = __alloc_percpu(struct_size(n->pf, kcnts, s->nkeys),
|
||||
__alignof__(struct tc_u32_pcnt));
|
||||
n->pf = __alloc_percpu_gfp(struct_size(n->pf, kcnts, s->nkeys),
|
||||
__alignof__(struct tc_u32_pcnt),
|
||||
GFP_KERNEL_ACCOUNT);
|
||||
if (!n->pf) {
|
||||
err = -ENOBUFS;
|
||||
goto errfree;
|
||||
@@ -1144,7 +1145,7 @@ static int u32_change(struct net *net, struct sk_buff *in_skb,
|
||||
goto errout;
|
||||
|
||||
#ifdef CONFIG_CLS_U32_MARK
|
||||
n->pcpu_success = alloc_percpu(u32);
|
||||
n->pcpu_success = alloc_percpu_gfp(u32, GFP_KERNEL_ACCOUNT);
|
||||
if (!n->pcpu_success) {
|
||||
err = -ENOMEM;
|
||||
goto errout;
|
||||
|
||||
Reference in New Issue
Block a user