Netdev List
 help / color / mirror / Atom feed
* [PATCH v6 bpf 0/3] xdp: fix page_pool leaks
@ 2026-11-04 12:13 Slawomir Stepien
  2026-11-04 12:13 ` [PATCH v6 bpf 1/3] xdp: introduce xdp_convert_skb_to_buff() Slawomir Stepien
                   ` (3 more replies)
  0 siblings, 4 replies; 7+ messages in thread
From: Slawomir Stepien @ 2026-11-04 12:13 UTC (permalink / raw)
  To: ast, daniel, davem, kuba, hawk, john.fastabend, sdf
  Cc: netdev, bpf, magnus.karlsson, aleksander.lobakin,
	ilias.apalodimas, toke, lorenzo, ihor.solodrai, tavip,
	maciej.fijalkowski, sst

v1:
https://lore.kernel.org/bpf/20251003140243.2534865-1-maciej.fijalkowski@intel.com/
v1->v2:
- add skb to xdp_buff converter (Jakub)
- postpone restoration of veth's xdp_rxq_info mem model (AI)

v2:
https://lore.kernel.org/bpf/20251017143103.2620164-1-maciej.fijalkowski@intel.com/T/
v2->v3:
- make skb->xdp_buff conversion agnostic of current skb->data position
  (Toke)
- do not pull mac header in veth (Toke)
- use virt_to_head_page() when checking if xdp->data comes from
  page_pool (Olek)
- add ack from Toke

v3:
https://lore.kernel.org/bpf/20251022125209.2649287-1-maciej.fijalkowski@intel.com/
v3->v4:
- include skb->head when calculating packet length (Toke, lkp)

v4:
https://lore.kernel.org/bpf/20251027121318.2679226-1-maciej.fijalkowski@intel.com/
v4->v5:
- rely again on sk_buff::pp_recycle when setting mem type on rxq
  (Jesper, Jakub)
- add ack from Toke on patch 2 and restore it on patch 1

v5:
https://lore.kernel.org/bpf/20251029221315.2694841-1-maciej.fijalkowski@intel.com/
v5->v6:
- rebase onto bpf/master
- patch 1: set MEM_TYPE_PAGE_POOL unconditionally in
  xdp_convert_skb_to_buff(); mem type only matters for frags consumed
  by the program, and those always come from skb_pp_cow_data() (Jakub)
- patch 1: add DEBUG_NET_WARN_ON_ONCE() for a non-linear skb without
  pp_recycle and document the expectation in kdoc (Jakub); the warning
  is limited to non-linear skbs since linear ones legitimately skip
  the CoW
- new patch 2: veth_xdp_rcv_one() ran the program on an xdp_frame with
  whatever mem type the previous packet left on rq->xdp_rxq; use the
  frame's mem type instead, as cpumap and devmap do. With patch 3 alone
  veth sends page_pool frames to its peer and the peer's program
  trimming frags crashed the kernel, so this comes first
- patch 3 (was 2): veth turns the buff into an xdp_frame on
  XDP_{TX,REDIRECT}, so the head is released through mem type too;
  keep the queue's original model unless the buffer was copied into
  rq->page_pool (Jakub)
- patch 3: take page_pool refs on compound heads via netmem helpers
- patch 3: do not restore the rxq mem model after XDP_{TX,REDIRECT}
  and on the error path; with patch 2 every path sets its own mem type
- patch 3: veth can now redirect MEM_TYPE_PAGE_POOL frames, which needs
  commit b3ca872947ef ("bpf: Use the frame mem type in devmap egress
  XDP") in devmap; list it as a stable prerequisite
- drop Reviewed-by/Tested-by tags as patches 1 and 3 changed

Hi,

here we fix page_pool leaks which happen when fragment is released
within XDP program and memory type is set incorrectly. The reports come
from syzbot and AF_XDP test suite.

Most of the changes are about pulling out the common code for
init/prepare xdp_buff based on existing skb and supplying it with
correct xdp_rxq_info's mem_type that is assigned to xdp_buff. A bit more
stuff, page_pool related, had to be done on veth side.

Originally, this work was started by Octavian Purdila and continued by
Maciej up to v5. Maciej then took the larger veth rework from the v5
review to net-next as an RFC [1]. This v6 picks up the v5 series, with
Maciej's agreement, as the minimal fix for bpf and stable, and addresses
the v5 review. Maciej stays the author of patches 1 and 3 and my changes
are noted in their commit messages; patch 2 is new.

For veth, Jakub listed three options: stop turning skbs into xdp_frames
in veth, decide the head's mem type when the CoW was skipped, or CoW the
head on the Tx/Redirect path. The first one changes veth's XDP handling
beyond a fix (it is what the RFC does), so patch 3 takes the second:
veth knows whether it just copied the buffer into rq->page_pool, and
only then advertises MEM_TYPE_PAGE_POOL. An skb that skipped the CoW is
linear and keeps the queue's original model, as before this series.
The two do not conflict: the RFC can go on top of this series and
replace the veth parts of patches 2 and 3.

[1] https://lore.kernel.org/all/20260509084858.773921-1-maciej.fijalkowski@intel.com/

Patch 2 fixes the same problem for frames in veth_xdp_rcv_one() that
commit b3ca872947ef ("bpf: Use the frame mem type in devmap egress
XDP") fixed for devmap; cpumap already handled it.

Testing (x86_64, QEMU via virtme-ng, KASAN + DEBUG_VM + DEBUG_NET; the
series ran on bpf/master fb7bd3a2aad7 and, rebased without changes, the
reproducers, kprobe path checks, devmap test and selftests below ran
again on 05441d697f7f with the same results; the runs without the series
on bpf/master c8a8a68d3ab4):
- reproducer: an xdp.frags program that trims all frags with
  bpf_xdp_adjust_tail() on 8000-byte pings over a 9000 MTU veth pair;
  without the series both generic XDP and native veth XDP hit
  "BUG: Bad page state ... page_pool leak" followed by
  VM_BUG_ON_PAGE(page_ref_count(page) == 0) and a panic; with the
  series 20/20 packets are trimmed with no splat
- native veth XDP_TX of multi-buffer packets, with and without
  trimming frags first (200 large + 200 small pings each), then veth
  teardown and a 75s wait for page_pool "stalled pool shutdown" or
  negative inflight warnings: none
- BPF selftests on bpf/master with tools/testing/selftests/bpf/config:
  ./test_progs -t ns_xsk,xdp: all 34 tests (205 subtests) passed, no
  splats, with and without the series
- the standalone AF_XDP suite (test_xsk.sh) also runs the tests that
  test_progs skips as flaky; XDP_ADJUST_TAIL_SHRINK_MULTI_BUFF reports
  PASS either way, but without the series dmesg shows 60 "page_pool
  leak" splats from generic XDP (bpf_xdp_adjust_tail -> __xdp_return ->
  page_frag_free on pages allocated by skb_pp_cow_data()); with the
  series there are none
- frame path: one veth bounces 8000-byte pings with XDP_TX, its peer
  trims all frags of the resulting frames (IPv6 off, so no skb touches
  the peer's rxq first); with patches 1+3 only: "page_pool leak",
  VM_BUG_ON_PAGE() and a panic in veth_xdp_rcv_one(); with the whole
  series 50/50 frames are trimmed, kprobes show all __xdp_return() calls
  with MEM_TYPE_PAGE_POOL, no splat, also with IPv6 on so that skbs and
  frames interleave on the peer's queue; bpf/master alone passes as well
  since veth only sends MEM_TYPE_PAGE_SHARED frames there
- remaining veth paths, each confirmed with kprobes: skbs that skip the
  CoW (frames passed up by one veth and forwarded back into it: 1229 of
  1260 runs without skb_pp_cow_data(), heads from the page_pool),
  XDP_REDIRECT failing on the skb path (40 xdp_return_buff()) and on the
  frame path (40 xdp_return_frame()), XDP_PASS of 20 frames after their
  frags were trimmed; no splat, and none of the page_pool shutdown or
  inflight warnings 75s after teardown
- devmap egress after patch 3: veth redirects copied skbs through a
  devmap whose egress program trims all frags; with the series 20/20
  trimmed, all __xdp_return() calls with MEM_TYPE_PAGE_POOL, no splat,
  nothing 75s after teardown; with b3ca872947ef reverted on top of the
  series: "page_pool leak" from page_frag_free() under
  dev_map_bpf_prog_run(), VM_BUG_ON_PAGE() and a panic, hence the
  stable prerequisite in patch 3
- W=1 builds of the touched files with LLVM=1 and with DEBUG_NET=n: no
  warnings
- XDP_ADJUST_TAIL_GROW fails in DRV mode with and without the series,
  so it is unrelated
- not tested: page_pool frames from hardware drivers redirected into
  veth, bpf_xdp_pull_data(), multi-queue load, the full test_progs run

Performance (the same VM with 8 vCPUs, non-debug kernels with
PAGE_POOL_STATS, bpf/master fb7bd3a2aad7 with and without the series;
pktgen sends skbs into one end of a veth pair, the program on the other
end counts packets; median of 6 runs of 8s over two boots per kernel):
- 64-byte packets with native XDP_DROP, XDP_TX and XDP_REDIRECT (devmap
  to another veth), and generic XDP_DROP on 1 and 4 CPUs with 64 and
  8000-byte packets: within 2% of the base kernel, no regression
- native veth with 8000-byte packets (3 frags): XDP_TX +8.8%
  (0.507 -> 0.552 Mpps), XDP_REDIRECT +8.4% (0.613 -> 0.664 Mpps).
  Without the series veth_xdp_get() takes plain page references on
  the head and frags, so the pages copied into rq->page_pool are
  released from the pool instead of recycled (~1.2M/s
  rx_pp_recycle_released_ref plus slow allocations); with page_pool
  references they go back to the ring (~1.4M/s rx_pp_recycle_ring, no
  released_ref)
Absolute numbers in a VM differ between boots, so only the relative
change is meaningful.

This v6 was prepared with an AI coding assistant (Claude) from the v5
review thread; the assistant wrote the code changes, all of patch 2,
the changelog updates, and ran the tests listed above.



Maciej Fijalkowski (2):
  xdp: introduce xdp_convert_skb_to_buff()
  veth: update mem type in xdp_buff

Slawomir Stepien (1):
  veth: use the frame mem type when running XDP on an xdp_frame

 drivers/net/veth.c | 56 ++++++++++++++++++++++++++++++----------------
 include/net/xdp.h  | 36 +++++++++++++++++++++++++++++
 net/core/dev.c     | 25 ++++-----------------
 3 files changed, 77 insertions(+), 40 deletions(-)


base-commit: 05441d697f7f9a927264f4058cbbc6a03dfcac9d
-- 
2.56.0


^ permalink raw reply	[flat|nested] 7+ messages in thread

* [PATCH v6 bpf 1/3] xdp: introduce xdp_convert_skb_to_buff()
  2026-11-04 12:13 [PATCH v6 bpf 0/3] xdp: fix page_pool leaks Slawomir Stepien
@ 2026-11-04 12:13 ` Slawomir Stepien
  2026-11-04 12:53   ` bot+bpf-ci
  2026-11-04 12:13 ` [PATCH v6 bpf 2/3] veth: use the frame mem type when running XDP on an xdp_frame Slawomir Stepien
                   ` (2 subsequent siblings)
  3 siblings, 1 reply; 7+ messages in thread
From: Slawomir Stepien @ 2026-11-04 12:13 UTC (permalink / raw)
  To: ast, daniel, davem, kuba, hawk, john.fastabend, sdf
  Cc: netdev, bpf, magnus.karlsson, aleksander.lobakin,
	ilias.apalodimas, toke, lorenzo, ihor.solodrai, tavip,
	maciej.fijalkowski, sst, syzbot+ff145014d6b0ce64a173

From: Maciej Fijalkowski <maciej.fijalkowski@intel.com>

Currently, generic XDP hook uses xdp_rxq_info from netstack Rx queues
which do not have its XDP memory model registered. There is a case when
XDP program calls bpf_xdp_adjust_tail() BPF helper, which in turn
releases underlying memory. This happens when it consumes enough amount
of bytes and when XDP buffer has fragments. For this action the memory
model knowledge passed to XDP program is crucial so that core can call
suitable function for freeing/recycling the page.

For netstack queues it defaults to MEM_TYPE_PAGE_SHARED (0) due to lack
of mem model registration. The problem we're fixing here is when kernel
copied the skb to new buffer backed by system's page_pool and XDP buffer
is built around it. Then when bpf_xdp_adjust_tail() calls
__xdp_return(), it acts incorrectly due to mem type not being set to
MEM_TYPE_PAGE_POOL and causes a page leak.

Pull out the existing code from bpf_prog_run_generic_xdp() that
init/prepares xdp_buff onto new helper xdp_convert_skb_to_buff() and
embed there rxq's mem_type initialization that is assigned to xdp_buff.
Make it agnostic to current skb->data position.

The mem type is only consulted when the program releases fragments; the
head can not be freed from under the skb. All current users pass skbs
with fragments through skb_pp_cow_data() first, and when the CoW fails
the skb is linearized, so fragments seen here are always page_pool
backed. Hence set MEM_TYPE_PAGE_POOL unconditionally and warn under
CONFIG_DEBUG_NET if a non-linear skb shows up without pp_recycle.

This problem was triggered by syzbot as well as AF_XDP test suite which
is about to be integrated to BPF CI.

Reported-by: syzbot+ff145014d6b0ce64a173@syzkaller.appspotmail.com
Closes: https://lore.kernel.org/netdev/6756c37b.050a0220.a30f1.019a.GAE@google.com/
Fixes: e6d5dbdd20aa ("xdp: add multi-buff support for xdp running in generic mode")
Suggested-by: Jakub Kicinski <kuba@kernel.org>
Co-developed-by: Octavian Purdila <tavip@google.com>
Signed-off-by: Octavian Purdila <tavip@google.com> # whole analysis, testing, initiating a fix
Signed-off-by: Maciej Fijalkowski <maciej.fijalkowski@intel.com> # commit msg and proposed more robust fix
[sst: rebased onto bpf/master; set MEM_TYPE_PAGE_POOL unconditionally, add
 DEBUG_NET_WARN_ON_ONCE() and kdoc as agreed in v4/v5 review; dropped
 Tested-by/Reviewed-by as the code changed]
Signed-off-by: Slawomir Stepien <sst@poczta.fm>
---
 include/net/xdp.h | 36 ++++++++++++++++++++++++++++++++++++
 net/core/dev.c    | 25 ++++---------------------
 2 files changed, 40 insertions(+), 21 deletions(-)

diff --git a/include/net/xdp.h b/include/net/xdp.h
index 07231adfb5f8..28bd78dbef20 100644
--- a/include/net/xdp.h
+++ b/include/net/xdp.h
@@ -389,6 +389,42 @@ struct sk_buff *xdp_build_skb_from_frame(struct xdp_frame *xdpf,
 					 struct net_device *dev);
 struct xdp_frame *xdpf_clone(struct xdp_frame *xdpf);
 
+/**
+ * xdp_convert_skb_to_buff - build an xdp_buff on top of an skb
+ * @skb: skb to describe, packet data is taken from its MAC header onwards
+ * @xdp: xdp_buff to initialize
+ * @xdp_rxq: Rx queue info to attach to @xdp
+ *
+ * The memory type set on @xdp_rxq is only used to release fragments that
+ * the XDP program consumes; the head can not be freed from under the skb.
+ * A non-linear @skb is expected to have gone through skb_pp_cow_data(), so
+ * all of its fragments are page_pool backed.
+ */
+static inline
+void xdp_convert_skb_to_buff(struct sk_buff *skb, struct xdp_buff *xdp,
+			     struct xdp_rxq_info *xdp_rxq)
+{
+	u32 frame_sz, pkt_len;
+
+	/* SKB "head" area always have tailroom for skb_shared_info */
+	frame_sz = skb_end_pointer(skb) - skb->head;
+	frame_sz += SKB_DATA_ALIGN(sizeof(struct skb_shared_info));
+	pkt_len = skb_tail_pointer(skb) - skb_mac_header(skb);
+
+	xdp_init_buff(xdp, frame_sz, xdp_rxq);
+	xdp_prepare_buff(xdp, skb->head, skb->mac_header, pkt_len, true);
+
+	if (skb_is_nonlinear(skb)) {
+		skb_shinfo(skb)->xdp_frags_size = skb->data_len;
+		xdp_buff_set_frags_flag(xdp);
+	} else {
+		xdp_buff_clear_frags_flag(xdp);
+	}
+
+	DEBUG_NET_WARN_ON_ONCE(skb_is_nonlinear(skb) && !skb->pp_recycle);
+	xdp->rxq->mem.type = MEM_TYPE_PAGE_POOL;
+}
+
 static inline
 void xdp_convert_frame_to_buff(const struct xdp_frame *frame,
 			       struct xdp_buff *xdp)
diff --git a/net/core/dev.c b/net/core/dev.c
index 3ac700d37e59..4eeeb0182a6a 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -5667,35 +5667,18 @@ static struct netdev_rx_queue *netif_get_rxqueue(struct sk_buff *skb)
 u32 bpf_prog_run_generic_xdp(struct sk_buff *skb, struct xdp_buff *xdp,
 			     const struct bpf_prog *xdp_prog)
 {
-	void *orig_data, *orig_data_end, *hard_start;
+	void *orig_data, *orig_data_end;
 	struct netdev_rx_queue *rxqueue;
 	bool orig_bcast, orig_host;
-	u32 mac_len, frame_sz;
+	u32 metalen, act, mac_len;
 	__be16 orig_eth_type;
 	struct ethhdr *eth;
-	u32 metalen, act;
 	int off;
 
-	/* The XDP program wants to see the packet starting at the MAC
-	 * header.
-	 */
+	rxqueue = netif_get_rxqueue(skb);
 	mac_len = skb->data - skb_mac_header(skb);
-	hard_start = skb->data - skb_headroom(skb);
-
-	/* SKB "head" area always have tailroom for skb_shared_info */
-	frame_sz = (void *)skb_end_pointer(skb) - hard_start;
-	frame_sz += SKB_DATA_ALIGN(sizeof(struct skb_shared_info));
 
-	rxqueue = netif_get_rxqueue(skb);
-	xdp_init_buff(xdp, frame_sz, &rxqueue->xdp_rxq);
-	xdp_prepare_buff(xdp, hard_start, skb_headroom(skb) - mac_len,
-			 skb_headlen(skb) + mac_len, true);
-	if (skb_is_nonlinear(skb)) {
-		skb_shinfo(skb)->xdp_frags_size = skb->data_len;
-		xdp_buff_set_frags_flag(xdp);
-	} else {
-		xdp_buff_clear_frags_flag(xdp);
-	}
+	xdp_convert_skb_to_buff(skb, xdp, &rxqueue->xdp_rxq);
 
 	orig_data_end = xdp->data_end;
 	orig_data = xdp->data;
-- 
2.56.0


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH v6 bpf 2/3] veth: use the frame mem type when running XDP on an xdp_frame
  2026-11-04 12:13 [PATCH v6 bpf 0/3] xdp: fix page_pool leaks Slawomir Stepien
  2026-11-04 12:13 ` [PATCH v6 bpf 1/3] xdp: introduce xdp_convert_skb_to_buff() Slawomir Stepien
@ 2026-11-04 12:13 ` Slawomir Stepien
  2026-11-04 12:13 ` [PATCH v6 bpf 3/3] veth: update mem type in xdp_buff Slawomir Stepien
  2026-11-04 12:44 ` [PATCH v6 bpf 0/3] xdp: fix page_pool leaks Maciej Fijalkowski
  3 siblings, 0 replies; 7+ messages in thread
From: Slawomir Stepien @ 2026-11-04 12:13 UTC (permalink / raw)
  To: ast, daniel, davem, kuba, hawk, john.fastabend, sdf
  Cc: netdev, bpf, magnus.karlsson, aleksander.lobakin,
	ilias.apalodimas, toke, lorenzo, ihor.solodrai, tavip,
	maciej.fijalkowski, sst, stable

veth_xdp_rcv_one() runs the XDP program on frames queued by
ndo_xdp_xmit(), i.e. redirected to veth or sent back by the peer with
XDP_TX. The xdp_buff points at rq->xdp_rxq, whose mem type is whatever
the previous packet left there: the queue's MEM_TYPE_PAGE_SHARED, or
the type of an earlier frame or skb. frame->mem_type is copied in only
for XDP_TX and XDP_REDIRECT, after the program has run.

bpf_xdp_adjust_tail() and bpf_xdp_pull_data() release whole frags with
the rxq mem type. For a MEM_TYPE_PAGE_POOL frame and a stale
MEM_TYPE_PAGE_SHARED rxq, page_frag_free() drops the reference owned by
the page_pool:

  BUG: Bad page state in process ping  pfn:2244a
  page dumped because: page_pool leak
   bad_page.cold+0x58/0x6b
   __free_frozen_pages+0x837/0x1280
   page_frag_free+0x1a4/0x200
   __xdp_return+0x3a8/0x9c0
   bpf_xdp_adjust_tail+0x8a0/0xd10
   bpf_prog_007aa02d10594afa_xdp_trim_drop+0x8f/0xd8
   veth_xdp_rcv_one+0x389/0x10e0
   veth_xdp_rcv+0x1c5/0xb20
   veth_poll+0x33d/0xe00
  ...
  kernel BUG at ./include/linux/mm.h:1820!

The reverse, a MEM_TYPE_PAGE_SHARED frame after a page_pool one, hands
pages that are not page_pool backed to page_pool_put_full_netmem().

Set the rxq mem type from the frame before running the program, as
cpumap and devmap do, and drop the now redundant updates in the XDP_TX
and XDP_REDIRECT paths.

The splat above is from two veth peers with the next patch applied:
one bounces multi-buffer packets with XDP_TX, the other trims all frags
of the resulting frames. Page pool backed frames from other drivers
can hit the same path today.

Fixes: 718a18a0c8a6 ("veth: Rework veth_xdp_rcv_skb in order to accept non-linear skb")
Cc: <stable@vger.kernel.org>
Signed-off-by: Slawomir Stepien <sst@poczta.fm>
---
 drivers/net/veth.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/drivers/net/veth.c b/drivers/net/veth.c
index 3eca92d3dc60..afae2c6533b9 100644
--- a/drivers/net/veth.c
+++ b/drivers/net/veth.c
@@ -658,6 +658,8 @@ static struct xdp_frame *veth_xdp_rcv_one(struct veth_rq *rq,
 
 		xdp_convert_frame_to_buff(frame, xdp);
 		xdp->rxq = &rq->xdp_rxq;
+		/* The program may release frags, do it the frame's way */
+		xdp->rxq->mem.type = frame->mem_type;
 		vxbuf.skb = NULL;
 
 		act = bpf_prog_run_xdp(xdp_prog, xdp);
@@ -669,7 +671,6 @@ static struct xdp_frame *veth_xdp_rcv_one(struct veth_rq *rq,
 			break;
 		case XDP_TX:
 			orig_frame = *frame;
-			xdp->rxq->mem.type = frame->mem_type;
 			if (unlikely(veth_xdp_tx(rq, xdp, bq) < 0)) {
 				trace_xdp_exception(rq->dev, xdp_prog, act);
 				frame = &orig_frame;
@@ -681,7 +682,6 @@ static struct xdp_frame *veth_xdp_rcv_one(struct veth_rq *rq,
 			goto xdp_xmit;
 		case XDP_REDIRECT:
 			orig_frame = *frame;
-			xdp->rxq->mem.type = frame->mem_type;
 			if (xdp_do_redirect(rq->dev, xdp, xdp_prog)) {
 				frame = &orig_frame;
 				stats->rx_drops++;
-- 
2.56.0


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH v6 bpf 3/3] veth: update mem type in xdp_buff
  2026-11-04 12:13 [PATCH v6 bpf 0/3] xdp: fix page_pool leaks Slawomir Stepien
  2026-11-04 12:13 ` [PATCH v6 bpf 1/3] xdp: introduce xdp_convert_skb_to_buff() Slawomir Stepien
  2026-11-04 12:13 ` [PATCH v6 bpf 2/3] veth: use the frame mem type when running XDP on an xdp_frame Slawomir Stepien
@ 2026-11-04 12:13 ` Slawomir Stepien
  2026-11-04 12:53   ` bot+bpf-ci
  2026-11-04 12:44 ` [PATCH v6 bpf 0/3] xdp: fix page_pool leaks Maciej Fijalkowski
  3 siblings, 1 reply; 7+ messages in thread
From: Slawomir Stepien @ 2026-11-04 12:13 UTC (permalink / raw)
  To: ast, daniel, davem, kuba, hawk, john.fastabend, sdf
  Cc: netdev, bpf, magnus.karlsson, aleksander.lobakin,
	ilias.apalodimas, toke, lorenzo, ihor.solodrai, tavip,
	maciej.fijalkowski, sst, stable

From: Maciej Fijalkowski <maciej.fijalkowski@intel.com>

When skb's headroom is not sufficient for XDP purposes,
skb_pp_cow_data() returns new skb with requested headroom space. This
skb was provided by page_pool.

For CONFIG_DEBUG_VM=y and XDP program that uses bpf_xdp_adjust_tail()
against a skb with frags, and mentioned helper consumed enough amount of
bytes that in turn released the page, following splat was observed:

[   32.204881] BUG: Bad page state in process test_progs  pfn:11c98b
[   32.207167] page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x11c98b
[   32.210084] flags: 0x1fffe0000000000(node=0|zone=1|lastcpupid=0x7fff)
[   32.212493] raw: 01fffe0000000000 dead000000000040 ff11000123c9b000 0000000000000000
[   32.218056] raw: 0000000000000000 0000000000000001 00000000ffffffff 0000000000000000
[   32.220900] page dumped because: page_pool leak
[   32.222636] Modules linked in: bpf_testmod(O) bpf_preload
[   32.224632] CPU: 6 UID: 0 PID: 3612 Comm: test_progs Tainted: G O        6.17.0-rc5-gfec474d29325 #6969 PREEMPT
[   32.224638] Tainted: [O]=OOT_MODULE
[   32.224639] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS rel-1.14.0-0-g155821a1990b-prebuilt.qemu.org 04/01/2014
[   32.224641] Call Trace:
[   32.224644]  <IRQ>
[   32.224646]  dump_stack_lvl+0x4b/0x70
[   32.224653]  bad_page.cold+0xbd/0xe0
[   32.224657]  __free_frozen_pages+0x838/0x10b0
[   32.224660]  ? skb_pp_cow_data+0x782/0xc30
[   32.224665]  bpf_xdp_shrink_data+0x221/0x530
[   32.224668]  ? skb_pp_cow_data+0x6d1/0xc30
[   32.224671]  bpf_xdp_adjust_tail+0x598/0x810
[   32.224673]  ? xsk_destruct_skb+0x321/0x800
[   32.224678]  bpf_prog_004ac6bb21de57a7_xsk_xdp_adjust_tail+0x52/0xd6
[   32.224681]  veth_xdp_rcv_skb+0x45d/0x15a0
[   32.224684]  ? get_stack_info_noinstr+0x16/0xe0
[   32.224688]  ? veth_set_channels+0x920/0x920
[   32.224691]  ? get_stack_info+0x2f/0x80
[   32.224693]  ? unwind_next_frame+0x3af/0x1df0
[   32.224697]  veth_xdp_rcv.constprop.0+0x38a/0xbe0
[   32.224700]  ? common_startup_64+0x13e/0x148
[   32.224703]  ? veth_xdp_rcv_one+0xcd0/0xcd0
[   32.224706]  ? stack_trace_save+0x84/0xa0
[   32.224709]  ? stack_depot_save_flags+0x28/0x820
[   32.224713]  ? __resched_curr.constprop.0+0x332/0x3b0
[   32.224716]  ? timerqueue_add+0x217/0x320
[   32.224719]  veth_poll+0x115/0x5e0
[   32.224722]  ? veth_xdp_rcv.constprop.0+0xbe0/0xbe0
[   32.224726]  ? update_load_avg+0x1cb/0x12d0
[   32.224730]  ? update_cfs_group+0x121/0x2c0
[   32.224733]  __napi_poll+0xa0/0x420
[   32.224736]  net_rx_action+0x901/0xe90
[   32.224740]  ? run_backlog_napi+0x50/0x50
[   32.224743]  ? clockevents_program_event+0x1cc/0x280
[   32.224746]  ? hrtimer_interrupt+0x31e/0x7c0
[   32.224749]  handle_softirqs+0x151/0x430
[   32.224752]  do_softirq+0x3f/0x60
[   32.224755]  </IRQ>

It's because xdp_rxq with mem model set to MEM_TYPE_PAGE_SHARED was used
when initializing xdp_buff.

Fix this by using new helper xdp_convert_skb_to_buff(). Unlike generic
XDP, veth turns the xdp_buff into an xdp_frame on XDP_{TX,REDIRECT}, so
the head is later released through the mem type as well and the
helper's frags-only reasoning is not enough here. Advertise
MEM_TYPE_PAGE_POOL only when the whole buffer was just copied into
rq->page_pool by skb_pp_cow_data(); otherwise keep the queue's original
MEM_TYPE_PAGE_SHARED model. An skb that skips the CoW has no frags, so
nothing can be released through the mem type while the program runs.

XDP_{TX,REDIRECT} no longer reset the mem type to the queue's original
model before the buff is converted to a frame, as the frame has to
carry the type chosen above. Nothing needs to be restored afterwards
either: the previous patch makes the frame path set its own mem type
and every skb sets it here. Add a page_pool variant of veth_xdp_get()
so that we avoid refcount underflow when draining page frag.

Frames redirected out of veth can now be MEM_TYPE_PAGE_POOL. A devmap
egress program only releases their frags correctly since commit
b3ca872947ef ("bpf: Use the frame mem type in devmap egress XDP"), so
that commit has to come first when backporting.

Fixes: 0ebab78cbcbf ("net: veth: add page_pool for page recycling")
Reported-by: Alexei Starovoitov <ast@kernel.org>
Closes: https://lore.kernel.org/bpf/CAADnVQ+bBofJDfieyOYzSmSujSfJwDTQhiz3aJw7hE+4E2_iPA@mail.gmail.com/
Cc: <stable@vger.kernel.org> # 6.4.x: b3ca872947ef: bpf: Use the frame mem type in devmap egress XDP
Cc: <stable@vger.kernel.org> # 6.4.x
Signed-off-by: Maciej Fijalkowski <maciej.fijalkowski@intel.com>
[sst: rebased onto bpf/master; claim MEM_TYPE_PAGE_POOL only after CoW into
 rq->page_pool; take page_pool refs on compound heads via netmem helpers;
 drop the rq mem model restores as every path sets its own mem type now;
 dropped Reviewed-by as the code changed]
Signed-off-by: Slawomir Stepien <sst@poczta.fm>
---
 drivers/net/veth.c | 52 +++++++++++++++++++++++++++++++---------------
 1 file changed, 35 insertions(+), 17 deletions(-)

diff --git a/drivers/net/veth.c b/drivers/net/veth.c
index afae2c6533b9..e159f0e00ddb 100644
--- a/drivers/net/veth.c
+++ b/drivers/net/veth.c
@@ -741,7 +741,7 @@ static void veth_xdp_rcv_bulk_skb(struct veth_rq *rq, void **frames,
 	}
 }
 
-static void veth_xdp_get(struct xdp_buff *xdp)
+static void veth_xdp_get_shared(struct xdp_buff *xdp)
 {
 	struct skb_shared_info *sinfo = xdp_get_shared_info_from_buff(xdp);
 	int i;
@@ -754,12 +754,36 @@ static void veth_xdp_get(struct xdp_buff *xdp)
 		netmem_inc_ref(skb_frag_netmem(&sinfo->frags[i]));
 }
 
+static void veth_xdp_get_pp(struct xdp_buff *xdp)
+{
+	struct skb_shared_info *sinfo = xdp_get_shared_info_from_buff(xdp);
+	int i;
+
+	page_pool_ref_netmem(netmem_compound_head(virt_to_netmem(xdp->data)));
+	if (likely(!xdp_buff_has_frags(xdp)))
+		return;
+
+	for (i = 0; i < sinfo->nr_frags; i++) {
+		netmem_ref netmem = skb_frag_netmem(&sinfo->frags[i]);
+
+		page_pool_ref_netmem(netmem_compound_head(netmem));
+	}
+}
+
+static void veth_xdp_get(struct xdp_buff *xdp)
+{
+	if (xdp->rxq->mem.type == MEM_TYPE_PAGE_POOL)
+		veth_xdp_get_pp(xdp);
+	else
+		veth_xdp_get_shared(xdp);
+}
+
 static int veth_convert_skb_to_xdp_buff(struct veth_rq *rq,
 					struct xdp_buff *xdp,
 					struct sk_buff **pskb)
 {
 	struct sk_buff *skb = *pskb;
-	u32 frame_sz;
+	bool pp_cow = false;
 
 	if (skb_shared(skb) || skb_head_is_locked(skb) ||
 	    skb_is_nonlinear(skb) ||
@@ -768,21 +792,17 @@ static int veth_convert_skb_to_xdp_buff(struct veth_rq *rq,
 			goto drop;
 
 		skb = *pskb;
+		pp_cow = true;
 	}
 
-	/* SKB "head" area always have tailroom for skb_shared_info */
-	frame_sz = skb_end_pointer(skb) - skb->head;
-	frame_sz += SKB_DATA_ALIGN(sizeof(struct skb_shared_info));
-	xdp_init_buff(xdp, frame_sz, &rq->xdp_rxq);
-	xdp_prepare_buff(xdp, skb->head, skb_headroom(skb),
-			 skb_headlen(skb), true);
-
-	if (skb_shinfo(skb)->nr_frags) {
-		skb_shinfo(skb)->xdp_frags_size = skb->data_len;
-		xdp_buff_set_frags_flag(xdp);
-	} else {
-		xdp_buff_clear_frags_flag(xdp);
-	}
+	xdp_convert_skb_to_buff(skb, xdp, &rq->xdp_rxq);
+	/* Unlike generic XDP, XDP_{TX,REDIRECT} turn the buff into an
+	 * xdp_frame, which later releases the head via mem.type as well.
+	 * Only claim page_pool memory when the whole buffer was copied into
+	 * rq->page_pool above; an skb that skipped the CoW is linear.
+	 */
+	if (!pp_cow)
+		rq->xdp_rxq.mem = rq->xdp_mem;
 	*pskb = skb;
 
 	return 0;
@@ -830,7 +850,6 @@ static struct sk_buff *veth_xdp_rcv_skb(struct veth_rq *rq,
 	case XDP_TX:
 		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++;
@@ -842,7 +861,6 @@ static struct sk_buff *veth_xdp_rcv_skb(struct veth_rq *rq,
 	case XDP_REDIRECT:
 		veth_xdp_get(xdp);
 		consume_skb(skb);
-		xdp->rxq->mem = rq->xdp_mem;
 		if (xdp_do_redirect(rq->dev, xdp, xdp_prog)) {
 			stats->rx_drops++;
 			goto err_xdp;
-- 
2.56.0


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* Re: [PATCH v6 bpf 0/3] xdp: fix page_pool leaks
  2026-11-04 12:13 [PATCH v6 bpf 0/3] xdp: fix page_pool leaks Slawomir Stepien
                   ` (2 preceding siblings ...)
  2026-11-04 12:13 ` [PATCH v6 bpf 3/3] veth: update mem type in xdp_buff Slawomir Stepien
@ 2026-11-04 12:44 ` Maciej Fijalkowski
  3 siblings, 0 replies; 7+ messages in thread
From: Maciej Fijalkowski @ 2026-11-04 12:44 UTC (permalink / raw)
  To: Slawomir Stepien
  Cc: ast, daniel, davem, kuba, hawk, john.fastabend, sdf, netdev, bpf,
	magnus.karlsson, aleksander.lobakin, ilias.apalodimas, toke,
	lorenzo, ihor.solodrai, tavip

On Wed, Nov 04, 2026 at 01:13:13PM +0100, Slawomir Stepien wrote:
> v1:
> https://lore.kernel.org/bpf/20251003140243.2534865-1-maciej.fijalkowski@intel.com/
> v1->v2:
> - add skb to xdp_buff converter (Jakub)
> - postpone restoration of veth's xdp_rxq_info mem model (AI)
> 
> v2:
> https://lore.kernel.org/bpf/20251017143103.2620164-1-maciej.fijalkowski@intel.com/T/
> v2->v3:
> - make skb->xdp_buff conversion agnostic of current skb->data position
>   (Toke)
> - do not pull mac header in veth (Toke)
> - use virt_to_head_page() when checking if xdp->data comes from
>   page_pool (Olek)
> - add ack from Toke
> 
> v3:
> https://lore.kernel.org/bpf/20251022125209.2649287-1-maciej.fijalkowski@intel.com/
> v3->v4:
> - include skb->head when calculating packet length (Toke, lkp)
> 
> v4:
> https://lore.kernel.org/bpf/20251027121318.2679226-1-maciej.fijalkowski@intel.com/
> v4->v5:
> - rely again on sk_buff::pp_recycle when setting mem type on rxq
>   (Jesper, Jakub)
> - add ack from Toke on patch 2 and restore it on patch 1
> 
> v5:
> https://lore.kernel.org/bpf/20251029221315.2694841-1-maciej.fijalkowski@intel.com/
> v5->v6:
> - rebase onto bpf/master
> - patch 1: set MEM_TYPE_PAGE_POOL unconditionally in
>   xdp_convert_skb_to_buff(); mem type only matters for frags consumed
>   by the program, and those always come from skb_pp_cow_data() (Jakub)
> - patch 1: add DEBUG_NET_WARN_ON_ONCE() for a non-linear skb without
>   pp_recycle and document the expectation in kdoc (Jakub); the warning
>   is limited to non-linear skbs since linear ones legitimately skip
>   the CoW
> - new patch 2: veth_xdp_rcv_one() ran the program on an xdp_frame with
>   whatever mem type the previous packet left on rq->xdp_rxq; use the
>   frame's mem type instead, as cpumap and devmap do. With patch 3 alone
>   veth sends page_pool frames to its peer and the peer's program
>   trimming frags crashed the kernel, so this comes first
> - patch 3 (was 2): veth turns the buff into an xdp_frame on
>   XDP_{TX,REDIRECT}, so the head is released through mem type too;
>   keep the queue's original model unless the buffer was copied into
>   rq->page_pool (Jakub)
> - patch 3: take page_pool refs on compound heads via netmem helpers
> - patch 3: do not restore the rxq mem model after XDP_{TX,REDIRECT}
>   and on the error path; with patch 2 every path sets its own mem type
> - patch 3: veth can now redirect MEM_TYPE_PAGE_POOL frames, which needs
>   commit b3ca872947ef ("bpf: Use the frame mem type in devmap egress
>   XDP") in devmap; list it as a stable prerequisite
> - drop Reviewed-by/Tested-by tags as patches 1 and 3 changed

I mentioned the changes [0] that were developed after v5 revision yet you
decided to go with what you had - that might be okay but to be transparent
it would be worth stating *why* you chose not to pursue further or explore
approach where __do_xdp_generic() is called from veth.

Was it due to semantics change pointed out by Bjorn? Or is it too big a
hammer for fixing page_pool leaks and should be implemented as a
follow-up?

Regardless, thank you for reviving this work, I am to blame for not
dedicating further effort here.

[0]: https://lore.kernel.org/bpf/20260509084858.773921-5-maciej.fijalkowski@intel.com/

Thanks,
MF

> 
> Hi,
> 
> here we fix page_pool leaks which happen when fragment is released
> within XDP program and memory type is set incorrectly. The reports come
> from syzbot and AF_XDP test suite.
> 
> Most of the changes are about pulling out the common code for
> init/prepare xdp_buff based on existing skb and supplying it with
> correct xdp_rxq_info's mem_type that is assigned to xdp_buff. A bit more
> stuff, page_pool related, had to be done on veth side.
> 
> Originally, this work was started by Octavian Purdila and continued by
> Maciej up to v5. Maciej then took the larger veth rework from the v5
> review to net-next as an RFC [1]. This v6 picks up the v5 series, with
> Maciej's agreement, as the minimal fix for bpf and stable, and addresses
> the v5 review. Maciej stays the author of patches 1 and 3 and my changes
> are noted in their commit messages; patch 2 is new.
> 
> For veth, Jakub listed three options: stop turning skbs into xdp_frames
> in veth, decide the head's mem type when the CoW was skipped, or CoW the
> head on the Tx/Redirect path. The first one changes veth's XDP handling
> beyond a fix (it is what the RFC does), so patch 3 takes the second:
> veth knows whether it just copied the buffer into rq->page_pool, and
> only then advertises MEM_TYPE_PAGE_POOL. An skb that skipped the CoW is
> linear and keeps the queue's original model, as before this series.
> The two do not conflict: the RFC can go on top of this series and
> replace the veth parts of patches 2 and 3.
> 
> [1] https://lore.kernel.org/all/20260509084858.773921-1-maciej.fijalkowski@intel.com/
> 
> Patch 2 fixes the same problem for frames in veth_xdp_rcv_one() that
> commit b3ca872947ef ("bpf: Use the frame mem type in devmap egress
> XDP") fixed for devmap; cpumap already handled it.
> 
> Testing (x86_64, QEMU via virtme-ng, KASAN + DEBUG_VM + DEBUG_NET; the
> series ran on bpf/master fb7bd3a2aad7 and, rebased without changes, the
> reproducers, kprobe path checks, devmap test and selftests below ran
> again on 05441d697f7f with the same results; the runs without the series
> on bpf/master c8a8a68d3ab4):
> - reproducer: an xdp.frags program that trims all frags with
>   bpf_xdp_adjust_tail() on 8000-byte pings over a 9000 MTU veth pair;
>   without the series both generic XDP and native veth XDP hit
>   "BUG: Bad page state ... page_pool leak" followed by
>   VM_BUG_ON_PAGE(page_ref_count(page) == 0) and a panic; with the
>   series 20/20 packets are trimmed with no splat
> - native veth XDP_TX of multi-buffer packets, with and without
>   trimming frags first (200 large + 200 small pings each), then veth
>   teardown and a 75s wait for page_pool "stalled pool shutdown" or
>   negative inflight warnings: none
> - BPF selftests on bpf/master with tools/testing/selftests/bpf/config:
>   ./test_progs -t ns_xsk,xdp: all 34 tests (205 subtests) passed, no
>   splats, with and without the series
> - the standalone AF_XDP suite (test_xsk.sh) also runs the tests that
>   test_progs skips as flaky; XDP_ADJUST_TAIL_SHRINK_MULTI_BUFF reports
>   PASS either way, but without the series dmesg shows 60 "page_pool
>   leak" splats from generic XDP (bpf_xdp_adjust_tail -> __xdp_return ->
>   page_frag_free on pages allocated by skb_pp_cow_data()); with the
>   series there are none
> - frame path: one veth bounces 8000-byte pings with XDP_TX, its peer
>   trims all frags of the resulting frames (IPv6 off, so no skb touches
>   the peer's rxq first); with patches 1+3 only: "page_pool leak",
>   VM_BUG_ON_PAGE() and a panic in veth_xdp_rcv_one(); with the whole
>   series 50/50 frames are trimmed, kprobes show all __xdp_return() calls
>   with MEM_TYPE_PAGE_POOL, no splat, also with IPv6 on so that skbs and
>   frames interleave on the peer's queue; bpf/master alone passes as well
>   since veth only sends MEM_TYPE_PAGE_SHARED frames there
> - remaining veth paths, each confirmed with kprobes: skbs that skip the
>   CoW (frames passed up by one veth and forwarded back into it: 1229 of
>   1260 runs without skb_pp_cow_data(), heads from the page_pool),
>   XDP_REDIRECT failing on the skb path (40 xdp_return_buff()) and on the
>   frame path (40 xdp_return_frame()), XDP_PASS of 20 frames after their
>   frags were trimmed; no splat, and none of the page_pool shutdown or
>   inflight warnings 75s after teardown
> - devmap egress after patch 3: veth redirects copied skbs through a
>   devmap whose egress program trims all frags; with the series 20/20
>   trimmed, all __xdp_return() calls with MEM_TYPE_PAGE_POOL, no splat,
>   nothing 75s after teardown; with b3ca872947ef reverted on top of the
>   series: "page_pool leak" from page_frag_free() under
>   dev_map_bpf_prog_run(), VM_BUG_ON_PAGE() and a panic, hence the
>   stable prerequisite in patch 3
> - W=1 builds of the touched files with LLVM=1 and with DEBUG_NET=n: no
>   warnings
> - XDP_ADJUST_TAIL_GROW fails in DRV mode with and without the series,
>   so it is unrelated
> - not tested: page_pool frames from hardware drivers redirected into
>   veth, bpf_xdp_pull_data(), multi-queue load, the full test_progs run
> 
> Performance (the same VM with 8 vCPUs, non-debug kernels with
> PAGE_POOL_STATS, bpf/master fb7bd3a2aad7 with and without the series;
> pktgen sends skbs into one end of a veth pair, the program on the other
> end counts packets; median of 6 runs of 8s over two boots per kernel):
> - 64-byte packets with native XDP_DROP, XDP_TX and XDP_REDIRECT (devmap
>   to another veth), and generic XDP_DROP on 1 and 4 CPUs with 64 and
>   8000-byte packets: within 2% of the base kernel, no regression
> - native veth with 8000-byte packets (3 frags): XDP_TX +8.8%
>   (0.507 -> 0.552 Mpps), XDP_REDIRECT +8.4% (0.613 -> 0.664 Mpps).
>   Without the series veth_xdp_get() takes plain page references on
>   the head and frags, so the pages copied into rq->page_pool are
>   released from the pool instead of recycled (~1.2M/s
>   rx_pp_recycle_released_ref plus slow allocations); with page_pool
>   references they go back to the ring (~1.4M/s rx_pp_recycle_ring, no
>   released_ref)
> Absolute numbers in a VM differ between boots, so only the relative
> change is meaningful.
> 
> This v6 was prepared with an AI coding assistant (Claude) from the v5
> review thread; the assistant wrote the code changes, all of patch 2,
> the changelog updates, and ran the tests listed above.
> 
> 
> 
> Maciej Fijalkowski (2):
>   xdp: introduce xdp_convert_skb_to_buff()
>   veth: update mem type in xdp_buff
> 
> Slawomir Stepien (1):
>   veth: use the frame mem type when running XDP on an xdp_frame
> 
>  drivers/net/veth.c | 56 ++++++++++++++++++++++++++++++----------------
>  include/net/xdp.h  | 36 +++++++++++++++++++++++++++++
>  net/core/dev.c     | 25 ++++-----------------
>  3 files changed, 77 insertions(+), 40 deletions(-)
> 
> 
> base-commit: 05441d697f7f9a927264f4058cbbc6a03dfcac9d
> -- 
> 2.56.0
> 

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH v6 bpf 1/3] xdp: introduce xdp_convert_skb_to_buff()
  2026-11-04 12:13 ` [PATCH v6 bpf 1/3] xdp: introduce xdp_convert_skb_to_buff() Slawomir Stepien
@ 2026-11-04 12:53   ` bot+bpf-ci
  0 siblings, 0 replies; 7+ messages in thread
From: bot+bpf-ci @ 2026-11-04 12:53 UTC (permalink / raw)
  To: sst, ast, daniel, davem, kuba, hawk, john.fastabend, sdf
  Cc: netdev, bpf, magnus.karlsson, aleksander.lobakin,
	ilias.apalodimas, toke, lorenzo, ihor.solodrai, tavip,
	maciej.fijalkowski, sst, syzbot+ff145014d6b0ce64a173, ast, andrii,
	daniel, martin.lau, eddyz87, yonghong.song, mason, ihor.solodrai

[-- Attachment #1: Type: text/plain, Size: 4359 bytes --]

> xdp: introduce xdp_convert_skb_to_buff()

TL;DR:
- Persistent mem.type store in shared rxq may make BPF_PROG_TEST_RUN frag
  release hit page_pool path: WARN, leaked page
- Commit message nits: inaccurate "assigned to xdp_buff", stale "about to be
  integrated"

> diff --git a/include/net/xdp.h b/include/net/xdp.h
> --- a/include/net/xdp.h
> +++ b/include/net/xdp.h

[ ... ]

> +/**
> + * xdp_convert_skb_to_buff - build an xdp_buff on top of an skb
> + * @skb: skb to describe, packet data is taken from its MAC header onwards
> + * @xdp: xdp_buff to initialize
> + * @xdp_rxq: Rx queue info to attach to @xdp
> + *
> + * The memory type set on @xdp_rxq is only used to release fragments that
> + * the XDP program consumes; the head can not be freed from under the skb.
> + * A non-linear @skb is expected to have gone through skb_pp_cow_data(), so
> + * all of its fragments are page_pool backed.
> + */

[ ... ]

> +	DEBUG_NET_WARN_ON_ONCE(skb_is_nonlinear(skb) && !skb->pp_recycle);
> +	xdp->rxq->mem.type = MEM_TYPE_PAGE_POOL;
> +}

Can this store break other users of the same xdp_rxq_info?

bpf_prog_run_generic_xdp() passes &netif_get_rxqueue(skb)->xdp_rxq, which
is the device's long-lived dev->_rx[i].xdp_rxq.  The new mem.type is never
restored, so after the first generic XDP packet the queue stays
MEM_TYPE_PAGE_POOL.  Before this patch nothing wrote this field, and it
stayed at MEM_TYPE_PAGE_SHARED from xdp_rxq_info_reg().

bpf_prog_test_run_xdp() builds its xdp_buff on the same structure:

    rxqueue = __netif_get_rx_queue(current->nsproxy->net_ns->loopback_dev, 0);
    rxqueue->xdp_rxq.frag_size = PAGE_SIZE;
    xdp_init_buff(&xdp, rxqueue->xdp_rxq.frag_size, &rxqueue->xdp_rxq);

(or the rxq selected by ctx->ingress_ifindex in xdp_convert_md_to_buff()).
It allocates its frags with alloc_page(GFP_KERNEL) and relies on the
default MEM_TYPE_PAGE_SHARED.

If an xdp.frags program run through BPF_PROG_TEST_RUN then releases a whole
frag with bpf_xdp_adjust_tail() or bpf_xdp_pull_data():

bpf_xdp_shrink_data()
  __xdp_return(netmem, MEM_TYPE_PAGE_POOL, ...)
    page_pool_put_full_netmem(netmem_get_pp(netmem), ...)

this runs the page_pool path on an ordinary page.  pp_ref_count overlays
page->private, which is 0, so page_pool_unref_netmem() sees
atomic_long_sub_return() return -1 and hits WARN_ON(ret < 0).  The page is
not freed, and it has already been removed from sinfo->frags, so the
__free_page() loop at the end of bpf_prog_test_run_xdp() does not free it
either.

To reproduce, attach a generic XDP program to lo, pass one packet, then run
BPF_PROG_TEST_RUN on an xdp.frags program with data_size_in larger than one
page whose bpf_xdp_adjust_tail() shrinks by at least the last frag.  The
result is a WARN (a panic with panic_on_warn) and a leaked page per run.

The kdoc says "The memory type set on @xdp_rxq is only used to release
fragments that the XDP program consumes", and the commit message says "The
mem type is only consulted when the program releases fragments".  Both
describe only the generic XDP buffer, and neither mentions that the value
persists in shared rxq state.

Could mem.type be saved and restored around the program run, or could
bpf_prog_test_run_xdp() set the mem type it needs (or use its own
xdp_rxq_info)?  The later veth commits in this series only touch
drivers/net/veth.c and do not address this.

Also, the commit message says:

> Pull out the existing code from bpf_prog_run_generic_xdp() that
> init/prepares xdp_buff onto new helper xdp_convert_skb_to_buff() and
> embed there rxq's mem_type initialization that is assigned to xdp_buff.

Is "assigned to xdp_buff" accurate?  struct xdp_buff has no mem type field;
the helper writes xdp->rxq->mem.type, i.e. netdev_rx_queue::xdp_rxq.mem.type.

And the commit message also says:

> This problem was triggered by syzbot as well as AF_XDP test suite which
> is about to be integrated to BPF CI.

This isn't a bug, but is "about to be integrated" still accurate?
selftests/bpf test_xsk.c already appears to be integrated into test_progs
in this tree (d1aec26fce25), so the wording looks stale.


---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md

CI run summary: https://github.com/kernel-patches/bpf/actions/runs/41828746707

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH v6 bpf 3/3] veth: update mem type in xdp_buff
  2026-11-04 12:13 ` [PATCH v6 bpf 3/3] veth: update mem type in xdp_buff Slawomir Stepien
@ 2026-11-04 12:53   ` bot+bpf-ci
  0 siblings, 0 replies; 7+ messages in thread
From: bot+bpf-ci @ 2026-11-04 12:53 UTC (permalink / raw)
  To: sst, ast, daniel, davem, kuba, hawk, john.fastabend, sdf
  Cc: netdev, bpf, magnus.karlsson, aleksander.lobakin,
	ilias.apalodimas, toke, lorenzo, ihor.solodrai, tavip,
	maciej.fijalkowski, sst, stable, ast, andrii, daniel, martin.lau,
	eddyz87, yonghong.song, mason, ihor.solodrai

[-- Attachment #1: Type: text/plain, Size: 6455 bytes --]

> veth: update mem type in xdp_buff

TL;DR: Commit message/stable tag question: the Cc: stable prerequisites may be
incomplete, since the patch needs xdp_convert_skb_to_buff() and the veth frame
mem type commit, which may not be backported.

> veth: update mem type in xdp_buff
>
> When skb's headroom is not sufficient for XDP purposes,
> skb_pp_cow_data() returns new skb with requested headroom space. This
> skb was provided by page_pool.
>
> For CONFIG_DEBUG_VM=y and XDP program that uses bpf_xdp_adjust_tail()
> against a skb with frags, and mentioned helper consumed enough amount of
> bytes that in turn released the page, following splat was observed:
>
> [   32.204881] BUG: Bad page state in process test_progs  pfn:11c98b
> [   32.207167] page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x11c98b
> [   32.210084] flags: 0x1fffe0000000000(node=0|zone=1|lastcpupid=0x7fff)
> [   32.212493] raw: 01fffe0000000000 dead000000000040 ff11000123c9b000 0000000000000000
> [   32.218056] raw: 0000000000000000 0000000000000001 00000000ffffffff 0000000000000000
> [   32.220900] page dumped because: page_pool leak
> [   32.222636] Modules linked in: bpf_testmod(O) bpf_preload
> [   32.224632] CPU: 6 UID: 0 PID: 3612 Comm: test_progs Tainted: G O        6.17.0-rc5-gfec474d29325 #6969 PREEMPT
> [   32.224638] Tainted: [O]=OOT_MODULE
> [   32.224639] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS rel-1.14.0-0-g155821a1990b-prebuilt.qemu.org 04/01/2014
> [   32.224641] Call Trace:
> [   32.224644]  <IRQ>
> [   32.224646]  dump_stack_lvl+0x4b/0x70
> [   32.224653]  bad_page.cold+0xbd/0xe0
> [   32.224657]  __free_frozen_pages+0x838/0x10b0
> [   32.224660]  ? skb_pp_cow_data+0x782/0xc30
> [   32.224665]  bpf_xdp_shrink_data+0x221/0x530
> [   32.224668]  ? skb_pp_cow_data+0x6d1/0xc30
> [   32.224671]  bpf_xdp_adjust_tail+0x598/0x810
> [   32.224673]  ? xsk_destruct_skb+0x321/0x800
> [   32.224678]  bpf_prog_004ac6bb21de57a7_xsk_xdp_adjust_tail+0x52/0xd6
> [   32.224681]  veth_xdp_rcv_skb+0x45d/0x15a0
> [   32.224684]  ? get_stack_info_noinstr+0x16/0xe0
> [   32.224688]  ? veth_set_channels+0x920/0x920
> [   32.224691]  ? get_stack_info+0x2f/0x80
> [   32.224693]  ? unwind_next_frame+0x3af/0x1df0
> [   32.224697]  veth_xdp_rcv.constprop.0+0x38a/0xbe0
> [   32.224700]  ? common_startup_64+0x13e/0x148
> [   32.224703]  ? veth_xdp_rcv_one+0xcd0/0xcd0
> [   32.224706]  ? stack_trace_save+0x84/0xa0
> [   32.224709]  ? stack_depot_save_flags+0x28/0x820
> [   32.224713]  ? __resched_curr.constprop.0+0x332/0x3b0
> [   32.224716]  ? timerqueue_add+0x217/0x320
> [   32.224719]  veth_poll+0x115/0x5e0
> [   32.224722]  ? veth_xdp_rcv.constprop.0+0xbe0/0xbe0
> [   32.224726]  ? update_load_avg+0x1cb/0x12d0
> [   32.224730]  ? update_cfs_group+0x121/0x2c0
> [   32.224733]  __napi_poll+0xa0/0x420
> [   32.224736]  net_rx_action+0x901/0xe90
> [   32.224740]  ? run_backlog_napi+0x50/0x50
> [   32.224743]  ? clockevents_program_event+0x1cc/0x280
> [   32.224746]  ? hrtimer_interrupt+0x31e/0x7c0
> [   32.224749]  handle_softirqs+0x151/0x430
> [   32.224752]  do_softirq+0x3f/0x60
> [   32.224755]  </IRQ>
>
> It's because xdp_rxq with mem model set to MEM_TYPE_PAGE_SHARED was used
> when initializing xdp_buff.
>
> Fix this by using new helper xdp_convert_skb_to_buff(). Unlike generic
> XDP, veth turns the xdp_buff into an xdp_frame on XDP_{TX,REDIRECT}, so
> the head is later released through the mem type as well and the
> helper's frags-only reasoning is not enough here. Advertise
> MEM_TYPE_PAGE_POOL only when the whole buffer was just copied into
> rq->page_pool by skb_pp_cow_data(); otherwise keep the queue's original
> MEM_TYPE_PAGE_SHARED model. An skb that skips the CoW has no frags, so
> nothing can be released through the mem type while the program runs.
>
> XDP_{TX,REDIRECT} no longer reset the mem type to the queue's original
> model before the buff is converted to a frame, as the frame has to
> carry the type chosen above. Nothing needs to be restored afterwards
> either: the previous patch makes the frame path set its own mem type
> and every skb sets it here. Add a page_pool variant of veth_xdp_get()
> so that we avoid refcount underflow when draining page frag.
>
> Frames redirected out of veth can now be MEM_TYPE_PAGE_POOL. A devmap
> egress program only releases their frags correctly since commit
> b3ca872947ef ("bpf: Use the frame mem type in devmap egress XDP"), so
> that commit has to come first when backporting.
>
> Fixes: 0ebab78cbcbf ("net: veth: add page_pool for page recycling")
> Reported-by: Alexei Starovoitov <ast@kernel.org>
> Closes: https://lore.kernel.org/bpf/CAADnVQ+bBofJDfieyOYzSmSujSfJwDTQhiz3aJw7hE+4E2_iPA@mail.gmail.com/
> Cc: <stable@vger.kernel.org> # 6.4.x: b3ca872947ef: bpf: Use the frame mem type in devmap egress XDP
> Cc: <stable@vger.kernel.org> # 6.4.x
> Signed-off-by: Maciej Fijalkowski <maciej.fijalkowski@intel.com>
> [sst: rebased onto bpf/master; claim MEM_TYPE_PAGE_POOL only after CoW into
>  rq->page_pool; take page_pool refs on compound heads via netmem helpers;
>  drop the rq mem model restores as every path sets its own mem type now;
>  dropped Reviewed-by as the code changed]
> Signed-off-by: Slawomir Stepien <sst@poczta.fm>
>

The changelog says b3ca872947ef has to come first when backporting, and the
Cc: stable tag lists only that commit as a prerequisite:

  Cc: <stable@vger.kernel.org> # 6.4.x: b3ca872947ef: bpf: Use the frame mem type in devmap egress XDP

Does the prerequisite list also need 7a20126eee2a ("xdp: introduce
xdp_convert_skb_to_buff()")?

veth_convert_skb_to_xdp_buff() now calls xdp_convert_skb_to_buff(), so this
patch will not build without it.  That commit has no Cc: stable tag, and its
Fixes: target is newer than the 6.4.x stable target given here, so stable
tooling may not pick it up for every tree this patch is tagged for.

Should 7a20126eee2a get a stable tag?

Similarly, dropping the rq->xdp_mem restore is only safe once
veth_xdp_rcv_one() sets the mem type before the program runs, which is done
by 8fbcefc9f069 ("veth: use the frame mem type when running XDP on an
xdp_frame").  Should that commit also be listed as a prerequisite?


---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md

CI run summary: https://github.com/kernel-patches/bpf/actions/runs/41828746707

^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2026-11-04 12:53 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-04 12:13 [PATCH v6 bpf 0/3] xdp: fix page_pool leaks Slawomir Stepien
2026-11-04 12:13 ` [PATCH v6 bpf 1/3] xdp: introduce xdp_convert_skb_to_buff() Slawomir Stepien
2026-11-04 12:53   ` bot+bpf-ci
2026-11-04 12:13 ` [PATCH v6 bpf 2/3] veth: use the frame mem type when running XDP on an xdp_frame Slawomir Stepien
2026-11-04 12:13 ` [PATCH v6 bpf 3/3] veth: update mem type in xdp_buff Slawomir Stepien
2026-11-04 12:53   ` bot+bpf-ci
2026-11-04 12:44 ` [PATCH v6 bpf 0/3] xdp: fix page_pool leaks Maciej Fijalkowski

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox