Re: [PATCH] xsk: fix memory corruptions in net/core/xdp.c

From: Clement Lecigne

Date: Fri Jun 26 2026 - 05:13:11 EST


Thanks, Olek!

I submitted a v2 of the patch directly as a reply to this thread by
mistake, do you still want me to post this new version separately?

On Thu, Jun 25, 2026 at 5:14 PM Alexander Lobakin
<aleksander.lobakin@xxxxxxxxx> wrote:
>
> From: Clement Lecigne <clecigne@xxxxxxxxxx>
> Date: Wed, 24 Jun 2026 08:41:28 +0000
>
> > From: Clément Lecigne <clecigne@xxxxxxxxxx>
> >
> > Commit 560d958c6c68 ("xsk: add generic XSk &xdp_buff -> skb conversion")
> > introduced a vulnerability in the handling of XDP_PASS for AF_XDP zero-copy
> > frames.
> >
> > Note: Currently, this specific AF_XDP zero-copy conversion path is only
> > reachable from the drivers/net/ethernet/intel/ice driver.
>
> idpf uses this, too (every driver based on libeth_xdp in general,
> currently these two).

Done.

>
> >
> > When building an skb, xdp_build_skb_from_zc() uses the chunk size
> > (xdp->frame_sz) for the allocation. However, napi_build_skb() automatically
> > reserves space at the end of the allocation for the skb_shared_info
> > structure.
> >
> > Most high performance UMEM applications use 4K chunks, where the
> > corruption cannot happen. However, if the UMEM is configured with 2KB
> > chunks (a very common configuration to maximize packet density in memory),
> > a standard 1500 MTU packet will trigger the corruption because the required
> > space exceeds the 2048 byte chunk size:
> >
> > Headroom (256) + Packet (1514) + skb_shared_info (320) = 2090 bytes
> >
> > Because 2090 bytes > 2048 bytes and __skb_put() does not perform bounds
> > checking, the memcpy() writes past the available linear data area and
> > corrupts the skb_shared_info structure. This can lead to arbitrary code
> > execution if pointers like destructor_arg are overwritten.
> >
> > Additionally, in xdp_copy_frags_from_zc(), the allocation size is set
> > strictly to the fragment size (len), but the subsequent memcpy() uses
> > LARGEST_ALIGN(len). This mismatch results in an out-of-bounds write of
> > up to 7 bytes, which triggers KASAN warnings and is unsafe despite typical
> > page pool allocator padding.
> >
> > Fix the skb allocation in xdp_build_skb_from_zc() by dynamically
> > calculating the exact truesize required: the sum of the headroom, the
> > packet length, and the skb_shared_info overhead, properly aligned via
> > SKB_DATA_ALIGN.
> >
> > Fix the out-of-bounds write in xdp_copy_frags_from_zc() by rounding up
> > the allocation request using LARGEST_ALIGN(len) to match the copy
> > operation.
> >
> > Fixes: 560d958c6c68 ("xsk: add generic XSk &xdp_buff -> skb conversion")
> > CC: Alexander Lobakin <aleksander.lobakin@xxxxxxxxx>
> > CC: Eric Dumazet <edumazet@xxxxxxxxxx>
> > Signed-off-by: Clément Lecigne <clecigne@xxxxxxxxxx>
> > ---
> > diff --git a/net/core/xdp.c b/net/core/xdp.c
> > index 9890a30584ba..f36d1fb875ab 100644
> > --- a/net/core/xdp.c
> > +++ b/net/core/xdp.c
> > @@ -699,7 +699,7 @@ static noinline bool xdp_copy_frags_from_zc(struct sk_buff *skb,
> > for (u32 i = 0; i < nr_frags; i++) {
> > const skb_frag_t *frag = &xinfo->frags[i];
> > u32 len = skb_frag_size(frag);
> > - u32 offset, truesize = len;
> > + u32 offset, truesize = LARGEST_ALIGN(len);
>
> I think you need to re-sort this to keep RCT, now that the truesize
> initialization is way longer than it was.

Done.

>
> const skb_frag_t *frag = &xinfo->frags[i];
> u32 offset, len = skb_frag_size(frag);
> u32 truesize = LARGEST_ALIGN(len);
> struct page *page;
>
> > struct page *page;
> >
> > page = page_pool_dev_alloc(pp, &offset, &truesize);
>
> BTW usually LARGEST_ALIGN() aligns to 16, I've never seen a bigger one.
> IIRC Page Pool never returns a truesize aligned to a smaller value. But
> if you're really able to trigger this, it probably does?
>
> > @@ -740,7 +740,9 @@ struct sk_buff *xdp_build_skb_from_zc(struct xdp_buff *xdp)
> > {
> > const struct xdp_rxq_info *rxq = xdp->rxq;
> > u32 len = xdp->data_end - xdp->data_meta;
> > - u32 truesize = xdp->frame_sz;
> > + u32 headroom = xdp->data_meta - xdp->data_hard_start;
> > + u32 truesize = SKB_DATA_ALIGN(headroom + len) +
> > + SKB_DATA_ALIGN(sizeof(struct skb_shared_info));
>
> Ah now I get it: xdp->frame_sz doesn't account the shinfo for
> single-buffer frames, only for multi-buffer ones. The fix looks correct,
> but I'd use SKB_HEAD_ALIGN() since it does exactly what you're
> open-coding here and sort the declarations:

Good idea, done.


>
> {
> u32 hr = 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 = SKB_HEAD_ALIGN(hr + len);
> struct sk_buff *skb = NULL;
> struct page_pool *pp;
> int metalen;
> void *data;
>
> if (!IS_ENABLED(CONFIG_PAGE_POOL))
> return NULL;
>
> ...
>
> > struct sk_buff *skb = NULL;
> > struct page_pool *pp;
> > int metalen;
> > @@ -762,7 +764,7 @@ 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));
>
> Thanks,
> Olek

Thanks,
-clem

Attachment: smime.p7s
Description: S/MIME Cryptographic Signature