Netdev List
 help / color / mirror / Atom feed
* [PATCH net v3 0/4] net: devmem: fix RX and TX binding teardown on device unregistration
@ 2026-10-14 16:17 Mina Almasry
  2026-10-14 16:17 ` [PATCH net v3 1/4] net: devmem: unmap dma_buf synchronously on queue uninstall Mina Almasry
                   ` (3 more replies)
  0 siblings, 4 replies; 8+ messages in thread
From: Mina Almasry @ 2026-10-14 16:17 UTC (permalink / raw)
  To: Jakub Kicinski, Mina Almasry, Pavel Begunkov, David Wei,
	Stanislav Fomichev, Taehee Yoo, Paolo Abeni, Kaiyuan Zhang,
	Bobby Eshleman, netdev, linux-kernel, linux-kselftest,
	linux-media, dri-devel
  Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Simon Horman,
	Shuah Khan, Sumit Semwal, Christian König, Daniel Borkmann,
	Nikolay Aleksandrov, Tariq Toukan, Kaifeng Wang, Dragos Tatulea

Fix two device-unregistration lifecycle bugs in devmem TCP bindings and
add netdevsim support plus selftests covering both RX and TX device
unregistration:

1. net: devmem: unmap dma_buf synchronously on queue uninstall
   Synchronously unmap and detach binding->attachment under binding->lock
   when the last RX queue is uninstalled in mp_dmabuf_devmem_uninstall(),
   and unmap RX bindings that were already unbound on NETDEV_UNREGISTER,
   preventing slab-use-after-free in dma_unmap_sg_attrs() when the binding
   is freed after dma_dev.

2. net: devmem: detach TX bindings on NETDEV_UNREGISTER
   Extend net_devmem_dev_unregister() to fully detach and unmap TX
   bindings when binding->dev unregisters, or clear binding->vdev alone
   when only a virtual device unregisters while binding->dev lives.

3. netdevsim: support devmem RX and TX bindings
   Enable PP_FLAG_DMA_MAP | PP_FLAG_ALLOW_UNREADABLE_NETMEM, 64-bit DMA mask
   on nsim_bus_dev, and NETMEM_TX_DMA on netdevsim so devmem RX/TX bindings
   can be exercised in software selftests.

4. selftests: net: add devmem RX and TX netdev unregister tests
   Add devmem_bind_rx_unregister_check and devmem_bind_tx_unregister_check
   to tools/testing/selftests/net/nl_netdev.py.

Cc: Tariq Toukan <tariqt@nvidia.com>
Cc: Kaifeng Wang <kaifengw@google.com>
Cc: Dragos Tatulea <dtatulea@nvidia.com>

---
v3:
- 1/4: Run the NETDEV_UNREGISTER walk under the netdev instance lock,
  and skip it when the device only moves to another netns. (Sashiko)
- 2/4: Detach all matching TX bindings, then synchronize_net() once
  before unmapping them. (Sashiko)
- 3/4: Only request page pool DMA mapping with CONFIG_HAS_DMA, refuse
  pp_hold on memory provider pools, and drop unreadable skbs in the
  software PSP checksum path. (Sashiko)
- 4/4: Check only the test's dma-buf in debugfs, skip instead of passing
  silently, clean up on errors, compute UDMABUF_CREATE per arch, and
  add the needed config options. (Sashiko, Dragos)
- Link to v2: https://lore.kernel.org/netdev/20261012174300.2338784-1-almasrymina@google.com/

v2:
- 1/4: Don't unmap when a leased queue is unleased. Keep bindings in
  net_devmem_dmabuf_bindings until freed, hidden from TX lookups by an
  xarray mark, and unmap unbound RX bindings on NETDEV_UNREGISTER. Update
  the binding->lock and binding->ref comments. (Sashiko)
- 2/4: Detach TX bindings from the same net_devmem_lock-protected walk,
  which now also covers unbound TX bindings, and clear the LIVE mark
  instead of erasing the ID. The walk no longer uses RCU or takes
  binding references (Stanislav).
- Link to v1: https://lore.kernel.org/netdev/20261010025532.839559-1-almasrymina@google.com/

Mina Almasry (4):
  net: devmem: unmap dma_buf synchronously on queue uninstall
  net: devmem: detach TX bindings on NETDEV_UNREGISTER
  netdevsim: support devmem RX and TX bindings
  selftests: net: add devmem RX and TX netdev unregister tests

 drivers/net/netdevsim/bus.c              |   1 +
 drivers/net/netdevsim/netdev.c           |  27 ++++-
 drivers/net/netdevsim/psp.c              |   5 +
 net/core/devmem.c                        | 143 ++++++++++++++++++++---
 net/core/devmem.h                        |  10 +-
 net/core/netdev-genl.c                   |   1 +
 tools/testing/selftests/net/config       |   4 +
 tools/testing/selftests/net/nl_netdev.py | 125 +++++++++++++++++++-
 8 files changed, 290 insertions(+), 26 deletions(-)


base-commit: 513444cc62e231a11c9b15ea88de0890da9b2b4f
-- 
2.56.0.176.g3575e65682-goog


^ permalink raw reply	[flat|nested] 8+ messages in thread

* [PATCH net v3 1/4] net: devmem: unmap dma_buf synchronously on queue uninstall
  2026-10-14 16:17 [PATCH net v3 0/4] net: devmem: fix RX and TX binding teardown on device unregistration Mina Almasry
@ 2026-10-14 16:17 ` Mina Almasry
  2026-10-15 19:21   ` netdev-bot+sashiko
  2026-10-14 16:17 ` [PATCH net v3 2/4] net: devmem: detach TX bindings on NETDEV_UNREGISTER Mina Almasry
                   ` (2 subsequent siblings)
  3 siblings, 1 reply; 8+ messages in thread
From: Mina Almasry @ 2026-10-14 16:17 UTC (permalink / raw)
  To: Jakub Kicinski, Mina Almasry, Pavel Begunkov, David Wei,
	Stanislav Fomichev, Taehee Yoo, Paolo Abeni, Kaiyuan Zhang,
	Bobby Eshleman, netdev, linux-kernel, linux-kselftest,
	linux-media, dri-devel
  Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Simon Horman,
	Shuah Khan, Sumit Semwal, Christian König, Daniel Borkmann,
	Nikolay Aleksandrov, Tariq Toukan, Kaifeng Wang, Dragos Tatulea

When a bound net_device is unregistered while userspace still holds a
netlink or TCP socket reference on a devmem binding,
mp_dmabuf_devmem_uninstall() clears binding->dev to NULL so
netdev_nl_sock_priv_destroy() will not touch the unregistered
net_device, but leaves the dma_buf attachment mapped until
__net_devmem_dmabuf_binding_free() runs later on socket close.

Because dma_buf_attach() does not take a reference on dma_dev, device
removal frees dma_dev while the attachment is still mapped. Closing the
netlink socket afterwards triggers a slab use-after-free in
dma_unmap_sg_attrs():

  BUG: KASAN: slab-use-after-free in dma_unmap_sg_attrs+0x76/0x2b0
  Read of size 8 at addr ffff88810fae64b0 by task kworker/3:1/53
  Workqueue: events __net_devmem_dmabuf_binding_free
  Call Trace:
   dma_unmap_sg_attrs+0x76/0x2b0
   unmap_udmabuf+0x8e/0x100
   dma_buf_unmap_attachment+0x145/0x330
   dma_buf_unmap_attachment_unlocked+0x96/0x130
   __net_devmem_dmabuf_binding_free+0x156/0x2e0
  Freed by task 9:
   kfree+0x1b8/0x550
   nsim_bus_dev_release+0xe/0x60
   device_release+0xcf/0x250

Add net_devmem_dmabuf_binding_unmap() serialized under binding->lock
and null out binding->sgt and binding->attachment after unmapping so
unmapping is idempotent across queue uninstall and deferred binding
destruction. Call it synchronously when the last bound RX queue is
uninstalled in mp_dmabuf_devmem_uninstall(), except when a leased
queue is unleased: the physical device is still registered and, if
restarting its queue failed, may still be using the binding.

The same use-after-free happens when the netlink socket is closed
before the device is removed but RX tokens or the pending release work
keep the binding alive. Such bindings are no longer installed on any
queue and were already erased from net_devmem_dmabuf_bindings. Keep
bindings in net_devmem_dmabuf_bindings until they are freed, hide
unbound ones from TX lookups with an xarray mark, and on
NETDEV_UNREGISTER clear binding->dev of unbound RX bindings for the
device and unmap them, unless the device is a virtual device leasing
queues, for the same reason as above. The walk runs under the netdev
instance lock, so it cannot clear binding->dev while
net_devmem_unbind_dmabuf() is still closing queues, and under
net_devmem_lock, which serializes it with the final free. It is skipped
when the device only moves to another netns.

