FAILED: patch "[PATCH] usb: gadget: udc: fix use-after-free in usb_gadget_state_work" failed to apply to 6.12-stable tree
Subsystems (2)
[email protected]
maintainerThe patch below does not apply to the 6.12-stable tree.
If someone wants it applied there, or to any other stable or longterm tree, then please email the backport, including the original git commit id to <[email protected]>.
To reproduce the conflict and resubmit, you may use the following commands:
git fetch https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/ linux-6.12.y
git checkout FETCH_HEAD
git cherry-pick -x baeb66fbd4201d1c4325074e78b1f557dff89b5b
# <resolve conflicts, build, test, etc.>
git commit -s
git send-email --to '<[email protected]>' --in-reply-to '2025120121-sift-shiny-dd51@gregkh' --subject-prefix 'PATCH 6.12.y' HEAD^..
Possible dependencies:
thanks,
greg k-h
------------------ original commit in Linus's tree ------------------
From baeb66fbd4201d1c4325074e78b1f557dff89b5b Mon Sep 17 00:00:00 2001
From: Jimmy Hu <[email protected]>
Date: Thu, 23 Oct 2025 05:49:45 +0000
Subject: [PATCH] usb: gadget: udc: fix use-after-free in usb_gadget_state_work
A race condition during gadget teardown can lead to a use-after-free in usb_gadget_state_work(), as reported by KASAN:
BUG: KASAN: invalid-access in sysfs_notify+0x2c/0xd0 Workqueue: events usb_gadget_state_work
The fundamental race occurs because a concurrent event (e.g., an interrupt) can call usb_gadget_set_state() and schedule gadget->work at any time during the cleanup process in usb_del_gadget().
Commit 399a45e5237c ("usb: gadget: core: flush gadget workqueue after device removal") attempted to fix this by moving flush_work() to after device_del(). However, this does not fully solve the race, as a new work item can still be scheduled *after* flush_work() completes but before the gadget's memory is freed, leading to the same use-after-free.
This patch fixes the race condition robustly by introducing a 'teardown' flag and a 'state_lock' spinlock to the usb_gadget struct. The flag is set during cleanup in usb_del_gadget() *before* calling flush_work() to prevent any new work from being scheduled once cleanup has commenced. The scheduling site, usb_gadget_set_state(), now checks this flag under the lock before queueing the work, thus safely closing the race window.
drivers/usb/gadget/udc/core.c +16 -1
@@ -1126,8 +1126,13 @@ static void usb_gadget_state_work(struct work_struct *work)void usb_gadget_set_state (struct usb_gadget *gadget,enum usb_device_state state) { +unsigned long flags; + +spin_lock_irqsave (&gadget->state_lock, flags); gadget->state = state; -schedule_work (&gadget->work); +if (!gadget->teardown) +schedule_work (&gadget->work); +spin_unlock_irqrestore (&gadget->state_lock, flags);trace_usb_gadget_set_state (gadget,0 ); }EXPORT_SYMBOL_GPL (usb_gadget_set_state);
@@ -1361,6 +1366,8 @@ static void usb_udc_nop_release(struct device *dev)void usb_initialize_gadget (struct device *parent,struct usb_gadget *gadget,void (*release)(struct device *dev)) { +spin_lock_init (&gadget->state_lock); + gadget->teardown = false;INIT_WORK (&gadget->work, usb_gadget_state_work); gadget->dev.parent = parent;
@@ -1535,6 +1542,7 @@ EXPORT_SYMBOL_GPL(usb_add_gadget_udc);void usb_del_gadget (struct usb_gadget *gadget) {struct usb_udc *udc = gadget->udc; +unsigned long flags;if (!udc)return ;
@@ -1548,6 +1556,13 @@ void usb_del_gadget(struct usb_gadget *gadget)kobject_uevent (&udc->dev.kobj,KOBJ_REMOVE );sysfs_remove_link (&udc->dev.kobj,"gadget" );device_del (&gadget->dev); +/* +* Set the teardown flag before flushing the work to prevent new work +* from being scheduled while we are cleaning up. +*/ +spin_lock_irqsave (&gadget->state_lock, flags); + gadget->teardown = true; +spin_unlock_irqrestore (&gadget->state_lock, flags);flush_work (&gadget->work);ida_free (&gadget_id_numbers, gadget->id_number);cancel_work_sync (&udc->vbus_work);
include/linux/usb/gadget.h +5 -0
@@ -376,6 +376,9 @@ struct usb_gadget_ops {* can handle. The UDC must support this and all slower speeds and lower * number of lanes. * @state: the state we are now (attached, suspended, configured, etc) +* @state_lock: Spinlock protecting the `state` and `teardown` members. +* @teardown: True if the device is undergoing teardown, used to prevent +* new work from being scheduled during cleanup. * @name: Identifies the controller hardware type. Used in diagnostics * and sometimes configuration. * @dev: Driver model state for this abstract device.
@@ -451,6 +454,8 @@ struct usb_gadget {enum usb_ssp_rate max_ssp_rate;enum usb_device_state state; +spinlock_t state_lock; +bool teardown;const char *name;struct device dev;unsigned isoch_delay;
Sasha Levin
maintainerPATCH 1/2 usb: udc: Add trace event for usb_gadget_set_state
From: Kuen-Han Tsai <[email protected]>
[ Upstream commit 7bf1158514e410310aec975e630cec99d4e4092f ]
While the userspace program can be notified of gadget state changes, timing issue can lead to missed transitions when reading the state value.
Introduce a trace event for usb_gadget_set_state to reliably track state transitions.
Stable-dep-of: baeb66fbd420 ("usb: gadget: udc: fix use-after-free in usb_gadget_state_work")
--- drivers/usb/gadget/udc/core.c | 1 + drivers/usb/gadget/udc/trace.h | 5 +++++ 2 files changed, 6 insertions(+)
drivers/usb/gadget/udc/core.c +1 -0
@@ -1128,6 +1128,7 @@ void usb_gadget_set_state(struct usb_gadget *gadget, { gadget->state = state;schedule_work (&gadget->work); +trace_usb_gadget_set_state (gadget,0 ); }EXPORT_SYMBOL_GPL (usb_gadget_set_state);
drivers/usb/gadget/udc/trace.h +5 -0
@@ -81,6 +81,11 @@ DECLARE_EVENT_CLASS(udc_log_gadget, __entry->ret) ); +DEFINE_EVENT (udc_log_gadget, usb_gadget_set_state, + TP_PROTO(struct usb_gadget *g, int ret), +TP_ARGS (g, ret) +); +DEFINE_EVENT (udc_log_gadget, usb_gadget_frame_number, TP_PROTO(struct usb_gadget *g, int ret), TP_ARGS(g, ret)
Signature · 1 line
2.51.0
Sasha Levin
maintainerPATCH 2/2 usb: gadget: udc: fix use-after-free in usb_gadget_state_work
From: Jimmy Hu <[email protected]>
[ Upstream commit baeb66fbd4201d1c4325074e78b1f557dff89b5b ]
A race condition during gadget teardown can lead to a use-after-free in usb_gadget_state_work(), as reported by KASAN:
BUG: KASAN: invalid-access in sysfs_notify+0x2c/0xd0 Workqueue: events usb_gadget_state_work
The fundamental race occurs because a concurrent event (e.g., an interrupt) can call usb_gadget_set_state() and schedule gadget->work at any time during the cleanup process in usb_del_gadget().
Commit 399a45e5237c ("usb: gadget: core: flush gadget workqueue after device removal") attempted to fix this by moving flush_work() to after device_del(). However, this does not fully solve the race, as a new work item can still be scheduled *after* flush_work() completes but before the gadget's memory is freed, leading to the same use-after-free.
This patch fixes the race condition robustly by introducing a 'teardown' flag and a 'state_lock' spinlock to the usb_gadget struct. The flag is set during cleanup in usb_del_gadget() *before* calling flush_work() to prevent any new work from being scheduled once cleanup has commenced. The scheduling site, usb_gadget_set_state(), now checks this flag under the lock before queueing the work, thus safely closing the race window.
--- drivers/usb/gadget/udc/core.c | 17 ++++++++++++++++- include/linux/usb/gadget.h | 5 +++++ 2 files changed, 21 insertions(+), 1 deletion(-)
drivers/usb/gadget/udc/core.c +16 -1
@@ -1126,8 +1126,13 @@ static void usb_gadget_state_work(struct work_struct *work)void usb_gadget_set_state (struct usb_gadget *gadget,enum usb_device_state state) { +unsigned long flags; + +spin_lock_irqsave (&gadget->state_lock, flags); gadget->state = state; -schedule_work (&gadget->work); +if (!gadget->teardown) +schedule_work (&gadget->work); +spin_unlock_irqrestore (&gadget->state_lock, flags);trace_usb_gadget_set_state (gadget,0 ); }EXPORT_SYMBOL_GPL (usb_gadget_set_state);
@@ -1361,6 +1366,8 @@ static void usb_udc_nop_release(struct device *dev)void usb_initialize_gadget (struct device *parent,struct usb_gadget *gadget,void (*release)(struct device *dev)) { +spin_lock_init (&gadget->state_lock); + gadget->teardown = false;INIT_WORK (&gadget->work, usb_gadget_state_work); gadget->dev.parent = parent;
@@ -1535,6 +1542,7 @@ EXPORT_SYMBOL_GPL(usb_add_gadget_udc);void usb_del_gadget (struct usb_gadget *gadget) {struct usb_udc *udc = gadget->udc; +unsigned long flags;if (!udc)return ;
@@ -1548,6 +1556,13 @@ void usb_del_gadget(struct usb_gadget *gadget)kobject_uevent (&udc->dev.kobj,KOBJ_REMOVE );sysfs_remove_link (&udc->dev.kobj,"gadget" );device_del (&gadget->dev); +/* +* Set the teardown flag before flushing the work to prevent new work +* from being scheduled while we are cleaning up. +*/ +spin_lock_irqsave (&gadget->state_lock, flags); + gadget->teardown = true; +spin_unlock_irqrestore (&gadget->state_lock, flags);flush_work (&gadget->work);ida_free (&gadget_id_numbers, gadget->id_number);cancel_work_sync (&udc->vbus_work);
include/linux/usb/gadget.h +5 -0
@@ -376,6 +376,9 @@ struct usb_gadget_ops {* can handle. The UDC must support this and all slower speeds and lower * number of lanes. * @state: the state we are now (attached, suspended, configured, etc) +* @state_lock: Spinlock protecting the `state` and `teardown` members. +* @teardown: True if the device is undergoing teardown, used to prevent +* new work from being scheduled during cleanup. * @name: Identifies the controller hardware type. Used in diagnostics * and sometimes configuration. * @dev: Driver model state for this abstract device.
@@ -451,6 +454,8 @@ struct usb_gadget {enum usb_ssp_rate max_ssp_rate;enum usb_device_state state; +spinlock_t state_lock; +bool teardown;const char *name;struct device dev;unsigned isoch_delay;
Signature · 1 line
2.51.0