Bali, Indonesia
Insights

When Hierarchical Qdiscs Break: Anatomy of the TEQL Use-After-Free in net/sched

October 14, 2026
0:00
For more than two decades, the Linux kernel's traffic control framework (net/sched) has served as the quiet backbone for packet scheduling, policing, and shaping across millions of production edge nodes. It is an intricate, highly extensible subsystem where generic network device queues (netdev_queue) interface with modular queuing disciplines (Qdisc). Yet, deep within this subsystem lurk historical artifacts designed during the late 1990s. One such relic is the True Equalizer Link (sch_teql), an early software load-balancing scheduler originally devised to aggregate multiple serial or asynchronous lines across distinct netdevices. The danger of maintaining such legacy abstractions became acute with the disclosure of CVE-2026-23074 (documented in an analysis of CVE-2026-23074 and its operational ramifications). Discovered by researcher GangMin Kim, the vulnerability highlights a fundamental disconnect between modern hierarchical qdisc assumptions and legacy module lifecycle invariants. When sch_teql is forced into a role it was never architected to fulfill—operating as a leaf or child qdisc nested beneath a classful scheduler like HTB or PRIO—the kernel enters a corrupted state during interface teardown, ultimately producing a exploitable use-after-free (UAF).
To understand why sch_teql fails so catastrophically when nested, we need to inspect how modern classful qdiscs handle encapsulation and state ownership. Under typical traffic control architectures, a root qdisc attaches directly to a network device's transmit queue (dev_queue). Classful schedulers divide available bandwidth among individual classes, each of which can hold its own child qdisc attached via the standard qdisc_graft mechanism. When a child qdisc attaches to an inner class, it assumes standard lifecycle encapsulation. The parent class assumes primary responsibility for triggering dequeue operations, tracking backlog counters, and invoking the child's reset() and destroy() callbacks whenever the class hierarchy is dismantled or the interface is brought down via dev_close(). In contrast, root qdiscs bind directly to the underlying struct netdev_queue and interact with the kernel's softirq packet processing loop (NET_TX_SOFTIRQ). The fundamental conflict is that sch_teql completely rejects this standard tree containment model. Rather than confining its packet references and device linkages to the queue on which it is instantiated, a teql instance joins a communal master device (teqlX). Packet scheduling is performed across the pool of slave network devices linked through an internal list (teql_master). When packet transmission occurs, teql bypasses standard tree propagation by coordinating state directly across the slave devices' transmit rings.
The architectural friction turns deadly during hierarchy teardown or replacement. When an operator runs tc qdisc add ... parent <classid> teql, the kernel permits the attachment because sch_teql historically implemented standard qdisc operation handlers (Qdisc_ops) without explicitly rejecting child attachments in its initialization hook (teql_qdisc_init).
BASH
# Creating an unconventional hierarchy that destabilizes teql
tc qdisc add dev eth0 root handle 1: htb default 10
tc class add dev eth0 parent 1: classid 1:10 htb rate 100mbit
tc qdisc add dev eth0 parent 1:10 handle 20: teql0
When this hierarchy is destroyed—either via an explicit tc qdisc del command, an interface unregister event, or container namespace termination—the classful parent cleans up its classes by invoking qdisc_put() and releasing child data structures. However, sch_teql maintains internal linked-list references across the master equalizer device and retains stale assumptions about device queue scheduling callbacks. During parent teardown, the memory slab backing the Qdisc structure for sch_teql is freed back to the kernel slab allocator (kmalloc caches). However, the lingering registrations inside the equalizer device's transmit tables still point to the freed memory addresses. When subsequent network egress sweeps or queue purges traverse the master teql device, the kernel dereferences these dangling pointers. A local adversary with the ability to manage network namespaces (such as an unprivileged container root possessing CAP_NET_ADMIN inside its own user namespace) can reclaim these freed slab entries with attacker-controlled payload structures, converting an invalid architectural hierarchy into full kernel privilege escalation.
The ultimate patch upstream restricts sch_teql so that it can only ever be bound as a root discipline on an actual physical or virtual device, flatly refusing assignment to inner classes. However, enterprise production environments often lag kernel errata schedules, requiring defence-in-depth mitigations at both the host runtime and platform boundary layers. First, evaluate whether the teql module has any operational reason to exist within your kernel runtime. On virtually all contemporary infrastructure, multi-link bonding and traffic aggregation are handled using modern channel bonding (bonding), teaming (team), or multipath routing (MPTCP). The sch_teql driver is an archaic module that should never be dynamically loaded on standard cloud hosts. Disabling kernel auto-loading of sch_teql entirely neutralizes the local attack surface:
BASH
# Prevent kernel auto-loading of the legacy equalizer scheduler
echo 'install sch_teql /bin/true' > /etc/modprobe.d/disable-teql.conf
Second, enforce boundaries around unprivileged user namespaces. Exploits against network traffic control almost universally rely on low-privileged users spinning up transient user and network namespaces (CLONE_NEWUSER | CLONE_NEWNET) to gain localized CAP_NET_ADMIN capabilities. If your workloads do not strictly depend on rootless container builders, consider disabling unprivileged user namespace instantiation using sysctl (kernel.unprivileged_userns_clone = 0 or distribution equivalents via AppArmor or user namespace path restrictions). Finally, inspect existing traffic control configurations using modern tooling. A simple shell pipeline auditing all active non-root disciplines can reveal abnormal attachment patterns across software bridges and physical links:
BASH
# Audit non-root qdiscs active across network namespaces
ip netns exec <target_ns> tc qdisc show
CVE-2026-23074 serves as a potent case study in the hidden cost of Linux kernel backwards compatibility. When subsystems as fundamental as net/sched evolve to support sophisticated multiqueue NICs and complex hierarchical schedulers, older modules that rely on implicit global state or unconventional cross-device mechanics inevitably turn into landmines. Keeping infrastructure secure requires not only applying kernel vendor errata promptly, but proactively reducing module attack surfaces by pruning obsolete kernel facilities before an exploit chain proves they are broken.