AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/10/15 21:30 flow patch-triage
2m Args:
null

Results:
{
  "EnableConfigs": [
    "NET_DEVMEM",
    "NETDEVSIM",
    "UDMABUF",
    "SYNC_FILE",
    "INET_PSP",
    "DEBUG_FS"
  ],
  "FocusSymbols": [
    "net_devmem_dev_unregister",
    "net_devmem_unbind_dmabuf",
    "net_devmem_bind_dmabuf",
    "net_devmem_bind_dmabuf_to_queue",
    "net_devmem_lookup_dmabuf",
    "__net_devmem_dmabuf_binding_free",
    "netdev_nl_bind_rx_doit",
    "netdev_nl_bind_tx_doit",
    "netif_mp_close_rxq",
    "nsim_bus_dev_new",
    "nsim_do_psp"
  ],
  "KMSANReasoning": "The patch series adds devmem (dmabuf) support to netdevsim and fixes lifecycle management and synchronization issues during netdev unregistration for devmem bindings. The primary risks associated with these changes are concurrency bugs, locking issues, and use-after-free (UAF) conditions during device detach/unregistration, which are covered by KASAN and LOCKDEP. No uninitialized memory is exposed, no uninitialized struct fields or padding are copied to userspace, and all new/modified local variables and structures are fully initialized. Therefore, KMSAN is not needed.",
  "NeedsKMSAN": false,
  "Reasoning": "The patch series modifies core networking memory provider / devmem TCP logic (net/core/devmem.c, net/core/netdev_rx_queue.c, and net/core/netdev-genl.c) as well as the netdevsim driver. It adjusts dmabuf unmapping lifecycles, adds synchronization upon netdev unregistration, changes return types and error handling in queue close callbacks, and adds unreadable netmem support and DMA coercion to netdevsim. These code paths are fully reachable in virtualized environments via netlink (netdev family), TCP sendmsg, and netdevsim/udmabuf devices. Fuzzing is warranted to uncover potential concurrency issues, unregister/unbind race conditions, or locking violations.",
  "WorthFuzzing": true
}

