[PATCH RFC] usb: gadget: f_tcm: Fix false positive circular locking dependency

"syzbot" <[email protected]> Wed, 29 Jul 2026 11:27:57 +0000 (UTC)
Newsgroups dev.linux.lists.syzbot
Message-ID <[email protected]>
Lockdep reports a possible circular locking dependency:

WARNING: possible circular locking dependency detected
------------------------------------------------------
task is trying to acquire lock:
(gadget_subsys.su_mutex){+.+.}-{4:4}, at:
uvcg_streaming_header_allow_link+0x75/0x4d0
drivers/usb/gadget/function/uvc_configfs.c:1750

but task is already holding lock:
(&sb->s_type->i_mutex_key#24){+.+.}-{4:4}, at: inode_lock
include/linux/fs.h:1024 [inline]
(&sb->s_type->i_mutex_key#24){+.+.}-{4:4}, at:
configfs_symlink+0x3af/0x1030 fs/configfs/symlink.c:186

which lock already depends on the new lock.

the existing dependency chain (in reverse order) is:

-> #2 (&sb->s_type->i_mutex_key#24){+.+.}-{4:4}:
       inode_lock include/linux/fs.h:1024 [inline]
       configfs_depend_item_unlocked+0x153/0x420 fs/configfs/dir.c:1259
       usbg_make_tpg+0x1f6/0x590 drivers/usb/gadget/function/f_tcm.c:1686
       target_fabric_make_tpg+0xa8/0x6d0
       drivers/target/target_core_fabric_configfs.c:939
       configfs_mkdir+0x4f6/0x9e0 fs/configfs/dir.c:1360

-> #1 (&opts->dep_lock){+.+.}-{4:4}:
       mutex_lock_nested+0x5a/0x1d0 kernel/locking/rtmutex_api.c:578
       tcm_set_name+0x2b/0xd0 drivers/usb/gadget/function/f_tcm.c:2669
       function_make+0x1a8/0x360 drivers/usb/gadget/configfs.c:636
       configfs_mkdir+0x4f6/0x9e0 fs/configfs/dir.c:1360

-> #0 (gadget_subsys.su_mutex){+.+.}-{4:4}:
       mutex_lock_nested+0x5a/0x1d0 kernel/locking/rtmutex_api.c:578
       uvcg_streaming_header_allow_link+0x75/0x4d0
       drivers/usb/gadget/function/uvc_configfs.c:1750
       configfs_symlink+0x59a/0x1030 fs/configfs/symlink.c:196

The cycle is reported as follows. First, inode_lock ->
gadget_subsys.su_mutex: In configfs_symlink(), VFS holds the inode_lock of
the parent directory. The function then calls
uvcg_streaming_header_allow_link(), which acquires gadget_subsys.su_mutex.
Second, gadget_subsys.su_mutex -> opts->dep_lock: In configfs_mkdir() for
the gadget subsystem, configfs holds gadget_subsys.su_mutex and calls
function_make() -> tcm_set_name(), which acquires opts->dep_lock. Third,
opts->dep_lock -> inode_lock: In configfs_mkdir() for the target subsystem,
configfs calls target_fabric_make_tpg() -> usbg_make_tpg(). This function
acquires opts->dep_lock and then calls configfs_depend_item_unlocked().
Because the target and gadget subsystems are different,
configfs_depend_item_unlocked() acquires the inode_lock of the configfs
root directory.

This is a false positive because configfs assigns the default VFS lock
class (&sb->s_type->i_mutex_key) to all non-default directories, including
the root directory and user-created directories. Lockdep sees the
inode_lock on the symlink parent directory and the inode_lock on the
configfs root directory as the same lock class, assuming a cycle. In
reality, these are different inodes, and the root directory's inode_lock is
never held while waiting for su_mutex or dep_lock.

To break this false positive cycle, avoid holding opts->dep_lock and
tpg_instances_lock while calling configfs_depend_item_unlocked() in
usbg_make_tpg(). This severs the dep_lock -> i_mutex_key link in lockdep's
graph.

To do this safely without introducing race conditions or use-after-free
bugs, several steps are taken. First, pre-allocate and reserve the tpg in
the tpg_instances array before dropping the locks so that concurrent calls
to usbg_make_tpg() skip this instance. Second, take a reference on the
config_item (config_item_get()) before dropping the locks. This prevents a
concurrent rmdir on the function directory from freeing the item while we
are depending on it. Third, drop the extra reference (config_item_put())
only after we have fully unlocked tpg_instances_lock. This is crucial
because config_item_put() might trigger the release function
tcm_free_inst(), which acquires tpg_instances_lock and would cause a real
deadlock if we still held it.

Fixes: 4bb8548df632 ("usb: gadget: f_tcm: add configfs support")
Assisted-by: Gemini:gemini-3.5-flash Gemini:gemini-3.1-pro-preview syzbot
Reported-by: [email protected]
Closes: https://syzkaller.appspot.com/bug?extid=ed4b9a6a61280b24d9c3
Link: https://syzkaller.appspot.com/ai_job?id=cf0ad2c8-f356-4700-bd6a-e8c2d28a72cd
To: "Greg Kroah-Hartman" <[email protected]>
To: <[email protected]>
To: "Andrzej Pietrasiewicz" <[email protected]>
Cc: "Jiasheng Jiang" <[email protected]>
Cc: <[email protected]>
Cc: "Cen Zhang" <[email protected]>

---
diff --git a/drivers/usb/gadget/function/f_tcm.c b/drivers/usb/gadget/function/f_tcm.c
index b3fa5a17f..c8278053d 100644
--- a/drivers/usb/gadget/function/f_tcm.c
+++ b/drivers/usb/gadget/function/f_tcm.c
@@ -1660,6 +1660,7 @@ static struct se_portal_group *usbg_make_tpg(struct se_wwn *wwn,
 	int ret;
 	struct f_tcm_opts *opts;
 	unsigned i;
+	bool dep_taken = false;
 
 	if (strstr(name, "tpgt_") != name)
 		return ERR_PTR(-EINVAL);
@@ -1679,59 +1680,84 @@ static struct se_portal_group *usbg_make_tpg(struct se_wwn *wwn,
 	if (!opts->ready)
 		goto unlock_dep;
 
-	if (opts->has_dep) {
-		if (!try_module_get(opts->dependent))
-			goto unlock_dep;
-	} else {
-		ret = configfs_depend_item_unlocked(
-			wwn->wwn_group.cg_subsys,
-			&opts->func_inst.group.cg_item);
-		if (ret)
-			goto unlock_dep;
-	}
-
 	tpg = kzalloc_obj(struct usbg_tpg);
-	ret = -ENOMEM;
-	if (!tpg)
-		goto unref_dep;
+	if (!tpg) {
+		ret = -ENOMEM;
+		goto unlock_dep;
+	}
 	mutex_init(&tpg->tpg_mutex);
 	atomic_set(&tpg->tpg_port_count, 0);
 	tpg->workqueue = alloc_workqueue("tcm_usb_gadget",
 					 WQ_UNBOUND, WQ_UNBOUND_MAX_ACTIVE);
-	if (!tpg->workqueue)
-		goto free_tpg;
-
+	if (!tpg->workqueue) {
+		ret = -ENOMEM;
+		kfree(tpg);
+		goto unlock_dep;
+	}
 	tpg->tport = tport;
 	tpg->tport_tpgt = tpgt;
 
+	tpg_instances[i].tpg = tpg;
+
+	if (opts->has_dep) {
+		if (!try_module_get(opts->dependent)) {
+			ret = -ENODEV;
+			goto free_tpg;
+		}
+	} else {
+		config_item_get(&opts->func_inst.group.cg_item);
+		dep_taken = true;
+		mutex_unlock(&opts->dep_lock);
+		mutex_unlock(&tpg_instances_lock);
+
+		ret = configfs_depend_item_unlocked(
+			wwn->wwn_group.cg_subsys,
+			&opts->func_inst.group.cg_item);
+
+		if (ret) {
+			mutex_lock(&tpg_instances_lock);
+			tpg_instances[i].tpg = NULL;
+			mutex_unlock(&tpg_instances_lock);
+			destroy_workqueue(tpg->workqueue);
+			kfree(tpg);
+			config_item_put(&opts->func_inst.group.cg_item);
+			return ERR_PTR(ret);
+		}
+
+		mutex_lock(&tpg_instances_lock);
+		mutex_lock(&opts->dep_lock);
+	}
+
 	/*
 	 * SPC doesn't assign a protocol identifier for USB-SCSI, so we
 	 * pretend to be SAS..
 	 */
 	ret = core_tpg_register(wwn, &tpg->se_tpg, SCSI_PROTOCOL_SAS);
 	if (ret < 0)
-		goto free_workqueue;
+		goto unref_dep;
 
-	tpg_instances[i].tpg = tpg;
 	tpg->fi = tpg_instances[i].func_inst;
 	mutex_unlock(&opts->dep_lock);
 	mutex_unlock(&tpg_instances_lock);
+	if (dep_taken)
+		config_item_put(&opts->func_inst.group.cg_item);
 	return &tpg->se_tpg;
 
-free_workqueue:
-	destroy_workqueue(tpg->workqueue);
-free_tpg:
-	kfree(tpg);
 unref_dep:
 	if (opts->has_dep)
 		module_put(opts->dependent);
 	else
 		configfs_undepend_item_unlocked(&opts->func_inst.group.cg_item);
+free_tpg:
+	tpg_instances[i].tpg = NULL;
+	destroy_workqueue(tpg->workqueue);
+	kfree(tpg);
 unlock_dep:
 	mutex_unlock(&opts->dep_lock);
 unlock_inst:
 	mutex_unlock(&tpg_instances_lock);
-
+	if (dep_taken)
+		config_item_put(&opts->func_inst.group.cg_item);
 	return ERR_PTR(ret);
 }
 


base-commit: f5098b6bae761e346ebcd9da7f95622c04733cff
-- 
This is an AI-generated patch subject to moderation.
Reply with '#syz upstream' to Sign-off the patch as a human author
and send it to the upstream kernel mailing lists.
Reply with '#syz reject' to reject it ('#syz unreject' to undo).

See https://goo.gle/syzbot-ai-patches for information about AI-generated patches.
You can comment on the patch as usual, syzbot will try to address
the comments and send a new version of the patch if necessary.
syzbot engineers can be reached at [email protected].