* [PATCH] usb: gadget: f_tcm: Cancel delayed set_alt work on teardown
@ 2026-06-23 1:52 Cen Zhang
2026-06-27 0:08 ` Thinh Nguyen
0 siblings, 1 reply; 3+ messages in thread
From: Cen Zhang @ 2026-06-23 1:52 UTC (permalink / raw)
To: Greg Kroah-Hartman, Jiasheng Jiang, Kees Cook, Mike Christie,
Martin K . Petersen, Christophe JAILLET, Thinh Nguyen
Cc: linux-usb, linux-kernel, baijiaju1990, zzzccc427
tcm_set_alt() defers BOT/UAS endpoint setup to a system workqueue and
returns USB_GADGET_DELAYED_STATUS. The queued work keeps the function
state alive only by a raw struct f_uas pointer, but configfs unlink can
unbind and free that function before the work runs.
The buggy scenario involves two paths, with each column showing the order
within that path:
USB control request path: configfs unlink path:
1. SET_CONFIGURATION or 1. Userspace removes the function
SET_INTERFACE reaches symlink while the gadget is bound.
tcm_set_alt().
2. tcm_set_alt() queues delayed 2. config_usb_cfg_unlink() forces
set_alt work and returns gadget unregister.
USB_GADGET_DELAYED_STATUS.
3. tcm_delayed_set_alt() later 3. tcm_unbind() releases descriptors
dereferences struct f_uas. and tcm_free() frees struct f_uas.
Validation reproduced this kernel report:
BUG: KASAN: slab-use-after-free in tcm_delayed_set_alt+0x6c/0xef0
Call Trace:
<TASK>
dump_stack_lvl+0x66/0xa0
print_report+0xce/0x630
? tcm_delayed_set_alt+0x6c/0xef0
? srso_alias_return_thunk+0x5/0xfbef5
? __virt_addr_valid+0x188/0x320
? tcm_delayed_set_alt+0x6c/0xef0
kasan_report+0xe0/0x110
? tcm_delayed_set_alt+0x6c/0xef0
tcm_delayed_set_alt+0x6c/0xef0
? __pfx_tcm_delayed_set_alt+0x10/0x10
? process_one_work+0x4cb/0xb90
? rcu_is_watching+0x20/0x50
? tcm_delayed_set_alt+0x9/0xef0
process_one_work+0x4d7/0xb90
? __pfx_process_one_work+0x10/0x10
? srso_alias_return_thunk+0x5/0xfbef5
? __list_add_valid_or_report+0x37/0xf0
? __pfx_tcm_delayed_set_alt+0x10/0x10
? srso_alias_return_thunk+0x5/0xfbef5
worker_thread+0x2d8/0x570
? __pfx_worker_thread+0x10/0x10
kthread+0x1ad/0x1f0
? __pfx_kthread+0x10/0x10
ret_from_fork+0x3c9/0x540
? __pfx_ret_from_fork+0x10/0x10
? srso_alias_return_thunk+0x5/0xfbef5
? __switch_to+0x2e9/0x730
? __pfx_kthread+0x10/0x10
ret_from_fork_asm+0x1a/0x30
</TASK>
Allocated by task 544:
kasan_save_stack+0x33/0x60
kasan_save_track+0x14/0x30
__kasan_kmalloc+0x8f/0xa0
tcm_alloc+0x68/0x180
usb_get_function+0x36/0x60
config_usb_cfg_link+0x125/0x1b0
configfs_symlink+0x322/0x890
vfs_symlink+0xc2/0x270
filename_symlinkat+0x295/0x2f0
__x64_sys_symlinkat+0x62/0x90
do_syscall_64+0x115/0x6a0
entry_SYSCALL_64_after_hwframe+0x77/0x7f
Freed by task 661:
kasan_save_stack+0x33/0x60
kasan_save_track+0x14/0x30
kasan_save_free_info+0x3b/0x60
__kasan_slab_free+0x43/0x70
kfree+0x2f9/0x530
config_usb_cfg_unlink+0x173/0x1e0
configfs_unlink+0x1fa/0x340
vfs_unlink+0x15c/0x510
filename_unlinkat+0x2ba/0x450
__x64_sys_unlinkat+0x63/0x90
do_syscall_64+0x115/0x6a0
entry_SYSCALL_64_after_hwframe+0x77/0x7f
Make the delayed set_alt work item owned by struct f_uas instead of an
anonymous heap allocation. This gives teardown paths a stable handle for
the exact work item: disable cancels pending work without sleeping, and
unbind/free synchronously cancel it before descriptors or struct f_uas are
released.
Fixes: c52661d60f63 ("usb-gadget: Initial merge of target module for UASP + BOT")
Assisted-by: Codex:gpt-5.5
Signed-off-by: Cen Zhang <zzzccc427@gmail.com>
---
drivers/usb/gadget/function/f_tcm.c | 32 ++++++++++-------------------
drivers/usb/gadget/function/tcm.h | 2 ++
2 files changed, 13 insertions(+), 21 deletions(-)
diff --git a/drivers/usb/gadget/function/f_tcm.c b/drivers/usb/gadget/function/f_tcm.c
index 34d9f49e9987..4096f94586fe 100644
--- a/drivers/usb/gadget/function/f_tcm.c
+++ b/drivers/usb/gadget/function/f_tcm.c
@@ -2363,20 +2363,10 @@ static int tcm_bind(struct usb_configuration *c, struct usb_function *f)
return -ENOTSUPP;
}
-struct guas_setup_wq {
- struct work_struct work;
- struct f_uas *fu;
- unsigned int alt;
-};
-
static void tcm_delayed_set_alt(struct work_struct *wq)
{
- struct guas_setup_wq *work = container_of(wq, struct guas_setup_wq,
- work);
- struct f_uas *fu = work->fu;
- int alt = work->alt;
-
- kfree(work);
+ struct f_uas *fu = container_of(wq, struct f_uas, delayed_set_alt);
+ int alt = fu->delayed_alt;
if (fu->flags & USBG_IS_BOT)
bot_cleanup_old_alt(fu);
@@ -2413,15 +2403,8 @@ static int tcm_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
return -EOPNOTSUPP;
if ((alt == USB_G_ALT_INT_BBB) || (alt == USB_G_ALT_INT_UAS)) {
- struct guas_setup_wq *work;
-
- work = kmalloc_obj(*work, GFP_ATOMIC);
- if (!work)
- return -ENOMEM;
- INIT_WORK(&work->work, tcm_delayed_set_alt);
- work->fu = fu;
- work->alt = alt;
- schedule_work(&work->work);
+ fu->delayed_alt = alt;
+ schedule_work(&fu->delayed_set_alt);
return USB_GADGET_DELAYED_STATUS;
}
return -EOPNOTSUPP;
@@ -2431,6 +2414,8 @@ static void tcm_disable(struct usb_function *f)
{
struct f_uas *fu = to_f_uas(f);
+ cancel_work(&fu->delayed_set_alt);
+
if (fu->flags & USBG_IS_UAS)
uasp_cleanup_old_alt(fu);
else if (fu->flags & USBG_IS_BOT)
@@ -2583,11 +2568,15 @@ static void tcm_free(struct usb_function *f)
{
struct f_uas *tcm = to_f_uas(f);
+ cancel_work_sync(&tcm->delayed_set_alt);
kfree(tcm);
}
static void tcm_unbind(struct usb_configuration *c, struct usb_function *f)
{
+ struct f_uas *tcm = to_f_uas(f);
+
+ cancel_work_sync(&tcm->delayed_set_alt);
usb_free_all_descriptors(f);
}
@@ -2621,6 +2610,7 @@ static struct usb_function *tcm_alloc(struct usb_function_instance *fi)
fu->function.free_func = tcm_free;
fu->tpg = tpg_instances[i].tpg;
+ INIT_WORK(&fu->delayed_set_alt, tcm_delayed_set_alt);
hash_init(fu->stream_hash);
mutex_unlock(&tpg_instances_lock);
diff --git a/drivers/usb/gadget/function/tcm.h b/drivers/usb/gadget/function/tcm.h
index 009974d81d66..b07bf4aa7d88 100644
--- a/drivers/usb/gadget/function/tcm.h
+++ b/drivers/usb/gadget/function/tcm.h
@@ -123,6 +123,8 @@ struct f_uas {
struct usbg_tpg *tpg;
struct usb_function function;
u16 iface;
+ struct work_struct delayed_set_alt;
+ unsigned int delayed_alt;
u32 flags;
#define USBG_ENABLED (1 << 0)
--
2.43.0
^ permalink raw reply related [flat|nested] 3+ messages in thread* Re: [PATCH] usb: gadget: f_tcm: Cancel delayed set_alt work on teardown
2026-06-23 1:52 [PATCH] usb: gadget: f_tcm: Cancel delayed set_alt work on teardown Cen Zhang
@ 2026-06-27 0:08 ` Thinh Nguyen
2026-06-27 10:39 ` Cen Zhang
0 siblings, 1 reply; 3+ messages in thread
From: Thinh Nguyen @ 2026-06-27 0:08 UTC (permalink / raw)
To: Cen Zhang
Cc: Greg Kroah-Hartman, Jiasheng Jiang, Kees Cook, Mike Christie,
Martin K . Petersen, Christophe JAILLET, Thinh Nguyen,
linux-usb@vger.kernel.org, linux-kernel@vger.kernel.org,
baijiaju1990@gmail.com
On Tue, Jun 23, 2026, Cen Zhang wrote:
> tcm_set_alt() defers BOT/UAS endpoint setup to a system workqueue and
> returns USB_GADGET_DELAYED_STATUS. The queued work keeps the function
> state alive only by a raw struct f_uas pointer, but configfs unlink can
> unbind and free that function before the work runs.
>
> The buggy scenario involves two paths, with each column showing the order
> within that path:
>
> USB control request path: configfs unlink path:
> 1. SET_CONFIGURATION or 1. Userspace removes the function
> SET_INTERFACE reaches symlink while the gadget is bound.
> tcm_set_alt().
> 2. tcm_set_alt() queues delayed 2. config_usb_cfg_unlink() forces
> set_alt work and returns gadget unregister.
> USB_GADGET_DELAYED_STATUS.
> 3. tcm_delayed_set_alt() later 3. tcm_unbind() releases descriptors
> dereferences struct f_uas. and tcm_free() frees struct f_uas.
>
> Validation reproduced this kernel report:
> BUG: KASAN: slab-use-after-free in tcm_delayed_set_alt+0x6c/0xef0
>
> Call Trace:
> <TASK>
> dump_stack_lvl+0x66/0xa0
> print_report+0xce/0x630
> ? tcm_delayed_set_alt+0x6c/0xef0
> ? srso_alias_return_thunk+0x5/0xfbef5
> ? __virt_addr_valid+0x188/0x320
> ? tcm_delayed_set_alt+0x6c/0xef0
> kasan_report+0xe0/0x110
> ? tcm_delayed_set_alt+0x6c/0xef0
> tcm_delayed_set_alt+0x6c/0xef0
> ? __pfx_tcm_delayed_set_alt+0x10/0x10
> ? process_one_work+0x4cb/0xb90
> ? rcu_is_watching+0x20/0x50
> ? tcm_delayed_set_alt+0x9/0xef0
> process_one_work+0x4d7/0xb90
> ? __pfx_process_one_work+0x10/0x10
> ? srso_alias_return_thunk+0x5/0xfbef5
> ? __list_add_valid_or_report+0x37/0xf0
> ? __pfx_tcm_delayed_set_alt+0x10/0x10
> ? srso_alias_return_thunk+0x5/0xfbef5
> worker_thread+0x2d8/0x570
> ? __pfx_worker_thread+0x10/0x10
> kthread+0x1ad/0x1f0
> ? __pfx_kthread+0x10/0x10
> ret_from_fork+0x3c9/0x540
> ? __pfx_ret_from_fork+0x10/0x10
> ? srso_alias_return_thunk+0x5/0xfbef5
> ? __switch_to+0x2e9/0x730
> ? __pfx_kthread+0x10/0x10
> ret_from_fork_asm+0x1a/0x30
> </TASK>
>
> Allocated by task 544:
> kasan_save_stack+0x33/0x60
> kasan_save_track+0x14/0x30
> __kasan_kmalloc+0x8f/0xa0
> tcm_alloc+0x68/0x180
> usb_get_function+0x36/0x60
> config_usb_cfg_link+0x125/0x1b0
> configfs_symlink+0x322/0x890
> vfs_symlink+0xc2/0x270
> filename_symlinkat+0x295/0x2f0
> __x64_sys_symlinkat+0x62/0x90
> do_syscall_64+0x115/0x6a0
> entry_SYSCALL_64_after_hwframe+0x77/0x7f
>
> Freed by task 661:
> kasan_save_stack+0x33/0x60
> kasan_save_track+0x14/0x30
> kasan_save_free_info+0x3b/0x60
> __kasan_slab_free+0x43/0x70
> kfree+0x2f9/0x530
> config_usb_cfg_unlink+0x173/0x1e0
> configfs_unlink+0x1fa/0x340
> vfs_unlink+0x15c/0x510
> filename_unlinkat+0x2ba/0x450
> __x64_sys_unlinkat+0x63/0x90
> do_syscall_64+0x115/0x6a0
> entry_SYSCALL_64_after_hwframe+0x77/0x7f
>
> Make the delayed set_alt work item owned by struct f_uas instead of an
> anonymous heap allocation. This gives teardown paths a stable handle for
> the exact work item: disable cancels pending work without sleeping, and
> unbind/free synchronously cancel it before descriptors or struct f_uas are
> released.
>
> Fixes: c52661d60f63 ("usb-gadget: Initial merge of target module for UASP + BOT")
Cc stable for these kinds of fixes.
> Assisted-by: Codex:gpt-5.5
> Signed-off-by: Cen Zhang <zzzccc427@gmail.com>
> ---
> drivers/usb/gadget/function/f_tcm.c | 32 ++++++++++-------------------
> drivers/usb/gadget/function/tcm.h | 2 ++
> 2 files changed, 13 insertions(+), 21 deletions(-)
>
> diff --git a/drivers/usb/gadget/function/f_tcm.c b/drivers/usb/gadget/function/f_tcm.c
> index 34d9f49e9987..4096f94586fe 100644
> --- a/drivers/usb/gadget/function/f_tcm.c
> +++ b/drivers/usb/gadget/function/f_tcm.c
> @@ -2363,20 +2363,10 @@ static int tcm_bind(struct usb_configuration *c, struct usb_function *f)
> return -ENOTSUPP;
> }
>
> -struct guas_setup_wq {
> - struct work_struct work;
> - struct f_uas *fu;
> - unsigned int alt;
> -};
> -
> static void tcm_delayed_set_alt(struct work_struct *wq)
> {
> - struct guas_setup_wq *work = container_of(wq, struct guas_setup_wq,
> - work);
> - struct f_uas *fu = work->fu;
> - int alt = work->alt;
> -
> - kfree(work);
> + struct f_uas *fu = container_of(wq, struct f_uas, delayed_set_alt);
> + int alt = fu->delayed_alt;
>
> if (fu->flags & USBG_IS_BOT)
> bot_cleanup_old_alt(fu);
> @@ -2413,15 +2403,8 @@ static int tcm_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
> return -EOPNOTSUPP;
>
> if ((alt == USB_G_ALT_INT_BBB) || (alt == USB_G_ALT_INT_UAS)) {
> - struct guas_setup_wq *work;
> -
> - work = kmalloc_obj(*work, GFP_ATOMIC);
> - if (!work)
> - return -ENOMEM;
> - INIT_WORK(&work->work, tcm_delayed_set_alt);
> - work->fu = fu;
> - work->alt = alt;
> - schedule_work(&work->work);
> + fu->delayed_alt = alt;
> + schedule_work(&fu->delayed_set_alt);
> return USB_GADGET_DELAYED_STATUS;
> }
> return -EOPNOTSUPP;
> @@ -2431,6 +2414,8 @@ static void tcm_disable(struct usb_function *f)
> {
> struct f_uas *fu = to_f_uas(f);
>
> + cancel_work(&fu->delayed_set_alt);
> +
Couldn't a race still happen here? I don't think you can just use
cancel_work_sync in tcm_disable here.
BR,
Thinh
> if (fu->flags & USBG_IS_UAS)
> uasp_cleanup_old_alt(fu);
> else if (fu->flags & USBG_IS_BOT)
> @@ -2583,11 +2568,15 @@ static void tcm_free(struct usb_function *f)
> {
> struct f_uas *tcm = to_f_uas(f);
>
> + cancel_work_sync(&tcm->delayed_set_alt);
> kfree(tcm);
> }
>
> static void tcm_unbind(struct usb_configuration *c, struct usb_function *f)
> {
> + struct f_uas *tcm = to_f_uas(f);
> +
> + cancel_work_sync(&tcm->delayed_set_alt);
> usb_free_all_descriptors(f);
> }
>
> @@ -2621,6 +2610,7 @@ static struct usb_function *tcm_alloc(struct usb_function_instance *fi)
> fu->function.free_func = tcm_free;
> fu->tpg = tpg_instances[i].tpg;
>
> + INIT_WORK(&fu->delayed_set_alt, tcm_delayed_set_alt);
> hash_init(fu->stream_hash);
> mutex_unlock(&tpg_instances_lock);
>
> diff --git a/drivers/usb/gadget/function/tcm.h b/drivers/usb/gadget/function/tcm.h
> index 009974d81d66..b07bf4aa7d88 100644
> --- a/drivers/usb/gadget/function/tcm.h
> +++ b/drivers/usb/gadget/function/tcm.h
> @@ -123,6 +123,8 @@ struct f_uas {
> struct usbg_tpg *tpg;
> struct usb_function function;
> u16 iface;
> + struct work_struct delayed_set_alt;
> + unsigned int delayed_alt;
>
> u32 flags;
> #define USBG_ENABLED (1 << 0)
> --
> 2.43.0
^ permalink raw reply [flat|nested] 3+ messages in thread* Re: [PATCH] usb: gadget: f_tcm: Cancel delayed set_alt work on teardown
2026-06-27 0:08 ` Thinh Nguyen
@ 2026-06-27 10:39 ` Cen Zhang
0 siblings, 0 replies; 3+ messages in thread
From: Cen Zhang @ 2026-06-27 10:39 UTC (permalink / raw)
To: Thinh Nguyen
Cc: Greg Kroah-Hartman, Jiasheng Jiang, Kees Cook, Mike Christie,
Martin K . Petersen, Christophe JAILLET,
linux-usb@vger.kernel.org, linux-kernel@vger.kernel.org,
baijiaju1990@gmail.com
Hi Thinh,
Thinh Nguyen <Thinh.Nguyen@synopsys.com> 于2026年6月27日周六 08:08写道:
>
> Couldn't a race still happen here? I don't think you can just use
> cancel_work_sync in tcm_disable here.
>
You're right. tcm_disable() can be called while the composite device lock is
held, so using cancel_work_sync() there is not safe.
I reworked this in v2 by using a small delayed-set-alt state machine:
tcm_disable() now only uses a non-sleeping cancellation path. If the work is
still queued, it is cancelled and cleaned up immediately. If it is already
running, it is marked cancelled and the worker owns the cleanup before
returning. cancel_work_sync() is only used from unbind/free paths where
sleeping is safe.
I also added Cc: stable@vger.kernel.org as suggested.
Thanks,
Cen Zhang
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-06-27 10:39 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-23 1:52 [PATCH] usb: gadget: f_tcm: Cancel delayed set_alt work on teardown Cen Zhang
2026-06-27 0:08 ` Thinh Nguyen
2026-06-27 10:39 ` Cen Zhang
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox