From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 0AF943CB2EA; Tue, 13 Oct 2026 12:19:20 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1791893961; cv=none; b=WesfEt9pXKi8ZZJTVVfC1xI20NROZNFIJ5mN2HU6zsQmTAR70SrygkgFsBsgmIgWziFPVNtT+afHri/oHSVHz5SYSwkDxqqGFpPIxdGoe4SvByN6Qd6Foi+aGdAR1qEBrQtHrzPsHi/BaHt8JNNXCCxptk0lRvRO6S5EQRiNS7k= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1791893961; c=relaxed/simple; bh=ogbIcrc+s22XzdxDIZF2J65txio2VB7tEUlZXWfctiI=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=HoQwR004kOhlpQMObiYkbGZXrRO3WedrrLQuoYiBOBKbjFwoDvqAy5HNoK+V55km3h23Nem0apJMmNAovAYtOmO7BFUrhnPU06TN4TQGs/YAAsB0VbxDnXYkrHvPLIl08CJwrZa15WxuUePT4Apx4flFh9SurVu2Hv7TnCgeyC8= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linuxfoundation.org header.i=@linuxfoundation.org header.b=GFzmeUWp; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linuxfoundation.org header.i=@linuxfoundation.org header.b="GFzmeUWp" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 4AA8F1F000FF; Tue, 13 Oct 2026 12:19:19 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linuxfoundation.org; s=korg; t=1791893959; bh=S0OU2Hje4BTI8TaLdH7v2BZ/IhgsT9AFjHyMyVF8spg=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=GFzmeUWp2zdsh+HUPAv7xoRKLd+iqiYU359tIl1EDTcbC7wD/Bz8HXNSzUc9v0npz 7BAitENKbFdCns7Qt37bId2DU1QTOgtnhST6fjfFIf4YiiF0amuM4pEg1b5tDAggsY FvYDozZW6OEDlL7c/Iivut4U1Ija0G45V+zeOVgE= From: Greg Kroah-Hartman To: stable@vger.kernel.org Cc: Greg Kroah-Hartman , patches@lists.linux.dev, vega@nebusec.ai, Jamal Hadi Salim , Breno Leitao , Jakub Kicinski , Sasha Levin Subject: [PATCH 6.18 401/621] net/sched: account classifier filter allocations to memcg Date: Tue, 13 Oct 2026 13:36:36 +0200 Message-ID: <20261013112930.358130124@linuxfoundation.org> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261013112920.855194808@linuxfoundation.org> References: <20261013112920.855194808@linuxfoundation.org> User-Agent: quilt/0.69 X-stable: review X-Patchwork-Hint: ignore Precedence: bulk X-Mailing-List: patches@lists.linux.dev List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit 6.18-stable review patch. If anyone has any objections, please let me know. ------------------ From: Jamal Hadi Salim [ Upstream commit 1beb81947eb486716af80db7a584f9e9fef7e003 ] 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 ... ...) while watching a memcg-limited cgroup: system slab grows far faster than memory.current, pinning kernel memory outside memcg charging. Fixes: 0da974f4f303 ("[NET]: Conversions from kmalloc+memset to k(z|c)alloc.") Reported-by: vega@nebusec.ai Signed-off-by: Jamal Hadi Salim Reviewed-by: Breno Leitao Link: https://patch.msgid.link/20260819143733.57538-1-jhs@mojatatu.com Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- net/sched/cls_api.c | 2 +- net/sched/cls_basic.c | 6 ++++-- net/sched/cls_bpf.c | 6 +++--- net/sched/cls_cgroup.c | 2 +- net/sched/cls_flow.c | 2 +- net/sched/cls_flower.c | 4 ++-- net/sched/cls_fw.c | 4 ++-- net/sched/cls_matchall.c | 4 ++-- net/sched/cls_route.c | 4 ++-- net/sched/cls_u32.c | 11 ++++++----- 10 files changed, 24 insertions(+), 21 deletions(-) diff --git a/net/sched/cls_api.c b/net/sched/cls_api.c index e56de2d3dc102..975d22dcf6b81 100644 --- a/net/sched/cls_api.c +++ b/net/sched/cls_api.c @@ -3386,7 +3386,7 @@ int tcf_exts_init_ex(struct tcf_exts *exts, struct net *net, int action, */ exts->net = net; exts->actions = kcalloc(TCA_ACT_MAX_PRIO, sizeof(struct tc_action *), - GFP_KERNEL); + GFP_KERNEL_ACCOUNT); if (!exts->actions) return -ENOMEM; #endif diff --git a/net/sched/cls_basic.c b/net/sched/cls_basic.c index ecfaa4f9a04ea..d5ef0a66a73fa 100644 --- a/net/sched/cls_basic.c +++ b/net/sched/cls_basic.c @@ -193,7 +193,7 @@ static int basic_change(struct net *net, struct sk_buff *in_skb, return -EINVAL; } - fnew = kzalloc(sizeof(*fnew), GFP_KERNEL); + fnew = kzalloc(sizeof(*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; } diff --git a/net/sched/cls_bpf.c b/net/sched/cls_bpf.c index a6003fa35c7c5..66b525a852875 100644 --- a/net/sched/cls_bpf.c +++ b/net/sched/cls_bpf.c @@ -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(sizeof(*prog), GFP_KERNEL); + prog = kzalloc(sizeof(*prog), GFP_KERNEL_ACCOUNT); if (!prog) return -ENOBUFS; diff --git a/net/sched/cls_cgroup.c b/net/sched/cls_cgroup.c index 424252982d6ae..8f6563e50e63e 100644 --- a/net/sched/cls_cgroup.c +++ b/net/sched/cls_cgroup.c @@ -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(sizeof(*head), GFP_KERNEL); + new = kzalloc(sizeof(*head), GFP_KERNEL_ACCOUNT); if (!new) return -ENOBUFS; diff --git a/net/sched/cls_flow.c b/net/sched/cls_flow.c index 9b472578bf75f..61632e64d1fb8 100644 --- a/net/sched/cls_flow.c +++ b/net/sched/cls_flow.c @@ -438,7 +438,7 @@ static int flow_change(struct net *net, struct sk_buff *in_skb, return -EOPNOTSUPP; } - fnew = kzalloc(sizeof(*fnew), GFP_KERNEL); + fnew = kzalloc(sizeof(*fnew), GFP_KERNEL_ACCOUNT); if (!fnew) return -ENOBUFS; diff --git a/net/sched/cls_flower.c b/net/sched/cls_flower.c index 099ff6a3e1f51..5e61bf334ed84 100644 --- a/net/sched/cls_flower.c +++ b/net/sched/cls_flower.c @@ -2237,7 +2237,7 @@ static struct fl_flow_mask *fl_create_new_mask(struct cls_fl_head *head, struct fl_flow_mask *newmask; int err; - newmask = kzalloc(sizeof(*newmask), GFP_KERNEL); + newmask = kzalloc(sizeof(*newmask), GFP_KERNEL_ACCOUNT); if (!newmask) return ERR_PTR(-ENOMEM); @@ -2398,7 +2398,7 @@ static int fl_change(struct net *net, struct sk_buff *in_skb, goto errout_tb; } - fnew = kzalloc(sizeof(*fnew), GFP_KERNEL); + fnew = kzalloc(sizeof(*fnew), GFP_KERNEL_ACCOUNT); if (!fnew) { err = -ENOBUFS; goto errout_tb; diff --git a/net/sched/cls_fw.c b/net/sched/cls_fw.c index fd9c6c2815a1c..e32eecdf675fe 100644 --- a/net/sched/cls_fw.c +++ b/net/sched/cls_fw.c @@ -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(sizeof(struct fw_filter), GFP_KERNEL); + fnew = kzalloc(sizeof(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(sizeof(struct fw_filter), GFP_KERNEL); + f = kzalloc(sizeof(struct fw_filter), GFP_KERNEL_ACCOUNT); if (f == NULL) return -ENOBUFS; diff --git a/net/sched/cls_matchall.c b/net/sched/cls_matchall.c index f03bf5da39ee8..762ba74aa7be0 100644 --- a/net/sched/cls_matchall.c +++ b/net/sched/cls_matchall.c @@ -189,7 +189,7 @@ static int mall_change(struct net *net, struct sk_buff *in_skb, return -EINVAL; } - new = kzalloc(sizeof(*new), GFP_KERNEL); + new = kzalloc(sizeof(*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; diff --git a/net/sched/cls_route.c b/net/sched/cls_route.c index 5a0ddcf3e2b40..ac32c083f6335 100644 --- a/net/sched/cls_route.c +++ b/net/sched/cls_route.c @@ -449,7 +449,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(sizeof(struct route4_bucket), GFP_KERNEL); + b = kzalloc(sizeof(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(sizeof(struct route4_filter), GFP_KERNEL); + f = kzalloc(sizeof(struct route4_filter), GFP_KERNEL_ACCOUNT); if (!f) goto errout; diff --git a/net/sched/cls_u32.c b/net/sched/cls_u32.c index ea021dc39fea3..ac344aabe8595 100644 --- a/net/sched/cls_u32.c +++ b/net/sched/cls_u32.c @@ -841,7 +841,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(struct_size(new, sel.keys, s->nkeys), GFP_KERNEL); + new = kzalloc(struct_size(new, sel.keys, s->nkeys), GFP_KERNEL_ACCOUNT); if (!new) return NULL; @@ -1139,15 +1139,16 @@ static int u32_change(struct net *net, struct sk_buff *in_skb, goto erridr; } - n = kzalloc(struct_size(n, sel.keys, s->nkeys), GFP_KERNEL); + n = kzalloc(struct_size(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; @@ -1169,7 +1170,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; -- 2.53.0