All of lore.kernel.org
 help / color / mirror / Atom feed
From: Dragos Tatulea <dtatulea@nvidia.com>
To: Mina Almasry <almasrymina@google.com>,
	Stanislav Fomichev <sdf.kernel@gmail.com>
Cc: Hengbin Zhang <uqbarz@gmail.com>,
	netdev@vger.kernel.org, "David S. Miller" <davem@davemloft.net>,
	Eric Dumazet <edumazet@google.com>,
	Jakub Kicinski <kuba@kernel.org>, Paolo Abeni <pabeni@redhat.com>,
	Taehee Yoo <ap420073@gmail.com>,
	Bobby Eshleman <bobbyeshleman@meta.com>,
	linux-kernel@vger.kernel.org
Subject: Re: [BUG] net: devmem: TX dma-buf binding UAF on netdev-genl socket close
Date: Wed, 9 Sep 2026 11:16:55 +0200	[thread overview]
Message-ID: <1454a86d-5a8d-4ae7-b6a4-24f256729649@nvidia.com> (raw)
In-Reply-To: <CAHS8izPN6P4=CKFZkowo+t2tx=nu5Q8JjVdZ=_c3eMrMG=9LxA@mail.gmail.com>



On 08.09.26 20:25, Mina Almasry wrote:
> On Tue, Sep 8, 2026 at 10:35 AM Stanislav Fomichev <sdf.kernel@gmail.com> wrote:
>>
>> On 09/08, Hengbin Zhang wrote:
>>> Hi all,
>>>
>>> Up front: I'm not a kernel developer, just someone who reads net code on
>>> the side, so please bear with me if I get some terminology or conventions
>>> wrong.  I found this issue while looking at the netmem/devmem code, and
>>> with some help from AI tooling I managed to reproduce it in QEMU and
>>> manually confirm the crash — I do believe it's a real bug.  Still, treat
>>> the analysis below as my best guess rather than a certainty.  Thanks a lot
>>> for taking the time to review this!
>>>
>>> One-line summary
>>> ================
>>>
>>> A TX (DMA_TO_DEVICE) dma-buf binding created with NETDEV_CMD_BIND_TX stores
>>> binding->dev without taking a netdev reference and never registers itself
>>> with any RX queue of the device.  When the bound device is unregistered and
>>> freed while the owning netlink socket is still open, closing the socket
>>> makes netdev_nl_sock_priv_destroy() call netdev_hold()/netdev_lock() on the
>>> already freed net_device -> use-after-free.  Confirmed with KASAN and by a
>>> real page fault / kernel panic on the same code path (log below).
>>>
>>> Environment
>>> ===========
>>>
>>> - Kernel: v7.3.0-rc1-00515-g9f0346dcbea3 (commit 9f0346dcbea3), x86_64,
>>>   with KASAN enabled; relevant config knobs are in the reproducer README
>>>   (link below).
>>> - Test setup: QEMU (TCG) guest, initramfs only.  The kernel image has one
>>>   local, test-only addition: a small stub PCI driver (source in the
>>>   reproducer link below).  No core net code was modified.
>>>
>>> Steps to reproduce
>>> ==================
>>>
>>> I reproduced this in a QEMU guest.  QEMU cannot emulate any of the in-tree
>>> NETMEM_TX_DMA devices (bnxt/mlx5/gve/fbnic are real NICs), so I wrote a
>>> tiny test-only stub PCI driver (source in the reproducer link below) that
>>> provides the same device-side properties: a net_device with
>>> netmem_tx = NETMEM_TX_DMA and a DMA-capable parent.  The bug lives in core
>>> net code, not in the driver, so this scenario should equally apply to real
>>> hardware — e.g. binding TX on a real netmem-TX NIC, then removing/unbinding
>>> that NIC while the netlink socket stays open.
>>>
>>> 1. Build the stub driver from the gist into the kernel (CONFIG_STUBNET=y)
>>>    or as a module, boot the guest with "-device edu".
>>> 2. Boot with the static "init" program from the gist (repro.c, run as
>>>    PID 1 in an initramfs).  It performs, in order:
>>>    a. DMA_HEAP_IOCTL_ALLOC on /dev/dma_heap/system -> dmabuf fd;
>>>    b. NETDEV_CMD_BIND_TX on the stub device (ifindex + dmabuf fd) and keeps
>>>       the netlink socket open; BIND_TX succeeds and returns a dmabuf id;
>>>    c. writes "1" to /sys/bus/pci/devices/<bdf>/remove, i.e. unregisters and
>>>       FREES the stub net_device (refcount drops to 1 in netdev_run_todo);
>>>    d. closes the netlink socket.
>>> 3. Expected: closing the socket cleanly tears the binding down.
>>>    Actual: use-after-free, see log below.  On a kernel without KASAN the
>>>    same path takes a page fault and panics (second log excerpt below), so
>>>    this is not a KASAN-only artifact.
>>>
>>> KASAN log
>>> =========
>>>
>>>   stub0 ifindex = 2
>>>   dmabuf fd = 4
>>>   netdev family id=20 version=1
>>>   nl got: type=20 ... genl cmd=15 plen=8      <- BIND_TX ok (dmabuf id)
>>>   writing 1 to /sys/bus/pci/devices/0000:00:04.0/remove
>>>   stub0 gone (unregistered); net_device should now be freed
>>>   === CLOSING NETLINK SOCKET (expect UAF in netdev_hold) ===
>>>   [   43.895794] BUG: KASAN: slab-use-after-free in
>>>              netdev_nl_sock_priv_destroy+0x196/0x1c0
>>>   [   43.896434] Read of size 8 at addr ffff888002f06580 by task init/1
>>>   Call Trace:
>>>    netdev_nl_sock_priv_destroy+0x196/0x1c0
>>>    genl_release+0xee/0x190
>>>    netlink_release+0x715/0x13a0
>>>    __sock_release+0xa1/0x260
>>>    sock_close+0x10/0x20
>>>    __x64_sys_close+0x78/0xd0
>>>   Allocated by task 1:
>>>    __kvmalloc_node_noprof+0x202/0x5b0
>>>    alloc_netdev_mqs+0x7e/0x1270
>>>    stubnet_probe+0x120/0x3c0
>>>   Freed by task 1:
>>>    kfree+0x127/0x3b0
>>>    device_release+0xc8/0x240
>>>    kobject_put+0x101/0x1e0
>>>    netdev_run_todo+0x5a5/0xd70
>>>    unregister_netdev+0x104/0x180
>>>    stubnet_remove+0x3f/0x60
>>>    pci_device_remove+0xa6/0x180
>>>    remove_store+0xcc/0xe0
>>>   The buggy address belongs to the object ... which belongs to the cache
>>>   kmalloc-4k of size 4096; freed 4096-byte region [ffff888002f06000,
>>>   ffff888002f07000).
>>>
>>> The kernel then continued in the same function and faulted for real:
>>>
>>>   [   43.905270] BUG: unable to handle page fault for address: ffff8880b0ef9000
>>>   [   43.927189] RIP: 0010:netdev_nl_sock_priv_destroy+0xad/0x1c0
>>>   ...
>>>   [   43.938987] Kernel panic - not syncing: Fatal exception
>>>
>>> Analysis
>>> ========
>>>
>>> The flaw is a four-step chain: BIND_TX stores a raw net_device pointer
>>> without taking a reference; TX bindings never populate bound_rxqs; the
>>> unregister cleanup that clears binding->dev only matches RX queues; so when
>>> the socket is closed after the device was unregistered and freed,
>>> netdev_nl_sock_priv_destroy() runs netdev_hold()/netdev_lock() on the freed
>>> net_device.
>>>
>>> 1) Binding creation - raw pointer, no reference (net/core/devmem.c,
>>>    net_devmem_bind_dmabuf(), DMA_TO_DEVICE = TX path):
>>>
>>>     binding->dev = dev;              // raw store, NO netdev_hold()
>>>     xa_init_flags(&binding->bound_rxqs, XA_FLAGS_ALLOC);
>>>     // TX path never calls net_devmem_bind_dmabuf_to_queue()
>>>     // -> bound_rxqs stays EMPTY; the unregister cleanup (which matches
>>>     //    queues) cannot see this binding
>>>
>>> 2) Device unregister - the only binding->dev clearing point is RX-only:
>>>
>>>     // net/core/dev.c:12395 dev_memory_provider_uninstall()
>>>     for (i = 0; i < dev->real_num_rx_queues; i++)
>>>             __netif_mp_uninstall_rxq(&dev->_rx[i], &dev->_rx[i].mp_params);
>>>             // scans the device's OWN RX queues only -> TX binding invisible
>>>
>>>     // net/core/devmem.c:537 mp_dmabuf_devmem_uninstall() - the ONLY place
>>>     // that clears binding->dev:
>>>     WRITE_ONCE(binding->dev, NULL);  // reached only when a bound queue is
>>>                                      // uninstalled (RX-only); never fires
>>>                                      // for TX bindings
>>>
>>> 3) Free - the binding contributes zero references (net/core/dev.c):
>>>
>>>     // netdev_wait_allrefs_any()/netdev_run_todo()
>>>     if (netdev_refcnt_read(dev) == 1)    // no holder left; the binding holds
>>>                                          // no reference either
>>>             free_netdev(dev);            // net_device freed while the socket
>>>                                          // and its binding are still alive
>>>
>>> 4) Socket close - teardown on freed memory (net/core/netdev-genl.c:1445,
>>>    netdev_nl_sock_priv_destroy()):
>>>
>>>     dev = binding->dev;              // RX: NULL (cleared in step 2);
>>>                                      // TX: dangling
>>>     if (!dev) { unbind; continue; }  // safe branch - never taken for TX
>>>     netdev_hold(dev, ...);           // UAF #1: refcount/tracker increment on
>>>                                      // the freed net_device
>>>     netdev_lock(dev);                // UAF #2: mutex on freed memory
>>>
>>> Full reproducer materials (stub driver source, trigger program, build/run
>>> README and the complete serial log):
>>> https://gist.github.com/hharryz/119f067937448ac6e30eb31e7f08dc5a
>>>
>>> I'd be happy to keep testing, digging deeper into the analysis, and trying
>>> to put together a possible fix if that helps.  Thanks a lot for your time!
>>
>> Hmm, this looks legit, I don't think we have a proper test for this
>> condition :-/ I can try to take a stab unless someone else volunteers.
> 
> Yes I'll volunteer to clean up my mess :P
> 
> My pet LLM suggested this not-yet-tested change:
> https://termbin.com/e4d2. It's trying to detect if there is a tx
> binding attached to the netdev at unregister time and clears it.
>
Hi Mina, 
There is a similar dev binding check patch for another reason (upcoming
data-direct support) here:
https://lore.kernel.org/all/20260810175446.3945358-2-dtatulea@nvidia.com/

If you get to send it before the v2 gets sent a part of data-direct support,
could you wrap the checking part as a helper please?

Thanks,
Dragos

      parent reply	other threads:[~2026-09-09  9:17 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-08  4:31 [BUG] net: devmem: TX dma-buf binding UAF on netdev-genl socket close Hengbin Zhang
2026-09-08 17:34 ` Stanislav Fomichev
2026-09-08 18:25   ` Mina Almasry
2026-09-09  1:39     ` Stanislav Fomichev
2026-09-09  9:16     ` Dragos Tatulea [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1454a86d-5a8d-4ae7-b6a4-24f256729649@nvidia.com \
    --to=dtatulea@nvidia.com \
    --cc=almasrymina@google.com \
    --cc=ap420073@gmail.com \
    --cc=bobbyeshleman@meta.com \
    --cc=davem@davemloft.net \
    --cc=edumazet@google.com \
    --cc=kuba@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=netdev@vger.kernel.org \
    --cc=pabeni@redhat.com \
    --cc=sdf.kernel@gmail.com \
    --cc=uqbarz@gmail.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.