[PATCH v3] net/af_xdp: fix shared UMEM refcount corruption

Stephen Hemminger stephen at networkplumber.org
Tue Aug 18 16:07:15 CEST 2026


On Mon, 17 Aug 2026 12:13:01 -0400
sandeep.penigalapati at intel.com wrote:

> From: Sandeep Penigalapati <sandeep.penigalapati at intel.com>
> 
> Shared UMEM is meant to be shared by a limited number of sockets,
> governed by the mempool size (max_xsks). When the UMEM was already at
> capacity (refcnt >= max_xsks), xdp_umem_configure() returned the UMEM
> without incrementing its refcount, so the extra socket used it
> unaccounted for.
> 
> This missing reference has two consequences. During queue setup the
> fill-queue reservation is chosen from the refcount, so the sharing
> socket reserves into its own uninitialised fill queue and crashes. At
> close, the under-counted refcount reaches zero while the UMEM is still
> in use, freeing it early and causing a use-after-free.
> 
> Reject sharing once the UMEM is at capacity by returning NULL, so queue
> setup fails cleanly with -ENOMEM. This applies the per-mempool socket
> limit that shared UMEM was always intended to respect.
> 
> Harden the failure path this makes reachable: clear rxq->umem and its
> paired txq->umem when xsk_configure() fails, and skip queues whose UMEM
> is not yet set in get_shared_umem(), so a later scan over the same
> mempool cannot dereference a NULL or dangling UMEM.
> 
> Also document the shared mempool sizing requirement (4096 mbufs per
> socket).
> 
> Note: on stable branches this is a behaviour change. Shared-UMEM setups
> that previously appeared to start, until the fill-queue crash or the
> use-after-free at close, now fail cleanly at Rx queue setup with
> -ENOMEM.
> 
> Fixes: 74b46340e2d4 ("net/af_xdp: support shared UMEM")
> Cc: stable at dpdk.org
> 
> Signed-off-by: Sandeep Penigalapati <sandeep.penigalapati at intel.com>
> ---

AI still spots errors on this patch. It can be wrong, but it
does seem to track error paths well.

Review of [PATCH v3] net/af_xdp: fix shared UMEM refcount corruption

The core fix is right: returning NULL once refcnt >= max_xsks removes
both the unaccounted reference and the bogus reserve_before decision
that followed from it.  Fixes: tag resolves to 74b46340e2d4
("net/af_xdp: support shared UMEM"), so Cc: stable is appropriate.

Findings below are against the tree with the patch applied.


Error
-----

1. drivers/net/af_xdp/rte_eth_af_xdp.c, xsk_configure()

   The fq_bufs allocated before socket creation are leaked on the
   error paths this patch is hardening.

   In the shared case reserve_before is false, so the 2048 mbufs
   obtained by rte_pktmbuf_alloc_bulk() are not handed to
   reserve_fill_queue() until after the socket exists:

	ret = rte_pktmbuf_alloc_bulk(rxq->umem->mb_pool, fq_bufs,
				     reserve_size);
	...
	if (reserve_before) { ... }          /* skipped when sharing */
	...
	ret = load_custom_xdp_prog(...);
	if (ret)
		goto out_umem;               /* fq_bufs leaked */
	...
	ret = create_shared_socket(...);
	if (ret)
		goto out_umem;               /* fq_bufs leaked */

	if (!reserve_before)
		ret = reserve_fill_queue(...);

   reserve_fill_queue_zc() frees the array itself when it fails, and
   the reserve_before path therefore cleans up, but out_umem does not.
   Every sharing socket that fails to bind leaks a full burst of
   mbufs back out of the shared mempool, which is the same mempool
   whose size now decides max_xsks.

   Suggest freeing them at out_umem, guarded so the buffers are not
   freed twice:

	out_xsk:
		xsk_socket__delete(rxq->xsk);
	out_umem:
		if (!reserve_before)
			rte_pktmbuf_free_bulk(fq_bufs, reserve_size);

   (or a bool tracking whether reserve_fill_queue() has consumed
   them, if the out_xsk path is folded in later).

   This predates the patch, but it is on the exact failure path the
   commit message says it is hardening, and the new -ENOMEM rejection
   makes it easier to reach.


