]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
xhci: sideband: Fix race condition in sideband unregister
authorMathias Nyman <mathias.nyman@linux.intel.com>
Fri, 7 Nov 2025 16:28:18 +0000 (18:28 +0200)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Sun, 9 Nov 2025 01:54:45 +0000 (10:54 +0900)
Uttkarsh Aggarwal observed a kernel panic during sideband un-register
and found it was caused by a race condition between sideband unregister,
and creating sideband interrupters.
The issue occurrs when thread T1 runs uaudio_disconnect() and released
sb->xhci via sideband_unregister, while thread T2 simultaneously accessed
the now-NULL sb->xhci in xhci_sideband_create_interrupter() resulting in
a crash.

Ensure new endpoints or interrupter can't be added to a sidenband after
xhci_sideband_unregister() cleared the existing ones, and unlocked the
sideband mutex.
Reorganize code so that mutex is only taken and released once in
xhci_sideband_unregister(), and clear sb->vdev while mutex is taken.

Use mutex guards to reduce human unlock errors in code

Refuse to add endpoints or interrupter if sb->vdev is not set.
sb->vdev is set when sideband is created and registered.

Reported-by: Uttkarsh Aggarwal <uttkarsh.aggarwal@oss.qualcomm.com>
Closes: https://lore.kernel.org/linux-usb/20251028080043.27760-1-uttkarsh.aggarwal@oss.qualcomm.com
Fixes: de66754e9f80 ("xhci: sideband: add initial api to register a secondary interrupter entity")
Reviewed-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Signed-off-by: Mathias Nyman <mathias.nyman@linux.intel.com>
Link: https://patch.msgid.link/20251107162819.1362579-4-mathias.nyman@linux.intel.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/usb/host/xhci-sideband.c

index e771a476fef2e07e8bf5773fbd4be0b15ec9088b..a85f62a73313ab9481fb508074b8f47985eadb37 100644 (file)
@@ -73,9 +73,12 @@ err:
        return NULL;
 }
 
+/* Caller must hold sb->mutex */
 static void
 __xhci_sideband_remove_endpoint(struct xhci_sideband *sb, struct xhci_virt_ep *ep)
 {
+       lockdep_assert_held(&sb->mutex);
+
        /*
         * Issue a stop endpoint command when an endpoint is removed.
         * The stop ep cmd handler will handle the ring cleanup.
@@ -86,6 +89,25 @@ __xhci_sideband_remove_endpoint(struct xhci_sideband *sb, struct xhci_virt_ep *e
        sb->eps[ep->ep_index] = NULL;
 }
 
+/* Caller must hold sb->mutex */
+static void
+__xhci_sideband_remove_interrupter(struct xhci_sideband *sb)
+{
+       struct usb_device *udev;
+
+       lockdep_assert_held(&sb->mutex);
+
+       if (!sb->ir)
+               return;
+
+       xhci_remove_secondary_interrupter(xhci_to_hcd(sb->xhci), sb->ir);
+       sb->ir = NULL;
+       udev = sb->vdev->udev;
+
+       if (udev->state != USB_STATE_NOTATTACHED)
+               usb_offload_put(udev);
+}
+
 /* sideband api functions */
 
 /**
@@ -131,14 +153,16 @@ xhci_sideband_add_endpoint(struct xhci_sideband *sb,
        struct xhci_virt_ep *ep;
        unsigned int ep_index;
 
-       mutex_lock(&sb->mutex);
+       guard(mutex)(&sb->mutex);
+
+       if (!sb->vdev)
+               return -ENODEV;
+
        ep_index = xhci_get_endpoint_index(&host_ep->desc);
        ep = &sb->vdev->eps[ep_index];
 
-       if (ep->ep_state & EP_HAS_STREAMS) {
-               mutex_unlock(&sb->mutex);
+       if (ep->ep_state & EP_HAS_STREAMS)
                return -EINVAL;
-       }
 
        /*
         * Note, we don't know the DMA mask of the audio DSP device, if its
@@ -148,14 +172,11 @@ xhci_sideband_add_endpoint(struct xhci_sideband *sb,
         * and let this function add the endpoint and allocate the ring buffer
         * with the smallest common DMA mask
         */
-       if (sb->eps[ep_index] || ep->sideband) {
-               mutex_unlock(&sb->mutex);
+       if (sb->eps[ep_index] || ep->sideband)
                return -EBUSY;
-       }
 
        ep->sideband = sb;
        sb->eps[ep_index] = ep;
-       mutex_unlock(&sb->mutex);
 
        return 0;
 }
@@ -180,18 +201,16 @@ xhci_sideband_remove_endpoint(struct xhci_sideband *sb,
        struct xhci_virt_ep *ep;
        unsigned int ep_index;
 
-       mutex_lock(&sb->mutex);
+       guard(mutex)(&sb->mutex);
+
        ep_index = xhci_get_endpoint_index(&host_ep->desc);
        ep = sb->eps[ep_index];
 
-       if (!ep || !ep->sideband || ep->sideband != sb) {
-               mutex_unlock(&sb->mutex);
+       if (!ep || !ep->sideband || ep->sideband != sb)
                return -ENODEV;
-       }
 
        __xhci_sideband_remove_endpoint(sb, ep);
        xhci_initialize_ring_info(ep->ring);
-       mutex_unlock(&sb->mutex);
 
        return 0;
 }
@@ -316,28 +335,25 @@ xhci_sideband_create_interrupter(struct xhci_sideband *sb, int num_seg,
        if (!sb || !sb->xhci)
                return -ENODEV;
 
-       mutex_lock(&sb->mutex);
-       if (sb->ir) {
-               ret = -EBUSY;
-               goto out;
-       }
+       guard(mutex)(&sb->mutex);
+
+       if (!sb->vdev)
+               return -ENODEV;
+
+       if (sb->ir)
+               return -EBUSY;
 
        sb->ir = xhci_create_secondary_interrupter(xhci_to_hcd(sb->xhci),
                                                   num_seg, imod_interval,
                                                   intr_num);
-       if (!sb->ir) {
-               ret = -ENOMEM;
-               goto out;
-       }
+       if (!sb->ir)
+               return -ENOMEM;
 
        udev = sb->vdev->udev;
        ret = usb_offload_get(udev);
 
        sb->ir->ip_autoclear = ip_autoclear;
 
-out:
-       mutex_unlock(&sb->mutex);
-
        return ret;
 }
 EXPORT_SYMBOL_GPL(xhci_sideband_create_interrupter);
@@ -352,21 +368,12 @@ EXPORT_SYMBOL_GPL(xhci_sideband_create_interrupter);
 void
 xhci_sideband_remove_interrupter(struct xhci_sideband *sb)
 {
-       struct usb_device *udev;
-
-       if (!sb || !sb->ir)
+       if (!sb)
                return;
 
-       mutex_lock(&sb->mutex);
-       xhci_remove_secondary_interrupter(xhci_to_hcd(sb->xhci), sb->ir);
-
-       sb->ir = NULL;
-       udev = sb->vdev->udev;
+       guard(mutex)(&sb->mutex);
 
-       if (udev->state != USB_STATE_NOTATTACHED)
-               usb_offload_put(udev);
-
-       mutex_unlock(&sb->mutex);
+       __xhci_sideband_remove_interrupter(sb);
 }
 EXPORT_SYMBOL_GPL(xhci_sideband_remove_interrupter);
 
@@ -465,6 +472,7 @@ EXPORT_SYMBOL_GPL(xhci_sideband_register);
 void
 xhci_sideband_unregister(struct xhci_sideband *sb)
 {
+       struct xhci_virt_device *vdev;
        struct xhci_hcd *xhci;
        int i;
 
@@ -473,17 +481,23 @@ xhci_sideband_unregister(struct xhci_sideband *sb)
 
        xhci = sb->xhci;
 
-       mutex_lock(&sb->mutex);
-       for (i = 0; i < EP_CTX_PER_DEV; i++)
-               if (sb->eps[i])
-                       __xhci_sideband_remove_endpoint(sb, sb->eps[i]);
-       mutex_unlock(&sb->mutex);
+       scoped_guard(mutex, &sb->mutex) {
+               vdev = sb->vdev;
+               if (!vdev)
+                       return;
+
+               for (i = 0; i < EP_CTX_PER_DEV; i++)
+                       if (sb->eps[i])
+                               __xhci_sideband_remove_endpoint(sb, sb->eps[i]);
 
-       xhci_sideband_remove_interrupter(sb);
+               __xhci_sideband_remove_interrupter(sb);
+
+               sb->vdev = NULL;
+       }
 
        spin_lock_irq(&xhci->lock);
        sb->xhci = NULL;
-       sb->vdev->sideband = NULL;
+       vdev->sideband = NULL;
        spin_unlock_irq(&xhci->lock);
 
        kfree(sb);