* [PATCH] usb: dwc3: Fix race condition between concurrent dwc3_remove_requests() call paths
@ 2025-10-28 8:05 Manish Nagar
2025-10-28 8:46 ` Greg Kroah-Hartman
2025-10-29 23:47 ` Thinh Nguyen
0 siblings, 2 replies; 5+ messages in thread
From: Manish Nagar @ 2025-10-28 8:05 UTC (permalink / raw)
To: Greg Kroah-Hartman, Thinh Nguyen; +Cc: linux-usb, linux-kernel
This patch addresses a race condition caused by unsynchronized
execution of multiple call paths invoking `dwc3_remove_requests()`,
leading to premature freeing of USB requests and subsequent crashes.
Three distinct execution paths interact with `dwc3_remove_requests()`:
Path 1:
Triggered via `dwc3_gadget_reset_interrupt()` during USB reset
handling. The call stack includes:
- `dwc3_ep0_reset_state()`
- `dwc3_ep0_stall_and_restart()`
- `dwc3_ep0_out_start()`
- `dwc3_remove_requests()`
- `dwc3_gadget_del_and_unmap_request()`
Path 2:
Also initiated from `dwc3_gadget_reset_interrupt()`, but through
`dwc3_stop_active_transfers()`. The call stack includes:
- `dwc3_stop_active_transfers()`
- `dwc3_remove_requests()`
- `dwc3_gadget_del_and_unmap_request()`
Path 3:
Occurs independently during `adb root` execution, which triggers
USB function unbind and bind operations. The sequence includes:
- `gserial_disconnect()`
- `usb_ep_disable()`
- `dwc3_gadget_ep_disable()`
- `dwc3_remove_requests()` with `-ESHUTDOWN` status
Path 3 operates asynchronously and lacks synchronization with Paths
1 and 2. When Path 3 completes, it disables endpoints and frees 'out'
requests. If Paths 1 or 2 are still processing these requests,
accessing freed memory leads to a crash due to use-after-free conditions.
To prevent this race condition, `usb_ep_disable()` should be made
synchronous. Specifically:
- Issue an `ENDXFER` command to stop the endpoint.
- Ensure all pending USB requests are returned to the function driver
via `dwc3_gadget_giveback()` before freeing.
Since `gserial_disconnect` calls `usb_ep_disable()` first, modifying
`ep_disable()` to invoke the `complete()` callback for gser USB
requests ensures safe deallocation.
Additionally, the driver already includes the `dwc->ep0_in_setup`
completion mechanism, which is triggered upon returning to the
SETUP stage. This can be leveraged to coordinate and synchronize
the cleanup process effectively.
Signed-off-by: Manish Nagar <manish.nagar@oss.qualcomm.com>
---
drivers/usb/dwc3/gadget.c | 25 +++++++++++++++----------
1 file changed, 15 insertions(+), 10 deletions(-)
diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c
index 6f18b4840a25..93c20d5edea1 100644
--- a/drivers/usb/dwc3/gadget.c
+++ b/drivers/usb/dwc3/gadget.c
@@ -1064,7 +1064,7 @@ static int __dwc3_gadget_ep_disable(struct dwc3_ep *dep)
{
struct dwc3 *dwc = dep->dwc;
u32 reg;
- u32 mask;
+ int ret;
trace_dwc3_gadget_ep_disable(dep);
@@ -1077,18 +1077,23 @@ static int __dwc3_gadget_ep_disable(struct dwc3_ep *dep)
dwc3_writel(dwc->regs, DWC3_DALEPENA, reg);
dwc3_remove_requests(dwc, dep, -ESHUTDOWN);
+ /*
+ * Stop the endpoint by issuing ENDXFER and synchronously complete
+ * all pending USB requests before returning from ep disable.
+ */
+ if (dep->flags & DWC3_EP_DELAY_STOP) {
+ spin_unlock(&dwc->lock);
+ reinit_completion(&dwc->ep0_in_setup);
+ ret = wait_for_completion_timeout(&dwc->ep0_in_setup,
+ msecs_to_jiffies(DWC3_PULL_UP_TIMEOUT));
+ spin_lock(&dwc->lock);
+ if (ret == 0)
+ dwc3_ep0_reset_state(dwc);
+ }
dep->stream_capable = false;
dep->type = 0;
- mask = DWC3_EP_TXFIFO_RESIZED | DWC3_EP_RESOURCE_ALLOCATED;
- /*
- * dwc3_remove_requests() can exit early if DWC3 EP delayed stop is
- * set. Do not clear DEP flags, so that the end transfer command will
- * be reattempted during the next SETUP stage.
- */
- if (dep->flags & DWC3_EP_DELAY_STOP)
- mask |= (DWC3_EP_DELAY_STOP | DWC3_EP_TRANSFER_STARTED);
- dep->flags &= mask;
+ dep->flags &= DWC3_EP_TXFIFO_RESIZED;
/* Clear out the ep descriptors for non-ep0 */
if (dep->number > 1) {
--
2.25.1
^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH] usb: dwc3: Fix race condition between concurrent dwc3_remove_requests() call paths
2025-10-28 8:05 [PATCH] usb: dwc3: Fix race condition between concurrent dwc3_remove_requests() call paths Manish Nagar
@ 2025-10-28 8:46 ` Greg Kroah-Hartman
2025-10-29 23:47 ` Thinh Nguyen
1 sibling, 0 replies; 5+ messages in thread
From: Greg Kroah-Hartman @ 2025-10-28 8:46 UTC (permalink / raw)
To: Manish Nagar; +Cc: Thinh Nguyen, linux-usb, linux-kernel
On Tue, Oct 28, 2025 at 01:35:53PM +0530, Manish Nagar wrote:
> This patch addresses a race condition caused by unsynchronized
> execution of multiple call paths invoking `dwc3_remove_requests()`,
> leading to premature freeing of USB requests and subsequent crashes.
>
> Three distinct execution paths interact with `dwc3_remove_requests()`:
> Path 1:
> Triggered via `dwc3_gadget_reset_interrupt()` during USB reset
> handling. The call stack includes:
> - `dwc3_ep0_reset_state()`
> - `dwc3_ep0_stall_and_restart()`
> - `dwc3_ep0_out_start()`
> - `dwc3_remove_requests()`
> - `dwc3_gadget_del_and_unmap_request()`
>
> Path 2:
> Also initiated from `dwc3_gadget_reset_interrupt()`, but through
> `dwc3_stop_active_transfers()`. The call stack includes:
> - `dwc3_stop_active_transfers()`
> - `dwc3_remove_requests()`
> - `dwc3_gadget_del_and_unmap_request()`
>
> Path 3:
> Occurs independently during `adb root` execution, which triggers
> USB function unbind and bind operations. The sequence includes:
> - `gserial_disconnect()`
> - `usb_ep_disable()`
> - `dwc3_gadget_ep_disable()`
> - `dwc3_remove_requests()` with `-ESHUTDOWN` status
>
> Path 3 operates asynchronously and lacks synchronization with Paths
> 1 and 2. When Path 3 completes, it disables endpoints and frees 'out'
> requests. If Paths 1 or 2 are still processing these requests,
> accessing freed memory leads to a crash due to use-after-free conditions.
>
> To prevent this race condition, `usb_ep_disable()` should be made
> synchronous. Specifically:
> - Issue an `ENDXFER` command to stop the endpoint.
> - Ensure all pending USB requests are returned to the function driver
> via `dwc3_gadget_giveback()` before freeing.
>
> Since `gserial_disconnect` calls `usb_ep_disable()` first, modifying
> `ep_disable()` to invoke the `complete()` callback for gser USB
> requests ensures safe deallocation.
>
> Additionally, the driver already includes the `dwc->ep0_in_setup`
> completion mechanism, which is triggered upon returning to the
> SETUP stage. This can be leveraged to coordinate and synchronize
> the cleanup process effectively.
>
> Signed-off-by: Manish Nagar <manish.nagar@oss.qualcomm.com>
> ---
> drivers/usb/dwc3/gadget.c | 25 +++++++++++++++----------
> 1 file changed, 15 insertions(+), 10 deletions(-)
What commit id does this fix?
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH] usb: dwc3: Fix race condition between concurrent dwc3_remove_requests() call paths
2025-10-28 8:05 [PATCH] usb: dwc3: Fix race condition between concurrent dwc3_remove_requests() call paths Manish Nagar
2025-10-28 8:46 ` Greg Kroah-Hartman
@ 2025-10-29 23:47 ` Thinh Nguyen
[not found] ` <CADGrZwXmnKn68v_cR3o+MiLPAyo+ujtgbx50sK=+4rOfgcoyBA@mail.gmail.com>
1 sibling, 1 reply; 5+ messages in thread
From: Thinh Nguyen @ 2025-10-29 23:47 UTC (permalink / raw)
To: Manish Nagar
Cc: Greg Kroah-Hartman, Thinh Nguyen, linux-usb@vger.kernel.org,
linux-kernel@vger.kernel.org
Hi,
On Tue, Oct 28, 2025, Manish Nagar wrote:
> This patch addresses a race condition caused by unsynchronized
> execution of multiple call paths invoking `dwc3_remove_requests()`,
> leading to premature freeing of USB requests and subsequent crashes.
>
> Three distinct execution paths interact with `dwc3_remove_requests()`:
> Path 1:
> Triggered via `dwc3_gadget_reset_interrupt()` during USB reset
> handling. The call stack includes:
> - `dwc3_ep0_reset_state()`
> - `dwc3_ep0_stall_and_restart()`
> - `dwc3_ep0_out_start()`
> - `dwc3_remove_requests()`
> - `dwc3_gadget_del_and_unmap_request()`
>
> Path 2:
> Also initiated from `dwc3_gadget_reset_interrupt()`, but through
> `dwc3_stop_active_transfers()`. The call stack includes:
> - `dwc3_stop_active_transfers()`
> - `dwc3_remove_requests()`
> - `dwc3_gadget_del_and_unmap_request()`
>
> Path 3:
> Occurs independently during `adb root` execution, which triggers
> USB function unbind and bind operations. The sequence includes:
> - `gserial_disconnect()`
> - `usb_ep_disable()`
> - `dwc3_gadget_ep_disable()`
> - `dwc3_remove_requests()` with `-ESHUTDOWN` status
>
> Path 3 operates asynchronously and lacks synchronization with Paths
> 1 and 2. When Path 3 completes, it disables endpoints and frees 'out'
> requests. If Paths 1 or 2 are still processing these requests,
> accessing freed memory leads to a crash due to use-after-free conditions.
>
> To prevent this race condition, `usb_ep_disable()` should be made
> synchronous. Specifically:
> - Issue an `ENDXFER` command to stop the endpoint.
> - Ensure all pending USB requests are returned to the function driver
> via `dwc3_gadget_giveback()` before freeing.
>
> Since `gserial_disconnect` calls `usb_ep_disable()` first, modifying
> `ep_disable()` to invoke the `complete()` callback for gser USB
> requests ensures safe deallocation.
A gadget driver can deallocate the request on request completion other
than usb_ep_disable().
>
> Additionally, the driver already includes the `dwc->ep0_in_setup`
> completion mechanism, which is triggered upon returning to the
> SETUP stage. This can be leveraged to coordinate and synchronize
> the cleanup process effectively.
>
> Signed-off-by: Manish Nagar <manish.nagar@oss.qualcomm.com>
> ---
> drivers/usb/dwc3/gadget.c | 25 +++++++++++++++----------
> 1 file changed, 15 insertions(+), 10 deletions(-)
>
> diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c
> index 6f18b4840a25..93c20d5edea1 100644
> --- a/drivers/usb/dwc3/gadget.c
> +++ b/drivers/usb/dwc3/gadget.c
> @@ -1064,7 +1064,7 @@ static int __dwc3_gadget_ep_disable(struct dwc3_ep *dep)
> {
> struct dwc3 *dwc = dep->dwc;
> u32 reg;
> - u32 mask;
> + int ret;
>
> trace_dwc3_gadget_ep_disable(dep);
>
> @@ -1077,18 +1077,23 @@ static int __dwc3_gadget_ep_disable(struct dwc3_ep *dep)
> dwc3_writel(dwc->regs, DWC3_DALEPENA, reg);
>
> dwc3_remove_requests(dwc, dep, -ESHUTDOWN);
> + /*
> + * Stop the endpoint by issuing ENDXFER and synchronously complete
> + * all pending USB requests before returning from ep disable.
> + */
> + if (dep->flags & DWC3_EP_DELAY_STOP) {
The DWC3_EP_DELAY_STOP flag is not meant for this. There are times when
we want to delay issuing End Transfer command, and the flag is meant for
that. You're selectively wait for End Transfer. So, this function is
sometime synchronous and sometime it's not.
> + spin_unlock(&dwc->lock);
> + reinit_completion(&dwc->ep0_in_setup);
> + ret = wait_for_completion_timeout(&dwc->ep0_in_setup,
> + msecs_to_jiffies(DWC3_PULL_UP_TIMEOUT));
Don't use DWC3_PULL_UP_TIMEOUT, it's meant for PULL_UP timeout?
> + spin_lock(&dwc->lock);
> + if (ret == 0)
> + dwc3_ep0_reset_state(dwc);
> + }
>
> dep->stream_capable = false;
> dep->type = 0;
> - mask = DWC3_EP_TXFIFO_RESIZED | DWC3_EP_RESOURCE_ALLOCATED;
> - /*
> - * dwc3_remove_requests() can exit early if DWC3 EP delayed stop is
> - * set. Do not clear DEP flags, so that the end transfer command will
> - * be reattempted during the next SETUP stage.
> - */
> - if (dep->flags & DWC3_EP_DELAY_STOP)
> - mask |= (DWC3_EP_DELAY_STOP | DWC3_EP_TRANSFER_STARTED);
> - dep->flags &= mask;
> + dep->flags &= DWC3_EP_TXFIFO_RESIZED;
What happen to DWC3_EP_RESOURCE_ALLOCATED flag, why was it removed?
>
> /* Clear out the ep descriptors for non-ep0 */
> if (dep->number > 1) {
> --
> 2.25.1
>
What you're doing here only mitigates the issue.
Whenever we call dwc3_gadget_giveback(), there's a momentary release of
spinlock, and there will be a chance for a race. It can also come from
usb_ep_dequeue() and not just usb_ep_disable().
Perhaps just this change is sufficient to solve the race issue.
Hopefully we don't need reference count for dwc3_request.
diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c
index 1f67fb6aead5..a1d2ff9254b4 100644
--- a/drivers/usb/dwc3/gadget.c
+++ b/drivers/usb/dwc3/gadget.c
@@ -228,6 +228,13 @@ void dwc3_gadget_giveback(struct dwc3_ep *dep, struct dwc3_request *req,
{
struct dwc3 *dwc = dep->dwc;
+ /*
+ * The request might have been processed and completed while the
+ * spinlock was released. Skip processing if already completed.
+ */
+ if (req->status == DWC3_REQUEST_STATUS_COMPLETED)
+ return;
+
dwc3_gadget_del_and_unmap_request(dep, req, status);
req->status = DWC3_REQUEST_STATUS_COMPLETED;
BR,
Thinh
^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH] usb: dwc3: Fix race condition between concurrent dwc3_remove_requests() call paths
[not found] ` <CADGrZwXmnKn68v_cR3o+MiLPAyo+ujtgbx50sK=+4rOfgcoyBA@mail.gmail.com>
@ 2025-11-14 0:29 ` Thinh Nguyen
2025-11-19 17:04 ` Manish Nagar
0 siblings, 1 reply; 5+ messages in thread
From: Thinh Nguyen @ 2025-11-14 0:29 UTC (permalink / raw)
To: Manish Nagar
Cc: Thinh Nguyen, Greg Kroah-Hartman, linux-usb@vger.kernel.org,
linux-kernel@vger.kernel.org
On Thu, Nov 13, 2025, Manish Nagar wrote:
> Hi ,
>
> Thanks for the suggestion ,
>
> On above your suggestion, I added a check for dep->number as
>
> if ((req->status == DWC3_REQUEST_STATUS_COMPLETED) && (dep->number > 1))
> return;
>
> I included this condition to prevent enumeration failures. During the sequence
> dwc3_gadget_giveback → dwc3_ep0_interrupt → dwc3_thread_interrupt, the giveback
> for ep0 does not complete, so this check ensures proper handling.
>
> Please share your feedback, and I will proceed with v2 accordingly.
>
No, add checks for ep0 too.
Try this:
diff --git a/drivers/usb/dwc3/ep0.c b/drivers/usb/dwc3/ep0.c
index b4229aa13f37..e0bad5708664 100644
--- a/drivers/usb/dwc3/ep0.c
+++ b/drivers/usb/dwc3/ep0.c
@@ -94,6 +94,7 @@ static int __dwc3_gadget_ep0_queue(struct dwc3_ep *dep,
req->request.actual = 0;
req->request.status = -EINPROGRESS;
req->epnum = dep->number;
+ req->status = DWC3_REQUEST_STATUS_QUEUED;
list_add_tail(&req->list, &dep->pending_list);
diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c
index 1f67fb6aead5..a1d2ff9254b4 100644
--- a/drivers/usb/dwc3/gadget.c
+++ b/drivers/usb/dwc3/gadget.c
@@ -228,6 +228,13 @@ void dwc3_gadget_giveback(struct dwc3_ep *dep, struct dwc3_request *req,
{
struct dwc3 *dwc = dep->dwc;
+ /*
+ * The request might have been processed and completed while the
+ * spinlock was released. Skip processing if already completed.
+ */
+ if (req->status == DWC3_REQUEST_STATUS_COMPLETED)
+ return;
+
dwc3_gadget_del_and_unmap_request(dep, req, status);
req->status = DWC3_REQUEST_STATUS_COMPLETED;
---
BR,
Thinh
^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH] usb: dwc3: Fix race condition between concurrent dwc3_remove_requests() call paths
2025-11-14 0:29 ` Thinh Nguyen
@ 2025-11-19 17:04 ` Manish Nagar
0 siblings, 0 replies; 5+ messages in thread
From: Manish Nagar @ 2025-11-19 17:04 UTC (permalink / raw)
To: Thinh Nguyen
Cc: Greg Kroah-Hartman, linux-usb@vger.kernel.org,
linux-kernel@vger.kernel.org
Hi Greg,
I can’t identify a specific commit ID that I’m confident was the patch
introducing the bug.But I found the race condition and suggested this
patch to fix it.
Regards,
Manish
On 11/14/2025 5:59 AM, Thinh Nguyen wrote:
> On Thu, Nov 13, 2025, Manish Nagar wrote:
>> Hi ,
>>
>> Thanks for the suggestion ,
>>
>> On above your suggestion, I added a check for dep->number as
>>
>> if ((req->status == DWC3_REQUEST_STATUS_COMPLETED) && (dep->number > 1))
>> return;
>>
>> I included this condition to prevent enumeration failures. During the sequence
>> dwc3_gadget_giveback → dwc3_ep0_interrupt → dwc3_thread_interrupt, the giveback
>> for ep0 does not complete, so this check ensures proper handling.
>>
>> Please share your feedback, and I will proceed with v2 accordingly.
>>
> No, add checks for ep0 too.
>
> Try this:
>
> diff --git a/drivers/usb/dwc3/ep0.c b/drivers/usb/dwc3/ep0.c
> index b4229aa13f37..e0bad5708664 100644
> --- a/drivers/usb/dwc3/ep0.c
> +++ b/drivers/usb/dwc3/ep0.c
> @@ -94,6 +94,7 @@ static int __dwc3_gadget_ep0_queue(struct dwc3_ep *dep,
> req->request.actual = 0;
> req->request.status = -EINPROGRESS;
> req->epnum = dep->number;
> + req->status = DWC3_REQUEST_STATUS_QUEUED;
>
> list_add_tail(&req->list, &dep->pending_list);
>
> diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c
> index 1f67fb6aead5..a1d2ff9254b4 100644
> --- a/drivers/usb/dwc3/gadget.c
> +++ b/drivers/usb/dwc3/gadget.c
> @@ -228,6 +228,13 @@ void dwc3_gadget_giveback(struct dwc3_ep *dep, struct dwc3_request *req,
> {
> struct dwc3 *dwc = dep->dwc;
>
> + /*
> + * The request might have been processed and completed while the
> + * spinlock was released. Skip processing if already completed.
> + */
> + if (req->status == DWC3_REQUEST_STATUS_COMPLETED)
> + return;
> +
> dwc3_gadget_del_and_unmap_request(dep, req, status);
> req->status = DWC3_REQUEST_STATUS_COMPLETED;
>
> ---
>
> BR,
> Thinh
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2025-11-19 17:04 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-10-28 8:05 [PATCH] usb: dwc3: Fix race condition between concurrent dwc3_remove_requests() call paths Manish Nagar
2025-10-28 8:46 ` Greg Kroah-Hartman
2025-10-29 23:47 ` Thinh Nguyen
[not found] ` <CADGrZwXmnKn68v_cR3o+MiLPAyo+ujtgbx50sK=+4rOfgcoyBA@mail.gmail.com>
2025-11-14 0:29 ` Thinh Nguyen
2025-11-19 17:04 ` Manish Nagar
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).