All of lore.kernel.org
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Slawomir Stepien" <sst@poczta.fm>
Cc: bpf@vger.kernel.org
Subject: Re: [PATCH RFC net-next v2 4/4] veth: use generic skb XDP handling
Date: Wed, 11 Nov 2026 10:20:20 +0000	[thread overview]
Message-ID: <sashiko-outbox-208778@kernel.org> (raw)
In-Reply-To: <20261110101941.1059662-5-sst@poczta.fm>

Thank you for your contribution! Sashiko AI review found 5 potential
issues to consider:
- [High] veth: peer NAPI ring bypassed in veth_xmit() on skb-backed
  XDP_TX
- [High] veth: native XDP bypassed in veth_xdp_rcv_skb() on redirected
  skbs
- [High] net: corrupted skb->tail in __bpf_prog_run_generic_xdp() on
  tail adjust
- [Medium] veth: dropped XDP_TX skbs miscounted as sent in
  veth_xdp_rcv_skb()
- [Medium] veth: pre-XDP COW failure miscounted and warns on shared skb
--

commit 50d191b571e1e34c4b0f0b9a37ee28f9c686bec9
Author: Maciej Fijalkowski <maciej.fijalkowski@intel.com>

veth: use generic skb XDP handling

This commit switches veth's skb-backed XDP receive path in veth_xdp_rcv_skb()
to use __do_xdp_generic() instead of converting skbs to xdp_frames for XDP_TX
and XDP_REDIRECT.

> diff --git a/drivers/net/veth.c b/drivers/net/veth.c
> index a61842b2585bf..049c95dbb8be5 100644
> --- a/drivers/net/veth.c
> +++ b/drivers/net/veth.c

[ ... ]

