CVE-2026-90099: net/sched: account classifier filter allocations to memcg
In the Linux kernel, the following vulnerability has been resolved:
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 GFPKERNEL without GFPACCOUNT, allowing unprivileged users to pin kernel memory outside memcg charging. The shared tcfextsinitex() action array allocation in clsapi.c was also uncharged; this patch closes it along with the per-classifier filter-object/percpu/aux allocations that remain unaccounted.
Add GFPKERNELACCOUNT to: - the shared tcfextsinitex() action array (clsapi.c), common to every filter of every classifier (32 pointers, 256 bytes); - the filter-object, per-CPU-counter, and per-filter aux allocations in clsbasic, clsbpf, clscgroup, clsflow, clsflower, clsfw, clsmatchall, clsroute and clsu32; - the u32initknode() replace-path knode allocation (clsu32.c), which allocates the same struct tcuknode + sel.keys on every replace of an existing knode and was missed by the create-path-only conversion.
Also fix the clsbasic error path: basicchange() inserts fnew into the IDR before allocating the per-CPU counter. If allocpercpu() fails the errout path kfree'd fnew without idrremove, leaving a dangling pointer in the IDR. With GFPKERNELACCOUNT the percpu alloc becomes failable on demand (memcg at memory.max), making the dead path attacker-reachable and burning the handle permanently. Add the idrremove on the percpu failure path, matching the basicsetparms failure-path pattern.
Note: vega@nebusec.ai provided a poc for basiccls, but it was easy to extend to the other classifiers.
Conditions to recreate the bug: - CONFIGNETSCHED, CONFIGNETCLS (the classifier being used), CONFIGNETCLSACT, CONFIGMEMCG, CONFIGUSERNS, CONFIGNETNS. - Unprivileged user in a fresh user+network namespace (unshare -Urn), or root with CAPNETADMIN. - 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.
Affected Software
Event History
Frequently Asked Questions
Who can trigger the uncharged kernel-memory allocations?
The issue is described as allowing unprivileged users to pin kernel memory outside memcg charging through tc classifier change paths. Exposure therefore depends on whether unprivileged users can perform the relevant traffic-control classifier operations.
Which traffic-control classifiers are involved?
The affected allocation paths listed are cls_basic, cls_bpf, cls_cgroup, cls_flow, cls_flower, cls_fw, cls_matchall, cls_route, and cls_u32. The shared tcf_exts_init_ex() action-array allocation in cls_api.c is also affected and is common to filters of every classifier.
Are replacement operations affected as well as new filter creation?
Yes. The u32 replace path is specifically called out: u32_init_knode() allocates a tc_u_knode structure and selector keys on every replacement of an existing knode, and this allocation was previously missed by a create-path-only conversion.
What changes in the resolved code?
The relevant filter-object, per-CPU counter, auxiliary-data, and action-array allocations are changed from GFP_KERNEL to GFP_KERNEL_ACCOUNT so they are charged to the memory cgroup. The cls_basic error path is also corrected after inserting a new filter into the IDR before per-CPU counter allocation.