Linux USB
 help / color / mirror / Atom feed
* [PATCH 1/3] usb: gadget: f_tcm: take the TPG in tcm_bind() instead of tcm_alloc()
@ 2026-10-22 13:49 Slawomir Stepien
  2026-10-22 13:49 ` [PATCH 2/3] usb: gadget: f_tcm: detach gadget when dropping an enabled TPG Slawomir Stepien
                   ` (2 more replies)
  0 siblings, 3 replies; 6+ messages in thread
From: Slawomir Stepien @ 2026-10-22 13:49 UTC (permalink / raw)
  To: gregkh, andrzej.p, jiashengjiangcool, yun.zhou, zzzccc427,
	bigeasy, nab
  Cc: linux-usb, linux-kernel, Slawomir Stepien,
	syzbot+2799146a53c356f8017d

tcm_alloc() runs when a TCM function is symlinked into a gadget
configuration. It searches tpg_instances[] for the function instance
and copies the TPG pointer into fu->tpg at that point, under
tpg_instances_lock. This has two problems.

The function and the TPG can be set up in either order. If the
function is linked into a configuration before the TPG directory is
created, tpg_instances[i].tpg is still NULL and fu->tpg stays NULL.
Creating and enabling the TPG afterwards sets can_attach, so
tcm_bind() succeeds, and the first USB request that reaches the
target code dereferences the NULL fu->tpg.

Removing a TPG also leaves can_attach set for configfs instances, so a
later bind is not refused even though the TPG it would use is gone.

Store the TPG in struct f_tcm_opts instead: set it in usbg_make_tpg()
and clear it in usbg_drop_tpg(), under opts->dep_lock. In tcm_bind(),
refuse to bind unless both can_attach and opts->tpg are set, and copy
opts->tpg into fu->tpg there. Also clear can_attach in usbg_drop_tpg()
for configfs instances. The legacy g_target driver sets can_attach once
at module load, and its attach callback does not set it again, so
leave it alone in that case or g_target could never bind again after a
TPG is recreated.

With fu->tpg no longer set in tcm_alloc(), the tpg_instances[] lookup
there serves no purpose: f->fi already identifies the instance for the
item's whole lifetime. Drop it, and with it the tpg_instances_lock
acquisition. config_usb_cfg_link() calls tcm_alloc() with gi->lock
held, so this also removes the gi->lock -> tpg_instances_lock edge
from the following lockdep report:

  WARNING: possible circular locking dependency detected
  task is trying to acquire lock:
   (tpg_instances_lock){+.+.}-{4:4}, at: tcm_alloc
  but task is already holding lock:
   (&gi->lock){+.+.}-{4:4}, at: config_usb_cfg_link

  the existing dependency chain (in reverse order) is:
  -> #2 (&gi->lock):
         config_usb_cfg_link
         configfs_symlink
  -> #1 (&sb->s_type->i_mutex_key#24):
         configfs_depend_item_unlocked
         usbg_make_tpg
         target_fabric_make_tpg
         configfs_mkdir
  -> #0 (tpg_instances_lock):
         tcm_alloc
         usb_get_function
         config_usb_cfg_link
         configfs_symlink

Fixes: 4bb8548df632 ("usb: gadget: f_tcm: add configfs support")
Reported-by: syzbot+2799146a53c356f8017d@syzkaller.appspotmail.com
Closes: https://syzkaller.appspot.com/bug?extid=2799146a53c356f8017d
Link: https://syzkaller.appspot.com/ai_job?id=51e1f189-6d91-4321-a6ba-07d4eb3cd8cd
Assisted-by: Gemini:gemini-4-argon Gemini:gemini-3.8-flash syzbot
Signed-off-by: Slawomir Stepien <sst@poczta.fm>
---
 drivers/usb/gadget/function/f_tcm.c | 29 +++++++++++------------------
 drivers/usb/gadget/function/u_tcm.h |  4 ++++
 2 files changed, 15 insertions(+), 18 deletions(-)

diff --git a/drivers/usb/gadget/function/f_tcm.c b/drivers/usb/gadget/function/f_tcm.c
index 9e6d4f39900a..cc75d7e9792a 100644
--- a/drivers/usb/gadget/function/f_tcm.c
+++ b/drivers/usb/gadget/function/f_tcm.c
@@ -1720,6 +1720,9 @@ static struct se_portal_group *usbg_make_tpg(struct se_wwn *wwn,
 
 	tpg_instances[i].tpg = tpg;
 	tpg->fi = tpg_instances[i].func_inst;
+	mutex_lock(&opts->dep_lock);
+	opts->tpg = tpg;
+	mutex_unlock(&opts->dep_lock);
 	mutex_unlock(&tpg_instances_lock);
 	return &tpg->se_tpg;
 
@@ -1760,11 +1763,14 @@ static void usbg_drop_tpg(struct se_portal_group *se_tpg)
 		opts = container_of(tpg_instances[i].func_inst,
 			struct f_tcm_opts, func_inst);
 		mutex_lock(&opts->dep_lock);
-		if (opts->has_dep)
+		opts->tpg = NULL;
+		if (opts->has_dep) {
 			module_put(opts->dependent);
-		else
+		} else {
+			opts->can_attach = false;
 			configfs_undepend_item_unlocked(
 				&opts->func_inst.group.cg_item);
+		}
 		mutex_unlock(&opts->dep_lock);
 	}
 	mutex_unlock(&tpg_instances_lock);
