[PATCH net v1 2/4] net: devmem: detach TX bindings on NETDEV_UNREGISTER
flat view
COOLING8d
SUPERSEDED
From: Mina Almasry <hidden>
Date: 2026-10-10 02:55:40
Also in:
dri-devel, linux-kselftest, linux-media, lkml
Subsystem:
networking [general], the rest · Maintainers:
"David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, Linus Torvalds
Revision v1 of 4 in this series.
Revisions (4)
- v1 current
- v2 [diff vs current]
- v3 [diff vs current]
- v4 [diff vs current]
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
Add net_devmem_dev_unregister() called on NETDEV_UNREGISTER:
- When binding->dev (the physical DMA-capable device) unregisters,
erase the binding ID, clear both binding->dev and binding->vdev, wait
for in-flight RCU/TX readers via synchronize_net(), and unmap the
dma_buf 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 <redacted>
Cc: Stanislav Fomichev <sdf@fomichev.me>
Cc: Bobby Eshleman <redacted>
Cc: Tariq Toukan <tariqt@nvidia.com>
Cc: Kaifeng Wang <redacted>
Signed-off-by: Mina Almasry <redacted>
---
net/core/devmem.c | 45 ++++++++++++++++++++++++++++++++++++++++++
net/core/devmem.h | 5 +++++
net/core/netdev-genl.c | 1 +
3 files changed, 51 insertions(+)
diff --git a/net/core/devmem.c b/net/core/devmem.c
index 5e4070b82df44..da7b3e8abaa13 100644
--- a/net/core/devmem.c
+++ b/net/core/devmem.c@@ -169,6 +169,51 @@ void net_devmem_unbind_dmabuf(struct net_devmem_dmabuf_binding *binding) percpu_ref_kill(&binding->ref); } +void net_devmem_dev_unregister(struct net_device *dev) +{ + struct net_devmem_dmabuf_binding *binding; + unsigned long xa_idx; + + rcu_read_lock(); + xa_for_each(&net_devmem_dmabuf_bindings, xa_idx, binding) { + if (!percpu_ref_tryget(&binding->ref)) + continue; + rcu_read_unlock(); + + if (xa_empty(&binding->bound_rxqs)) { + if (READ_ONCE(binding->dev) == dev) { + xa_erase(&net_devmem_dmabuf_bindings, + binding->id); + + mutex_lock(&binding->lock); + WRITE_ONCE(binding->dev, NULL); + WRITE_ONCE(binding->vdev, NULL); + mutex_unlock(&binding->lock); + + /* Ensure no tx net_devmem_lookup_dmabuf() or + * validate_xmit_unreadable_skb() are in flight + * after detach. + */ + synchronize_net(); + + mutex_lock(&binding->lock); + net_devmem_dmabuf_binding_unmap(binding); + mutex_unlock(&binding->lock); + } else if (READ_ONCE(binding->vdev) == dev) { + mutex_lock(&binding->lock); + WRITE_ONCE(binding->vdev, NULL); + mutex_unlock(&binding->lock); + + synchronize_net(); + } + } + + net_devmem_dmabuf_binding_put(binding); + rcu_read_lock(); + } + rcu_read_unlock(); +} + 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)
diff --git a/net/core/devmem.h b/net/core/devmem.h
index 4a293a7d1149c..b572013d602c7 100644
--- a/net/core/devmem.h
+++ b/net/core/devmem.h@@ -100,6 +100,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 +197,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..5bfcd513fb0e8 100644
--- a/net/core/netdev-genl.c
+++ b/net/core/netdev-genl.c@@ -1487,6 +1487,7 @@ static int netdev_genl_netdevice_event(struct notifier_block *nb, netdev_unlock_full_to_ops(netdev); break; case NETDEV_UNREGISTER: + net_devmem_dev_unregister(netdev); netdev_lock(netdev); netdev_genl_dev_notify(netdev, NETDEV_CMD_DEV_DEL_NTF); netdev_unlock(netdev);
--
2.56.0.385.gd3acb90ef8-goog