[RFT PATCH v3] xhci: sideband: Fix race condition in sideband unregister

Mathias Nyman posted 1 patch 3 months, 1 week ago
drivers/usb/host/xhci-sideband.c | 102 ++++++++++++++++++-------------
1 file changed, 58 insertions(+), 44 deletions(-)
[RFT PATCH v3] xhci: sideband: Fix race condition in sideband unregister
Posted by Mathias Nyman 3 months, 1 week ago
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")
Signed-off-by: Mathias Nyman <mathias.nyman@linux.intel.com>
---

v3:
  Remove extra blank line
  Add lockdep_assert_held() and comment for functions where caller must hold mutex

v2:
  use guard() and fix missing mutex_unlock as recommended by greg k-h

---
 drivers/usb/host/xhci-sideband.c | 102 ++++++++++++++++++-------------
 1 file changed, 58 insertions(+), 44 deletions(-)

diff --git a/drivers/usb/host/xhci-sideband.c b/drivers/usb/host/xhci-sideband.c
index e771a476fef2..a85f62a73313 100644
--- a/drivers/usb/host/xhci-sideband.c
+++ b/drivers/usb/host/xhci-sideband.c
@@ -73,9 +73,12 @@ xhci_ring_to_sgtable(struct xhci_sideband *sb, struct xhci_ring *ring)
 	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);
-- 
2.43.0
Re: [RFT PATCH v3] xhci: sideband: Fix race condition in sideband unregister
Posted by Greg KH 3 months, 1 week ago
On Wed, Oct 29, 2025 at 02:24:35PM +0200, Mathias Nyman wrote:
> 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")
> Signed-off-by: Mathias Nyman <mathias.nyman@linux.intel.com>
> ---

Looks good, thanks for respinning this.  I don't know if it fixes the
issue, but it looks sane :)

Reviewed-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Re: [RFT PATCH v3] xhci: sideband: Fix race condition in sideband unregister
Posted by Mathias Nyman 3 months, 1 week ago
On 10/29/25 14:51, Greg KH wrote:
> On Wed, Oct 29, 2025 at 02:24:35PM +0200, Mathias Nyman wrote:
>> 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")
>> Signed-off-by: Mathias Nyman <mathias.nyman@linux.intel.com>
>> ---
> 
> Looks good, thanks for respinning this.  I don't know if it fixes the
> issue, but it looks sane :)
> 
> Reviewed-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

Thanks for reviewing this.

I don't have the hardware to test this myself either

Thanks
Mathias
Re: [RFT PATCH v3] xhci: sideband: Fix race condition in sideband unregister
Posted by Uttkarsh Aggarwal 3 months ago

On 10/29/2025 7:22 PM, Mathias Nyman wrote:
> On 10/29/25 14:51, Greg KH wrote:
>> On Wed, Oct 29, 2025 at 02:24:35PM +0200, Mathias Nyman wrote:
>>> 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")
>>> Signed-off-by: Mathias Nyman <mathias.nyman@linux.intel.com>
>>> ---
>>
>> Looks good, thanks for respinning this.  I don't know if it fixes the
>> issue, but it looks sane :)
>>
>> Reviewed-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
> 
> Thanks for reviewing this.
> 
> I don't have the hardware to test this myself either
> 
> Thanks
> Mathias

Hi Mathias,

I have shared the patch with our OEMs and so far, have not received any 
reports of issues from them.

Thanks
Uttkarsh
Re: [RFT PATCH v3] xhci: sideband: Fix race condition in sideband unregister
Posted by Mathias Nyman 3 months ago
On 11/7/25 08:16, Uttkarsh Aggarwal wrote:
> 
> 
> On 10/29/2025 7:22 PM, Mathias Nyman wrote:
>> On 10/29/25 14:51, Greg KH wrote:
>>> On Wed, Oct 29, 2025 at 02:24:35PM +0200, Mathias Nyman wrote:
>>>> 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")
>>>> Signed-off-by: Mathias Nyman <mathias.nyman@linux.intel.com>
>>>> ---
>>>
>>> Looks good, thanks for respinning this.  I don't know if it fixes the
>>> issue, but it looks sane :)
>>>
>>> Reviewed-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
>>
>> Thanks for reviewing this.
>>
>> I don't have the hardware to test this myself either
>>
>> Thanks
>> Mathias
> 
> Hi Mathias,
> 
> I have shared the patch with our OEMs and so far, have not received any reports of issues from them.
> 
> Thanks
> Uttkarsh
> 

Thanks, I'll send it forward

-Mathias