@@ -2300,10 +2306,11 @@ static int tcm_bind(struct usb_configuration *c, struct usb_function *f)
 	opts = container_of(f->fi, struct f_tcm_opts, func_inst);
 
 	mutex_lock(&opts->dep_lock);
-	if (!opts->can_attach) {
+	if (!opts->can_attach || !opts->tpg) {
 		mutex_unlock(&opts->dep_lock);
 		return -ENODEV;
 	}
+	fu->tpg = opts->tpg;
 	mutex_unlock(&opts->dep_lock);
 	us = usb_gstrings_attach(c->cdev, tcm_strings,
 		ARRAY_SIZE(tcm_us_strings));
@@ -2729,22 +2736,10 @@ static void tcm_unbind(struct usb_configuration *c, struct usb_function *f)
 static struct usb_function *tcm_alloc(struct usb_function_instance *fi)
 {
 	struct f_uas *fu;
-	unsigned i;
-
-	mutex_lock(&tpg_instances_lock);
-	for (i = 0; i < TPG_INSTANCES; ++i)
-		if (tpg_instances[i].func_inst == fi)
-			break;
-	if (i == TPG_INSTANCES) {
-		mutex_unlock(&tpg_instances_lock);
-		return ERR_PTR(-ENODEV);
-	}
 
 	fu = kzalloc_obj(*fu);
-	if (!fu) {
-		mutex_unlock(&tpg_instances_lock);
+	if (!fu)
 		return ERR_PTR(-ENOMEM);
-	}
 
 	fu->function.name = "Target Function";
 	fu->function.bind = tcm_bind;
@@ -2754,12 +2749,10 @@ static struct usb_function *tcm_alloc(struct usb_function_instance *fi)
 	fu->function.setup = tcm_setup;
 	fu->function.disable = tcm_disable;
 	fu->function.free_func = tcm_free;
-	fu->tpg = tpg_instances[i].tpg;
 	INIT_WORK(&fu->delayed_set_alt, tcm_delayed_set_alt);
 	spin_lock_init(&fu->delayed_set_alt_lock);
 
 	hash_init(fu->stream_hash);
-	mutex_unlock(&tpg_instances_lock);
 
 	return &fu->function;
 }
diff --git a/drivers/usb/gadget/function/u_tcm.h b/drivers/usb/gadget/function/u_tcm.h
index 2cd15d9a1c0d..fa38d55c2a17 100644
--- a/drivers/usb/gadget/function/u_tcm.h
+++ b/drivers/usb/gadget/function/u_tcm.h
@@ -15,6 +15,8 @@
 
 #include <linux/usb/composite.h>
 
+struct usbg_tpg;
+
 /**
  * @dependent: optional dependent module. Meant for legacy gadget.
  * If non-null its refcount will be increased when a tpg is created and
@@ -23,10 +25,12 @@
  * @ready: true if the dependent module information is set.
  * @can_attach: true a function can be bound to gadget
  * @has_dep: true if there is a dependent module
+ * @tpg: target portal group associated with this function instance.
  *
  */
 struct f_tcm_opts {
 	struct usb_function_instance	func_inst;
+	struct usbg_tpg			*tpg;
 	struct module			*dependent;
 	struct mutex			dep_lock;
 	bool				ready;
-- 
2.56.0


^ permalink raw reply related	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2026-10-22 14:02 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-22 13:49 [PATCH 1/3] usb: gadget: f_tcm: take the TPG in tcm_bind() instead of tcm_alloc() Slawomir Stepien
2026-10-22 13:49 ` [PATCH 2/3] usb: gadget: f_tcm: detach gadget when dropping an enabled TPG Slawomir Stepien
2026-10-22 14:00   ` sashiko-bot
2026-10-22 13:49 ` [PATCH 3/3] usb: gadget: f_tcm: don't hold dep_lock across unregister_gadget_item() Slawomir Stepien
2026-10-22 13:58   ` sashiko-bot
2026-10-22 14:02 ` [PATCH 1/3] usb: gadget: f_tcm: take the TPG in tcm_bind() instead of tcm_alloc() sashiko-bot

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox