From: Maciej Fijalkowski <maciej.fijalkowski@intel.com>
To: Simon Horman <horms@kernel.org>
Cc: <blbllhy@gmail.com>, <magnus.karlsson@intel.com>,
<kerneljasonxing@gmail.com>, <sdf@fomichev.me>,
<davem@davemloft.net>, <edumazet@google.com>, <kuba@kernel.org>,
<pabeni@redhat.com>, <netdev@vger.kernel.org>,
<bpf@vger.kernel.org>, <linux-kernel@vger.kernel.org>,
<AutonomousCodeSecurity@microsoft.com>,
<tgopinath@linux.microsoft.com>, <kys@microsoft.com>
Subject: Re: [PATCH net v3] xsk: fix NULL pointer dereference in __xsk_rcv()
Date: Wed, 19 Aug 2026 13:29:38 +0200 [thread overview]
Message-ID: <aoWTos9p9CTVfvma@boxer> (raw)
In-Reply-To: <20260810132505.769431-1-horms@kernel.org>
On Mon, Aug 10, 2026 at 02:25:05PM +0100, Simon Horman wrote:
> This is an AI-generated review of your patch. The human sending this
> email has considered the AI review valid, or at least plausible.
> Full review at: https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260806204757.47817-1-blbllhy@gmail.com?part=1
> ---
> --- Patch [1]: [PATCH net v3] xsk: fix NULL pointer dereference in __xsk_rcv() ---
> commit ee824acf57213f0a5f7afb287ff22c8b8eefdc3c
> Author: Cen Zhang (Microsoft) <blbllhy@gmail.com>
>
> xsk: fix NULL pointer dereference in __xsk_rcv()
>
> The patch converts the multi-buffer copy path in __xsk_rcv() into a
> two-stage transaction: all buffers for a packet are allocated and staged
> on the pool's xskb_list first, and only once that succeeds are the data
> copied and the RX descriptors reserved. On an allocation failure the
> staged buffers are recycled with xsk_buff_free().
>
> Link: https://lore.kernel.org/netdev/20260724164719.99563-1-blbllhy@gmail.com
>
> A couple of questions below, starting with the changelog.
>
> The commit message ends at:
>
> "Only after the allocation stage succeeds, copy the data, reserve the
> RX descriptors, and release the buffers in an error-free loop using
> xsk_buff_get_frag()."
>
> [Severity: Low]
> [Finding: main-1-0]
> [Sources: opus-5]
> Should the message also describe the two new drop conditions the patch
> adds?
>
> The first is the new WARN_ON_ONCE(!list_empty(&xs->pool->xskb_list)),
> which drops the packet and bumps rx_dropped based on pool-wide state that
> is also written by xsk_buff_add_frag() and drained by xsk_rcv_zc().
>
> The second is the new handling of a Fill Ring that hands out the same
> chunk twice ("xsk_xdp == first || !list_empty(&xskb->list_node)"), which
> is entirely under userspace control. Before this patch, an aligned-mode
> Fill Ring containing a duplicate address made __xp_alloc() return
> &pool->heads[xp_aligned_extract_idx(addr)] twice and the old loop emitted
> two RX descriptors both pointing at that chunk; now the packet is dropped
> and the Fill Ring entries are consumed with no descriptor produced.
>
> Both are explained in the v3 changelog below the "---" separator, which is
> dropped when the patch is applied, so neither git history nor a stable
> backporter (the patch carries a Fixes: tag) sees them.
>
> > diff --git a/net/xdp/xsk.c b/net/xdp/xsk.c
> > index 7855ee09c4b64..02ff3ad97410f 100644
> > --- a/net/xdp/xsk.c
> > +++ b/net/xdp/xsk.c
>
> [ ... ]
>
> > @@ -343,11 +344,34 @@ static int __xsk_rcv(struct xdp_sock *xs, struct xdp_buff *xdp, u32 len)
> > frag = &sinfo->frags[0];
> > }
> >
> > + if (WARN_ON_ONCE(!list_empty(&xs->pool->xskb_list)))
> > + goto err_alloc;
>
> [Severity: Medium]
> [Finding: main-1-1]
> [Sources: opus-5]
> Can this condition ever clear itself once it fires? The err_alloc label
> only does:
>
> err_alloc:
> xs->rx_dropped++;
> return -ENOMEM;
>
> so pool->xskb_list is neither drained nor repaired. pool->xskb_list is a
> field of the long-lived struct xsk_buff_pool, initialized once by
> INIT_LIST_HEAD(&pool->xskb_list) in xp_create_and_assign_umem(), and it is
> never reset per packet.
>
> If a stale entry is ever left on that list, doesn't every subsequent
> multi-buffer copy-mode packet for every socket sharing the pool take this
> branch and get dropped, with the stranded buffers never returned to
> pool->free_list? Since WARN_ON_ONCE only prints once, the drops after that
> are silent.
>
> Would draining the list here, or keeping the staging list call-local, be
> preferable to a bailout for a condition that persists?
>
> > +
> > + first = xsk_buff_alloc(xs->pool);
> > + if (!first)
> > + goto err_alloc;
> > +
> > + xdp_buff_set_frags_flag(first);
> > + for (i = 1; i < num_desc; i++) {
> > + xsk_xdp = xsk_buff_alloc(xs->pool);
> > + if (!xsk_xdp)
> > + goto err_free;
> > +
> > + xskb = container_of(xsk_xdp, struct xdp_buff_xsk, xdp);
> > + if (unlikely(xsk_xdp == first ||
> > + !list_empty(&xskb->list_node)))
> > + goto err_free;
> > +
> > + list_add_tail(&xskb->list_node, &xs->pool->xskb_list);
> > + }
>
> [Severity: High]
> [Finding: main-5-7]
> [Sources: opus-5]
> What serializes access to xs->pool->xskb_list here? The two callers of
> __xsk_rcv() do not share a lock:
>
> net/xdp/xsk.c:xsk_generic_rcv() {
> err = xsk_rcv_check(xs, xdp, len);
> if (!err) {
> spin_lock_bh(&xs->pool->rx_lock);
> err = __xsk_rcv(xs, xdp, len);
> ...
> }
>
> net/xdp/xsk.c:xsk_rcv() {
> ...
> err = __xsk_rcv(xs, xdp, len);
> if (!err)
> xdp_return_buff(xdp);
> ...
> }
Hi, catching up on emails. Once again this is AI nonsense.
A single pool can NOT be shared in two different modes. Referred race is
about SKB and DRV modes. This is simply impossible to happen.
I see v4 got applied with local-list approach. But to back up the original
idea, pool->xskb_list would be safe to use IMHO.
cpumap issue is real tho, but it doesn't sound right to have workarounds
in xsk due to cpumap being broken.
>
> xsk_rcv() (reached from __xsk_map_redirect()) holds no pool lock, so the
> per-packet staging state now lives in pool-global list state with an empty
> lockset. Previously each buffer was a local variable in this function.
>
> A concrete unlocked, non-NAPI caller is cpumap:
>
> kernel/bpf/cpumap.c:cpu_map_bpf_prog_run_xdp() {
> struct xdp_rxq_info rxq = {};
> ...
> rxq.dev = xdpf->dev_rx;
> rxq.mem.type = xdpf->mem_type;
> /* TODO: report queue_index to xdp_rxq_info */
> ...
> err = xdp_do_redirect(xdpf->dev_rx, &xdp, rcpu->prog);
> }
>
> queue_index defaults to 0 and mem.type is page-based, so an XDP_REDIRECT
> into an xskmap from that program passes xsk_dev_queue_valid(), takes the
> copy path, and runs from a cpumap kthread that can execute concurrently
> with the NIC NAPI, another cpumap kthread, or a generic-XDP
> xsk_generic_rcv() on another CPU that only holds rx_lock.
>
> Can that lead to concurrent list_add_tail() on the same list_head, i.e.
> list corruption? And can xsk_buff_get_frag() in the copy loop pop a buffer
> staged by the other context, so the same umem chunk ends up published in
> two different RX descriptors?
>
> Note also that the helpers touching this list in
> include/net/xdp_sock_drv.h have no internal locking:
>
> include/net/xdp_sock_drv.h:xsk_buff_get_frag() {
> frag = list_first_entry_or_null(&xskb->pool->xskb_list,
> struct xdp_buff_xsk, list_node);
> if (frag) {
> list_del_init(&frag->list_node);
> ret = &frag->xdp;
> }
> }
>
> They were previously reached only from the NAPI/zero-copy driver context.
>
> > +
> > + xdp_buff_clear_frags_flag(first);
> > + xsk_xdp = first;
> > do {
> > u32 to_len = frame_size + meta_len;
> > u32 copied;
> >
> > - xsk_xdp = xsk_buff_alloc(xs->pool);
> > + next = xsk_buff_get_frag(xsk_xdp);
>
> [Severity: Low]
> [Finding: main-2-3]
> [Sources: opus-5]
> This isn't a bug today, but the producer/consumer protocol for
> pool->xskb_list is now implemented two different ways in-tree.
>
> The canonical producer is:
>
> include/net/xdp_sock_drv.h:xsk_buff_add_frag() {
> if (!__xdp_buff_add_frag(head, virt_to_netmem(data),
> offset_in_page(data), xdp->data_end - data,
> xdp->frame_sz, false))
> return false;
>
> frag = container_of(xdp, struct xdp_buff_xsk, xdp);
> list_add_tail(&frag->list_node, &frag->pool->xskb_list);
> }
>
> __xdp_buff_add_frag() sets the head's XDP_FLAGS_HAS_FRAGS and updates the
> head's skb_shared_info nr_frags/len. The new code open-codes the producer
> side with a bare list_add_tail() plus xdp_buff_set_frags_flag(first) and no
> skb_shared_info initialization, while using the official consumer
> xsk_buff_get_frag(). That yields a frags-flagged xdp_buff whose shared info
> at xdp_data_hard_end() is uninitialized. Today nothing reads it
> (xsk_buff_free() only writes nr_frags = 0), but would it be safer to reuse
> xsk_buff_add_frag() here?
>
> Related: xsk_buff_get_frag() is declared as
>
> static inline struct xdp_buff *xsk_buff_get_frag(const struct xdp_buff *first)
>
> and every other caller passes the head buffer, whereas this loop passes the
> fragment returned by the previous iteration. That only works because the
> body uses the argument solely to reach xskb->pool.
>
> > copy_to = xsk_xdp->data - meta_len;
> >
> > copied = xsk_copy_xdp(copy_to, ©_from, to_len, &from_len, &frag, rem);
> > @@ -356,10 +380,21 @@ static int __xsk_rcv(struct xdp_sock *xs, struct xdp_buff *xdp, u32 len)
> > xskb = container_of(xsk_xdp, struct xdp_buff_xsk, xdp);
> > __xsk_rcv_zc_safe(xs, xskb, copied - meta_len,
> > rem ? XDP_PKT_CONTD : 0);
> > + xsk_xdp = next;
> > meta_len = 0;
> > } while (rem);
> >
> > return 0;
> > +
> > +err_free:
> > + /* Re-set frags flag; xsk_buff_alloc() may have cleared first->flags
> > + * if the same Fill Ring address aliased back to first.
> > + */
> > + xdp_buff_set_frags_flag(first);
> > + xsk_buff_free(first);
> > +err_alloc:
> > + xs->rx_dropped++;
> > + return -ENOMEM;
> > }
>
> Following on from the locking question above: with the frags flag set,
> xsk_buff_free() walks the whole pool list:
>
> include/net/xdp_sock_drv.h:xsk_buff_free() {
> list_for_each_entry_safe(pos, tmp, xskb_list, list_node) {
> list_del_init(&pos->list_node);
> xp_free(pos);
> }
> ...
> }
>
> so if another context has entries staged on pool->xskb_list at that moment,
> does this error path xp_free() buffers that are still in flight there,
> skewing pool->free_list/free_heads accounting?
prev parent reply other threads:[~2026-08-19 11:30 UTC|newest]
Thread overview: 4+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-06 20:47 [PATCH net v3] xsk: fix NULL pointer dereference in __xsk_rcv() Cen Zhang (Microsoft)
2026-08-10 13:25 ` Simon Horman
2026-08-13 21:42 ` Cen Zhang (Microsoft)
2026-08-19 11:29 ` Maciej Fijalkowski [this message]
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=aoWTos9p9CTVfvma@boxer \
--to=maciej.fijalkowski@intel.com \
--cc=AutonomousCodeSecurity@microsoft.com \
--cc=blbllhy@gmail.com \
--cc=bpf@vger.kernel.org \
--cc=davem@davemloft.net \
--cc=edumazet@google.com \
--cc=horms@kernel.org \
--cc=kerneljasonxing@gmail.com \
--cc=kuba@kernel.org \
--cc=kys@microsoft.com \
--cc=linux-kernel@vger.kernel.org \
--cc=magnus.karlsson@intel.com \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=sdf@fomichev.me \
--cc=tgopinath@linux.microsoft.com \
/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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox