diff mbox series

[v1] usb: dwc3: gadget: fix gadget workqueue use-after-free

Message ID 20250122024452.50289-1-royluo@google.com (mailing list archive)
State New
Headers show
Series [v1] usb: dwc3: gadget: fix gadget workqueue use-after-free | expand

Commit Message

Roy Luo Jan. 22, 2025, 2:44 a.m. UTC
`dwc3_gadget_soft_disconnect` function, called as part of
`device_del(&gadget->dev)`, queues a new work item to the gadget workqueue
after the workqueue has been flushed in `usb_del_gadget`.
This leads to a potential use-after-free issue.

To fix this, flush the workqueue in the `release` function before freeing
the gadget. This ensures that all work items are processed before the
gadget is destroyed.

Fixes: 1ff24d40b3c3 ("usb: dwc3: gadget: Fix incorrect UDC state after manual deconfiguration")
Signed-off-by: Roy Luo <royluo@google.com>
---
 drivers/usb/dwc3/gadget.c | 1 +
 1 file changed, 1 insertion(+)


base-commit: f066b5a6c7a06adfb666b7652cc99b4ff264f4ed

Comments

Thinh Nguyen Jan. 28, 2025, 1:44 a.m. UTC | #1
On Wed, Jan 22, 2025, Roy Luo wrote:
> `dwc3_gadget_soft_disconnect` function, called as part of

The dwc3_gadget_soft_disconnect() isn't directly part of
device_del(&gadget->dev). It should be part of disconnect.

Can you provide the full sequence of events so I can have more context?
The handling of the flushing of gadget->work should not be part of the
dwc3.

> `device_del(&gadget->dev)`, queues a new work item to the gadget workqueue
> after the workqueue has been flushed in `usb_del_gadget`.
> This leads to a potential use-after-free issue.
> 
> To fix this, flush the workqueue in the `release` function before freeing
> the gadget. This ensures that all work items are processed before the
> gadget is destroyed.
> 
> Fixes: 1ff24d40b3c3 ("usb: dwc3: gadget: Fix incorrect UDC state after manual deconfiguration")

Since the patch above is now in the mainline, may need to add a stable
tag. 

Thanks,
Thinh

> Signed-off-by: Roy Luo <royluo@google.com>
> ---
>  drivers/usb/dwc3/gadget.c | 1 +
>  1 file changed, 1 insertion(+)
> 
> diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c
> index d27af65eb08a..12055e3af622 100644
> --- a/drivers/usb/dwc3/gadget.c
> +++ b/drivers/usb/dwc3/gadget.c
> @@ -4580,6 +4580,7 @@ static void dwc_gadget_release(struct device *dev)
>  {
>  	struct usb_gadget *gadget = container_of(dev, struct usb_gadget, dev);
>  
> +	flush_work(&gadget->work);
>  	kfree(gadget);
>  }
>  
> 
> base-commit: f066b5a6c7a06adfb666b7652cc99b4ff264f4ed
> -- 
> 2.48.0.rc2.279.g1de40edade-goog
>
Roy Luo Jan. 31, 2025, 8:21 p.m. UTC | #2
On Mon, Jan 27, 2025 at 5:44 PM Thinh Nguyen <Thinh.Nguyen@synopsys.com> wrote:
>
> On Wed, Jan 22, 2025, Roy Luo wrote:
> > `dwc3_gadget_soft_disconnect` function, called as part of
>
> The dwc3_gadget_soft_disconnect() isn't directly part of
> device_del(&gadget->dev). It should be part of disconnect.
>
> Can you provide the full sequence of events so I can have more context?
> The handling of the flushing of gadget->work should not be part of the
> dwc3.


