[PATCH RFC] usbip: fix use-after-free in event handler during device teardown
"syzbot" <[email protected]>
| Newsgroups | dev.linux.lists.syzbot |
|---|---|
| Message-ID | <[email protected]> |
During device teardown (e.g., when unbinding a device), `vhci_stop()` calls
`usbip_stop_eh()` to wait for the `event_handler` workqueue to finish
processing shutdown events. `usbip_stop_eh()` uses
`wait_event_interruptible()`. If the user-space process triggering the
teardown receives a signal, the wait is aborted immediately. Since the
return value is ignored, the teardown path proceeds to free the `vhci_hcd`
(and the embedded `usbip_device`) while the `event_handler` is still
actively using it, resulting in a use-after-free.
Even if `wait_event_interruptible()` is changed to `wait_event()`, a
secondary race exists. The `event_handler` pops events from a global
`event_list` before processing them. If `usbip_event_add()` is called
concurrently, it might allocate and add a second event to the list because
the first one was already popped. When `event_handler` finishes processing
the first event, it clears the event flags and wakes up `usbip_stop_eh()`.
`usbip_stop_eh()` sees the flags are cleared, returns, and the device is
freed. However, the `event_handler` then loops back, pops the second event
from the list, and attempts to access the now-freed device structure.
Fix this by changing `wait_event_interruptible()` to `wait_event()` so that
the teardown path waits uninterruptibly, guaranteeing that background tasks
have logically finished before memory is freed. Additionally, add a
`flush_workqueue(usbip_queue)` call immediately after `wait_event()` to act
as a strict synchronization barrier, ensuring that the worker thread has
completely drained the `event_list` and exited the function before
`usbip_stop_eh()` returns and the device is freed. The declarations of
`usbip_queue` and `usbip_work` are moved above `usbip_stop_eh()` to allow
calling `flush_workqueue()`.
BUG: KASAN: slab-use-after-free in __raw_spin_lock_irqsave
include/linux/spinlock_api_smp.h:132 [inline]
BUG: KASAN: slab-use-after-free in _raw_spin_lock_irqsave+0x40/0x60
kernel/locking/spinlock.c:166
Read of size 1 at addr ffff88811cecc478 by task kworker/u8:5/6058
Call Trace:
<TASK>
lock_acquire+0x84/0x350 kernel/locking/lockdep.c:5842
__raw_spin_lock_irqsave include/linux/spinlock_api_smp.h:132 [inline]
_raw_spin_lock_irqsave+0x40/0x60 kernel/locking/spinlock.c:166
unset_event drivers/usb/usbip/usbip_event.c:35 [inline]
event_handler+0x2b0/0x4b0 drivers/usb/usbip/usbip_event.c:80
process_one_work kernel/workqueue.c:3322 [inline]
process_scheduled_works+0xa8e/0x14e0 kernel/workqueue.c:3405
worker_thread+0x92d/0xe10 kernel/workqueue.c:3486
kthread+0x388/0x470 kernel/kthread.c:436
ret_from_fork+0x514/0xb70 arch/x86/kernel/process.c:158
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
</TASK>
Fixes: bb7871ad99ea ("usbip: event handler as one thread")
Assisted-by: Gemini:gemini-3.5-flash Gemini:gemini-3.1-pro-preview syzbot
Reported-by: [email protected]
Closes: https://syzkaller.appspot.com/bug?extid=31117fde582fe6a0cd62
Link: https://syzkaller.appspot.com/ai_job?id=48067f76-5bee-4b9c-a4d6-69685bd6f80d
To: "Greg Kroah-Hartman" <[email protected]>
To: <[email protected]>
To: "Shuah Khan" <[email protected]>
To: "Valentina Manea" <[email protected]>
To: "Nobuo Iwata" <[email protected]>
Cc: "Hongren Zheng" <[email protected]>
Cc: <[email protected]>
---
diff --git a/drivers/usb/usbip/usbip_event.c b/drivers/usb/usbip/usbip_event.c
index 0e00c2d00..e498102e8 100644
--- a/drivers/usb/usbip/usbip_event.c
+++ b/drivers/usb/usbip/usbip_event.c
@@ -105,6 +105,11 @@ int usbip_start_eh(struct usbip_device *ud)
}
EXPORT_SYMBOL_GPL(usbip_start_eh);
+#define WORK_QUEUE_NAME "usbip_event"
+
+static struct workqueue_struct *usbip_queue;
+static DECLARE_WORK(usbip_work, event_handler);
+
void usbip_stop_eh(struct usbip_device *ud)
{
unsigned long pending = ud->event & ~USBIP_EH_BYE;
@@ -115,16 +120,12 @@ void usbip_stop_eh(struct usbip_device *ud)
if (pending)
usbip_dbg_eh("usbip_eh waiting completion %lx\n", pending);
- wait_event_interruptible(ud->eh_waitq, !(ud->event & ~USBIP_EH_BYE));
+ wait_event(ud->eh_waitq, !(ud->event & ~USBIP_EH_BYE));
+ flush_workqueue(usbip_queue);
usbip_dbg_eh("usbip_eh has stopped\n");
}
EXPORT_SYMBOL_GPL(usbip_stop_eh);
-#define WORK_QUEUE_NAME "usbip_event"
-
-static struct workqueue_struct *usbip_queue;
-static DECLARE_WORK(usbip_work, event_handler);
-
int usbip_init_eh(void)
{
usbip_queue = create_singlethread_workqueue(WORK_QUEUE_NAME);
base-commit: 075b74841bd0065a3bda3440873c747938e69b68
--
This is an AI-generated patch subject to moderation.
Reply with '#syz upstream' to Sign-off the patch as a human author
and send it to the upstream kernel mailing lists.
Reply with '#syz reject' to reject it ('#syz unreject' to undo).
See https://goo.gle/syzbot-ai-patches for information about AI-generated patches.
You can comment on the patch as usual, syzbot will try to address
the comments and send a new version of the patch if necessary.
syzbot engineers can be reached at [email protected].