From: Mathias Nyman <mathias.nyman@linux.intel.com>
To: YuanZhe Liu <25031212351@stu.xidian.edu.cn>,
Mathias Nyman <mathias.nyman@intel.com>,
Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: linux-usb@vger.kernel.org, linux-kernel@vger.kernel.org,
stable@vger.kernel.org, Bin Yu <byu@xidian.edu.cn>,
Cheng Wen <wencheng@xidian.edu.cn>
Subject: Re: [PATCH v2] usb: xhci: Fix use-after-free race in xhci_stop_device()
Date: Tue, 3 Nov 2026 16:56:02 +0200 [thread overview]
Message-ID: <3ca5cced-f429-42d9-9bbb-f5111acd9665@linux.intel.com> (raw)
In-Reply-To: <20261026123628.7493-1-25031212351@stu.xidian.edu.cn>
Hi
On 10/26/26 14:36, YuanZhe Liu wrote:
> A KASAN slab-use-after-free read was observed in xhci_stop_device():
>
> BUG: KASAN: slab-use-after-free in xhci_stop_device.constprop.0.isra.0+0x395/0x430
> drivers/usb/host/xhci-hub.c:479
> Read of size 8 at addr ffff88804e85d2f0
>
> Freed by task 585700:
> kfree+0xf8/0x390 mm/slub.c:6566
> xhci_free_virt_device+0x3f/0x50 drivers/usb/host/xhci-mem.c:881
> xhci_free_dev+0x286/0x350 drivers/usb/host/xhci.c:4131
> usb_disconnect+0x6b6/0x910 drivers/usb/core/hub.c:2391
> ...
> usbdev_ioctl+0x6f05/0x86d0 drivers/usb/core/devio.c:2826
>
> Allocated by task 572283:
> xhci_alloc_virt_device+0x171/0xb70 drivers/usb/host/xhci-mem.c:986
> xhci_alloc_dev+0x475/0x840 drivers/usb/host/xhci.c:4271
> usb_alloc_dev+0xe8/0xdc0 drivers/usb/core/usb.c:661
> hub_event+0x1c82/0x4050 drivers/usb/core/hub.c:5953
>
> xhci_stop_device() reads xhci->devs[slot_id] without xhci->lock and
> keeps using the pointer across xhci_alloc_command(GFP_NOIO), which may
> sleep. All callers (the port suspend and bus suspend paths in
> xhci_hub_control() and xhci_bus_suspend()) drop xhci->lock before
> calling it, so a concurrent usb_disconnect() -> xhci_free_dev() can
> clear xhci->devs[slot_id] and free virt_dev under xhci->lock in that
> window. The endpoint loop then dereferences the freed virt_dev.
>
> commit 88716a93766b ("usb: xhci: Fix panic if disconnect") added a
> NULL check for this case, but that only helps when the device was
> already freed before the check; it does not close the window.
>
> Re-check xhci->devs[slot_id] against the pointer fetched earlier after
> taking xhci->lock and bail out if it no longer matches, or if the
> device is already being torn down. The tracepoint reloads
> virt_dev->udev, so it moves under the lock and only runs while the
> device is still attached.
>
> xhci_free_dev() clears virt_dev->udev without xhci->lock, and only
> later detaches the device from xhci->devs[] in xhci_free_virt_device(),
> also under the lock. Take xhci->lock when clearing ->udev so that
> xhci_stop_device() can rely on it while holding the same lock.
> xhci_free_virt_device() frees the device under xhci->lock, so a
> matching pointer while holding the lock keeps virt_dev alive until
> xhci_stop_device() drops the lock, after which virt_dev is not used
> anymore.
>
> Fixes: be88fe4f4dda93e3 ("USB: xHCI: port power management implementation")
> Cc: stable@vger.kernel.org
> Co-developed-by: Bin Yu <byu@xidian.edu.cn>
> Signed-off-by: Bin Yu <byu@xidian.edu.cn>
> Co-developed-by: Cheng Wen <wencheng@xidian.edu.cn>
> Signed-off-by: Cheng Wen <wencheng@xidian.edu.cn>
> Signed-off-by: YuanZhe Liu <25031212351@stu.xidian.edu.cn>
Thanks for the patch, good catch.
I have some small minor adjustments that would make final code a bit cleaner
> ---
> Changes since v1:
> - xhci_free_dev(): clear virt_dev->udev under xhci->lock, so that
> checking it under the same lock actually synchronizes with the
> store.
> - xhci_stop_device(): bail out with -ENODEV once ->udev has been
> cleared, instead of queueing stop-endpoint commands for a slot
> that is already being disabled.
>
> drivers/usb/host/xhci-hub.c | 19 +++++++++++++++++--
> drivers/usb/host/xhci.c | 3 +++
> 2 files changed, 20 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c
> index b0264bd8577a..f4401f2fcea0 100644
> --- a/drivers/usb/host/xhci-hub.c
> +++ b/drivers/usb/host/xhci-hub.c
> @@ -467,13 +467,28 @@ static int xhci_stop_device(struct xhci_hcd *xhci, int slot_id, int suspend)
> if (!virt_dev)
> return -ENODEV;
>
> - trace_xhci_stop_device(virt_dev);
> -
> cmd = xhci_alloc_command(xhci, true, GFP_NOIO);
> if (!cmd)
> return -ENOMEM;
>
> spin_lock_irqsave(&xhci->lock, flags);
> +
> + /*
> + * Callers drop xhci->lock before calling us and allocating cmd
> + * above can sleep, so xhci_free_dev() may have detached and freed
> + * the device in the meantime. It removes the device from
> + * xhci->devs[slot_id] and clears ->udev under xhci->lock, so while
> + * the lock is held a matching, still-attached device stays alive
> + * and keeps its udev.
> + */
Comment above is not needed, can be removed
> + if (xhci->devs[slot_id] != virt_dev || !virt_dev->udev) {
> + spin_unlock_irqrestore(&xhci->lock, flags);
> + xhci_free_command(xhci, cmd);
> + return -ENODEV;
> + }
No point in keeping the 'virt_dev = xhci->devs[slot_id]' before taking the lock.
Just move that part inside the lock as well:
virt_dev = xhci->devs[slot_id];
if (!virt_dev || !virt_dev->udev) {
unlock
free command
return -ENODEV
}
> +
> + trace_xhci_stop_device(virt_dev);
> +
> for (i = LAST_EP_INDEX; i > 0; i--) {
> if (virt_dev->eps[i].ring && virt_dev->eps[i].ring->dequeue) {
> struct xhci_ep_ctx *ep_ctx;
> diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c
> index 5c759ad28199..9e375eb48f38 100644
> --- a/drivers/usb/host/xhci.c
> +++ b/drivers/usb/host/xhci.c
> @@ -4128,9 +4128,12 @@ static void xhci_free_dev(struct usb_hcd *hcd, struct usb_device *udev)
> trace_xhci_free_dev(slot_ctx);
>
> /* Stop any wayward timer functions (which may grab the lock) */
> + spin_lock_irqsave(&xhci->lock, flags);
> for (i = 0; i < 31; i++)
> virt_dev->eps[i].ep_state &= ~EP_STOP_CMD_PENDING;
> virt_dev->udev = NULL;
> + spin_unlock_irqrestore(&xhci->lock, flags);
> +
Should work, we are now using virt_dev->udev as a flag to tell xhci_stop_device() to
not queue any stop endpoint commands as we know a 'disable slot' command will be
queued shortly.
Another option would be to add an actual 'disable_slot_pending' flag to struct
xhci_virt_device, and set it in xhci_disable_slot() when we really are queuing the
disable slot command with lock held.
While looking at this I realize that xhci_disable_and_free_slot() might also race
with xhci_stop_device(), but that change doesn't need to be part of this patch.
Thanks
Mathias
next prev parent reply other threads:[~2026-11-03 14:56 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-26 12:36 YuanZhe Liu
2026-10-30 6:01 ` YuanZhe Liu
2026-11-03 14:56 ` Mathias Nyman [this message]
2026-11-03 16:01 ` [PATCH v3] " YuanZhe Liu
2026-11-03 23:00 ` Michal Pecio
2026-11-04 1:32 ` [PATCH v4] " YuanZhe Liu
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=3ca5cced-f429-42d9-9bbb-f5111acd9665@linux.intel.com \
--to=mathias.nyman@linux.intel.com \
--cc=25031212351@stu.xidian.edu.cn \
--cc=byu@xidian.edu.cn \
--cc=gregkh@linuxfoundation.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-usb@vger.kernel.org \
--cc=mathias.nyman@intel.com \
--cc=stable@vger.kernel.org \
--cc=wencheng@xidian.edu.cn \
/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®