From: Shuah Khan <skhan@linuxfoundation.org>
To: "João Moreira Fernandes" <joao.fernandes@ist.utl.pt>,
"Valentina Manea" <valentina.manea.m@gmail.com>,
"Shuah Khan" <shuah@kernel.org>,
"Greg Kroah-Hartman" <gregkh@linuxfoundation.org>
Cc: Hongren Zheng <i@zenithal.me>,
linux-usb@vger.kernel.org, linux-kernel@vger.kernel.org,
Shuah Khan <skhan@linuxfoundation.org>
Subject: Re: [PATCH] usbip: flush the event handler in usbip_stop_eh() to fix use-after-free
Date: Sat, 26 Sep 2026 13:57:19 -0600 [thread overview]
Message-ID: <6a97a599-db32-4fbc-90ca-e052cc23d317@linuxfoundation.org> (raw)
In-Reply-To: <20260715133855.2312018-1-joao.fernandes@ist.utl.pt>
On 7/15/26 07:38, João Moreira Fernandes wrote:
> usbip_stop_eh() is used by the stub, vhci and vudc sides to wait for the
> event handler to finish with a usbip_device before the caller tears it
> down. It waits only on the event bitmask:
>
> wait_event_interruptible(ud->eh_waitq, !(ud->event & ~USBIP_EH_BYE));
>
> but event_handler() clears those bits with unset_event() *before* its
> final dereferences of ud:
>
> if (ud->event & USBIP_EH_SHUTDOWN) {
> ud->eh_ops.shutdown(ud);
> unset_event(ud, USBIP_EH_SHUTDOWN); /* wait may now return */
> }
> ...
> mutex_unlock(&ud->sysfs_lock); /* still touches ud */
> wake_up(&ud->eh_waitq); /* still touches ud */
>
> Once the last non-BYE bit is cleared the waiter can wake and the caller
> proceeds to free ud. For the stub side, stub_disconnect() ->
> shutdown_busid() calls usbip_stop_eh() and then stub_device_free() frees
> the stub_device that embeds ud, while event_handler() is still executing
> mutex_unlock(&ud->sysfs_lock) and wake_up(&ud->eh_waitq), and may still
> mutex_lock(&ud->sysfs_lock) on a second queued event for the same ud.
> The result is a use-after-free in the usbip_event workqueue.
>
> It is readily triggered by a reverse/exported-device teardown that
> unbinds usbip-host while the importer is still attached: the socket EOF
> queues SDEV_EVENT_ERROR_TCP (SHUTDOWN|RESET) from the stub_rx thread at
> the same moment stub_disconnect() queues SDEV_EVENT_REMOVED
> (SHUTDOWN|BYE), so the handler re-runs shutdown/reset on ud in the exact
> window where it is being freed.
>
> Reproduced under KASAN on v6.12.95 (usbip built as vhci_hcd importer and
> usbip-host exporter on two separate VMs). The freed object is the
> stub_device, freed by the unbind write and then read by the event
> workqueue:
>
> BUG: KASAN: slab-use-after-free in event_handler+0x2ba/0x3a0
> Read of size 8 at addr ffff888005cc6058 by task kworker/u8:5/63
> Workqueue: usbip_event event_handler
> Call Trace:
> event_handler+0x2ba/0x3a0
> process_one_work+0x5c2/0xfd0
> worker_thread+0x49d/0xb00
> kthread+0x246/0x300
>
> Allocated by task 1657:
> stub_probe+0xf0/0xb00
> usb_probe_device+0xaa/0x2e0
> bind_store+0xce/0x140
> vfs_write+0x87c/0xd70
>
> Freed by task 1660:
> kfree+0x1a4/0x3a0
> stub_disconnect+0x21e/0x340
> usb_unbind_device+0x6b/0x170
> device_release_driver_internal+0x384/0x550
> unbind_store+0xdb/0xf0
> vfs_write+0x87c/0xd70
>
> The same run produced 37 KASAN splats against the one freed object,
> reached through every place the handler still touches ud after clearing
> the event bits: mutex_lock() on ud->sysfs_lock, the eh_waitq wake
> (__wake_up_common / finish_wait / prepare_to_wait_event), and
> stub_shutdown_connection() called from event_handler().
>
> Waiting on the event bits cannot close the window, because the handler's
> last accesses to ud are inherently after the bit is cleared. Instead,
> flush the event work in usbip_stop_eh() so the handler has fully returned
> before the caller is allowed to free ud. Every *_EVENT_REMOVED sets
> USBIP_EH_BYE, after which usbip_event_add() no longer queues new work for
> that ud, so flush_work() only has to wait out the in-flight run. Guard
> the flush with usbip_in_eh() so it is skipped in the (stub reset) path
> that runs from the handler itself, and move the usbip_work declaration
> above usbip_stop_eh() so it is in scope. None of the three usbip_stop_eh()
> callers hold ud->sysfs_lock, so flushing the worker (which takes it)
> cannot deadlock.
>
> With the patch applied, the same KASAN reproducer runs the teardown
> suite repeatedly with zero KASAN reports.
Hmm. Did you test this change with usbip host and client? What kind of
testing was done on this change?
thanks,
-- Shuah
prev parent reply other threads:[~2026-09-26 19:57 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-15 13:38 João Moreira Fernandes
2026-09-26 19:57 ` Shuah Khan [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=6a97a599-db32-4fbc-90ca-e052cc23d317@linuxfoundation.org \
--to=skhan@linuxfoundation.org \
--cc=gregkh@linuxfoundation.org \
--cc=i@zenithal.me \
--cc=joao.fernandes@ist.utl.pt \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-usb@vger.kernel.org \
--cc=shuah@kernel.org \
--cc=valentina.manea.m@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®