Yes, it's a part of disconnect, and disconnect is a part of gadget unbind.
Let me try my best to explain. Here's the call stack for usb_del_gadget:
-> usb_del_gadget
    -> flush_work(&gadget->work)
    -> device_del
        -> bus_remove_device
        -> device_release_driver
        -> gadget_unbind_driver
        -> usb_gadget_disconnect_locked
        -> dwc3_gadget_pullup
        -> dwc3_gadget_soft_disconnect
        -> usb_gadget_set_state
        -> schedule_work(&gadget->work)

Then when usb_put_gadget is called, gadget could be freed before
gadget->work is executed.
-> usb_put_gadget
-> put_device
-> kobject_put
-> device_release
-> dwc_gadget_release
-> kfree(gadget)

>
> Since the patch above is now in the mainline, may need to add a stable
> tag.

Ack, will cc stable in the next revision.

Regards,
Roy Luo
Thinh Nguyen Jan. 31, 2025, 11:44 p.m. UTC | #3
Cc Alan

On Fri, Jan 31, 2025, Roy Luo wrote:
> On Mon, Jan 27, 2025 at 5:44 PM Thinh Nguyen <Thinh.Nguyen@synopsys.com> wrote:
> >
> > On Wed, Jan 22, 2025, Roy Luo wrote:
> > > `dwc3_gadget_soft_disconnect` function, called as part of
> >
> > The dwc3_gadget_soft_disconnect() isn't directly part of
> > device_del(&gadget->dev). It should be part of disconnect.
> >
> > Can you provide the full sequence of events so I can have more context?
> > The handling of the flushing of gadget->work should not be part of the
> > dwc3.
> 
> 
> Yes, it's a part of disconnect, and disconnect is a part of gadget unbind.
> Let me try my best to explain. Here's the call stack for usb_del_gadget:
> -> usb_del_gadget
>     -> flush_work(&gadget->work)
>     -> device_del
>         -> bus_remove_device
>         -> device_release_driver
>         -> gadget_unbind_driver
>         -> usb_gadget_disconnect_locked
>         -> dwc3_gadget_pullup
>         -> dwc3_gadget_soft_disconnect
>         -> usb_gadget_set_state
>         -> schedule_work(&gadget->work)
> 
> Then when usb_put_gadget is called, gadget could be freed before
> gadget->work is executed.
> -> usb_put_gadget
> -> put_device
> -> kobject_put
> -> device_release
> -> dwc_gadget_release
> -> kfree(gadget)
> 

Thanks for the details!

The UDC core is initiating and handling the gadget->work, so the
flushing of the gadget->work should also be handled there.

Since the usb_gadget_disconnect_locked() may trigger a state change work
on unbind, the flushing of the gadget->work should to be moved to
gadget_unbind_driver() instead:

diff --git a/drivers/usb/gadget/udc/core.c b/drivers/usb/gadget/udc/core.c
index f8c1ef465e45..9e4abd6e40f8 100644
--- a/drivers/usb/gadget/udc/core.c
+++ b/drivers/usb/gadget/udc/core.c
@@ -1568,7 +1568,6 @@ void usb_del_gadget(struct usb_gadget *gadget)
 
        kobject_uevent(&udc->dev.kobj, KOBJ_REMOVE);
        sysfs_remove_link(&udc->dev.kobj, "gadget");
-       flush_work(&gadget->work);
        device_del(&gadget->dev);
        ida_free(&gadget_id_numbers, gadget->id_number);
        cancel_work_sync(&udc->vbus_work);
@@ -1694,6 +1693,8 @@ static void gadget_unbind_driver(struct device *dev)
                synchronize_irq(gadget->irq);
        mutex_unlock(&udc->connect_lock);
 
+       flush_work(&gadget->work);
+
        udc->driver->unbind(gadget);
 
        mutex_lock(&udc->connect_lock);


