qdisc_restart() computes txq = skb_get_tx_queue(dev, skb), which indexes dev->_tx[] with skb->queue_mapping and no bound. A tc BPF program attached to a transmit qdisc can store an arbitrary __sk_buff.queue_mapping after the tx queue was picked; that store is not re-capped on this path (the existing cap in netdev_tx_queue_mapping() only covers the skip_txqueue path), so an out-of-range mapping indexes past dev->_tx[]: BUG: KASAN: slab-out-of-bounds in sch_direct_xmit+0x744/0x89c Read of size 8 at addr ffff00000e0802d0 by task A-254-2_S4/100 sch_direct_xmit+0x744/0x89c __qdisc_run+0x34c/0x1d04 __dev_queue_xmit+0x1aec/0x306c Allocated by task 1: alloc_netdev_mqs+0x758/0xfa0 The same stale value is then handed to dev_hard_start_xmit(), which reports the same fault. Re-cap the mapping with netdev_cap_txqueue() before it is used to pick the tx queue. Fixes: 10c51b56232d ("net: add skb_get_tx_queue() helper") Assisted-by: LLM Signed-off-by: Dudu Lu --- Tested base ----------- Detected on Linux 7.3-rc4 (commit 93f51579e7df), arm64 QEMU guest. Re-checked against mainline f47d223627f8 (v7.3-341): the affected code is unchanged between the two, so the bug is still present there. Checked against linux-next next-20261021 (commit 9a482184d910): the affected code is unchanged there as well, so the bug is still present in linux-next too. Reproducer status ----------------- A reproducer was written and executed on an arm64 QEMU guest (KASAN, CONFIG_NET_SCH_PRIO=y, cls_bpf); it fires at the claimed sink, and still fires on a kernel built with ea4d4b5dddb5 applied. The reproducer source is not posted here per Documentation/process/security-bugs.rst; it is available on request. Triggering conditions --------------------- CAP_NET_ADMIN (attach the classifier and qdisc). The residual is the store made by a tc BPF program attached to a transmit qdisc, which ea4d4b5dddb5 explicitly scopes out. Notes ----- Built and checkpatch-clean: this patch was applied on top of 7.3-rc4 (commit 93f51579e7df) and a full arm64 kernel (defconfig) was built with it -- no new warnings or errors. scripts/checkpatch.pl reports 0 errors, 0 warnings. net/sched/sch_generic.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/net/sched/sch_generic.c b/net/sched/sch_generic.c index 6f6a6f0d5eb0..c326d12942e5 100644 --- a/net/sched/sch_generic.c +++ b/net/sched/sch_generic.c @@ -432,6 +432,13 @@ static inline bool qdisc_restart(struct Qdisc *q, int *packets, int budget) root_lock = qdisc_lock(q); dev = qdisc_dev(q); + /* A tc BPF program attached to a transmit qdisc can store an + * arbitrary skb->queue_mapping after the tx queue was picked, so + * the value reaching here is not necessarily one the device can + * serve. Re-cap it before it indexes dev->_tx[]. + */ + skb_set_queue_mapping(skb, + netdev_cap_txqueue(dev, skb_get_queue_mapping(skb))); txq = skb_get_tx_queue(dev, skb); return sch_direct_xmit(skb, q, dev, txq, root_lock, validate);