From: sashiko-bot@kernel.org
To: "Björn Töpel" <bjorn@kernel.org>, bpf@vger.kernel.org
Subject: Re: [PATCH net-next v1 06/11] page_pool: Coordinate refill with memory providers
Date: Wed, 11 Nov 2026 10:22:45 +0000 [thread overview]
Message-ID: <sashiko-outbox-208789@kernel.org> (raw)
In-Reply-To: <20261110102039.1002605-7-bjorn@kernel.org>
Thank you for your contribution! Sashiko AI review found 3 potential
issues to consider:
- [High] xsk: use-after-free of pp->pool in xsk_pp_refill_done() after
detach
- [Medium] net: wrong mp_napi state after netdev_rx_queue_reconfig()
error recovery
- [Medium] net: dangling rxq->napi left by netif_mp_napi_del() and
republished
--
commit 3fe1ff48cc345ebc097d0481e784047c7126ab42
Author: Björn Töpel <bjorn@kernel.org>
page_pool: Coordinate refill with memory providers
This commit adds page_pool_refill_done() and NAPI wakeup tracking on RX
queues so that memory providers such as AF_XDP can re-poll or wake queues
when buffers return.
> diff --git a/net/core/netdev_rx_queue.c b/net/core/netdev_rx_queue.c
> index 4e2f906fbc735..3fe904b56791b 100644
> --- a/net/core/netdev_rx_queue.c
> +++ b/net/core/netdev_rx_queue.c
> @@ -116,6 +116,94 @@ bool netif_all_mps_have_cap(struct net_device *dev, u32 cap)
> return true;
> }
>
> +/**
> + * netif_mp_wake - schedule the NAPI that refills a memory provider queue
> + * @rxq: physical RX queue of the provider
> + *
> + * Context: IRQs enabled, not in hardirq. The caller keeps the device of
> + * @rxq alive.
> + *
> + * Return: false if no NAPI refills the queue.
> + */
> +bool netif_mp_wake(struct netdev_rx_queue *rxq)
> +{
> + struct napi_struct *napi;
> + bool running = false;
> +
> + rcu_read_lock();
> + napi = rcu_dereference(rxq->mp_napi);
> + if (napi) {
> + local_bh_disable();
> + napi_schedule(napi);
> + local_bh_enable();
> + running = true;
> + }
> + rcu_read_unlock();
> +
> + return running;
> +}
> +
> +/* Point wakeups at the NAPI of the running queue, and schedule it once: a
> + * refill during the start may have found no buffers.
> + */
> +void netif_mp_update_napi(struct netdev_rx_queue *rxq)
> +{
> + const struct memory_provider_ops *ops = rxq->mp_params.mp_ops;
> + struct napi_struct *napi = NULL;
> +
> + if (rxq->mp_armed && ops && ops->refill_done)
> + napi = rxq->napi;
> + if (rcu_access_pointer(rxq->mp_napi) == napi)
> + return;
> +
> + rcu_assign_pointer(rxq->mp_napi, napi);
> + if (napi)
> + netif_mp_wake(rxq);
> +}
> +
> +static void netif_mp_arm_napi(struct netdev_rx_queue *rxq)
> +{
> + rxq->mp_armed = true;
> + netif_mp_update_napi(rxq);
> +}
> +
> +static void netif_mp_disarm_napi(struct netdev_rx_queue *rxq)
> +{
> + rxq->mp_armed = false;
> + RCU_INIT_POINTER(rxq->mp_napi, NULL);
> +}
> +
> +void netif_mp_arm_dev(struct net_device *dev)
> +{
> + unsigned int i;
> +
> + for (i = 0; i < dev->real_num_rx_queues; i++)
> + netif_mp_arm_napi(__netif_get_rx_queue(dev, i));
> +}
> +
> +void netif_mp_disarm_dev(struct net_device *dev)
> +{
> + unsigned int i;
> +
> + for (i = 0; i < dev->num_rx_queues; i++)
> + netif_mp_disarm_napi(&dev->_rx[i]);
> +}
> +
> +/* The grace period of netif_napi_del() covers wakeups that found @napi. */
> +void netif_mp_napi_del(struct napi_struct *napi)
> +{
> + struct net_device *dev = napi->dev;
> + unsigned int i;
> +
> + /* free_netdev() frees the queues before it deletes the last NAPIs */
> + if (dev->reg_state != NETREG_REGISTERED)
> + return;
> +
> + for (i = 0; i < dev->num_rx_queues; i++)
> + if (rcu_access_pointer(dev->_rx[i].mp_napi) == napi)
> + RCU_INIT_POINTER(dev->_rx[i].mp_napi, NULL);
> +}
[Severity: Medium]
Does netif_mp_napi_del() leave a dangling pointer in dev->_rx[i].napi?
__netif_napi_del_locked() calls netif_mp_napi_del(), which clears
dev->_rx[i].mp_napi, including when mp_napi is already NULL because
__dev_close_many() or netdev_rx_queue_reconfig() disarmed the queue first.
Because dev->_rx[i].napi is left pointing to @napi, if a driver deletes a
NAPI without calling netif_queue_set_napi(..., NULL), the next
netif_mp_update_napi() copies the freed dev->_rx[i].napi back into
rxq->mp_napi and calls napi_schedule() on it, causing a use-after-free.
> @@ -152,13 +240,15 @@ static int netdev_rx_queue_reconfig(struct net_device *dev,
> goto err_free_new_queue_mem;
>
> if (netif_running(dev)) {
> + netif_mp_disarm_napi(rxq);
> err = qops->ndo_queue_stop(dev, old_mem, rxq_idx);
> if (err)
> - goto err_free_new_queue_mem;
> + goto err_arm_old_queue;
>
> err = qops->ndo_queue_start(dev, qcfg_new, new_mem, rxq_idx);
> if (err)
> goto err_start_queue;
> + netif_mp_arm_napi(rxq);
> } else {
> swap(new_mem, old_mem);
> }
> @@ -183,8 +273,13 @@ static int netdev_rx_queue_reconfig(struct net_device *dev,
> "Failed to restart old queue in error path. RX queue %d may be unhealthy.",
> rxq_idx);
> qops->ndo_queue_mem_free(dev, old_mem);
> + goto err_free_new_queue_mem;
> }
>
> +err_arm_old_queue:
> + /* The old queue may run */
> + netif_mp_arm_napi(rxq);
> +
> err_free_new_queue_mem:
> qops->ndo_queue_mem_free(dev, new_mem);
[Severity: Medium]
Does err_arm_old_queue arm rxq->mp_napi using the target rxq->mp_params
instead of the running old queue's provider state?
Both __netif_mp_open_rxq() and __netif_mp_close_rxq() mutate rxq->mp_params
before calling netdev_rx_queue_reconfig():
net/core/netdev_rx_queue.c:__netif_mp_open_rxq() {
...
rxq->mp_params = *p;
...
ret = netdev_rx_queue_reconfig(dev, rxq_idx, &qcfg[0], &qcfg[1]);
if (ret)
goto err_clear_mp;
return 0;
err_clear_mp:
memset(&rxq->mp_params, 0, sizeof(rxq->mp_params));
return ret;
}
When ndo_queue_stop() or ndo_queue_start() fails during __netif_mp_open_rxq(),
err_arm_old_queue sees the new provider in rxq->mp_params, sets rxq->mp_napi
to rxq->napi, and wakes NAPI before err_clear_mp zeroes rxq->mp_params,
leaving rxq->mp_napi set with no provider installed. Conversely,
__netif_mp_close_rxq() zeroes rxq->mp_params before calling
netdev_rx_queue_reconfig(), so on failure err_arm_old_queue sees NULL mp_ops
and leaves rxq->mp_napi NULL even though the old provider-backed queue is
still running.
> diff --git a/net/xdp/xsk_pp.c b/net/xdp/xsk_pp.c
> index c2214e718cdb3..a77213f7fef6b 100644
> --- a/net/xdp/xsk_pp.c
> +++ b/net/xdp/xsk_pp.c
[ ... ]
> @@ -232,11 +253,47 @@ static bool xsk_pp_release_netmem(struct page_pool *pool, netmem_ref netmem)
> pp->state[idx] = XSK_PP_REUSE;
> pp->reuse[pp->reuse_cnt++] = idx;
> }
> + starved = pp->starved;
> + WRITE_ONCE(pp->starved, false);
> spin_unlock_bh(&pp->lock);
>
> + /* A starved queue may wait for this release */
> + if (starved)
> + xsk_pp_wake(pp);
> +
> return false;
> }
>
> +static bool xsk_pp_refill_done(struct page_pool *pool, enum pp_refill state)
> +{
> + struct xsk_pp *pp = pool->mp_priv;
> + struct xsk_buff_pool *xp = pp->pool;
> + bool wait = xp->uses_need_wakeup;
> + bool more;
> +
> + if (state == PP_REFILL_FULL && !READ_ONCE(pp->starved))
> + return true;
> +
> + spin_lock_bh(&pp->lock);
> + if (!pp->fq) {
> + spin_unlock_bh(&pp->lock);
> + return true;
> + }
[Severity: High]
Can xsk_pp_refill_done() dereference pp->pool after xp_destroy() has freed
it?
A page_pool holds its own reference on struct xsk_pp via pp->refs, while
pp->pool is an uncounted backpointer to struct xsk_buff_pool. When
xp_release_deferred() tears down the pool, xsk_pp_unbind() calls
netif_mp_close_rxq() and unconditionally calls xsk_pp_detach(pp) (which sets
pp->fq = NULL under pp->lock and unmaps DMA), and xp_release_deferred() then
frees pp->pool in xp_destroy(). If netif_mp_close_rxq() fails in
netdev_rx_queue_reconfig(), the old queue and its page_pool keep running and
calling xsk_pp_refill_done(), which reads xp->uses_need_wakeup before taking
pp->lock and checking pp->fq, causing a use-after-free.
In addition, if netif_mp_open_rxq() fails in xsk_pp_bind() while a page_pool
still holds a reference on pp (so xsk_pp_abandon(pp) returns false),
xsk_pp_bind() leaves pp->fq non-NULL when xsk_bind() calls xp_destroy().
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261110102039.1002605-1-bjorn@kernel.org?part=6
next prev parent reply other threads:[~2026-11-11 10:22 UTC|newest]
Thread overview: 49+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-10 10:20 [PATCH net-next v1 00/11] xsk: Zero copy through page-pool memory providers Björn Töpel
2026-11-10 10:20 ` [PATCH net-next v1 01/11] net: Get skb fragment addresses through netmem Björn Töpel
2026-11-12 8:32 ` saeed bishara
2026-11-10 10:20 ` [PATCH net-next v1 02/11] net: Add memory provider capabilities Björn Töpel
2026-11-10 22:19 ` Stanislav Fomichev
2026-11-11 12:41 ` Björn Töpel
2026-11-11 23:01 ` Stanislav Fomichev
2026-11-11 10:22 ` sashiko-bot
2026-11-11 22:41 ` netdev-bot+sashiko
2026-11-10 10:20 ` [PATCH net-next v1 03/11] xdp: Copy buffers with restricted lifetime Björn Töpel
2026-11-10 22:32 ` Stanislav Fomichev
2026-11-11 13:27 ` Björn Töpel
2026-11-11 23:05 ` Stanislav Fomichev
2026-11-11 10:22 ` sashiko-bot
2026-11-11 22:41 ` netdev-bot+sashiko
2026-11-10 10:20 ` [PATCH net-next v1 04/11] xsk: Allocate UMEM chunks through page_pool Björn Töpel
2026-11-10 15:08 ` Magnus Karlsson
2026-11-11 13:51 ` Björn Töpel
2026-11-10 23:02 ` Stanislav Fomichev
2026-11-11 13:59 ` Björn Töpel
2026-11-11 23:09 ` Stanislav Fomichev
2026-11-11 22:41 ` netdev-bot+sashiko
2026-11-10 10:20 ` [PATCH net-next v1 05/11] xsk: Install UMEM page pool providers in the core Björn Töpel
2026-11-10 23:50 ` Stanislav Fomichev
2026-11-11 14:01 ` Björn Töpel
2026-11-11 8:59 ` Magnus Karlsson
2026-11-11 19:26 ` Björn Töpel
2026-11-12 8:08 ` Magnus Karlsson
2026-11-11 10:22 ` sashiko-bot
2026-11-11 22:41 ` netdev-bot+sashiko
2026-11-10 10:20 ` [PATCH net-next v1 06/11] page_pool: Coordinate refill with memory providers Björn Töpel
2026-11-10 23:51 ` Stanislav Fomichev
2026-11-11 19:37 ` Björn Töpel
2026-11-11 23:13 ` Stanislav Fomichev
2026-11-11 10:22 ` sashiko-bot [this message]
2026-11-11 22:41 ` netdev-bot+sashiko
2026-11-10 10:20 ` [PATCH net-next v1 07/11] xsk: Deliver provider chunks without copying Björn Töpel
2026-11-11 22:41 ` netdev-bot+sashiko
2026-11-11 23:20 ` Stanislav Fomichev
2026-11-10 10:20 ` [PATCH net-next v1 08/11] eth: fbnic: Support XDP redirect Björn Töpel
2026-11-11 10:22 ` sashiko-bot
2026-11-10 10:20 ` [PATCH net-next v1 09/11] eth: fbnic: Use netmem for RX headers and XDP_TX Björn Töpel
2026-11-10 10:20 ` [PATCH net-next v1 10/11] eth: fbnic: Support RX buffers it cannot split Björn Töpel
2026-11-11 10:22 ` sashiko-bot
2026-11-11 21:45 ` Dimitri Daskalakis
2026-11-11 22:41 ` netdev-bot+sashiko
2026-11-10 10:20 ` [PATCH net-next v1 11/11] eth: fbnic: Support AF_XDP page pool zero copy Björn Töpel
2026-11-11 10:22 ` sashiko-bot
2026-11-11 22:41 ` netdev-bot+sashiko
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=sashiko-outbox-208789@kernel.org \
--to=sashiko-bot@kernel.org \
--cc=bjorn@kernel.org \
--cc=bpf@vger.kernel.org \
--cc=sashiko-reviews@lists.linux.dev \
/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