Fixes: f8350a4358fc ("net: page_pool: add a mp hook to unregister_netdevice*")
Fixes: 0afc44d8cdf6 ("net: devmem: fix kernel panic when netlink socket close after module unload")
Cc: Taehee Yoo <ap420073@gmail.com>
Cc: Stanislav Fomichev <sdf@fomichev.me>
Cc: Tariq Toukan <tariqt@nvidia.com>
Cc: Kaifeng Wang <kaifengw@google.com>
Assisted-by: LLM
Signed-off-by: Mina Almasry <almasrymina@google.com>
Reviewed-by: Dragos Tatulea <dtatulea@nvidia.com>
---
v3:
- Run the NETDEV_UNREGISTER walk under the netdev instance lock so it
  cannot race with net_devmem_unbind_dmabuf() (Sashiko).
- Skip the walk when the device only moves to another netns (Sashiko).

v2:
- Don't unmap when a leased queue is unleased (Sashiko).
- Unmap unbound RX bindings on NETDEV_UNREGISTER; keep bindings in
  net_devmem_dmabuf_bindings until freed, hidden from TX lookups by
  NET_DEVMEM_BINDING_LIVE (Sashiko).
- Update the binding->lock and binding->ref comments (Sashiko).
---
 net/core/devmem.c      | 92 +++++++++++++++++++++++++++++++++++-------
 net/core/devmem.h      | 10 ++++-
 net/core/netdev-genl.c |  1 +
 3 files changed, 86 insertions(+), 17 deletions(-)

diff --git a/net/core/devmem.c b/net/core/devmem.c
index f4d60654ce7fd..93cc2815d935a 100644
--- a/net/core/devmem.c
+++ b/net/core/devmem.c
@@ -20,6 +20,7 @@
 #include <net/tcp.h>
 #include <trace/events/page_pool.h>
 
+#include "dev.h"
 #include "devmem.h"
 #include "mp_dmabuf_devmem.h"
 #include "page_pool_priv.h"
@@ -27,6 +28,8 @@
 /* Device memory support */
 
 static DEFINE_XARRAY_FLAGS(net_devmem_dmabuf_bindings, XA_FLAGS_ALLOC1);
+#define NET_DEVMEM_BINDING_LIVE XA_MARK_1
+static DEFINE_MUTEX(net_devmem_lock);
 
 static const struct memory_provider_ops dmabuf_devmem_ops;
 
@@ -58,6 +61,21 @@ static void net_devmem_dmabuf_binding_release(struct percpu_ref *ref)
 	schedule_work(&binding->unbind_w);
 }
 
+static void
+net_devmem_dmabuf_binding_unmap(struct net_devmem_dmabuf_binding *binding)
+{
+	lockdep_assert_held(&binding->lock);
+
+	if (!binding->sgt)
+		return;
+
+	dma_buf_unmap_attachment_unlocked(binding->attachment, binding->sgt,
+					  binding->direction);
+	dma_buf_detach(binding->dmabuf, binding->attachment);
+	binding->sgt = NULL;
+	binding->attachment = NULL;
+}
+
 void __net_devmem_dmabuf_binding_free(struct work_struct *wq)
 {
 	struct net_devmem_dmabuf_binding *binding = container_of(wq, typeof(*binding), unbind_w);
@@ -74,9 +92,13 @@ void __net_devmem_dmabuf_binding_free(struct work_struct *wq)
 		  size, avail))
 		gen_pool_destroy(binding->chunk_pool);
 
-	dma_buf_unmap_attachment_unlocked(binding->attachment, binding->sgt,
-					  binding->direction);
-	dma_buf_detach(binding->dmabuf, binding->attachment);
+	mutex_lock(&net_devmem_lock);
+	mutex_lock(&binding->lock);
+	net_devmem_dmabuf_binding_unmap(binding);
+	mutex_unlock(&binding->lock);
+	xa_erase(&net_devmem_dmabuf_bindings, binding->id);
+	mutex_unlock(&net_devmem_lock);
+
 	dma_buf_put(binding->dmabuf);
 	xa_destroy(&binding->bound_rxqs);
 	percpu_ref_exit(&binding->ref);
@@ -129,10 +151,11 @@ void net_devmem_unbind_dmabuf(struct net_devmem_dmabuf_binding *binding)
 	unsigned long xa_idx;
 	unsigned int rxq_idx;
 
-	xa_erase(&net_devmem_dmabuf_bindings, binding->id);
+	xa_clear_mark(&net_devmem_dmabuf_bindings, binding->id,
+		      NET_DEVMEM_BINDING_LIVE);
 
 	/* Ensure no tx net_devmem_lookup_dmabuf() are in flight after the
-	 * erase.
+	 * mark is cleared.
 	 */
 	synchronize_net();
 
@@ -153,6 +176,38 @@ void net_devmem_unbind_dmabuf(struct net_devmem_dmabuf_binding *binding)
 	percpu_ref_kill(&binding->ref);
 }
 
+static void
+net_devmem_dev_unregister_rx(struct net_devmem_dmabuf_binding *binding,
+			     unsigned long xa_idx, struct net_device *dev)
+{
+	if (xa_get_mark(&net_devmem_dmabuf_bindings, xa_idx,
+			NET_DEVMEM_BINDING_LIVE) ||
+	    READ_ONCE(binding->dev) != dev)
+		return;
+
+	mutex_lock(&binding->lock);
+	WRITE_ONCE(binding->dev, NULL);
+	if (!netif_is_queue_leasee(dev))
+		net_devmem_dmabuf_binding_unmap(binding);
+	mutex_unlock(&binding->lock);
+}
+
+void net_devmem_dev_unregister(struct net_device *dev)
+{
+	struct net_devmem_dmabuf_binding *binding;
+	unsigned long xa_idx;
+
+	if (dev->reg_state != NETREG_UNREGISTERING)
+		return;
+
+	mutex_lock(&net_devmem_lock);
+	xa_for_each(&net_devmem_dmabuf_bindings, xa_idx, binding) {
+		if (binding->direction == DMA_FROM_DEVICE)
+			net_devmem_dev_unregister_rx(binding, xa_idx, dev);
+	}
+	mutex_unlock(&net_devmem_lock);
+}
+
 int net_devmem_bind_dmabuf_to_queue(struct net_device *dev, u32 rxq_idx,
 				    struct net_devmem_dmabuf_binding *binding,
 				    struct netlink_ext_ack *extack)
@@ -331,6 +386,8 @@ net_devmem_bind_dmabuf(struct net_device *dev, void *vdev,
 			      GFP_KERNEL);
 	if (err < 0)
 		goto err_free_chunks;
+	xa_set_mark(&net_devmem_dmabuf_bindings, binding->id,
+		    NET_DEVMEM_BINDING_LIVE);
 
 	list_add(&binding->list, &priv->bindings);
 