BR,
Thinh
Alan Stern Feb. 1, 2025, 4:52 p.m. UTC | #4
On Fri, Jan 31, 2025 at 11:44:17PM +0000, Thinh Nguyen wrote:
> Cc Alan
> 
> On Fri, Jan 31, 2025, Roy Luo wrote:
> > On Mon, Jan 27, 2025 at 5:44 PM Thinh Nguyen <Thinh.Nguyen@synopsys.com> wrote:
> > >
> > > On Wed, Jan 22, 2025, Roy Luo wrote:
> > > > `dwc3_gadget_soft_disconnect` function, called as part of
> > >
> > > The dwc3_gadget_soft_disconnect() isn't directly part of
> > > device_del(&gadget->dev). It should be part of disconnect.
> > >
> > > Can you provide the full sequence of events so I can have more context?
> > > The handling of the flushing of gadget->work should not be part of the
> > > dwc3.
> > 
> > 
> > Yes, it's a part of disconnect, and disconnect is a part of gadget unbind.
> > Let me try my best to explain. Here's the call stack for usb_del_gadget:
> > -> usb_del_gadget
> >     -> flush_work(&gadget->work)
> >     -> device_del
> >         -> bus_remove_device
> >         -> device_release_driver
> >         -> gadget_unbind_driver
> >         -> usb_gadget_disconnect_locked
> >         -> dwc3_gadget_pullup
> >         -> dwc3_gadget_soft_disconnect
> >         -> usb_gadget_set_state
> >         -> schedule_work(&gadget->work)
> > 
> > Then when usb_put_gadget is called, gadget could be freed before
> > gadget->work is executed.
> > -> usb_put_gadget
> > -> put_device
> > -> kobject_put
> > -> device_release
> > -> dwc_gadget_release
> > -> kfree(gadget)
> > 
> 
> Thanks for the details!
> 
> The UDC core is initiating and handling the gadget->work, so the
> flushing of the gadget->work should also be handled there.
> 
> Since the usb_gadget_disconnect_locked() may trigger a state change work
> on unbind, the flushing of the gadget->work should to be moved to
> gadget_unbind_driver() instead:
> 
> diff --git a/drivers/usb/gadget/udc/core.c b/drivers/usb/gadget/udc/core.c
> index f8c1ef465e45..9e4abd6e40f8 100644
> --- a/drivers/usb/gadget/udc/core.c
> +++ b/drivers/usb/gadget/udc/core.c
> @@ -1568,7 +1568,6 @@ void usb_del_gadget(struct usb_gadget *gadget)
>  
>         kobject_uevent(&udc->dev.kobj, KOBJ_REMOVE);
>         sysfs_remove_link(&udc->dev.kobj, "gadget");
> -       flush_work(&gadget->work);
>         device_del(&gadget->dev);
>         ida_free(&gadget_id_numbers, gadget->id_number);
>         cancel_work_sync(&udc->vbus_work);
> @@ -1694,6 +1693,8 @@ static void gadget_unbind_driver(struct device *dev)
>                 synchronize_irq(gadget->irq);
>         mutex_unlock(&udc->connect_lock);
>  
> +       flush_work(&gadget->work);
> +
>         udc->driver->unbind(gadget);
>  
>         mutex_lock(&udc->connect_lock);

What about instead moving the flush_work() call down just one line, 
after the device_del(&gadget->dev) call rather than before it?

The work queue doesn't need to be flushed every time a driver unbinds 
from the gadget, only when the gadget is about to be deallocated.

