mirror of https://lore.kernel.org/lkml/
 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 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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®