@@ -359,9 +416,11 @@ net_devmem_bind_dmabuf(struct net_device *dev, void *vdev,
 struct net_devmem_dmabuf_binding *net_devmem_lookup_dmabuf(u32 id)
 {
 	struct net_devmem_dmabuf_binding *binding;
+	unsigned long xa_idx = id;
 
 	rcu_read_lock();
-	binding = xa_load(&net_devmem_dmabuf_bindings, id);
+	binding = xa_find(&net_devmem_dmabuf_bindings, &xa_idx, id,
+			  NET_DEVMEM_BINDING_LIVE);
 	if (binding) {
 		if (!net_devmem_dmabuf_binding_get(binding))
 			binding = NULL;
@@ -529,16 +588,19 @@ static void mp_dmabuf_devmem_uninstall(void *mp_priv,
 	unsigned long xa_idx;
 
 	xa_for_each(&binding->bound_rxqs, xa_idx, bound_rxq) {
-		if (bound_rxq == rxq) {
-			xa_erase(&binding->bound_rxqs, xa_idx);
-			if (xa_empty(&binding->bound_rxqs)) {
-				mutex_lock(&binding->lock);
-				ASSERT_EXCLUSIVE_WRITER(binding->dev);
-				WRITE_ONCE(binding->dev, NULL);
-				mutex_unlock(&binding->lock);
-			}
-			break;
+		if (bound_rxq != rxq)
+			continue;
+
+		xa_erase(&binding->bound_rxqs, xa_idx);
+		if (xa_empty(&binding->bound_rxqs)) {
+			mutex_lock(&binding->lock);
+			ASSERT_EXCLUSIVE_WRITER(binding->dev);
+			WRITE_ONCE(binding->dev, NULL);
+			if (!rxq->lease)
+				net_devmem_dmabuf_binding_unmap(binding);
+			mutex_unlock(&binding->lock);
 		}
+		break;
 	}
 }
 
diff --git a/net/core/devmem.h b/net/core/devmem.h
index 4a293a7d1149c..5c47c8c3c7d2d 100644
--- a/net/core/devmem.h
+++ b/net/core/devmem.h
@@ -27,7 +27,7 @@ struct net_devmem_dmabuf_binding {
 	 */
 	void *vdev;
 	struct gen_pool *chunk_pool;
-	/* Protect dev */
+	/* Protect dev, attachment and sgt */
 	struct mutex lock;
 
 	/* The user holds a ref (via the netlink API) for as long as they want
@@ -38,7 +38,8 @@ struct net_devmem_dmabuf_binding {
 	 *
 	 * The binding undos itself and unmaps the underlying dmabuf once all
 	 * those refs are dropped and the binding is no longer desired or in
-	 * use.
+	 * use. The dmabuf may be unmapped earlier, when the device it is
+	 * mapped for is unregistered.
 	 *
 	 * net_devmem_get_net_iov() on dmabuf net_iovs will increment this
 	 * reference, making sure that the binding remains alive until all the
@@ -100,6 +101,7 @@ net_devmem_bind_dmabuf(struct net_device *dev, void *vdev,
 		       struct netlink_ext_ack *extack);
 struct net_devmem_dmabuf_binding *net_devmem_lookup_dmabuf(u32 id);
 void net_devmem_unbind_dmabuf(struct net_devmem_dmabuf_binding *binding);
+void net_devmem_dev_unregister(struct net_device *dev);
 int net_devmem_bind_dmabuf_to_queue(struct net_device *dev, u32 rxq_idx,
 				    struct net_devmem_dmabuf_binding *binding,
 				    struct netlink_ext_ack *extack);
@@ -196,6 +198,10 @@ net_devmem_unbind_dmabuf(struct net_devmem_dmabuf_binding *binding)
 {
 }
 
+static inline void net_devmem_dev_unregister(struct net_device *dev)
+{
+}
+
 static inline int
 net_devmem_bind_dmabuf_to_queue(struct net_device *dev, u32 rxq_idx,
 				struct net_devmem_dmabuf_binding *binding,
diff --git a/net/core/netdev-genl.c b/net/core/netdev-genl.c
index 33b9f4eb9565a..b04e22937b789 100644
--- a/net/core/netdev-genl.c
+++ b/net/core/netdev-genl.c
@@ -1488,6 +1488,7 @@ static int netdev_genl_netdevice_event(struct notifier_block *nb,
 		break;
 	case NETDEV_UNREGISTER:
 		netdev_lock(netdev);
+		net_devmem_dev_unregister(netdev);
 		netdev_genl_dev_notify(netdev, NETDEV_CMD_DEV_DEL_NTF);
 		netdev_unlock(netdev);
 		break;
-- 
2.56.0.176.g3575e65682-goog


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH net v3 2/4] net: devmem: detach TX bindings on NETDEV_UNREGISTER
  2026-10-14 16:17 [PATCH net v3 0/4] net: devmem: fix RX and TX binding teardown on device unregistration Mina Almasry
  2026-10-14 16:17 ` [PATCH net v3 1/4] net: devmem: unmap dma_buf synchronously on queue uninstall Mina Almasry
@ 2026-10-14 16:17 ` Mina Almasry
  2026-10-15 19:21   ` netdev-bot+sashiko
  2026-10-14 16:17 ` [PATCH net v3 3/4] netdevsim: support devmem RX and TX bindings Mina Almasry
  2026-10-14 16:17 ` [PATCH net v3 4/4] selftests: net: add devmem RX and TX netdev unregister tests Mina Almasry
  3 siblings, 1 reply; 8+ messages in thread
From: Mina Almasry @ 2026-10-14 16:17 UTC (permalink / raw)
  To: Jakub Kicinski, Mina Almasry, Pavel Begunkov, David Wei,
	Stanislav Fomichev, Taehee Yoo, Paolo Abeni, Kaiyuan Zhang,
	Bobby Eshleman, netdev, linux-kernel, linux-kselftest,
	linux-media, dri-devel
  Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Simon Horman,
	Shuah Khan, Sumit Semwal, Christian König, Daniel Borkmann,
	Nikolay Aleksandrov, Tariq Toukan, Kaifeng Wang, Dragos Tatulea

TX devmem bindings (NETDEV_CMD_BIND_TX) have no bound RX queues, so
mp_dmabuf_devmem_uninstall() never runs when binding->dev or
binding->vdev unregisters. As a result, binding->dev and binding->vdev
dangle pointing to freed struct net_device memory, and the dma_buf
remains attached and mapped after device removal.

Closing the netlink socket after netdev unregistration dereferences the
freed struct net_device in netdev_nl_sock_priv_destroy():

  BUG: unable to handle page fault for address: ffff88824ca9b000
  Oops: Oops: 0000 [#1] SMP KASAN NOPTI
  RIP: 0010:netdev_nl_sock_priv_destroy+0xbd/0x1e0
  Call Trace:
   genl_release+0xf5/0x190
   netlink_release+0xd01/0x18d0
   __sock_release+0xb0/0x270
   sock_close+0x18/0x20

Extend net_devmem_dev_unregister() to TX bindings, including ones
whose netlink socket is already closed but which TCP retransmits keep
alive:
- When binding->dev (the physical DMA-capable device) unregisters,
  clear the binding's NET_DEVMEM_BINDING_LIVE mark and both
  binding->dev and binding->vdev. Then wait for in-flight RCU/TX readers
  with a single synchronize_net() and unmap the detached bindings'
  dma_bufs under binding->lock.
- When only binding->vdev (virtual device) unregisters while
  binding->dev lives, only clear binding->vdev so no new TX sends match
  the virtual device while allowing in-flight TX packets on binding->dev
  to finish DMA safely.

Fixes: bd61848900bf ("net: devmem: Implement TX path")
Fixes: 1abe839b34ae ("net: devmem: support TX over NETMEM_TX_NO_DMA devices")
Cc: Kaiyuan Zhang <kaiyuanz@google.com>
Cc: Stanislav Fomichev <sdf@fomichev.me>
Cc: Bobby Eshleman <bobbyeshleman@meta.com>
Cc: Tariq Toukan <tariqt@nvidia.com>
Cc: Kaifeng Wang <kaifengw@google.com>
Assisted-by: LLM
Signed-off-by: Mina Almasry <almasrymina@google.com>
Reviewed-by: Dragos Tatulea <dtatulea@nvidia.com>
---
v3:
- Detach all matching TX bindings first, then call synchronize_net()
  once before unmapping them, instead of once per binding. Don't wait
  for a grace period when only binding->vdev is cleared, since vdev is
  never dereferenced (Sashiko).
- Don't detach TX bindings when the device only moves to another netns
  (Sashiko).

v2:
- Build on the net_devmem_dev_unregister() walk from the previous patch,
  so unbound TX bindings are also detached (Sashiko). Clear the
  binding's LIVE mark instead of erasing its ID.
- The walk holds net_devmem_lock, which the final free also takes
  before erasing the binding, instead of using RCU and
  percpu_ref_tryget(), so it no longer takes a binding reference
  (Stanislav).
---
 net/core/devmem.c | 51 +++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 51 insertions(+)

diff --git a/net/core/devmem.c b/net/core/devmem.c
index 93cc2815d935a..dd07203076808 100644
--- a/net/core/devmem.c
+++ b/net/core/devmem.c
@@ -192,9 +192,55 @@ net_devmem_dev_unregister_rx(struct net_devmem_dmabuf_binding *binding,
 	mutex_unlock(&binding->lock);
 }
 
+static bool
+net_devmem_dev_unregister_tx(struct net_devmem_dmabuf_binding *binding,
+			     unsigned long xa_idx, struct net_device *dev)
+{
+	if (READ_ONCE(binding->dev) == dev) {
+		xa_clear_mark(&net_devmem_dmabuf_bindings, xa_idx,
+			      NET_DEVMEM_BINDING_LIVE);
+
+		mutex_lock(&binding->lock);
+		WRITE_ONCE(binding->dev, NULL);
+		WRITE_ONCE(binding->vdev, NULL);
+		mutex_unlock(&binding->lock);
+		return true;
+	}
+
+	if (READ_ONCE(binding->vdev) == dev) {
+		mutex_lock(&binding->lock);
+		WRITE_ONCE(binding->vdev, NULL);
+		mutex_unlock(&binding->lock);
+	}
+
+	return false;
+}
+
+static void net_devmem_unmap_detached_tx(void)
+{
+	struct net_devmem_dmabuf_binding *binding;
+	unsigned long xa_idx;
+
+	/* Ensure no tx net_devmem_lookup_dmabuf() or
+	 * validate_xmit_unreadable_skb() are in flight after detach.
+	 */
+	synchronize_net();
+
+	xa_for_each(&net_devmem_dmabuf_bindings, xa_idx, binding) {
+		if (binding->direction != DMA_TO_DEVICE ||
+		    READ_ONCE(binding->dev))
+			continue;
+
+		mutex_lock(&binding->lock);
+		net_devmem_dmabuf_binding_unmap(binding);
+		mutex_unlock(&binding->lock);
+	}
+}
+
 void net_devmem_dev_unregister(struct net_device *dev)
 {
 	struct net_devmem_dmabuf_binding *binding;
+	bool tx_detached = false;
 	unsigned long xa_idx;
 
 	if (dev->reg_state != NETREG_UNREGISTERING)
@@ -204,7 +250,12 @@ void net_devmem_dev_unregister(struct net_device *dev)
 	xa_for_each(&net_devmem_dmabuf_bindings, xa_idx, binding) {
 		if (binding->direction == DMA_FROM_DEVICE)
 			net_devmem_dev_unregister_rx(binding, xa_idx, dev);
+		else if (net_devmem_dev_unregister_tx(binding, xa_idx, dev))
+			tx_detached = true;
 	}
+
+	if (tx_detached)
+		net_devmem_unmap_detached_tx();
 	mutex_unlock(&net_devmem_lock);
 }
 
-- 
2.56.0.176.g3575e65682-goog


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH net v3 3/4] netdevsim: support devmem RX and TX bindings
  2026-10-14 16:17 [PATCH net v3 0/4] net: devmem: fix RX and TX binding teardown on device unregistration Mina Almasry
  2026-10-14 16:17 ` [PATCH net v3 1/4] net: devmem: unmap dma_buf synchronously on queue uninstall Mina Almasry
  2026-10-14 16:17 ` [PATCH net v3 2/4] net: devmem: detach TX bindings on NETDEV_UNREGISTER Mina Almasry
@ 2026-10-14 16:17 ` Mina Almasry
  2026-10-15 19:21   ` netdev-bot+sashiko
  2026-10-14 16:17 ` [PATCH net v3 4/4] selftests: net: add devmem RX and TX netdev unregister tests Mina Almasry
  3 siblings, 1 reply; 8+ messages in thread
From: Mina Almasry @ 2026-10-14 16:17 UTC (permalink / raw)
  To: Jakub Kicinski, Mina Almasry, Pavel Begunkov, David Wei,
	Stanislav Fomichev, Taehee Yoo, Paolo Abeni, Kaiyuan Zhang,
	Bobby Eshleman, netdev, linux-kernel, linux-kselftest,
	linux-media, dri-devel
  Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Simon Horman,
	Shuah Khan, Sumit Semwal, Christian König, Daniel Borkmann,
	Nikolay Aleksandrov, Tariq Toukan, Kaifeng Wang, Dragos Tatulea

Set a 64-bit DMA mask on nsim_bus_dev, enable NETMEM_TX_DMA on the
netdevsim net_device, and configure page_pool instances with
PP_FLAG_ALLOW_UNREADABLE_NETMEM and queue_idx, plus PP_FLAG_DMA_MAP |
PP_FLAG_DMA_SYNC_DEV when CONFIG_HAS_DMA is set, so devmem bind-rx and
bind-tx netlink operations can be tested against netdevsim in software
selftests.

The pp_hold debugfs file allocates pages from the queue 0 page pool, so
refuse it when that pool is backed by a memory provider, and take the
netdev instance lock there so a concurrent bind-rx cannot swap the pool
in between. The software PSP path checksums the payload when the peer
does not decapsulate, so drop unreadable skbs there.

Cc: Tariq Toukan <tariqt@nvidia.com>
Cc: Kaifeng Wang <kaifengw@google.com>
Assisted-by: LLM
Signed-off-by: Mina Almasry <almasrymina@google.com>
Reviewed-by: Dragos Tatulea <dtatulea@nvidia.com>
---
v3:
- Only request page pool DMA mapping when CONFIG_HAS_DMA is set
  (Sashiko).
- Refuse pp_hold on a page pool backed by a memory provider, under the
  netdev instance lock (Sashiko).
- Drop unreadable skbs in the software PSP path when it would have to
  checksum the payload (Sashiko).
---
 drivers/net/netdevsim/bus.c    |  1 +
 drivers/net/netdevsim/netdev.c | 27 +++++++++++++++++++++------
 drivers/net/netdevsim/psp.c    |  5 +++++
 3 files changed, 27 insertions(+), 6 deletions(-)

diff --git a/drivers/net/netdevsim/bus.c b/drivers/net/netdevsim/bus.c
index 5c55c308487b4..f87f5e037b6fb 100644
--- a/drivers/net/netdevsim/bus.c
+++ b/drivers/net/netdevsim/bus.c
@@ -462,6 +462,7 @@ nsim_bus_dev_new(unsigned int id, unsigned int port_count, unsigned int num_queu
 	nsim_bus_dev->port_count = port_count;
 	nsim_bus_dev->num_queues = num_queues;
 	nsim_bus_dev->initial_net = current->nsproxy->net_ns;
+	dma_coerce_mask_and_coherent(&nsim_bus_dev->dev, DMA_BIT_MASK(64));
 	/* Disallow using nsim_bus_dev */
 	smp_store_release(&nsim_bus_dev->init, false);
 
diff --git a/drivers/net/netdevsim/netdev.c b/drivers/net/netdevsim/netdev.c
index b4a99f3ceac60..357ea9e5623b3 100644
--- a/drivers/net/netdevsim/netdev.c
+++ b/drivers/net/netdevsim/netdev.c
@@ -414,19 +414,27 @@ static int nsim_poll(struct napi_struct *napi, int budget)
 	return done;
 }
 
-static int nsim_create_page_pool(struct page_pool **p, struct napi_struct *napi)
+static int nsim_create_page_pool(struct net_device *dev, struct page_pool **p,
+				 struct napi_struct *napi, int idx)
 {
+	struct netdevsim *ns = netdev_priv(dev);
 	struct page_pool_params params = {
 		.order = 0,
 		.pool_size = NSIM_RING_SIZE,
+		.flags = PP_FLAG_ALLOW_UNREADABLE_NETMEM,
+		.max_len = PAGE_SIZE,
 		.nid = NUMA_NO_NODE,
-		.dev = &napi->dev->dev,
+		.dev = &ns->nsim_bus_dev->dev,
 		.napi = napi,
 		.dma_dir = DMA_BIDIRECTIONAL,
-		.netdev = napi->dev,
+		.netdev = dev,
+		.queue_idx = idx,
 	};
 	struct page_pool *pool;
 
+	if (IS_ENABLED(CONFIG_HAS_DMA))
+		params.flags |= PP_FLAG_DMA_MAP | PP_FLAG_DMA_SYNC_DEV;
+
 	pool = page_pool_create(&params);
 	if (IS_ERR(pool))
 		return PTR_ERR(pool);
@@ -450,7 +458,7 @@ static int nsim_init_napi(struct netdevsim *ns)
 	for (i = 0; i < dev->num_rx_queues; i++) {
 		rq = ns->rq[i];
 
-		err = nsim_create_page_pool(&rq->page_pool, &rq->napi);
+		err = nsim_create_page_pool(dev, &rq->page_pool, &rq->napi, i);
 		if (err)
 			goto err_pp_destroy;
 	}
@@ -762,14 +770,16 @@ nsim_queue_mem_alloc(struct net_device *dev,
 	if (ns->rq_reset_mode == 1) {
 		if (!netif_running(ns->netdev))
 			return -ENETDOWN;
-		return nsim_create_page_pool(&qmem->pp, &ns->rq[idx]->napi);
+		return nsim_create_page_pool(dev, &qmem->pp,
+					     &ns->rq[idx]->napi, idx);
 	}
 
 	qmem->rq = nsim_queue_alloc();
 	if (!qmem->rq)
 		return -ENOMEM;
 
-	err = nsim_create_page_pool(&qmem->rq->page_pool, &qmem->rq->napi);
+	err = nsim_create_page_pool(dev, &qmem->rq->page_pool, &qmem->rq->napi,
+				    idx);
 	if (err)
 		goto err_free;
 
@@ -929,12 +939,15 @@ nsim_pp_hold_write(struct file *file, const char __user *data,
 		return ret;
 
 	rtnl_lock();
+	netdev_lock(ns->netdev);
 	ret = count;
 	if (val == !!ns->page)
 		goto exit;
 
 	if (!netif_running(ns->netdev) && val) {
 		ret = -ENETDOWN;
+	} else if (val && page_pool_is_unreadable(ns->rq[0]->page_pool)) {
+		ret = -EOPNOTSUPP;
 	} else if (val) {
 		ns->page = page_pool_dev_alloc_pages(ns->rq[0]->page_pool);
 		if (!ns->page)
@@ -946,6 +959,7 @@ nsim_pp_hold_write(struct file *file, const char __user *data,
 	}
 
 exit:
+	netdev_unlock(ns->netdev);
 	rtnl_unlock();
 	return ret;
 }
@@ -1051,6 +1065,7 @@ static int nsim_init_netdevsim(struct netdevsim *ns)
 	ns->netdev->netdev_ops = &nsim_netdev_ops;
 	ns->netdev->stat_ops = &nsim_stat_ops;
 	ns->netdev->queue_mgmt_ops = &nsim_queue_mgmt_ops;
+	ns->netdev->netmem_tx = NETMEM_TX_DMA;
 	netdev_lockdep_set_classes(ns->netdev);
 
 	err = nsim_udp_tunnels_info_create(ns->nsim_dev, ns->netdev);
diff --git a/drivers/net/netdevsim/psp.c b/drivers/net/netdevsim/psp.c
index 6b3532b5e360d..908cc0d391dc6 100644
--- a/drivers/net/netdevsim/psp.c
+++ b/drivers/net/netdevsim/psp.c
@@ -86,6 +86,11 @@ nsim_do_psp(struct sk_buff *skb, struct netdevsim *ns,
 		__wsum csum;
 		int udplen;
 
+		if (!skb_frags_readable(skb)) {
+			rc = SKB_DROP_REASON_PSP_OUTPUT;
+			goto out_unlock;
+		}
+
 		/* Do not decapsulate. Receive the skb with the udp and psp
 		 * headers still there as if this is a normal udp packet.
 		 * psp_dev_encapsulate() sets udp checksum to 0, so we need to
-- 
2.56.0.176.g3575e65682-goog


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH net v3 4/4] selftests: net: add devmem RX and TX netdev unregister tests
  2026-10-14 16:17 [PATCH net v3 0/4] net: devmem: fix RX and TX binding teardown on device unregistration Mina Almasry
                   ` (2 preceding siblings ...)
  2026-10-14 16:17 ` [PATCH net v3 3/4] netdevsim: support devmem RX and TX bindings Mina Almasry
@ 2026-10-14 16:17 ` Mina Almasry
  3 siblings, 0 replies; 8+ messages in thread
From: Mina Almasry @ 2026-10-14 16:17 UTC (permalink / raw)
  To: Jakub Kicinski, Mina Almasry, Pavel Begunkov, David Wei,
	Stanislav Fomichev, Taehee Yoo, Paolo Abeni, Kaiyuan Zhang,
	Bobby Eshleman, netdev, linux-kernel, linux-kselftest,
	linux-media, dri-devel
  Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Simon Horman,
	Shuah Khan, Sumit Semwal, Christian König, Daniel Borkmann,
	Nikolay Aleksandrov, Tariq Toukan, Kaifeng Wang, Dragos Tatulea

Add devmem_bind_rx_unregister_check and devmem_bind_tx_unregister_check
to nl_netdev.py to verify that unregistering a netdevsim device while a
netlink socket holds an active RX or TX devmem binding synchronously
detaches the dma_buf attachment and cleanly closes the netlink socket
without use-after-free or page faults.

Add the dma-buf, udmabuf and devmem options the tests need to
tools/testing/selftests/net/config.

Cc: Tariq Toukan <tariqt@nvidia.com>
Cc: Kaifeng Wang <kaifengw@google.com>
Assisted-by: LLM
Signed-off-by: Mina Almasry <almasrymina@google.com>
Reviewed-by: Dragos Tatulea <dtatulea@nvidia.com>
---
v3:
- Count attachments of the test's own dma-buf only, and skip instead of
  passing when debugfs bufinfo is not readable (Sashiko).
- Close the netlink socket after removing the device, clean up with
  defer(), and skip on EOPNOTSUPP from bind-rx/bind-tx (Sashiko).
- Replace the hardcoded UDMABUF_CREATE value with a helper that computes
  it for the running architecture (Sashiko, Dragos), and close the memfd
  and /dev/udmabuf fds on errors (Sashiko).
- Add the needed options to tools/testing/selftests/net/config (Sashiko).
---
 tools/testing/selftests/net/config       |   4 +
 tools/testing/selftests/net/nl_netdev.py | 125 ++++++++++++++++++++++-
 2 files changed, 126 insertions(+), 3 deletions(-)

diff --git a/tools/testing/selftests/net/config b/tools/testing/selftests/net/config
index 737e7e6327b37..1e21f0055a2c0 100644
--- a/tools/testing/selftests/net/config
+++ b/tools/testing/selftests/net/config
@@ -11,6 +11,7 @@ CONFIG_CRYPTO_ARIA=y
 CONFIG_CRYPTO_CHACHA20POLY1305=m
 CONFIG_CRYPTO_SHA1=y
 CONFIG_CRYPTO_SM4_GENERIC=y
+CONFIG_DEBUG_FS=y
 CONFIG_DEBUG_INFO_BTF=y
 CONFIG_DEBUG_INFO_BTF_MODULES=n
 CONFIG_DUMMY=y
@@ -76,6 +77,7 @@ CONFIG_NET_CLS_BPF=m
 CONFIG_NET_CLS_FLOWER=m
 CONFIG_NET_CLS_MATCHALL=m
 CONFIG_NET_CLS_U32=m
+CONFIG_NET_DEVMEM=y
 CONFIG_NETDEVSIM=m
 CONFIG_NET_DROP_MONITOR=m
 CONFIG_NETFILTER=y
@@ -120,6 +122,7 @@ CONFIG_OPENVSWITCH=m
 CONFIG_PAGE_POOL_STATS=y
 CONFIG_PSAMPLE=m
 CONFIG_RPS=y
+CONFIG_SYNC_FILE=y
 CONFIG_SYN_COOKIES=y
 CONFIG_SYSCTL=y
 CONFIG_SYSFS=y
@@ -131,6 +134,7 @@ CONFIG_TEST_BPF=m
 CONFIG_TLS=m
 CONFIG_TRACEPOINTS=y
 CONFIG_TUN=y
+CONFIG_UDMABUF=y
 CONFIG_USER_NS=y
 CONFIG_VETH=y
 CONFIG_VLAN_8021Q=y
diff --git a/tools/testing/selftests/net/nl_netdev.py b/tools/testing/selftests/net/nl_netdev.py
index ceb44c8e1fec5..988878b71358c 100755
--- a/tools/testing/selftests/net/nl_netdev.py
+++ b/tools/testing/selftests/net/nl_netdev.py
@@ -6,10 +6,14 @@ Tests for the netdev netlink family.
 """
 
 import errno
+import fcntl
+import mmap
+import os
+import struct
 from os import system
-from lib.py import ksft_run, ksft_exit
+from lib.py import ksft_run, ksft_exit, KsftFailEx, KsftSkipEx
 from lib.py import ksft_eq, ksft_ge, ksft_ne, ksft_raises, ksft_busy_wait
-from lib.py import NetdevFamily, NetdevSimDev, NlError, defer, ip
+from lib.py import EthtoolFamily, NetdevFamily, NetdevSimDev, NlError, defer, ip
 
 
 def empty_check(nf) -> None:
@@ -366,6 +370,119 @@ def page_pool_stats_ifindex_check(nf) -> None:
     ksft_eq(cm.exception.nl_msg.extack['bad-attr'], '.info.id')
 
 
+def _udmabuf_create_ioctl() -> int:
+    """Return UDMABUF_CREATE, _IOW('u', 0x42, struct udmabuf_create)."""
+    machine = os.uname().machine
+    if machine.startswith(("alpha", "mips", "ppc", "powerpc", "sparc")):
+        ioc_write = 4 << 29
+    elif machine.startswith("parisc"):
+        ioc_write = 2 << 30
+    else:
+        ioc_write = 1 << 30
+    return ioc_write | 24 << 16 | ord("u") << 8 | 0x42
+
+
+def _create_udmabuf(num_pages=64) -> int:
+    """Create a sealed memfd-backed udmabuf fd for devmem tests."""
+    if not os.path.exists("/dev/udmabuf"):
+        raise KsftSkipEx("/dev/udmabuf is not available")
+
+    size = num_pages * mmap.PAGESIZE
+    memfd = os.memfd_create("devmem-ksft", os.MFD_ALLOW_SEALING)
+    try:
+        os.ftruncate(memfd, size)
+        fcntl.fcntl(memfd, fcntl.F_ADD_SEALS, fcntl.F_SEAL_SHRINK)
+        devfd = os.open("/dev/udmabuf", os.O_RDWR)
+        try:
+            req = bytearray(struct.pack("IIQQ", memfd, 0, 0, size))
+            return fcntl.ioctl(devfd, _udmabuf_create_ioctl(), req)
+        finally:
+            os.close(devfd)
+    finally:
+        os.close(memfd)
+
+
+def _dmabuf_attach_count(dmabuf_fd) -> int:
+    """Return the number of devices attached to dmabuf_fd, from debugfs."""
+    path = "/sys/kernel/debug/dma_buf/bufinfo"
+    try:
+        with open(path, "r", encoding="utf-8") as f:
+            lines = f.read().splitlines()
+    except OSError as e:
+        raise KsftSkipEx(f"{path} is not readable: {e}") from e
+
+    ino = os.fstat(dmabuf_fd).st_ino
+    found = False
+    for line in lines:
+        fields = line.split("\t")
+        if not found:
+            found = (len(fields) == 7 and fields[5].isdigit() and
+                     int(fields[5]) == ino)
+        elif line.startswith("Total "):
+            return int(line.split()[1])
+    raise KsftFailEx(f"dma-buf {ino} not found in {path}")
+
+
+def _devmem_bind(bind, attrs) -> None:
+    """Run a devmem bind op, skip the test if the device can't do devmem."""
+    try:
+        bind(attrs)
+    except NlError as e:
+        if e.error == errno.EOPNOTSUPP:
+            raise KsftSkipEx(f"devmem binding not supported: {e}") from e
+        raise
+
+
+def devmem_bind_rx_unregister_check(_nf) -> None:
+    """Verify RX devmem bindings detach dma_buf synchronously on netdev unregister."""
+    dmabuf_fd = _create_udmabuf()
+    defer(os.close, dmabuf_fd)
+    nf_priv = NetdevFamily()
+    defer(nf_priv.close)
+    nsimdev = NetdevSimDev(queue_count=2)
+    defer(nsimdev.remove)
+    nsim = nsimdev.nsims[0]
+
+    ip(f"link set dev {nsim.ifname} up")
+    with EthtoolFamily() as ef:
+        ef.rings_set({"header": {"dev-index": nsim.ifindex},
+                      "tcp-data-split": "enabled"})
+    _devmem_bind(nf_priv.bind_rx, {
+        "ifindex": nsim.ifindex,
+        "fd": dmabuf_fd,
+        "queues": [{"id": 1, "type": "rx"}],
+    })
+    ksft_ge(_dmabuf_attach_count(dmabuf_fd), 1)
+
+    nsimdev.remove()
+    ksft_eq(_dmabuf_attach_count(dmabuf_fd), 0)
+
+    nf_priv.close()
+
+
+def devmem_bind_tx_unregister_check(_nf) -> None:
+    """Verify TX devmem bindings detach cleanly on netdev unregister without UAF."""
+    dmabuf_fd = _create_udmabuf()
+    defer(os.close, dmabuf_fd)
+    nf_priv = NetdevFamily()
+    defer(nf_priv.close)
+    nsimdev = NetdevSimDev(queue_count=2)
+    defer(nsimdev.remove)
+    nsim = nsimdev.nsims[0]
+
+    ip(f"link set dev {nsim.ifname} up")
+    _devmem_bind(nf_priv.bind_tx, {
+        "ifindex": nsim.ifindex,
+        "fd": dmabuf_fd,
+    })
+    ksft_ge(_dmabuf_attach_count(dmabuf_fd), 1)
+
+    nsimdev.remove()
+    ksft_eq(_dmabuf_attach_count(dmabuf_fd), 0)
+
+    nf_priv.close()
+
+
 def main() -> None:
     """ Ksft boiler plate main """
     nf = NetdevFamily()
@@ -379,7 +496,9 @@ def main() -> None:
               page_pool_check,
               page_pool_dump_ifindex,
               page_pool_ifindex_leak_check,
-              page_pool_stats_ifindex_check
+              page_pool_stats_ifindex_check,
+              devmem_bind_rx_unregister_check,
+              devmem_bind_tx_unregister_check
               ],
              args=(nf, ))
     ksft_exit()
-- 
2.56.0.176.g3575e65682-goog


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* Re: [PATCH net v3 1/4] net: devmem: unmap dma_buf synchronously on queue uninstall
  2026-10-14 16:17 ` [PATCH net v3 1/4] net: devmem: unmap dma_buf synchronously on queue uninstall Mina Almasry
@ 2026-10-15 19:21   ` netdev-bot+sashiko
  0 siblings, 0 replies; 8+ messages in thread
From: netdev-bot+sashiko @ 2026-10-15 19:21 UTC (permalink / raw)
  To: almasrymina
  Cc: kuba, asml.silence, dw, sdf, ap420073, pabeni, kaiyuanz,
	bobbyeshleman, netdev, linux-kernel, linux-kselftest, linux-media,
	dri-devel, andrew+netdev, davem, edumazet, horms, shuah,
	sumit.semwal, christian.koenig, daniel, razor, tariqt, kaifengw,
	dtatulea

Thank you for your contribution! Sashiko AI review found 1 potential
issue(s) to consider.

Critical: 0 · High: 1 · Medium: 0 · Low: 0 (1)

If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).

