[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