Alan Stern
Thinh Nguyen Feb. 3, 2025, 11:54 p.m. UTC | #5
On Sat, Feb 01, 2025, Alan Stern wrote:
> On Fri, Jan 31, 2025 at 11:44:17PM +0000, Thinh Nguyen wrote:
> > Cc Alan
> > 
> > On Fri, Jan 31, 2025, Roy Luo wrote:
> > > On Mon, Jan 27, 2025 at 5:44 PM Thinh Nguyen <Thinh.Nguyen@synopsys.com> wrote:
> > > >
> > > > On Wed, Jan 22, 2025, Roy Luo wrote:
> > > > > `dwc3_gadget_soft_disconnect` function, called as part of
> > > >
> > > > The dwc3_gadget_soft_disconnect() isn't directly part of
> > > > device_del(&gadget->dev). It should be part of disconnect.
> > > >
> > > > Can you provide the full sequence of events so I can have more context?
> > > > The handling of the flushing of gadget->work should not be part of the
> > > > dwc3.
> > > 
> > > 
> > > Yes, it's a part of disconnect, and disconnect is a part of gadget unbind.
> > > Let me try my best to explain. Here's the call stack for usb_del_gadget:
> > > -> usb_del_gadget
> > >     -> flush_work(&gadget->work)
> > >     -> device_del
> > >         -> bus_remove_device
> > >         -> device_release_driver
> > >         -> gadget_unbind_driver
> > >         -> usb_gadget_disconnect_locked
> > >         -> dwc3_gadget_pullup
> > >         -> dwc3_gadget_soft_disconnect
> > >         -> usb_gadget_set_state
> > >         -> schedule_work(&gadget->work)
> > > 
> > > Then when usb_put_gadget is called, gadget could be freed before
> > > gadget->work is executed.
> > > -> usb_put_gadget
> > > -> put_device
> > > -> kobject_put
> > > -> device_release
> > > -> dwc_gadget_release
> > > -> kfree(gadget)
> > > 
> > 
> > Thanks for the details!
> > 
> > The UDC core is initiating and handling the gadget->work, so the
> > flushing of the gadget->work should also be handled there.
> > 
> > Since the usb_gadget_disconnect_locked() may trigger a state change work
> > on unbind, the flushing of the gadget->work should to be moved to
> > gadget_unbind_driver() instead:
> > 
> > diff --git a/drivers/usb/gadget/udc/core.c b/drivers/usb/gadget/udc/core.c
> > index f8c1ef465e45..9e4abd6e40f8 100644
> > --- a/drivers/usb/gadget/udc/core.c
> > +++ b/drivers/usb/gadget/udc/core.c
> > @@ -1568,7 +1568,6 @@ void usb_del_gadget(struct usb_gadget *gadget)
> >  
> >         kobject_uevent(&udc->dev.kobj, KOBJ_REMOVE);
> >         sysfs_remove_link(&udc->dev.kobj, "gadget");
> > -       flush_work(&gadget->work);
> >         device_del(&gadget->dev);
> >         ida_free(&gadget_id_numbers, gadget->id_number);
> >         cancel_work_sync(&udc->vbus_work);
> > @@ -1694,6 +1693,8 @@ static void gadget_unbind_driver(struct device *dev)
> >                 synchronize_irq(gadget->irq);
> >         mutex_unlock(&udc->connect_lock);
> >  
> > +       flush_work(&gadget->work);
> > +
> >         udc->driver->unbind(gadget);
> >  
> >         mutex_lock(&udc->connect_lock);
> 
> What about instead moving the flush_work() call down just one line, 
> after the device_del(&gadget->dev) call rather than before it?
> 
> The work queue doesn't need to be flushed every time a driver unbinds 
> from the gadget, only when the gadget is about to be deallocated.
> 

That sounds good to me.

Thanks,
Thinh
Roy Luo Feb. 4, 2025, 12:04 a.m. UTC | #6
On Mon, Feb 3, 2025 at 3:54 PM Thinh Nguyen <Thinh.Nguyen@synopsys.com> wrote:
>
> On Sat, Feb 01, 2025, Alan Stern wrote:
> > On Fri, Jan 31, 2025 at 11:44:17PM +0000, Thinh Nguyen wrote:
> > > Cc Alan
> > >
> > > On Fri, Jan 31, 2025, Roy Luo wrote:
> > > > On Mon, Jan 27, 2025 at 5:44 PM Thinh Nguyen <Thinh.Nguyen@synopsys.com> wrote:
> > > > >
> > > > > On Wed, Jan 22, 2025, Roy Luo wrote:
> > > > > > `dwc3_gadget_soft_disconnect` function, called as part of
> > > > >
> > > > > The dwc3_gadget_soft_disconnect() isn't directly part of
> > > > > device_del(&gadget->dev). It should be part of disconnect.
> > > > >
> > > > > Can you provide the full sequence of events so I can have more context?
> > > > > The handling of the flushing of gadget->work should not be part of the
> > > > > dwc3.
> > > >
> > > >
> > > > Yes, it's a part of disconnect, and disconnect is a part of gadget unbind.
> > > > Let me try my best to explain. Here's the call stack for usb_del_gadget:
> > > > -> usb_del_gadget
> > > >     -> flush_work(&gadget->work)
> > > >     -> device_del
> > > >         -> bus_remove_device
> > > >         -> device_release_driver
> > > >         -> gadget_unbind_driver
> > > >         -> usb_gadget_disconnect_locked
> > > >         -> dwc3_gadget_pullup
> > > >         -> dwc3_gadget_soft_disconnect
> > > >         -> usb_gadget_set_state
> > > >         -> schedule_work(&gadget->work)
> > > >
> > > > Then when usb_put_gadget is called, gadget could be freed before
> > > > gadget->work is executed.
> > > > -> usb_put_gadget
> > > > -> put_device
> > > > -> kobject_put
> > > > -> device_release
> > > > -> dwc_gadget_release
> > > > -> kfree(gadget)
> > > >
> > >
> > > Thanks for the details!
> > >
> > > The UDC core is initiating and handling the gadget->work, so the
> > > flushing of the gadget->work should also be handled there.
> > >
> > > Since the usb_gadget_disconnect_locked() may trigger a state change work
> > > on unbind, the flushing of the gadget->work should to be moved to
> > > gadget_unbind_driver() instead:
> > >
> > > diff --git a/drivers/usb/gadget/udc/core.c b/drivers/usb/gadget/udc/core.c
> > > index f8c1ef465e45..9e4abd6e40f8 100644
> > > --- a/drivers/usb/gadget/udc/core.c
> > > +++ b/drivers/usb/gadget/udc/core.c
> > > @@ -1568,7 +1568,6 @@ void usb_del_gadget(struct usb_gadget *gadget)
> > >
> > >         kobject_uevent(&udc->dev.kobj, KOBJ_REMOVE);
> > >         sysfs_remove_link(&udc->dev.kobj, "gadget");
> > > -       flush_work(&gadget->work);
> > >         device_del(&gadget->dev);
> > >         ida_free(&gadget_id_numbers, gadget->id_number);
> > >         cancel_work_sync(&udc->vbus_work);
> > > @@ -1694,6 +1693,8 @@ static void gadget_unbind_driver(struct device *dev)
> > >                 synchronize_irq(gadget->irq);
> > >         mutex_unlock(&udc->connect_lock);
> > >
> > > +       flush_work(&gadget->work);
> > > +
> > >         udc->driver->unbind(gadget);
> > >
> > >         mutex_lock(&udc->connect_lock);
> >
> > What about instead moving the flush_work() call down just one line,
> > after the device_del(&gadget->dev) call rather than before it?
> >
> > The work queue doesn't need to be flushed every time a driver unbinds
> > from the gadget, only when the gadget is about to be deallocated.
> >
>
> That sounds good to me.
>
> Thanks,
> Thinh

Thank both of you for the review!
I've sent out https://lore.kernel.org/linux-usb/20250204000102.3989779-1-royluo@google.com
per Alan's suggestion.

Regards,
Roy
diff mbox series

Patch

diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c
index d27af65eb08a..12055e3af622 100644
--- a/drivers/usb/dwc3/gadget.c
+++ b/drivers/usb/dwc3/gadget.c
@@ -4580,6 +4580,7 @@  static void dwc_gadget_release(struct device *dev)
 {
 	struct usb_gadget *gadget = container_of(dev, struct usb_gadget, dev);
 
+	flush_work(&gadget->work);
 	kfree(gadget);
 }