- [High] RX bindings made through a leased queue (a netkit virtual device
  leasing a physical NIC's rxq) still hit the dma_unmap_sg_attrs() use-…

Pre-existing issues:
- [Low] This patch handles only DMA_FROM_DEVICE bindings when a device is
  unregistered.

--

net: devmem: unmap dma_buf synchronously on queue uninstall

This commit unmaps the devmem dma_buf attachment when the last bound RX
queue is uninstalled. Previously the unmap waited for the deferred binding
free, which could run after dma_dev had already been freed. It also keeps
bindings in net_devmem_dmabuf_bindings until they are freed, hides unbound
ones from TX lookups with an xarray mark, and unmaps unbound RX bindings
on NETDEV_UNREGISTER, except for leased queues.

> diff --git a/net/core/devmem.c b/net/core/devmem.c
> index f4d60654ce7fd..93cc2815d935a 100644
> --- a/net/core/devmem.c
> +++ b/net/core/devmem.c

[ ... ]

> @@ -153,6 +176,38 @@ void net_devmem_unbind_dmabuf(struct net_devmem_dmabuf_binding *binding)

[ ... ]

> +void net_devmem_dev_unregister(struct net_device *dev)
> +{
> +	struct net_devmem_dmabuf_binding *binding;
> +	unsigned long xa_idx;
> +
> +	if (dev->reg_state != NETREG_UNREGISTERING)
> +		return;
> +
> +	mutex_lock(&net_devmem_lock);
> +	xa_for_each(&net_devmem_dmabuf_bindings, xa_idx, binding) {
> +		if (binding->direction == DMA_FROM_DEVICE)
> +			net_devmem_dev_unregister_rx(binding, xa_idx, dev);

[Severity: Low]
This isn't a bug introduced by this patch, but TX bindings are skipped
here. They are never installed on an rxq either, so
mp_dmabuf_devmem_uninstall() never runs for them.

After the NIC is unregistered, a TX binding created through bind-tx still
has three things: its dma_buf mapping against the NIC's dma_dev, the
NET_DEVMEM_BINDING_LIVE mark, and stale binding->dev and binding->vdev
pointers.

Can a later netdev_nl_sock_priv_destroy() then call netdev_hold() and
netdev_lock() on the freed net_device? Could
__net_devmem_dmabuf_binding_free() also unmap against the freed dma_dev?
net_devmem_get_binding() would compare against the stale dev and vdev
pointers too.

The next patch in the series, "net: devmem: detach TX bindings on
NETDEV_UNREGISTER", appears to fix this. It adds
net_devmem_dev_unregister_tx(), which clears the LIVE mark and dev/vdev.
It also adds net_devmem_unmap_detached_tx(), which unmaps after
synchronize_net().

> +	}
> +	mutex_unlock(&net_devmem_lock);
> +}

[ ... ]

> @@ -529,16 +588,19 @@ static void mp_dmabuf_devmem_uninstall(void *mp_priv,
>  	unsigned long xa_idx;
>  
>  	xa_for_each(&binding->bound_rxqs, xa_idx, bound_rxq) {
> -		if (bound_rxq == rxq) {
> -			xa_erase(&binding->bound_rxqs, xa_idx);
> -			if (xa_empty(&binding->bound_rxqs)) {
> -				mutex_lock(&binding->lock);
> -				ASSERT_EXCLUSIVE_WRITER(binding->dev);
> -				WRITE_ONCE(binding->dev, NULL);
> -				mutex_unlock(&binding->lock);
> -			}
> -			break;
> +		if (bound_rxq != rxq)
> +			continue;
> +
> +		xa_erase(&binding->bound_rxqs, xa_idx);
> +		if (xa_empty(&binding->bound_rxqs)) {
> +			mutex_lock(&binding->lock);
> +			ASSERT_EXCLUSIVE_WRITER(binding->dev);
> +			WRITE_ONCE(binding->dev, NULL);
> +			if (!rxq->lease)
> +				net_devmem_dmabuf_binding_unmap(binding);

[Severity: High]
Can RX bindings on a leased queue still hit the dma_unmap_sg_attrs()
use-after-free described in the commit message?

Take a bind-rx on a netkit rxq leased from a physical NIC.
netdev_nl_bind_rx_doit() gets dma_dev from netdev_queue_get_dma_dev(),
which returns the physical NIC's DMA device. But binding->dev is the
netkit device, and bound_rxqs holds the netkit rxq.

On unlease, the lease pointers are cleared only after cleanup has run:

netdev_rx_queue_unlease()
    netif_rxq_cleanup_unlease(rxq_src, rxq_dst)
        __netif_mp_uninstall_rxq(virt_rxq, &p)
            mp_dmabuf_devmem_uninstall()
                WRITE_ONCE(binding->dev, NULL);
                if (!rxq->lease)   <-- lease still set, unmap skipped
    WRITE_ONCE(rxq_src->lease, NULL);
    WRITE_ONCE(rxq_dst->lease, NULL);
    netdev_put(rxq_src->dev, &rxq_src->lease_tracker);

net_devmem_dev_unregister_rx() does the same for unbound bindings,
because netif_is_queue_leasee() is true for netkit:

	WRITE_ONCE(binding->dev, NULL);
	if (!netif_is_queue_leasee(dev))
		net_devmem_dmabuf_binding_unmap(binding);

Once binding->dev is NULL, it seems nothing can unmap the binding before
the final free. When the physical NIC unregisters later,
dev_memory_provider_uninstall() passes the physical rxq, which is not in
bound_rxqs. net_devmem_dev_unregister() matches only on binding->dev,
which is never the physical device.

For example: with the netlink socket open, delete the netkit device,
remove the physical NIC, and then close the socket:

net_devmem_unbind_dmabuf()
  percpu_ref_kill()
    __net_devmem_dmabuf_binding_free()
      net_devmem_dmabuf_binding_unmap()     <-- sgt still non-NULL
        dma_buf_unmap_attachment_unlocked() <-- freed physical dma_dev

The commit message gives this reason for skipping the unmap:

  "the physical device is still registered"

Is that still accurate when the physical NIC is unregistered first? In
that case netkit_check_lease_unregister() unregisters the netkit device
from inside the physical NIC's own NETDEV_UNREGISTER. The unlease skip
then happens while the physical device is already NETREG_UNREGISTERING.

Closing the socket first while RX tokens keep the binding alive seems to
end the same way. The netkit unregister clears binding->dev through
net_devmem_dev_unregister_rx() without unmapping. Once the physical NIC
is removed, the last token release unmaps against the freed dma_dev.

For leased queues, would the binding need to track the physical device,
or be unmapped against it?

> +			mutex_unlock(&binding->lock);
>  		}
> +		break;
>  	}
>  }

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261014161754.3782346-1-almasrymina%40google.com

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH net v3 2/4] net: devmem: detach TX bindings on NETDEV_UNREGISTER
  2026-10-14 16:17 ` [PATCH net v3 2/4] net: devmem: detach TX bindings on NETDEV_UNREGISTER Mina Almasry
@ 2026-10-15 19:21   ` netdev-bot+sashiko
  0 siblings, 0 replies; 8+ messages in thread
From: netdev-bot+sashiko @ 2026-10-15 19:21 UTC (permalink / raw)
  To: almasrymina
  Cc: kuba, asml.silence, dw, sdf, ap420073, pabeni, kaiyuanz,
	bobbyeshleman, netdev, linux-kernel, linux-kselftest, linux-media,
	dri-devel, andrew+netdev, davem, edumazet, horms, shuah,
	sumit.semwal, christian.koenig, daniel, razor, tariqt, kaifengw,
	dtatulea

Thank you for your contribution! Sashiko AI review found 1 potential
issue(s) to consider.

Critical: 0 · High: 1 · Medium: 0 · Low: 0

If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).

