[RFC net-next 01/15] xdp: Size zero-copy skb heads by their contents

From: Björn Töpel

Date: Fri Oct 02 2026 - 15:03:44 EST


xdp_build_skb_from_zc() sizes the skb head by the XSK frame size,
which leaves no room for skb_shared_info. With 2 KiB chunks, a
1514 byte frame overwrites 48 bytes of it. Both copies also round
the length up to LARGEST_ALIGN, past the end of the new buffers.

Size the head from headroom plus data, copy exactly the data, and
drop frames whose head does not fit in a page.

Discovered by an AI code review agent. Reproduced on fbnic in QEMU
with AF_XDP zero-copy from the page-pool series: an XDP program that
adds metadata, grows the frame to its end and passes it gets UMEM
bytes in the skb's shared info. A new xskxceiver test,
XDP_PASS_FULL_FRAME, checks this and passes with the fix. The test
will be posted separately.

Fixes: 560d958c6c68 ("xsk: add generic XSk &xdp_buff -> skb conversion")
Signed-off-by: Björn Töpel <bjorn@xxxxxxxxxx>
---
net/core/xdp.c | 14 ++++++++++----
1 file changed, 10 insertions(+), 4 deletions(-)

diff --git a/net/core/xdp.c b/net/core/xdp.c
index 1d679e8fd649..386240bd24c9 100644
--- a/net/core/xdp.c
+++ b/net/core/xdp.c
@@ -709,7 +709,7 @@ static noinline bool xdp_copy_frags_from_zc(struct sk_buff *skb,
}

memcpy(page_address(page) + offset, skb_frag_address(frag),
- LARGEST_ALIGN(len));
+ len);
__skb_fill_page_desc_noacc(sinfo, i, page, offset, len);

tsize += truesize;
@@ -738,17 +738,23 @@ static noinline bool xdp_copy_frags_from_zc(struct sk_buff *skb,
*/
struct sk_buff *xdp_build_skb_from_zc(struct xdp_buff *xdp)
{
+ u32 headroom = xdp->data_meta - xdp->data_hard_start;
const struct xdp_rxq_info *rxq = xdp->rxq;
u32 len = xdp->data_end - xdp->data_meta;
- u32 truesize = xdp->frame_sz;
struct sk_buff *skb = NULL;
struct page_pool *pp;
+ u32 truesize;
int metalen;
void *data;

if (!IS_ENABLED(CONFIG_PAGE_POOL))
return NULL;

+ /* The XSK frame size leaves no room for skb_shared_info. */
+ truesize = SKB_HEAD_ALIGN(headroom + len);
+ if (unlikely(truesize > PAGE_SIZE))
+ return NULL;
+
local_lock_nested_bh(&system_page_pool.bh_lock);
pp = this_cpu_read(system_page_pool.pool);
data = page_pool_dev_alloc_va(pp, &truesize);
@@ -762,9 +768,9 @@ struct sk_buff *xdp_build_skb_from_zc(struct xdp_buff *xdp)
}

skb_mark_for_recycle(skb);
- skb_reserve(skb, xdp->data_meta - xdp->data_hard_start);
+ skb_reserve(skb, headroom);

- memcpy(__skb_put(skb, len), xdp->data_meta, LARGEST_ALIGN(len));
+ memcpy(__skb_put(skb, len), xdp->data_meta, len);

metalen = xdp->data - xdp->data_meta;
if (metalen > 0) {
--
2.55.0