net/core/dev.c | 2 ++ net/core/devmem.c | 47 +++++++++++++++++++++++++++++++++++++++++++++-- net/core/devmem.h | 7 +++++++ 3 files changed, 54 insertions(+), 2 deletions(-)
RX dma-buf bindings are invalidated by their memory provider when a
netdevice is unregistered. TX bindings have no bound RX queues and no
equivalent uninstall callback, so their physical and virtual netdevice
pointers remain live after the devices are freed.
Closing the owning netlink socket after device removal then makes
netdev_nl_sock_priv_destroy() dereference the freed physical netdevice to
hold and lock it. KASAN reports a slab-use-after-free and the kernel can
panic.
The binding can also outlive the device used for its dma-buf attachment.
Since dma_buf_attach() does not hold a reference to that device, deferred
binding cleanup can pass a freed device to dma_buf_unmap_attachment().
Invalidate TX bindings that refer to either the physical or virtual
netdevice during unregister. Keep a reference on the exact DMA device
until the attachment is unmapped. The netlink socket destructor then uses
the existing device-gone path, while delayed dma-buf cleanup retains a
valid DMA device.
NETDEV_CMD_BIND_TX does not require GENL_ADMIN_PERM. The failure was
reproduced with the binding owned by UID 65534 across module removal.
Fixes: bd61848900bf ("net: devmem: Implement TX path")
Cc: stable@vger.kernel.org
Assisted-by: Codex:gpt-5
Signed-off-by: Felix Hoffmann <f3lix.dev@gmx.de>
---
The reproducer is available privately on request.
Testing:
- Full x86-64 kernel build with generic KASAN enabled
- Unpatched kernel: UID 65534 bind, netdevice removal, and socket close
produced the reported KASAN use-after-free and panic
- Patched kernel: the identical sequence completed without a KASAN report
- Patched kernel: 10 additional bind, removal, and close iterations passed
net/core/dev.c | 2 ++
net/core/devmem.c | 47 +++++++++++++++++++++++++++++++++++++++++++++--
net/core/devmem.h | 7 +++++++
3 files changed, 54 insertions(+), 2 deletions(-)
diff --git a/net/core/dev.c b/net/core/dev.c
index ecfbd72d5d1a..c325fa7e0d6f 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -12401,6 +12401,8 @@ static void dev_memory_provider_uninstall(struct net_device *dev)
__netif_mp_uninstall_rxq(rxq, &rxq->mp_params);
}
+
+ net_devmem_uninstall_tx_bindings(dev);
}
/* devices must be UP and netdev_lock()'d */
diff --git a/net/core/devmem.c b/net/core/devmem.c
index f4d60654ce7f..a21a8fe92f58 100644
--- a/net/core/devmem.c
+++ b/net/core/devmem.c
@@ -77,6 +77,7 @@ void __net_devmem_dmabuf_binding_free(struct work_struct *wq)
dma_buf_unmap_attachment_unlocked(binding->attachment, binding->sgt,
binding->direction);
dma_buf_detach(binding->dmabuf, binding->attachment);
+ put_device(binding->dma_dev);
dma_buf_put(binding->dmabuf);
xa_destroy(&binding->bound_rxqs);
percpu_ref_exit(&binding->ref);
@@ -153,6 +154,46 @@ void net_devmem_unbind_dmabuf(struct net_devmem_dmabuf_binding *binding)
percpu_ref_kill(&binding->ref);
}
+void net_devmem_uninstall_tx_bindings(struct net_device *dev)
+{
+ struct net_devmem_dmabuf_binding *binding;
+ struct net_devmem_dmabuf_binding *found;
+ unsigned long xa_idx;
+
+ /* Unlike RX bindings, TX bindings have no memory provider whose
+ * uninstall callback can invalidate their net_device pointers.
+ */
+again:
+ found = NULL;
+ rcu_read_lock();
+ xa_for_each(&net_devmem_dmabuf_bindings, xa_idx, binding) {
+ if (binding->direction != DMA_TO_DEVICE ||
+ (READ_ONCE(binding->dev) != dev &&
+ READ_ONCE(binding->vdev) != dev))
+ continue;
+
+ if (!net_devmem_dmabuf_binding_get(binding))
+ continue;
+ found = binding;
+ break;
+ }
+ rcu_read_unlock();
+
+ if (!found)
+ return;
+
+ binding = found;
+ mutex_lock(&binding->lock);
+ if (binding->direction == DMA_TO_DEVICE &&
+ (binding->dev == dev || binding->vdev == dev)) {
+ WRITE_ONCE(binding->dev, NULL);
+ WRITE_ONCE(binding->vdev, NULL);
+ }
+ mutex_unlock(&binding->lock);
+ net_devmem_dmabuf_binding_put(binding);
+ goto again;
+}
+
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)
@@ -233,12 +274,13 @@ net_devmem_bind_dmabuf(struct net_device *dev, void *vdev,
binding->dmabuf = dmabuf;
binding->direction = direction;
+ binding->dma_dev = get_device(dma_dev);
binding->attachment = dma_buf_attach(binding->dmabuf, dma_dev);
if (IS_ERR(binding->attachment)) {
err = PTR_ERR(binding->attachment);
NL_SET_ERR_MSG(extack, "Failed to bind dmabuf to device");
- goto err_exit_ref;
+ goto err_put_dma_dev;
}
binding->sgt = dma_buf_map_attachment_unlocked(binding->attachment,
@@ -347,7 +389,8 @@ net_devmem_bind_dmabuf(struct net_device *dev, void *vdev,
direction);
err_detach:
dma_buf_detach(dmabuf, binding->attachment);
-err_exit_ref:
+err_put_dma_dev:
+ put_device(binding->dma_dev);
percpu_ref_exit(&binding->ref);
err_free_binding:
kfree(binding);
diff --git a/net/core/devmem.h b/net/core/devmem.h
index 4a293a7d1149..a40f0a7c221f 100644
--- a/net/core/devmem.h
+++ b/net/core/devmem.h
@@ -19,6 +19,8 @@ struct net_devmem_dmabuf_binding {
struct dma_buf *dmabuf;
struct dma_buf_attachment *attachment;
struct sg_table *sgt;
+ /* Device used to map the dma-buf. Held until the mapping is removed. */
+ struct device *dma_dev;
/* Physical NIC that does the actual DMA for this binding. */
struct net_device *dev;
/* Opaque cookie identifying the virtual device (e.g. netkit) the user
@@ -100,6 +102,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_uninstall_tx_bindings(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 +199,10 @@ net_devmem_unbind_dmabuf(struct net_devmem_dmabuf_binding *binding)
{
}
+static inline void net_devmem_uninstall_tx_bindings(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,
--
2.43.0
On Thu, Sep 10, 2026 at 7:37 AM Felix Hoffmann <f3lix.dev@gmx.de> wrote:
>
> RX dma-buf bindings are invalidated by their memory provider when a
> netdevice is unregistered. TX bindings have no bound RX queues and no
> equivalent uninstall callback, so their physical and virtual netdevice
> pointers remain live after the devices are freed.
>
> Closing the owning netlink socket after device removal then makes
> netdev_nl_sock_priv_destroy() dereference the freed physical netdevice to
> hold and lock it. KASAN reports a slab-use-after-free and the kernel can
> panic.
>
> The binding can also outlive the device used for its dma-buf attachment.
> Since dma_buf_attach() does not hold a reference to that device, deferred
> binding cleanup can pass a freed device to dma_buf_unmap_attachment().
>
> Invalidate TX bindings that refer to either the physical or virtual
> netdevice during unregister. Keep a reference on the exact DMA device
> until the attachment is unmapped. The netlink socket destructor then uses
> the existing device-gone path, while delayed dma-buf cleanup retains a
> valid DMA device.
>
> NETDEV_CMD_BIND_TX does not require GENL_ADMIN_PERM. The failure was
> reproduced with the binding owned by UID 65534 across module removal.
>
> Fixes: bd61848900bf ("net: devmem: Implement TX path")
> Cc: stable@vger.kernel.org
> Assisted-by: Codex:gpt-5
> Signed-off-by: Felix Hoffmann <f3lix.dev@gmx.de>
> ---
> The reproducer is available privately on request.
>
> Testing:
> - Full x86-64 kernel build with generic KASAN enabled
> - Unpatched kernel: UID 65534 bind, netdevice removal, and socket close
> produced the reported KASAN use-after-free and panic
> - Patched kernel: the identical sequence completed without a KASAN report
> - Patched kernel: 10 additional bind, removal, and close iterations passed
>
> net/core/dev.c | 2 ++
> net/core/devmem.c | 47 +++++++++++++++++++++++++++++++++++++++++++++--
> net/core/devmem.h | 7 +++++++
> 3 files changed, 54 insertions(+), 2 deletions(-)
>
> diff --git a/net/core/dev.c b/net/core/dev.c
> index ecfbd72d5d1a..c325fa7e0d6f 100644
> --- a/net/core/dev.c
> +++ b/net/core/dev.c
> @@ -12401,6 +12401,8 @@ static void dev_memory_provider_uninstall(struct net_device *dev)
>
> __netif_mp_uninstall_rxq(rxq, &rxq->mp_params);
> }
> +
> + net_devmem_uninstall_tx_bindings(dev);
> }
>
> /* devices must be UP and netdev_lock()'d */
> diff --git a/net/core/devmem.c b/net/core/devmem.c
> index f4d60654ce7f..a21a8fe92f58 100644
> --- a/net/core/devmem.c
> +++ b/net/core/devmem.c
> @@ -77,6 +77,7 @@ void __net_devmem_dmabuf_binding_free(struct work_struct *wq)
> dma_buf_unmap_attachment_unlocked(binding->attachment, binding->sgt,
> binding->direction);
> dma_buf_detach(binding->dmabuf, binding->attachment);
> + put_device(binding->dma_dev);
> dma_buf_put(binding->dmabuf);
> xa_destroy(&binding->bound_rxqs);
> percpu_ref_exit(&binding->ref);
> @@ -153,6 +154,46 @@ void net_devmem_unbind_dmabuf(struct net_devmem_dmabuf_binding *binding)
> percpu_ref_kill(&binding->ref);
> }
>
> +void net_devmem_uninstall_tx_bindings(struct net_device *dev)
> +{
> + struct net_devmem_dmabuf_binding *binding;
> + struct net_devmem_dmabuf_binding *found;
> + unsigned long xa_idx;
> +
> + /* Unlike RX bindings, TX bindings have no memory provider whose
> + * uninstall callback can invalidate their net_device pointers.
> + */
Please remove these LLM generated comments that make no sense. The
comment is correct but no one reading this code is wondering if the TX
binding have a memory provider.
> +again:
> + found = NULL;
> + rcu_read_lock();
> + xa_for_each(&net_devmem_dmabuf_bindings, xa_idx, binding) {
> + if (binding->direction != DMA_TO_DEVICE ||
> + (READ_ONCE(binding->dev) != dev &&
> + READ_ONCE(binding->vdev) != dev))
> + continue;
> +
Can you do a deeper investigation on what to do here or explain why
this is correct. I'm not sure we should do anything on the vdev
unregestiring? hmm...
FWIW I think probably an in-tree test would not go to net, but would
be a separate patch that goes to net-next? Maybe? IDK. Up to stan.
--
Thanks,
Mina
Thanks for the review, Mina, and for the pointers, Stan. I removed the RX-versus-TX comment and the added DMA-device field comment. You were right to question invalidating the entire binding when the virtual device disappears. The socket destructor uses binding->dev; binding->vdev is only a cookie for the route comparison in net_devmem_get_binding(). The revision clears only vdev on virtual-device unregister and leaves the physical binding intact. Clearing the cookie still matters because keeping its old address could match a newly allocated netdevice. Physical-device unregister clears both pointers. I factored the TX-binding scan into a reference-taking lookup helper. It holds RCU while finding the binding and taking its reference; invalidation then takes binding->lock, which also protects the socket destructor's netdevice reference acquisition. The temporary binding reference covers the interval outside RCU and is released after invalidation. I read Dragos's proposed netdev_has_dmabuf_binding() helper. Its boolean physical-device/DMA-device query is useful for data-direct, but teardown also needs a binding reference and virtual-cookie matching. The factored lookup handles those requirements; I have not added an unused exported boolean API. Dragos, does this separation work for your planned helper? I added a separate KUnit test patch covering real netdevice unregister with test-owned binding metadata: physical removal, virtual removal, both orders, multiple matches, unrelated/RX bindings, and reference balancing. All five cases pass under KASAN and lockdep. These are kernel unit tests; they do not replace the hardware DMA/netlink selftest Stan proposed. I have not included or claimed to run that hardware test. The current netdev guidance says that a fix and its related test should go to the same tree as separate commits. I therefore prepared the fix and KUnit coverage as a two-patch v2 for net. If you prefer the KUnit patch in net-next, I can split the routing accordingly. I also added credit and a link to Hengbin's earlier report, and corrected the missing Cc recipients identified by the submission checks. I'll send v2 as a fresh thread with a link to v1 once this discussion is settled, as the netdev submission guidance requests. Thanks, Felix
On 09/10, Felix Hoffmann wrote:
> RX dma-buf bindings are invalidated by their memory provider when a
> netdevice is unregistered. TX bindings have no bound RX queues and no
> equivalent uninstall callback, so their physical and virtual netdevice
> pointers remain live after the devices are freed.
>
> Closing the owning netlink socket after device removal then makes
> netdev_nl_sock_priv_destroy() dereference the freed physical netdevice to
> hold and lock it. KASAN reports a slab-use-after-free and the kernel can
> panic.
>
> The binding can also outlive the device used for its dma-buf attachment.
> Since dma_buf_attach() does not hold a reference to that device, deferred
> binding cleanup can pass a freed device to dma_buf_unmap_attachment().
>
> Invalidate TX bindings that refer to either the physical or virtual
> netdevice during unregister. Keep a reference on the exact DMA device
> until the attachment is unmapped. The netlink socket destructor then uses
> the existing device-gone path, while delayed dma-buf cleanup retains a
> valid DMA device.
>
> NETDEV_CMD_BIND_TX does not require GENL_ADMIN_PERM. The failure was
> reproduced with the binding owned by UID 65534 across module removal.
>
> Fixes: bd61848900bf ("net: devmem: Implement TX path")
> Cc: stable@vger.kernel.org
> Assisted-by: Codex:gpt-5
> Signed-off-by: Felix Hoffmann <f3lix.dev@gmx.de>
> ---
> The reproducer is available privately on request.
As I mentioned on https://lore.kernel.org/netdev/aqC4XSt7JrrTv4sG@devvm7509.cco0.facebook.com/
I'd like us to have an in-tree selftest for that.
And Dragos also had a suggestion to use a new helper for checking if
there are any outstanding tx dmabufs attached...
© 2016 - 2026 Red Hat, Inc.