- [High] The TX-binding dangling `binding->dev` use-after-free can still
  happen through a race in the leased-queue (netkit) bind-tx path.

--

net: devmem: detach TX bindings on NETDEV_UNREGISTER

This extends net_devmem_dev_unregister() to cover TX devmem bindings.
When binding->dev unregisters, the patch clears the binding's LIVE mark
and both dev and vdev, then unmaps the dma_buf after synchronize_net().
When only binding->vdev unregisters, it clears just binding->vdev.

> diff --git a/net/core/devmem.c b/net/core/devmem.c
> index 93cc2815d935a..dd07203076808 100644
> --- a/net/core/devmem.c
> +++ b/net/core/devmem.c
> @@ -192,9 +192,55 @@ net_devmem_dev_unregister_rx(struct net_devmem_dmabuf_binding *binding,
>  	mutex_unlock(&binding->lock);
>  }
>  
> +static bool
> +net_devmem_dev_unregister_tx(struct net_devmem_dmabuf_binding *binding,
> +			     unsigned long xa_idx, struct net_device *dev)
> +{
> +	if (READ_ONCE(binding->dev) == dev) {

[ ... ]

> +		return true;
> +	}
> +
> +	if (READ_ONCE(binding->vdev) == dev) {
> +		mutex_lock(&binding->lock);
> +		WRITE_ONCE(binding->vdev, NULL);
> +		mutex_unlock(&binding->lock);
> +	}

[Severity: High]
Can a TX binding still end up with a dangling binding->dev through the
leased queue (netkit) bind-tx path?

In netdev_nl_bind_tx_doit(), when the target is a NETMEM_TX_NO_DMA
leasee, netdev_find_netmem_tx_dev() finds the physical device. It reads
the lease without taking the physical device's lock. It also only checks
netif_device_present(), and the unregister path never clears that:

net/core/netdev-genl.c:netdev_find_netmem_tx_dev() {
    ...
		phys_dev = lease_rxq->dev;
		if (netif_device_present(phys_dev) &&
		    phys_dev->netmem_tx == NETMEM_TX_DMA)
			return phys_dev;
    ...
}

The bind then goes ahead without rechecking bind_dev->reg_state:

net/core/netdev-genl.c:netdev_nl_bind_tx_doit() {
    ...
	if (bind_dev != netdev)
		netdev_lock(bind_dev);

	dma_dev = netdev_queue_get_dma_dev(bind_dev, 0, NETDEV_QUEUE_TYPE_TX);

	binding = net_devmem_bind_dmabuf(bind_dev,
    ...
}

On the direct path, netdev_get_by_index_lock()->netdev_put_lock() rejects
reg_state > NETREG_REGISTERED. Nothing does the same check for bind_dev
here.

The sequence seems to be:

1. The physical device P unregisters.
   unregister_netdevice_many_notify() sets P->reg_state to
   NETREG_UNREGISTERING and calls the NETDEV_UNREGISTER notifiers.

2. The netdev-genl notifier runs before netkit's, because it was
   registered earlier with equal priority. It calls
   net_devmem_dev_unregister(P) under netdev_lock(P).

3. A concurrent unprivileged bind-tx on netkit already holds netkit's
   lock and is blocked in netdev_lock(P). It now continues. The lease is
   still in place, so it creates a binding with dev = P and
   vdev = netkit, maps the dmabuf, and marks the binding LIVE.

4. netkit_check_lease_unregister(P) unregisters netkit. In
   net_devmem_dev_unregister(netkit)->net_devmem_dev_unregister_tx(),
   binding->dev is P, so only the branch above runs and only
   binding->vdev is cleared.

5. netdev_run_todo() sets P->reg_state to NETREG_UNREGISTERED. Any
   rebroadcast NETDEV_UNREGISTER then returns early at:

	if (dev->reg_state != NETREG_UNREGISTERING)
		return;

   net_devmem_bind_dmabuf() sets binding->dev = dev without taking a
   netdev_hold(), so P can be freed.

6. When the socket closes, netdev_nl_sock_priv_destroy() reads the stale
   binding->dev and calls netdev_hold() and netdev_lock() on it. This is
   the same oops quoted in the commit message. The dma_buf also stays
   attached to the removed device until the final free.

NETDEV_CMD_BIND_TX is unprivileged. Hitting this needs a race with the
physical device's unregistration. That is still the path the commit
message and the 1abe839b34ae Fixes: tag say this patch fixes. Later
patches in the series don't appear to touch net/ or drivers/net/netkit.c
to address it.

Would it make sense to recheck bind_dev->reg_state after
netdev_lock(bind_dev) in netdev_nl_bind_tx_doit()? Extending the
unregister walk doesn't look like it can close this window on its own.

[ ... ]

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261014161754.3782346-1-almasrymina%40google.com

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH net v3 3/4] netdevsim: support devmem RX and TX bindings
  2026-10-14 16:17 ` [PATCH net v3 3/4] netdevsim: support devmem RX and TX bindings Mina Almasry
@ 2026-10-15 19:21   ` netdev-bot+sashiko
  0 siblings, 0 replies; 8+ messages in thread
From: netdev-bot+sashiko @ 2026-10-15 19:21 UTC (permalink / raw)
  To: almasrymina
  Cc: kuba, asml.silence, dw, sdf, ap420073, pabeni, kaiyuanz,
	bobbyeshleman, netdev, linux-kernel, linux-kselftest, linux-media,
	dri-devel, andrew+netdev, davem, edumazet, horms, shuah,
	sumit.semwal, christian.koenig, daniel, razor, tariqt, kaifengw,
	dtatulea

Thank you for your contribution! Sashiko AI review found 2 potential
issue(s) to consider.

Critical: 0 · High: 1 · Medium: 1 · Low: 0

If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).

- [High] nsim_bus_dev_new() now calls
  dma_coerce_mask_and_coherent(&nsim_bus_dev->dev, DMA_BIT_MASK(64)) on…
- [Medium] Before this patch, netdevsim page pools did not DMA-map pages.

--

netdevsim: support devmem RX and TX bindings

The patch sets a 64-bit DMA mask on the netdevsim bus device and enables
NETMEM_TX_DMA. It also creates page pools with
PP_FLAG_ALLOW_UNREADABLE_NETMEM and a queue_idx, and adds DMA mapping
flags under CONFIG_HAS_DMA, so devmem bind-rx and bind-tx can be tested
against netdevsim. Finally, it refuses pp_hold when the queue 0 pool is
backed by a memory provider and drops unreadable skbs in the software PSP
checksum path.

> diff --git a/drivers/net/netdevsim/bus.c b/drivers/net/netdevsim/bus.c
> index 5c55c308487b4..f87f5e037b6fb 100644
> --- a/drivers/net/netdevsim/bus.c
> +++ b/drivers/net/netdevsim/bus.c
> @@ -462,6 +462,7 @@ nsim_bus_dev_new(unsigned int id, unsigned int port_count, unsigned int num_queu
>  	nsim_bus_dev->port_count = port_count;
>  	nsim_bus_dev->num_queues = num_queues;
>  	nsim_bus_dev->initial_net = current->nsproxy->net_ns;
> +	dma_coerce_mask_and_coherent(&nsim_bus_dev->dev, DMA_BIT_MASK(64));

[Severity: High]
Can this oops on sparc64 when a netdevsim device is created?

The nsim bus device has no DMA or IOMMU setup, so dev->archdata.iommu
stays NULL. On sparc64, get_arch_dma_ops() returns the global sun4u/sun4v
ops for devices that have no per-device dma_ops. The call chain is:

new_device_store()
  nsim_bus_dev_new()
    dma_coerce_mask_and_coherent()
      dma_set_mask_and_coherent()
        dma_supported()
          dma_4u_supported()

arch/sparc/kernel/iommu.c:dma_4u_supported() {
	struct iommu *iommu = dev->archdata.iommu;

	if (ali_sound_dma_hack(dev, device_mask))
		return 1;

	if (device_mask < iommu->dma_addr_mask)
		return 0;
	return 1;
}

ali_sound_dma_hack() returns 0 for non-PCI devices. That would make the
iommu->dma_addr_mask read a NULL pointer dereference.

Even if the mask check got through, the page_pool mappings enabled under
CONFIG_HAS_DMA in nsim_create_page_pool() would still go through the same
arch IOMMU ops with a NULL iommu. CONFIG_HAS_DMA is set on sparc64.

The return value of dma_coerce_mask_and_coherent() is also ignored here.
Should a failure be handled, or should PP_FLAG_DMA_MAP depend on whether
the mask was actually set?

>  	/* Disallow using nsim_bus_dev */
>  	smp_store_release(&nsim_bus_dev->init, false);
>  
> diff --git a/drivers/net/netdevsim/netdev.c b/drivers/net/netdevsim/netdev.c
> index b4a99f3ceac60..357ea9e5623b3 100644
> --- a/drivers/net/netdevsim/netdev.c
> +++ b/drivers/net/netdevsim/netdev.c
> @@ -414,19 +414,27 @@ static int nsim_poll(struct napi_struct *napi, int budget)
>  	return done;
>  }
>  
> -static int nsim_create_page_pool(struct page_pool **p, struct napi_struct *napi)
> +static int nsim_create_page_pool(struct net_device *dev, struct page_pool **p,
> +				 struct napi_struct *napi, int idx)
>  {
> +	struct netdevsim *ns = netdev_priv(dev);
>  	struct page_pool_params params = {
>  		.order = 0,
>  		.pool_size = NSIM_RING_SIZE,
> +		.flags = PP_FLAG_ALLOW_UNREADABLE_NETMEM,
> +		.max_len = PAGE_SIZE,
>  		.nid = NUMA_NO_NODE,
> -		.dev = &napi->dev->dev,
> +		.dev = &ns->nsim_bus_dev->dev,
>  		.napi = napi,
>  		.dma_dir = DMA_BIDIRECTIONAL,
> -		.netdev = napi->dev,
> +		.netdev = dev,
> +		.queue_idx = idx,
>  	};
>  	struct page_pool *pool;
>  
> +	if (IS_ENABLED(CONFIG_HAS_DMA))
> +		params.flags |= PP_FLAG_DMA_MAP | PP_FLAG_DMA_SYNC_DEV;
> +

[Severity: Medium]
Pool pages are now DMA-mapped against &ns->nsim_bus_dev->dev. Can the
deferred page_pool release worker end up unmapping against a struct
device that has already been freed?

nsim_destroy() returns the pp_hold page on purpose late, after
unregister_netdevice() has already destroyed the pool via nsim_stop():

drivers/net/netdevsim/netdev.c:nsim_destroy() {
	...
	/* Put this intentionally late to exercise the orphaning path */
	if (ns->page) {
		page_pool_put_full_page(pp_page_to_nmdesc(ns->page)->pp,
					ns->page, false);
		ns->page = NULL;
	}
	...
}

That can recycle the page into the retired pool's ptr_ring instead of
releasing it.

When PP_DMA_INDEX_BITS evaluates to 0, page_pool_register_dma_index()
does not track the mapping, so the first page_pool_scrub() has nothing to
unmap. Later the retry worker drains the ring:

page_pool_release_retry()
  ...
    __page_pool_unmap_netmem_dma()
      dma_unmap_page_attrs(pool->p.dev, ...)

In this case there is no xa_cmpxchg guard, so the unmap runs
unconditionally.

page_pool holds no reference on pool->p.dev. nsim_bus_dev_release()
kfree()s the nsim_bus_dev that embeds the device once device_unregister()
drops the last reference.

If page_pool_release_retry() runs after that, does dma_unmap_page_attrs()
dereference freed memory?

It is not clear which shipping configurations end up with
PP_DMA_INDEX_BITS == 0, so this may be limited in practice.

>  	pool = page_pool_create(&params);
>  	if (IS_ERR(pool))
>  		return PTR_ERR(pool);

[ ... ]

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261014161754.3782346-1-almasrymina%40google.com

^ permalink raw reply	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2026-10-15 19:21 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-14 16:17 [PATCH net v3 0/4] net: devmem: fix RX and TX binding teardown on device unregistration Mina Almasry
2026-10-14 16:17 ` [PATCH net v3 1/4] net: devmem: unmap dma_buf synchronously on queue uninstall Mina Almasry
2026-10-15 19:21   ` netdev-bot+sashiko
2026-10-14 16:17 ` [PATCH net v3 2/4] net: devmem: detach TX bindings on NETDEV_UNREGISTER Mina Almasry
2026-10-15 19:21   ` netdev-bot+sashiko
2026-10-14 16:17 ` [PATCH net v3 3/4] netdevsim: support devmem RX and TX bindings Mina Almasry
2026-10-15 19:21   ` netdev-bot+sashiko
2026-10-14 16:17 ` [PATCH net v3 4/4] selftests: net: add devmem RX and TX netdev unregister tests Mina Almasry

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox