BPF List
 help / color / mirror / Atom feed
From: Simon Horman <horms@kernel.org>
To: Jamal Hadi Salim <jhs@mojatatu.com>
Cc: netdev@vger.kernel.org, Victor Nogueira <victor@mojatatu.com>,
	Jiri Pirko <jiri@resnulli.us>,
	"David S . Miller" <davem@davemloft.net>,
	Eric Dumazet <edumazet@kernel.org>,
	Jakub Kicinski <kuba@kernel.org>, Paolo Abeni <pabeni@redhat.com>,
	Jesper Dangaard Brouer <hawk@kernel.org>,
	Daniel Borkmann <daniel@iogearbox.net>,
	Sashiko <sashiko-bot@kernel.org>,
	bpf@vger.kernel.org, stable@vger.kernel.org
Subject: Re: [PATCH net v2] net/sched: clamp skb->queue_mapping before indexing the tx queue
Date: Thu, 15 Oct 2026 13:58:22 +0100	[thread overview]
Message-ID: <20261015125822.GH83879@horms.kernel.org> (raw)
In-Reply-To: <QDISC-MD1X.v2.20261009135842@mojatatu.com>

On Fri, Oct 09, 2026 at 02:09:03PM -0400, Jamal Hadi Salim wrote:
> This issue was caught by Sashiko (nipa). A producer-side path cap is
> already merged (ea4d4b5dddb5); this patch adds the matching consumer-side
> cap.
> 
> The issue:
> The tx queue is picked and capped in netdev_core_pick_tx(), before the
> __dev_xmit_skb() qdisc enqueue path. A tc BPF program running inside
> egress qdisc (not clsact) can then change __sk_buff->queue_mapping;
> that store is never re-checked. bpf_convert_ctx_access() bounds it only
> by NO_QUEUE_MAPPING, not by the device's queue count.
> 
> On dequeue, qdisc_restart() calls netdev_get_tx_queue(), which
> indexes dev->_tx[] with that value. netdev_get_tx_queue() guards it with
> just a DEBUG_NET_WARN_ON_ONCE, so sch_direct_xmit() takes the out-of-bounds
> txq->_xmit_lock and writes txq->xmit_lock_owner past the end of the
> allocation on non-lltx devices such as ifb. Drivers that index their rings
> from skb_get_queue_mapping() (ex: ifb) go out of range the same way.
> 
> The Fix:
> Cap the mapping at the consumer and write the capped value back, so both
> the qdisc's _tx[] lookup and any later driver read stay in range. The cap
> folds into the dequeue path instead of a separate walk over the returned
> list: dequeue_skb() caps the head, and each bulk helper caps the element it
> appends. netdev_cap_txqueue() already logs a ratelimited notice and selects
> queue 0. The single-packet case pays one extra compare against
> real_num_tx_queues.
> 
> A bulk-dequeued list is transmitted under the single tx queue lock of the
> head skb, so every element must belong to that same queue. The fast bulk
> helper (TCQ_F_ONETXQUEUE, taken on BQL devices by the per-queue children
> of mq, mqprio, taprio and htb, and by any qdisc on a single-queue device
> such as the prio root in the reproducer) must therefore compare each
> capped follower against the head and requeue a mismatch, exactly as the
> slow helper already does; appending a follower that caps to a different
> queue would send it under the wrong _xmit_lock.
> 
> Conditions to recreate the bug:
> Load a SCHED_CLS or SCHED_ACT program that stores __sk_buff->queue_mapping
> (a tc BPF action is enough), attach it to a transmit qdisc (for example a
> matchall filter on a prio root), and send a packet. With a 1-queue device a
> store of 1 indexes one past dev->_tx[]. Under KASAN with panic_on_warn=1
> the unfixed kernel faults with "BUG: KASAN: slab-out-of-bounds in
> sch_direct_xmit". Loading the program needs CAP_BPF/CAP_SYS_ADMIN, so the
> trigger is root.
> 
> Fixes: 74e31ca850c1 ("bpf: add skb->queue_mapping write access from tc clsact")
> Reported-by: Sashiko (nipa) <sashiko-bot@kernel.org>
> Closes: https://lore.kernel.org/netdev/179033713973.2160803.4914570693994398206@kernel.org/
> 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
> Cc: stable@vger.kernel.org
> Reviewed-by: Victor Nogueira <victor@mojatatu.com>
> Signed-off-by: Jamal Hadi Salim <jhs@mojatatu.com>
> ---
> v1 -> v2:
> - try_bulk_dequeue_skb(): the fast bulk helper (TCQ_F_ONETXQUEUE) capped
>   each follower but appended it without comparing it against the head, so
>   a mismatched follower was still sent under the head's txq lock. Compare
>   the capped follower with the head and requeue a mismatch to skb_bad_txq,
>   matching try_bulk_dequeue_skb_slow(). Reported by Sashiko (nipa).
> ---
>  net/sched/sch_generic.c | 53 ++++++++++++++++++++++++++++++++++++++++++++-----
>  1 file changed, 48 insertions(+), 5 deletions(-)

