All of lore.kernel.org
 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: 3+ 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]
2026-10-15 20:52   ` Jamal Hadi Salim

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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.