> @@ -741,71 +746,26 @@ static void veth_xdp_rcv_bulk_skb(struct veth_rq *rq, void **frames,
>  static struct sk_buff *veth_xdp_rcv_skb(struct veth_rq *rq,
>  					struct sk_buff *skb,
> -					struct veth_xdp_tx_bq *bq,
>  					struct veth_stats *stats)
>  {
> -	void *orig_data, *orig_data_end;
>  	struct bpf_prog *xdp_prog;
>  	struct veth_xdp_buff vxbuf;
> -	struct xdp_buff *xdp = &vxbuf.xdp;
> -	u32 act, metalen;
> -	int off;
> +	struct xdp_generic_ctx ctx = {
> +		.page_pool = rq->page_pool,
> +		.xdp_rxq = &rq->xdp_rxq,
> +		.xdp_skb = &vxbuf.skb,
> +		.xdp = &vxbuf.xdp,
> +	};
> +	int ret;
>  
>  	skb_prepare_for_gro(skb);
> +	/* Generic XDP expects skb->mac_len to be set, as
> +	 * __netif_receive_skb_core() does before running it: the generic
> +	 * cpumap redirect pulls it before queueing the skb.
> +	 */
> +	skb->mac_len = skb->data - skb_mac_header(skb);
>  
>  	rcu_read_lock();
>  	xdp_prog = rcu_dereference(rq->xdp_prog);
> @@ -814,100 +774,36 @@ static struct sk_buff *veth_xdp_rcv_skb(struct veth_rq *rq,
>  		goto out;
>  	}
>  
> -	__skb_push(skb, skb->data - skb_mac_header(skb));
> -	if (veth_convert_skb_to_xdp_buff(rq, xdp, &skb))
> -		goto drop;
> -	vxbuf.skb = skb;
> -
> -	orig_data = xdp->data;
> -	orig_data_end = xdp->data_end;
> -
> -	act = bpf_prog_run_xdp(xdp_prog, xdp);
> -
> -	switch (act) {
> +	ret = __do_xdp_generic(xdp_prog, &skb, &ctx);

[Severity: High]
Does calling __do_xdp_generic() here cause veth's native XDP program to be
skipped whenever an skb arrives from the peer with skb->redirected set?

When a packet is redirected to the peer veth's egress (for example via TC
bpf_redirect() in __bpf_tx_skb(), which calls skb_set_redirected_noclear()),
skb->redirected remains set across veth_xmit() -> ____dev_forward_skb() ->
skb_scrub_packet().  In __do_xdp_generic() -> netif_receive_generic_xdp():

net/core/dev.c:netif_receive_generic_xdp() {
    ...
	if (skb_is_redirected(skb))
		return XDP_PASS;
    ...
}

Previously veth_xdp_rcv_skb() always ran rq->xdp_prog via bpf_prog_run_xdp().
With this change, any skb with skb->redirected set bypasses rq->xdp_prog and
is passed straight up the stack as XDP_PASS.

> +	switch (ctx.act) {
>  	case XDP_PASS:
>  		break;
>  	case XDP_TX:

[Severity: High]
Does routing skb-backed XDP_TX through generic_xdp_tx() -> veth_xmit()
bypass the peer's NAPI receive ring when the peer has GRO enabled without an
XDP program?

Previously XDP_TX went through veth_xdp_tx() -> veth_xdp_flush_bq() ->
veth_xdp_xmit(), which queued frames into the peer's rq->xdp_ring whenever
rcu_access_pointer(rq->napi) was non-NULL.  Now generic_xdp_tx() calls
veth_xmit(), which requires veth_skb_is_eligible_for_gro(dev, rcv, skb) to
use rq->napi:

drivers/net/veth.c:veth_xmit() {
    ...
		use_napi = rcu_access_pointer(rq->napi) &&
			   veth_skb_is_eligible_for_gro(dev, rcv, skb);
    ...
}

When the peer only has GRO enabled and no XDP program, the XDP-enabled device
still has NETIF_F_ALL_TSO set in dev->features while skb->destructor has been
cleared by skb_prepare_for_gro() or skb_pp_cow_data().  As a result,
veth_skb_is_eligible_for_gro() returns false, veth_forward_skb() falls back to
__netif_rx() instead of queueing to the peer's rq->xdp_ring, and the
"xdp_tx to peer with gro on" test in tools/testing/selftests/net/veth.sh
fails with 0 packets on the peer's XDP/NAPI ring.

> -		veth_xdp_get(xdp);
> -		consume_skb(skb);
> -		xdp->rxq->mem = rq->xdp_mem;
> -		if (unlikely(veth_xdp_tx(rq, xdp, bq) < 0)) {
> -			trace_xdp_exception(rq->dev, xdp_prog, act);
> -			stats->rx_drops++;
> -			goto err_xdp;
> -		}
> -		stats->xdp_tx++;
> -		rcu_read_unlock();
> -		goto xdp_xmit;
> +		/* A failed transmit is counted in the device's tx_dropped
> +		 * by generic_xdp_tx(), so it is not added to xdp_tx_err.
> +		 */
> +		if (!ctx.err)
> +			stats->xdp_skb_tx++;
> +		break;

[Severity: Medium]
Does !ctx.err miscount packets dropped inside veth_xmit() as successful
stats->xdp_skb_tx transmits?

In generic_xdp_tx(), netdev_start_xmit() calls veth_xmit() and checks
dev_xmit_complete(rc).  On every drop path in veth_xmit() (such as when the
peer is down, the packet exceeds the peer MTU, or the peer's xdp_ring is full
with veth's default IFF_NO_QUEUE), veth_xmit() frees the skb and returns
NET_XMIT_DROP.  Because dev_xmit_complete(NET_XMIT_DROP) is true,
generic_xdp_tx() returns 0 without firing trace_xdp_exception(), and
veth_xdp_rcv_skb() increments stats->xdp_skb_tx (and rq->stats.vs.xdp_tx) for
dropped packets.

[ ... ]

>  	default:
> -		bpf_warn_invalid_xdp_action(rq->dev, xdp_prog, act);
> -		fallthrough;
>  	case XDP_ABORTED:
> -		trace_xdp_exception(rq->dev, xdp_prog, act);
> -		fallthrough;
>  	case XDP_DROP:
>  		stats->xdp_drops++;
> -		goto xdp_drop;
> +		break;
>  	}

[Severity: Medium]
When pre-XDP skb COW fails in netif_skb_check_for_xdp(), doesn't
netif_receive_generic_xdp() return XDP_DROP and cause this switch to count
the allocation failure in stats->xdp_drops instead of stats->rx_drops?

Previously a failure in veth_convert_skb_to_xdp_buff() jumped to drop and
incremented stats->rx_drops (which feeds rx_dropped in veth_get_stats64()).
In addition, when skb_needs_xdp_cow(skb) is true because skb_shared(skb) is
set and skb_pp_cow_data() fails, netif_skb_check_for_xdp() falls back to
pskb_expand_head(skb, ...), which triggers DEBUG_NET_WARN_ON_ONCE(1) on
shared skbs.

>  	rcu_read_unlock();
>  
> -	/* check if bpf_xdp_adjust_head was used */
> -	off = orig_data - xdp->data;
> -	if (off > 0)
> -		__skb_push(skb, off);
> -	else if (off < 0)
> -		__skb_pull(skb, -off);
> -
> -	skb_reset_mac_header(skb);
> -
> -	/* XDP frag metadata (e.g. nr_frags) are updated in eBPF helpers
> -	 * (e.g. bpf_xdp_adjust_tail). Remove the old fragment contribution
> -	 * from skb->len before updating data_len, then add the new one back.
> -	 */
> -	skb->len -= skb->data_len;
> -	if (xdp_buff_has_frags(xdp)) {
> -		skb->data_len = skb_shinfo(skb)->xdp_frags_size;
> -		skb->len += skb->data_len;
> -	} else {
> -		skb->data_len = 0;
> -	}
> -
> -	/* Synchronize the skb tail with XDP's updated linear area. */
> -	off = xdp->data_end - orig_data_end;
> -	if (off != 0) {
> -		skb_set_tail_pointer(skb, xdp->data_end - xdp->data);
> -		skb->len += off; /* positive on grow, negative on shrink */
> -	}
> -
> -	skb->protocol = eth_type_trans(skb, rq->dev);
> -
> -	metalen = xdp->data - xdp->data_meta;
> -	if (metalen)
> -		skb_metadata_set(skb, metalen);
> +	if (ret == XDP_PASS)
> +		return skb;

[Severity: High]
Does replacing this tail-pointer update with __do_xdp_generic() corrupt
skb->tail when the XDP program adjusts xdp->data_end (via
bpf_xdp_adjust_tail() or bpf_xdp_pull_data())?

In the removed code (from cb6379feaaff ("veth: fix skb length accounting
after XDP frag adjustment")), skb->data was at the MAC header (equal to
xdp->data) when skb_set_tail_pointer(skb, xdp->data_end - xdp->data) ran, and
eth_type_trans() pulled ETH_HLEN afterwards.  In netif_receive_generic_xdp(),
__skb_pull(*pskb, mac_len) pulls mac_len before calling
__bpf_prog_run_generic_xdp():

net/core/dev.c:__bpf_prog_run_generic_xdp() {
    ...
	/* check if bpf_xdp_adjust_tail was used */
	off = xdp->data_end - orig_data_end;
	if (off != 0) {
		skb_set_tail_pointer(skb, xdp->data_end - xdp->data);
		skb->len += off; /* positive on grow, negative on shrink */
	}
    ...
}

Because skb->data is at xdp->data + mac_len there, skb_set_tail_pointer()
adds xdp->data_end - xdp->data to skb->data and sets skb_tail_pointer(skb) to
xdp->data_end + mac_len (14 bytes past the end of the linear data).  This
corrupts consumers of skb_tail_pointer(skb) such as __pskb_pull_tail() and
skb_try_coalesce(), and if xdp->data_end was grown to within mac_len bytes of
xdp_data_hard_end(xdp), skb->tail exceeds skb->end into skb_shared_info.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261110101941.1059662-1-sst@poczta.fm?part=4

  reply	other threads:[~2026-11-11 10:20 UTC|newest]

Thread overview: 9+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-10 10:19 [PATCH RFC net-next v2 0/4] xdp: reuse generic skb XDP handling for veth Slawomir Stepien
2026-11-10 10:19 ` [PATCH RFC net-next v2 1/4] xdp: add mixed page_pool/page_shared memory type Slawomir Stepien
2026-11-10 10:19 ` [PATCH RFC net-next v2 2/4] xdp: return status from generic_xdp_tx() Slawomir Stepien
2026-11-11 10:20   ` sashiko-bot
2026-11-10 10:19 ` [PATCH RFC net-next v2 3/4] xdp: split generic XDP skb handling Slawomir Stepien
2026-11-11 10:20   ` sashiko-bot
2026-11-10 10:19 ` [PATCH RFC net-next v2 4/4] veth: use generic skb XDP handling Slawomir Stepien
2026-11-11 10:20   ` sashiko-bot [this message]
2026-11-10 12:48 ` [PATCH RFC net-next v2 0/4] xdp: reuse generic skb XDP handling for veth Maciej Fijalkowski

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=sashiko-outbox-208778@kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=bpf@vger.kernel.org \
    --cc=sashiko-reviews@lists.linux.dev \
    --cc=sst@poczta.fm \
    /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.