Warning
-------

2. drivers/net/af_xdp/rte_eth_af_xdp.c, eth_dev_close()

   The patch makes "mb_pool set, umem NULL" a deliberate marker for a
   queue whose setup failed, and get_shared_umem() correctly skips
   such queues with continue.  eth_dev_close() still treats the same
   state as end-of-list:

	for (i = 0; i < internals->queue_cnt; i++) {
		rxq = &internals->rx_queues[i];
		if (rxq->umem == NULL)
			break;
		xsk_socket__delete(rxq->xsk);
		...
	}

   If a middle queue fails setup and a later queue succeeds (easy with
   per-queue mempools: queue 0 on pool A, queue 1 on pool A rejected
   at capacity, queue 2 on pool B), close stops at queue 1 and leaks
   queue 2's xsk socket and its UMEM reference, so that UMEM is never
   destroyed.

   break should be continue.  Skipping is safe: a failed queue now has
   umem == NULL and its socket was already deleted or never created.

3. Commit message errno does not match the code.

   The message states twice that queue setup "fails cleanly with
   -ENOMEM".  xsk_configure() does return -ENOMEM, but
   eth_rx_queue_setup() discards it:

	if (xsk_configure(internals, rxq, nb_rx_desc)) {
		AF_XDP_LOG_LINE(ERR, "Failed to configure xdp socket");
		ret = -EINVAL;
		goto err;
	}

   The application sees -EINVAL.  Either propagate the return value
   from xsk_configure() or reword the commit message; the behaviour
   note for the stable branches should say what the application will
   actually observe.


Info
----

4. xsk_configure(), early return leaves txq->umem stale.

	rxq->umem = xdp_umem_configure(internals, rxq);
	if (rxq->umem == NULL)
		return -ENOMEM;
	txq->umem = rxq->umem;

   The out_umem path now clears both rxq->umem and txq->umem, but this
   return clears only rxq->umem.  Harmless on a first setup because the
   queue arrays are rte_zmalloc'd, but stale after a re-setup of a
   queue that previously succeeded.  Clearing txq->umem here too would
   make the two exits consistent.

5. The capacity check and the increment are not atomic.

   get_shared_umem() drops internal_list_lock before returning, so the
   load of refcnt and the fetch_add that follows are separate steps.
   Two threads configuring queues on the same mempool can both observe
   refcnt < max_xsks and both increment.  Control-path setup is
   normally single threaded so this is not urgent, but a
   compare-exchange loop would actually enforce the limit the patch is
   adding.

6. Log text when max_xsks is zero.

   max_xsks is only assigned when the creating port has shared_umem
   set, and it is populated_size / 4096, so it is 0 for a pool smaller
   than 4096 mbufs or for a UMEM created by a non-shared port.  In
   those cases the new message reads "already at max 0 sockets", which
   points away from the real cause.  Worth special-casing, e.g. report
   the mempool as too small for shared UMEM when max_xsks == 0.

7. get_shared_umem(), the new check can use the existing alias.

	if (internals->rx_queues[i].umem == NULL)
		continue;

   list_rxq already points at &internals->rx_queues[i] a few lines
   above; list_rxq->umem would be shorter and match the ctx_exists()
   call just above it.


Notes
-----

Documentation change is accurate: max_xsks is populated_size divided
by ETH_AF_XDP_NUM_BUFFERS (4096), so 4096 * N is the correct figure
for N sockets.

Not verified: build.  libxdp/libbpf were not available here, so the
af_xdp PMD was not compiled.


More information about the stable mailing list