[PATCH] xhci: sideband: check vdev liveness before removing endpoints on unregister

From: 胡连勤

Date: Mon Sep 07 2026 - 09:24:46 EST


xhci_sideband_unregister() assumes the virtual device (vdev) is still
alive when iterating sideband endpoints and issuing stop endpoint
commands. However, xhci_disable_and_free_slot() may have already freed
vdev and its out_ctx before xhci_sideband_unregister() is invoked.

This happens when xhci_setup_device() gets COMP_USB_TRANSACTION_ERROR
(e.g. device not responding to setup address during bus reset recovery),
causing vdev to be freed before xhci_sideband_unregister() is called:

hub_event()
xhci_setup_device() <-- COMP_USB_TRANSACTION_ERROR
xhci_disable_and_free_slot()
xhci_free_virt_device()
kfree(out_ctx), kfree(vdev)
xhci->devs[slot_id] = NULL
...
usb_disconnect()
uaudio_disconnect()
xhci_sideband_unregister()
xhci_stop_endpoint_sync()
xhci_get_ep_ctx() <-- CRASH (deref freed out_ctx)

Unable to handle kernel paging request at virtual address dead000000000122
Call trace:
xhci_get_ep_ctx+0x0/0x38
xhci_sideband_unregister+0x68/0xf0
uaudio_disconnect+0x70/0x144
usb_audio_disconnect+0x7c/0x268
usb_unbind_interface+0x13c/0x340
device_release_driver_internal+0x1c4/0x2bc
device_release_driver+0x18/0x28
bus_remove_device+0x158/0x170
device_del+0x1c8/0x320
usb_disable_device+0x84/0x190
usb_disconnect+0xe8/0x338
hub_event+0xbd8/0x19ac
process_scheduled_works+0x200/0x9d8
worker_thread+0x154/0x3b0
kthread+0x11c/0x1a0

Fix this by caching the slot_id in the sideband structure at
registration time, then checking under xhci->lock whether
xhci->devs[slot_id] still matches sb->vdev before issuing stop
endpoint commands. If vdev has been freed, skip endpoint cleanup
entirely - the xHCI has already disabled the slot.
The interrupter is still removed as it does not depend on vdev.

The slot_id is cached in sb->slot_id rather than read from vdev at
unregister time because vdev may already be freed, making
sb->vdev->slot_id a dangling dereference.

Fixes: de66754e9f80 ("xhci: sideband: add initial api to register a secondary interrupter entity")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Lianqin Hu <hulianqin@xxxxxxxx>
---
drivers/usb/host/xhci-sideband.c | 35 ++++++++++++++++++++++++++-----
include/linux/usb/xhci-sideband.h | 2 ++
2 files changed, 32 insertions(+), 5 deletions(-)

diff --git a/drivers/usb/host/xhci-sideband.c b/drivers/usb/host/xhci-sideband.c
index a5deeee4d5dc..1a83f03c2cf6 100644
--- a/drivers/usb/host/xhci-sideband.c
+++ b/drivers/usb/host/xhci-sideband.c
@@ -437,6 +437,7 @@ xhci_sideband_register(struct usb_interface *intf, enum xhci_sideband_type type,

sb->xhci = xhci;
sb->vdev = vdev;
+ sb->slot_id = udev->slot_id;
sb->intf = intf;
sb->type = type;
sb->notify_client = notify_client;
@@ -464,6 +465,7 @@ xhci_sideband_unregister(struct xhci_sideband *sb)
struct xhci_virt_device *vdev;
struct xhci_hcd *xhci;
int i;
+ bool vdev_alive = false;

if (!sb)
return;
@@ -473,11 +475,32 @@ xhci_sideband_unregister(struct xhci_sideband *sb)
scoped_guard(mutex, &sb->mutex) {
vdev = sb->vdev;
if (!vdev)
- return;
+ goto out;
+
+ /*
+ * Check if vdev is still the active device for its slot.
+ * xhci_disable_and_free_slot() may have already freed vdev
+ * and cleared xhci->devs[slot_id] (e.g. on
+ * COMP_USB_TRANSACTION_ERROR during bus reset recovery in
+ * xhci_setup_device). In that case sb->vdev->out_ctx is
+ * dangling and issuing stop endpoint commands would crash
+ * with a paging request at LIST_POISON1 + offset. If vdev
+ * is gone, just clear the sideband pointers without
+ * touching xHCI.
+ */
+ spin_lock_irq(&xhci->lock);
+ vdev_alive = (xhci->devs[sb->slot_id] == vdev);
+ spin_unlock_irq(&xhci->lock);

- for (i = 0; i < EP_CTX_PER_DEV; i++)
- if (sb->eps[i])
- __xhci_sideband_remove_endpoint(sb, sb->eps[i]);
+ if (vdev_alive) {
+ for (i = 0; i < EP_CTX_PER_DEV; i++)
+ if (sb->eps[i])
+ __xhci_sideband_remove_endpoint(sb, sb->eps[i]);
+ } else {
+ xhci_warn(xhci, "sideband unreg: vdev slot %d already freed, skipping ep cleanup\n",
+ sb->slot_id);
+ for (i = 0; i < EP_CTX_PER_DEV; i++)
+ sb->eps[i] = NULL;
+ }

__xhci_sideband_remove_interrupter(sb);

@@ -486,9 +509,11 @@ xhci_sideband_unregister(struct xhci_sideband *sb)

spin_lock_irq(&xhci->lock);
sb->xhci = NULL;
- vdev->sideband = NULL;
+ if (vdev_alive)
+ vdev->sideband = NULL;
spin_unlock_irq(&xhci->lock);

+out:
kfree(sb);
}
EXPORT_SYMBOL_GPL(xhci_sideband_unregister);
diff --git a/include/linux/usb/xhci-sideband.h b/include/linux/usb/xhci-sideband.h
index 005257085dcb..9d86b1daee27 100644
--- a/include/linux/usb/xhci-sideband.h
+++ b/include/linux/usb/xhci-sideband.h
@@ -40,6 +40,7 @@ struct xhci_sideband_event {
* struct xhci_sideband - representation of a sideband accessed usb device.
* @xhci: The xhci host controller the usb device is connected to
* @vdev: the usb device accessed via sideband
+ * @slot_id: cached slot ID for vdev validity checking
* @eps: array of endpoints controlled via sideband
* @ir: event handling and buffer for sideband accessed device
* @type: xHCI sideband type
@@ -52,6 +53,7 @@ struct xhci_sideband_event {
struct xhci_sideband {
struct xhci_hcd *xhci;
struct xhci_virt_device *vdev;
+ int slot_id;
struct xhci_virt_ep *eps[EP_CTX_PER_DEV];
struct xhci_interrupter *ir;
enum xhci_sideband_type type;
--
2.48.1