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: 2h56m, last: 2h53m
✨ 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
Based on kernel commit 23c62304dde2:

Root Cause Analysis

1. Relevant Files, Structs, and Functions


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):

  1. 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.
  2. Dependency #3 (tpg_instances_lock -> &sb->s_type->i_mutex_key):

    • When userspace creates a target portal group under target configfs (e.g., mkdir /sys/kernel/config/target/core/tcm_usb_gadget/.../tpgt_1), usbg_make_tpg() is executed.
    • usbg_make_tpg() acquires mutex_lock(&tpg_instances_lock) at line 1669.
    • While still holding tpg_instances_lock, it calls configfs_depend_item_unlocked(wwn->wwn_group.cg_subsys, &opts->func_inst.group.cg_item) at line 1692.
    • Because the target item belongs to gadget_subsys while the caller belongs to target_subsys (target_subsys != caller_subsys), configfs_depend_item_unlocked() acquires the root directory inode lock of configfs at line 1268:
      inode_lock(d_inode(root->cg_item.ci_dentry));
      
    • In fs/configfs/inode.c, only default groups receive custom lockdep classes (default_group_class[depth - 1]). The configfs root directory inode retains the default superblock lock class &sb->s_type->i_mutex_key.
    • Established lock order: tpg_instances_lock $\rightarrow$ &sb->s_type->i_mutex_key.
  3. 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:

  1. 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).
  2. 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.
▶ ▼ ✨ AI Jobs (1)
ID Workflow Result Correct Bug Created Started Finished Revision
253e78f8 assessment-security DenialOfService: ❌ Exploitable: ❌ FilesystemTrigger: ❌ FuzzingArtifact: ❌ MemoryCorruption: ❌ NetworkNeedsSetup: ❌ NetworkTrigger: ❌ PeripheralTrigger: ❌ RemoteTrigger: ❌ Unprivileged: ❌ UserNamespace: ❌ VMGuestTrigger: ❌ VMHostTrigger: ❌ ❓ possible deadlock in uvcg_streaming_class_drop_link 2026/11/15 16:21 2026/11/15 16:22 2026/11/15 17:04 819ebe34

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>

Crashes (3):
Time Kernel Commit Syzkaller Config Log Report Syz repro C repro VM info Assets (help?) Manager Title
2026/11/15 16:23 upstream 23c62304dde2 819ebe34 .config console log report info [disk image] [vmlinux] [kernel image] ci-upstream-kasan-gce-smack-root possible deadlock in uvcg_streaming_class_drop_link
2026/11/15 16:23 upstream 23c62304dde2 819ebe34 .config console log report info [disk image] [vmlinux] [kernel image] ci-upstream-kasan-gce-smack-root possible deadlock in uvcg_streaming_class_drop_link
2026/11/15 16:21 upstream 23c62304dde2 819ebe34 .config console log report info [disk image] [vmlinux] [kernel image] ci-upstream-kasan-gce-smack-root possible deadlock in uvcg_streaming_class_drop_link
* Struck through repros no longer work on HEAD.