CVE-2026-98797: net: cap skb->queue_mapping when the tx queue is picked
In the Linux kernel, the following vulnerability has been resolved:
net: cap skb->queuemapping when the tx queue is picked
skbedit can set skb->queuemapping and raise the per-CPU skiptxqueue flag so devqueuexmit() honours the mapping. devqueuexmit() cleared the flag before schhandleegress() and only read it afterwards, so the flag was not confined to the xmit that set it: a nested xmit (mirred redirect or mirror, or a drop after skbedit) could set the flag and the outer xmit would consume it for an skb that never went through skbedit.
A forwarded packet still carries the ingress NIC's rxqueue + 1 in skb->queuemapping, so the outer device then indexes its tx queue state with that stale value. Taprio's child array q->qdiscs[] is sized to the device's queue count, so taprioenqueue() indexes past its allocation and dereferences the result as a struct Qdisc .
We (ab)use the skb->nfskipegress which means "skip netfilter egress for this packet" to tag to "am I in tc egress?". Despite the overload I dont see it as a conflict since the marker is set only around the single schhandleegress() call and ingress path is guarded by tcatingress. I will send a followup(net-next) patch once this hits net-next to rename the skb->nfskipegress bit/flag to skb->skipegress
Arm the flag only from the egress classifier that can use it: raise skiptxqueue from tcfskbeditact() only when it runs inside schhandleegress(), thanks to skb->nfskipegress. An egress qdisc classifier runs in q->enqueue(), after the tx queue has been picked, so a mapping it sets cannot affect the current packet; arming the flag there only pollutes it for a later xmit. Then own the flag for the xmit frame the egress hook runs in: save the incoming value and clear it just before schhandleegress(), and restore it after the hook - on the consumed (drop) path, or, in the same call that reads it, on the surviving path. The save and the restores stay inside the egressneededkey static branch, so a packet pays for them only when egress hooks are active (2f1e85b1aee4).
Store the value netdevcaptxqueue() selected back into skb->queuemapping in netdevtxqueuemapping(), as netdevcorepicktx() already does, so the skiptxqueue path never hands a later reader on the xmit path a mapping the device cannot serve. A store made still later in the same frame, by a tc BPF program attached to a transmit qdisc, is outside this path and is not re-capped; a separate followup will resolve that path.
netdevxmitskiptxqueue() returns the previous flag value so the save-and-clear is one call, and a no-op stub is provided when CONFIGNETEGRESS is disabled. skb->nfskipegress is compiled under CONFIGNETEGRESS rather than CONFIGNETFILTERSKIPEGRESS, so skbattcegress() is valid whenever the egress path is built.
A local user in a network namespace can redirect a packet from a device with more TX queues to one with fewer after setting a mapping valid only on the larger device. That reaches these reads and, under KASAN, faults with "slab-out-of-bounds in taprioenqueue".
Conditions to recreate the bug: the report's own trigger is a local user with CAPNETADMIN in a network namespace, so no eBPF program is needed. With CONFIGNETSCHTAPRIO=y, CONFIGNETACTSKBEDIT=y, CONFIGNETACTMIRRED=y, CONFIGNETCLSMATCHALL=y, CONFIGNETSCHPRIO=y and KASAN enabled, create qa (3 queues), qb (2 queues) and qc (1 queue) as dummy devices; put a taprio root on qb (numtc 1, queues 2@0) and clsact on all three; then add an egress matchall filter on every device. On qa: "action skbedit queuemapping 2 pipe action mirred egress redirect dev qb". On qb: "action mirred egress mirror dev qc". On qc: "action skbedit queuemapping 0 pipe". Send one packet out qa. qc's skbedit sets the flag while qb's outer xmit is in flight; without the fix qb consumes it and reads its two-entry taprio child array with the forwarded packet's stale mapping. A qc whose skb ---truncated---