Re: [PATCH net v4] net/sched: clamp skb->queue_mapping before indexing the tx queue

From
netdev-bot+sashiko@kernel.org
Date
2026-10-25 12:27:20
Also in
bpf, stable
Reply to
Jamal Hadi Salim, 2026-10-21
Thank you for your contribution! Sashiko AI review found 2 potential
issue(s) to consider.

Critical: 0 · High: 1 · Medium: 0 · Low: 1

If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).

- [High] Incomplete fix: the patch re-caps a queue_mapping stored by tc
  BPF only at dequeue time, in dequeue_skb(), __skb_dequeue_bad_txq()…
- [Low] For TCQ_F_ONETXQUEUE qdiscs, qdisc_cap_skb_tx_queue() overwrites
  every skb's queue_mapping with q->dev_queue's index, including valid…

--

net/sched: clamp skb->queue_mapping before indexing the tx queue

This patch re-caps skb->queue_mapping in the qdisc dequeue path
(dequeue_skb(), __skb_dequeue_bad_txq() and the bulk helpers). The goal is
to stop a tc BPF store made after netdev_core_pick_tx() from indexing past
dev->_tx[]. For TCQ_F_ONETXQUEUE qdiscs the mapping is pinned to
q->dev_queue; for other qdiscs it goes through netdev_cap_txqueue().

Closes: https://lore.kernel.org/netdev/179033713973.2160803.4914570693994398206@kernel.org/ (local)
Link: https://netdev-ai.bots.linux.dev/sashiko/#/patchset/QDISC-9R8V.v2.20260924072708%40mojatatu.com
Link: https://netdev-ai.bots.linux.dev/sashiko/#/patchset/QDISC-MD1X.v1.20261005193231%40mojatatu.com
Link: https://netdev-ai.bots.linux.dev/sashiko/#/patchset/QDISC-MD1X.v3.r0.20261015165943-1%40mojatatu.com
Link: https://lore.kernel.org/netdev/20261015125822.GH83879@horms.kernel.org/ (local)
quoted hunk ↗ jump to hunk
diff --git a/net/sched/sch_generic.c b/net/sched/sch_generic.c
index 6f6a6f0d5eb0d..fdd070840cb55 100644
--- a/net/sched/sch_generic.c
+++ b/net/sched/sch_generic.c
@@ -96,9 +96,37 @@ static void qdisc_maybe_clear_missed(struct Qdisc *q,
 
 #define SKB_XOFF_MAGIC ((struct sk_buff *)1UL)
 
+/* A tc BPF program may change skb->queue_mapping after the tx queue
+ * was picked, so re-cap it before use. A TCQ_F_ONETXQUEUE qdisc is
+ * bound to a single tx queue, so pin the mapping to q->dev_queue.
+ */
+static u16 qdisc_cap_skb_tx_queue(struct Qdisc *q, struct sk_buff *skb)
+{
+	u16 queue = skb_get_queue_mapping(skb);
+	struct net_device *dev = qdisc_dev(q);
+	u16 capped;
+
+	if (q->flags & TCQ_F_ONETXQUEUE)
+		capped = q->dev_queue - netdev_get_tx_queue(dev, 0);
[Severity: Low]
This isn't a memory safety issue, and the commit message does describe
the pinning. Should the change in steering behaviour still be called out
for stable users?

For TCQ_F_ONETXQUEUE qdiscs, this overwrites every skb's queue_mapping
with the index of q->dev_queue. That includes valid in-range values set
by a filter on a per-queue child, for example in tcf_skbedit_act():

	if (params->flags & SKBEDIT_F_QUEUE_MAPPING &&
	    skb->dev->real_num_tx_queues > params->queue_mapping) {

Before this patch, qdisc_restart() used skb_get_tx_queue() and honoured
an in-range mapping on the head skb. Under mq, mqprio, htb offload or
taprio offload children on multiqueue devices, that steering is now
silently replaced with the child's own queue.

The pre-patch try_bulk_dequeue_skb() already sent followers under the
head's tx lock whatever their mapping was. The TCQ_F_ONETXQUEUE comment
in include/net/sch_generic.h also documents the invariant being restored.

However, Documentation/networking/tc-queue-filters.rst describes
"skbedit queue_mapping $tx_qid" without saying where the filter has to
sit. Would a note there help?
+	else
+		capped = netdev_cap_txqueue(dev, queue);
[ ... ]
quoted hunk ↗ jump to hunk
@@ -322,6 +358,8 @@ static struct sk_buff *dequeue_skb(struct Qdisc *q, bool *validate,
 	skb = q->dequeue(q);
 	if (skb) {
 bulk:
+		/* cap the head; the bulk helpers cap every appended element */
+		qdisc_cap_skb_tx_queue(q, skb);
[Severity: High]
This caps the mapping at dequeue time. Can the same BPF-stored value
still reach a consumer on the enqueue side that uses it as an array
index?

taprio_enqueue() in net/sched/sch_taprio.c indexes q->qdiscs[], which
has dev->num_tx_queues entries, without a bounds check:

net/sched/sch_taprio.c:taprio_enqueue() {
    ...
	queue = skb_get_queue_mapping(skb);

	child = q->qdiscs[queue];
	if (unlikely(child == &noop_qdisc))
    ...
}

This path looks reachable with software-mode taprio as the root:

  - taprio_attach() installs the taprio root on every tx queue
  - a prio child is grafted onto a taprio class
  - a cls_bpf filter on that prio stores a large
    __sk_buff->queue_mapping (bpf_convert_ctx_access() only rejects
    values >= NO_QUEUE_MAPPING)
  - a duplicating netem sits on one of the prio bands

netem_enqueue() clones the skb, and __copy_skb_header() copies
queue_mapping. It then re-enqueues the clone at the root:

net/sched/sch_netem.c:netem_enqueue() {
    ...
	if (skb2) {
		struct Qdisc *rootq = qdisc_root_bh(sch);

		skb2->tc_depth++; /* prevent duplicating a dup... */
		rootq->enqueue(skb2, rootq, to_free);
    ...
}

netem inherits its dev_queue from prio, and taprio is grafted on every
tx queue, so rootq is the taprio root. That gives:

netem_enqueue()
  taprio_enqueue()
    child = q->qdiscs[queue]   /* out of bounds */
    taprio_enqueue_one()
      qdisc_enqueue()          /* calls child->enqueue */

All of this runs before dequeue_skb(), so the new cap never sees the
clone.

Commit ea4d4b5dddb5 cited this taprio_enqueue() out-of-bounds access. It
left the "tc BPF program attached to a transmit qdisc" store path for a
separate followup.

Does closing that case need a check on the store or enqueue side, or in
taprio_enqueue() itself?
 		if (qdisc_may_bulk(q))
 			try_bulk_dequeue_skb(q, skb, txq, packets, budget);
 		else
-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/QDISC-MD1X.v4.r0.20261021042722-1%40mojatatu.com
Thread (2 messages) 2 messages, 2 authors, last reply 10d ago
Keyboard shortcuts
hback out one level
jnext message in thread / next series-diff position
kprevious message in thread / previous series-diff position
ldrill in
Escclose help / fold thread tree
?toggle this help