Re: [syzbot] [usb?] general protection fault in usb_gadget_udc_reset (4)

From: Alan Stern

Date: Tue Mar 10 2026 - 12:23:51 EST


On Mon, Mar 09, 2026 at 08:43:01AM -0700, syzbot wrote:
> Hello,
>
> syzbot has tested the proposed patch but the reproducer is still triggering an issue:
> general protection fault in usb_gadget_udc_reset
>
> usb 2-1: reset high-speed USB device number 8 using dummy_hcd
> usb 2-1: device descriptor read/8, error -32
> gadget gadget.1: Reset #1, gadget ffff888029d60c40 driver 0000000000000000
> Oops: general protection fault, probably for non-canonical address 0xdffffc0000000008: 0000 [#1] SMP KASAN PTI

That's helpful. And it turns out the reason that "raw-gadget.0" changes
to "gadget" right before the crash is because the raw-gadget driver has
been unbound, and when no driver is bound the dev_xxxx() calls use the
bus-type name instead.

An audit shows that the untimely driver unbinding occurs because of an
error in synchronization. The code in dummy-hcd which emulates
synchronize_irq() should run after the emulated interrupts are disabled,
not before. That code needs to be moved from dummy_pullup() to
dummy_udc_async_callbacks().

Let's see if this fixes the bug.

Alan Stern

#syz test: upstream 651690480a96

Index: usb-devel/drivers/usb/gadget/udc/core.c
===================================================================
--- usb-devel.orig/drivers/usb/gadget/udc/core.c
+++ usb-devel/drivers/usb/gadget/udc/core.c
@@ -1197,7 +1197,9 @@ EXPORT_SYMBOL_GPL(usb_udc_vbus_handler);
void usb_gadget_udc_reset(struct usb_gadget *gadget,
struct usb_gadget_driver *driver)
{
+ dev_info(&gadget->dev, "Reset #1, gadget %p driver %p\n", gadget, driver);
driver->reset(gadget);
+ dev_info(&gadget->dev, "Reset #2\n");
usb_gadget_set_state(gadget, USB_STATE_DEFAULT);
}
EXPORT_SYMBOL_GPL(usb_gadget_udc_reset);
Index: usb-devel/drivers/usb/gadget/udc/dummy_hcd.c
===================================================================
--- usb-devel.orig/drivers/usb/gadget/udc/dummy_hcd.c
+++ usb-devel/drivers/usb/gadget/udc/dummy_hcd.c
@@ -908,21 +908,6 @@ static int dummy_pullup(struct usb_gadge
spin_lock_irqsave(&dum->lock, flags);
dum->pullup = (value != 0);
set_link_state(dum_hcd);
- if (value == 0) {
- /*
- * Emulate synchronize_irq(): wait for callbacks to finish.
- * This seems to be the best place to emulate the call to
- * synchronize_irq() that's in usb_gadget_remove_driver().
- * Doing it in dummy_udc_stop() would be too late since it
- * is called after the unbind callback and unbind shouldn't
- * be invoked until all the other callbacks are finished.
- */
- while (dum->callback_usage > 0) {
- spin_unlock_irqrestore(&dum->lock, flags);
- usleep_range(1000, 2000);
- spin_lock_irqsave(&dum->lock, flags);
- }
- }
spin_unlock_irqrestore(&dum->lock, flags);

usb_hcd_poll_rh_status(dummy_hcd_to_hcd(dum_hcd));
@@ -945,6 +930,23 @@ static void dummy_udc_async_callbacks(st

spin_lock_irq(&dum->lock);
dum->ints_enabled = enable;
+ if (!enable) {
+ /*
+ * Emulate synchronize_irq(): wait for callbacks to finish.
+ * This seems to be the best place to emulate the call to
+ * synchronize_irq() that's in usb_gadget_remove_driver().
+ * It has to come after dum->ints_enabled is clear. But
+ * doing it in dummy_udc_stop() would be too late since that
+ * routine is called after the unbind callback, and unbind
+ * shouldn't be invoked until all the other callbacks are
+ * finished.
+ */
+ while (dum->callback_usage > 0) {
+ spin_unlock_irq(&dum->lock);
+ usleep_range(1000, 2000);
+ spin_lock_irq(&dum->lock);
+ }
+ }
spin_unlock_irq(&dum->lock);
}