syzbot
possible deadlock in uvcg_streaming_class_drop_link
Status:
moderation: reported on 2026/11/15 17:09
Subsystems:
fs
Labels:
prio:low
[Documentation on labels]
Reported-by: syzbot+390ee4d6f346b3a35ad7@syzkaller.appspotmail.com
First crash: 1h35m, last: 1h33m
✨ AI-generated root cause summary:
A circular locking dependency warning is triggered between gadget_subsys.su_mutex, tpg_instances_lock, and the VFS inode mutex (&sb->s_type->i_mutex_key). During TCM portal group creation, usbg_make_tpg() calls configfs_depend_item_unlocked() while holding tpg_instances_lock (previously acquired under gadget_subsys.su_mutex), which acquires the configfs root directory inode lock. When subsequently unlinking a UVC streaming class symlink, vfs_unlink() holds the symlink's inode lock and invokes uvcg_streaming_class_drop_link(), which acquires gadget_subsys.su_mutex for hierarchy navigation. Because configfs symlink inodes and the configfs root directory inode both share the default superblock inode lockdep class (&sb->s_type->i_mutex_key), lockdep detects a circular dependency (su_mutex -> tpg_instances_lock -> i_mutex_key -> su_mutex).
▶
▼
✨ Full AI root cause analysis
Root Cause Analysis
1. Relevant Files, Structs, and Functions
- Files:
- Functions:
- Locks and Structs:
gadget_subsys.su_mutex (struct mutex, subsystem lock of gadget_subsys defined in drivers/usb/gadget/configfs.c)
tpg_instances_lock (struct mutex, global lock in drivers/usb/gadget/function/f_tcm.c)
&sb->s_type->i_mutex_key (struct rw_semaphore inode->i_rwsem, default lockdep class key for inodes on the configfs superblock)
2. Technical Mechanism and Trigger Flow
The reported lockdep warning occurs because lockdep observes a cycle among gadget_subsys.su_mutex, tpg_instances_lock, and the VFS inode->i_rwsem lock class (&sb->s_type->i_mutex_key):
-
Dependency #1 & #2 (gadget_subsys.su_mutex -> tpg_instances_lock):
- When a userspace process creates a TCM function instance via
mkdir under the gadget subsystem (e.g., /sys/kernel/config/usb_gadget/g1/functions/tcm.usb0), VFS calls configfs_mkdir().
configfs_mkdir() locks &subsys->su_mutex (gadget_subsys.su_mutex).
- Under
gadget_subsys.su_mutex, type->ct_group_ops->make_group() invokes function_make() (drivers/usb/gadget/configfs.c:626), which calls usb_get_function_instance() -> tcm_alloc_inst() (drivers/usb/gadget/function/f_tcm.c:2687).
tcm_alloc_inst() acquires mutex_lock(&tpg_instances_lock) to register the new function instance into tpg_instances[].
- Established lock order:
gadget_subsys.su_mutex $\rightarrow$ tpg_instances_lock.
-
Dependency #3 (tpg_instances_lock -> &sb->s_type->i_mutex_key):
-
Dependency #0 (&sb->s_type->i_mutex_key -> gadget_subsys.su_mutex):
- When userspace unlinks a symlink pointing to a UVC streaming header/format (e.g.,
unlink under /sys/kernel/config/usb_gadget/g1/functions/uvc.usb0/streaming/class/...), VFS enters vfs_unlink().
vfs_unlink() acquires inode_lock(target) on the symlink inode (fs/namei.c:5653).
- Like the root directory inode, symlink inodes created in configfs have
sd->s_depth == -1 and therefore retain the default lockdep class &sb->s_type->i_mutex_key.
vfs_unlink() then invokes configfs_unlink(), which calls the item's drop_link callback: uvcg_streaming_class_drop_link().
- Per configfs documentation (
Documentation/filesystems/configfs.rst), hierarchy traversal (src->ci_parent->ci_parent) requires taking the subsystem mutex. At line 3502, uvcg_streaming_class_drop_link() acquires mutex_lock(su_mutex), which is gadget_subsys.su_mutex.
- Established lock order:
&sb->s_type->i_mutex_key $\rightarrow$ gadget_subsys.su_mutex.
3. Invariant Violation and Root Cause
The issue is twofold:
- Unnecessary nesting in
f_tcm.c:
In commit 9dbf74f4022f ("usb: gadget: f_tcm: fix deadlock in usbg_make_tpg()"), dep_lock was removed from around configfs_depend_item_unlocked() to prevent a circular dependency with su_mutex. However, configfs_depend_item_unlocked() was left inside mutex_lock(&tpg_instances_lock). Because tpg_instances_lock is taken under gadget_subsys.su_mutex in tcm_alloc_inst(), holding it while calling configfs_depend_item_unlocked() reproduces the exact same lock ordering issue (su_mutex $\rightarrow$ tpg_instances_lock $\rightarrow$ configfs root inode_lock).
- Lockdep class aliasing in configfs:
In configfs, VFS
inode_lock is outer to subsys->su_mutex for filesystem mutations (mkdir, unlink, rmdir). configfs_depend_item_unlocked() acquires inode_lock on the mount root directory (root->cg_item.ci_dentry), whereas vfs_unlink() holds inode_lock on a symlink inode. Because neither object is assigned a specialized lockdep subkey in fs/configfs/inode.c, lockdep treats both distinct inodes as the identical lock class &sb->s_type->i_mutex_key, generating a false circular locking dependency splat.
Sample crash report:
======================================================
WARNING: possible circular locking dependency detected
syzkaller #0 Tainted: G L
------------------------------------------------------
syz.1.1849/13608 is trying to acquire lock:
ffffffff901ad880 (gadget_subsys.su_mutex){+.+.}-{4:4}, at: uvcg_streaming_class_drop_link+0x70/0x540 drivers/usb/gadget/function/uvc_configfs.c:3502
but task is already holding lock:
ffff88803d10a750 (&sb->s_type->i_mutex_key#24){++++}-{4:4}, at: inode_lock include/linux/fs.h:1027 [inline]
ffff88803d10a750 (&sb->s_type->i_mutex_key#24){++++}-{4:4}, at: vfs_unlink+0xd4/0x720 fs/namei.c:5653
which lock already depends on the new lock.
the existing dependency chain (in reverse order) is:
-> #3 (&sb->s_type->i_mutex_key#24){++++}-{4:4}:
down_write+0x35/0x50 kernel/locking/rwsem.c:1631
inode_lock include/linux/fs.h:1027 [inline]
configfs_depend_item_unlocked+0x153/0x420 fs/configfs/dir.c:1268
usbg_make_tpg+0x1e0/0x550 drivers/usb/gadget/function/f_tcm.c:1692
target_fabric_make_tpg+0xa8/0x6d0 drivers/target/target_core_fabric_configfs.c:947
configfs_mkdir+0x4f6/0x9e0 fs/configfs/dir.c:1369
vfs_mkdir+0x417/0x670 fs/namei.c:5428
filename_mkdirat+0x288/0x560 fs/namei.c:5461
__do_sys_mkdirat fs/namei.c:5482 [inline]
__se_sys_mkdirat+0x35/0x150 fs/namei.c:5479
do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
do_syscall_64+0x16a/0x530 arch/x86/entry/syscall_64.c:89
entry_SYSCALL_64_after_hwframe+0x77/0x7f
-> #2 (tpg_instances_lock){+.+.}-{4:4}:
__mutex_lock_common kernel/locking/rtmutex_api.c:561 [inline]
mutex_lock_nested+0x55/0x1d0 kernel/locking/rtmutex_api.c:580
tcm_alloc_inst+0x65/0x1a0 drivers/usb/gadget/function/f_tcm.c:2687
try_get_usb_function_instance drivers/usb/gadget/functions.c:28 [inline]
usb_get_function_instance+0xe3/0x2f0 drivers/usb/gadget/functions.c:44
function_make+0x127/0x360 drivers/usb/gadget/configfs.c:626
configfs_mkdir+0x4f6/0x9e0 fs/configfs/dir.c:1369
vfs_mkdir+0x417/0x670 fs/namei.c:5428
filename_mkdirat+0x288/0x560 fs/namei.c:5461
__do_sys_mkdirat fs/namei.c:5482 [inline]
__se_sys_mkdirat+0x35/0x150 fs/namei.c:5479
do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
do_syscall_64+0x16a/0x530 arch/x86/entry/syscall_64.c:89
entry_SYSCALL_64_after_hwframe+0x77/0x7f
-> #1 (func_lock){+.+.}-{4:4}:
__mutex_lock_common kernel/locking/rtmutex_api.c:561 [inline]
mutex_lock_nested+0x55/0x1d0 kernel/locking/rtmutex_api.c:580
try_get_usb_function_instance drivers/usb/gadget/functions.c:18 [inline]
usb_get_function_instance+0x2e/0x2f0 drivers/usb/gadget/functions.c:44
function_make+0x127/0x360 drivers/usb/gadget/configfs.c:626
configfs_mkdir+0x4f6/0x9e0 fs/configfs/dir.c:1369
vfs_mkdir+0x417/0x670 fs/namei.c:5428
filename_mkdirat+0x288/0x560 fs/namei.c:5461
__do_sys_mkdirat fs/namei.c:5482 [inline]
__se_sys_mkdirat+0x35/0x150 fs/namei.c:5479
do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
do_syscall_64+0x16a/0x530 arch/x86/entry/syscall_64.c:89
entry_SYSCALL_64_after_hwframe+0x77/0x7f
-> #0 (gadget_subsys.su_mutex){+.+.}-{4:4}:
check_prev_add kernel/locking/lockdep.c:3209 [inline]
check_prevs_add kernel/locking/lockdep.c:3328 [inline]
validate_chain kernel/locking/lockdep.c:3952 [inline]
__lock_acquire+0x164c/0x2de0 kernel/locking/lockdep.c:5288
lock_acquire+0x115/0x350 kernel/locking/lockdep.c:5942
__mutex_lock_common kernel/locking/rtmutex_api.c:561 [inline]
mutex_lock_nested+0x55/0x1d0 kernel/locking/rtmutex_api.c:580
uvcg_streaming_class_drop_link+0x70/0x540 drivers/usb/gadget/function/uvc_configfs.c:3502
configfs_unlink+0x44f/0x6b0 fs/configfs/symlink.c:258
vfs_unlink+0x279/0x720 fs/namei.c:5667
filename_unlinkat+0x297/0x5c0 fs/namei.c:5737
__do_sys_unlink fs/namei.c:5772 [inline]
__se_sys_unlink+0x2e/0x140 fs/namei.c:5769
do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
do_syscall_64+0x16a/0x530 arch/x86/entry/syscall_64.c:89
entry_SYSCALL_64_after_hwframe+0x77/0x7f
other info that might help us debug this:
Chain exists of:
gadget_subsys.su_mutex --> tpg_instances_lock --> &sb->s_type->i_mutex_key#24
Possible unsafe locking scenario:
CPU0 CPU1
---- ----
lock(&sb->s_type->i_mutex_key#24);
lock(tpg_instances_lock);
lock(&sb->s_type->i_mutex_key#24);
lock(gadget_subsys.su_mutex);
*** DEADLOCK ***
locks held by syz.1.1849/13608: 3, on CPU#1:
#0: ffff88801e7de528 (sb_writers#13){.+.+}-{0:0}, at: mnt_want_write+0x41/0x90 fs/namespace.c:531
#1: ffff88803d10f300 (&default_group_class[depth - 1]#8/1){+.+.}-{4:4}, at: inode_lock_nested include/linux/fs.h:1072 [inline]
#1: ffff88803d10f300 (&default_group_class[depth - 1]#8/1){+.+.}-{4:4}, at: __start_dirop fs/namei.c:2924 [inline]
#1: ffff88803d10f300 (&default_group_class[depth - 1]#8/1){+.+.}-{4:4}, at: start_dirop fs/namei.c:2948 [inline]
#1: ffff88803d10f300 (&default_group_class[depth - 1]#8/1){+.+.}-{4:4}, at: filename_unlinkat+0x198/0x5c0 fs/namei.c:5718
#2: ffff88803d10a750 (&sb->s_type->i_mutex_key#24){++++}-{4:4}, at: inode_lock include/linux/fs.h:1027 [inline]
#2: ffff88803d10a750 (&sb->s_type->i_mutex_key#24){++++}-{4:4}, at: vfs_unlink+0xd4/0x720 fs/namei.c:5653
stack backtrace:
CPU: 1 UID: 0 PID: 13608 Comm: syz.1.1849 Tainted: G L syzkaller #0 PREEMPT_{RT,(full)}
Tainted: [L]=SOFTLOCKUP
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 11/06/2026
Call Trace:
<TASK>
dump_stack_lvl+0xe8/0x150 lib/dump_stack.c:120
print_circular_bug+0x2e2/0x300 kernel/locking/lockdep.c:2087
check_noncircular+0x12f/0x150 kernel/locking/lockdep.c:2219
check_prev_add kernel/locking/lockdep.c:3209 [inline]
check_prevs_add kernel/locking/lockdep.c:3328 [inline]
validate_chain kernel/locking/lockdep.c:3952 [inline]
__lock_acquire+0x164c/0x2de0 kernel/locking/lockdep.c:5288
lock_acquire+0x115/0x350 kernel/locking/lockdep.c:5942
__mutex_lock_common kernel/locking/rtmutex_api.c:561 [inline]
mutex_lock_nested+0x55/0x1d0 kernel/locking/rtmutex_api.c:580
uvcg_streaming_class_drop_link+0x70/0x540 drivers/usb/gadget/function/uvc_configfs.c:3502
configfs_unlink+0x44f/0x6b0 fs/configfs/symlink.c:258
vfs_unlink+0x279/0x720 fs/namei.c:5667
filename_unlinkat+0x297/0x5c0 fs/namei.c:5737
__do_sys_unlink fs/namei.c:5772 [inline]
__se_sys_unlink+0x2e/0x140 fs/namei.c:5769
do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
do_syscall_64+0x16a/0x530 arch/x86/entry/syscall_64.c:89
entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7ff95cdd94d9
Code: ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 44 00 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 e8 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007ff95b01e028 EFLAGS: 00000246 ORIG_RAX: 0000000000000057
RAX: ffffffffffffffda RBX: 00007ff95d055fa0 RCX: 00007ff95cdd94d9
RDX: 0000000000000000 RSI: 0000000000000000 RDI: 0000200000000500
RBP: 00007ff95ce6804e R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000
R13: 00007ff95d056038 R14: 00007ff95d055fa0 R15: 00007ffec076c5f8
</TASK>
* Struck through repros no longer work on HEAD.