CVE-2026-31422 Overview
CVE-2026-31422 is a NULL pointer dereference vulnerability in the Linux kernel's traffic control classifier subsystem (net/sched/cls_flow.c). The flaw exists in the flow_change() function, which calls tcf_block_q() and dereferences q->handle to derive a default baseclass. Shared blocks leave block->q as NULL, triggering a kernel NULL pointer dereference when a flow filter without a fully qualified baseclass is created on a shared block. The issue has been resolved upstream by checking tcf_block_shared() before accessing block->q and returning -EINVAL for shared blocks.
Critical Impact
A local user with CAP_NET_ADMIN can crash the kernel by creating a cls_flow filter on a shared traffic control block without specifying a fully qualified baseclass, resulting in a denial of service.
Affected Products
- Linux kernel — upstream branches prior to the fix commits referenced by kernel.org stable
- Linux distributions shipping vulnerable kernel versions that include the cls_flow classifier
- Systems with CONFIG_NET_CLS_FLOW enabled and shared traffic control blocks in use
Discovery Timeline
- 2026-04-13 - CVE-2026-31422 published to NVD
- 2026-04-18 - Last updated in NVD database
Technical Details for CVE-2026-31422
Vulnerability Analysis
The Linux kernel implements packet classification through the cls_flow module under the traffic control (tc) framework. When a user creates a new flow filter via the tc_new_tfilter() netlink handler, control flows into flow_change() in net/sched/cls_flow.c. The function attempts to derive a default baseclass by calling tcf_block_q() and then dereferencing the queueing discipline's handle field.
Traffic control supports two block types: per-qdisc blocks, where block->q references the owning qdisc, and shared blocks, where block->q is intentionally NULL because the block is associated with multiple qdiscs. The vulnerable code path does not distinguish between these cases. When a filter is added to a shared block without a fully qualified baseclass, the kernel dereferences the NULL block->q pointer and triggers a fault at offset 0x38, as confirmed by the KASAN report in the upstream commit message.
Root Cause
The root cause is missing input validation in flow_change(). The function assumes block->q is always valid before computing the default baseclass. Shared TC blocks, introduced to allow filter sharing across qdiscs, break this assumption. The fix adds an early tcf_block_shared() check and returns -EINVAL when the caller has not specified a fully qualified baseclass on a shared block.
Attack Vector
Exploitation requires local access and CAP_NET_ADMIN in the current network namespace, which is sufficient to issue tc filter operations. An unprivileged user inside a user namespace with this capability can also reach the code path. The attacker creates a shared block, attaches a cls_flow filter without a baseclass, and triggers the NULL dereference. The result is a kernel oops or panic, depending on the panic_on_oops setting, causing denial of service on the host or container host.
No verified public proof-of-concept code is available. The KASAN trace in the upstream commit references flow_change at net/sched/cls_flow.c:508, called from tc_new_tfilter and rtnetlink_rcv_msg, which describes the trigger path.
Detection Methods for CVE-2026-31422
Indicators of Compromise
- Kernel oops or panic messages referencing flow_change in net/sched/cls_flow.c within dmesg or /var/log/kern.log
- KASAN reports showing null-ptr-deref in range [0x0000000000000038-0x000000000000003f] originating from tc_new_tfilter
- Unexpected rtnetlink_rcv_msg call traces tied to RTM_NEWTFILTER messages followed by host instability
Detection Strategies
- Audit tc filter add invocations targeting flow classifiers on shared blocks via auditd rules on the tc binary and netlink RTM_NEWTFILTER messages
- Monitor kernel ring buffer output for crash signatures referencing cls_flow and correlate with the originating process and user namespace
- Inventory hosts running kernel versions that have not yet pulled the referenced stable commits such as 1a280dd4, 415ea0c, 4a09f72, 57f94ac, 9428132, 9bf5fc3, a208c3e, and cc707a4
Monitoring Recommendations
- Forward kernel logs to a central SIEM and alert on BUG: or KASAN: entries that reference net/sched source files
- Track creation of shared TC blocks using tc qdisc commands with the ingress_block or egress_block parameters
- Restrict and log use of CAP_NET_ADMIN inside containers and user namespaces, where this capability is the primary prerequisite for reaching flow_change()
How to Mitigate CVE-2026-31422
Immediate Actions Required
- Apply the upstream kernel patches from the referenced git.kernel.org stable commits or upgrade to a distribution kernel that incorporates them
- Identify systems with CONFIG_NET_CLS_FLOW enabled and prioritize patching multi-tenant hosts, container hosts, and network appliances
- Drop CAP_NET_ADMIN from container and workload security profiles where it is not strictly required
Patch Information
The fix has been merged across multiple stable branches. Relevant upstream commits include Kernel Git Commit 1a280dd4, Kernel Git Commit 415ea0c, Kernel Git Commit 4a09f72, Kernel Git Commit 57f94ac, Kernel Git Commit 9428132, Kernel Git Commit 9bf5fc3, Kernel Git Commit a208c3e, and Kernel Git Commit cc707a4. The patch adds a tcf_block_shared() check in flow_change() and returns -EINVAL for shared blocks lacking a fully qualified baseclass.
Workarounds
- Unload or blacklist the cls_flow kernel module on systems that do not require flow-based classification
- Avoid configuring shared TC blocks via ingress_block/egress_block parameters until the patch is deployed
- Remove CAP_NET_ADMIN from non-trusted user namespaces and container runtimes to block reachability of the vulnerable code path
# Configuration example: block the cls_flow module until patched
echo 'install cls_flow /bin/true' | sudo tee /etc/modprobe.d/disable-cls_flow.conf
sudo rmmod cls_flow 2>/dev/null || true
# Verify the module cannot be loaded
sudo modprobe cls_flow && echo 'still loadable' || echo 'blocked'
Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.