[PATCH] usb: gadget: f_tcm: fix deadlock in usbg_make_tpg()

From: Yun Zhou

Date: Fri Jul 31 2026 - 04:13:20 EST


usbg_make_tpg() held dep_lock while calling
configfs_depend_item_unlocked(), which acquires the configfs root
inode lock when operating across subsystems. This creates a circular
lock dependency with configfs_rmdir():

dep_lock -> configfs root inode lock -> su_mutex -> dep_lock

In usbg_make_tpg(), dep_lock only serialized the read of opts->ready,
which is a monotonic flag that transitions from false to true exactly
once (in tcm_set_name()) and never reverts. Remove dep_lock from
usbg_make_tpg() entirely and use READ_ONCE/WRITE_ONCE to access
opts->ready locklessly instead.

Reported-by: syzbot+c9f9d646b08f3b6032fe@xxxxxxxxxxxxxxxxxxxxxxxxx
Closes: https://syzkaller.appspot.com/bug?extid=c9f9d646b08f3b6032fe
Fixes: 4bb8548df632 ("usb: gadget: f_tcm: add configfs support")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Yun Zhou <yun.zhou@xxxxxxxxxxxxx>
---
drivers/usb/gadget/function/f_tcm.c | 23 ++++++++++++-----------
1 file changed, 12 insertions(+), 11 deletions(-)

diff --git a/drivers/usb/gadget/function/f_tcm.c b/drivers/usb/gadget/function/f_tcm.c
index b3fa5a17fd2d..c727c19db25d 100644
--- a/drivers/usb/gadget/function/f_tcm.c
+++ b/drivers/usb/gadget/function/f_tcm.c
@@ -1675,19 +1675,25 @@ static struct se_portal_group *usbg_make_tpg(struct se_wwn *wwn,

opts = container_of(tpg_instances[i].func_inst, struct f_tcm_opts,
func_inst);
- mutex_lock(&opts->dep_lock);
- if (!opts->ready)
- goto unlock_dep;
+ if (!READ_ONCE(opts->ready))
+ goto unlock_inst;

if (opts->has_dep) {
if (!try_module_get(opts->dependent))
- goto unlock_dep;
+ goto unlock_inst;
} else {
+ /*
+ * configfs_depend_item_unlocked() may acquire the configfs
+ * root inode lock when the target belongs to a different
+ * subsystem. Calling it under dep_lock would create a
+ * circular dependency:
+ * dep_lock -> configfs inode lock -> su_mutex -> dep_lock
+ */
ret = configfs_depend_item_unlocked(
wwn->wwn_group.cg_subsys,
&opts->func_inst.group.cg_item);
if (ret)
- goto unlock_dep;
+ goto unlock_inst;
}

tpg = kzalloc_obj(struct usbg_tpg);
@@ -1714,7 +1720,6 @@ 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_unlock(&opts->dep_lock);
mutex_unlock(&tpg_instances_lock);
return &tpg->se_tpg;

@@ -1727,8 +1732,6 @@ static struct se_portal_group *usbg_make_tpg(struct se_wwn *wwn,
module_put(opts->dependent);
else
configfs_undepend_item_unlocked(&opts->func_inst.group.cg_item);
-unlock_dep:
- mutex_unlock(&opts->dep_lock);
unlock_inst:
mutex_unlock(&tpg_instances_lock);

@@ -2666,9 +2669,7 @@ static int tcm_set_name(struct usb_function_instance *f, const char *name)

pr_debug("tcm: Activating %s\n", name);

- mutex_lock(&opts->dep_lock);
- opts->ready = true;
- mutex_unlock(&opts->dep_lock);
+ WRITE_ONCE(opts->ready, true);

return 0;
}
--
2.43.0