summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMiguel PeƱaranda <mig.penaranda07@gmail.com>2026-08-10 14:12:09 +0200
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2026-09-02 14:28:36 +0200
commitbd4bffc621a8cb2f4d9ed9b6447415de524a3bef (patch)
treeaa92e010fe09b0f39653a0957c38b71ffd8a3cbd
parentbb572801290e25ec1c4753d14af35777303f5d6b (diff)
downloadlinux-stable-bd4bffc621a8cb2f4d9ed9b6447415de524a3bef.tar.gz
linux-stable-bd4bffc621a8cb2f4d9ed9b6447415de524a3bef.zip
usb: usbfs: fix use-after-free of usb_device in usbdev_release()
commit 0dd68b5d01d022fc9c5e71c82a82b0a94d3d0671 upstream. usbdev_release() drops its reference to the struct usb_device before draining the list of completed async URBs, but that drain path reads back through the same object: free_async() calls dec_usb_memory_use_count() for any URB whose buffer came from the usbfs mmap() region, and its first statement is bus_to_hcd(ps->dev->bus). After a disconnect the usbfs reference can be the last one, in which case usb_put_dev() frees the device and the subsequent loop reads offset 80 of freed memory and uses the result as a struct usb_hcd *, which hcd_buffer_free_pages() then dereferences. This is reachable by an unprivileged process that has read/write access to a /dev/bus/usb node: mmap() the fd, submit one URB with a buffer inside the mapping, wait for the device to be unplugged, then munmap() and close(). It reproduces on every attempt rather than being a race, because a live MAP_SHARED vma holds a reference on the struct file, so usbdev_release() cannot run until the last vma is gone and the freeing branch of dec_usb_memory_use_count() is always taken. BUG: KASAN: slab-use-after-free in dec_usb_memory_use_count+0x3ae/0x410 Read of size 8 at addr ffff8880122ee050 by task poc/769 CPU: 1 UID: 1000 PID: 769 Comm: poc Tainted: G B 6.12.94 #3 Call Trace: dec_usb_memory_use_count+0x3ae/0x410 free_async+0x2aa/0x4f0 usbdev_release+0x375/0x460 __fput+0x3ea/0xb50 __x64_sys_close+0x86/0x100 Allocated by task 11: usb_alloc_dev+0x55/0xd90 hub_event+0x2524/0x43d0 Freed by task 769: kfree+0x121/0x360 device_release+0xd2/0x280 usb_put_dev+0x23/0x30 usbdev_release+0x2d8/0x460 Release the device reference after the drain loop instead. Nothing between the two points requires it to have been dropped. Fixes: f7d34b445abc ("USB: Add support for usbfs zerocopy.") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-5 Signed-off-by: Miguel PeƱaranda <mig.penaranda07@gmail.com> Reviewed-by: Alan Stern <stern@rowland.harvard.edu> Link: https://patch.msgid.link/20260810121209.795089-1-mig.penaranda07@gmail.com Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
-rw-r--r--drivers/usb/core/devio.c2
1 files changed, 1 insertions, 1 deletions
diff --git a/drivers/usb/core/devio.c b/drivers/usb/core/devio.c
index 4184cd65a6aa..af15c41e4947 100644
--- a/drivers/usb/core/devio.c
+++ b/drivers/usb/core/devio.c
@@ -1111,7 +1111,6 @@ static int usbdev_release(struct inode *inode, struct file *file)
if (!ps->suspend_allowed)
usb_autosuspend_device(dev);
usb_unlock_device(dev);
- usb_put_dev(dev);
put_pid(ps->disc_pid);
put_cred(ps->cred);
@@ -1120,6 +1119,7 @@ static int usbdev_release(struct inode *inode, struct file *file)
free_async(as);
as = async_getcompleted(ps);
}
+ usb_put_dev(dev);
kfree(ps);
return 0;