1/1 2026/10/15 21:30 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit 180d7add7fd4a84a7be8a0708cc6ca75fd8c54b1\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Thu Oct 15 21:30:26 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/drivers/net/netdevsim/bus.c b/drivers/net/netdevsim/bus.c\nindex 5c55c308487b4..f87f5e037b6fb 100644\n--- a/drivers/net/netdevsim/bus.c\n+++ b/drivers/net/netdevsim/bus.c\n@@ -462,6 +462,7 @@ nsim_bus_dev_new(unsigned int id, unsigned int port_count, unsigned int num_queu\n \tnsim_bus_dev-\u003eport_count = port_count;\n \tnsim_bus_dev-\u003enum_queues = num_queues;\n \tnsim_bus_dev-\u003einitial_net = current-\u003ensproxy-\u003enet_ns;\n+\tdma_coerce_mask_and_coherent(\u0026nsim_bus_dev-\u003edev, DMA_BIT_MASK(64));\n \t/* Disallow using nsim_bus_dev */\n \tsmp_store_release(\u0026nsim_bus_dev-\u003einit, false);\n \ndiff --git a/drivers/net/netdevsim/netdev.c b/drivers/net/netdevsim/netdev.c\nindex b4a99f3ceac60..357ea9e5623b3 100644\n--- a/drivers/net/netdevsim/netdev.c\n+++ b/drivers/net/netdevsim/netdev.c\n@@ -414,19 +414,27 @@ static int nsim_poll(struct napi_struct *napi, int budget)\n \treturn done;\n }\n \n-static int nsim_create_page_pool(struct page_pool **p, struct napi_struct *napi)\n+static int nsim_create_page_pool(struct net_device *dev, struct page_pool **p,\n+\t\t\t\t struct napi_struct *napi, int idx)\n {\n+\tstruct netdevsim *ns = netdev_priv(dev);\n \tstruct page_pool_params params = {\n \t\t.order = 0,\n \t\t.pool_size = NSIM_RING_SIZE,\n+\t\t.flags = PP_FLAG_ALLOW_UNREADABLE_NETMEM,\n+\t\t.max_len = PAGE_SIZE,\n \t\t.nid = NUMA_NO_NODE,\n-\t\t.dev = \u0026napi-\u003edev-\u003edev,\n+\t\t.dev = \u0026ns-\u003ensim_bus_dev-\u003edev,\n \t\t.napi = napi,\n \t\t.dma_dir = DMA_BIDIRECTIONAL,\n-\t\t.netdev = napi-\u003edev,\n+\t\t.netdev = dev,\n+\t\t.queue_idx = idx,\n \t};\n \tstruct page_pool *pool;\n \n+\tif (IS_ENABLED(CONFIG_HAS_DMA))\n+\t\tparams.flags |= PP_FLAG_DMA_MAP | PP_FLAG_DMA_SYNC_DEV;\n+\n \tpool = page_pool_create(\u0026params);\n \tif (IS_ERR(pool))\n \t\treturn PTR_ERR(pool);\n@@ -450,7 +458,7 @@ static int nsim_init_napi(struct netdevsim *ns)\n \tfor (i = 0; i \u003c dev-\u003enum_rx_queues; i++) {\n \t\trq = ns-\u003erq[i];\n \n-\t\terr = nsim_create_page_pool(\u0026rq-\u003epage_pool, \u0026rq-\u003enapi);\n+\t\terr = nsim_create_page_pool(dev, \u0026rq-\u003epage_pool, \u0026rq-\u003enapi, i);\n \t\tif (err)\n \t\t\tgoto err_pp_destroy;\n \t}\n@@ -762,14 +770,16 @@ nsim_queue_mem_alloc(struct net_device *dev,\n \tif (ns-\u003erq_reset_mode == 1) {\n \t\tif (!netif_running(ns-\u003enetdev))\n \t\t\treturn -ENETDOWN;\n-\t\treturn nsim_create_page_pool(\u0026qmem-\u003epp, \u0026ns-\u003erq[idx]-\u003enapi);\n+\t\treturn nsim_create_page_pool(dev, \u0026qmem-\u003epp,\n+\t\t\t\t\t     \u0026ns-\u003erq[idx]-\u003enapi, idx);\n \t}\n \n \tqmem-\u003erq = nsim_queue_alloc();\n \tif (!qmem-\u003erq)\n \t\treturn -ENOMEM;\n \n-\terr = nsim_create_page_pool(\u0026qmem-\u003erq-\u003epage_pool, \u0026qmem-\u003erq-\u003enapi);\n+\terr = nsim_create_page_pool(dev, \u0026qmem-\u003erq-\u003epage_pool, \u0026qmem-\u003erq-\u003enapi,\n+\t\t\t\t    idx);\n \tif (err)\n \t\tgoto err_free;\n \n@@ -929,12 +939,15 @@ nsim_pp_hold_write(struct file *file, const char __user *data,\n \t\treturn ret;\n \n \trtnl_lock();\n+\tnetdev_lock(ns-\u003enetdev);\n \tret = count;\n \tif (val == !!ns-\u003epage)\n \t\tgoto exit;\n \n \tif (!netif_running(ns-\u003enetdev) \u0026\u0026 val) {\n \t\tret = -ENETDOWN;\n+\t} else if (val \u0026\u0026 page_pool_is_unreadable(ns-\u003erq[0]-\u003epage_pool)) {\n+\t\tret = -EOPNOTSUPP;\n \t} else if (val) {\n \t\tns-\u003epage = page_pool_dev_alloc_pages(ns-\u003erq[0]-\u003epage_pool);\n \t\tif (!ns-\u003epage)\n@@ -946,6 +959,7 @@ nsim_pp_hold_write(struct file *file, const char __user *data,\n \t}\n \n exit:\n+\tnetdev_unlock(ns-\u003enetdev);\n \trtnl_unlock();\n \treturn ret;\n }\n@@ -1051,6 +1065,7 @@ static int nsim_init_netdevsim(struct netdevsim *ns)\n \tns-\u003enetdev-\u003enetdev_ops = \u0026nsim_netdev_ops;\n \tns-\u003enetdev-\u003estat_ops = \u0026nsim_stat_ops;\n \tns-\u003enetdev-\u003equeue_mgmt_ops = \u0026nsim_queue_mgmt_ops;\n+\tns-\u003enetdev-\u003enetmem_tx = NETMEM_TX_DMA;\n \tnetdev_lockdep_set_classes(ns-\u003enetdev);\n \n \terr = nsim_udp_tunnels_info_create(ns-\u003ensim_dev, ns-\u003enetdev);\ndiff --git a/drivers/net/netdevsim/psp.c b/drivers/net/netdevsim/psp.c\nindex 6b3532b5e360d..908cc0d391dc6 100644\n--- a/drivers/net/netdevsim/psp.c\n+++ b/drivers/net/netdevsim/psp.c\n@@ -86,6 +86,11 @@ nsim_do_psp(struct sk_buff *skb, struct netdevsim *ns,\n \t\t__wsum csum;\n \t\tint udplen;\n \n+\t\tif (!skb_frags_readable(skb)) {\n+\t\t\trc = SKB_DROP_REASON_PSP_OUTPUT;\n+\t\t\tgoto out_unlock;\n+\t\t}\n+\n \t\t/* Do not decapsulate. Receive the skb with the udp and psp\n \t\t * headers still there as if this is a normal udp packet.\n \t\t * psp_dev_encapsulate() sets udp checksum to 0, so we need to\ndiff --git a/include/net/page_pool/memory_provider.h b/include/net/page_pool/memory_provider.h\nindex 255ce4cfd9755..a999a9b28bc70 100644\n--- a/include/net/page_pool/memory_provider.h\n+++ b/include/net/page_pool/memory_provider.h\n@@ -26,8 +26,8 @@ void net_mp_niov_clear_page_pool(struct net_iov *niov);\n int netif_mp_open_rxq(struct net_device *dev, unsigned int rxq_idx,\n \t\t      const struct pp_memory_provider_params *p,\n \t\t      struct netlink_ext_ack *extack);\n-void netif_mp_close_rxq(struct net_device *dev, unsigned int rxq_idx,\n-\t\t\tconst struct pp_memory_provider_params *old_p);\n+int netif_mp_close_rxq(struct net_device *dev, unsigned int rxq_idx,\n+\t\t       const struct pp_memory_provider_params *old_p);\n \n /**\n   * net_mp_netmem_place_in_cache() - give a netmem to a page pool\ndiff --git a/net/core/devmem.c b/net/core/devmem.c\nindex f4d60654ce7fd..d33a092c6e4bb 100644\n--- a/net/core/devmem.c\n+++ b/net/core/devmem.c\n@@ -12,6 +12,7 @@\n #include \u003clinux/mm.h\u003e\n #include \u003clinux/netdevice.h\u003e\n #include \u003clinux/types.h\u003e\n+#include \u003cnet/netdev_lock.h\u003e\n #include \u003cnet/netdev_queues.h\u003e\n #include \u003cnet/netdev_rx_queue.h\u003e\n #include \u003cnet/page_pool/helpers.h\u003e\n@@ -20,6 +21,7 @@\n #include \u003cnet/tcp.h\u003e\n #include \u003ctrace/events/page_pool.h\u003e\n \n+#include \"dev.h\"\n #include \"devmem.h\"\n #include \"mp_dmabuf_devmem.h\"\n #include \"page_pool_priv.h\"\n@@ -27,6 +29,7 @@\n /* Device memory support */\n \n static DEFINE_XARRAY_FLAGS(net_devmem_dmabuf_bindings, XA_FLAGS_ALLOC1);\n+#define NET_DEVMEM_BINDING_LIVE XA_MARK_1\n \n static const struct memory_provider_ops dmabuf_devmem_ops;\n \n@@ -58,9 +61,26 @@ static void net_devmem_dmabuf_binding_release(struct percpu_ref *ref)\n \tschedule_work(\u0026binding-\u003eunbind_w);\n }\n \n+static void\n+net_devmem_dmabuf_binding_unmap(struct net_devmem_dmabuf_binding *binding)\n+{\n+\tlockdep_assert_held(\u0026binding-\u003elock);\n+\n+\tif (!binding-\u003esgt)\n+\t\treturn;\n+\n+\tdma_buf_unmap_attachment_unlocked(binding-\u003eattachment, binding-\u003esgt,\n+\t\t\t\t\t  binding-\u003edirection);\n+\tdma_buf_detach(binding-\u003edmabuf, binding-\u003eattachment);\n+\tbinding-\u003esgt = NULL;\n+\tbinding-\u003eattachment = NULL;\n+}\n+\n void __net_devmem_dmabuf_binding_free(struct work_struct *wq)\n {\n \tstruct net_devmem_dmabuf_binding *binding = container_of(wq, typeof(*binding), unbind_w);\n+\tnetdevice_tracker dev_tracker;\n+\tstruct net_device *dev;\n \n \tsize_t size, avail;\n \n@@ -74,9 +94,23 @@ void __net_devmem_dmabuf_binding_free(struct work_struct *wq)\n \t\t  size, avail))\n \t\tgen_pool_destroy(binding-\u003echunk_pool);\n \n-\tdma_buf_unmap_attachment_unlocked(binding-\u003eattachment, binding-\u003esgt,\n-\t\t\t\t\t  binding-\u003edirection);\n-\tdma_buf_detach(binding-\u003edmabuf, binding-\u003eattachment);\n+\tmutex_lock(\u0026binding-\u003elock);\n+\tdev = binding-\u003edev;\n+\tif (dev)\n+\t\tnetdev_hold(dev, \u0026dev_tracker, GFP_KERNEL);\n+\tmutex_unlock(\u0026binding-\u003elock);\n+\n+\tif (dev)\n+\t\tnetdev_lock(dev);\n+\tmutex_lock(\u0026binding-\u003elock);\n+\tnet_devmem_dmabuf_binding_unmap(binding);\n+\tmutex_unlock(\u0026binding-\u003elock);\n+\txa_erase(\u0026net_devmem_dmabuf_bindings, binding-\u003eid);\n+\tif (dev) {\n+\t\tnetdev_unlock(dev);\n+\t\tnetdev_put(dev, \u0026dev_tracker);\n+\t}\n+\n \tdma_buf_put(binding-\u003edmabuf);\n \txa_destroy(\u0026binding-\u003ebound_rxqs);\n \tpercpu_ref_exit(\u0026binding-\u003eref);\n@@ -128,11 +162,13 @@ void net_devmem_unbind_dmabuf(struct net_devmem_dmabuf_binding *binding)\n \tstruct netdev_rx_queue *rxq;\n \tunsigned long xa_idx;\n \tunsigned int rxq_idx;\n+\tbool busy = false;\n \n-\txa_erase(\u0026net_devmem_dmabuf_bindings, binding-\u003eid);\n+\txa_clear_mark(\u0026net_devmem_dmabuf_bindings, binding-\u003eid,\n+\t\t      NET_DEVMEM_BINDING_LIVE);\n \n \t/* Ensure no tx net_devmem_lookup_dmabuf() are in flight after the\n-\t * erase.\n+\t * mark is cleared.\n \t */\n \tsynchronize_net();\n \n@@ -147,12 +183,82 @@ void net_devmem_unbind_dmabuf(struct net_devmem_dmabuf_binding *binding)\n \n \t\trxq_idx = get_netdev_rx_queue_index(rxq);\n \n-\t\tnetif_mp_close_rxq(binding-\u003edev, rxq_idx, \u0026mp_params);\n+\t\tif (netif_mp_close_rxq(binding-\u003edev, rxq_idx, \u0026mp_params))\n+\t\t\tbusy = true;\n+\t}\n+\n+\tif (binding-\u003edirection == DMA_FROM_DEVICE \u0026\u0026 binding-\u003edev \u0026\u0026 !busy) {\n+\t\tmutex_lock(\u0026binding-\u003elock);\n+\t\tWRITE_ONCE(binding-\u003edev, NULL);\n+\t\tnet_devmem_dmabuf_binding_unmap(binding);\n+\t\tmutex_unlock(\u0026binding-\u003elock);\n \t}\n \n \tpercpu_ref_kill(\u0026binding-\u003eref);\n }\n \n+static bool\n+net_devmem_dev_unregister_rx(struct net_devmem_dmabuf_binding *binding,\n+\t\t\t     unsigned long xa_idx, struct net_device *dev)\n+{\n+\treturn binding-\u003edirection == DMA_FROM_DEVICE \u0026\u0026\n+\t       READ_ONCE(binding-\u003edev) == dev \u0026\u0026\n+\t       !xa_get_mark(\u0026net_devmem_dmabuf_bindings, xa_idx,\n+\t\t\t    NET_DEVMEM_BINDING_LIVE);\n+}\n+\n+static bool\n+net_devmem_dev_unregister_tx(struct net_devmem_dmabuf_binding *binding,\n+\t\t\t     unsigned long xa_idx, struct net_device *dev)\n+{\n+\tif (binding-\u003edirection != DMA_TO_DEVICE)\n+\t\treturn false;\n+\n+\tif (READ_ONCE(binding-\u003evdev) == dev)\n+\t\tWRITE_ONCE(binding-\u003evdev, NULL);\n+\n+\tif (READ_ONCE(binding-\u003edev) != dev)\n+\t\treturn false;\n+\n+\t__xa_clear_mark(\u0026net_devmem_dmabuf_bindings, xa_idx,\n+\t\t\tNET_DEVMEM_BINDING_LIVE);\n+\treturn true;\n+}\n+\n+static void net_devmem_dev_detach(struct net_devmem_dmabuf_binding *binding,\n+\t\t\t\t  struct net_device *dev)\n+{\n+\tmutex_lock(\u0026binding-\u003elock);\n+\tWRITE_ONCE(binding-\u003edev, NULL);\n+\tWRITE_ONCE(binding-\u003evdev, NULL);\n+\tif (!netif_is_queue_leasee(dev))\n+\t\tnet_devmem_dmabuf_binding_unmap(binding);\n+\tmutex_unlock(\u0026binding-\u003elock);\n+}\n+\n+void net_devmem_dev_unregister(struct net_device *dev)\n+{\n+\tstruct net_devmem_dmabuf_binding *binding;\n+\tunsigned long xa_idx;\n+\n+\tnetdev_assert_locked(dev);\n+\n+\tif (dev-\u003ereg_state != NETREG_UNREGISTERING)\n+\t\treturn;\n+\n+\txa_lock(\u0026net_devmem_dmabuf_bindings);\n+\txa_for_each(\u0026net_devmem_dmabuf_bindings, xa_idx, binding) {\n+\t\tif (!net_devmem_dev_unregister_rx(binding, xa_idx, dev) \u0026\u0026\n+\t\t    !net_devmem_dev_unregister_tx(binding, xa_idx, dev))\n+\t\t\tcontinue;\n+\n+\t\txa_unlock(\u0026net_devmem_dmabuf_bindings);\n+\t\tnet_devmem_dev_detach(binding, dev);\n+\t\txa_lock(\u0026net_devmem_dmabuf_bindings);\n+\t}\n+\txa_unlock(\u0026net_devmem_dmabuf_bindings);\n+}\n+\n int net_devmem_bind_dmabuf_to_queue(struct net_device *dev, u32 rxq_idx,\n \t\t\t\t    struct net_devmem_dmabuf_binding *binding,\n \t\t\t\t    struct netlink_ext_ack *extack)\n@@ -168,20 +274,16 @@ int net_devmem_bind_dmabuf_to_queue(struct net_device *dev, u32 rxq_idx,\n \tif (binding-\u003eniov_shift != PAGE_SHIFT)\n \t\tmp_params.rx_page_size = 1U \u003c\u003c binding-\u003eniov_shift;\n \n-\terr = netif_mp_open_rxq(dev, rxq_idx, \u0026mp_params, extack);\n-\tif (err)\n-\t\treturn err;\n-\n \trxq = __netif_get_rx_queue(dev, rxq_idx);\n \terr = xa_alloc(\u0026binding-\u003ebound_rxqs, \u0026xa_idx, rxq, xa_limit_32b,\n \t\t       GFP_KERNEL);\n \tif (err)\n-\t\tgoto err_close_rxq;\n+\t\treturn err;\n \n-\treturn 0;\n+\terr = netif_mp_open_rxq(dev, rxq_idx, \u0026mp_params, extack);\n+\tif (err)\n+\t\txa_erase(\u0026binding-\u003ebound_rxqs, xa_idx);\n \n-err_close_rxq:\n-\tnetif_mp_close_rxq(dev, rxq_idx, \u0026mp_params);\n \treturn err;\n }\n \n@@ -331,6 +433,8 @@ net_devmem_bind_dmabuf(struct net_device *dev, void *vdev,\n \t\t\t      GFP_KERNEL);\n \tif (err \u003c 0)\n \t\tgoto err_free_chunks;\n+\txa_set_mark(\u0026net_devmem_dmabuf_bindings, binding-\u003eid,\n+\t\t    NET_DEVMEM_BINDING_LIVE);\n \n \tlist_add(\u0026binding-\u003elist, \u0026priv-\u003ebindings);\n \n@@ -359,9 +463,11 @@ net_devmem_bind_dmabuf(struct net_device *dev, void *vdev,\n struct net_devmem_dmabuf_binding *net_devmem_lookup_dmabuf(u32 id)\n {\n \tstruct net_devmem_dmabuf_binding *binding;\n+\tunsigned long xa_idx = id;\n \n \trcu_read_lock();\n-\tbinding = xa_load(\u0026net_devmem_dmabuf_bindings, id);\n+\tbinding = xa_find(\u0026net_devmem_dmabuf_bindings, \u0026xa_idx, id,\n+\t\t\t  NET_DEVMEM_BINDING_LIVE);\n \tif (binding) {\n \t\tif (!net_devmem_dmabuf_binding_get(binding))\n \t\t\tbinding = NULL;\n@@ -529,16 +635,19 @@ static void mp_dmabuf_devmem_uninstall(void *mp_priv,\n \tunsigned long xa_idx;\n \n \txa_for_each(\u0026binding-\u003ebound_rxqs, xa_idx, bound_rxq) {\n-\t\tif (bound_rxq == rxq) {\n-\t\t\txa_erase(\u0026binding-\u003ebound_rxqs, xa_idx);\n-\t\t\tif (xa_empty(\u0026binding-\u003ebound_rxqs)) {\n-\t\t\t\tmutex_lock(\u0026binding-\u003elock);\n-\t\t\t\tASSERT_EXCLUSIVE_WRITER(binding-\u003edev);\n-\t\t\t\tWRITE_ONCE(binding-\u003edev, NULL);\n-\t\t\t\tmutex_unlock(\u0026binding-\u003elock);\n-\t\t\t}\n-\t\t\tbreak;\n+\t\tif (bound_rxq != rxq)\n+\t\t\tcontinue;\n+\n+\t\txa_erase(\u0026binding-\u003ebound_rxqs, xa_idx);\n+\t\tif (xa_empty(\u0026binding-\u003ebound_rxqs)) {\n+\t\t\tmutex_lock(\u0026binding-\u003elock);\n+\t\t\tASSERT_EXCLUSIVE_WRITER(binding-\u003edev);\n+\t\t\tWRITE_ONCE(binding-\u003edev, NULL);\n+\t\t\tif (!rxq-\u003elease)\n+\t\t\t\tnet_devmem_dmabuf_binding_unmap(binding);\n+\t\t\tmutex_unlock(\u0026binding-\u003elock);\n \t\t}\n+\t\tbreak;\n \t}\n }\n \ndiff --git a/net/core/devmem.h b/net/core/devmem.h\nindex 4a293a7d1149c..ce069e1736416 100644\n--- a/net/core/devmem.h\n+++ b/net/core/devmem.h\n@@ -27,7 +27,7 @@ struct net_devmem_dmabuf_binding {\n \t */\n \tvoid *vdev;\n \tstruct gen_pool *chunk_pool;\n-\t/* Protect dev */\n+\t/* Protect dev, attachment and sgt */\n \tstruct mutex lock;\n \n \t/* The user holds a ref (via the netlink API) for as long as they want\n@@ -38,7 +38,9 @@ struct net_devmem_dmabuf_binding {\n \t *\n \t * The binding undos itself and unmaps the underlying dmabuf once all\n \t * those refs are dropped and the binding is no longer desired or in\n-\t * use.\n+\t * use. RX bindings are unmapped earlier, once they are no longer\n+\t * installed on any queue. TX bindings are unmapped earlier if their\n+\t * device is unregistered.\n \t *\n \t * net_devmem_get_net_iov() on dmabuf net_iovs will increment this\n \t * reference, making sure that the binding remains alive until all the\n@@ -100,6 +102,7 @@ net_devmem_bind_dmabuf(struct net_device *dev, void *vdev,\n \t\t       struct netlink_ext_ack *extack);\n struct net_devmem_dmabuf_binding *net_devmem_lookup_dmabuf(u32 id);\n void net_devmem_unbind_dmabuf(struct net_devmem_dmabuf_binding *binding);\n+void net_devmem_dev_unregister(struct net_device *dev);\n int net_devmem_bind_dmabuf_to_queue(struct net_device *dev, u32 rxq_idx,\n \t\t\t\t    struct net_devmem_dmabuf_binding *binding,\n \t\t\t\t    struct netlink_ext_ack *extack);\n@@ -196,6 +199,10 @@ net_devmem_unbind_dmabuf(struct net_devmem_dmabuf_binding *binding)\n {\n }\n \n+static inline void net_devmem_dev_unregister(struct net_device *dev)\n+{\n+}\n+\n static inline int\n net_devmem_bind_dmabuf_to_queue(struct net_device *dev, u32 rxq_idx,\n \t\t\t\tstruct net_devmem_dmabuf_binding *binding,\ndiff --git a/net/core/netdev-genl.c b/net/core/netdev-genl.c\nindex 33b9f4eb9565a..b04e22937b789 100644\n--- a/net/core/netdev-genl.c\n+++ b/net/core/netdev-genl.c\n@@ -1488,6 +1488,7 @@ static int netdev_genl_netdevice_event(struct notifier_block *nb,\n \t\tbreak;\n \tcase NETDEV_UNREGISTER:\n \t\tnetdev_lock(netdev);\n+\t\tnet_devmem_dev_unregister(netdev);\n \t\tnetdev_genl_dev_notify(netdev, NETDEV_CMD_DEV_DEL_NTF);\n \t\tnetdev_unlock(netdev);\n \t\tbreak;\ndiff --git a/net/core/netdev_rx_queue.c b/net/core/netdev_rx_queue.c\nindex 00a7011eb4d57..a7261fedd5e76 100644\n--- a/net/core/netdev_rx_queue.c\n+++ b/net/core/netdev_rx_queue.c\n@@ -275,8 +275,8 @@ int netif_mp_open_rxq(struct net_device *dev, unsigned int rxq_idx,\n \treturn ret;\n }\n \n-static void __netif_mp_close_rxq(struct net_device *dev, unsigned int rxq_idx,\n-\t\t\t\t const struct pp_memory_provider_params *old_p)\n+static int __netif_mp_close_rxq(struct net_device *dev, unsigned int rxq_idx,\n+\t\t\t\tconst struct pp_memory_provider_params *old_p)\n {\n \tstruct netdev_queue_config qcfg[2];\n \tstruct netdev_rx_queue *rxq;\n@@ -289,35 +289,41 @@ static void __netif_mp_close_rxq(struct net_device *dev, unsigned int rxq_idx,\n \t */\n \tif (dev-\u003ereg_state \u003e NETREG_REGISTERED \u0026\u0026\n \t    !rxq-\u003emp_params.mp_ops)\n-\t\treturn;\n+\t\treturn 0;\n \n \tif (WARN_ON_ONCE(rxq-\u003emp_params.mp_ops != old_p-\u003emp_ops ||\n \t\t\t rxq-\u003emp_params.mp_priv != old_p-\u003emp_priv))\n-\t\treturn;\n+\t\treturn -EINVAL;\n \n \tnetdev_queue_config(dev, rxq_idx, \u0026qcfg[0]);\n \tmemset(\u0026rxq-\u003emp_params, 0, sizeof(rxq-\u003emp_params));\n \tnetdev_queue_config(dev, rxq_idx, \u0026qcfg[1]);\n \n \terr = netdev_rx_queue_reconfig(dev, rxq_idx, \u0026qcfg[0], \u0026qcfg[1]);\n-\tWARN_ON(err \u0026\u0026 err != -ENETDOWN);\n+\tif (err == -ENETDOWN)\n+\t\treturn 0;\n+\tWARN_ON(err);\n+\treturn err;\n }\n \n-void netif_mp_close_rxq(struct net_device *dev, unsigned int rxq_idx,\n-\t\t\tconst struct pp_memory_provider_params *old_p)\n+int netif_mp_close_rxq(struct net_device *dev, unsigned int rxq_idx,\n+\t\t       const struct pp_memory_provider_params *old_p)\n {\n+\tint ret;\n+\n \tif (WARN_ON_ONCE(rxq_idx \u003e= dev-\u003ereal_num_rx_queues))\n-\t\treturn;\n+\t\treturn -EINVAL;\n \tif (!netif_rxq_is_leased(dev, rxq_idx))\n \t\treturn __netif_mp_close_rxq(dev, rxq_idx, old_p);\n \n \tif (!__netif_get_rx_queue_lease(\u0026dev, \u0026rxq_idx, NETIF_VIRT_TO_PHYS)) {\n \t\tWARN_ON_ONCE(1);\n-\t\treturn;\n+\t\treturn -EINVAL;\n \t}\n \tnetdev_lock(dev);\n-\t__netif_mp_close_rxq(dev, rxq_idx, old_p);\n+\tret = __netif_mp_close_rxq(dev, rxq_idx, old_p);\n \tnetdev_unlock(dev);\n+\treturn ret;\n }\n \n void __netif_mp_uninstall_rxq(struct netdev_rx_queue *rxq,\ndiff --git a/tools/testing/selftests/net/config b/tools/testing/selftests/net/config\nindex 737e7e6327b37..1e21f0055a2c0 100644\n--- a/tools/testing/selftests/net/config\n+++ b/tools/testing/selftests/net/config\n@@ -11,6 +11,7 @@ CONFIG_CRYPTO_ARIA=y\n CONFIG_CRYPTO_CHACHA20POLY1305=m\n CONFIG_CRYPTO_SHA1=y\n CONFIG_CRYPTO_SM4_GENERIC=y\n+CONFIG_DEBUG_FS=y\n CONFIG_DEBUG_INFO_BTF=y\n CONFIG_DEBUG_INFO_BTF_MODULES=n\n CONFIG_DUMMY=y\n@@ -76,6 +77,7 @@ CONFIG_NET_CLS_BPF=m\n CONFIG_NET_CLS_FLOWER=m\n CONFIG_NET_CLS_MATCHALL=m\n CONFIG_NET_CLS_U32=m\n+CONFIG_NET_DEVMEM=y\n CONFIG_NETDEVSIM=m\n CONFIG_NET_DROP_MONITOR=m\n CONFIG_NETFILTER=y\n@@ -120,6 +122,7 @@ CONFIG_OPENVSWITCH=m\n CONFIG_PAGE_POOL_STATS=y\n CONFIG_PSAMPLE=m\n CONFIG_RPS=y\n+CONFIG_SYNC_FILE=y\n CONFIG_SYN_COOKIES=y\n CONFIG_SYSCTL=y\n CONFIG_SYSFS=y\n@@ -131,6 +134,7 @@ CONFIG_TEST_BPF=m\n CONFIG_TLS=m\n CONFIG_TRACEPOINTS=y\n CONFIG_TUN=y\n+CONFIG_UDMABUF=y\n CONFIG_USER_NS=y\n CONFIG_VETH=y\n CONFIG_VLAN_8021Q=y\ndiff --git a/tools/testing/selftests/net/nl_netdev.py b/tools/testing/selftests/net/nl_netdev.py\nindex ceb44c8e1fec5..988878b71358c 100755\n--- a/tools/testing/selftests/net/nl_netdev.py\n+++ b/tools/testing/selftests/net/nl_netdev.py\n@@ -6,10 +6,14 @@ Tests for the netdev netlink family.\n \"\"\"\n \n import errno\n+import fcntl\n+import mmap\n+import os\n+import struct\n from os import system\n-from lib.py import ksft_run, ksft_exit\n+from lib.py import ksft_run, ksft_exit, KsftFailEx, KsftSkipEx\n from lib.py import ksft_eq, ksft_ge, ksft_ne, ksft_raises, ksft_busy_wait\n-from lib.py import NetdevFamily, NetdevSimDev, NlError, defer, ip\n+from lib.py import EthtoolFamily, NetdevFamily, NetdevSimDev, NlError, defer, ip\n \n \n def empty_check(nf) -\u003e None:\n@@ -366,6 +370,119 @@ def page_pool_stats_ifindex_check(nf) -\u003e None:\n     ksft_eq(cm.exception.nl_msg.extack['bad-attr'], '.info.id')\n \n \n+def _udmabuf_create_ioctl() -\u003e int:\n+    \"\"\"Return UDMABUF_CREATE, _IOW('u', 0x42, struct udmabuf_create).\"\"\"\n+    machine = os.uname().machine\n+    if machine.startswith((\"alpha\", \"mips\", \"ppc\", \"powerpc\", \"sparc\")):\n+        ioc_write = 4 \u003c\u003c 29\n+    elif machine.startswith(\"parisc\"):\n+        ioc_write = 2 \u003c\u003c 30\n+    else:\n+        ioc_write = 1 \u003c\u003c 30\n+    return ioc_write | 24 \u003c\u003c 16 | ord(\"u\") \u003c\u003c 8 | 0x42\n+\n+\n+def _create_udmabuf(num_pages=64) -\u003e int:\n+    \"\"\"Create a sealed memfd-backed udmabuf fd for devmem tests.\"\"\"\n+    if not os.path.exists(\"/dev/udmabuf\"):\n+        raise KsftSkipEx(\"/dev/udmabuf is not available\")\n+\n+    size = num_pages * mmap.PAGESIZE\n+    memfd = os.memfd_create(\"devmem-ksft\", os.MFD_ALLOW_SEALING)\n+    try:\n+        os.ftruncate(memfd, size)\n+        fcntl.fcntl(memfd, fcntl.F_ADD_SEALS, fcntl.F_SEAL_SHRINK)\n+        devfd = os.open(\"/dev/udmabuf\", os.O_RDWR)\n+        try:\n+            req = bytearray(struct.pack(\"IIQQ\", memfd, 0, 0, size))\n+            return fcntl.ioctl(devfd, _udmabuf_create_ioctl(), req)\n+        finally:\n+            os.close(devfd)\n+    finally:\n+        os.close(memfd)\n+\n+\n+def _dmabuf_attach_count(dmabuf_fd) -\u003e int:\n+    \"\"\"Return the number of devices attached to dmabuf_fd, from debugfs.\"\"\"\n+    path = \"/sys/kernel/debug/dma_buf/bufinfo\"\n+    try:\n+        with open(path, \"r\", encoding=\"utf-8\") as f:\n+            lines = f.read().splitlines()\n+    except OSError as e:\n+        raise KsftSkipEx(f\"{path} is not readable: {e}\") from e\n+\n+    ino = os.fstat(dmabuf_fd).st_ino\n+    found = False\n+    for line in lines:\n+        fields = line.split(\"\\t\")\n+        if not found:\n+            found = (len(fields) == 7 and fields[5].isdigit() and\n+                     int(fields[5]) == ino)\n+        elif line.startswith(\"Total \"):\n+            return int(line.split()[1])\n+    raise KsftFailEx(f\"dma-buf {ino} not found in {path}\")\n+\n+\n+def _devmem_bind(bind, attrs) -\u003e None:\n+    \"\"\"Run a devmem bind op, skip the test if the device can't do devmem.\"\"\"\n+    try:\n+        bind(attrs)\n+    except NlError as e:\n+        if e.error == errno.EOPNOTSUPP:\n+            raise KsftSkipEx(f\"devmem binding not supported: {e}\") from e\n+        raise\n+\n+\n+def devmem_bind_rx_unregister_check(_nf) -\u003e None:\n+    \"\"\"Verify RX devmem bindings detach dma_buf synchronously on netdev unregister.\"\"\"\n+    dmabuf_fd = _create_udmabuf()\n+    defer(os.close, dmabuf_fd)\n+    nf_priv = NetdevFamily()\n+    defer(nf_priv.close)\n+    nsimdev = NetdevSimDev(queue_count=2)\n+    defer(nsimdev.remove)\n+    nsim = nsimdev.nsims[0]\n+\n+    ip(f\"link set dev {nsim.ifname} up\")\n+    with EthtoolFamily() as ef:\n+        ef.rings_set({\"header\": {\"dev-index\": nsim.ifindex},\n+                      \"tcp-data-split\": \"enabled\"})\n+    _devmem_bind(nf_priv.bind_rx, {\n+        \"ifindex\": nsim.ifindex,\n+        \"fd\": dmabuf_fd,\n+        \"queues\": [{\"id\": 1, \"type\": \"rx\"}],\n+    })\n+    ksft_ge(_dmabuf_attach_count(dmabuf_fd), 1)\n+\n+    nsimdev.remove()\n+    ksft_eq(_dmabuf_attach_count(dmabuf_fd), 0)\n+\n+    nf_priv.close()\n+\n+\n+def devmem_bind_tx_unregister_check(_nf) -\u003e None:\n+    \"\"\"Verify TX devmem bindings detach cleanly on netdev unregister without UAF.\"\"\"\n+    dmabuf_fd = _create_udmabuf()\n+    defer(os.close, dmabuf_fd)\n+    nf_priv = NetdevFamily()\n+    defer(nf_priv.close)\n+    nsimdev = NetdevSimDev(queue_count=2)\n+    defer(nsimdev.remove)\n+    nsim = nsimdev.nsims[0]\n+\n+    ip(f\"link set dev {nsim.ifname} up\")\n+    _devmem_bind(nf_priv.bind_tx, {\n+        \"ifindex\": nsim.ifindex,\n+        \"fd\": dmabuf_fd,\n+    })\n+    ksft_ge(_dmabuf_attach_count(dmabuf_fd), 1)\n+\n+    nsimdev.remove()\n+    ksft_eq(_dmabuf_attach_count(dmabuf_fd), 0)\n+\n+    nf_priv.close()\n+\n+\n def main() -\u003e None:\n     \"\"\" Ksft boiler plate main \"\"\"\n     nf = NetdevFamily()\n@@ -379,7 +496,9 @@ def main() -\u003e None:\n               page_pool_check,\n               page_pool_dump_ifindex,\n               page_pool_ifindex_leak_check,\n-              page_pool_stats_ifindex_check\n+              page_pool_stats_ifindex_check,\n+              devmem_bind_rx_unregister_check,\n+              devmem_bind_tx_unregister_check\n               ],\n              args=(nf, ))\n     ksft_exit()\n"
}

2/1 2026/10/15 21:30 agent patch-evaluator
2m Model:
core

Args:
null

Results:
{
  "EnableConfigs": [
    "NET_DEVMEM",
    "NETDEVSIM",
    "UDMABUF",
    "SYNC_FILE",
    "INET_PSP",
    "DEBUG_FS"
  ],
  "FocusSymbols": [
    "net_devmem_dev_unregister",
    "net_devmem_unbind_dmabuf",
    "net_devmem_bind_dmabuf",
    "net_devmem_bind_dmabuf_to_queue",
    "net_devmem_lookup_dmabuf",
    "__net_devmem_dmabuf_binding_free",
    "netdev_nl_bind_rx_doit",
    "netdev_nl_bind_tx_doit",
    "netif_mp_close_rxq",
    "nsim_bus_dev_new",
    "nsim_do_psp"
  ],
  "Reasoning": "The patch series modifies core networking memory provider / devmem TCP logic (net/core/devmem.c, net/core/netdev_rx_queue.c, and net/core/netdev-genl.c) as well as the netdevsim driver. It adjusts dmabuf unmapping lifecycles, adds synchronization upon netdev unregistration, changes return types and error handling in queue close callbacks, and adds unreadable netmem support and DMA coercion to netdevsim. These code paths are fully reachable in virtualized environments via netlink (netdev family), TCP sendmsg, and netdevsim/udmabuf devices. Fuzzing is warranted to uncover potential concurrency issues, unregister/unbind race conditions, or locking violations.",
  "WorthFuzzing": true
}

Instruction:
You are an expert Linux kernel maintainer and security engineer.
Your job is to review a provided patch series and evaluate whether it warrants fuzzing with syzkaller.

IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on internal assumptions. You must actively use your code access
tools to inspect the actual source code, callers, and surrounding context.

================================================================================
1. CORE TRIAGE PHILOSOPHY
================================================================================
The goal of patch fuzzing is to discover crashes, regressions, exposed latent bugs,
and newly triggered assertions introduced by the patch series.

- REACHABILITY IS THE PRIMARY GATE:
  Fuzzing can only discover bugs in code that can actually execute in standard virtualized
  environments (GCE or QEMU, utilizing software-emulated devices like USB gadgets, netdev, tun/tap).
  If the modified code is structurally unreachable (see Section 2), it MUST NOT be fuzzed,
  regardless of whether it adds assertions or complex logic.

- DO NOT BLINDLY TRUST "NO FUNCTIONAL CHANGE" (NFCI) OR "REFACTORING" CLAIMS:
  Patch authors routinely label changes as "cleanups", "refactorings", or state
  "No functional change intended". Do NOT take these claims at face value.
  Code refactorings that rearrange logic, introduce helper functions, or alter state management
  in core subsystems frequently introduce subtle semantic shifts or uncover latent kernel bugs.
  If reachable executable code is modified or refactored, it MUST be fuzzed.

- NEW OR MODIFIED ASSERTIONS IN REACHABLE CODE MUST BE FUZZED:
  When a patch introduces or modifies runtime checks or assertions (e.g., WARN_ON*, VM_WARN_ON*,
  BUG_ON*, lockdep_assert*) in reachable code paths, it enforces new or stricter invariants.
  Even if the author believes the invariant always holds, fuzzing is essential to verify whether
  an unusual sequence of operations can violate it.

================================================================================
2. WHEN TO RETURN WorthFuzzing=false (NEGATIVE CRITERIA)
================================================================================
Return WorthFuzzing=false ONLY IF all modified code falls strictly into one or more of these categories:

- Non-kernel and non-executable changes:
  * Modifications to Documentation/, comments, or spelling fixes.
  * User-space directories, self-tests, samples, or scripts (e.g., tools/, samples/, scripts/, usr/)
    that do not affect the compiled kernel image (vmlinux) or kernel modules.
  * Purely decorative logging (e.g., message strings in pr_err, printk, dev_info) or tracepoints
    that do not alter control flow or data structures.
  * Build system or Kconfig changes that do not alter compiled C logic.
- Structurally unreachable hardware:
  * Vendor-specific PCIe switches, SmartNICs, or GPU drivers (e.g., mlxsw, pds_core, qed,
    ionic, amdgpu) requiring physical ASIC/PCIe cards not emulated in standard QEMU.
- Unreachable execution paths:
  * Driver teardown callbacks (.remove, .shutdown, pci_unregister_driver) executed only during
    physical PCI hot-unplug or manual sysfs driver unbinding.
  * Code paths exclusive to architectures other than the target architecture.

================================================================================
3. WHEN TO RETURN WorthFuzzing=true (POSITIVE CRITERIA)
================================================================================
Return WorthFuzzing=true whenever the patch touches reachable executable code, including:
- Core Subsystems:
  * Any logic modifications in memory management (mm/), synchronization/locking (kernel/locking/),
    BPF, scheduler, core networking, VFS, or syscall handling.
- Refactorings and Code Cleanups:
  * Any restructuring of reachable data structures, helper abstractions, or algorithm flows.
- Runtime Assertions and Defensive Checks:
  * Any introduction or alteration of assertions (WARN_ON*, VM_WARN_ON*, BUG_ON*, etc.) in reachable paths.
- Reachable Drivers and Protocols:
  * Drivers accessible via virtual buses (virtio, USB gadget, loopback, netlink, binder, sockets, etc.).

================================================================================
4. EXTRACTING FocusSymbols (PREVENTING DILUTION)
================================================================================
When WorthFuzzing=true, you must extract specific kernel functions into FocusSymbols to guide the fuzzer:

- AVOID UBIQUITOUS LIFECYCLE HOT-PATHS:
  Do NOT list generic, ubiquitous functions called by almost every program in the corpus
  (including, but not limited to: general memory allocators and deallocators, page fault
  and trap handlers, or core synchronization primitives; this is not an exhaustive list).
  Listing ubiquitous functions causes the fuzzer to classify thousands of unrelated tests as "focused",
  which severely dilutes fuzzing effort away from the actual changes.

- TARGET SPECIFIC FEATURE LOGIC AND ENTRYPOINTS:
  List functions that specifically implement the logic being added or altered, or direct API entrypoints
  for the subsystem feature under review.

- HANDLING STATIC INLINE FUNCTIONS IN HEADERS (.h):
  Compiler-inlined static functions (such as static inlines in mm/*.h or include/linux/*.h) lack
  distinct symbol addresses in vmlinux and cannot be targeted directly by symbol coverage filters.
  If the changes are primarily in static inline helpers, identify non-static, feature-specific caller
  functions in .c files that exercise them (avoiding ubiquitous lifecycle wrappers).

================================================================================
5. IDENTIFYING EnableConfigs
================================================================================
Identify any specific CONFIG_ options required to properly compile and reach the modified code:
- Inspect Kconfig files and #ifdef guards; do not make assumptions.
- Check "depends on" lines in Kconfig to include any non-standard parent subsystem configs needed.
- Strip any 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4").
Prefer calling several tools at the same time to save round-trips.


Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.

Prompt:
Target architecture: amd64

For your convenience, here is the diff of the changes:
commit 180d7add7fd4a84a7be8a0708cc6ca75fd8c54b1
Author: syz-cluster <triage@syzkaller.com>
Date:   Thu Oct 15 21:30:26 2026 +0000

    syz-cluster: applied patch under review

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
diff --git a/include/net/page_pool/memory_provider.h b/include/net/page_pool/memory_provider.h
index 255ce4cfd9755..a999a9b28bc70 100644
--- a/include/net/page_pool/memory_provider.h
+++ b/include/net/page_pool/memory_provider.h
@@ -26,8 +26,8 @@ void net_mp_niov_clear_page_pool(struct net_iov *niov);
 int netif_mp_open_rxq(struct net_device *dev, unsigned int rxq_idx,
 		      const struct pp_memory_provider_params *p,
 		      struct netlink_ext_ack *extack);
-void netif_mp_close_rxq(struct net_device *dev, unsigned int rxq_idx,
-			const struct pp_memory_provider_params *old_p);
+int netif_mp_close_rxq(struct net_device *dev, unsigned int rxq_idx,
+		       const struct pp_memory_provider_params *old_p);
 
 /**
   * net_mp_netmem_place_in_cache() - give a netmem to a page pool
diff --git a/net/core/devmem.c b/net/core/devmem.c
index f4d60654ce7fd..d33a092c6e4bb 100644
--- a/net/core/devmem.c
+++ b/net/core/devmem.c
@@ -12,6 +12,7 @@
 #include <linux/mm.h>
 #include <linux/netdevice.h>
 #include <linux/types.h>
+#include <net/netdev_lock.h>
 #include <net/netdev_queues.h>
 #include <net/netdev_rx_queue.h>
 #include <net/page_pool/helpers.h>
@@ -20,6 +21,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 +29,7 @@
 /* Device memory support */
 
 static DEFINE_XARRAY_FLAGS(net_devmem_dmabuf_bindings, XA_FLAGS_ALLOC1);
+#define NET_DEVMEM_BINDING_LIVE XA_MARK_1
 
 static const struct memory_provider_ops dmabuf_devmem_ops;
 
@@ -58,9 +61,26 @@ 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);
+	netdevice_tracker dev_tracker;
+	struct net_device *dev;
 
 	size_t size, avail;
 
@@ -74,9 +94,23 @@ 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(&binding->lock);
+	dev = binding->dev;
+	if (dev)
+		netdev_hold(dev, &dev_tracker, GFP_KERNEL);
+	mutex_unlock(&binding->lock);
+
+	if (dev)
+		netdev_lock(dev);
+	mutex_lock(&binding->lock);
+	net_devmem_dmabuf_binding_unmap(binding);
+	mutex_unlock(&binding->lock);
+	xa_erase(&net_devmem_dmabuf_bindings, binding->id);
+	if (dev) {
+		netdev_unlock(dev);
+		netdev_put(dev, &dev_tracker);
+	}
+
 	dma_buf_put(binding->dmabuf);
 	xa_destroy(&binding->bound_rxqs);
 	percpu_ref_exit(&binding->ref);
@@ -128,11 +162,13 @@ void net_devmem_unbind_dmabuf(struct net_devmem_dmabuf_binding *binding)
 	struct netdev_rx_queue *rxq;
 	unsigned long xa_idx;
 	unsigned int rxq_idx;
+	bool busy = false;
 
-	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();
 
@@ -147,12 +183,82 @@ void net_devmem_unbind_dmabuf(struct net_devmem_dmabuf_binding *binding)
 
 		rxq_idx = get_netdev_rx_queue_index(rxq);
 
-		netif_mp_close_rxq(binding->dev, rxq_idx, &mp_params);
+		if (netif_mp_close_rxq(binding->dev, rxq_idx, &mp_params))
+			busy = true;
+	}
+
+	if (binding->direction == DMA_FROM_DEVICE && binding->dev && !busy) {
+		mutex_lock(&binding->lock);
+		WRITE_ONCE(binding->dev, NULL);
+		net_devmem_dmabuf_binding_unmap(binding);
+		mutex_unlock(&binding->lock);
 	}
 
 	percpu_ref_kill(&binding->ref);
 }
 
+static bool
+net_devmem_dev_unregister_rx(struct net_devmem_dmabuf_binding *binding,
+			     unsigned long xa_idx, struct net_device *dev)
+{
+	return binding->direction == DMA_FROM_DEVICE &&
+	       READ_ONCE(binding->dev) == dev &&
+	       !xa_get_mark(&net_devmem_dmabuf_bindings, xa_idx,
+			    NET_DEVMEM_BINDING_LIVE);
+}
+
+static bool
+net_devmem_dev_unregister_tx(struct net_devmem_dmabuf_binding *binding,
+			     unsigned long xa_idx, struct net_device *dev)
+{
+	if (binding->direction != DMA_TO_DEVICE)
+		return false;
+
+	if (READ_ONCE(binding->vdev) == dev)
+		WRITE_ONCE(binding->vdev, NULL);
+
+	if (READ_ONCE(binding->dev) != dev)
+		return false;
+
+	__xa_clear_mark(&net_devmem_dmabuf_bindings, xa_idx,
+			NET_DEVMEM_BINDING_LIVE);
+	return true;
+}
+
+static void net_devmem_dev_detach(struct net_devmem_dmabuf_binding *binding,
+				  struct net_device *dev)
+{
+	mutex_lock(&binding->lock);
+	WRITE_ONCE(binding->dev, NULL);
+	WRITE_ONCE(binding->vdev, 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;
+
+	netdev_assert_locked(dev);
+
+	if (dev->reg_state != NETREG_UNREGISTERING)
+		return;
+
+	xa_lock(&net_devmem_dmabuf_bindings);
+	xa_for_each(&net_devmem_dmabuf_bindings, xa_idx, binding) {
+		if (!net_devmem_dev_unregister_rx(binding, xa_idx, dev) &&
+		    !net_devmem_dev_unregister_tx(binding, xa_idx, dev))
+			continue;
+
+		xa_unlock(&net_devmem_dmabuf_bindings);
+		net_devmem_dev_detach(binding, dev);
+		xa_lock(&net_devmem_dmabuf_bindings);
+	}
+	xa_unlock(&net_devmem_dmabuf_bindings);
+}
+
 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)
@@ -168,20 +274,16 @@ int net_devmem_bind_dmabuf_to_queue(struct net_device *dev, u32 rxq_idx,
 	if (binding->niov_shift != PAGE_SHIFT)
 		mp_params.rx_page_size = 1U << binding->niov_shift;
 
-	err = netif_mp_open_rxq(dev, rxq_idx, &mp_params, extack);
-	if (err)
-		return err;
-
 	rxq = __netif_get_rx_queue(dev, rxq_idx);
 	err = xa_alloc(&binding->bound_rxqs, &xa_idx, rxq, xa_limit_32b,
 		       GFP_KERNEL);
 	if (err)
-		goto err_close_rxq;
+		return err;
 
-	return 0;
+	err = netif_mp_open_rxq(dev, rxq_idx, &mp_params, extack);
+	if (err)
+		xa_erase(&binding->bound_rxqs, xa_idx);
 
-err_close_rxq:
-	netif_mp_close_rxq(dev, rxq_idx, &mp_params);
 	return err;
 }
 
@@ -331,6 +433,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 +463,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 +635,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..ce069e1736416 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,9 @@ 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. RX bindings are unmapped earlier, once they are no longer
+	 * installed on any queue. TX bindings are unmapped earlier if their
+	 * device 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 +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_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 +199,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;
diff --git a/net/core/netdev_rx_queue.c b/net/core/netdev_rx_queue.c
index 00a7011eb4d57..a7261fedd5e76 100644
--- a/net/core/netdev_rx_queue.c
+++ b/net/core/netdev_rx_queue.c
@@ -275,8 +275,8 @@ int netif_mp_open_rxq(struct net_device *dev, unsigned int rxq_idx,
 	return ret;
 }
 
-static void __netif_mp_close_rxq(struct net_device *dev, unsigned int rxq_idx,
-				 const struct pp_memory_provider_params *old_p)
+static int __netif_mp_close_rxq(struct net_device *dev, unsigned int rxq_idx,
+				const struct pp_memory_provider_params *old_p)
 {
 	struct netdev_queue_config qcfg[2];
 	struct netdev_rx_queue *rxq;
@@ -289,35 +289,41 @@ static void __netif_mp_close_rxq(struct net_device *dev, unsigned int rxq_idx,
 	 */
 	if (dev->reg_state > NETREG_REGISTERED &&
 	    !rxq->mp_params.mp_ops)
-		return;
+		return 0;
 
 	if (WARN_ON_ONCE(rxq->mp_params.mp_ops != old_p->mp_ops ||
 			 rxq->mp_params.mp_priv != old_p->mp_priv))
-		return;
+		return -EINVAL;
 
 	netdev_queue_config(dev, rxq_idx, &qcfg[0]);
 	memset(&rxq->mp_params, 0, sizeof(rxq->mp_params));
 	netdev_queue_config(dev, rxq_idx, &qcfg[1]);
 
 	err = netdev_rx_queue_reconfig(dev, rxq_idx, &qcfg[0], &qcfg[1]);
-	WARN_ON(err && err != -ENETDOWN);
+	if (err == -ENETDOWN)
+		return 0;
+	WARN_ON(err);
+	return err;
 }
 
-void netif_mp_close_rxq(struct net_device *dev, unsigned int rxq_idx,
-			const struct pp_memory_provider_params *old_p)
+int netif_mp_close_rxq(struct net_device *dev, unsigned int rxq_idx,
+		       const struct pp_memory_provider_params *old_p)
 {
+	int ret;
+
 	if (WARN_ON_ONCE(rxq_idx >= dev->real_num_rx_queues))
-		return;
+		return -EINVAL;
 	if (!netif_rxq_is_leased(dev, rxq_idx))
 		return __netif_mp_close_rxq(dev, rxq_idx, old_p);
 
 	if (!__netif_get_rx_queue_lease(&dev, &rxq_idx, NETIF_VIRT_TO_PHYS)) {
 		WARN_ON_ONCE(1);
-		return;
+		return -EINVAL;
 	}
 	netdev_lock(dev);
-	__netif_mp_close_rxq(dev, rxq_idx, old_p);
+	ret = __netif_mp_close_rxq(dev, rxq_idx, old_p);
 	netdev_unlock(dev);
+	return ret;
 }
 
 void __netif_mp_uninstall_rxq(struct netdev_rx_queue *rxq,
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()

3/2 2026/10/15 21:30 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 12462
							output: 61
							thoughts: 1044
						

Thoughts:
**Fuzzing Target Identification: A Networking Deep Dive**

Okay, here's what I'm thinking. This patch looks promising for fuzzing, let's determine if it's worth it, figure out the configurations, and identify potential focus symbols.

First, I see the following files have been modified: `drivers/net/netdevsim/bus.c`, `drivers/net/netdevsim/netdev.c`, `drivers/net/netdevsim/psp.c`, `include/net/page_pool/memory_provider.h`, `net/core/devmem.c`, `net/core/devmem.h`, `net/core/netdev-genl.c`, `net/core/netdev_rx_queue.c`, and a directory in `tools/testing/selftests`.

The significant areas are `net/core/devmem.c`, `net/core/netdev_rx_queue.c`, `net/core/netdev-genl.c`, and the `drivers/net/netdevsim/*` directory. Specifically, `netdevsim` is a key component, commonly used in testing and syzkaller fuzzing. Devmem is crucial here as it deals with TCP/DMA buffer memory provider, which is part of core networking. This is reachable in a standard QEMU environment using netlink and netdev genl family, or through `netdevsim`.

Now, digging deeper into the patch:

*   In `net/core/devmem.c`, I see changes related to locking, unmapping, error handling, and the introduction of `net_devmem_dev_unregister` which gets called from `netdev_genl_netdevice_event` when `NETDEV_UNREGISTER` occurs. The order of operations in `net_devmem_bind_dmabuf_to_queue` has been changed, `xa_alloc` vs. `netif_mp_open_rxq`, `xa_find` has been added, and the uninstall function has changed.
*   `net/core/netdev_rx_queue.c` has changes, the return type of `__netif_mp_close_rxq` and `netif_mp_close_rxq` now returns an int.
*   `drivers/net/netdevsim/netdev.c` modifies the way page pools are created, sets various flags for page pool functionality like `PP_FLAG_ALLOW_UNREADABLE_NETMEM`, uses the bus dev for DMA, and sets up DMA mapping and synchronization. It also handles unreadable page pool scenarios with a lock.
*   In `drivers/net/netdevsim/psp.c`, it checks for the readability of skb fragments.

This is all core networking, devmem, and netdevsim functionality, which are ripe targets.

Can I reach this in QEMU? Yes, I can create `netdevsim` through `sysfs`, `debugfs`, `driverctl`, `RTNL` interactions, and netdev genl will allow me to interact via `NETDEV_CMD_BIND_RX`, `NETDEV_CMD_BIND_TX`, and importantly, the changes in `net_devmem_dev_unregister` linked to `NETDEV_UNREGISTER`. There are now added locks, altered dmabuf binding lifetime and unmapping, and assertions.

Given all of this, I'm confident this is worth fuzzing. I'd give it a `WorthFuzzing=true` tag.

Now, I'll need to check the Kconfig files and use `grepper` to see what configurations are necessary and to start identifying the best focus symbols.



4/2 2026/10/15 21:30 tool grepper
0m Args:
{
  "Expression": "config NET_DEVMEM|config NETDEVSIM",
  "PathPrefixes": [
    "net/core/Kconfig",
    "drivers/net/Kconfig",
    "drivers/net/netdevsim/Kconfig"
  ]
}

Results:
{
  "Output": "drivers/net/Kconfig=602=source \"drivers/net/hyperv/Kconfig\"\ndrivers/net/Kconfig-603-\ndrivers/net/Kconfig:604:config NETDEVSIM\ndrivers/net/Kconfig-605-\ttristate \"Simulated networking device\"\n"
}

5/2 2026/10/15 21:30 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 14455
							output: 29
							thoughts: 15
						

Thoughts:
Okay, here's my summary, tailored for an expert audience, as if it's my own thought process:

**Hunting Down CONFIG_NET_DEVMEM**

Alright, I need to get to the bottom of this. The first thing I'm going to do is locate the definition of `CONFIG_NET_DEVMEM`. I know it's a kernel configuration option, so the hunt begins. I'll need to use my usual tools – likely a combination of `find`, `grep`, and potentially some `kconfig` introspection, depending on how quickly I want to get the answer. I'll need to be mindful of cross-references and any potential conditional inclusions, since this is a kernel setting. Let's see what we can dig up.



6/2 2026/10/15 21:30 tool grepper
0m Args:
{
  "Expression": "config NET_DEVMEM",
  "PathPrefixes": [
    "net/"
  ]
}

Results:
{
  "Output": "net/Kconfig=66=config SKB_EXTENSIONS\n--\nnet/Kconfig-68-\nnet/Kconfig:69:config NET_DEVMEM\nnet/Kconfig-70-\tdef_bool y\n"
}

7/2 2026/10/15 21:30 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 14728
							output: 31
							thoughts: 21
						

Thoughts:
Here's my summary, as though I'm thinking through this process:

**Diving into the Kernel Configuration Landscape**

Okay, let's get down to brass tacks. I need to understand something related to the kernel configuration, and the likely spot to begin looking is the Kconfig file for the relevant subsystem. The natural first step is to zero in on `net/Kconfig`, it's the obvious starting point. I suspect that the key information I need will be found around line 69 of the file. Time to crack open the source and start digging. I'll need to use my usual command, probably `read-file`, to get the contents into my workspace. Let's see what secrets it holds!



8/2 2026/10/15 21:30 tool read-file
0m Args:
{
  "File": "net/Kconfig",
  "FirstLine": 60,
  "LineCount": 40
}

Results:
{
  "Contents": "  60:\tconfig NET_REDIRECT\n  61:\t\tbool\n  62:\t\n  63:\tconfig SKB_DECRYPTED\n  64:\t\tbool\n  65:\t\n  66:\tconfig SKB_EXTENSIONS\n  67:\t\tbool\n  68:\t\n  69:\tconfig NET_DEVMEM\n  70:\t\tdef_bool y\n  71:\t\tselect GENERIC_ALLOCATOR\n  72:\t\tdepends on DMA_SHARED_BUFFER\n  73:\t\tdepends on PAGE_POOL\n  74:\t\n  75:\tconfig NET_SHAPER\n  76:\t\tbool\n  77:\t\n  78:\tconfig NET_CRC32C\n  79:\t\tbool\n  80:\t\tselect CRC32\n  81:\t\n  82:\tmenu \"Networking options\"\n  83:\t\n  84:\tsource \"net/packet/Kconfig\"\n  85:\tsource \"net/psp/Kconfig\"\n  86:\tsource \"net/unix/Kconfig\"\n  87:\tsource \"net/tls/Kconfig\"\n  88:\tsource \"net/xfrm/Kconfig\"\n  89:\tsource \"net/iucv/Kconfig\"\n  90:\tsource \"net/smc/Kconfig\"\n  91:\tsource \"drivers/dibs/Kconfig\"\n  92:\tsource \"net/xdp/Kconfig\"\n  93:\t\n  94:\tconfig NET_HANDSHAKE\n  95:\t\tbool\n  96:\t\tdepends on SUNRPC || NVME_TARGET_TCP || NVME_TCP\n  97:\t\tdefault y\n  98:\t\n  99:\tconfig NET_HANDSHAKE_KUNIT_TEST\n"
}

9/2 2026/10/15 21:30 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 15381
							output: 28
							thoughts: 82
						

Thoughts:
Okay, here's my understanding, broken down as if these were my internal thoughts:

**Analyzing Kernel Configuration and Build Dependencies**

Alright, I see this `NET_DEVMEM` configuration option in the kernel config. The `def_bool y` is straightforward; it's enabled by default. Now, looking at the dependencies, it selects `GENERIC_ALLOCATOR`, which is interesting, and requires `DMA_SHARED_BUFFER` and `PAGE_POOL`. That gives me a good start in tracing where this functionality fits into the larger picture. So, it's obviously involved with memory allocation in the network device context, particularly when dealing with DMA and page pools.

Next, I need to understand how the associated source code, `net/core/devmem.c`, actually gets built. Let's delve into the `net/core/Makefile` to see what triggers the compilation of that specific file. This should outline the necessary build dependencies. Knowing the dependencies will make tracing why and when this particular functionality is utilized much simpler.



10/2 2026/10/15 21:30 tool grepper
0m Args:
{
  "Expression": "devmem",
  "PathPrefixes": [
    "net/core/Makefile"
  ]
}

Results:
{
  "Output": "net/core/Makefile=51=obj-$(CONFIG_NET_TEST) += net_test.o\nnet/core/Makefile:52:obj-$(CONFIG_NET_DEVMEM) += devmem.o\nnet/core/Makefile-53-obj-$(CONFIG_DEBUG_NET) += lock_debug.o\n"
}

11/2 2026/10/15 21:30 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 15797
							output: 34
							thoughts: 20
						

Thoughts:
Okay, here's my take on those thoughts, tailored for an expert audience:

**Investigating `drivers/net/Kconfig` and `NETDEVSIM`**

Right, so we're looking at network device simulation, and my immediate thought is to zero in on `drivers/net/Kconfig`. This is standard operating procedure, of course. I need to understand how `NETDEVSIM` (or whatever the relevant configuration symbol might be) is defined, what its dependencies are, and how it relates to the various simulated network devices that might be enabled. We need a proper understanding of the build-time configuration options, dependencies, and implications. I want to quickly grasp the conditional compilation blocks and the broader structure. I'm especially interested in whether `NETDEVSIM` is a top-level option or part of a more granular configuration hierarchy. Is there a chain of dependencies I need to untangle? Are there any obvious red flags in the dependencies, like conflicting options or overly broad requirements? I anticipate this Kconfig file will reveal the key to how these simulated devices are built and included.



12/2 2026/10/15 21:31 tool read-file
0m Args:
{
  "File": "drivers/net/Kconfig",
  "FirstLine": 603,
  "LineCount": 30
}

Results:
{
  "Contents": " 603:\t\n 604:\tconfig NETDEVSIM\n 605:\t\ttristate \"Simulated networking device\"\n 606:\t\tdepends on DEBUG_FS\n 607:\t\tdepends on INET\n 608:\t\tdepends on PSAMPLE || PSAMPLE=n\n 609:\t\tdepends on PTP_1588_CLOCK_MOCK || PTP_1588_CLOCK_MOCK=n\n 610:\t\tselect NET_DEVLINK\n 611:\t\tselect PAGE_POOL\n 612:\t\tselect NET_SHAPER\n 613:\t\thelp\n 614:\t\t  This driver is a developer testing tool and software model that can\n 615:\t\t  be used to test various control path networking APIs, especially\n 616:\t\t  HW-offload related.\n 617:\t\n 618:\t\t  To compile this driver as a module, choose M here: the module\n 619:\t\t  will be called netdevsim.\n 620:\t\n 621:\tconfig NET_FAILOVER\n 622:\t\ttristate \"Failover driver\"\n 623:\t\tselect FAILOVER\n 624:\t\thelp\n 625:\t\t  This provides an automated failover mechanism via APIs to create\n 626:\t\t  and destroy a failover master netdev and manages a primary and\n 627:\t\t  standby slave netdevs that get registered via the generic failover\n 628:\t\t  infrastructure. This can be used by paravirtual drivers to enable\n 629:\t\t  an alternate low latency datapath. It also enables live migration of\n 630:\t\t  a VM with direct attached VF by failing over to the paravirtual\n 631:\t\t  datapath when the VF is unplugged.\n 632:\t\n"
}

13/2 2026/10/15 21:31 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 16547
							output: 20
							thoughts: 36
						

Thoughts:
Okay, here's my take on those thoughts, framed as an expert's internal monologue:

**Investigating DMA Buffer Selection**

Alright, let's dive in. I need to understand how the system decides whether to use the `DMA_SHARED_BUFFER` versus, say, something in the `PAGE_POOL`. Clearly, they represent different memory allocation strategies for DMA operations, and I need to figure out the selection criteria. I'll start with the obvious: what triggers the use of `DMA_SHARED_BUFFER`? I suspect there's some kind of configuration setting, driver-specific flag, or resource constraint that tips the scales. Time to pull out `grepper` and see what the code's got to say about that. Let's find out how and where this decision is made.



14/2 2026/10/15 21:31 tool grepper
0m Args:
{
  "Expression": "config DMA_SHARED_BUFFER"
}

Results:
{
  "Output": "drivers/base/Kconfig=213=source \"drivers/base/regmap/Kconfig\"\ndrivers/base/Kconfig-214-\ndrivers/base/Kconfig:215:config DMA_SHARED_BUFFER\ndrivers/base/Kconfig-216-\tbool\n"
}

15/2 2026/10/15 21:31 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 16853
							output: 20
							thoughts: 87
						

Thoughts:
Here's my summary, as I see it:

**Investigating the `DMA_SHARED_BUFFER` Configuration**

Okay, so I'm looking at how `DMA_SHARED_BUFFER` is being enabled within the kernel. I see it's linked to `UDMABUF` and associated configurations. Specifically, in the network self-tests configuration (`tools/testing/selftests/net/config`), `UDMABUF` is set to "y", `NETDEVSIM` to "m", `NET_DEVMEM` to "y", and `DEBUG_FS` is also enabled. These are the kinds of configurations that can often indirectly control the allocation method used. I need to understand the relationship and identify what, exactly, is driving this `DMA_SHARED_BUFFER` selection. I need to trace the dependencies to see *why* and *how* `DMA_SHARED_BUFFER` is being chosen, and what the implications are. Let's dig in and figure out the exact selection path!



16/2 2026/10/15 21:31 tool grepper
0m Args:
{
  "Expression": "select DMA_SHARED_BUFFER"
}

Results:
{
  "Output": "Documentation/driver-api/dma-buf.rst=65=Any exporters or users of the dma-buf buffer sharing framework must have a\nDocumentation/driver-api/dma-buf.rst:66:'select DMA_SHARED_BUFFER' in their respective Kconfigs.\nDocumentation/driver-api/dma-buf.rst-67-\n--\ndrivers/accel/habanalabs/Kconfig=6=config DRM_ACCEL_HABANALABS\n--\ndrivers/accel/habanalabs/Kconfig-12-\tselect HWMON\ndrivers/accel/habanalabs/Kconfig:13:\tselect DMA_SHARED_BUFFER\ndrivers/accel/habanalabs/Kconfig-14-\tselect CRC32\n--\ndrivers/dma-buf/Kconfig=4=config SYNC_FILE\n--\ndrivers/dma-buf/Kconfig-6-\tdefault n\ndrivers/dma-buf/Kconfig:7:\tselect DMA_SHARED_BUFFER\ndrivers/dma-buf/Kconfig-8-\thelp\n--\ndrivers/dma-buf/Kconfig=52=config DMABUF_KUNIT_TEST\n--\ndrivers/dma-buf/Kconfig-54-\tdepends on KUNIT\ndrivers/dma-buf/Kconfig:55:\tselect DMA_SHARED_BUFFER\ndrivers/dma-buf/Kconfig-56-\tdefault KUNIT_ALL_TESTS\n--\ndrivers/dma-buf/Kconfig=60=menuconfig DMABUF_HEAPS\ndrivers/dma-buf/Kconfig-61-\tbool \"DMA-BUF Userland Memory Heaps\"\ndrivers/dma-buf/Kconfig:62:\tselect DMA_SHARED_BUFFER\ndrivers/dma-buf/Kconfig-63-\thelp\n--\ndrivers/gpu/drm/Kconfig=8=menuconfig DRM\n--\ndrivers/gpu/drm/Kconfig-13-\tselect I2C\ndrivers/gpu/drm/Kconfig:14:\tselect DMA_SHARED_BUFFER\ndrivers/gpu/drm/Kconfig-15-\tselect SYNC_FILE\n--\ndrivers/gpu/host1x/Kconfig=6=config TEGRA_HOST1X\n--\ndrivers/gpu/host1x/Kconfig-8-\tdepends on ARCH_TEGRA || COMPILE_TEST\ndrivers/gpu/host1x/Kconfig:9:\tselect DMA_SHARED_BUFFER\ndrivers/gpu/host1x/Kconfig-10-\tselect TEGRA_HOST1X_CONTEXT_BUS\n--\ndrivers/iio/Kconfig=15=config IIO_BUFFER\ndrivers/iio/Kconfig-16-\tbool \"Enable buffer support within IIO\"\ndrivers/iio/Kconfig:17:\tselect DMA_SHARED_BUFFER\ndrivers/iio/Kconfig-18-\thelp\n--\ndrivers/infiniband/Kconfig=2=menuconfig INFINIBAND\n--\ndrivers/infiniband/Kconfig-7-\tdepends on !ALPHA\ndrivers/infiniband/Kconfig:8:\tselect DMA_SHARED_BUFFER\ndrivers/infiniband/Kconfig-9-\tselect IRQ_POLL\n--\ndrivers/infiniband/Kconfig=44=config INFINIBAND_USER_MEM\n--\ndrivers/infiniband/Kconfig-47-\tdepends on MMU\ndrivers/infiniband/Kconfig:48:\tselect DMA_SHARED_BUFFER\ndrivers/infiniband/Kconfig-49-\tdefault y\n--\ndrivers/iommu/iommufd/Kconfig=51=config IOMMUFD_TEST\n--\ndrivers/iommu/iommufd/Kconfig-56-\tdepends on IOMMU_PT_AMDV1=y || IOMMUFD=IOMMU_PT_AMDV1\ndrivers/iommu/iommufd/Kconfig:57:\tselect DMA_SHARED_BUFFER\ndrivers/iommu/iommufd/Kconfig-58-\tselect IOMMUFD_DRIVER\n--\ndrivers/media/common/videobuf2/Kconfig=3=config VIDEOBUF2_CORE\ndrivers/media/common/videobuf2/Kconfig:4:\tselect DMA_SHARED_BUFFER\ndrivers/media/common/videobuf2/Kconfig-5-\ttristate\n--\ndrivers/media/common/videobuf2/Kconfig=13=config VIDEOBUF2_DMA_CONTIG\n--\ndrivers/media/common/videobuf2/Kconfig-16-\tselect VIDEOBUF2_MEMOPS\ndrivers/media/common/videobuf2/Kconfig:17:\tselect DMA_SHARED_BUFFER\ndrivers/media/common/videobuf2/Kconfig-18-\ndrivers/media/common/videobuf2/Kconfig=19=config VIDEOBUF2_VMALLOC\n--\ndrivers/media/common/videobuf2/Kconfig-22-\tselect VIDEOBUF2_MEMOPS\ndrivers/media/common/videobuf2/Kconfig:23:\tselect DMA_SHARED_BUFFER\ndrivers/media/common/videobuf2/Kconfig-24-\n--\ndrivers/media/platform/nvidia/tegra-vde/Kconfig=1=config VIDEO_TEGRA_VDE\n--\ndrivers/media/platform/nvidia/tegra-vde/Kconfig-6-\tdepends on VIDEO_DEV\ndrivers/media/platform/nvidia/tegra-vde/Kconfig:7:\tselect DMA_SHARED_BUFFER\ndrivers/media/platform/nvidia/tegra-vde/Kconfig-8-\tselect IOMMU_IOVA\n--\ndrivers/misc/Kconfig=266=config QCOM_FASTRPC\n--\ndrivers/misc/Kconfig-269-\tdepends on RPMSG\ndrivers/misc/Kconfig:270:\tselect DMA_SHARED_BUFFER\ndrivers/misc/Kconfig-271-\tselect QCOM_SCM\n--\ndrivers/tee/Kconfig=3=menuconfig TEE\n--\ndrivers/tee/Kconfig-6-\tselect CRYPTO_LIB_SHA1\ndrivers/tee/Kconfig:7:\tselect DMA_SHARED_BUFFER\ndrivers/tee/Kconfig-8-\tselect GENERIC_ALLOCATOR\n--\ndrivers/usb/gadget/Kconfig=192=config USB_F_FS\ndrivers/usb/gadget/Kconfig:193:\tselect DMA_SHARED_BUFFER\ndrivers/usb/gadget/Kconfig-194-\ttristate\n--\ndrivers/xen/Kconfig=145=config XEN_GNTDEV_DMABUF\n--\ndrivers/xen/Kconfig-147-\tdepends on XEN_GNTDEV \u0026\u0026 XEN_GRANT_DMA_ALLOC\ndrivers/xen/Kconfig:148:\tselect DMA_SHARED_BUFFER\ndrivers/xen/Kconfig-149-\thelp\n--\nsamples/Kconfig=230=config SAMPLE_VFIO_MDEV_MBOCHS\n--\nsamples/Kconfig-233-\tselect VFIO_MDEV\nsamples/Kconfig:234:\tselect DMA_SHARED_BUFFER\nsamples/Kconfig-235-\thelp\n--\nsound/soc/fsl/Kconfig=6=config SND_SOC_FSL_ASRC\n--\nsound/soc/fsl/Kconfig-8-\tdepends on HAS_DMA\nsound/soc/fsl/Kconfig:9:\tselect DMA_SHARED_BUFFER\nsound/soc/fsl/Kconfig-10-\tselect REGMAP_MMIO\n"
}

17/2 2026/10/15 21:31 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 18982
							output: 19
							thoughts: 26
						

Thoughts:
Here's my attempt at summarizing the thought process, tailored for an expert audience:

**Analyzing UDMABUF and DMA_SHARED_BUFFER**

Okay, so `UDMABUF` seems to be involved. A quick mental check: does `UDMABUF` implicitly select or relate to `DMA_SHARED_BUFFER`? It's a key interaction to understand, as it defines memory sharing in these contexts. I need to make sure I understand the relationship to make any informed decisions about buffer management. Let me dive in and refresh my memory, this coupling is critical for optimal hardware utilization.



18/2 2026/10/15 21:31 tool grepper
0m Args:
{
  "Expression": "config UDMABUF"
}

Results:
{
  "Output": "drivers/dma-buf/Kconfig=20=config SW_SYNC\n--\ndrivers/dma-buf/Kconfig-32-\ndrivers/dma-buf/Kconfig:33:config UDMABUF\ndrivers/dma-buf/Kconfig-34-\tbool \"userspace dmabuf misc driver\"\n"
}

19/2 2026/10/15 21:31 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 19243
							output: 36
							thoughts: 26
						

Thoughts:
Here's a summary of my thought process, as requested:

**Investigating DMA-BUF Configuration Options**

Okay, let's dive into this. To get a handle on the current configuration for DMA-BUF, I need to check the relevant Kconfig file. Based on past experience with the kernel, that's likely located in `drivers/dma-buf/Kconfig`. I'll use a `read-file` command to pull the content of that file and quickly scan for something around line 33. I'm expecting to find either the core configuration option itself or a conditional block related to it. That should give me a good starting point to understand the DMA-BUF's current settings and dependencies. This approach usually works for quickly understanding the context of the configurations within the kernel.



20/2 2026/10/15 21:31 tool read-file
0m Args:
{
  "File": "drivers/dma-buf/Kconfig",
  "FirstLine": 30,
  "LineCount": 20
}

Results:
{
  "Contents": "  30:\t\t  WARNING: improper use of this can result in deadlocking kernel\n  31:\t\t  drivers from userspace. Intended for test and debug only.\n  32:\t\n  33:\tconfig UDMABUF\n  34:\t\tbool \"userspace dmabuf misc driver\"\n  35:\t\tdefault n\n  36:\t\tdepends on DMA_SHARED_BUFFER\n  37:\t\tdepends on MEMFD_CREATE || COMPILE_TEST\n  38:\t\tdepends on MMU\n  39:\t\thelp\n  40:\t\t  A driver to let userspace turn memfd regions into dma-bufs.\n  41:\t\t  Qemu can use this to create host dmabufs for guest framebuffers.\n  42:\t\n  43:\tconfig DMABUF_DEBUG\n  44:\t\tbool \"DMA-BUF debug checks\"\n  45:\t\tdepends on DMA_SHARED_BUFFER\n  46:\t\tdefault y if DEBUG_KERNEL\n  47:\t\thelp\n  48:\t\t  This option enables additional checks for DMA-BUF importers and\n  49:\t\t  exporters. Specifically it validates that importers do not peek at the\n"
}

21/2 2026/10/15 21:31 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 19761
							output: 31
							thoughts: 344
						

Thoughts:
**My Analysis of DMA Shared Buffer Dependencies**

Okay, so I'm digging into this `UDMABUF` situation. It's clear that it hinges on `DMA_SHARED_BUFFER`. But what actually *enables* `DMA_SHARED_BUFFER` in the first place? I need to understand the dependencies. Hmm, is it a direct selection by something like `DRM` or perhaps `SYNC_FILE`? That's the question.

And just to confirm my train of thought, I remember seeing that `NET_DEVMEM` *also* requires `DMA_SHARED_BUFFER`, and additionally, it needs `PAGE_POOL`. Further, `NETDEVSIM` seems to have a selection dependency on `PAGE_POOL`. Alright, got it.

So, if I'm understanding this correctly, my typical configuration would likely include `NET_DEVMEM`, `NETDEVSIM`, `UDMABUF`, the choice between `SYNC_FILE` or a direct `DMA_SHARED_BUFFER` selection, and potentially `DEBUG_FS` for debugging. I'm building a mental map of this dependency chain.

Now, let's switch gears and focus on `net_devmem_dev_unregister`. I need to track where that's called because it suggests something about the lifecycle and usage of these shared buffers. Let me check the source code: Ah, here it is in `net/core/netdev-genl.c`:

```c
	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;
```

Interesting, it's called during `NETDEV_UNREGISTER` within `netdev-genl.c`. Okay, so `netdev-genl.c` is part of core networking, which is `CONFIG_NET`. Perfect, that narrows things down considerably. Now I need to see what's calling `net_devmem_bind_dmabuf`, `net_devmem_unbind_dmabuf`, and `net_devmem_bind_dmabuf_to_queue`. I will revisit `net/core/netdev-genl.c` for any devmem bindings to see what's actually utilizing this mechanism within the core networking context.



22/2 2026/10/15 21:31 tool grepper
0m Args:
{
  "Expression": "net_devmem_bind",
  "PathPrefixes": [
    "net/core/"
  ]
}

Results:
{
  "Output": "net/core/devmem.c=239=void net_devmem_dev_unregister(struct net_device *dev)\n--\nnet/core/devmem.c-261-\nnet/core/devmem.c:262:int net_devmem_bind_dmabuf_to_queue(struct net_device *dev, u32 rxq_idx,\nnet/core/devmem.c-263-\t\t\t\t    struct net_devmem_dmabuf_binding *binding,\n--\nnet/core/devmem.c=290=struct net_devmem_dmabuf_binding *\nnet/core/devmem.c:291:net_devmem_bind_dmabuf(struct net_device *dev, void *vdev,\nnet/core/devmem.c-292-\t\t       struct device *dma_dev,\n--\nnet/core/devmem.h=96=struct net_devmem_dmabuf_binding *\nnet/core/devmem.h:97:net_devmem_bind_dmabuf(struct net_device *dev, void *vdev,\nnet/core/devmem.h-98-\t\t       struct device *dma_dev,\n--\nnet/core/devmem.h=105=void net_devmem_dev_unregister(struct net_device *dev);\nnet/core/devmem.h:106:int net_devmem_bind_dmabuf_to_queue(struct net_device *dev, u32 rxq_idx,\nnet/core/devmem.h-107-\t\t\t\t    struct net_devmem_dmabuf_binding *binding,\n--\nnet/core/devmem.h=180=static inline struct net_devmem_dmabuf_binding *\nnet/core/devmem.h:181:net_devmem_bind_dmabuf(struct net_device *dev, void *vdev,\nnet/core/devmem.h-182-\t\t       struct device *dma_dev,\n--\nnet/core/devmem.h=206=static inline int\nnet/core/devmem.h:207:net_devmem_bind_dmabuf_to_queue(struct net_device *dev, u32 rxq_idx,\nnet/core/devmem.h-208-\t\t\t\tstruct net_devmem_dmabuf_binding *binding,\n--\nnet/core/netdev-genl.c=1013=int netdev_nl_bind_rx_doit(struct sk_buff *skb, struct genl_info *info)\n--\nnet/core/netdev-genl.c-1094-\nnet/core/netdev-genl.c:1095:\tbinding = net_devmem_bind_dmabuf(netdev, NULL, dma_dev, DMA_FROM_DEVICE,\nnet/core/netdev-genl.c-1096-\t\t\t\t\t dmabuf_fd, niov_shift, priv,\n--\nnet/core/netdev-genl.c-1103-\tfor_each_set_bit(rxq_idx, rxq_bitmap, netdev-\u003ereal_num_rx_queues) {\nnet/core/netdev-genl.c:1104:\t\terr = net_devmem_bind_dmabuf_to_queue(netdev, rxq_idx, binding,\nnet/core/netdev-genl.c-1105-\t\t\t\t\t\t      info-\u003eextack);\n--\nnet/core/netdev-genl.c=1177=int netdev_nl_bind_tx_doit(struct sk_buff *skb, struct genl_info *info)\n--\nnet/core/netdev-genl.c-1242-\nnet/core/netdev-genl.c:1243:\tbinding = net_devmem_bind_dmabuf(bind_dev,\nnet/core/netdev-genl.c-1244-\t\t\t\t\t bind_dev != netdev ? netdev : NULL,\n"
}

23/2 2026/10/15 21:31 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 21679
							output: 33
							thoughts: 52
						

Thoughts:
Okay, here's my take on those thoughts, tailored for an expert audience:

**Analyzing Generic Netlink Handlers and DMA Buffer Unbinding**

Right, so, `netdev_nl_bind_rx_doit` and `netdev_nl_bind_tx_doit`. Those are clearly generic netlink handlers. Pretty standard stuff for network device interaction. My initial thought is to map the context where `net_devmem_unbind_dmabuf` is called. That sounds like a DMA buffer unbinding function, likely associated with direct memory access. I need to understand what's triggering this unbinding to identify potential areas for optimization, debugging, or security review. Let's see what calls `net_devmem_unbind_dmabuf`, that'll give me a clearer picture of where this functionality is leveraged within the networking stack.



24/2 2026/10/15 21:31 tool grepper
0m Args:
{
  "Expression": "net_devmem_unbind_dmabuf",
  "PathPrefixes": [
    "net/"
  ]
}

Results:
{
  "Output": "net/core/devmem.c=147=void net_devmem_free_dmabuf(struct net_iov *niov)\n--\nnet/core/devmem.c-159-\nnet/core/devmem.c:160:void net_devmem_unbind_dmabuf(struct net_devmem_dmabuf_binding *binding)\nnet/core/devmem.c-161-{\n--\nnet/core/devmem.h=103=struct net_devmem_dmabuf_binding *net_devmem_lookup_dmabuf(u32 id);\nnet/core/devmem.h:104:void net_devmem_unbind_dmabuf(struct net_devmem_dmabuf_binding *binding);\nnet/core/devmem.h-105-void net_devmem_dev_unregister(struct net_device *dev);\n--\nnet/core/devmem.h=197=static inline void\nnet/core/devmem.h:198:net_devmem_unbind_dmabuf(struct net_devmem_dmabuf_binding *binding)\nnet/core/devmem.h-199-{\n--\nnet/core/netdev-genl.c=1013=int netdev_nl_bind_rx_doit(struct sk_buff *skb, struct genl_info *info)\n--\nnet/core/netdev-genl.c-1125-err_unbind:\nnet/core/netdev-genl.c:1126:\tnet_devmem_unbind_dmabuf(binding);\nnet/core/netdev-genl.c-1127-err_rxq_bitmap:\n--\nnet/core/netdev-genl.c=1451=void netdev_nl_sock_priv_destroy(struct netdev_nl_sock *priv)\n--\nnet/core/netdev-genl.c-1463-\t\t\tmutex_unlock(\u0026binding-\u003elock);\nnet/core/netdev-genl.c:1464:\t\t\tnet_devmem_unbind_dmabuf(binding);\nnet/core/netdev-genl.c-1465-\t\t\tcontinue;\n--\nnet/core/netdev-genl.c-1470-\t\tnetdev_lock(dev);\nnet/core/netdev-genl.c:1471:\t\tnet_devmem_unbind_dmabuf(binding);\nnet/core/netdev-genl.c-1472-\t\tnetdev_unlock(dev);\n"
}

25/2 2026/10/15 21:31 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 22589
							output: 33
							thoughts: 47
						

Thoughts:
Okay, let's see...

**Deconstructing Netlink Socket Closure and DMA Buffer Lookups**

Right, `netdev_nl_sock_priv_destroy` gets called when the netlink socket closes. That's good to know, that's the cleanup function! Let me keep that in mind. Now, I need to understand `net_devmem_lookup_dmabuf` a little bit better. Where does that actually get called from? Time to trace those calls and understand the contexts. I really need to get a handle on the relationships here.



26/2 2026/10/15 21:31 tool grepper
0m Args:
{
  "Expression": "net_devmem_lookup_dmabuf",
  "PathPrefixes": [
    "net/"
  ]
}

Results:
{
  "Output": "net/core/devmem.c=160=void net_devmem_unbind_dmabuf(struct net_devmem_dmabuf_binding *binding)\n--\nnet/core/devmem.c-169-\nnet/core/devmem.c:170:\t/* Ensure no tx net_devmem_lookup_dmabuf() are in flight after the\nnet/core/devmem.c-171-\t * mark is cleared.\n--\nnet/core/devmem.c=291=net_devmem_bind_dmabuf(struct net_device *dev, void *vdev,\n--\nnet/core/devmem.c-462-\nnet/core/devmem.c:463:struct net_devmem_dmabuf_binding *net_devmem_lookup_dmabuf(u32 id)\nnet/core/devmem.c-464-{\n--\nnet/core/devmem.c=490=struct net_devmem_dmabuf_binding *net_devmem_get_binding(struct sock *sk,\n--\nnet/core/devmem.c-497-\nnet/core/devmem.c:498:\tbinding = net_devmem_lookup_dmabuf(dmabuf_id);\nnet/core/devmem.c-499-\tif (!binding || !binding-\u003etx_vec) {\n--\nnet/core/devmem.h=97=net_devmem_bind_dmabuf(struct net_device *dev, void *vdev,\n--\nnet/core/devmem.h-102-\t\t       struct netlink_ext_ack *extack);\nnet/core/devmem.h:103:struct net_devmem_dmabuf_binding *net_devmem_lookup_dmabuf(u32 id);\nnet/core/devmem.h-104-void net_devmem_unbind_dmabuf(struct net_devmem_dmabuf_binding *binding);\n--\nnet/core/devmem.h=181=net_devmem_bind_dmabuf(struct net_device *dev, void *vdev,\n--\nnet/core/devmem.h-191-\nnet/core/devmem.h:192:static inline struct net_devmem_dmabuf_binding *net_devmem_lookup_dmabuf(u32 id)\nnet/core/devmem.h-193-{\n"
}

27/2 2026/10/15 21:31 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 23404
							output: 31
							thoughts: 39
						

Thoughts:
Here's my analysis of the provided snippet, as if it were my thought process:

**Tracing `net_devmem_get_binding` in the devmem TCP context**

Okay, so I'm dealing with devmem TCP, and I see that `net_devmem_get_binding` gets called when data is being sent. Interesting. The obvious next step is to trace the call to `net_devmem_get_binding` to understand *why* it's being called and what context it requires. I need to pinpoint every instance of the call to this specific function. This will help me understand how data binding works within this particular implementation and any dependencies. Time to dig in and see where this function is being invoked and what information it relies on.



28/2 2026/10/15 21:31 tool grepper
0m Args:
{
  "Expression": "net_devmem_get_binding",
  "PathPrefixes": [
    "net/"
  ]
}

Results:
{
  "Output": "net/core/devmem.c=485=void net_devmem_put_net_iov(struct net_iov *niov)\n--\nnet/core/devmem.c-489-\nnet/core/devmem.c:490:struct net_devmem_dmabuf_binding *net_devmem_get_binding(struct sock *sk,\nnet/core/devmem.c-491-\t\t\t\t\t\t\t unsigned int dmabuf_id)\n--\nnet/core/devmem.h=158=struct net_devmem_dmabuf_binding *\nnet/core/devmem.h:159:net_devmem_get_binding(struct sock *sk, unsigned int dmabuf_id);\nnet/core/devmem.h-160-struct net_iov *\n--\nnet/core/devmem.h=235=static inline struct net_devmem_dmabuf_binding *\nnet/core/devmem.h:236:net_devmem_get_binding(struct sock *sk, unsigned int dmabuf_id)\nnet/core/devmem.h-237-{\n--\nnet/ipv4/tcp.c=1116=int tcp_sendmsg_locked(struct sock *sk, struct msghdr *msg, size_t size)\n--\nnet/ipv4/tcp.c-1158-\t\t\tif (!sockc_err \u0026\u0026 sockc.dmabuf_id) {\nnet/ipv4/tcp.c:1159:\t\t\t\tbinding = net_devmem_get_binding(sk, sockc.dmabuf_id);\nnet/ipv4/tcp.c-1160-\t\t\t\tif (IS_ERR(binding)) {\n"
}

29/2 2026/10/15 21:31 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 24064
							output: 35
							thoughts: 1098
						

Thoughts:
**Deep Dive into the Netdev DMABUF Integration**

Okay, here's what's catching my eye. I'm seeing a connection between `tcp_sendmsg_locked` and `sockc.dmabuf_id` being set. That immediately flags a Devmem TCP TX pathway. Also, the Netdev netlink commands `netdev_nl_bind_rx_doit` and `netdev_nl_bind_tx_doit` are popping up. Definitely related!

Now, let's break down the functions touched by the patch. In `net/core/devmem.c`, we've got a new helper `net_devmem_dmabuf_binding_unmap`. A few functions are modified, like `__net_devmem_dmabuf_binding_free` which now uses `netdev_hold`, `netdev_lock`, and `net_devmem_dmabuf_binding_unmap`, and `net_devmem_unbind_dmabuf` now clears `NET_DEVMEM_BINDING_LIVE` and checks the return of `netif_mp_close_rxq` - that's interesting because it's handling the unbinding case. There are also a few new static helpers: `net_devmem_dev_unregister_rx`, `net_devmem_dev_unregister_tx`, and `net_devmem_dev_detach`. The function `net_devmem_dev_unregister` is now exported and callable, and it is called from `netdev_genl_netdevice_event`.  `net_devmem_bind_dmabuf_to_queue` is getting reordered, specifically the `xa_alloc` and `netif_mp_open_rxq` calls. We also see `net_devmem_bind_dmabuf` setting `NET_DEVMEM_BINDING_LIVE` and `net_devmem_lookup_dmabuf` finding live bindings. The `mp_dmabuf_devmem_uninstall` function is modified to unmap bindings.

Over in `net/core/netdev_rx_queue.c`, the return type of `netif_mp_close_rxq` is changed to `int` and error handling is getting modified. The helper `__netif_mp_close_rxq` also returns an int and does error checks.

`net/core/netdev-genl.c` has `netdev_genl_netdevice_event`, which calls `net_devmem_dev_unregister` when `NETDEV_UNREGISTER` is triggered.

Now, the Netdevsim driver (`drivers/net/netdevsim/netdev.c`, `drivers/net/netdevsim/psp.c`, `drivers/net/netdevsim/bus.c`) is where the DMA-BUF integration is happening. I see `nsim_create_page_pool` being set up with `PP_FLAG_ALLOW_UNREADABLE_NETMEM`, which is something to focus on. `nsim_queue_mem_alloc` passes the updated args to `nsim_create_page_pool`, and `nsim_pp_hold_write` handles the unreadable netmem. The `nsim_init_netdevsim` sets `ns->netdev->netmem_tx = NETMEM_TX_DMA`, so it's a good spot to investigate. `nsim_do_psp` checks `!skb_frags_readable(skb)`. Also, I see `nsim_bus_dev_new` calling `dma_coerce_mask_and_coherent`.

Now, considering the FocusSymbols criteria, I need to zoom in on the specific feature logic and entrypoints. That means avoiding the ubiquitous lifecycle hot-paths, like general allocators or locks.

So, let's focus on the non-static symbols:

*   `net_devmem_dev_unregister`
*   `net_devmem_unbind_dmabuf`
*   `net_devmem_bind_dmabuf_to_queue`
*   `net_devmem_bind_dmabuf`
*   `net_devmem_lookup_dmabuf`
*   `__net_devmem_dmabuf_binding_free`
*   `netif_mp_close_rxq`
*   `netdev_nl_bind_rx_doit`
*   `netdev_nl_bind_tx_doit`
*   `nsim_create_page_pool` (and its callers like `nsim_init_napi`, `nsim


30/2 2026/10/15 21:31 tool grepper
0m Args:
{
  "Expression": "nsim_do_psp",
  "PathPrefixes": [
    "drivers/net/netdevsim/"
  ]
}

Results:
{
  "Output": "drivers/net/netdevsim/netdev.c=123=static netdev_tx_t nsim_start_xmit(struct sk_buff *skb, struct net_device *dev)\n--\ndrivers/net/netdevsim/netdev.c-149-\ndrivers/net/netdevsim/netdev.c:150:\tdr = nsim_do_psp(skb, ns, peer_ns, \u0026psp_ext);\ndrivers/net/netdevsim/netdev.c-151-\tif (dr)\n--\ndrivers/net/netdevsim/netdevsim.h=454=enum skb_drop_reason\ndrivers/net/netdevsim/netdevsim.h:455:nsim_do_psp(struct sk_buff *skb, struct netdevsim *ns,\ndrivers/net/netdevsim/netdevsim.h-456-\t    struct netdevsim *peer_ns, struct skb_ext **psp_ext);\n--\ndrivers/net/netdevsim/netdevsim.h=460=static inline enum skb_drop_reason\ndrivers/net/netdevsim/netdevsim.h:461:nsim_do_psp(struct sk_buff *skb, struct netdevsim *ns,\ndrivers/net/netdevsim/netdevsim.h-462-\t    struct netdevsim *peer_ns, struct skb_ext **psp_ext)\n--\ndrivers/net/netdevsim/psp.c=17=enum skb_drop_reason\ndrivers/net/netdevsim/psp.c:18:nsim_do_psp(struct sk_buff *skb, struct netdevsim *ns,\ndrivers/net/netdevsim/psp.c-19-\t    struct netdevsim *peer_ns, struct skb_ext **psp_ext)\n"
}

31/2 2026/10/15 21:31 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 26687
							output: 42
							thoughts: 536
						

Thoughts:
**My Analysis of Potential Kernel Configuration Requirements**

Okay, so I'm digging into these functions and their static/non-static status, trying to understand the configuration implications. `nsim_do_psp` is definitively non-static, which is a key starting point. I confirmed that `nsim_bus_dev_new` in `drivers/net/netdevsim/bus.c` is also non-static; this is good information.

Then, there's `netdev_genl_netdevice_event`, which is static but called on NETDEV_UNREGISTER, so I'm noting that down. I'm focusing on the non-static ones right now because those are the interesting ones from a module perspective. The non-static nature of `net_devmem_dev_unregister` in `net/core/devmem.c` is significant. The related functions: `net_devmem_bind_dmabuf`, `net_devmem_unbind_dmabuf`, `net_devmem_bind_dmabuf_to_queue`, `net_devmem_lookup_dmabuf`, and `__net_devmem_dmabuf_binding_free` are *all* non-static, so the related system seems active. I'm also adding `netif_mp_close_rxq` from `net/core/netdev_rx_queue.c` to the list because that's non-static as well.

In the `netdevsim` driver, I see `nsim_queue_mem_alloc` being called via `netdev_queue_mgmt_ops`, specifically through `ndo_queue_mem_alloc`. This is important because it highlights another point of interaction.

Now, I'm checking the required kernel configurations. I've got:

*   `NETDEVSIM`: Simulated networking device (depends on `DEBUG_FS`, `INET`, selects `NET_DEVLINK`, `PAGE_POOL`, `NET_SHAPER`).
*   `NET_DEVMEM`: Device memory TCP support (depends on `DMA_SHARED_BUFFER`, `PAGE_POOL`, selects `GENERIC_ALLOCATOR`).
*   `UDMABUF`: User-space dmabuf devices for devmem testing (depends on `DMA_SHARED_BUFFER`, `MEMFD_CREATE`).
*   `SYNC_FILE`: selects `DMA_SHARED_BUFFER` (a safeguard to ensure `DMA_SHARED_BUFFER` is enabled).
*   `DEBUG_FS`: Required by `NETDEVSIM`.

I'm making sure to review `net/core/devmem.c` for any additional `#ifdef` or other configurations that I might have missed. Gotta be thorough.



32/2 2026/10/15 21:31 tool grepper
0m Args:
{
  "Expression": "#if",
  "PathPrefixes": [
    "net/core/devmem.c",
    "net/core/devmem.h"
  ]
}

Results:
{
  "Output": "net/core/devmem.h-9- */\nnet/core/devmem.h:10:#ifndef _NET_DEVMEM_H\nnet/core/devmem.h-11-#define _NET_DEVMEM_H\n--\nnet/core/devmem.h=18=struct net_devmem_dmabuf_binding {\n--\nnet/core/devmem.h-80-\nnet/core/devmem.h:81:#if defined(CONFIG_NET_DEVMEM)\nnet/core/devmem.h-82-/* Owner of the dma-buf chunks inserted into the gen pool. Each scatterlist\n"
}

33/2 2026/10/15 21:31 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 28021
							output: 32
							thoughts: 166
						

Thoughts:
**Investigating DMA Configuration in Netdevsim**

Okay, so I'm digging into how DMA is handled within the network device simulation code. My initial point of reference is `net/core/devmem.h`, which seems to be gated by `CONFIG_NET_DEVMEM`. I'll keep that in mind, but my focus right now is on `netdevsim`. I need to figure out how DMA is enabled and used there.

Let's start with `drivers/net/netdevsim/netdev.c`. I immediately spot `IS_ENABLED(CONFIG_HAS_DMA)` in `nsim_create_page_pool`. Aha, there it is! Conditional compilation based on whether DMA is enabled. If `CONFIG_HAS_DMA` is set, `PP_FLAG_DMA_MAP` and `PP_FLAG_DMA_SYNC_DEV` are set in `params.flags`. That makes sense. I remember `HAS_DMA` is usually default 'y' on amd64, so on most typical systems, this path is going to be taken.

Now, let's see if the Packet Scheduler, `drivers/net/netdevsim/psp.c`, also has any config. No, looks like it is absent. I'll need to check the `Makefile` or `Kconfig` files in that directory to get the full picture and see what other dependencies may apply.



34/2 2026/10/15 21:32 tool grepper
0m Args:
{
  "Expression": "psp.o",
  "PathPrefixes": [
    "drivers/net/netdevsim/"
  ]
}

Results:
{
  "Output": "drivers/net/netdevsim/Makefile=21=ifneq ($(CONFIG_INET_PSP),)\ndrivers/net/netdevsim/Makefile:22:netdevsim-objs += psp.o\ndrivers/net/netdevsim/Makefile-23-endif\n--\ndrivers/net/netdevsim/psp.c=194=static void nsim_get_stats(struct psp_dev *psd, struct psp_dev_stats *stats)\n--\ndrivers/net/netdevsim/psp.c-208-\ndrivers/net/netdevsim/psp.c:209:static struct psp_dev_ops nsim_psp_ops = {\ndrivers/net/netdevsim/psp.c-210-\t.set_config\t= nsim_psp_set_config,\n--\ndrivers/net/netdevsim/psp.c=249=nsim_psp_rereg_write(struct file *file, const char __user *data, size_t count,\n--\ndrivers/net/netdevsim/psp.c-258-\ndrivers/net/netdevsim/psp.c:259:\tpsd = psp_dev_create(ns-\u003enetdev, \u0026nsim_psp_ops, \u0026nsim_psp_caps, ns);\ndrivers/net/netdevsim/psp.c-260-\tif (IS_ERR(psd)) {\n--\ndrivers/net/netdevsim/psp.c=279=int nsim_psp_init(struct netdevsim *ns)\n--\ndrivers/net/netdevsim/psp.c-283-\ndrivers/net/netdevsim/psp.c:284:\tpsd = psp_dev_create(ns-\u003enetdev, \u0026nsim_psp_ops, \u0026nsim_psp_caps, ns);\ndrivers/net/netdevsim/psp.c-285-\tif (IS_ERR(psd))\n"
}

35/2 2026/10/15 21:32 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 29002
							output: 19
							thoughts: 40
						

Thoughts:
Okay, here's my interpretation of this snippet, rendered as a first-person account suitable for a network systems expert like yourself:

**Analyzing the Conditional Network Device Driver Inclusion**

Alright, I'm looking at this Makefile snippet and immediately my mind is dissecting the implications. It's a classic conditional compilation scenario: `ifneq ($(CONFIG_INET_PSP),) netdevsim-objs += psp.o endif`. This screams "include a specific module, `psp.o`, within the `netdevsim` driver *only* if a certain configuration option, `CONFIG_INET_PSP`, is defined and enabled."

The key is, of course, that variable: `CONFIG_INET_PSP`.  My immediate next step - and this is almost instinctual by now – is to dive into the `Kconfig` files. I need to understand what this `CONFIG_INET_PSP` actually *represents*. What functionality does it enable? Is it related to Performance, Security or Power Saving? What are the dependencies?  I need to see the context, the description, and the default settings for this config option. Understanding `CONFIG_INET_PSP` is absolutely crucial.

Is it a compile-time option, or a run-time option? What are the implications in memory and power usage? Depending on the definition within Kconfig, there are many avenues of questioning to explore, and I need this context before I can confidently say what this means in practical terms. Ultimately, the presence or absence of this conditional determines if `psp.o` gets linked with `netdevsim`, and therefore determines if the associated functionality is part of the final build. This is where I start.



36/2 2026/10/15 21:32 tool grepper
0m Args:
{
  "Expression": "config INET_PSP"
}

Results:
{
  "Output": "net/psp/Kconfig-4-#\nnet/psp/Kconfig:5:config INET_PSP\nnet/psp/Kconfig-6-\tbool \"PSP Security Protocol support\"\n"
}

37/2 2026/10/15 21:32 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 29468
							output: 32
							thoughts: 38
						

Thoughts:
Here's a summary of my thought process:

**Diving into the netdevsim and PSP Configuration**

Okay, so `CONFIG_INET_PSP` is the key – it seems to trigger the inclusion of `psp.o` within the `netdevsim` module. That's interesting, and definitely something I need to understand better. My next step is clear: I need to go straight to `net/psp/Kconfig` and take a look around line 5. That's likely where the dependency or configuration logic for `CONFIG_INET_PSP` with respect to the PSP driver is defined, and I need to see how it all interrelates. Let's see what the configuration options reveal.



38/2 2026/10/15 21:32 tool read-file
0m Args:
{
  "File": "net/psp/Kconfig",
  "FirstLine": 1,
  "LineCount": 20
}

Results:
{
  "Contents": "   1:\t# SPDX-License-Identifier: GPL-2.0-only\n   2:\t#\n   3:\t# PSP configuration\n   4:\t#\n   5:\tconfig INET_PSP\n   6:\t\tbool \"PSP Security Protocol support\"\n   7:\t\tdepends on INET\n   8:\t\tselect SKB_DECRYPTED\n   9:\t\tselect SKB_EXTENSIONS\n  10:\t\tselect SOCK_VALIDATE_XMIT\n  11:\t\thelp\n  12:\t\tEnable kernel support for the PSP Security Protocol (PSP).\n  13:\t\tFor more information see:\n  14:\t\t  https://raw.githubusercontent.com/google/psp/main/doc/PSP_Arch_Spec.pdf\n  15:\t\n  16:\t\tIf unsure, say N.\n"
}

39/2 2026/10/15 21:32 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 29894
							output: 37
							thoughts: 592
						

Thoughts:
**Kernel Configuration Deep Dive**

Okay, so I need to piece together the necessary kernel configurations for my work. It looks like `INET_PSP` is crucial; it's what enables the build and execution of `nsim_do_psp` within `drivers/net/netdevsim/psp.c`. Then, `NET_DEVMEM` is, unsurprisingly, required for `net/core/devmem.c`.  `NETDEVSIM` is essential to build `drivers/net/netdevsim/`. For user-space dmabuf creation when testing net devmem dmabuf, I need `UDMABUF`. The selection of `DMA_SHARED_BUFFER` is dependent on `SYNC_FILE` (or `DMABUF_HEAPS`, or another exporter); alternatively, `UDMABUF` needs it. And finally, `DEBUG_FS` is a prerequisite for `NETDEVSIM`.

Let me quickly review those configurations to make sure I haven't missed anything:
*   `NET_DEVMEM`
*   `NETDEVSIM`
*   `INET_PSP`
*   `UDMABUF`
*   `SYNC_FILE`
*   `DEBUG_FS`

Now, let's turn to the focus symbols. I've got a list of important functions to include:
*   `net_devmem_dev_unregister`
*   `net_devmem_unbind_dmabuf`
*   `net_devmem_bind_dmabuf`
*   `net_devmem_bind_dmabuf_to_queue`
*   `net_devmem_lookup_dmabuf`
*   `__net_devmem_dmabuf_binding_free`
*   `netif_mp_close_rxq`
*   `nsim_do_psp`
*   `nsim_bus_dev_new`
*   `nsim_pp_hold_write` (this is in debugfs)

Okay, now let's check. My main concern is with `nsim_pp_hold_write`. Is it available as a symbol in vmlinux or netdevsim.ko? I remember seeing it in `drivers/net/netdevsim/netdev.c`. Ah, yes, it's defined as `static ssize_t nsim_pp_hold_write(...)`. Hmm, that means it's *static*. This means it might be inlined or a local symbol, which is a problem. But if it's a file_operations write method...
```c
static const struct file_operations nsim_pp_hold_fops = { ... .write = nsim_pp_hold_write, ... };
```
That might give it a symbol if it isn't inlined. But, to be on the safe side and avoid problems with non-exported symbols, let's try a different symbol. Let me look at `nsim_queue_mem_alloc`:

```c
int nsim_queue_mem_alloc(struct net_device *dev, ...)
```

Oh, wait, is that static too? I'll need to double-check that. Let me look in `netdev.c` around line 760. I need to be careful with these static functions; they can be tricky.



40/2 2026/10/15 21:32 tool grepper
0m Args:
{
  "Expression": "nsim_queue_mem_alloc",
  "PathPrefixes": [
    "drivers/net/netdevsim/"
  ]
}

Results:
{
  "Output": "drivers/net/netdevsim/netdev.c=758=static int\ndrivers/net/netdevsim/netdev.c:759:nsim_queue_mem_alloc(struct net_device *dev,\ndrivers/net/netdevsim/netdev.c-760-\t\t     struct netdev_queue_config *qcfg,\n--\ndrivers/net/netdevsim/netdev.c=865=static const struct netdev_queue_mgmt_ops nsim_queue_mgmt_ops = {\ndrivers/net/netdevsim/netdev.c-866-\t.ndo_queue_mem_size\t= sizeof(struct nsim_queue_mem),\ndrivers/net/netdevsim/netdev.c:867:\t.ndo_queue_mem_alloc\t= nsim_queue_mem_alloc,\ndrivers/net/netdevsim/netdev.c-868-\t.ndo_queue_mem_free\t= nsim_queue_mem_free,\n"
}

41/2 2026/10/15 21:32 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 31512
							output: 370
							thoughts: 746
						

Thoughts:
**My Assessment of the Net Devmem and Netdevsim Code**

Okay, let's break this down. I see that `netdev_rx_queue_reconfig` eventually calls `ndo_queue_mem_alloc`, which is the entry point I need to consider. I'm also now thinking about the netlink entry points, and I spot `netdev_nl_bind_rx_doit` and `netdev_nl_bind_tx_doit` in `net/core/netdev-genl.c`. Ah yes, these are direct Netlink entry points, I see the light! And, I mustn't forget `netdev_nl_sock_priv_destroy`.

Now, let's get down to specifics. I've enumerated a set of key focus symbols, including: `net_devmem_dev_unregister`, `net_devmem_unbind_dmabuf`, `net_devmem_bind_dmabuf`, `net_devmem_bind_dmabuf_to_queue`, `net_devmem_lookup_dmabuf`, `__net_devmem_dmabuf_binding_free`, `netdev_nl_bind_rx_doit`, `netdev_nl_bind_tx_doit`, `netif_mp_close_rxq`, `nsim_bus_dev_new`, and `nsim_do_psp`.

I'm now going to double-check each one, drilling down to be sure. I'm seeing `net_devmem_dev_unregister` as a new function for unregistering devices with devmem bindings. `net_devmem_unbind_dmabuf` is altered to handle queue closing errors and unmapping dmabuf. `net_devmem_bind_dmabuf` and `net_devmem_bind_dmabuf_to_queue` are related to the binding process. I'm picking up `net_devmem_lookup_dmabuf` to look up live marks, and `__net_devmem_dmabuf_binding_free` is a workqueue callback and has had a rewrite of its locking and unmapping. Then there are the `netdev_nl_*` functions, which are Genl entrypoints for rx and tx devmem bindings. And, `netif_mp_close_rxq` has changed its signature to return an int, which is good for error handling. Finally, `nsim_bus_dev_new` is related to netdevsim device creation, setting the dma coherent mask, and `nsim_do_psp` is related to PSP transmission and checking readable skb frags.

Okay, are there any that are general allocators, locks, page fault handlers, or generic scheduler functions? No, it looks like these are all very specific to net devmem (dmabuf memory provider) and netdevsim features. This all looks solid, well-reasoned, and targeted. I'm ready to call `set-results`.



42/2 2026/10/15 21:32 tool set-results
0m Args:
{
  "EnableConfigs": [
    "NET_DEVMEM",
    "NETDEVSIM",
    "UDMABUF",
    "SYNC_FILE",
    "INET_PSP",
    "DEBUG_FS"
  ],
  "FocusSymbols": [
    "net_devmem_dev_unregister",
    "net_devmem_unbind_dmabuf",
    "net_devmem_bind_dmabuf",
    "net_devmem_bind_dmabuf_to_queue",
    "net_devmem_lookup_dmabuf",
    "__net_devmem_dmabuf_binding_free",
    "netdev_nl_bind_rx_doit",
    "netdev_nl_bind_tx_doit",
    "netif_mp_close_rxq",
    "nsim_bus_dev_new",
    "nsim_do_psp"
  ],
  "Reasoning": "The patch series modifies core networking memory provider / devmem TCP logic (net/core/devmem.c, net/core/netdev_rx_queue.c, and net/core/netdev-genl.c) as well as the netdevsim driver. It adjusts dmabuf unmapping lifecycles, adds synchronization upon netdev unregistration, changes return types and error handling in queue close callbacks, and adds unreadable netmem support and DMA coercion to netdevsim. These code paths are fully reachable in virtualized environments via netlink (netdev family), TCP sendmsg, and netdevsim/udmabuf devices. Fuzzing is warranted to uncover potential concurrency issues, unregister/unbind race conditions, or locking violations.",
  "WorthFuzzing": true
}

Results:
{
  "EnableConfigs": [
    "NET_DEVMEM",
    "NETDEVSIM",
    "UDMABUF",
    "SYNC_FILE",
    "INET_PSP",
    "DEBUG_FS"
  ],
  "FocusSymbols": [
    "net_devmem_dev_unregister",
    "net_devmem_unbind_dmabuf",
    "net_devmem_bind_dmabuf",
    "net_devmem_bind_dmabuf_to_queue",
    "net_devmem_lookup_dmabuf",
    "__net_devmem_dmabuf_binding_free",
    "netdev_nl_bind_rx_doit",
    "netdev_nl_bind_tx_doit",
    "netif_mp_close_rxq",
    "nsim_bus_dev_new",
    "nsim_do_psp"
  ],
  "Reasoning": "The patch series modifies core networking memory provider / devmem TCP logic (net/core/devmem.c, net/core/netdev_rx_queue.c, and net/core/netdev-genl.c) as well as the netdevsim driver. It adjusts dmabuf unmapping lifecycles, adds synchronization upon netdev unregistration, changes return types and error handling in queue close callbacks, and adds unreadable netmem support and DMA coercion to netdevsim. These code paths are fully reachable in virtualized environments via netlink (netdev family), TCP sendmsg, and netdevsim/udmabuf devices. Fuzzing is warranted to uncover potential concurrency issues, unregister/unbind race conditions, or locking violations.",
  "WorthFuzzing": true
}

43/1 2026/10/15 21:32 action If
0m Args:
{
  "WorthFuzzing": true
}

Results:
null

44/2 2026/10/15 21:32 agent kmsan-evaluator
0m Model:
core

Args:
null

Results:
{
  "KMSANReasoning": "The patch series adds devmem (dmabuf) support to netdevsim and fixes lifecycle management and synchronization issues during netdev unregistration for devmem bindings. The primary risks associated with these changes are concurrency bugs, locking issues, and use-after-free (UAF) conditions during device detach/unregistration, which are covered by KASAN and LOCKDEP. No uninitialized memory is exposed, no uninitialized struct fields or padding are copied to userspace, and all new/modified local variables and structures are fully initialized. Therefore, KMSAN is not needed.",
  "NeedsKMSAN": false
}

Instruction:
You are an expert Linux kernel security engineer specializing in kernel memory
error detectors (KASAN and KMSAN). Your job is to review the provided patch series and
determine if the code changes justify spawning a dedicated KMSAN (KernelMemorySanitizer)
fuzzing session in addition to standard KASAN fuzzing.

CRITICAL DISTINCTION BETWEEN KASAN AND KMSAN:
- Standard KASAN kernel builds (upstream-apparmor-kasan.config) already enable
  a comprehensive suite of debugging tools and sanitizers, including KASAN
  (out-of-bounds accesses, use-after-free, double free, invalid free), LOCKDEP
  (locking bugs and deadlocks), UB-sanitizers, and memory corruption checks.
- KMSAN (KernelMemorySanitizer) detects reads of UNINITIALIZED memory (stack, heap,
  or page allocations) and kernel-to-user memory info-leaks.

Rule: THERE IS NO SENSE IN RUNNING A KMSAN SESSION IF A BUG CAN BE CAUGHT BY KASAN,
LOCKDEP, OR OTHER STANDARD BUG DETECTORS.
A dedicated KMSAN fuzzing session incurs significant resource costs. You must ONLY
set NeedsKMSAN=true if the code changes introduce or expose UNINITIALIZED MEMORY risks
that are detected ONLY by KMSAN.

Look holistically at the patch series and surrounding code. Even if no direct
uninitialized field accesses or new buffer allocations are added in the diff itself,
a patch may alter control flow, bounds checking, or data length calculations in ways
that change how the rest of the code operates on existing buffers (e.g. allowing
uninitialized stack/heap memory to be read, copied to user space, or used in control
flow). Do not hesitate to use your code access tools to inspect the surrounding code,
called functions, and callers.

Set NeedsKMSAN=true ONLY IF the patch introduces or modifies:
1. Kernel structures sent to user space (via copy_to_user, put_user, netlink skb
   attributes, ioctl output arguments, socket options, or BPF buffers) where fields
   or structure padding might not be fully initialized/zeroed.
2. Conditional logic or branching that depends on potentially uninitialized variables
   or struct fields.
3. Allocation or initialization of complex data structures where uninitialized fields
   could be read later in reachable code paths.
4. Bounds checks, lengths, or logic in a way that allows surrounding code to access
   uninitialized bytes of existing buffers.

Set NeedsKMSAN=false IF:
- The code changes primarily risk out-of-bounds access, array overflows, NULL pointer
  dereferences, locking deadlocks, or use-after-free bugs (these are already caught
  by KASAN, LOCKDEP, or standard bug detectors).
- All stack/heap structures touched or introduced by the patch are fully zeroed
  or initialized (e.g. using = {0}, memset, kzalloc) before being read or copied.
- The patch does not introduce any risk of uninitialized memory usage or info-leaks.

Use your code access tools to inspect the surrounding code if necessary, then provide
detailed KMSANReasoning contrasting KASAN vs KMSAN applicability for this patch.
Prefer calling several tools at the same time to save round-trips.


Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.

Prompt:
Target architecture: amd64

For your convenience, here is the diff of the changes:
commit 180d7add7fd4a84a7be8a0708cc6ca75fd8c54b1
Author: syz-cluster <triage@syzkaller.com>
Date:   Thu Oct 15 21:30:26 2026 +0000

    syz-cluster: applied patch under review

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
diff --git a/include/net/page_pool/memory_provider.h b/include/net/page_pool/memory_provider.h
index 255ce4cfd9755..a999a9b28bc70 100644
--- a/include/net/page_pool/memory_provider.h
+++ b/include/net/page_pool/memory_provider.h
@@ -26,8 +26,8 @@ void net_mp_niov_clear_page_pool(struct net_iov *niov);
 int netif_mp_open_rxq(struct net_device *dev, unsigned int rxq_idx,
 		      const struct pp_memory_provider_params *p,
 		      struct netlink_ext_ack *extack);
-void netif_mp_close_rxq(struct net_device *dev, unsigned int rxq_idx,
-			const struct pp_memory_provider_params *old_p);
+int netif_mp_close_rxq(struct net_device *dev, unsigned int rxq_idx,
+		       const struct pp_memory_provider_params *old_p);
 
 /**
   * net_mp_netmem_place_in_cache() - give a netmem to a page pool
diff --git a/net/core/devmem.c b/net/core/devmem.c
index f4d60654ce7fd..d33a092c6e4bb 100644
--- a/net/core/devmem.c
+++ b/net/core/devmem.c
@@ -12,6 +12,7 @@
 #include <linux/mm.h>
 #include <linux/netdevice.h>
 #include <linux/types.h>
+#include <net/netdev_lock.h>
 #include <net/netdev_queues.h>
 #include <net/netdev_rx_queue.h>
 #include <net/page_pool/helpers.h>
@@ -20,6 +21,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 +29,7 @@
 /* Device memory support */
 
 static DEFINE_XARRAY_FLAGS(net_devmem_dmabuf_bindings, XA_FLAGS_ALLOC1);
+#define NET_DEVMEM_BINDING_LIVE XA_MARK_1
 
 static const struct memory_provider_ops dmabuf_devmem_ops;
 
@@ -58,9 +61,26 @@ 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);
+	netdevice_tracker dev_tracker;
+	struct net_device *dev;
 
 	size_t size, avail;
 
@@ -74,9 +94,23 @@ 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(&binding->lock);
+	dev = binding->dev;
+	if (dev)
+		netdev_hold(dev, &dev_tracker, GFP_KERNEL);
+	mutex_unlock(&binding->lock);
+
+	if (dev)
+		netdev_lock(dev);
+	mutex_lock(&binding->lock);
+	net_devmem_dmabuf_binding_unmap(binding);
+	mutex_unlock(&binding->lock);
+	xa_erase(&net_devmem_dmabuf_bindings, binding->id);
+	if (dev) {
+		netdev_unlock(dev);
+		netdev_put(dev, &dev_tracker);
+	}
+
 	dma_buf_put(binding->dmabuf);
 	xa_destroy(&binding->bound_rxqs);
 	percpu_ref_exit(&binding->ref);
@@ -128,11 +162,13 @@ void net_devmem_unbind_dmabuf(struct net_devmem_dmabuf_binding *binding)
 	struct netdev_rx_queue *rxq;
 	unsigned long xa_idx;
 	unsigned int rxq_idx;
+	bool busy = false;
 
-	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();
 
@@ -147,12 +183,82 @@ void net_devmem_unbind_dmabuf(struct net_devmem_dmabuf_binding *binding)
 
 		rxq_idx = get_netdev_rx_queue_index(rxq);
 
-		netif_mp_close_rxq(binding->dev, rxq_idx, &mp_params);
+		if (netif_mp_close_rxq(binding->dev, rxq_idx, &mp_params))
+			busy = true;
+	}
+
+	if (binding->direction == DMA_FROM_DEVICE && binding->dev && !busy) {
+		mutex_lock(&binding->lock);
+		WRITE_ONCE(binding->dev, NULL);
+		net_devmem_dmabuf_binding_unmap(binding);
+		mutex_unlock(&binding->lock);
 	}
 
 	percpu_ref_kill(&binding->ref);
 }
 
+static bool
+net_devmem_dev_unregister_rx(struct net_devmem_dmabuf_binding *binding,
+			     unsigned long xa_idx, struct net_device *dev)
+{
+	return binding->direction == DMA_FROM_DEVICE &&
+	       READ_ONCE(binding->dev) == dev &&
+	       !xa_get_mark(&net_devmem_dmabuf_bindings, xa_idx,
+			    NET_DEVMEM_BINDING_LIVE);
+}
+
+static bool
+net_devmem_dev_unregister_tx(struct net_devmem_dmabuf_binding *binding,
+			     unsigned long xa_idx, struct net_device *dev)
+{
+	if (binding->direction != DMA_TO_DEVICE)
+		return false;
+
+	if (READ_ONCE(binding->vdev) == dev)
+		WRITE_ONCE(binding->vdev, NULL);
+
+	if (READ_ONCE(binding->dev) != dev)
+		return false;
+
+	__xa_clear_mark(&net_devmem_dmabuf_bindings, xa_idx,
+			NET_DEVMEM_BINDING_LIVE);
+	return true;
+}
+
+static void net_devmem_dev_detach(struct net_devmem_dmabuf_binding *binding,
+				  struct net_device *dev)
+{
+	mutex_lock(&binding->lock);
+	WRITE_ONCE(binding->dev, NULL);
+	WRITE_ONCE(binding->vdev, 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;
+
+	netdev_assert_locked(dev);
+
+	if (dev->reg_state != NETREG_UNREGISTERING)
+		return;
+
+	xa_lock(&net_devmem_dmabuf_bindings);
+	xa_for_each(&net_devmem_dmabuf_bindings, xa_idx, binding) {
+		if (!net_devmem_dev_unregister_rx(binding, xa_idx, dev) &&
+		    !net_devmem_dev_unregister_tx(binding, xa_idx, dev))
+			continue;
+
+		xa_unlock(&net_devmem_dmabuf_bindings);
+		net_devmem_dev_detach(binding, dev);
+		xa_lock(&net_devmem_dmabuf_bindings);
+	}
+	xa_unlock(&net_devmem_dmabuf_bindings);
+}
+
 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)
@@ -168,20 +274,16 @@ int net_devmem_bind_dmabuf_to_queue(struct net_device *dev, u32 rxq_idx,
 	if (binding->niov_shift != PAGE_SHIFT)
 		mp_params.rx_page_size = 1U << binding->niov_shift;
 
-	err = netif_mp_open_rxq(dev, rxq_idx, &mp_params, extack);
-	if (err)
-		return err;
-
 	rxq = __netif_get_rx_queue(dev, rxq_idx);
 	err = xa_alloc(&binding->bound_rxqs, &xa_idx, rxq, xa_limit_32b,
 		       GFP_KERNEL);
 	if (err)
-		goto err_close_rxq;
+		return err;
 
-	return 0;
+	err = netif_mp_open_rxq(dev, rxq_idx, &mp_params, extack);
+	if (err)
+		xa_erase(&binding->bound_rxqs, xa_idx);
 
-err_close_rxq:
-	netif_mp_close_rxq(dev, rxq_idx, &mp_params);
 	return err;
 }
 
@@ -331,6 +433,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 +463,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 +635,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..ce069e1736416 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,9 @@ 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. RX bindings are unmapped earlier, once they are no longer
+	 * installed on any queue. TX bindings are unmapped earlier if their
+	 * device 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 +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_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 +199,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;
diff --git a/net/core/netdev_rx_queue.c b/net/core/netdev_rx_queue.c
index 00a7011eb4d57..a7261fedd5e76 100644
--- a/net/core/netdev_rx_queue.c
+++ b/net/core/netdev_rx_queue.c
@@ -275,8 +275,8 @@ int netif_mp_open_rxq(struct net_device *dev, unsigned int rxq_idx,
 	return ret;
 }
 
-static void __netif_mp_close_rxq(struct net_device *dev, unsigned int rxq_idx,
-				 const struct pp_memory_provider_params *old_p)
+static int __netif_mp_close_rxq(struct net_device *dev, unsigned int rxq_idx,
+				const struct pp_memory_provider_params *old_p)
 {
 	struct netdev_queue_config qcfg[2];
 	struct netdev_rx_queue *rxq;
@@ -289,35 +289,41 @@ static void __netif_mp_close_rxq(struct net_device *dev, unsigned int rxq_idx,
 	 */
 	if (dev->reg_state > NETREG_REGISTERED &&
 	    !rxq->mp_params.mp_ops)
-		return;
+		return 0;
 
 	if (WARN_ON_ONCE(rxq->mp_params.mp_ops != old_p->mp_ops ||
 			 rxq->mp_params.mp_priv != old_p->mp_priv))
-		return;
+		return -EINVAL;
 
 	netdev_queue_config(dev, rxq_idx, &qcfg[0]);
 	memset(&rxq->mp_params, 0, sizeof(rxq->mp_params));
 	netdev_queue_config(dev, rxq_idx, &qcfg[1]);
 
 	err = netdev_rx_queue_reconfig(dev, rxq_idx, &qcfg[0], &qcfg[1]);
-	WARN_ON(err && err != -ENETDOWN);
+	if (err == -ENETDOWN)
+		return 0;
+	WARN_ON(err);
+	return err;
 }
 
-void netif_mp_close_rxq(struct net_device *dev, unsigned int rxq_idx,
-			const struct pp_memory_provider_params *old_p)
+int netif_mp_close_rxq(struct net_device *dev, unsigned int rxq_idx,
+		       const struct pp_memory_provider_params *old_p)
 {
+	int ret;
+
 	if (WARN_ON_ONCE(rxq_idx >= dev->real_num_rx_queues))
-		return;
+		return -EINVAL;
 	if (!netif_rxq_is_leased(dev, rxq_idx))
 		return __netif_mp_close_rxq(dev, rxq_idx, old_p);
 
 	if (!__netif_get_rx_queue_lease(&dev, &rxq_idx, NETIF_VIRT_TO_PHYS)) {
 		WARN_ON_ONCE(1);
-		return;
+		return -EINVAL;
 	}
 	netdev_lock(dev);
-	__netif_mp_close_rxq(dev, rxq_idx, old_p);
+	ret = __netif_mp_close_rxq(dev, rxq_idx, old_p);
 	netdev_unlock(dev);
+	return ret;
 }
 
 void __netif_mp_uninstall_rxq(struct netdev_rx_queue *rxq,
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()

45/3 2026/10/15 21:32 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 11674
							output: 48
							thoughts: 794
						

Thoughts:
**Deep Dive into the Patch Series: DMA, Page Pools, and Device Memory**

Okay, so this patch series has caught my attention. It's touching a decent spread of components, so let's break down my thoughts.

First, I see the change in `drivers/net/netdevsim/bus.c` – forcing a 64-bit DMA mask. This is straightforward; a targeted DMA constraint. Nothing that jumps out immediately.

Then, `drivers/net/netdevsim/netdev.c` is getting a significant overhaul related to page pools. I'm noting the addition of `dev` and `idx` parameters in `nsim_create_page_pool`. The `PP_FLAG_ALLOW_UNREADABLE_NETMEM` flag is new. I'm also seeing flags like `PP_FLAG_DMA_MAP` and `PP_FLAG_DMA_SYNC_DEV` being added if DMA is enabled. The `nsim_pp_hold_write` with its check for an unreadable page pool is interesting. I'll need to understand how the locking mechanism plays with the unreadability check. The `-EOPNOTSUPP` return when the page pool is unreadable indicates it’s an explicitly unsupported path, which makes sense. I see also that `netmem_tx` is now set to `NETMEM_TX_DMA`, which reinforces the DMA theme.

`drivers/net/netdevsim/psp.c` is checking `skb_frags_readable`, and dropping packets with `SKB_DROP_REASON_PSP_OUTPUT` if they're not readable. That's a good sanity check – a defensive approach.

Now, the `include/net/page_pool/memory_provider.h` change in the function signature of `netif_mp_close_rxq` from `void` to `int` is a signal of changes percolating up. Propagating a return value up the call chain is generally a good thing for error handling, and it will be interesting to chase down where this return is used.

The `net/core/devmem.c` changes are particularly interesting. We have device memory support with `NET_DEVMEM_BINDING_LIVE` and `net_devmem_dmabuf_bindings`. I am going to pay attention to the unmapping, unbinding, and freeing logic in `net_devmem_dmabuf_binding_unmap`, `__net_devmem_dmabuf_binding_free` and `net_devmem_unbind_dmabuf`. Specifically, the hold/lock semantics need scrutiny. The reordering in `net_devmem_bind_dmabuf_to_queue` indicates potential robustness enhancements. I also see that they're using `xa_find` with `NET_DEVMEM_BINDING_LIVE` for lookups, this is an excellent use of XArray.

The calls to `net_devmem_dev_unregister` from `net/core/netdev-genl.c` on `NETDEV_UNREGISTER` are important to note. This ensures proper cleanup on device removal. And the corresponding change in `net/core/netdev_rx_queue.c` to propagate the return from `__netif_mp_close_rxq` reinforces a robust error handling chain.

Finally, selftests and Python scripts – good. Testing is vital, especially when dealing with such changes.

I need to circle back to KASAN and KMSAN. The critical thing here is to audit this code to be certain that these changes don't introduce or expose new uninitialized memory risks. Let me search the code and dig in!



46/3 2026/10/15 21:32 tool grepper
0m Args:
{
  "Expression": "net_devmem_dev_unregister",
  "PathPrefixes": [
    "net/core/devmem.c",
    "net/core/devmem.h"
  ]
}

Results:
{
  "Output": "net/core/devmem.c=200=static bool\nnet/core/devmem.c:201:net_devmem_dev_unregister_rx(struct net_devmem_dmabuf_binding *binding,\nnet/core/devmem.c-202-\t\t\t     unsigned long xa_idx, struct net_device *dev)\n--\nnet/core/devmem.c=210=static bool\nnet/core/devmem.c:211:net_devmem_dev_unregister_tx(struct net_devmem_dmabuf_binding *binding,\nnet/core/devmem.c-212-\t\t\t     unsigned long xa_idx, struct net_device *dev)\n--\nnet/core/devmem.c=228=static void net_devmem_dev_detach(struct net_devmem_dmabuf_binding *binding,\n--\nnet/core/devmem.c-238-\nnet/core/devmem.c:239:void net_devmem_dev_unregister(struct net_device *dev)\nnet/core/devmem.c-240-{\n--\nnet/core/devmem.c-250-\txa_for_each(\u0026net_devmem_dmabuf_bindings, xa_idx, binding) {\nnet/core/devmem.c:251:\t\tif (!net_devmem_dev_unregister_rx(binding, xa_idx, dev) \u0026\u0026\nnet/core/devmem.c:252:\t\t    !net_devmem_dev_unregister_tx(binding, xa_idx, dev))\nnet/core/devmem.c-253-\t\t\tcontinue;\n--\nnet/core/devmem.h=104=void net_devmem_unbind_dmabuf(struct net_devmem_dmabuf_binding *binding);\nnet/core/devmem.h:105:void net_devmem_dev_unregister(struct net_device *dev);\nnet/core/devmem.h-106-int net_devmem_bind_dmabuf_to_queue(struct net_device *dev, u32 rxq_idx,\n--\nnet/core/devmem.h=198=net_devmem_unbind_dmabuf(struct net_devmem_dmabuf_binding *binding)\n--\nnet/core/devmem.h-201-\nnet/core/devmem.h:202:static inline void net_devmem_dev_unregister(struct net_device *dev)\nnet/core/devmem.h-203-{\n"
}

47/3 2026/10/15 21:32 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 13949
							output: 36
							thoughts: 30
						

Thoughts:
Okay, here's my take on those thoughts, tailored for an expert audience:

**Diving into `net/core/devmem.c` for Device Memory Management**

Alright, let's get down to brass tacks. I need to zero in on `net/core/devmem.c` and specifically focus on the area around `net_devmem_dev_unregister`. My gut tells me this is the nexus of some interesting changes. I suspect there's been some evolution, perhaps related to resource cleanup, device lifecycle handling, or maybe even memory allocation/deallocation strategies associated with network device memory mapping. I'll need to meticulously dissect not just `net_devmem_dev_unregister` itself, but also the functions that interact with it – anything that involves registration, allocation, deallocation, or any other changes around device memory management. Understanding the call paths, error handling, and any new locking mechanisms are going to be critical. I'm looking for the subtle details, the potential gotchas, and how the developers have approached concurrency. Time to get my hands dirty.



48/3 2026/10/15 21:32 tool read-file
0m Args:
{
  "File": "net/core/devmem.c",
  "FirstLine": 180,
  "LineCount": 90
}

Results:
{
  "Contents": " 180:\t\t\t\t.mp_priv\t= binding,\n 181:\t\t\t\t.mp_ops\t\t= \u0026dmabuf_devmem_ops,\n 182:\t\t\t};\n 183:\t\n 184:\t\t\trxq_idx = get_netdev_rx_queue_index(rxq);\n 185:\t\n 186:\t\t\tif (netif_mp_close_rxq(binding-\u003edev, rxq_idx, \u0026mp_params))\n 187:\t\t\t\tbusy = true;\n 188:\t\t}\n 189:\t\n 190:\t\tif (binding-\u003edirection == DMA_FROM_DEVICE \u0026\u0026 binding-\u003edev \u0026\u0026 !busy) {\n 191:\t\t\tmutex_lock(\u0026binding-\u003elock);\n 192:\t\t\tWRITE_ONCE(binding-\u003edev, NULL);\n 193:\t\t\tnet_devmem_dmabuf_binding_unmap(binding);\n 194:\t\t\tmutex_unlock(\u0026binding-\u003elock);\n 195:\t\t}\n 196:\t\n 197:\t\tpercpu_ref_kill(\u0026binding-\u003eref);\n 198:\t}\n 199:\t\n 200:\tstatic bool\n 201:\tnet_devmem_dev_unregister_rx(struct net_devmem_dmabuf_binding *binding,\n 202:\t\t\t\t     unsigned long xa_idx, struct net_device *dev)\n 203:\t{\n 204:\t\treturn binding-\u003edirection == DMA_FROM_DEVICE \u0026\u0026\n 205:\t\t       READ_ONCE(binding-\u003edev) == dev \u0026\u0026\n 206:\t\t       !xa_get_mark(\u0026net_devmem_dmabuf_bindings, xa_idx,\n 207:\t\t\t\t    NET_DEVMEM_BINDING_LIVE);\n 208:\t}\n 209:\t\n 210:\tstatic bool\n 211:\tnet_devmem_dev_unregister_tx(struct net_devmem_dmabuf_binding *binding,\n 212:\t\t\t\t     unsigned long xa_idx, struct net_device *dev)\n 213:\t{\n 214:\t\tif (binding-\u003edirection != DMA_TO_DEVICE)\n 215:\t\t\treturn false;\n 216:\t\n 217:\t\tif (READ_ONCE(binding-\u003evdev) == dev)\n 218:\t\t\tWRITE_ONCE(binding-\u003evdev, NULL);\n 219:\t\n 220:\t\tif (READ_ONCE(binding-\u003edev) != dev)\n 221:\t\t\treturn false;\n 222:\t\n 223:\t\t__xa_clear_mark(\u0026net_devmem_dmabuf_bindings, xa_idx,\n 224:\t\t\t\tNET_DEVMEM_BINDING_LIVE);\n 225:\t\treturn true;\n 226:\t}\n 227:\t\n 228:\tstatic void net_devmem_dev_detach(struct net_devmem_dmabuf_binding *binding,\n 229:\t\t\t\t\t  struct net_device *dev)\n 230:\t{\n 231:\t\tmutex_lock(\u0026binding-\u003elock);\n 232:\t\tWRITE_ONCE(binding-\u003edev, NULL);\n 233:\t\tWRITE_ONCE(binding-\u003evdev, NULL);\n 234:\t\tif (!netif_is_queue_leasee(dev))\n 235:\t\t\tnet_devmem_dmabuf_binding_unmap(binding);\n 236:\t\tmutex_unlock(\u0026binding-\u003elock);\n 237:\t}\n 238:\t\n 239:\tvoid net_devmem_dev_unregister(struct net_device *dev)\n 240:\t{\n 241:\t\tstruct net_devmem_dmabuf_binding *binding;\n 242:\t\tunsigned long xa_idx;\n 243:\t\n 244:\t\tnetdev_assert_locked(dev);\n 245:\t\n 246:\t\tif (dev-\u003ereg_state != NETREG_UNREGISTERING)\n 247:\t\t\treturn;\n 248:\t\n 249:\t\txa_lock(\u0026net_devmem_dmabuf_bindings);\n 250:\t\txa_for_each(\u0026net_devmem_dmabuf_bindings, xa_idx, binding) {\n 251:\t\t\tif (!net_devmem_dev_unregister_rx(binding, xa_idx, dev) \u0026\u0026\n 252:\t\t\t    !net_devmem_dev_unregister_tx(binding, xa_idx, dev))\n 253:\t\t\t\tcontinue;\n 254:\t\n 255:\t\t\txa_unlock(\u0026net_devmem_dmabuf_bindings);\n 256:\t\t\tnet_devmem_dev_detach(binding, dev);\n 257:\t\t\txa_lock(\u0026net_devmem_dmabuf_bindings);\n 258:\t\t}\n 259:\t\txa_unlock(\u0026net_devmem_dmabuf_bindings);\n 260:\t}\n 261:\t\n 262:\tint net_devmem_bind_dmabuf_to_queue(struct net_device *dev, u32 rxq_idx,\n 263:\t\t\t\t\t    struct net_devmem_dmabuf_binding *binding,\n 264:\t\t\t\t\t    struct netlink_ext_ack *extack)\n 265:\t{\n 266:\t\tstruct pp_memory_provider_params mp_params = {\n 267:\t\t\t.mp_priv\t= binding,\n 268:\t\t\t.mp_ops\t\t= \u0026dmabuf_devmem_ops,\n 269:\t\t};\n"
}

49/3 2026/10/15 21:32 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 15687
							output: 143
							thoughts: 2342
						

Thoughts:
**Patch Series Analysis and KMSAN Justification**

Okay, let's break this down. The patch series focuses on improving the management of `devmem` and `dmabuf` interactions, specifically around the net device unregistration process in the netdevsim driver. My primary concern here, as always, is ensuring memory safety and preventing race conditions, especially Use-After-Free (UAF) scenarios. It's also adding testability to netdevsim which is good.

The code modifications primarily involve changes to `drivers/net/netdevsim/`, `net/core/devmem.c`, and `net/core/netdev_rx_queue.c`. In `netdevsim`, there are DMA mask settings for the simulated bus and adjustments to `page_pool` initialization and holding writes. `psp.c` includes a check for readable skb fragments, crucial for preventing unreadable memory being processed. The core `net/core/devmem.c` changes address the intricacies of dmabuf unmapping and detachment, along with synchronization during netdev unregistration. The `net/core/netdev_rx_queue.c` file is just returning a error code if reconfiguration fails.

Now, the critical question: KMSAN (Kernel Memory Sanitizer) vs. KASAN (Kernel Address Sanitizer). KMSAN is specifically designed to catch reads of uninitialized memory, while KASAN, LOCKDEP, and other standard bug detectors are already enabled in our builds and handle out-of-bounds accesses, use-after-free, double frees, locking bugs, and memory corruption. Running a KMSAN session is costly. It should only be used if there's a risk of introducing or exposing uninitialized memory that *only* KMSAN can detect.

We need to consider if these changes introduce or modify kernel structures sent to user space where fields or structure padding might not be fully initialized, whether there's conditional logic based on potentially uninitialized variables, if complex data structures are allocated/initialized with potentially uninitialized fields, or if these patches introduce bounds checks, lengths, or logic that allows surrounding code to access uninitialized bytes.

After careful review of each file and variable within the changes, it's clear that all structures, such as `struct page_pool_params` and the local variables, are explicitly initialized using designated initializers or direct assignment. The code specifically *avoids* reading unreadable memory with `skb_frags_readable` in `psp.c`, preventing uninitialized state issues. The key focus of these patches is on ensuring proper lifetime management, synchronization, and locking around `struct net_devmem_dmabuf_binding` and `struct net_device` during device unregistration, and avoiding UAF and synchronization issues.

KMSAN is not designed to detect unreadable memory that the kernel isn't *supposed* to read. That's typically handled by page faults or bus errors, and the patch has a check to prevent those issues from arising. Additionally, KMSAN only tracks RAM allocated to the system, not non-RAM memory types.

Therefore, because this patch series does *not* introduce:

1.  Kernel structures sent to user space with potential uninitialized fields.
2.  Conditional logic depending on uninitialized variables.
3.  Allocation or initialization of complex data structures with potentially uninitialized fields.
4.  Risks around bounds checks or buffer lengths.

**NeedsKMSAN = false.**

This patch series is primarily addressing the risks of UAF, synchronization errors, and device attachment/detachment errors during netdev unregistration. KASAN and LOCKDEP are specifically designed and tuned to catch these types of bugs and are already in place. This patch series does not introduce any risk of uninitialized memory usage or info-leaks, so a dedicated KMSAN session is not necessary.



50/3 2026/10/15 21:33 tool set-results
0m Args:
{
  "KMSANReasoning": "The patch series adds devmem (dmabuf) support to netdevsim and fixes lifecycle management and synchronization issues during netdev unregistration for devmem bindings. The primary risks associated with these changes are concurrency bugs, locking issues, and use-after-free (UAF) conditions during device detach/unregistration, which are covered by KASAN and LOCKDEP. No uninitialized memory is exposed, no uninitialized struct fields or padding are copied to userspace, and all new/modified local variables and structures are fully initialized. Therefore, KMSAN is not needed.",
  "NeedsKMSAN": false
}

Results:
{
  "KMSANReasoning": "The patch series adds devmem (dmabuf) support to netdevsim and fixes lifecycle management and synchronization issues during netdev unregistration for devmem bindings. The primary risks associated with these changes are concurrency bugs, locking issues, and use-after-free (UAF) conditions during device detach/unregistration, which are covered by KASAN and LOCKDEP. No uninitialized memory is exposed, no uninitialized struct fields or padding are copied to userspace, and all new/modified local variables and structures are fully initialized. Therefore, KMSAN is not needed.",
  "NeedsKMSAN": false
}

LLM Calls Summary:
Total Calls Total Tokens Avg Tokens Total Duration (Seconds) Avg Duration (Seconds)
Tool Calls Summary:
Total Calls Total Duration (Seconds) Avg Duration (Seconds)