Hi Jamal,

Weirdly, this patch is mangled and does not apply.

> 
> diff --git a/net/sched/sch_generic.c b/net/sched/sch_generic.c
> index 6f6a6f0d5eb0..b65bec7e2643 100644
> --- a/net/sched/sch_generic.c
> +++ b/net/sched/sch_generic.c
> @@ -96,9 +96,33 @@ static void qdisc_maybe_clear_missed(struct Qdisc *q,

This hunk results in 31 lines, not 33.

>  
>  #define SKB_XOFF_MAGIC ((struct sk_buff *)1UL)
>  
> +/* A tc BPF program on a transmit qdisc can change __sk_buff->queue_mapping
> + * after the tx queue was picked. Re-cap it against the device and write it
> + * back, so a driver that re-reads skb_get_queue_mapping() stays in range.
> + */
> +static u16 qdisc_cap_skb_tx_queue(struct net_device *dev, struct sk_buff *skb)
> +{
> +	u16 queue = skb_get_queue_mapping(skb);
> +	u16 capped;
> +
> +	capped = netdev_cap_txqueue(dev, queue);
> +	if (unlikely(capped != queue))
> +		skb_set_queue_mapping(skb, capped);
> +
> +	return capped;
> +}
> +
> +static struct netdev_queue *skb_cap_tx_queue(struct net_device *dev,
> +					     struct sk_buff *skb)
> +{
> +	return netdev_get_tx_queue(dev, qdisc_cap_skb_tx_queue(dev, skb));
> +}
> +
>  static inline struct sk_buff *__skb_dequeue_bad_txq(struct Qdisc *q)
>  {
> -	const struct netdev_queue *txq = q->dev_queue;
> +	const struct netdev_queue *txq;
>  	spinlock_t *lock = NULL;
>  	struct sk_buff *skb;
>  

...

> @@ -216,6 +242,17 @@ static void try_bulk_dequeue_skb(struct Qdisc *q,

And this hunk results in 15 lines, not 17.

>  		if (!nskb)
>  			break;
>  
> +		/* Re-cap a mapping changed after pick; requeue a
> +		 * follower that no longer matches the head, which owns
> +		 * the _xmit_lock for the whole list.
> +		 */
> +		if (unlikely(qdisc_cap_skb_tx_queue(dev, nskb) !=
> +			     skb_get_queue_mapping(skb))) {
> +			qdisc_enqueue_skb_bad_txq(q, nskb);
> +			break;
> +		}
>  		bytelimit -= nskb->len; /* covers GSO len */
>  		skb->next = nskb;
>  		skb = nskb;

...

pw-bot: changes-requested

      reply	other threads:[~2026-10-15 12:58 UTC|newest]

Thread overview: 2+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-09 18:09 [PATCH net v2] net/sched: clamp skb->queue_mapping before indexing the tx queue Jamal Hadi Salim
2026-10-15 12:58 ` Simon Horman [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261015125822.GH83879@horms.kernel.org \
    --to=horms@kernel.org \
    --cc=bpf@vger.kernel.org \
    --cc=daniel@iogearbox.net \
    --cc=davem@davemloft.net \
    --cc=edumazet@kernel.org \
    --cc=hawk@kernel.org \
    --cc=jhs@mojatatu.com \
    --cc=jiri@resnulli.us \
    --cc=kuba@kernel.org \
    --cc=netdev@vger.kernel.org \
    --cc=pabeni@redhat.com \
    --cc=sashiko-bot@kernel.org \
    --cc=stable@vger.kernel.org \
    --cc=victor@mojatatu.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox