* Re: [PATCH] usb: core: hcd: cancel giveback work synchronously on stop
2026-10-23 17:40 [PATCH] usb: core: hcd: cancel giveback work synchronously on stop Igor Putko
@ 2026-10-23 19:15 ` Alan Stern
2026-10-23 19:31 ` IgorpetinDev
2026-10-24 8:36 ` [PATCH v2] usb: core: hcd: flush giveback work " Igor Putko
2026-10-24 15:56 ` [PATCH v3] " Igor Putko
2 siblings, 1 reply; 7+ messages in thread
From: Alan Stern @ 2026-10-23 19:15 UTC (permalink / raw)
To: Igor Putko
Cc: Greg Kroah-Hartman, Tejun Heo, Oliver Neukum, linux-usb,
linux-kernel, stable, syzbot+a5b220cacb0618384d5a
On Fri, Oct 23, 2026 at 08:40:15PM +0300, Igor Putko wrote:
> When an HCD is stopped/removed (either during module unload, driver
> unbind, or an error path in probe), pending URBs are unlinked and
> completed. However, for root hubs or HCDs with giveback done in BH
> context, the work function usb_giveback_urb_bh() may still be executing
> on another CPU after __usb_hcd_giveback_urb() finishes.
>
> Since the caller may immediately free the struct usb_hcd via usb_put_hcd()
> after usb_stop_hcd() or usb_remove_hcd(), the running worker accesses
> the embedded giveback_urb_bh structure after it has been freed,
> triggering a slab-use-after-free write detected by KASAN.
>
> Ensure all giveback work is fully flushed and cancelled in usb_stop_hcd()
> by calling cancel_work_sync() on both high and low priority BHs.
Sashiko's criticism has a point. If there are any URBs still pending on
either of the BH lists when usb_stop_hcd() returns, something is already
badly wrong -- that's never supposed to happen. Whatever it is, it
needs to be fixed in the driver that submitted the pending URB, not here
in usb_stop_hcd().
Alan Stern
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH] usb: core: hcd: cancel giveback work synchronously on stop
2026-10-23 19:15 ` Alan Stern
@ 2026-10-23 19:31 ` IgorpetinDev
2026-10-24 1:53 ` Alan Stern
0 siblings, 1 reply; 7+ messages in thread
From: IgorpetinDev @ 2026-10-23 19:31 UTC (permalink / raw)
To: Alan Stern
Cc: Greg Kroah-Hartman, Tejun Heo, Oliver Neukum, linux-usb,
linux-kernel, stable, syzbot+a5b220cacb0618384d5a
Hi Alan,
Thanks for looking at the patch.
I think the bot caused some confusion by talking about stranded URBs.
There are actually no pending URBs left on the list at all. The driver
properly killed all of them.
The issue is a race inside usb_giveback_urb_bh itself. The function
usb_kill_urb() unblocks as soon as __usb_hcd_giveback_urb() drops
use_count and wakes the waitqueue. The caller immediately wakes up,
finishes removal, and frees struct usb_hcd via usb_put_hcd().
Meanwhile, the worker thread on the other CPU is still running the
tail of usb_giveback_urb_bh(). Right after __usb_hcd_giveback_urb()
returns, it tries to access bh->completing_ep, bh->lock, and
bh->running. Because bh is embedded directly inside struct usb_hcd,
this access hits already freed memory.
So all URBs are already completed and bh->head is empty, but the
teardown path still needs to wait for the worker thread to fully exit
before the HCD structure gets freed.
Would calling flush_work() on both BH work items be the better way to
synchronize this without the concern raised by the bot?
Thanks,
Igor
On Fri, Oct 23, 2026 at 10:15 PM Alan Stern <stern@rowland.harvard.edu> wrote:
>
> On Fri, Oct 23, 2026 at 08:40:15PM +0300, Igor Putko wrote:
> > When an HCD is stopped/removed (either during module unload, driver
> > unbind, or an error path in probe), pending URBs are unlinked and
> > completed. However, for root hubs or HCDs with giveback done in BH
> > context, the work function usb_giveback_urb_bh() may still be executing
> > on another CPU after __usb_hcd_giveback_urb() finishes.
> >
> > Since the caller may immediately free the struct usb_hcd via usb_put_hcd()
> > after usb_stop_hcd() or usb_remove_hcd(), the running worker accesses
> > the embedded giveback_urb_bh structure after it has been freed,
> > triggering a slab-use-after-free write detected by KASAN.
> >
> > Ensure all giveback work is fully flushed and cancelled in usb_stop_hcd()
> > by calling cancel_work_sync() on both high and low priority BHs.
>
> Sashiko's criticism has a point. If there are any URBs still pending on
> either of the BH lists when usb_stop_hcd() returns, something is already
> badly wrong -- that's never supposed to happen. Whatever it is, it
> needs to be fixed in the driver that submitted the pending URB, not here
> in usb_stop_hcd().
>
> Alan Stern
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH] usb: core: hcd: cancel giveback work synchronously on stop
2026-10-23 19:31 ` IgorpetinDev
@ 2026-10-24 1:53 ` Alan Stern
0 siblings, 0 replies; 7+ messages in thread
From: Alan Stern @ 2026-10-24 1:53 UTC (permalink / raw)
To: IgorpetinDev
Cc: Greg Kroah-Hartman, Tejun Heo, Oliver Neukum, linux-usb,
linux-kernel, stable, syzbot+a5b220cacb0618384d5a
On Fri, Oct 23, 2026 at 10:31:27PM +0300, IgorpetinDev wrote:
> Hi Alan,
>
> Thanks for looking at the patch.
>
> I think the bot caused some confusion by talking about stranded URBs.
> There are actually no pending URBs left on the list at all. The driver
> properly killed all of them.
> The issue is a race inside usb_giveback_urb_bh itself. The function
> usb_kill_urb() unblocks as soon as __usb_hcd_giveback_urb() drops
> use_count and wakes the waitqueue. The caller immediately wakes up,
> finishes removal, and frees struct usb_hcd via usb_put_hcd().
>
> Meanwhile, the worker thread on the other CPU is still running the
> tail of usb_giveback_urb_bh(). Right after __usb_hcd_giveback_urb()
> returns, it tries to access bh->completing_ep, bh->lock, and
> bh->running. Because bh is embedded directly inside struct usb_hcd,
> this access hits already freed memory.
Okay, now I understand your concern.
> So all URBs are already completed and bh->head is empty, but the
> teardown path still needs to wait for the worker thread to fully exit
> before the HCD structure gets freed.
> Would calling flush_work() on both BH work items be the better way to
> synchronize this without the concern raised by the bot?
Yes, I think it would. After all, it's just good form to flush or
cancel a work item before deallocating it, if you can't prove that it
must already be idle.
Alan Stern
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH v2] usb: core: hcd: flush giveback work on stop
2026-10-23 17:40 [PATCH] usb: core: hcd: cancel giveback work synchronously on stop Igor Putko
2026-10-23 19:15 ` Alan Stern
@ 2026-10-24 8:36 ` Igor Putko
2026-10-24 15:56 ` [PATCH v3] " Igor Putko
2 siblings, 0 replies; 7+ messages in thread
From: Igor Putko @ 2026-10-24 8:36 UTC (permalink / raw)
To: Greg Kroah-Hartman
Cc: Alan Stern, Tejun Heo, Oliver Neukum, linux-usb, linux-kernel,
stable, syzbot+a5b220cacb0618384d5a, Igor Putko
When an HCD is stopped/removed (either during module unload, driver
unbind, or an error path in probe), pending URBs are unlinked and
completed. However, for root hubs or HCDs with giveback done in BH
context, the work function usb_giveback_urb_bh() may still be executing
on another CPU after __usb_hcd_giveback_urb() drops the URB use_count
and wakes up waiters in usb_kill_urb().
Since the caller may immediately free the struct usb_hcd via usb_put_hcd()
after usb_stop_hcd() or usb_remove_hcd(), the running worker accesses
the embedded giveback_urb_bh structure in its epilogue (e.g. clearing
completing_ep or clearing the running flag) after the memory has been
freed, triggering a slab-use-after-free write detected by KASAN.
Ensure all giveback work has fully completed in usb_stop_hcd() by calling
flush_work() on both high and low priority BHs before deallocating.
Fixes: 8fea0c8fda30 ("usb: core: hcd: Convert from tasklet to BH workqueue")
Cc: stable@vger.kernel.org
Reported-by: syzbot+a5b220cacb0618384d5a@syzkaller.appspotmail.com
Closes: https://syzkaller.appspotmail.com/bug?extid=a5b220cacb0618384d5a
Suggested-by: Alan Stern <stern@rowland.harvard.edu>
Signed-off-by: Igor Putko <igorpetindev@gmail.com>
---
v1 -> v2:
- Use flush_work() instead of cancel_work_sync() to ensure no pending
work items are dropped, as suggested by Alan Stern.
---
drivers/usb/core/hcd.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c
index 8e8a5f59b319..e6854d67ed8c 100644
--- a/drivers/usb/core/hcd.c
+++ b/drivers/usb/core/hcd.c
@@ -2787,6 +2787,9 @@ static void usb_stop_hcd(struct usb_hcd *hcd)
/* In case the HCD restarted the timer, stop it again. */
clear_bit(HCD_FLAG_POLL_RH, &hcd->flags);
timer_delete_sync(&hcd->rh_timer);
+
+ flush_work(&hcd->high_prio_bh.bh);
+ flush_work(&hcd->low_prio_bh.bh);
}
/**
--
2.47.3
^ permalink raw reply [flat|nested] 7+ messages in thread* [PATCH v3] usb: core: hcd: flush giveback work on stop
2026-10-23 17:40 [PATCH] usb: core: hcd: cancel giveback work synchronously on stop Igor Putko
2026-10-23 19:15 ` Alan Stern
2026-10-24 8:36 ` [PATCH v2] usb: core: hcd: flush giveback work " Igor Putko
@ 2026-10-24 15:56 ` Igor Putko
2026-10-24 18:28 ` Alan Stern
2 siblings, 1 reply; 7+ messages in thread
From: Igor Putko @ 2026-10-24 15:56 UTC (permalink / raw)
To: Greg Kroah-Hartman
Cc: Alan Stern, Tejun Heo, Oliver Neukum, linux-usb, linux-kernel,
stable, syzbot+a5b220cacb0618384d5a, Igor Putko
When an HCD is stopped/removed (either during module unload, driver
unbind, or an error path in probe), pending URBs are unlinked and
completed. However, for root hubs or HCDs with giveback done in BH
context, the work function usb_giveback_urb_bh() may still be executing
on another CPU after __usb_hcd_giveback_urb() drops the URB use_count
and wakes up waiters in usb_kill_urb().
Since the caller may immediately free the struct usb_hcd via usb_put_hcd()
after usb_stop_hcd() or usb_remove_hcd(), the running worker accesses
the embedded giveback_urb_bh structure in its epilogue (e.g. clearing
completing_ep or clearing the running flag) after the memory has been
freed, triggering a slab-use-after-free write detected by KASAN.
Ensure all giveback work has fully completed in usb_stop_hcd() by calling
flush_work() on both high and low priority BHs before deallocating.
Also remove the misleading comment in usb_remove_hcd() claiming
flush_work() is unneeded, and document the queue emptiness in the
usb_stop_hcd() kerneldoc.
Fixes: 8fea0c8fda30 ("usb: core: hcd: Convert from tasklet to BH workqueue")
Reported-by: syzbot+a5b220cacb0618384d5a@syzkaller.appspotmail.com
Closes: https://syzkaller.appspotmail.com/bug?extid=a5b220cacb0618384d5a
Suggested-by: Alan Stern <stern@rowland.harvard.edu>
Signed-off-by: Igor Putko <igorpetindev@gmail.com>
---
v2 -> v3:
- Remove obsolete comment in usb_remove_hcd() regarding flush_work().
- Add comment in usb_stop_hcd() explaining why flush_work() is needed.
- Update kerneldoc for usb_stop_hcd() to state giveback queues are empty.
v1 -> v2:
- Use flush_work() instead of cancel_work_sync() as suggested by Alan Stern.
drivers/usb/core/hcd.c | 19 +++++++++----------
1 file changed, 9 insertions(+), 10 deletions(-)
diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c
index 8e8a5f59b319..f403795b17ba 100644
--- a/drivers/usb/core/hcd.c
+++ b/drivers/usb/core/hcd.c
@@ -2773,6 +2773,8 @@ static void usb_put_invalidate_rhdev(struct usb_hcd *hcd)
* usb_stop_hcd - Halt the HCD
* @hcd: the usb_hcd that has to be halted
*
+ * The two giveback queues should be entirely empty when this runs.
+ *
* Stop the root-hub polling timer and invoke the HCD's ->stop callback.
*/
static void usb_stop_hcd(struct usb_hcd *hcd)
@@ -2787,6 +2789,13 @@ static void usb_stop_hcd(struct usb_hcd *hcd)
/* In case the HCD restarted the timer, stop it again. */
clear_bit(HCD_FLAG_POLL_RH, &hcd->flags);
timer_delete_sync(&hcd->rh_timer);
+
+ /*
+ * Even though all URBs have been given back, the work routine itself
+ * may still be finishing up and accessing the usb_hcd structure.
+ */
+ flush_work(&hcd->high_prio_bh.bh);
+ flush_work(&hcd->low_prio_bh.bh);
}
/**
@@ -3059,16 +3068,6 @@ void usb_remove_hcd(struct usb_hcd *hcd)
usb_disconnect(&rhdev); /* Sets rhdev to NULL */
mutex_unlock(&usb_bus_idr_lock);
- /*
- * flush_work() isn't needed here because:
- * - driver's disconnect() called from usb_disconnect() should
- * make sure its URBs are completed during the disconnect()
- * callback
- *
- * - it is too late to run complete() here since driver may have
- * been removed already now
- */
-
/* Prevent any more root-hub status calls from the timer.
* The HCD might still restart the timer (if a port status change
* interrupt occurs), but usb_hcd_poll_rh_status() won't invoke
--
2.47.3
^ permalink raw reply [flat|nested] 7+ messages in thread* Re: [PATCH v3] usb: core: hcd: flush giveback work on stop
2026-10-24 15:56 ` [PATCH v3] " Igor Putko
@ 2026-10-24 18:28 ` Alan Stern
0 siblings, 0 replies; 7+ messages in thread
From: Alan Stern @ 2026-10-24 18:28 UTC (permalink / raw)
To: Igor Putko
Cc: Greg Kroah-Hartman, Tejun Heo, Oliver Neukum, linux-usb,
linux-kernel, stable, syzbot+a5b220cacb0618384d5a
On Sat, Oct 24, 2026 at 06:56:14PM +0300, Igor Putko wrote:
> When an HCD is stopped/removed (either during module unload, driver
> unbind, or an error path in probe), pending URBs are unlinked and
> completed. However, for root hubs or HCDs with giveback done in BH
> context, the work function usb_giveback_urb_bh() may still be executing
> on another CPU after __usb_hcd_giveback_urb() drops the URB use_count
> and wakes up waiters in usb_kill_urb().
>
> Since the caller may immediately free the struct usb_hcd via usb_put_hcd()
> after usb_stop_hcd() or usb_remove_hcd(), the running worker accesses
> the embedded giveback_urb_bh structure in its epilogue (e.g. clearing
> completing_ep or clearing the running flag) after the memory has been
> freed, triggering a slab-use-after-free write detected by KASAN.
>
> Ensure all giveback work has fully completed in usb_stop_hcd() by calling
> flush_work() on both high and low priority BHs before deallocating.
> Also remove the misleading comment in usb_remove_hcd() claiming
> flush_work() is unneeded, and document the queue emptiness in the
> usb_stop_hcd() kerneldoc.
>
> Fixes: 8fea0c8fda30 ("usb: core: hcd: Convert from tasklet to BH workqueue")
> Reported-by: syzbot+a5b220cacb0618384d5a@syzkaller.appspotmail.com
> Closes: https://syzkaller.appspotmail.com/bug?extid=a5b220cacb0618384d5a
> Suggested-by: Alan Stern <stern@rowland.harvard.edu>
> Signed-off-by: Igor Putko <igorpetindev@gmail.com>
> ---
> v2 -> v3:
> - Remove obsolete comment in usb_remove_hcd() regarding flush_work().
> - Add comment in usb_stop_hcd() explaining why flush_work() is needed.
> - Update kerneldoc for usb_stop_hcd() to state giveback queues are empty.
> v1 -> v2:
> - Use flush_work() instead of cancel_work_sync() as suggested by Alan Stern.
Reviewed-by: Alan Stern <stern@rowland.harvard.edu>
> drivers/usb/core/hcd.c | 19 +++++++++----------
> 1 file changed, 9 insertions(+), 10 deletions(-)
>
> diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c
> index 8e8a5f59b319..f403795b17ba 100644
> --- a/drivers/usb/core/hcd.c
> +++ b/drivers/usb/core/hcd.c
> @@ -2773,6 +2773,8 @@ static void usb_put_invalidate_rhdev(struct usb_hcd *hcd)
> * usb_stop_hcd - Halt the HCD
> * @hcd: the usb_hcd that has to be halted
> *
> + * The two giveback queues should be entirely empty when this runs.
> + *
> * Stop the root-hub polling timer and invoke the HCD's ->stop callback.
> */
> static void usb_stop_hcd(struct usb_hcd *hcd)
> @@ -2787,6 +2789,13 @@ static void usb_stop_hcd(struct usb_hcd *hcd)
> /* In case the HCD restarted the timer, stop it again. */
> clear_bit(HCD_FLAG_POLL_RH, &hcd->flags);
> timer_delete_sync(&hcd->rh_timer);
> +
> + /*
> + * Even though all URBs have been given back, the work routine itself
> + * may still be finishing up and accessing the usb_hcd structure.
> + */
> + flush_work(&hcd->high_prio_bh.bh);
> + flush_work(&hcd->low_prio_bh.bh);
> }
>
> /**
> @@ -3059,16 +3068,6 @@ void usb_remove_hcd(struct usb_hcd *hcd)
> usb_disconnect(&rhdev); /* Sets rhdev to NULL */
> mutex_unlock(&usb_bus_idr_lock);
>
> - /*
> - * flush_work() isn't needed here because:
> - * - driver's disconnect() called from usb_disconnect() should
> - * make sure its URBs are completed during the disconnect()
> - * callback
> - *
> - * - it is too late to run complete() here since driver may have
> - * been removed already now
> - */
> -
> /* Prevent any more root-hub status calls from the timer.
> * The HCD might still restart the timer (if a port status change
> * interrupt occurs), but usb_hcd_poll_rh_status() won't invoke
> --
> 2.47.3
>
^ permalink raw reply [flat|nested] 7+ messages in thread