| 0/0 |
2026/07/31 03:13 |
flow |
assessment-security |
37m
Results: map[DenialOfService:false Explanation:Based on the analysis of the provided lockdep report and the kernel source code,
here is the assessment of the bug:
**Analysis:**
The bug report indicates a "possible circular locking dependency detected" by
lockdep. The reported lock chain involves three locks:
1. `gadget_subsys.su_mutex`
2. `opts->dep_lock`
3. `inode_lock` (specifically the lock class `&sb->s_type->i_mutex_key#23` which
corresponds to VFS directory inode locks in configfs).
The dependency chain is formed by the following operations:
- **`inode_lock` -> `gadget_subsys.su_mutex`**: When creating a symlink via
`configfs_symlink`, the VFS locks the `inode_lock` of the symlink's parent
directory (e.g., `.../ext.0`). The configfs callback `uvcg_extension_allow_link`
then acquires `gadget_subsys.su_mutex`.
- **`gadget_subsys.su_mutex` -> `opts->dep_lock`**: When creating a new gadget
function via `configfs_mkdir`, configfs holds `gadget_subsys.su_mutex` and calls
`function_make` -> `tcm_set_name`, which acquires `opts->dep_lock`.
- **`opts->dep_lock` -> `inode_lock`**: When creating a target portal group
(tpgt) via `configfs_mkdir`, the target subsystem holds `opts->dep_lock` (in
`usbg_make_tpg`) and calls `configfs_depend_item_unlocked`, which acquires the
`inode_lock` of the gadget subsystem's root directory (e.g.,
`/config/usb_gadget`).
Lockdep flags this as a circular dependency because it tracks lock classes, not
individual lock instances. It sees `inode_lock` being acquired while holding
`opts->dep_lock`, and `gadget_subsys.su_mutex` being acquired while holding
`inode_lock`.
However, this is a **lockdep false positive**. The `inode_lock` instances
involved in the cycle belong to completely different directories (the symlink's
parent directory deep in the hierarchy vs. the subsystem's root directory).
Because they are different inodes, threads acquiring them concurrently will not
block each other, and a real deadlock will not occur. Furthermore, configfs
employs internal synchronization mechanisms (like `configfs_dirent_lock` and
state flags such as `CONFIGFS_USET_DROPPING`) that strictly serialize structural
modifications (like `rmdir` and `mkdir`), preventing any theoretical deadlocks
that might arise from reverse lock ordering.
In production kernels, lockdep is disabled, meaning this warning will not be
printed and no system halt or panic will occur. Since there is no actual
deadlock, it does not cause a Denial of Service.
Additionally, `configfs` does not have the `FS_USERNS_MOUNT` flag, meaning it
cannot be mounted inside an unprivileged user namespace. The default mount at
`/sys/kernel/config` is owned by root, and unprivileged users do not have the
write permissions required to create directories (`mkdir`) or symlinks within
it. Therefore, this code path is completely inaccessible to unprivileged users.
**Conclusion:**
- **Exploitable:** No.
- **Denial of Service:** No (it is a lockdep false positive; no actual deadlock
occurs).
- **Accessible From Unprivileged Processes:** No (requires root privileges to
modify configfs).
- **Accessible From User Namespaces:** No (configfs cannot be mounted in user
namespaces). Exploitable:false FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:false UserNamespace:false VMGuestTrigger:false VMHostTrigger:false]
|
| 1/1 |
2026/07/31 03:13 |
action |
syz-repro-to-c-repro |
0m
Results: map[SimplifiedCRepro:// autogenerated by syzkaller (https://github.com/google/syzkaller)
#define _GNU_SOURCE
#include <endian.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mount.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <unistd.h>
#ifndef __NR_mkdirat
#define __NR_mkdirat 34
#endif
#ifndef __NR_mmap
#define __NR_mmap 222
#endif
#ifndef __NR_mount
#define __NR_mount 40
#endif
#ifndef __NR_symlinkat
#define __NR_symlinkat 36
#endif
int main(void)
{
syscall(__NR_mmap, /*addr=*/0x1ffff000ul, /*len=*/0x1000, /*prot=*/0ul, /*flags=MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE*/0x32ul, /*fd=*/(intptr_t)-1, /*offset=*/0ul);
syscall(__NR_mmap, /*addr=*/0x20000000ul, /*len=*/0x1000000, /*prot=PROT_WRITE|PROT_READ|PROT_EXEC*/7ul, /*flags=MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE*/0x32ul, /*fd=*/(intptr_t)-1, /*offset=*/0ul);
syscall(__NR_mmap, /*addr=*/0x21000000ul, /*len=*/0x1000, /*prot=*/0ul, /*flags=MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE*/0x32ul, /*fd=*/(intptr_t)-1, /*offset=*/0ul);
const char* reason;
(void)reason;
if (write(1, "executing program\n", sizeof("executing program\n") - 1)) {}
// mkdirat arguments: [
// fd: fd_dir (resource)
// path: ptr[in, buffer] {
// buffer: {2e 2f 6d 79 63 6f 6e 66 69 67 00} (length 0xb)
// }
// mode: open_mode = 0x1ff (8 bytes)
// ]
memcpy((void*)0x20000000, "./myconfig\000", 11);
syscall(__NR_mkdirat, /*fd=*/0xffffff9c, /*path=*/0x20000000ul, /*mode=S_IXOTH|S_IWOTH|S_IROTH|S_IXGRP|S_IWGRP|S_IRGRP|S_IXUSR|S_IWUSR|0x100*/0x1fful);
// mount arguments: [
// src: nil
// dst: ptr[in, buffer] {
// buffer: {2e 2f 6d 79 63 6f 6e 66 69 67 00} (length 0xb)
// }
// type: ptr[in, buffer] {
// buffer: {63 6f 6e 66 69 67 66 73 00} (length 0x9)
// }
// flags: mount_flags = 0x0 (8 bytes)
// data: nil
// ]
memcpy((void*)0x20000100, "./myconfig\000", 11);
memcpy((void*)0x20000200, "configfs\000", 9);
syscall(__NR_mount, /*src=*/0ul, /*dst=*/0x20000100ul, /*type=*/0x20000200ul, /*flags=*/0ul, /*data=*/0ul);
// mkdirat arguments: [
// fd: fd_dir (resource)
// path: ptr[in, buffer] {
// buffer: {2e 2f 6d 79 63 6f 6e 66 69 67 2f 75 73 62 5f 67 61 64 67 65 74 2f 67 31 2f 73 74 72 69 6e 67 73 2f 30 78 34 30 39 00} (length 0x27)
// }
// mode: open_mode = 0x1ff (8 bytes)
// ]
memcpy((void*)0x20000400, "./myconfig/usb_gadget/g1/strings/0x409\000", 39);
syscall(__NR_mkdirat, /*fd=*/0xffffff9c, /*path=*/0x20000400ul, /*mode=S_IXOTH|S_IWOTH|S_IROTH|S_IXGRP|S_IWGRP|S_IRGRP|S_IXUSR|S_IWUSR|0x100*/0x1fful);
// mkdirat arguments: [
// fd: fd_dir (resource)
// path: ptr[in, buffer] {
// buffer: {2e 2f 6d 79 63 6f 6e 66 69 67 2f 75 73 62 5f 67 61 64 67 65 74 2f 67 31 2f 73 74 72 69 6e 67 73 2f 30 78 34 30 39 2f 73 31 00} (length 0x2a)
// }
// mode: open_mode = 0x1ff (8 bytes)
// ]
memcpy((void*)0x20000500, "./myconfig/usb_gadget/g1/strings/0x409/s1\000", 42);
syscall(__NR_mkdirat, /*fd=*/0xffffff9c, /*path=*/0x20000500ul, /*mode=S_IXOTH|S_IWOTH|S_IROTH|S_IXGRP|S_IWGRP|S_IRGRP|S_IXUSR|S_IWUSR|0x100*/0x1fful);
// mkdirat arguments: [
// fd: fd_dir (resource)
// path: ptr[in, buffer] {
// buffer: {2e 2f 6d 79 63 6f 6e 66 69 67 2f 75 73 62 5f 67 61 64 67 65 74 2f 67 31 2f 66 75 6e 63 74 69 6f 6e 73 2f 75 76 63 2e 75 73 62 30 00} (length 0x2c)
// }
// mode: open_mode = 0x1ff (8 bytes)
// ]
memcpy((void*)0x20000600, "./myconfig/usb_gadget/g1/functions/uvc.usb0\000", 44);
syscall(__NR_mkdirat, /*fd=*/0xffffff9c, /*path=*/0x20000600ul, /*mode=S_IXOTH|S_IWOTH|S_IROTH|S_IXGRP|S_IWGRP|S_IRGRP|S_IXUSR|S_IWUSR|0x100*/0x1fful);
// mkdirat arguments: [
// fd: fd_dir (resource)
// path: ptr[in, buffer] {
// buffer: {2e 2f 6d 79 63 6f 6e 66 69 67 2f 75 73 62 5f 67 61 64 67 65 74 2f 67 31 2f 66 75 6e 63 74 69 6f 6e 73 2f 75 76 63 2e 75 73 62 30 2f 63 6f 6e 74 72 6f 6c 2f 65 78 74 65 6e 73 69 6f 6e 73 2f 65 78 74 2e 30 00} (length 0x45)
// }
// mode: open_mode = 0x1ff (8 bytes)
// ]
memcpy((void*)0x20000700, "./myconfig/usb_gadget/g1/functions/uvc.usb0/control/extensions/ext.0\000", 69);
syscall(__NR_mkdirat, /*fd=*/0xffffff9c, /*path=*/0x20000700ul, /*mode=S_IXOTH|S_IWOTH|S_IROTH|S_IXGRP|S_IWGRP|S_IRGRP|S_IXUSR|S_IWUSR|0x100*/0x1fful);
// symlinkat arguments: [
// old: ptr[in, buffer] {
// buffer: {2e 2f 6d 79 63 6f 6e 66 69 67 2f 75 73 62 5f 67 61 64 67 65 74 2f 67 31 2f 73 74 72 69 6e 67 73 2f 30 78 34 30 39 2f 73 31 00} (length 0x2a)
// }
// newfd: fd_dir (resource)
// new: ptr[in, buffer] {
// buffer: {2e 2f 6d 79 63 6f 6e 66 69 67 2f 75 73 62 5f 67 61 64 67 65 74 2f 67 31 2f 66 75 6e 63 74 69 6f 6e 73 2f 75 76 63 2e 75 73 62 30 2f 63 6f 6e 74 72 6f 6c 2f 65 78 74 65 6e 73 69 6f 6e 73 2f 65 78 74 2e 30 2f 73 74 72 00} (length 0x49)
// }
// ]
memcpy((void*)0x20000800, "./myconfig/usb_gadget/g1/strings/0x409/s1\000", 42);
memcpy((void*)0x20000900, "./myconfig/usb_gadget/g1/functions/uvc.usb0/control/extensions/ext.0/str\000", 73);
syscall(__NR_symlinkat, /*old=*/0x20000800ul, /*newfd=*/0xffffff9c, /*new=*/0x20000900ul);
// mkdirat arguments: [
// fd: fd_dir (resource)
// path: ptr[in, buffer] {
// buffer: {2e 2f 63 6f 6e 66 69 67 00} (length 0x9)
// }
// mode: open_mode = 0x1ff (8 bytes)
// ]
memcpy((void*)0x20000000, "./config\000", 9);
syscall(__NR_mkdirat, /*fd=*/0xffffff9c, /*path=*/0x20000000ul, /*mode=S_IXOTH|S_IWOTH|S_IROTH|S_IXGRP|S_IWGRP|S_IRGRP|S_IXUSR|S_IWUSR|0x100*/0x1fful);
// mount arguments: [
// src: nil
// dst: ptr[in, buffer] {
// buffer: {2e 2f 63 6f 6e 66 69 67 00} (length 0x9)
// }
// type: ptr[in, buffer] {
// buffer: {63 6f 6e 66 69 67 66 73 00} (length 0x9)
// }
// flags: mount_flags = 0x0 (8 bytes)
// data: nil
// ]
memcpy((void*)0x20000000, "./config\000", 9);
memcpy((void*)0x20000100, "configfs\000", 9);
syscall(__NR_mount, /*src=*/0ul, /*dst=*/0x20000000ul, /*type=*/0x20000100ul, /*flags=*/0ul, /*data=*/0ul);
// mkdirat arguments: [
// fd: fd_dir (resource)
// path: ptr[in, buffer] {
// buffer: {2e 2f 63 6f 6e 66 69 67 2f 75 73 62 5f 67 61 64 67 65 74 2f 67 31 00} (length 0x17)
// }
// mode: open_mode = 0x1ff (8 bytes)
// ]
memcpy((void*)0x20000300, "./config/usb_gadget/g1\000", 23);
syscall(__NR_mkdirat, /*fd=*/0xffffff9c, /*path=*/0x20000300ul, /*mode=S_IXOTH|S_IWOTH|S_IROTH|S_IXGRP|S_IWGRP|S_IRGRP|S_IXUSR|S_IWUSR|0x100*/0x1fful);
// mkdirat arguments: [
// fd: fd_dir (resource)
// path: ptr[in, buffer] {
// buffer: {2e 2f 63 6f 6e 66 69 67 2f 75 73 62 5f 67 61 64 67 65 74 2f 67 31 2f 66 75 6e 63 74 69 6f 6e 73 2f 74 63 6d 2e 75 73 62 30 00} (length 0x2a)
// }
// mode: open_mode = 0x1ff (8 bytes)
// ]
memcpy((void*)0x20000500, "./config/usb_gadget/g1/functions/tcm.usb0\000", 42);
syscall(__NR_mkdirat, /*fd=*/0xffffff9c, /*path=*/0x20000500ul, /*mode=S_IXOTH|S_IWOTH|S_IROTH|S_IXGRP|S_IWGRP|S_IRGRP|S_IXUSR|S_IWUSR|0x100*/0x1fful);
// mkdirat arguments: [
// fd: fd_dir (resource)
// path: ptr[in, buffer] {
// buffer: {2e 2f 63 6f 6e 66 69 67 2f 74 61 72 67 65 74 2f 75 73 62 5f 67 61 64 67 65 74 00} (length 0x1b)
// }
// mode: open_mode = 0x1ff (8 bytes)
// ]
memcpy((void*)0x20000e00, "./config/target/usb_gadget\000", 27);
syscall(__NR_mkdirat, /*fd=*/0xffffff9c, /*path=*/0x20000e00ul, /*mode=S_IXOTH|S_IWOTH|S_IROTH|S_IXGRP|S_IWGRP|S_IRGRP|S_IXUSR|S_IWUSR|0x100*/0x1fful);
// mkdirat arguments: [
// fd: fd_dir (resource)
// path: ptr[in, buffer] {
// buffer: {2e 2f 63 6f 6e 66 69 67 2f 74 61 72 67 65 74 2f 75 73 62 5f 67 61 64 67 65 74 2f 6e 61 61 2e 31 32 33 34 35 36 37 38 39 30 31 32 33 34 35 36 00} (length 0x30)
// }
// mode: open_mode = 0x1ff (8 bytes)
// ]
memcpy((void*)0x20000f00, "./config/target/usb_gadget/naa.1234567890123456\000", 48);
syscall(__NR_mkdirat, /*fd=*/0xffffff9c, /*path=*/0x20000f00ul, /*mode=S_IXOTH|S_IWOTH|S_IROTH|S_IXGRP|S_IWGRP|S_IRGRP|S_IXUSR|S_IWUSR|0x100*/0x1fful);
// mkdirat arguments: [
// fd: fd_dir (resource)
// path: ptr[in, buffer] {
// buffer: {2e 2f 63 6f 6e 66 69 67 2f 74 61 72 67 65 74 2f 75 73 62 5f 67 61 64 67 65 74 2f 6e 61 61 2e 31 32 33 34 35 36 37 38 39 30 31 32 33 34 35 36 2f 74 70 67 74 5f 31 00} (length 0x37)
// }
// mode: open_mode = 0x1ff (8 bytes)
// ]
memcpy((void*)0x20001000, "./config/target/usb_gadget/naa.1234567890123456/tpgt_1\000", 55);
syscall(__NR_mkdirat, /*fd=*/0xffffff9c, /*path=*/0x20001000ul, /*mode=S_IXOTH|S_IWOTH|S_IROTH|S_IXGRP|S_IWGRP|S_IRGRP|S_IXUSR|S_IWUSR|0x100*/0x1fful);
return 0;
}
]
|
| 2/1 |
2026/07/31 03:13 |
action |
kernel-checkouter |
0m
Results: map[KernelSrc:/app/workdir/cache/src/bc72b0baf0345f0b613f258c98b80ee7d9215ac1]
|
| 3/1 |
2026/07/31 03:13 |
action |
kernel-builder |
0m
Results: map[KernelObj:/app/workdir/cache/build/8dbafce49c4e73348834a7f02ab3e9aa0ec73fc5]
|
| 4/1 |
2026/07/31 03:13 |
action |
codesearch-prepare |
0m
Results: map[Index:codesearch-index]
|
| 5/1 |
2026/07/31 03:13 |
agent |
expert |
37m
Model:
Results: map[DenialOfService:false Exploitable:false FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:false UserNamespace:false VMGuestTrigger:false VMHostTrigger:false]
Instruction: You are an experienced Linux kernel security engineer. Your task is to analyze given kernel bug report
and determine its security impact based on the following dimensions.
Use the provided tools to examine the source code, check for capability checks (e.g., capable(), ns_capable()),
and understand the nature of the bug. Analyze the given kernel build and configuration.
You can check the kernel config by grepping ".config" file; you can check kernel cmdline by grepping
".config" file for "CONFIG_CMDLINE=". Assume sysctl parameters have default values.
But analyze for the corresponding production build w/o debugging tools enabled (like KASAN, KMSAN, UBSAN).
Try different strategies when analyzing the bug:
- think of ways in which the vulnerable code is unreachable
- or the other way around: try to come up with different ideas of how an unprivileged user can reach the bug
If still unsure err on the side of the bug being non-exploitable/not-accessible.
In the final reply, provide a reasoning for your assessment.
Analysis dimensions:
* Exploitable:
Determine if the bug can result in memory corruption, elevated privileges, or an information leak.
Memory safety issues are almost always exploitable (KASAN or UBSAN reports for use-after-free, out-of-bounds;
refcounting issues, corrupted lists, etc). When kernel is crashing on a completely wild pointer access
(e.g. user-space address, or non-canonical address, but not on NULL or address corresponding to KASAN shadow
for NULL address), including both data accesses and control transfers, that also usually implies possibility
of exploitation. Such reports usually say "unable to handle kernel paging request".
Uses of uninitialized values detected by KMSAN may be exploitable b/c attacker frequently can affect uninit
values with spraying techniques. However, for these exploitability depends on how exactly the uninit value
is used in the code, and what it affects.
Information leaks are exploitable on their own and should be classified as such. A bug that copies kernel
memory contents to userspace (e.g. an out-of-bounds read whose result is returned to the caller, or
uninitialized stack/heap bytes written to a user buffer) is exploitable: it can reveal kernel pointer
values and defeat KASLR, expose sensitive data such as cryptographic keys or other processes' memory, and
serves as a necessary building block in most modern kernel privilege-escalation exploit chains. Do not classify
an information leak as non-exploitable solely because it does not directly cause a memory write or control-flow
hijack; the leak itself is the exploit primitive.
Think of what happens after the bug is triggered. Some bugs cause kernel panic and halt execution,
they are harder to exploit. For example, BUG reports halts the kernel. However, WARNING reports don't halt
execution in production builds. Debug bug detection tools (like KASAN, KMSAN, KCSAN, UBSAN) are also not enabled
in production builds, so attacker can freely exploit these bugs w/o being detected by these tools.
If you see an integer overflow, think how the overflowed value used later (if it's used as allocation size,
or an array index). If you see an out-of-bounds read, think if it's followed by an out-of-bounds write as well.
Some KCSAN data-races may be exploitable by skilled attackers as well. Think what data structures got corrupted
as the result of data races and how. However, note that kernel has lots of "benign" data races that don't lead
to any runtime misbehavior at all.
* Denial Of Service:
Determine if the bug can result in denial-of-service. Most bugs can, since they cause system crash,
hangs, deadlocks, or resource leaks. This is mostly applicable to WARNING bugs that won't cause system crash
in production. For these think what will be consequences of the violation of the kernel assumptions flagged
by the WARNING. In some cases the unexpected condition is also properly handled by the normal control flow
(e.g. with "if (WARN_ON(...))"), these won't cause denial-of-service. If the condition is not handled,
then it may or may not cause denial-of-service.
* Accessible From Unprivileged Processes:
Determine if the bug can be reached from a typical (non-root) user process that does NOT have any special capabilities
(like CAP_SYS_ADMIN, CAP_NET_ADMIN, CAP_NET_RAW, CAP_PERFMON) or access to device nodes restricted to root.
Assume that unprivileged_bpf_disabled=1, that is eBPF loading is not accessible. However, cBPF (classical BPF)
is still accessible to non-root processes.
Assume that user namespaces are not accessible, that is, the process cannot get the mentioned capabilities even
within a new user namespace (checked by ns_capable() function in the kernel sources).
* Accessible From User Namespaces:
Determine if the bug can be reached within a user-namespace where the process has all capabilities
(including CAP_SYS_ADMIN, CAP_NET_ADMIN, CAP_NET_RAW, CAP_PERFMON). Such capabilities are checked with ns_capable()
function in the kernel sources.
* VM Guest Trigger:
Determine if the bug can be triggered from the context of a typical KVM guest (e.g., set up by a QEMU VMM).
Consider accesses to standard Linux host paravirtualized features (virtio-blk, virtio-net, etc.),
and handling of VM exits in the KVM code.
* VM Host Trigger in The Confidential Computing Context:
Determine if the bug can be triggered in a confidential computing guest kernel from the context of a KVM host.
Consider access to standard Linux guest paravirtualized features (virtio-blk, virtio-net, etc.).
* Ethernet Network Trigger:
Determine if the bug can be triggered by processing ingress network Ethernet traffic, either directly (network stack)
or via drivers exposed to network data.
* Other Remote Trigger:
Determine if the bug can be triggered by processing remote traffic other than Ethernet (Wifi, Bluetooth, NFC, etc).
* Peripheral Trigger:
Determine if the bug can be triggered via an untrusted peripheral device that can be physically plugged
into a system, such as a USB device or a niche hardware driver handling external hardware inputs.
This is particularly important for mobile and desktop environments where users can plug in unknown devices.
* Malicious Filesystem Trigger:
Determine if the bug can be triggered by the kernel mounting and parsing a malicious filesystem image.
This is highly critical for Desktop and Mobile environments where external media or downloaded images
might be auto-mounted.
Don't make assumptions about the kernel source code (it may be different from what you assume it is).
Extensively use the provided code access tools (codesearch-*, git-*, grepper, etc)
to examine the actual source code, and confirm any assumptions.
Prefer calling several tools at the same time to save round-trips.
Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.
Prompt:
The kernel bug report is:
======================================================
WARNING: possible circular locking dependency detected
syzkaller #0 Not tainted
------------------------------------------------------
syz.0.18/4919 is trying to acquire lock:
ffff80008970de48 (gadget_subsys.su_mutex){+.+.}-{4:4}, at: uvcg_extension_allow_link+0x70/0x1f8 drivers/usb/gadget/function/uvc_configfs.c:1195
but task is already holding lock:
ffff0000f06efa40 (&sb->s_type->i_mutex_key#23){+.+.}-{4:4}, at: inode_lock include/linux/fs.h:1024 [inline]
ffff0000f06efa40 (&sb->s_type->i_mutex_key#23){+.+.}-{4:4}, at: configfs_symlink+0x394/0xde0 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#23){+.+.}-{4:4}:
down_write+0x50/0xc0 kernel/locking/rwsem.c:1631
inode_lock include/linux/fs.h:1024 [inline]
configfs_depend_item_unlocked+0x154/0x344 fs/configfs/dir.c:1259
usbg_make_tpg+0x230/0x4b4 drivers/usb/gadget/function/f_tcm.c:1686
target_fabric_make_tpg+0x98/0x518 drivers/target/target_core_fabric_configfs.c:939
configfs_mkdir+0x374/0x7d4 fs/configfs/dir.c:1360
vfs_mkdir+0x2dc/0x464 fs/namei.c:5276
filename_mkdirat+0x228/0x3f4 fs/namei.c:5309
__do_sys_mkdirat fs/namei.c:5330 [inline]
__se_sys_mkdirat fs/namei.c:5327 [inline]
__arm64_sys_mkdirat+0x94/0xb8 fs/namei.c:5327
__invoke_syscall arch/arm64/kernel/syscall.c:35 [inline]
invoke_syscall+0x98/0x244 arch/arm64/kernel/syscall.c:49
el0_svc_common+0xec/0x23c arch/arm64/kernel/syscall.c:121
do_el0_svc+0x4c/0x5c arch/arm64/kernel/syscall.c:140
el0_svc+0x64/0x260 arch/arm64/kernel/entry-common.c:736
el0t_64_sync_handler+0x48/0x148 arch/arm64/kernel/entry-common.c:755
el0t_64_sync+0x198/0x19c arch/arm64/kernel/entry.S:594
-> #1 (&opts->dep_lock){+.+.}-{4:4}:
__mutex_lock_common kernel/locking/mutex.c:646 [inline]
__mutex_lock+0x164/0xf14 kernel/locking/mutex.c:821
mutex_lock_nested+0x24/0x30 kernel/locking/mutex.c:874
tcm_set_name+0x34/0xd0 drivers/usb/gadget/function/f_tcm.c:2540
function_make+0x18c/0x300 drivers/usb/gadget/configfs.c:636
configfs_mkdir+0x374/0x7d4 fs/configfs/dir.c:1360
vfs_mkdir+0x2dc/0x464 fs/namei.c:5276
filename_mkdirat+0x228/0x3f4 fs/namei.c:5309
__do_sys_mkdirat fs/namei.c:5330 [inline]
__se_sys_mkdirat fs/namei.c:5327 [inline]
__arm64_sys_mkdirat+0x94/0xb8 fs/namei.c:5327
__invoke_syscall arch/arm64/kernel/syscall.c:35 [inline]
invoke_syscall+0x98/0x244 arch/arm64/kernel/syscall.c:49
el0_svc_common+0xec/0x23c arch/arm64/kernel/syscall.c:121
do_el0_svc+0x4c/0x5c arch/arm64/kernel/syscall.c:140
el0_svc+0x64/0x260 arch/arm64/kernel/entry-common.c:736
el0t_64_sync_handler+0x48/0x148 arch/arm64/kernel/entry-common.c:755
el0t_64_sync+0x198/0x19c arch/arm64/kernel/entry.S:594
-> #0 (gadget_subsys.su_mutex){+.+.}-{4:4}:
check_prev_add kernel/locking/lockdep.c:3165 [inline]
check_prevs_add kernel/locking/lockdep.c:3284 [inline]
validate_chain kernel/locking/lockdep.c:3908 [inline]
__lock_acquire+0x17c0/0x2ebc kernel/locking/lockdep.c:5237
lock_acquire+0x140/0x364 kernel/locking/lockdep.c:5868
__mutex_lock_common kernel/locking/mutex.c:646 [inline]
__mutex_lock+0x164/0xf14 kernel/locking/mutex.c:821
mutex_lock_nested+0x24/0x30 kernel/locking/mutex.c:874
uvcg_extension_allow_link+0x70/0x1f8 drivers/usb/gadget/function/uvc_configfs.c:1195
configfs_symlink+0x478/0xde0 fs/configfs/symlink.c:196
vfs_symlink+0x16c/0x290 fs/namei.c:5660
filename_symlinkat+0x18c/0x328 fs/namei.c:5685
__do_sys_symlinkat fs/namei.c:5705 [inline]
__se_sys_symlinkat fs/namei.c:5700 [inline]
__arm64_sys_symlinkat+0xa8/0xd4 fs/namei.c:5700
__invoke_syscall arch/arm64/kernel/syscall.c:35 [inline]
invoke_syscall+0x98/0x244 arch/arm64/kernel/syscall.c:49
el0_svc_common+0xec/0x23c arch/arm64/kernel/syscall.c:121
do_el0_svc+0x4c/0x5c arch/arm64/kernel/syscall.c:140
el0_svc+0x64/0x260 arch/arm64/kernel/entry-common.c:736
el0t_64_sync_handler+0x48/0x148 arch/arm64/kernel/entry-common.c:755
el0t_64_sync+0x198/0x19c arch/arm64/kernel/entry.S:594
other info that might help us debug this:
Chain exists of:
gadget_subsys.su_mutex --> &opts->dep_lock --> &sb->s_type->i_mutex_key#23
Possible unsafe locking scenario:
CPU0 CPU1
---- ----
lock(&sb->s_type->i_mutex_key#23);
lock(&opts->dep_lock);
lock(&sb->s_type->i_mutex_key#23);
lock(gadget_subsys.su_mutex);
*** DEADLOCK ***
2 locks held by syz.0.18/4919:
#0: ffff0000c38e0450 (sb_writers#11){.+.+}-{0:0}, at: mnt_want_write+0x44/0x9c fs/namespace.c:494
#1: ffff0000f06efa40 (&sb->s_type->i_mutex_key#23){+.+.}-{4:4}, at: inode_lock include/linux/fs.h:1024 [inline]
#1: ffff0000f06efa40 (&sb->s_type->i_mutex_key#23){+.+.}-{4:4}, at: configfs_symlink+0x394/0xde0 fs/configfs/symlink.c:186
stack backtrace:
CPU: 0 UID: 0 PID: 4919 Comm: syz.0.18 Not tainted syzkaller #0 PREEMPT
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 07/02/2026
Call trace:
show_stack+0x2c/0x3c arch/arm64/kernel/stacktrace.c:499 (C)
__dump_stack+0x30/0x40 lib/dump_stack.c:94
dump_stack_lvl+0xd8/0x12c lib/dump_stack.c:120
dump_stack+0x1c/0x28 lib/dump_stack.c:129
print_circular_bug+0x328/0x330 kernel/locking/lockdep.c:2043
check_noncircular+0x158/0x174 kernel/locking/lockdep.c:2175
check_prev_add kernel/locking/lockdep.c:3165 [inline]
check_prevs_add kernel/locking/lockdep.c:3284 [inline]
validate_chain kernel/locking/lockdep.c:3908 [inline]
__lock_acquire+0x17c0/0x2ebc kernel/locking/lockdep.c:5237
lock_acquire+0x140/0x364 kernel/locking/lockdep.c:5868
__mutex_lock_common kernel/locking/mutex.c:646 [inline]
__mutex_lock+0x164/0xf14 kernel/locking/mutex.c:821
mutex_lock_nested+0x24/0x30 kernel/locking/mutex.c:874
uvcg_extension_allow_link+0x70/0x1f8 drivers/usb/gadget/function/uvc_configfs.c:1195
configfs_symlink+0x478/0xde0 fs/configfs/symlink.c:196
vfs_symlink+0x16c/0x290 fs/namei.c:5660
filename_symlinkat+0x18c/0x328 fs/namei.c:5685
__do_sys_symlinkat fs/namei.c:5705 [inline]
__se_sys_symlinkat fs/namei.c:5700 [inline]
__arm64_sys_symlinkat+0xa8/0xd4 fs/namei.c:5700
__invoke_syscall arch/arm64/kernel/syscall.c:35 [inline]
invoke_syscall+0x98/0x244 arch/arm64/kernel/syscall.c:49
el0_svc_common+0xec/0x23c arch/arm64/kernel/syscall.c:121
do_el0_svc+0x4c/0x5c arch/arm64/kernel/syscall.c:140
el0_svc+0x64/0x260 arch/arm64/kernel/entry-common.c:736
el0t_64_sync_handler+0x48/0x148 arch/arm64/kernel/entry-common.c:755
el0t_64_sync+0x198/0x19c arch/arm64/kernel/entry.S:594
It is reproducible with the followint program.
Keep in mind that it may lack the precise threading, sandboxing, and some arguments of a working reproducer.
But it should give an idea of the involved syscalls.
// autogenerated by syzkaller (https://github.com/google/syzkaller)
#define _GNU_SOURCE
#include <endian.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mount.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <unistd.h>
#ifndef __NR_mkdirat
#define __NR_mkdirat 34
#endif
#ifndef __NR_mmap
#define __NR_mmap 222
#endif
#ifndef __NR_mount
#define __NR_mount 40
#endif
#ifndef __NR_symlinkat
#define __NR_symlinkat 36
#endif
int main(void)
{
syscall(__NR_mmap, /*addr=*/0x1ffff000ul, /*len=*/0x1000, /*prot=*/0ul, /*flags=MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE*/0x32ul, /*fd=*/(intptr_t)-1, /*offset=*/0ul);
syscall(__NR_mmap, /*addr=*/0x20000000ul, /*len=*/0x1000000, /*prot=PROT_WRITE|PROT_READ|PROT_EXEC*/7ul, /*flags=MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE*/0x32ul, /*fd=*/(intptr_t)-1, /*offset=*/0ul);
syscall(__NR_mmap, /*addr=*/0x21000000ul, /*len=*/0x1000, /*prot=*/0ul, /*flags=MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE*/0x32ul, /*fd=*/(intptr_t)-1, /*offset=*/0ul);
const char* reason;
(void)reason;
if (write(1, "executing program\n", sizeof("executing program\n") - 1)) {}
// mkdirat arguments: [
// fd: fd_dir (resource)
// path: ptr[in, buffer] {
// buffer: {2e 2f 6d 79 63 6f 6e 66 69 67 00} (length 0xb)
// }
// mode: open_mode = 0x1ff (8 bytes)
// ]
memcpy((void*)0x20000000, "./myconfig\000", 11);
syscall(__NR_mkdirat, /*fd=*/0xffffff9c, /*path=*/0x20000000ul, /*mode=S_IXOTH|S_IWOTH|S_IROTH|S_IXGRP|S_IWGRP|S_IRGRP|S_IXUSR|S_IWUSR|0x100*/0x1fful);
// mount arguments: [
// src: nil
// dst: ptr[in, buffer] {
// buffer: {2e 2f 6d 79 63 6f 6e 66 69 67 00} (length 0xb)
// }
// type: ptr[in, buffer] {
// buffer: {63 6f 6e 66 69 67 66 73 00} (length 0x9)
// }
// flags: mount_flags = 0x0 (8 bytes)
// data: nil
// ]
memcpy((void*)0x20000100, "./myconfig\000", 11);
memcpy((void*)0x20000200, "configfs\000", 9);
syscall(__NR_mount, /*src=*/0ul, /*dst=*/0x20000100ul, /*type=*/0x20000200ul, /*flags=*/0ul, /*data=*/0ul);
// mkdirat arguments: [
// fd: fd_dir (resource)
// path: ptr[in, buffer] {
// buffer: {2e 2f 6d 79 63 6f 6e 66 69 67 2f 75 73 62 5f 67 61 64 67 65 74 2f 67 31 2f 73 74 72 69 6e 67 73 2f 30 78 34 30 39 00} (length 0x27)
// }
// mode: open_mode = 0x1ff (8 bytes)
// ]
memcpy((void*)0x20000400, "./myconfig/usb_gadget/g1/strings/0x409\000", 39);
syscall(__NR_mkdirat, /*fd=*/0xffffff9c, /*path=*/0x20000400ul, /*mode=S_IXOTH|S_IWOTH|S_IROTH|S_IXGRP|S_IWGRP|S_IRGRP|S_IXUSR|S_IWUSR|0x100*/0x1fful);
// mkdirat arguments: [
// fd: fd_dir (resource)
// path: ptr[in, buffer] {
// buffer: {2e 2f 6d 79 63 6f 6e 66 69 67 2f 75 73 62 5f 67 61 64 67 65 74 2f 67 31 2f 73 74 72 69 6e 67 73 2f 30 78 34 30 39 2f 73 31 00} (length 0x2a)
// }
// mode: open_mode = 0x1ff (8 bytes)
// ]
memcpy((void*)0x20000500, "./myconfig/usb_gadget/g1/strings/0x409/s1\000", 42);
syscall(__NR_mkdirat, /*fd=*/0xffffff9c, /*path=*/0x20000500ul, /*mode=S_IXOTH|S_IWOTH|S_IROTH|S_IXGRP|S_IWGRP|S_IRGRP|S_IXUSR|S_IWUSR|0x100*/0x1fful);
// mkdirat arguments: [
// fd: fd_dir (resource)
// path: ptr[in, buffer] {
// buffer: {2e 2f 6d 79 63 6f 6e 66 69 67 2f 75 73 62 5f 67 61 64 67 65 74 2f 67 31 2f 66 75 6e 63 74 69 6f 6e 73 2f 75 76 63 2e 75 73 62 30 00} (length 0x2c)
// }
// mode: open_mode = 0x1ff (8 bytes)
// ]
memcpy((void*)0x20000600, "./myconfig/usb_gadget/g1/functions/uvc.usb0\000", 44);
syscall(__NR_mkdirat, /*fd=*/0xffffff9c, /*path=*/0x20000600ul, /*mode=S_IXOTH|S_IWOTH|S_IROTH|S_IXGRP|S_IWGRP|S_IRGRP|S_IXUSR|S_IWUSR|0x100*/0x1fful);
// mkdirat arguments: [
// fd: fd_dir (resource)
// path: ptr[in, buffer] {
// buffer: {2e 2f 6d 79 63 6f 6e 66 69 67 2f 75 73 62 5f 67 61 64 67 65 74 2f 67 31 2f 66 75 6e 63 74 69 6f 6e 73 2f 75 76 63 2e 75 73 62 30 2f 63 6f 6e 74 72 6f 6c 2f 65 78 74 65 6e 73 69 6f 6e 73 2f 65 78 74 2e 30 00} (length 0x45)
// }
// mode: open_mode = 0x1ff (8 bytes)
// ]
memcpy((void*)0x20000700, "./myconfig/usb_gadget/g1/functions/uvc.usb0/control/extensions/ext.0\000", 69);
syscall(__NR_mkdirat, /*fd=*/0xffffff9c, /*path=*/0x20000700ul, /*mode=S_IXOTH|S_IWOTH|S_IROTH|S_IXGRP|S_IWGRP|S_IRGRP|S_IXUSR|S_IWUSR|0x100*/0x1fful);
// symlinkat arguments: [
// old: ptr[in, buffer] {
// buffer: {2e 2f 6d 79 63 6f 6e 66 69 67 2f 75 73 62 5f 67 61 64 67 65 74 2f 67 31 2f 73 74 72 69 6e 67 73 2f 30 78 34 30 39 2f 73 31 00} (length 0x2a)
// }
// newfd: fd_dir (resource)
// new: ptr[in, buffer] {
// buffer: {2e 2f 6d 79 63 6f 6e 66 69 67 2f 75 73 62 5f 67 61 64 67 65 74 2f 67 31 2f 66 75 6e 63 74 69 6f 6e 73 2f 75 76 63 2e 75 73 62 30 2f 63 6f 6e 74 72 6f 6c 2f 65 78 74 65 6e 73 69 6f 6e 73 2f 65 78 74 2e 30 2f 73 74 72 00} (length 0x49)
// }
// ]
memcpy((void*)0x20000800, "./myconfig/usb_gadget/g1/strings/0x409/s1\000", 42);
memcpy((void*)0x20000900, "./myconfig/usb_gadget/g1/functions/uvc.usb0/control/extensions/ext.0/str\000", 73);
syscall(__NR_symlinkat, /*old=*/0x20000800ul, /*newfd=*/0xffffff9c, /*new=*/0x20000900ul);
// mkdirat arguments: [
// fd: fd_dir (resource)
// path: ptr[in, buffer] {
// buffer: {2e 2f 63 6f 6e 66 69 67 00} (length 0x9)
// }
// mode: open_mode = 0x1ff (8 bytes)
// ]
memcpy((void*)0x20000000, "./config\000", 9);
syscall(__NR_mkdirat, /*fd=*/0xffffff9c, /*path=*/0x20000000ul, /*mode=S_IXOTH|S_IWOTH|S_IROTH|S_IXGRP|S_IWGRP|S_IRGRP|S_IXUSR|S_IWUSR|0x100*/0x1fful);
// mount arguments: [
// src: nil
// dst: ptr[in, buffer] {
// buffer: {2e 2f 63 6f 6e 66 69 67 00} (length 0x9)
// }
// type: ptr[in, buffer] {
// buffer: {63 6f 6e 66 69 67 66 73 00} (length 0x9)
// }
// flags: mount_flags = 0x0 (8 bytes)
// data: nil
// ]
memcpy((void*)0x20000000, "./config\000", 9);
memcpy((void*)0x20000100, "configfs\000", 9);
syscall(__NR_mount, /*src=*/0ul, /*dst=*/0x20000000ul, /*type=*/0x20000100ul, /*flags=*/0ul, /*data=*/0ul);
// mkdirat arguments: [
// fd: fd_dir (resource)
// path: ptr[in, buffer] {
// buffer: {2e 2f 63 6f 6e 66 69 67 2f 75 73 62 5f 67 61 64 67 65 74 2f 67 31 00} (length 0x17)
// }
// mode: open_mode = 0x1ff (8 bytes)
// ]
memcpy((void*)0x20000300, "./config/usb_gadget/g1\000", 23);
syscall(__NR_mkdirat, /*fd=*/0xffffff9c, /*path=*/0x20000300ul, /*mode=S_IXOTH|S_IWOTH|S_IROTH|S_IXGRP|S_IWGRP|S_IRGRP|S_IXUSR|S_IWUSR|0x100*/0x1fful);
// mkdirat arguments: [
// fd: fd_dir (resource)
// path: ptr[in, buffer] {
// buffer: {2e 2f 63 6f 6e 66 69 67 2f 75 73 62 5f 67 61 64 67 65 74 2f 67 31 2f 66 75 6e 63 74 69 6f 6e 73 2f 74 63 6d 2e 75 73 62 30 00} (length 0x2a)
// }
// mode: open_mode = 0x1ff (8 bytes)
// ]
memcpy((void*)0x20000500, "./config/usb_gadget/g1/functions/tcm.usb0\000", 42);
syscall(__NR_mkdirat, /*fd=*/0xffffff9c, /*path=*/0x20000500ul, /*mode=S_IXOTH|S_IWOTH|S_IROTH|S_IXGRP|S_IWGRP|S_IRGRP|S_IXUSR|S_IWUSR|0x100*/0x1fful);
// mkdirat arguments: [
// fd: fd_dir (resource)
// path: ptr[in, buffer] {
// buffer: {2e 2f 63 6f 6e 66 69 67 2f 74 61 72 67 65 74 2f 75 73 62 5f 67 61 64 67 65 74 00} (length 0x1b)
// }
// mode: open_mode = 0x1ff (8 bytes)
// ]
memcpy((void*)0x20000e00, "./config/target/usb_gadget\000", 27);
syscall(__NR_mkdirat, /*fd=*/0xffffff9c, /*path=*/0x20000e00ul, /*mode=S_IXOTH|S_IWOTH|S_IROTH|S_IXGRP|S_IWGRP|S_IRGRP|S_IXUSR|S_IWUSR|0x100*/0x1fful);
// mkdirat arguments: [
// fd: fd_dir (resource)
// path: ptr[in, buffer] {
// buffer: {2e 2f 63 6f 6e 66 69 67 2f 74 61 72 67 65 74 2f 75 73 62 5f 67 61 64 67 65 74 2f 6e 61 61 2e 31 32 33 34 35 36 37 38 39 30 31 32 33 34 35 36 00} (length 0x30)
// }
// mode: open_mode = 0x1ff (8 bytes)
// ]
memcpy((void*)0x20000f00, "./config/target/usb_gadget/naa.1234567890123456\000", 48);
syscall(__NR_mkdirat, /*fd=*/0xffffff9c, /*path=*/0x20000f00ul, /*mode=S_IXOTH|S_IWOTH|S_IROTH|S_IXGRP|S_IWGRP|S_IRGRP|S_IXUSR|S_IWUSR|0x100*/0x1fful);
// mkdirat arguments: [
// fd: fd_dir (resource)
// path: ptr[in, buffer] {
// buffer: {2e 2f 63 6f 6e 66 69 67 2f 74 61 72 67 65 74 2f 75 73 62 5f 67 61 64 67 65 74 2f 6e 61 61 2e 31 32 33 34 35 36 37 38 39 30 31 32 33 34 35 36 2f 74 70 67 74 5f 31 00} (length 0x37)
// }
// mode: open_mode = 0x1ff (8 bytes)
// ]
memcpy((void*)0x20001000, "./config/target/usb_gadget/naa.1234567890123456/tpgt_1\000", 55);
syscall(__NR_mkdirat, /*fd=*/0xffffff9c, /*path=*/0x20001000ul, /*mode=S_IXOTH|S_IWOTH|S_IROTH|S_IXGRP|S_IWGRP|S_IRGRP|S_IXUSR|S_IWUSR|0x100*/0x1fful);
return 0;
}
Reply: Based on the analysis of the provided lockdep report and the kernel source code, here is the assessment of the bug:
**Analysis:**
The bug report indicates a "possible circular locking dependency detected" by lockdep. The reported lock chain involves three locks:
1. `gadget_subsys.su_mutex`
2. `opts->dep_lock`
3. `inode_lock` (specifically the lock class `&sb->s_type->i_mutex_key#23` which corresponds to VFS directory inode locks in configfs).
The dependency chain is formed by the following operations:
- **`inode_lock` -> `gadget_subsys.su_mutex`**: When creating a symlink via `configfs_symlink`, the VFS locks the `inode_lock` of the symlink's parent directory (e.g., `.../ext.0`). The configfs callback `uvcg_extension_allow_link` then acquires `gadget_subsys.su_mutex`.
- **`gadget_subsys.su_mutex` -> `opts->dep_lock`**: When creating a new gadget function via `configfs_mkdir`, configfs holds `gadget_subsys.su_mutex` and calls `function_make` -> `tcm_set_name`, which acquires `opts->dep_lock`.
- **`opts->dep_lock` -> `inode_lock`**: When creating a target portal group (tpgt) via `configfs_mkdir`, the target subsystem holds `opts->dep_lock` (in `usbg_make_tpg`) and calls `configfs_depend_item_unlocked`, which acquires the `inode_lock` of the gadget subsystem's root directory (e.g., `/config/usb_gadget`).
Lockdep flags this as a circular dependency because it tracks lock classes, not individual lock instances. It sees `inode_lock` being acquired while holding `opts->dep_lock`, and `gadget_subsys.su_mutex` being acquired while holding `inode_lock`.
However, this is a **lockdep false positive**. The `inode_lock` instances involved in the cycle belong to completely different directories (the symlink's parent directory deep in the hierarchy vs. the subsystem's root directory). Because they are different inodes, threads acquiring them concurrently will not block each other, and a real deadlock will not occur. Furthermore, configfs employs internal synchronization mechanisms (like `configfs_dirent_lock` and state flags such as `CONFIGFS_USET_DROPPING`) that strictly serialize structural modifications (like `rmdir` and `mkdir`), preventing any theoretical deadlocks that might arise from reverse lock ordering.
In production kernels, lockdep is disabled, meaning this warning will not be printed and no system halt or panic will occur. Since there is no actual deadlock, it does not cause a Denial of Service.
Additionally, `configfs` does not have the `FS_USERNS_MOUNT` flag, meaning it cannot be mounted inside an unprivileged user namespace. The default mount at `/sys/kernel/config` is owned by root, and unprivileged users do not have the write permissions required to create directories (`mkdir`) or symlinks within it. Therefore, this code path is completely inaccessible to unprivileged users.
**Conclusion:**
- **Exploitable:** No.
- **Denial of Service:** No (it is a lockdep false positive; no actual deadlock occurs).
- **Accessible From Unprivileged Processes:** No (requires root privileges to modify configfs).
- **Accessible From User Namespaces:** No (configfs cannot be mounted in user namespaces).
|
| 6/2 |
2026/07/31 03:13 |
llm |
expert |
0m
Model:
Tokens:
input: 14182
output: 48
thoughts: 886
Thoughts: **Circular Locking Dependency Investigation**
Okay, here's the situation. I've got a "possible circular locking dependency" bug report, and it looks like we have a lockdep issue on our hands. The lockdep report shows a circular dependency involving `gadget_subsys.su_mutex`, `&opts->dep_lock`, and `&sb->s_type->i_mutex_key#23` (inode_lock).
The dependency chain, as I understand it, is this: `gadget_subsys.su_mutex` --> `&opts->dep_lock` --> `&sb->s_type->i_mutex_key#23`. Let's break down the execution flow from the report.
On CPU0, we have `lock(&sb->s_type->i_mutex_key#23);` followed by `lock(gadget_subsys.su_mutex);`.
Meanwhile, on CPU1, it's `lock(&opts->dep_lock);` then `lock(&sb->s_type->i_mutex_key#23);`.
Now, let's dive into the stack traces. The first stack, where `gadget_subsys.su_mutex` is acquired while holding `inode_lock`, looks like this: `__mutex_lock -> mutex_lock_nested -> uvcg_extension_allow_link -> configfs_symlink -> vfs_symlink`. In `configfs_symlink`, it's holding `inode_lock` on the parent directory, and then it calls `uvcg_extension_allow_link`, which somehow attempts to acquire `gadget_subsys.su_mutex`. Isn't `gadget_subsys.su_mutex` supposed to be a configfs subsystem mutex? I need to check that.
The second stack trace, where `&opts->dep_lock` holds `gadget_subsys.su_mutex` looks like: `down_write -> inode_lock -> configfs_depend_item_unlocked -> usbg_make_tpg -> target_fabric_make_tpg -> configfs_mkdir`. Actually, no, the report shows that `&sb->s_type->i_mutex_key#23` is acquired *after* `&opts->dep_lock` is acquired. Now the chain makes more sense.
I'm starting to see the true sequence:
- -> `inode_lock` (#2): `configfs_depend_item_unlocked -> usbg_make_tpg -> target_fabric_make_tpg -> configfs_mkdir`
- -> `&opts->dep_lock` (#1): `tcm_set_name -> function_make -> configfs_mkdir`
- -> `gadget_subsys.su_mutex` (#0): `uvcg_extension_allow_link -> configfs_symlink`
This looks like `uvcg_extension_allow_link` and `configfs_symlink` are calling something that results in us attempting to take `gadget_subsys.su_mutex` while already holding a lock that will be required later by code that is going to take `&opts->dep_lock`, resulting in the circular dependency. I need to examine `uvcg_extension_allow_link` in `drivers/usb/gadget/function/uvc_configfs.c` very closely.
|
| 7/2 |
2026/07/31 03:13 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/usb/gadget/function/uvc_configfs.c Name:uvcg_extension_allow_link]
Results: map[SourceCode:1186: static int uvcg_extension_allow_link(struct config_item *src, struct config_item *tgt)
1187: {
1188: struct mutex *su_mutex = &src->ci_group->cg_subsys->su_mutex;
1189: struct uvcg_extension *xu = to_uvcg_extension(src);
1190: struct config_item *gadget_item;
1191: struct gadget_string *string;
1192: struct config_item *strings;
1193: int ret = 0;
1194:
1195: mutex_lock(su_mutex); /* for navigating configfs hierarchy */
1196:
1197: /* Validate that the target of the link is an entry in strings/<langid> */
1198: gadget_item = src->ci_parent->ci_parent->ci_parent->ci_parent->ci_parent;
1199: strings = config_group_find_item(to_config_group(gadget_item), "strings");
1200: if (!strings || tgt->ci_parent->ci_parent != strings) {
1201: ret = -EINVAL;
1202: goto put_strings;
1203: }
1204:
1205: string = to_gadget_string(tgt);
1206: xu->string_descriptor_index = string->usb_string.id;
1207:
1208: put_strings:
1209: config_item_put(strings);
1210: mutex_unlock(su_mutex);
1211:
1212: return ret;
1213: }
SourceFile:drivers/usb/gadget/function/uvc_configfs.c]
|
| 8/2 |
2026/07/31 03:13 |
llm |
expert |
0m
Model:
Tokens:
input: 16360
output: 46
thoughts: 84
Thoughts: Okay, here's my interpretation of this scenario, focusing on the potential locking issue, and delivered from a first-person expert perspective:
**Locking Order Analysis for USB Gadget Functionality**
Alright, I'm diving into this `uvcg_extension_allow_link` function, and the first thing that jumps out is the acquisition of the `su_mutex`. I'm immediately concerned because I've just been thinking about filesystem interactions. Then, it hits me: the `configfs_symlink` function. That one *has* to be holding the `inode_lock` on the parent directory, right? If it doesn't, this entire thing falls apart.
So, let me spell it out: we've got a potential locking order issue here. It's looking like `inode_lock` (held by the parent directory) is acquired *before* the `su_mutex`. This is the problematic order! I need to confirm this.
To properly vet this, I should focus on the `usbg_make_tpg` in `drivers/usb/gadget/function/f_tcm.c`, since that's a key part of the USB gadget functionality and potentially utilizes this locking pattern. It's not necessarily the most *likely* source, but the `f_tcm.c` module has the potential to cause issues with how `configfs` symlinks are created. I need to understand exactly how those functions interact to verify the locking order and see if we have a real problem on our hands.
|
| 9/2 |
2026/07/31 03:13 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/usb/gadget/function/f_tcm.c Name:usbg_make_tpg]
Results: map[SourceCode:1653: static struct se_portal_group *usbg_make_tpg(struct se_wwn *wwn,
1654: const char *name)
1655: {
1656: struct usbg_tport *tport = container_of(wwn, struct usbg_tport,
1657: tport_wwn);
1658: struct usbg_tpg *tpg;
1659: u16 tpgt;
1660: int ret;
1661: struct f_tcm_opts *opts;
1662: unsigned i;
1663:
1664: if (strstr(name, "tpgt_") != name)
1665: return ERR_PTR(-EINVAL);
1666: if (kstrtou16(name + 5, 0, &tpgt))
1667: return ERR_PTR(-EINVAL);
1668: ret = -ENODEV;
1669: mutex_lock(&tpg_instances_lock);
1670: for (i = 0; i < TPG_INSTANCES; ++i)
1671: if (tpg_instances[i].func_inst && !tpg_instances[i].tpg)
1672: break;
1673: if (i == TPG_INSTANCES)
1674: goto unlock_inst;
1675:
1676: opts = container_of(tpg_instances[i].func_inst, struct f_tcm_opts,
1677: func_inst);
1678: mutex_lock(&opts->dep_lock);
1679: if (!opts->ready)
1680: goto unlock_dep;
1681:
1682: if (opts->has_dep) {
1683: if (!try_module_get(opts->dependent))
1684: goto unlock_dep;
1685: } else {
1686: ret = configfs_depend_item_unlocked(
1687: wwn->wwn_group.cg_subsys,
1688: &opts->func_inst.group.cg_item);
1689: if (ret)
1690: goto unlock_dep;
1691: }
1692:
1693: tpg = kzalloc_obj(struct usbg_tpg);
1694: ret = -ENOMEM;
1695: if (!tpg)
1696: goto unref_dep;
1697: mutex_init(&tpg->tpg_mutex);
1698: atomic_set(&tpg->tpg_port_count, 0);
1699: tpg->workqueue = alloc_workqueue("tcm_usb_gadget",
1700: WQ_UNBOUND, WQ_UNBOUND_MAX_ACTIVE);
1701: if (!tpg->workqueue)
1702: goto free_tpg;
1703:
1704: tpg->tport = tport;
1705: tpg->tport_tpgt = tpgt;
1706:
1707: /*
1708: * SPC doesn't assign a protocol identifier for USB-SCSI, so we
1709: * pretend to be SAS..
1710: */
1711: ret = core_tpg_register(wwn, &tpg->se_tpg, SCSI_PROTOCOL_SAS);
1712: if (ret < 0)
1713: goto free_workqueue;
1714:
1715: tpg_instances[i].tpg = tpg;
1716: tpg->fi = tpg_instances[i].func_inst;
1717: mutex_unlock(&opts->dep_lock);
1718: mutex_unlock(&tpg_instances_lock);
1719: return &tpg->se_tpg;
1720:
1721: free_workqueue:
1722: destroy_workqueue(tpg->workqueue);
1723: free_tpg:
1724: kfree(tpg);
1725: unref_dep:
1726: if (opts->has_dep)
1727: module_put(opts->dependent);
1728: else
1729: configfs_undepend_item_unlocked(&opts->func_inst.group.cg_item);
1730: unlock_dep:
1731: mutex_unlock(&opts->dep_lock);
1732: unlock_inst:
1733: mutex_unlock(&tpg_instances_lock);
1734:
1735: return ERR_PTR(ret);
1736: }
SourceFile:drivers/usb/gadget/function/f_tcm.c]
|
| 10/2 |
2026/07/31 03:13 |
llm |
expert |
28m
Model:
Tokens:
input: 18163
output: 45
thoughts: 120
Thoughts: **Analyzing the Locking Order in USB Gadget Configuration**
Okay, so I'm looking at this `usbg_make_tpg` function, specifically how it handles locking. It looks like it's taking `opts->dep_lock` first, then immediately calls `configfs_depend_item_unlocked`. Now, the interesting part is that `configfs_depend_item_unlocked` acquires the `inode_lock` of the directory. This establishes a locking order of `opts->dep_lock` before `inode_lock`. This isn't inherently concerning, but I need to ensure it adheres to established locking protocols.
Hmm, but what about the potential contention with `su_mutex`? I recall that there might be a separate lock that interacts with `opts->dep_lock`. I need to ensure there are no unintended locking order reversals. Let me check the interaction between `su_mutex` and `opts->dep_lock`. Let's examine `tcm_set_name` in `drivers/usb/gadget/function/f_tcm.c` to see how this plays out. This will provide a fuller picture of the locking relationships within the context of the TCM function.
|
| 11/2 |
2026/07/31 03:42 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/usb/gadget/function/f_tcm.c Name:tcm_set_name]
Results: map[SourceCode:2534: static int tcm_set_name(struct usb_function_instance *f, const char *name)
2535: {
2536: struct f_tcm_opts *opts = container_of(f, struct f_tcm_opts, func_inst);
2537:
2538: pr_debug("tcm: Activating %s\n", name);
2539:
2540: mutex_lock(&opts->dep_lock);
2541: opts->ready = true;
2542: mutex_unlock(&opts->dep_lock);
2543:
2544: return 0;
2545: }
SourceFile:drivers/usb/gadget/function/f_tcm.c]
|
| 12/2 |
2026/07/31 03:42 |
llm |
expert |
1m
Model:
Tokens:
input: 18806
output: 38
thoughts: 30
Thoughts: Here's a summary of my understanding:
**Navigating the USB Gadget Configuration System**
Okay, so I'm tracing the flow of how USB gadget names are set up within the kernel. It seems like the core function `tcm_set_name` is being invoked from `function_make`, and that's all happening within the `configfs.c` file in the `drivers/usb/gadget` directory. This suggests the name setting is tightly integrated with the ConfigFS framework, which makes perfect sense for a flexible gadget configuration system. I'm assuming `function_make` is responsible for creating a new USB function instance, and then calls `tcm_set_name` to assign it a specific name within the ConfigFS hierarchy. This probably means the name itself is derived from some external source, maybe provided through a user-space ConfigFS interface? Need to dig deeper into the surrounding code in `configfs.c` to confirm. It is not clear from this data why this process takes place.
|
| 13/2 |
2026/07/31 03:44 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/usb/gadget/configfs.c Name:function_make]
Results: map[SourceCode: 601: static struct config_group *function_make(
602: struct config_group *group,
603: const char *name)
604: {
605: struct gadget_info *gi;
606: struct usb_function_instance *fi;
607: char buf[MAX_NAME_LEN];
608: char *func_name;
609: char *instance_name;
610: int ret;
611:
612: if (strlen(name) >= MAX_NAME_LEN)
613: return ERR_PTR(-ENAMETOOLONG);
614:
615: scnprintf(buf, MAX_NAME_LEN, "%s", name);
616:
617: func_name = buf;
618: instance_name = strchr(func_name, '.');
619: if (!instance_name) {
620: pr_err("Unable to locate . in FUNC.INSTANCE\n");
621: return ERR_PTR(-EINVAL);
622: }
623: *instance_name = '\0';
624: instance_name++;
625:
626: fi = usb_get_function_instance(func_name);
627: if (IS_ERR(fi))
628: return ERR_CAST(fi);
629:
630: ret = config_item_set_name(&fi->group.cg_item, "%s", name);
631: if (ret) {
632: usb_put_function_instance(fi);
633: return ERR_PTR(ret);
634: }
635: if (fi->set_inst_name) {
636: ret = fi->set_inst_name(fi, instance_name);
637: if (ret) {
638: usb_put_function_instance(fi);
639: return ERR_PTR(ret);
640: }
641: }
642:
643: gi = container_of(group, struct gadget_info, functions_group);
644:
645: mutex_lock(&gi->lock);
646: list_add_tail(&fi->cfs_list, &gi->available_func);
647: mutex_unlock(&gi->lock);
648: return &fi->group;
649: }
SourceFile:drivers/usb/gadget/configfs.c]
|
| 14/2 |
2026/07/31 03:44 |
llm |
expert |
0m
Model:
Tokens:
input: 19817
output: 36
thoughts: 61
Thoughts: Here's my thought process, as requested:
**Deconstructing the ConfigFS Creation Process**
Okay, so I'm looking at this code and immediately I see `function_make`. My brain immediately flags that as a configfs item creation function, right? It just *feels* right, and the name backs that up. It's obviously responsible for bringing new objects into existence within the configfs namespace.
Now, I'm trying to trace the lifecycle here, and I see `function_make` is being invoked from `configfs_mkdir`. Wait, so `configfs_mkdir` is a crucial entry point here, a central piece. And what is it doing? Ah, there's a potential concurrency concern I need to keep in mind, right away. I need to be aware of how this works, so I'll check what kind of mutex/lock protection is in place around this operation. Does `configfs_mkdir` use a mutex or a spinlock, or nothing at all? Is it safe, or am I missing something?
Let me just quickly check the implementation of `configfs_mkdir`. I'll go straight to `fs/configfs/dir.c` β that's where I expect to find the meat of the directory-related operations, the directory creation in particular. I will confirm my hypothesis and understand what is going on.
|
| 15/2 |
2026/07/31 03:44 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/configfs/dir.c Name:configfs_mkdir]
Results: map[SourceCode:1289: static struct dentry *configfs_mkdir(struct mnt_idmap *idmap, struct inode *dir,
1290: struct dentry *dentry, umode_t mode)
1291: {
1292: int ret = 0;
1293: int module_got = 0;
1294: struct config_group *group = NULL;
1295: struct config_item *item = NULL;
1296: struct config_item *parent_item;
1297: struct configfs_subsystem *subsys;
1298: struct configfs_dirent *sd;
1299: const struct config_item_type *type;
1300: struct module *subsys_owner = NULL, *new_item_owner = NULL;
1301: struct configfs_fragment *frag;
1302: struct name_snapshot n;
1303: const char *name;
1304:
1305: take_dentry_name_snapshot(&n, dentry);
1306: name = n.name.name;
1307:
1308: sd = dentry->d_parent->d_fsdata;
1309:
1310: /*
1311: * Fake invisibility if dir belongs to a group/default groups hierarchy
1312: * being attached
1313: */
1314: if (!configfs_dirent_is_ready(sd)) {
1315: ret = -ENOENT;
1316: goto out;
1317: }
1318:
1319: if (!(sd->s_type & CONFIGFS_USET_DIR)) {
1320: ret = -EPERM;
1321: goto out;
1322: }
1323:
1324: frag = new_fragment();
1325: if (!frag) {
1326: ret = -ENOMEM;
1327: goto out;
1328: }
1329:
1330: /* Get a working ref for the duration of this function */
1331: parent_item = configfs_get_config_item(dentry->d_parent);
1332: type = parent_item->ci_type;
1333: subsys = to_config_group(parent_item)->cg_subsys;
1334: BUG_ON(!subsys);
1335:
1336: if (!type || !type->ct_group_ops ||
1337: (!type->ct_group_ops->make_group &&
1338: !type->ct_group_ops->make_item)) {
1339: ret = -EPERM; /* Lack-of-mkdir returns -EPERM */
1340: goto out_put;
1341: }
1342:
1343: /*
1344: * The subsystem may belong to a different module than the item
1345: * being created. We don't want to safely pin the new item but
1346: * fail to pin the subsystem it sits under.
1347: */
1348: if (!subsys->su_group.cg_item.ci_type) {
1349: ret = -EINVAL;
1350: goto out_put;
1351: }
1352: subsys_owner = subsys->su_group.cg_item.ci_type->ct_owner;
1353: if (!try_module_get(subsys_owner)) {
1354: ret = -EINVAL;
1355: goto out_put;
1356: }
1357:
1358: mutex_lock(&subsys->su_mutex);
1359: if (type->ct_group_ops->make_group) {
1360: group = type->ct_group_ops->make_group(to_config_group(parent_item), name);
1361: if (!group)
1362: group = ERR_PTR(-ENOMEM);
1363: if (!IS_ERR(group)) {
1364: link_group(to_config_group(parent_item), group);
1365: item = &group->cg_item;
1366: } else
1367: ret = PTR_ERR(group);
1368: } else {
1369: item = type->ct_group_ops->make_item(to_config_group(parent_item), name);
1370: if (!item)
1371: item = ERR_PTR(-ENOMEM);
1372: if (!IS_ERR(item))
1373: link_obj(parent_item, item);
1374: else
1375: ret = PTR_ERR(item);
1376: }
1377: mutex_unlock(&subsys->su_mutex);
1378:
1379: if (ret) {
1380: /*
1381: * If ret != 0, then link_obj() was never called.
1382: * There are no extra references to clean up.
1383: */
1384: goto out_subsys_put;
1385: }
1386:
1387: /*
1388: * link_obj() has been called (via link_group() for groups).
1389: * From here on out, errors must clean that up.
1390: */
1391:
1392: type = item->ci_type;
1393: if (!type) {
1394: ret = -EINVAL;
1395: goto out_unlink;
1396: }
1397:
1398: new_item_owner = type->ct_owner;
1399: if (!try_module_get(new_item_owner)) {
1400: ret = -EINVAL;
1401: goto out_unlink;
1402: }
1403:
1404: /*
1405: * I hate doing it this way, but if there is
1406: * an error, module_put() probably should
1407: * happen after any cleanup.
1408: */
1409: module_got = 1;
1410:
1411: /*
1412: * Make racing rmdir() fail if it did not tag parent with
1413: * CONFIGFS_USET_DROPPING
1414: * Note: if CONFIGFS_USET_DROPPING is already set, attach_group() will
1415: * fail and let rmdir() terminate correctly
1416: */
1417: spin_lock(&configfs_dirent_lock);
1418: /* This will make configfs_detach_prep() fail */
1419: sd->s_type |= CONFIGFS_USET_IN_MKDIR;
1420: spin_unlock(&configfs_dirent_lock);
1421:
1422: if (group)
1423: ret = configfs_attach_group(item, dentry, frag);
1424: else
1425: ret = configfs_attach_item(item, dentry, frag);
1426:
1427: spin_lock(&configfs_dirent_lock);
1428: sd->s_type &= ~CONFIGFS_USET_IN_MKDIR;
1429: if (!ret)
1430: configfs_dir_set_ready(dentry->d_fsdata);
1431: spin_unlock(&configfs_dirent_lock);
1432:
1433: out_unlink:
1434: if (ret) {
1435: /* Tear down everything we built up */
1436: mutex_lock(&subsys->su_mutex);
1437:
1438: client_disconnect_notify(parent_item, item);
1439: if (group)
1440: unlink_group(group);
1441: else
1442: unlink_obj(item);
1443: client_drop_item(parent_item, item);
1444:
1445: mutex_unlock(&subsys->su_mutex);
1446:
1447: if (module_got)
1448: module_put(new_item_owner);
1449: }
1450:
1451: out_subsys_put:
1452: if (ret)
1453: module_put(subsys_owner);
1454:
1455: out_put:
1456: /*
1457: * link_obj()/link_group() took a reference from child->parent,
1458: * so the parent is safely pinned. We can drop our working
1459: * reference.
1460: */
1461: config_item_put(parent_item);
1462: put_fragment(frag);
1463:
1464: out:
1465: release_dentry_name_snapshot(&n);
1466: return ERR_PTR(ret);
1467: }
SourceFile:fs/configfs/dir.c]
|
| 16/2 |
2026/07/31 03:44 |
llm |
expert |
0m
Model:
Tokens:
input: 22794
output: 46
thoughts: 386
Thoughts: **Analyzing the Lock Ordering in configfs_mkdir and Related Functions**
Okay, let's break this down. I'm seeing a potential locking issue within the `configfs_mkdir` function and its related call chain. Specifically, I'm concerned about the order in which locks are acquired. I realize that `configfs_mkdir` calls either `make_group` or `make_item` while it is itself holding the `su_mutex`. The critical path I'm focusing on is `su_mutex` -> `opts->dep_lock`.
I've traced this ordering back. `make_group` within `configfs_mkdir` calls into `function_make`, which eventually leads to a call to `tcm_set_name`. And that's where the problem arises: `tcm_set_name` acquires `opts->dep_lock`. So, my first observation is the acquisition order: `su_mutex` is held by `configfs_mkdir`, which then indirectly leads to the acquisition of `opts->dep_lock` through `tcm_set_name`. This is where `su_mutex` -> `opts->dep_lock` is established.
Further, I'm also seeing an additional acquisition order: `opts->dep_lock` -> `inode_lock`. I can see this in `usbg_make_tpg`. I recall that `usbg_make_tpg` is also a `make_group` callback, but this time for a target/usb_gadget/naa.../tpgt_1. I'm pretty certain that `usbg_make_tpg` is called from `target_fabric_make_tpg`. More importantly, `target_fabric_make_tpg` is called from `configfs_mkdir`, which holds `su_mutex`. This means that `su_mutex` is held when `usbg_make_tpg` is invoked.
So to recap: `configfs_mkdir` -> `target_fabric_make_tpg` -> `usbg_make_tpg`. I need to verify, but if `usbg_make_tpg` is called from `configfs_mkdir`, it validates my assertion that the `su_mutex` is held during that call. It also means that the dependency on `opts->dep_lock` which is acquired in `usbg_make_tpg`, has to be carefully investigated.
I should check the `target_fabric_make_tpg` function. I will check the source code in `drivers/target/target_core_fabric_configfs.c` right away. I need to make sure this lock ordering is correct and doesn't lead to any deadlocks.
|
| 17/2 |
2026/07/31 03:44 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/target/target_core_fabric_configfs.c Name:target_fabric_make_tpg]
Results: map[SourceCode: 926: static struct config_group *target_fabric_make_tpg(
927: struct config_group *group,
928: const char *name)
929: {
930: struct se_wwn *wwn = container_of(group, struct se_wwn, wwn_group);
931: struct target_fabric_configfs *tf = wwn->wwn_tf;
932: struct se_portal_group *se_tpg;
933:
934: if (!tf->tf_ops->fabric_make_tpg) {
935: pr_err("tf->tf_ops->fabric_make_tpg is NULL\n");
936: return ERR_PTR(-ENOSYS);
937: }
938:
939: se_tpg = tf->tf_ops->fabric_make_tpg(wwn, name);
940: if (!se_tpg || IS_ERR(se_tpg))
941: return ERR_PTR(-EINVAL);
942:
943: config_group_init_type_name(&se_tpg->tpg_group, name,
944: &tf->tf_tpg_base_cit);
945:
946: config_group_init_type_name(&se_tpg->tpg_lun_group, "lun",
947: &tf->tf_tpg_lun_cit);
948: configfs_add_default_group(&se_tpg->tpg_lun_group,
949: &se_tpg->tpg_group);
950:
951: config_group_init_type_name(&se_tpg->tpg_np_group, "np",
952: &tf->tf_tpg_np_cit);
953: configfs_add_default_group(&se_tpg->tpg_np_group,
954: &se_tpg->tpg_group);
955:
956: config_group_init_type_name(&se_tpg->tpg_acl_group, "acls",
957: &tf->tf_tpg_nacl_cit);
958: configfs_add_default_group(&se_tpg->tpg_acl_group,
959: &se_tpg->tpg_group);
960:
961: config_group_init_type_name(&se_tpg->tpg_attrib_group, "attrib",
962: &tf->tf_tpg_attrib_cit);
963: configfs_add_default_group(&se_tpg->tpg_attrib_group,
964: &se_tpg->tpg_group);
965:
966: config_group_init_type_name(&se_tpg->tpg_auth_group, "auth",
967: &tf->tf_tpg_auth_cit);
968: configfs_add_default_group(&se_tpg->tpg_auth_group,
969: &se_tpg->tpg_group);
970:
971: config_group_init_type_name(&se_tpg->tpg_param_group, "param",
972: &tf->tf_tpg_param_cit);
973: configfs_add_default_group(&se_tpg->tpg_param_group,
974: &se_tpg->tpg_group);
975:
976: return &se_tpg->tpg_group;
977: }
SourceFile:drivers/target/target_core_fabric_configfs.c]
|
| 18/2 |
2026/07/31 03:44 |
llm |
expert |
0m
Model:
Tokens:
input: 24855
output: 41
thoughts: 374
Thoughts: **Analyzing a Potential Lock Ordering Issue**
Okay, let me think this through. I see `target_fabric_make_tpg` is called from `configfs_mkdir`. Right, and that means `configfs_mkdir` holds the `su_mutex` belonging to the `target` subsystem. Hmm, if I remember correctly from lockdep, that `su_mutex` is actually `gadget_subsys.su_mutex`. But, wait a minute, `target_fabric_make_tpg` *is* within the `target` subsystem, so shouldn't it be holding `target_subsys.su_mutex`? That doesn't feel right.
Let's look at the lockdep report⦠ah, here it is:
-> #2 (&sb->s_type->i_mutex_key#23):
down_write+0x50/0xc0 kernel/locking/rwsem.c:1631
inode_lock include/linux/fs.h:1024 [inline]
configfs_depend_item_unlocked+0x154/0x344 fs/configfs/dir.c:1259
usbg_make_tpg+0x230/0x4b4 drivers/usb/gadget/function/f_tcm.c:1686
target_fabric_make_tpg+0x98/0x518 drivers/target/target_core_fabric_configfs.c:939
configfs_mkdir+0x374/0x7d4 fs/configfs/dir.c:1360
Okay, that's what I needed. It says that `configfs_depend_item_unlocked` acquires the `inode_lock` on the target item. Time to jump into `fs/configfs/dir.c` and take a closer look at `configfs_depend_item_unlocked`.
|
| 19/2 |
2026/07/31 03:44 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/configfs/dir.c Name:configfs_depend_item_unlocked]
Results: map[SourceCode:1216: /*
1217: * caller_subsys is a caller's subsystem not target's. This is used to
1218: * determine if we should lock root and check subsys or not. When we are
1219: * in the same subsystem as our target there is no need to do locking as
1220: * we know that subsys is valid and is not unregistered during this function
1221: * as we are called from callback of one of his children and VFS holds a lock
1222: * on some inode. Otherwise we have to lock our root to ensure that target's
1223: * subsystem it is not unregistered during this function.
1224: */
1225: int configfs_depend_item_unlocked(struct configfs_subsystem *caller_subsys,
1226: struct config_item *target)
1227: {
1228: struct configfs_subsystem *target_subsys;
1229: struct config_group *root, *parent;
1230: struct configfs_dirent *subsys_sd;
1231: int ret = -ENOENT;
1232:
1233: /* Disallow this function for configfs root */
1234: if (configfs_is_root(target))
1235: return -EINVAL;
1236:
1237: parent = target->ci_group;
1238: /*
1239: * This may happen when someone is trying to depend root
1240: * directory of some subsystem
1241: */
1242: if (configfs_is_root(&parent->cg_item)) {
1243: target_subsys = to_configfs_subsystem(to_config_group(target));
1244: root = parent;
1245: } else {
1246: target_subsys = parent->cg_subsys;
1247: /* Find a cofnigfs root as we may need it for locking */
1248: for (root = parent; !configfs_is_root(&root->cg_item);
1249: root = root->cg_item.ci_group)
1250: ;
1251: }
1252:
1253: if (target_subsys != caller_subsys) {
1254: /*
1255: * We are in other configfs subsystem, so we have to do
1256: * additional locking to prevent other subsystem from being
1257: * unregistered
1258: */
1259: inode_lock(d_inode(root->cg_item.ci_dentry));
1260:
1261: /*
1262: * As we are trying to depend item from other subsystem
1263: * we have to check if this subsystem is still registered
1264: */
1265: subsys_sd = configfs_find_subsys_dentry(
1266: root->cg_item.ci_dentry->d_fsdata,
1267: &target_subsys->su_group.cg_item);
1268: if (!subsys_sd)
1269: goto out_root_unlock;
1270: } else {
1271: subsys_sd = target_subsys->su_group.cg_item.ci_dentry->d_fsdata;
1272: }
1273:
1274: /* Now we can execute core of depend item */
1275: ret = configfs_do_depend_item(subsys_sd, target);
1276:
1277: if (target_subsys != caller_subsys)
1278: out_root_unlock:
1279: /*
1280: * We were called from subsystem other than our target so we
1281: * took some locks so now it's time to release them
1282: */
1283: inode_unlock(d_inode(root->cg_item.ci_dentry));
1284:
1285: return ret;
1286: }
SourceFile:fs/configfs/dir.c]
|
| 20/2 |
2026/07/31 03:44 |
llm |
expert |
0m
Model:
Tokens:
input: 26861
output: 38
thoughts: 1880
Thoughts: **Analyzing a Potential ConfigFS Lock Dependency Cycle**
Okay, so I'm looking at this lockdep report, and it looks like `configfs_depend_item_unlocked` is locking the root directory's `inode_lock`. That's where we're starting. We know that `configfs_symlink` also locks the parent directory's `inode_lock`. The report says both of these use the same lock class, `&sb->s_type->i_mutex_key#23`, which means it's the standard configfs directory `inode_lock`.
Now, if we walk through the call chain, we have this:
1. `configfs_symlink` locks an `inode_lock` (parent directory).
2. `configfs_symlink` calls `uvcg_extension_allow_link`, which then locks `gadget_subsys.su_mutex`.
3. `configfs_mkdir` locks `gadget_subsys.su_mutex` (when making a function).
4. `function_make` then calls `tcm_set_name`, which locks `opts->dep_lock`.
5. `configfs_mkdir` locks `target_subsys.su_mutex` (when creating a tpgt).
6. `target_fabric_make_tpg` calls `usbg_make_tpg`, which then locks `opts->dep_lock`.
7. `usbg_make_tpg` calls `configfs_depend_item_unlocked`, which locks the root directory's `inode_lock`.
The crux of the matter is that `configfs_mkdir` can take different paths depending on what it's creating. If it's a function, it locks `gadget_subsys.su_mutex`. If it's a tpgt, it locks `target_subsys.su_mutex`. The lockdep report shows the cycle.
My initial thought was that there was a cycle: `inode_lock` -> `gadget_subsys.su_mutex` -> `opts->dep_lock` -> `inode_lock`, but now it looks more complicated:
1. `inode_lock` (parent of symlink) -> `gadget_subsys.su_mutex` (via `configfs_symlink`)
2. `gadget_subsys.su_mutex` -> `opts->dep_lock` (via `configfs_mkdir` (function) -> `function_make` -> `tcm_set_name`)
3. `opts->dep_lock` -> `inode_lock` (root directory) (via `configfs_mkdir` (tpgt) -> `target_fabric_make_tpg` -> `usbg_make_tpg` -> `configfs_depend_item_unlocked`)
To confirm this, I need to see if this is an actual deadlock possibility. So, let's look at a possible scenario.
* CPU0: Runs `configfs_symlink` (say, creating a symlink in `ext.0`), thus locking `inode_lock` of `ext.0`. Then `uvcg_extension_allow_link` is called, attempting to lock `gadget_subsys.su_mutex`.
* CPU1: Runs `configfs_mkdir` to create a function (tcm.usb0), which acquires `gadget_subsys.su_mutex` and then calls function_make -> tcm_set_name which tries to lock `opts->dep_lock`.
* CPU2: Runs `configfs_mkdir` to create a tpgt which acquires `target_subsys.su_mutex`. Then calls `target_fabric_make_tpg` -> `usbg_make_tpg` which locks `opts->dep_lock`. Then calls `configfs_depend_item_unlocked` which attempts to acquire the root directory's `inode_lock`.
The concern is CPU0 holds a lock on a child directory and CPU2 on the root directory. Are these the same lock? No, they are different inodes. Lockdep tracks lock classes. Since both are using `&sb->s_type->i_mutex_key#23`, it's flagged as a circular dependency. However, since the VFS locking allows different `inode_lock` instances to be locked, the situation is more nuanced.
The VFS uses `I_MUTEX_PARENT` and `I_MUTEX_CHILD`. The key insight is that `configfs_depend_item_unlocked` (CPU2) is locking the root directory, which is
|
| 21/2 |
2026/07/31 03:45 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/configfs/symlink.c Name:configfs_symlink]
Results: map[SourceCode: 135: int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
136: struct dentry *dentry, const char *symname)
137: {
138: int ret;
139: struct configfs_dirent *sd;
140: struct config_item *parent_item;
141: struct config_item *target_item = NULL;
142: const struct config_item_type *type;
143:
144: sd = dentry->d_parent->d_fsdata;
145: /*
146: * Fake invisibility if dir belongs to a group/default groups hierarchy
147: * being attached
148: */
149: if (!configfs_dirent_is_ready(sd))
150: return -ENOENT;
151:
152: parent_item = configfs_get_config_item(dentry->d_parent);
153: type = parent_item->ci_type;
154:
155: ret = -EPERM;
156: if (!type || !type->ct_item_ops ||
157: !type->ct_item_ops->allow_link)
158: goto out_put;
159:
160: /*
161: * This is really sick. What they wanted was a hybrid of
162: * link(2) and symlink(2) - they wanted the target resolved
163: * at syscall time (as link(2) would've done), be a directory
164: * (which link(2) would've refused to do) *AND* be a deep
165: * fucking magic, making the target busy from rmdir POV.
166: * symlink(2) is nothing of that sort, and the locking it
167: * gets matches the normal symlink(2) semantics. Without
168: * attempts to resolve the target (which might very well
169: * not even exist yet) done prior to locking the parent
170: * directory. This perversion, OTOH, needs to resolve
171: * the target, which would lead to obvious deadlocks if
172: * attempted with any directories locked.
173: *
174: * Unfortunately, that garbage is userland ABI and we should've
175: * said "no" back in 2005. Too late now, so we get to
176: * play very ugly games with locking.
177: *
178: * Try *ANYTHING* of that sort in new code, and you will
179: * really regret it. Just ask yourself - what could a BOFH
180: * do to me and do I want to find it out first-hand?
181: *
182: * AV, a thoroughly annoyed bastard.
183: */
184: inode_unlock(dir);
185: ret = get_target(symname, &target_item, dentry->d_sb);
186: inode_lock(dir);
187: if (ret)
188: goto out_put;
189:
190: if (dentry->d_inode || d_unhashed(dentry))
191: ret = -EEXIST;
192: else
193: ret = inode_permission(&nop_mnt_idmap, dir,
194: MAY_WRITE | MAY_EXEC);
195: if (!ret)
196: ret = type->ct_item_ops->allow_link(parent_item, target_item);
197: if (!ret) {
198: mutex_lock(&configfs_symlink_mutex);
199: ret = create_link(parent_item, target_item, dentry);
200: mutex_unlock(&configfs_symlink_mutex);
201: if (ret && type->ct_item_ops->drop_link)
202: type->ct_item_ops->drop_link(parent_item,
203: target_item);
204: }
205:
206: config_item_put(target_item);
207:
208: out_put:
209: config_item_put(parent_item);
210: return ret;
211: }
SourceFile:fs/configfs/symlink.c]
|
| 22/2 |
2026/07/31 03:45 |
llm |
expert |
0m
Model:
Tokens:
input: 31077
output: 46
thoughts: 631
Thoughts: **Analyzing a Potential ConfigFS Deadlock**
Okay, let's break this down. My initial observation is that `configfs_symlink` is doing something interesting with locks. It unlocks the directory `dir`, which was previously locked by the VFS using `I_MUTEX_PARENT`, resolves the link's target, and then re-locks `dir` using `inode_lock(dir)`. I see that `inode_lock` uses subclass 0. And, notably, `configfs_depend_item_unlocked` also uses `inode_lock` with subclass 0. Aha! So, they are indeed the same lock class. This is significant.
Now, let's retrace the potential deadlock scenario to fully understand the flow:
**CPU0:** Starts with `configfs_symlink`
1. It begins by unlocking `dir` using `inode_unlock(dir)`.
2. Next, it calls `get_target(...)`.
3. Then it re-locks `dir` using `inode_lock(dir)` (acquires the `inode_lock` of `ext.0`).
4. Then the function call to `type->ct_item_ops->allow_link(...)` gets called, which translates to `uvcg_extension_allow_link`.
5. Finally, it acquires the `mutex` using `mutex_lock(su_mutex)`, which acquires `gadget_subsys.su_mutex`.
**CPU1:** Deals with a `configfs_mkdir` call on `/config/usb_gadget/g1/functions/tcm.usb0`.
1. First, it acquires the `mutex` using `mutex_lock(su_mutex)`, which acquires `gadget_subsys.su_mutex`.
2. The function call to `type->ct_group_ops->make_group(...)` gets called, which translates to `function_make` and then `tcm_set_name`.
3. Next, it acquires the lock `mutex_lock(&opts->dep_lock)`, thus acquiring `opts->dep_lock`.
**CPU2:** Similarly, a `configfs_mkdir` call occurs on `/config/target/usb_gadget/naa.1234567890123456/tpgt_1`.
1. It begins by acquiring the `mutex` using `mutex_lock(su_mutex)`, which acquires `target_subsys.su_mutex`.
2. Then, it also calls `type->ct_group_ops->make_group(...)`, which translates to `target_fabric_make_tpg` and then `usbg_make_tpg`.
3. It acquires the lock `mutex_lock(&opts->dep_lock)`, thus acquiring `opts->dep_lock`.
4. It subsequently calls `configfs_depend_item_unlocked`.
5. Finally, it acquires the `inode_lock` of the configfs root using `inode_lock(root)`.
The crucial point here is that both CPU1 and CPU2 are attempting to acquire `opts->dep_lock`. Are they referencing the same `opts` instance? In CPU1, `tcm_set_name` obtains `opts` from the `f_tcm_opts` associated with `tcm.usb0`. In CPU2, `usbg_make_tpg` iterates through `tpg_instances` and finds an instance where `func_inst` is set, but `tpg` is not. This is a potential point of contention. We need to verify that `opts` is not shared between these two. I need to re-examine `usbg_make_tpg` to clarify this. There's a high chance of a shared resource here.
|
| 23/2 |
2026/07/31 03:45 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/usb/gadget/function/f_tcm.c Name:usbg_make_tpg]
Results: map[SourceCode:1653: static struct se_portal_group *usbg_make_tpg(struct se_wwn *wwn,
1654: const char *name)
1655: {
1656: struct usbg_tport *tport = container_of(wwn, struct usbg_tport,
1657: tport_wwn);
1658: struct usbg_tpg *tpg;
1659: u16 tpgt;
1660: int ret;
1661: struct f_tcm_opts *opts;
1662: unsigned i;
1663:
1664: if (strstr(name, "tpgt_") != name)
1665: return ERR_PTR(-EINVAL);
1666: if (kstrtou16(name + 5, 0, &tpgt))
1667: return ERR_PTR(-EINVAL);
1668: ret = -ENODEV;
1669: mutex_lock(&tpg_instances_lock);
1670: for (i = 0; i < TPG_INSTANCES; ++i)
1671: if (tpg_instances[i].func_inst && !tpg_instances[i].tpg)
1672: break;
1673: if (i == TPG_INSTANCES)
1674: goto unlock_inst;
1675:
1676: opts = container_of(tpg_instances[i].func_inst, struct f_tcm_opts,
1677: func_inst);
1678: mutex_lock(&opts->dep_lock);
1679: if (!opts->ready)
1680: goto unlock_dep;
1681:
1682: if (opts->has_dep) {
1683: if (!try_module_get(opts->dependent))
1684: goto unlock_dep;
1685: } else {
1686: ret = configfs_depend_item_unlocked(
1687: wwn->wwn_group.cg_subsys,
1688: &opts->func_inst.group.cg_item);
1689: if (ret)
1690: goto unlock_dep;
1691: }
1692:
1693: tpg = kzalloc_obj(struct usbg_tpg);
1694: ret = -ENOMEM;
1695: if (!tpg)
1696: goto unref_dep;
1697: mutex_init(&tpg->tpg_mutex);
1698: atomic_set(&tpg->tpg_port_count, 0);
1699: tpg->workqueue = alloc_workqueue("tcm_usb_gadget",
1700: WQ_UNBOUND, WQ_UNBOUND_MAX_ACTIVE);
1701: if (!tpg->workqueue)
1702: goto free_tpg;
1703:
1704: tpg->tport = tport;
1705: tpg->tport_tpgt = tpgt;
1706:
1707: /*
1708: * SPC doesn't assign a protocol identifier for USB-SCSI, so we
1709: * pretend to be SAS..
1710: */
1711: ret = core_tpg_register(wwn, &tpg->se_tpg, SCSI_PROTOCOL_SAS);
1712: if (ret < 0)
1713: goto free_workqueue;
1714:
1715: tpg_instances[i].tpg = tpg;
1716: tpg->fi = tpg_instances[i].func_inst;
1717: mutex_unlock(&opts->dep_lock);
1718: mutex_unlock(&tpg_instances_lock);
1719: return &tpg->se_tpg;
1720:
1721: free_workqueue:
1722: destroy_workqueue(tpg->workqueue);
1723: free_tpg:
1724: kfree(tpg);
1725: unref_dep:
1726: if (opts->has_dep)
1727: module_put(opts->dependent);
1728: else
1729: configfs_undepend_item_unlocked(&opts->func_inst.group.cg_item);
1730: unlock_dep:
1731: mutex_unlock(&opts->dep_lock);
1732: unlock_inst:
1733: mutex_unlock(&tpg_instances_lock);
1734:
1735: return ERR_PTR(ret);
1736: }
SourceFile:drivers/usb/gadget/function/f_tcm.c]
|
| 24/2 |
2026/07/31 03:45 |
llm |
expert |
0m
Model:
Tokens:
input: 33933
output: 37
thoughts: 1174
Thoughts: **Analyzing a Potential Lockdep False Positive**
Okay, so I'm looking at this lockdep report, and it seems like there might be a false positive here. Let's break it down. First, I see that `opts` is indeed the same lock class as `&opts->dep_lock`. The report suggests a potential circular dependency, which could lead to a deadlock between CPUs 0, 1, and 2.
CPU0 holds `inode_lock` on `ext.0` and is waiting for `gadget_subsys.su_mutex`. CPU1 holds `gadget_subsys.su_mutex` and is waiting for `opts->dep_lock`. CPU2 holds `opts->dep_lock` and is waiting for the `inode_lock` on the configfs root.
Now, the crucial point is that CPU0's `inode_lock` is on `ext.0`, while CPU2 wants the `inode_lock` of the configfs root. These are *different* inodes, even though lockdep sees them as the same lock class, since VFS uses the same lock class for all inodes in the same filesystem, unless subclassed. So, CPU2 won't block on CPU0 in this scenario because it will acquire its desired root lock. This strongly suggests a lockdep false positive.
I need to confirm this: Could CPU2 *actually* want the `inode_lock` on `ext.0`? No, it explicitly locks the root. Could CPU0 want the root lock? No, CPU0 locks `ext.0`. But, what if CPU0 is doing a `configfs_symlink` in the root directory? However, configfs root doesn't allow symlinks. Symlinks are only allowed where `allow_link` is defined, which is the case for `uvcg_extension` (that is, `ext.0`).
So, CPU0 locks `ext.0`. CPU2 locks the configfs root. Never the same inode. Wait, the VFS `inode_lock` *is* a single lock class for all inodes within a filesystem, unless subclassed. VFS uses subclasses to express parent/child relationships. When `configfs_symlink` takes the `inode_lock(dir)`, it uses subclass 0. `configfs_depend_item_unlocked` does the same. This means lockdep sees the same lock class and reports a circular dependency.
But since they are different inodes, they won't deadlock. Wait, could two threads execute `configfs_depend_item_unlocked` concurrently? Yes, but they'd both lock the root directory, so they serialize. Could two threads execute `configfs_symlink` concurrently? Yes, on different directories, if they are on the same directory, they serialize.
Could CPU0 and CPU2 deadlock if CPU0 locks root and CPU2 locks root? CPU0 cannot lock root because root doesn't have `allow_link`. Could CPU2 lock `ext.0`? No, CPU2 locks the root of the subsystem.
Wait a second. `configfs_depend_item_unlocked` finds the root of the target item. The target item is `opts->func_inst.group.cg_item`, and that is in `gadget_subsys`. The root of `gadget_subsys` is `/config/usb_gadget`. CPU2 is locking *that* directory. So, CPU0 locks `/config/usb_gadget/g1/functions/uvc.usb0/control/extensions/ext.0`.
They are different inodes! So the original report remains a false positive.
I need to make sure: Is there *any* other path where the `inode_lock` of `/config/usb_gadget` is acquired while holding `gadget_subsys.su_mutex`? No, `configfs_depend_item_unlocked` is called without that mutex held.
Wait a minute, `configfs_depend_item_unlocked` is called from `usbg_make_tpg`, which in turn is called from `target_fabric_make_tpg`, which is called from `configfs_mkdir` on `/config/target/...`. So, `target_subsys.su_mutex` is held at that point. Then the code locks the `/config/usb_gadget`'s `inode_lock`. Aha! This changes things. It means we have `target_subsys.su_mutex` -> `opts->dep_lock` -> `inode_lock` (of `/config/usb_gadget`). And, of course, the original `inode_lock` (of `ext.0`) -> `gadget_sub
|
| 25/2 |
2026/07/31 03:45 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/configfs/dir.c Name:configfs_rmdir]
Results: map[SourceCode:1469: static int configfs_rmdir(struct inode *dir, struct dentry *dentry)
1470: {
1471: struct config_item *parent_item;
1472: struct config_item *item;
1473: struct configfs_subsystem *subsys;
1474: struct configfs_dirent *sd;
1475: struct configfs_fragment *frag;
1476: struct module *subsys_owner = NULL, *dead_item_owner = NULL;
1477: int ret;
1478:
1479: sd = dentry->d_fsdata;
1480: if (sd->s_type & CONFIGFS_USET_DEFAULT)
1481: return -EPERM;
1482:
1483: /* Get a working ref until we have the child */
1484: parent_item = configfs_get_config_item(dentry->d_parent);
1485: subsys = to_config_group(parent_item)->cg_subsys;
1486: BUG_ON(!subsys);
1487:
1488: if (!parent_item->ci_type) {
1489: config_item_put(parent_item);
1490: return -EINVAL;
1491: }
1492:
1493: /* configfs_mkdir() shouldn't have allowed this */
1494: BUG_ON(!subsys->su_group.cg_item.ci_type);
1495: subsys_owner = subsys->su_group.cg_item.ci_type->ct_owner;
1496:
1497: /*
1498: * Ensure that no racing symlink() will make detach_prep() fail while
1499: * the new link is temporarily attached
1500: */
1501: do {
1502: struct dentry *wait;
1503:
1504: mutex_lock(&configfs_symlink_mutex);
1505: spin_lock(&configfs_dirent_lock);
1506: /*
1507: * Here's where we check for dependents. We're protected by
1508: * configfs_dirent_lock.
1509: * If no dependent, atomically tag the item as dropping.
1510: */
1511: ret = sd->s_dependent_count ? -EBUSY : 0;
1512: if (!ret) {
1513: ret = configfs_detach_prep(sd, &wait);
1514: if (ret)
1515: configfs_detach_rollback(sd);
1516: }
1517: spin_unlock(&configfs_dirent_lock);
1518: mutex_unlock(&configfs_symlink_mutex);
1519:
1520: if (ret) {
1521: if (ret != -EAGAIN) {
1522: config_item_put(parent_item);
1523: return ret;
1524: }
1525:
1526: /* Wait until the racing operation terminates */
1527: inode_lock(d_inode(wait));
1528: inode_unlock(d_inode(wait));
1529: dput(wait);
1530: }
1531: } while (ret == -EAGAIN);
1532:
1533: frag = sd->s_frag;
1534: if (down_write_killable(&frag->frag_sem)) {
1535: spin_lock(&configfs_dirent_lock);
1536: configfs_detach_rollback(sd);
1537: spin_unlock(&configfs_dirent_lock);
1538: config_item_put(parent_item);
1539: return -EINTR;
1540: }
1541: frag->frag_dead = true;
1542: up_write(&frag->frag_sem);
1543:
1544: /* Get a working ref for the duration of this function */
1545: item = configfs_get_config_item(dentry);
1546:
1547: /* Drop reference from above, item already holds one. */
1548: config_item_put(parent_item);
1549:
1550: if (item->ci_type)
1551: dead_item_owner = item->ci_type->ct_owner;
1552:
1553: if (sd->s_type & CONFIGFS_USET_DIR) {
1554: configfs_detach_group(dentry);
1555:
1556: mutex_lock(&subsys->su_mutex);
1557: client_disconnect_notify(parent_item, item);
1558: unlink_group(to_config_group(item));
1559: } else {
1560: configfs_detach_item(dentry);
1561:
1562: mutex_lock(&subsys->su_mutex);
1563: client_disconnect_notify(parent_item, item);
1564: unlink_obj(item);
1565: }
1566:
1567: client_drop_item(parent_item, item);
1568: mutex_unlock(&subsys->su_mutex);
1569:
1570: /* Drop our reference from above */
1571: config_item_put(item);
1572:
1573: module_put(dead_item_owner);
1574: module_put(subsys_owner);
1575:
1576: return 0;
1577: }
SourceFile:fs/configfs/dir.c]
|
| 26/2 |
2026/07/31 03:45 |
llm |
expert |
0m
Model:
Tokens:
input: 37794
output: 35
thoughts: 829
Thoughts: **Analyzing a Potential ConfigFS Deadlock**
Okay, let's break this down. My initial thought was, "VFS calls `configfs_rmdir` with the parent directory's `inode_lock` held. Then, `configfs_rmdir` grabs the `subsys->su_mutex`. Standard locking order: `inode_lock` before `su_mutex`. Cool." But hold on⦠I need to consider a different path.
`configfs_depend_item_unlocked` comes into play. That function acquires the *root* directory's `inode_lock`. And it gets called from `usbg_make_tpg`, which in turn is called from `configfs_mkdir`, and `configfs_mkdir` holds the `su_mutex` of the *target* subsystem. Thus we have `target_subsys.su_mutex` -> `inode_lock` (of root directory). That's the *reverse* of what I initially thought was the standard order! This strongly suggests a potential deadlock scenario. `inode_lock` then `su_mutex` is the safe path, but `su_mutex` then `inode_lock` suggests a potential deadlock.
Let's test this out. I'm going to run two threads, and step through the logic.
CPU0 does an `rmdir` on `/config/target/usb_gadget/naa.1234567890123456`. VFS locks the `inode_lock` on `/config/target/usb_gadget`. Then `configfs_rmdir` is called, and this tries to lock `target_subsys.su_mutex`.
At the same time, CPU1 does a `mkdir` on `/config/target/usb_gadget/naa.1234567890123456/tpgt_1`. VFS locks `inode_lock` of `/config/target/usb_gadget/naa.1234567890123456`. `configfs_mkdir` locks `target_subsys.su_mutex`. Now it calls `target_fabric_make_tpg` -> `usbg_make_tpg` -> `configfs_depend_item_unlocked`. Now `configfs_depend_item_unlocked` attempts to lock `inode_lock` of `/config/usb_gadget`, which is the root of the gadget subsystem.
Wait a second. CPU0 holds the `inode_lock` of `/config/target/usb_gadget` and CPU1 attempts to lock the `inode_lock` of `/config/usb_gadget`. However, these are different inodes, so no deadlock!
But⦠What if CPU0 tried to `rmdir` on `/config/usb_gadget` instead?
CPU0 now does an `rmdir` on `/config/usb_gadget`. VFS locks the `inode_lock` of `/config`. It calls `configfs_rmdir` and this tries to lock `gadget_subsys.su_mutex`.
And CPU1 is doing the same `mkdir` as before, and it locks `target_subsys.su_mutex`. It calls the same functions which then attempt to lock the `/config/usb_gadget` `inode_lock`.
In this case, CPU0 holds `inode_lock` of *both* `/config` and `/config/usb_gadget`, where `/config` is the parent and `/config/usb_gadget` is the child.
Wait a second, VFS `rmdir` does indeed lock *both* parent and child?! I need to verify that, let's check `vfs_rmdir` again.
|
| 27/2 |
2026/07/31 03:46 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/namei.c Name:vfs_rmdir]
Results: map[SourceCode:5339: /**
5340: * vfs_rmdir - remove directory
5341: * @idmap: idmap of the mount the inode was found from
5342: * @dir: inode of the parent directory
5343: * @dentry: dentry of the child directory
5344: * @delegated_inode: returns parent inode, if it's delegated.
5345: *
5346: * Remove a directory.
5347: *
5348: * If the inode has been found through an idmapped mount the idmap of
5349: * the vfsmount must be passed through @idmap. This function will then take
5350: * care to map the inode according to @idmap before checking permissions.
5351: * On non-idmapped mounts or if permission checking is to be performed on the
5352: * raw inode simply pass @nop_mnt_idmap.
5353: */
5354: int vfs_rmdir(struct mnt_idmap *idmap, struct inode *dir,
5355: struct dentry *dentry, struct delegated_inode *delegated_inode)
5356: {
5357: int error = may_delete_dentry(idmap, dir, dentry, true);
5358:
5359: if (error)
5360: return error;
5361:
5362: if (!dir->i_op->rmdir)
5363: return -EPERM;
5364:
5365: dget(dentry);
5366: inode_lock(dentry->d_inode);
5367:
5368: error = -EBUSY;
5369: if (is_local_mountpoint(dentry) ||
5370: (dentry->d_inode->i_flags & S_KERNEL_FILE))
5371: goto out;
5372:
5373: error = security_inode_rmdir(dir, dentry);
5374: if (error)
5375: goto out;
5376:
5377: error = try_break_deleg(dir, LEASE_BREAK_DIR_DELETE, delegated_inode);
5378: if (error)
5379: goto out;
5380:
5381: error = dir->i_op->rmdir(dir, dentry);
5382: if (error)
5383: goto out;
5384:
5385: shrink_dcache_parent(dentry);
5386: dentry->d_inode->i_flags |= S_DEAD;
5387: dont_mount(dentry);
5388: detach_mounts(dentry);
5389:
5390: out:
5391: inode_unlock(dentry->d_inode);
5392: dput(dentry);
5393: if (!error)
5394: d_delete_notify(dir, dentry);
5395: return error;
5396: }
SourceFile:fs/namei.c]
|
| 28/2 |
2026/07/31 03:46 |
llm |
expert |
0m
Model:
Tokens:
input: 40365
output: 37
thoughts: 751
Thoughts: **Analyzing a Potential Deadlock in ConfigFS**
Okay, so I'm diving into this locking situation in the ConfigFS code. My initial thought is to trace the execution paths and identify potential race conditions. I see `vfs_rmdir` locks the child directory's inode, which makes sense. The parent directory is already locked by the caller, in this case, `do_rmdir`.
Let's look at this specific scenario: CPU0 is running `rmdir` on `/config/usb_gadget`. It holds the `inode_lock` on `/config` (the parent) and `/config/usb_gadget` (the child). Then, it calls `configfs_rmdir`, and that's where things get interesting because this calls `gadget_subsys.su_mutex`.
Meanwhile, CPU1 is attempting a `mkdir` on `/config/target/usb_gadget/naa.../tpgt_1`. It has the `inode_lock` for its parent directory. Then, after that, CPU1 calls `configfs_mkdir` and locks `target_subsys.su_mutex`. Then, through various functions like `target_fabric_make_tpg` and `usbg_make_tpg`, the code eventually gets to `configfs_depend_item_unlocked`. Now, here's the kicker: `configfs_depend_item_unlocked` attempts to lock the `inode_lock` of `/config/usb_gadget`, the root of the gadget subsystem.
I pause here. My mind races: CPU0 has `/config/usb_gadget`'s `inode_lock` and wants `gadget_subsys.su_mutex`. CPU1 has `target_subsys.su_mutex` (and `opts->dep_lock`) and needs the `inode_lock` of `/config/usb_gadget`. But does CPU1 have the needed lock? No, CPU1 has the `target_subsys.su_mutex`, not the one that would cause a deadlock here. So, the original scenario won't deadlock on `gadget_subsys.su_mutex`.
Okay, what if CPU2 is also in the picture, running `mkdir` on `/config/usb_gadget/g1/functions/tcm.usb0`? CPU2 has the `inode_lock` for `/config/usb_gadget/g1/functions`, then `configfs_mkdir` which takes `gadget_subsys.su_mutex`. After that, there's `function_make` -> `tcm_set_name` and then it takes `opts->dep_lock`.
Now, let's combine these three CPUs:
CPU0: `rmdir` on `/config/usb_gadget`
* Holds `inode_lock` on `/config/usb_gadget`.
* Needs to take `gadget_subsys.su_mutex`.
CPU2: `mkdir` on `/config/usb_gadget/g1/functions/tcm.usb0`
* Holds `gadget_subsys.su_mutex`.
* Needs `opts->dep_lock`.
CPU1: `mkdir` on `/config/target/usb_gadget/naa.../tpgt_1`
* Holds `opts->dep_lock`.
* Needs `inode_lock` of `/config/usb_gadget`.
Aha! This is a classic deadlock scenario. Let me verify this sequence. Can we actually `rmdir` `/config/usb_gadget`? It's the root of the gadget subsystem. Can we remove a subsystem root? Let me check `configfs_rmdir`. This is the crux of the issue.
|
| 29/2 |
2026/07/31 03:46 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/configfs/dir.c Name:configfs_rmdir]
Results: map[SourceCode:1469: static int configfs_rmdir(struct inode *dir, struct dentry *dentry)
1470: {
1471: struct config_item *parent_item;
1472: struct config_item *item;
1473: struct configfs_subsystem *subsys;
1474: struct configfs_dirent *sd;
1475: struct configfs_fragment *frag;
1476: struct module *subsys_owner = NULL, *dead_item_owner = NULL;
1477: int ret;
1478:
1479: sd = dentry->d_fsdata;
1480: if (sd->s_type & CONFIGFS_USET_DEFAULT)
1481: return -EPERM;
1482:
1483: /* Get a working ref until we have the child */
1484: parent_item = configfs_get_config_item(dentry->d_parent);
1485: subsys = to_config_group(parent_item)->cg_subsys;
1486: BUG_ON(!subsys);
1487:
1488: if (!parent_item->ci_type) {
1489: config_item_put(parent_item);
1490: return -EINVAL;
1491: }
1492:
1493: /* configfs_mkdir() shouldn't have allowed this */
1494: BUG_ON(!subsys->su_group.cg_item.ci_type);
1495: subsys_owner = subsys->su_group.cg_item.ci_type->ct_owner;
1496:
1497: /*
1498: * Ensure that no racing symlink() will make detach_prep() fail while
1499: * the new link is temporarily attached
1500: */
1501: do {
1502: struct dentry *wait;
1503:
1504: mutex_lock(&configfs_symlink_mutex);
1505: spin_lock(&configfs_dirent_lock);
1506: /*
1507: * Here's where we check for dependents. We're protected by
1508: * configfs_dirent_lock.
1509: * If no dependent, atomically tag the item as dropping.
1510: */
1511: ret = sd->s_dependent_count ? -EBUSY : 0;
1512: if (!ret) {
1513: ret = configfs_detach_prep(sd, &wait);
1514: if (ret)
1515: configfs_detach_rollback(sd);
1516: }
1517: spin_unlock(&configfs_dirent_lock);
1518: mutex_unlock(&configfs_symlink_mutex);
1519:
1520: if (ret) {
1521: if (ret != -EAGAIN) {
1522: config_item_put(parent_item);
1523: return ret;
1524: }
1525:
1526: /* Wait until the racing operation terminates */
1527: inode_lock(d_inode(wait));
1528: inode_unlock(d_inode(wait));
1529: dput(wait);
1530: }
1531: } while (ret == -EAGAIN);
1532:
1533: frag = sd->s_frag;
1534: if (down_write_killable(&frag->frag_sem)) {
1535: spin_lock(&configfs_dirent_lock);
1536: configfs_detach_rollback(sd);
1537: spin_unlock(&configfs_dirent_lock);
1538: config_item_put(parent_item);
1539: return -EINTR;
1540: }
1541: frag->frag_dead = true;
1542: up_write(&frag->frag_sem);
1543:
1544: /* Get a working ref for the duration of this function */
1545: item = configfs_get_config_item(dentry);
1546:
1547: /* Drop reference from above, item already holds one. */
1548: config_item_put(parent_item);
1549:
1550: if (item->ci_type)
1551: dead_item_owner = item->ci_type->ct_owner;
1552:
1553: if (sd->s_type & CONFIGFS_USET_DIR) {
1554: configfs_detach_group(dentry);
1555:
1556: mutex_lock(&subsys->su_mutex);
1557: client_disconnect_notify(parent_item, item);
1558: unlink_group(to_config_group(item));
1559: } else {
1560: configfs_detach_item(dentry);
1561:
1562: mutex_lock(&subsys->su_mutex);
1563: client_disconnect_notify(parent_item, item);
1564: unlink_obj(item);
1565: }
1566:
1567: client_drop_item(parent_item, item);
1568: mutex_unlock(&subsys->su_mutex);
1569:
1570: /* Drop our reference from above */
1571: config_item_put(item);
1572:
1573: module_put(dead_item_owner);
1574: module_put(subsys_owner);
1575:
1576: return 0;
1577: }
SourceFile:fs/configfs/dir.c]
|
| 30/2 |
2026/07/31 03:46 |
llm |
expert |
0m
Model:
Tokens:
input: 43597
output: 39
thoughts: 77
Thoughts: Here's my thought process, as it stands:
**Diving into ConfigFS Subsystem Registration**
Okay, first things first, I see a check for `sd->s_type & CONFIGFS_USET_DEFAULT`. That's the key. Now I need to figure out the context. Are subsystem roots being created with either `CONFIGFS_USET_DEFAULT` or `CONFIGFS_USET_DIR`? That's what I need to establish. Subsystem roots are registered using `configfs_register_subsystem`, so that seems to be the logical entry point to understand what's going on. Let's dig into that function and see what it reveals about this `CONFIGFS_USET_DEFAULT` bit and how subsystem roots are actually initialized. That's the next step; figure out how the type flag gets used during the actual registration process.
|
| 31/2 |
2026/07/31 03:46 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/configfs/dir.c Name:configfs_register_subsystem]
Results: map[SourceCode:1872: int configfs_register_subsystem(struct configfs_subsystem *subsys)
1873: {
1874: int err;
1875: struct config_group *group = &subsys->su_group;
1876: struct dentry *dentry;
1877: struct dentry *root;
1878: struct configfs_dirent *sd;
1879: struct configfs_fragment *frag;
1880:
1881: frag = new_fragment();
1882: if (!frag)
1883: return -ENOMEM;
1884:
1885: root = configfs_pin_fs();
1886: if (IS_ERR(root)) {
1887: put_fragment(frag);
1888: return PTR_ERR(root);
1889: }
1890:
1891: if (!group->cg_item.ci_name)
1892: group->cg_item.ci_name = group->cg_item.ci_namebuf;
1893:
1894: sd = root->d_fsdata;
1895: mutex_lock(&configfs_subsystem_mutex);
1896: link_group(to_config_group(sd->s_element), group);
1897: mutex_unlock(&configfs_subsystem_mutex);
1898:
1899: inode_lock_nested(d_inode(root), I_MUTEX_PARENT);
1900:
1901: err = -ENOMEM;
1902: dentry = d_alloc_name(root, group->cg_item.ci_name);
1903: if (dentry) {
1904: d_add(dentry, NULL);
1905:
1906: err = configfs_dirent_exists(dentry);
1907: if (!err)
1908: err = configfs_attach_group(&group->cg_item,
1909: dentry, frag);
1910: if (err) {
1911: BUG_ON(d_inode(dentry));
1912: d_drop(dentry);
1913: } else {
1914: spin_lock(&configfs_dirent_lock);
1915: configfs_dir_set_ready(dentry->d_fsdata);
1916: spin_unlock(&configfs_dirent_lock);
1917: }
1918: dput(dentry);
1919: }
1920:
1921: inode_unlock(d_inode(root));
1922:
1923: if (err) {
1924: mutex_lock(&configfs_subsystem_mutex);
1925: unlink_group(group);
1926: mutex_unlock(&configfs_subsystem_mutex);
1927: configfs_release_fs();
1928: }
1929: put_fragment(frag);
1930:
1931: return err;
1932: }
SourceFile:fs/configfs/dir.c]
|
| 32/2 |
2026/07/31 03:46 |
llm |
expert |
0m
Model:
Tokens:
input: 44795
output: 38
thoughts: 119
Thoughts: **Investigating Subsystem Root Removal in ConfigFS**
Okay, so I'm trying to figure out how these subsystem roots are managed in ConfigFS. I know they're registered using `configfs_register_subsystem` - that much is clear. But it got me thinking, are they created like regular directories with `mkdir`? Because if not, the implications for removal are significant. I immediately thought about using `rmdir` to remove them, but I recall that `configfs_rmdir` only handles items *created* with `mkdir`.
Now, a red flag popped up. Let me recall how this works. I remember that `configfs_rmdir` includes a check for `sd->s_type & CONFIGFS_USET_DEFAULT`. That feels relevant, but it's not the whole story. The key is in how these subsystem roots are *created* in the first place. Are they designated as `CONFIGFS_USET_DIR` by `configfs_attach_group`? That seems like the likely candidate for handling it, as opposed to `mkdir`, so let me pull up `configfs_attach_group` and see what it does.
|
| 33/2 |
2026/07/31 03:46 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/configfs/dir.c Name:configfs_attach_group]
Results: map[SourceCode: 891: static int configfs_attach_group(struct config_item *item,
892: struct dentry *dentry,
893: struct configfs_fragment *frag)
894: {
895: int ret;
896: struct configfs_dirent *sd;
897:
898: ret = configfs_attach_item(item, dentry, frag);
899: if (!ret) {
900: sd = dentry->d_fsdata;
901: sd->s_type |= CONFIGFS_USET_DIR;
902:
903: /*
904: * FYI, we're faking mkdir in populate_groups()
905: * We must lock the group's inode to avoid races with the VFS
906: * which can already hit the inode and try to add/remove entries
907: * under it.
908: *
909: * We must also lock the inode to remove it safely in case of
910: * error, as rmdir() would.
911: */
912: inode_lock_nested(d_inode(dentry), I_MUTEX_CHILD);
913: configfs_adjust_dir_dirent_depth_before_populate(sd);
914: ret = populate_groups(to_config_group(item), frag);
915: if (ret) {
916: configfs_detach_group(dentry);
917: d_inode(dentry)->i_flags |= S_DEAD;
918: dont_mount(dentry);
919: }
920: configfs_adjust_dir_dirent_depth_after_populate(sd);
921: inode_unlock(d_inode(dentry));
922: if (ret)
923: d_delete(dentry);
924: }
925:
926: return ret;
927: }
SourceFile:fs/configfs/dir.c]
|
| 34/2 |
2026/07/31 03:46 |
llm |
expert |
0m
Model:
Tokens:
input: 45794
output: 40
thoughts: 241
Thoughts: **Analyzing the ConfigFS Removal Process**
Okay, so I'm diving into this `configfs_rmdir` function, and something's bugging me. It looks like it checks `sd->s_type & CONFIGFS_USET_DEFAULT` and returns `-EPERM` if it's a default group. My initial thought is, well, subsystem roots aren't *default* groups, are they? They're special cases, not created through a standard `mkdir`.
So, the next logical question is: can you even `rmdir` a subsystem root? Let's trace it. If I try to `rmdir /config/usb_gadget`, `vfs_rmdir` will call `configfs_rmdir`, right? `configfs_rmdir` then goes and does its thing: `parent_item = configfs_get_config_item(dentry->d_parent)`.
For that specific path, `/config/usb_gadget`, `dentry->d_parent` is just `/config`. Okay, so `parent_item` will be the configfs root item itself. Then, it hits the line, `subsys = to_config_group(parent_item)->cg_subsys;`. Here's the potential problem: the configfs root item DOES NOT have a `cg_subsys` member, does it? It's not a config group in the same way a subsystem root is. Hmmm, gotta check `configfs_get_config_item` to see exactly what it does with the root. Something's going to blow up here.
|
| 35/2 |
2026/07/31 03:46 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/configfs/dir.c Name:configfs_get_config_item]
Results: map[SourceCode: 120: static inline struct config_item *configfs_get_config_item(struct dentry *dentry)
121: {
122: struct config_item * item = NULL;
123:
124: spin_lock(&dentry->d_lock);
125: if (!d_unhashed(dentry)) {
126: struct configfs_dirent * sd = dentry->d_fsdata;
127: item = config_item_get(sd->s_element);
128: }
129: spin_unlock(&dentry->d_lock);
130:
131: return item;
132: }
SourceFile:fs/configfs/configfs_internal.h]
|
| 36/2 |
2026/07/31 03:46 |
llm |
expert |
0m
Model:
Tokens:
input: 46652
output: 40
thoughts: 258
Thoughts: **My Configfs Conundrum**
Okay, so I'm diving into this `configfs_rmdir` function, and something's not sitting right. Looking at the code:
```c
parent_item = configfs_get_config_item(dentry->d_parent);
subsys = to_config_group(parent_item)->cg_subsys;
BUG_ON(!subsys);
```
My brain immediately jumps to the edge case. If you try to `rmdir` a subsystem's root, `dentry->d_parent` is going to be `/config`. And that's where the problem arises: `/config`'s `s_element` is actually `configfs_root_group.cg_item`. And the thing about that, is that `configfs_root_group.cg_subsys` is set to NULL! This would trigger the `BUG_ON(!subsys)`! That seems like a serious flaw. But hold on, `vfs_rmdir` has that `may_delete_dentry` check, right? So, is something like `/config/usb_gadget` even *deletable*?
I need to back up and look at the registration process. Let's dig into `configfs_register_subsystem`. Ah, here it is: the system calls `d_alloc_name(root, group->cg_item.ci_name);`. But wait, it doesn't appear to be setting `dentry->d_inode->i_op->rmdir`! That seems like a major oversight.
Right, then I remember the `configfs_dir_inode_operations` is being used for *all* directories in configfs, not just the root, which would mean that this should be handled properly, right? I need to look into `may_delete_dentry` to see how it's handled. Maybe it calls some function specific to the subsystem that allows deletion.
|
| 37/2 |
2026/07/31 03:46 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/configfs/dir.c Name:configfs_dir_inode_operations]
Results: map[SourceCode:1579: const struct inode_operations configfs_dir_inode_operations = {
1580: .mkdir = configfs_mkdir,
1581: .rmdir = configfs_rmdir,
1582: .symlink = configfs_symlink,
1583: .unlink = configfs_unlink,
1584: .lookup = configfs_lookup,
1585: .setattr = configfs_setattr,
1586: };
SourceFile:fs/configfs/dir.c]
|
| 38/2 |
2026/07/31 03:46 |
llm |
expert |
0m
Model:
Tokens:
input: 47544
output: 38
thoughts: 374
Thoughts: **Unraveling a Potential ConfigFS Vulnerability**
Okay, so I'm looking at `/config` and specifically `configfs_rmdir`. If someone tries to remove a directory, like `/config/usb_gadget`, `configfs_rmdir` gets called. But wait, `/config/usb_gadget` is a *subsystem root*, which immediately makes me think about `CONFIGFS_USET_DEFAULT`. Let's dig into `configfs_register_subsystem`. I see it calls `configfs_attach_group`, which then calls `configfs_attach_item`. Right, `configfs_attach_item` sets `sd->s_type = CONFIGFS_USET_DIR`.
Hang on, `configfs_register_subsystem` links the group to `configfs_root_group`. The `configfs_root_group` doesn't have a `cg_subsys`. Therefore `BUG_ON(!subsys)` would trigger if someone tries to `rmdir` a subsystem root, which seems wrong!
But can they actually *rmdir* a subsystem root? Let's go back and examine `configfs_rmdir`.
```c
sd = dentry->d_fsdata;
if (sd->s_type & CONFIGFS_USET_DEFAULT)
return -EPERM;
```
Ah, here's the crucial bit. But, `configfs_register_subsystem` *already* sets `sd->s_type = CONFIGFS_USET_DIR`! But, wait, that's because `configfs_register_subsystem` calls `configfs_attach_group`, which calls `configfs_attach_item` andβ¦
`configfs_attach_item` does this:
```c
sd->s_type = type(item);
```
So what is `type(item)`? This deserves a closer look. Something is clearly not right, and I'm not sure yet if this is a bug or a missing protection.
|
| 39/2 |
2026/07/31 03:46 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/configfs/dir.c Name:configfs_attach_item]
Results: map[SourceCode: 842: /*
843: * The goal is that configfs_attach_item() (and
844: * configfs_attach_group()) can be called from either the VFS or this
845: * module. That is, they assume that the items have been created,
846: * the dentry allocated, and the dcache is all ready to go.
847: *
848: * If they fail, they must clean up after themselves as if they
849: * had never been called. The caller (VFS or local function) will
850: * handle cleaning up the dcache bits.
851: *
852: * configfs_detach_group() and configfs_detach_item() behave similarly on
853: * the way out. They assume that the proper semaphores are held, they
854: * clean up the configfs items, and they expect their callers will
855: * handle the dcache bits.
856: */
857: static int configfs_attach_item(struct config_item *item,
858: struct dentry *dentry,
859: struct configfs_fragment *frag)
860: {
861: int ret;
862:
863: ret = configfs_create_dir(item, dentry, frag);
864: if (!ret) {
865: ret = populate_attrs(item);
866: if (ret) {
867: /*
868: * We are going to remove an inode and its dentry but
869: * the VFS may already have hit and used them. Thus,
870: * we must lock them as rmdir() would.
871: */
872: inode_lock(d_inode(dentry));
873: configfs_detach_item(dentry);
874: d_inode(dentry)->i_flags |= S_DEAD;
875: dont_mount(dentry);
876: inode_unlock(d_inode(dentry));
877: d_delete(dentry);
878: }
879: }
880:
881: return ret;
882: }
SourceFile:fs/configfs/dir.c]
|
| 40/2 |
2026/07/31 03:46 |
llm |
expert |
0m
Model:
Tokens:
input: 49065
output: 38
thoughts: 18
Thoughts: Here's my understanding of the thought process, summarized as you requested:
**Navigating the `configfs_create_dir` Function**
Okay, so I'm focusing on the `configfs_create_dir` function. The key observation here is that this function is responsible for setting the `sd->s_type` value. This is important because `s_type` defines the filesystem type associated with the superblock, so this function is intimately involved with how configfs directories are represented in the kernel's file system structure. This means the mechanics of directory creation are directly intertwined with how the configfs type is established. I need to keep this relationship in mind as I delve deeper into the implementation.
|
| 41/2 |
2026/07/31 03:47 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/configfs/dir.c Name:configfs_create_dir]
Results: map[SourceCode: 293: static int configfs_create_dir(struct config_item *item, struct dentry *dentry,
294: struct configfs_fragment *frag)
295: {
296: int error;
297: umode_t mode = S_IFDIR| S_IRWXU | S_IRUGO | S_IXUGO;
298: struct dentry *p = dentry->d_parent;
299: struct inode *p_inode = d_inode(p);
300: struct inode *inode;
301:
302: BUG_ON(!item);
303:
304: error = configfs_make_dirent(p->d_fsdata, dentry, item, mode,
305: CONFIGFS_DIR | CONFIGFS_USET_CREATING,
306: frag);
307: if (unlikely(error))
308: return error;
309:
310: configfs_set_dir_dirent_depth(p->d_fsdata, dentry->d_fsdata);
311: inode = configfs_create(dentry, mode);
312: if (IS_ERR(inode))
313: goto out_remove;
314:
315: inode->i_op = &configfs_dir_inode_operations;
316: inode->i_fop = &configfs_dir_operations;
317: /* directory inodes start off with i_nlink == 2 (for "." entry) */
318: inc_nlink(inode);
319: d_make_persistent(dentry, inode);
320: inc_nlink(p_inode);
321: inode_set_mtime_to_ts(p_inode, inode_set_ctime_current(p_inode));
322: item->ci_dentry = dentry;
323: return 0;
324:
325: out_remove:
326: configfs_put(dentry->d_fsdata);
327: configfs_remove_dirent(dentry);
328: return PTR_ERR(inode);
329: }
SourceFile:fs/configfs/dir.c]
|
| 42/2 |
2026/07/31 03:47 |
llm |
expert |
0m
Model:
Tokens:
input: 49918
output: 38
thoughts: 203
Thoughts: **Investigating the Configuration File System (configfs) Subsystem Root Removal**
Okay, let me break this down. From what I'm seeing, the subsystem root in `configfs` is initialized with `CONFIGFS_DIR | CONFIGFS_USET_CREATING`. Then, when `configfs_attach_group` runs, it adds `CONFIGFS_USET_DIR`. So, the `s_type` value for the subsystem root ends up as `CONFIGFS_DIR | CONFIGFS_USET_DIR`. Crucially, it **doesn't** have `CONFIGFS_USET_DEFAULT` set.
Now, here's where things get interesting. If I try to remove this subsystem root using `rmdir`, the `configfs_rmdir` function is going to be invoked. Inside, it grabs the parent's config item and attempts to access its `cg_subsys` member:
```c
parent_item = configfs_get_config_item(dentry->d_parent);
subsys = to_config_group(parent_item)->cg_subsys;
BUG_ON(!subsys);
```
And it's this line that's bothering me: `BUG_ON(!subsys)`. It suggests that the removal will trigger a kernel BUG. But I am almost certain this is not the behaviour I've observed in the past.
Wait... Am I missing something? Does `configfs_root_group` actually have a `cg_subsys`? I need to double-check my understanding of how `configfs` structures are set up, and I should really look at `configfs_root_group` to confirm its internal structure to be certain. I am going to need to look at the source code to be sure about this.
|
| 43/2 |
2026/07/31 03:47 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/configfs/mount.c Name:configfs_root_group]
Results: map[SourceCode: 43: static struct config_group configfs_root_group = {
44: .cg_item = {
45: .ci_namebuf = "root",
46: .ci_name = configfs_root_group.cg_item.ci_namebuf,
47: },
48: };
SourceFile:fs/configfs/mount.c]
|
| 44/2 |
2026/07/31 03:47 |
llm |
expert |
0m
Model:
Tokens:
input: 50669
output: 40
thoughts: 538
Thoughts: **Analyzing the Vulnerability in ConfigFS's rmdir Handling**
Okay, so `cg_subsys` is definitely NULL, which means if `rmdir` is somehow called on a subsystem root, we're hitting that `BUG_ON`! The crux of the issue is: *can* you `rmdir` a subsystem root? My initial thought is it seems unlikely, but we need to verify.
Let's trace the logic. `vfs_rmdir` calls `may_delete_dentry`. Fine. But subsystem roots are created by `configfs_register_subsystem`, not the usual `mkdir` path. Can user space trigger this `rmdir`? If they were just standard directories within configfs, then sure, the user could call it, but are they?
Looking at `configfs_rmdir`, I see this check: `sd->s_type & CONFIGFS_USET_DEFAULT`, which would return -EPERM. Good, but subsystem roots shouldn't have `CONFIGFS_USET_DEFAULT`. But they *do* have `CONFIGFS_USET_DIR`. So, that's not our gatekeeper.
Wait, `configfs_register_subsystem` calls `configfs_attach_group`, which then calls `populate_groups`. But is `CONFIGFS_USET_DEFAULT` set anywhere in that flow? No. Okay, back to `configfs_register_subsystem`. It calls `link_group`, which then calls `configfs_attach_group`. So, is there any check within `configfs_rmdir` that would prevent a subsystem root from being removed?
Right, `configfs_rmdir` checks `sd->s_type & CONFIGFS_USET_DEFAULT`... and subsystem roots are *not* default groups, so this doesn't protect us. Aha, but then the crucial point is here:
```c
/* configfs_mkdir() shouldn't have allowed this */
BUG_ON(!subsys->su_group.cg_item.ci_type);
```
This *strongly* implies that `configfs_rmdir` is only intended to be called on items created by `configfs_mkdir`. If we can bypass that assumption and get `rmdir` called on a subsystem root, we crash the kernel!
So the question becomes: is there a way to trick the system into calling `configfs_rmdir` on a subsystem root? Let's go back and look at the control flow within `configfs_rmdir` and the `configfs_dir_inode_operations`. I see that the `configfs_rmdir` is the `.rmdir` operation. The root directory of configfs, which is `/config` has `configfs_root_inode_operations`. That must be relevant! We'll need to keep investigating to see if it is possible to bypass the constraints.
|
| 45/2 |
2026/07/31 03:47 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/configfs/dir.c Name:configfs_root_inode_operations]
Results: map[SourceCode:1588: const struct inode_operations configfs_root_inode_operations = {
1589: .lookup = configfs_lookup,
1590: .setattr = configfs_setattr,
1591: };
SourceFile:fs/configfs/dir.c]
|
| 46/2 |
2026/07/31 03:47 |
llm |
expert |
0m
Model:
Tokens:
input: 51970
output: 46
thoughts: 210
Thoughts: **Understanding ConfigFS and Directory Removal**
Okay, so it looks like `configfs_root_inode_operations` *doesn't* define a `.rmdir` operation. Got it! That means you absolutely cannot directly use `rmdir` on anything residing immediately under the `/config` directory. The `vfs_rmdir` function, the kernel's virtual filesystem rmdir implementation, meticulously checks the directory's inode operations (`dir->i_op->rmdir`). Since the inode operations for `/config` itself are missing the `.rmdir` function, `vfs_rmdir` immediately returns `-EPERM`, a "permission denied" error. Excellent! That confirms we *can't* accidentally remove a subsystem's root directory that way, which is exactly what we want. Phew! The important thing is that CPU0 can't simply `rmdir` the `/config/usb_gadget` directly.
Now, what about the case where CPU0 tries to execute `rmdir` on a directory *within* a subsystem's root, like `/config/target/usb_gadget`? Hold on. `/config/target` itself is a subsystem root, right? And `/config/target/usb_gadget` is a directory inside the `target` subsystem? So, can that be removed? Time to check the source code for `target_core_fabric_configfs.c`. Let's see how the `target` subsystem handles directory removal within its domain.
|
| 47/2 |
2026/07/31 03:47 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/target/target_core_fabric_configfs.c Name:target_fabric_make_wwn]
Results: map[SourceCode:1125: static struct config_group *target_fabric_make_wwn(
1126: struct config_group *group,
1127: const char *name)
1128: {
1129: struct target_fabric_configfs *tf = container_of(group,
1130: struct target_fabric_configfs, tf_group);
1131: struct se_wwn *wwn;
1132:
1133: if (!tf->tf_ops->fabric_make_wwn) {
1134: pr_err("tf->tf_ops.fabric_make_wwn is NULL\n");
1135: return ERR_PTR(-ENOSYS);
1136: }
1137:
1138: wwn = tf->tf_ops->fabric_make_wwn(tf, group, name);
1139: if (!wwn || IS_ERR(wwn))
1140: return ERR_PTR(-EINVAL);
1141:
1142: wwn->cmd_compl_affinity = SE_COMPL_AFFINITY_CPUID;
1143: wwn->wwn_tf = tf;
1144:
1145: config_group_init_type_name(&wwn->wwn_group, name, &tf->tf_tpg_cit);
1146:
1147: config_group_init_type_name(&wwn->fabric_stat_group, "fabric_statistics",
1148: &tf->tf_wwn_fabric_stats_cit);
1149: configfs_add_default_group(&wwn->fabric_stat_group, &wwn->wwn_group);
1150:
1151: config_group_init_type_name(&wwn->param_group, "param",
1152: &tf->tf_wwn_param_cit);
1153: configfs_add_default_group(&wwn->param_group, &wwn->wwn_group);
1154:
1155: if (tf->tf_ops->add_wwn_groups)
1156: tf->tf_ops->add_wwn_groups(wwn);
1157: return &wwn->wwn_group;
1158: }
SourceFile:drivers/target/target_core_fabric_configfs.c]
|
| 48/2 |
2026/07/31 03:47 |
llm |
expert |
0m
Model:
Tokens:
input: 53200
output: 46
thoughts: 56
Thoughts: Okay, let me think this through.
**Investigating the USB Gadget Configuration**
Alright, so `/config/target/usb_gadget` β that's a `target_fabric_configfs` group. My first thought is: was that directory created explicitly with a `mkdir` command, or is it a default group that gets instantiated somehow during the boot process or by some other system initialization? I'm going to have to check how the `target_fabric_configfs` fabric is actually registered within the system to figure this out. I need to trace back how that group gets defined, otherwise, I won't be able to determine the proper method to manipulate this directory. Time to dig into the relevant code.
|
| 49/2 |
2026/07/31 03:48 |
tool |
codesearch-definition-source |
0m
Error:
Args: map[ContextFile:drivers/target/target_core_fabric_configfs.c Name:target_fabric_configfs_register]
Results: map[SourceCode: SourceFile:]
|
| 50/2 |
2026/07/31 03:48 |
llm |
expert |
0m
Model:
Tokens:
input: 53476
output: 45
thoughts: 13
Thoughts: Okay, here's my interpretation of that thought process, tailored for an expert audience, and written in the first person:
**Analyzing the `target_core_register_fabric`**
Alright, let's dive into this. The mental checklist here starts with a direct focus on `target_core_register_fabric`. I'm immediately homing in on this specific element β likely within a larger system or design I'm working with. The context is implied: I'm in the process of debugging, optimizing, verifying, or perhaps even designing a hardware or software system. There's an expectation that this fabric is critical and needs a close look. My expertise allows me to zero in, rapidly formulating questions that are now forming in the back of my mind. What specifically is meant by βfabricβ? Is this a structural element, a control system, or something else entirely? Depending on the definition of the term, my line of questioning would begin to branch out. Is this fabric behaving as expected based on its expected architectural principles? What are the potential failure points associated with it? Are there any potential for data hazards? Are the registers correctly allocated and protected? Are there performance bottlenecks I can detect by analyzing this element? Essentially, this is my internal cue to start the diagnostic process. I'm ready to investigate and understand how that particular fabric is functioning.
|
| 51/2 |
2026/07/31 03:48 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/target/target_core_fabric_configfs.c Name:target_core_register_fabric]
Results: map[SourceCode: 179: /*
180: * Called from struct target_core_group_ops->make_group()
181: */
182: static struct config_group *target_core_register_fabric(
183: struct config_group *group,
184: const char *name)
185: {
186: struct target_fabric_configfs *tf;
187: int ret;
188:
189: pr_debug("Target_Core_ConfigFS: REGISTER -> group: %p name:"
190: " %s\n", group, name);
191:
192: tf = target_core_get_fabric(name);
193: if (!tf) {
194: pr_debug("target_core_register_fabric() trying autoload for %s\n",
195: name);
196:
197: /*
198: * Below are some hardcoded request_module() calls to automatically
199: * local fabric modules when the following is called:
200: *
201: * mkdir -p /sys/kernel/config/target/$MODULE_NAME
202: *
203: * Note that this does not limit which TCM fabric module can be
204: * registered, but simply provids auto loading logic for modules with
205: * mkdir(2) system calls with known TCM fabric modules.
206: */
207:
208: if (!strncmp(name, "iscsi", 5)) {
209: /*
210: * Automatically load the LIO Target fabric module when the
211: * following is called:
212: *
213: * mkdir -p $CONFIGFS/target/iscsi
214: */
215: ret = request_module("iscsi_target_mod");
216: if (ret < 0) {
217: pr_debug("request_module() failed for"
218: " iscsi_target_mod.ko: %d\n", ret);
219: return ERR_PTR(-EINVAL);
220: }
221: } else if (!strncmp(name, "loopback", 8)) {
222: /*
223: * Automatically load the tcm_loop fabric module when the
224: * following is called:
225: *
226: * mkdir -p $CONFIGFS/target/loopback
227: */
228: ret = request_module("tcm_loop");
229: if (ret < 0) {
230: pr_debug("request_module() failed for"
231: " tcm_loop.ko: %d\n", ret);
232: return ERR_PTR(-EINVAL);
233: }
234: }
235:
236: tf = target_core_get_fabric(name);
237: }
238:
239: if (!tf) {
240: pr_debug("target_core_get_fabric() failed for %s\n",
241: name);
242: return ERR_PTR(-EINVAL);
243: }
244: pr_debug("Target_Core_ConfigFS: REGISTER -> Located fabric:"
245: " %s\n", tf->tf_ops->fabric_name);
246: /*
247: * On a successful target_core_get_fabric() look, the returned
248: * struct target_fabric_configfs *tf will contain a usage reference.
249: */
250: pr_debug("Target_Core_ConfigFS: REGISTER tfc_wwn_cit -> %p\n",
251: &tf->tf_wwn_cit);
252:
253: config_group_init_type_name(&tf->tf_group, name, &tf->tf_wwn_cit);
254:
255: config_group_init_type_name(&tf->tf_disc_group, "discovery_auth",
256: &tf->tf_discovery_cit);
257: configfs_add_default_group(&tf->tf_disc_group, &tf->tf_group);
258:
259: pr_debug("Target_Core_ConfigFS: REGISTER -> Allocated Fabric: %s\n",
260: config_item_name(&tf->tf_group.cg_item));
261: return &tf->tf_group;
262: }
SourceFile:drivers/target/target_core_configfs.c]
|
| 52/2 |
2026/07/31 03:48 |
llm |
expert |
0m
Model:
Tokens:
input: 55139
output: 44
thoughts: 97
Thoughts: Here's my thought process, as it just played out:
**Analyzing USB Gadget Configuration**
Okay, so I'm looking at this `/config/target/usb_gadget` directory. It looks like it's created using `mkdir`. Interesting. And that `target_core_register_fabric` function is definitely called within the context of `target_core_group_ops->make_group()`. Right, that means the `/config/target/usb_gadget` directory is definitely a child of `/config/target`, created by `mkdir` *on* `/config/target`. It follows from that, that it *should* be removable using `rmdir`. That's an important detail. Now, I need to understand the counterpart function, so I'll check `target_core_deregister_fabric`. That's where the cleanup would happen.
|
| 53/2 |
2026/07/31 03:48 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/target/target_core_configfs.c Name:target_core_deregister_fabric]
Results: map[SourceCode: 264: /*
265: * Called from struct target_core_group_ops->drop_item()
266: */
267: static void target_core_deregister_fabric(
268: struct config_group *group,
269: struct config_item *item)
270: {
271: struct target_fabric_configfs *tf = container_of(
272: to_config_group(item), struct target_fabric_configfs, tf_group);
273:
274: pr_debug("Target_Core_ConfigFS: DEREGISTER -> Looking up %s in"
275: " tf list\n", config_item_name(item));
276:
277: pr_debug("Target_Core_ConfigFS: DEREGISTER -> located fabric:"
278: " %s\n", tf->tf_ops->fabric_name);
279: atomic_dec(&tf->tf_access_cnt);
280:
281: pr_debug("Target_Core_ConfigFS: DEREGISTER -> Releasing ci"
282: " %s\n", config_item_name(item));
283:
284: configfs_remove_default_groups(&tf->tf_group);
285: config_item_put(item);
286: }
SourceFile:drivers/target/target_core_configfs.c]
|
| 54/2 |
2026/07/31 03:48 |
llm |
expert |
0m
Model:
Tokens:
input: 55884
output: 38
thoughts: 3085
Thoughts: **Analysis of a Potential ConfigFS Deadlock**
Alright, let's break this down. First, I was examining the potential for a deadlock within the configfs subsystem, specifically related to USB gadget and target configurations. My initial thought was, "Can this actually happen?"
I started with a hypothetical scenario: `rmdir` on `/config/target/usb_gadget`. Simple enough, `rmdir` would acquire locks on the parent directory (`/config/target`) and the target directory (`/config/target/usb_gadget`)'s inodes and then call `configfs_rmdir`. This would then acquire a subsystem mutex. I had to mentally model the lock ordering. I started tracing the locks: inode lock of the target directory -> the subsystem mutex.
Then I shifted to a concurrent `mkdir` scenario, which is the crux of the problem. This involved creating a new target portal group (tpg) under `/config/target/usb_gadget`. This operation seemed to acquire a similar set of locks, and called through a different function, but ultimately also ended up taking the target subsystem mutex. So, `configfs_mkdir` would lock a different inode, but then eventually hit the same subsystem mutex. The call graph for these two processes were starting to feel similar, so I began to feel there might be something here.
I then considered another thread `rmdir`ing from a totally different directory: `/config/usb_gadget/g1/functions/tcm.usb0`. It looked like it would need to lock another subsystem mutex in order to proceed. In a similar vein, a concurrent `mkdir` on the same directory would also lock the same subsystem mutex, but then would need another lock `opts->dep_lock`. That was new, but it pointed to some deeper relationships.
It was here that I started to analyze the potential for a circular dependency, for which I started to track the lock order: `inode_lock` -> Subsystem Mutex -> `opts->dep_lock` -> `inode_lock`. I went through a quick mental check. I was trying to determine if they could even be the same lock. I realized the `inode_lock`s I was looking at were different directories (therefore, they are different inodes!), and hence, no deadlock. However, Lockdep was reporting a possible conflict, which was clearly a false positive. Still, I wasn't convinced.
I then thought, can a *single* thread cause a deadlock? Can a thread *ever* hold a directory in the `/config/usb_gadget` and then try to take the gadget subsystem mutex? The root directory of the subsystem cannot be removed; therefore, a thread can never hold the inode lock for `/config/usb_gadget` and then block on a subsystem mutex.
But then, it clicked. What if we `rmdir` `/config/usb_gadget/g1` instead? This operation locks the *parent* directory `/config/usb_gadget`. *That's* the key. I had to mentally go through `vfs_rmdir` to trace through the locks; first the parent, then the child. And it then would proceed to block on the subsystem mutex.
I went back and traced out the full locking scenarios:
* Thread 0 (rmdir): `inode_lock(/config/usb_gadget)` -> `inode_lock(/config/usb_gadget/g1)` -> `gadget_subsys.su_mutex`.
* Thread 1 (mkdir tpg): `inode_lock(/config/target/usb_gadget/naa...)` -> `target_subsys.su_mutex` -> `opts->dep_lock` -> `inode_lock(/config/usb_gadget)`.
* Thread 2 (mkdir tcm): `inode_lock(/config/usb_gadget/g1/functions)` -> `gadget_subsys.su_mutex` -> `opts->dep_lock`.
Eureka! A three-thread deadlock! Thread 0 holds `/config/usb_gadget`, waiting for the subsystem mutex. Thread 2 holds the subsystem mutex, waiting on `opts->dep_lock`. Thread 1 holds `opts->dep_lock`, and is waiting for `/config/usb_gadget`. A perfect cycle.
Now, could the threads all contend for the same lock? Yes, the `opts->dep_lock` is the same, and is created by the `tcm.usb0` instantiation. The TPG also uses the same `f_tcm_opts`. It's a race to `opts->dep_lock`.
I mentally ran through it. Thread 0 grabs `/config/usb_gadget` (and blocks) while Thread 2 grabs `gadget_subsys.
|
| 55/2 |
2026/07/31 03:49 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/configfs/mount.c Name:configfs_fs_type]
Results: map[SourceCode: 115: static struct file_system_type configfs_fs_type = {
116: .owner = THIS_MODULE,
117: .name = "configfs",
118: .init_fs_context = configfs_init_fs_context,
119: .kill_sb = kill_anon_super,
120: };
SourceFile:fs/configfs/mount.c]
|
| 56/2 |
2026/07/31 03:49 |
llm |
expert |
0m
Model:
Tokens:
input: 60165
output: 32
thoughts: 307
Thoughts: **Analyzing the Configfs Mounting Scenario**
Okay, so the initial check reveals that `configfs` *lacks* `FS_USERNS_MOUNT`. That immediately tells me I *cannot* mount `configfs` within a user namespace. This is crucial. Now, let's look at the reproducer code. The call uses a direct syscall to mount, with all the usual arguments. If `configfs` indeed can't be mounted in a user namespace, then this reproducer MUST be running as root, *inside* the initial namespace.
Hmm, this presents a problem. Syzkaller, which this seems to be, typically operates as root *within* a user namespace for its testing. But if it needs to mount `configfs`, and given the lack of `FS_USERNS_MOUNT`, that mount should be failing with an `-EPERM` error. Unless...syzkaller is running as root *in the initial namespace* for this specific test case.
The bug report provides further clues: "CPU: 0 UID: 0 PID: 4919 Comm: syz.0.18 Not tainted syzkaller #0 PREEMPT". UID is *0*, which means root. That confirms that assumption. If `configfs` is not mountable in user namespaces, then this bug is only reachable by root (specifically, with CAP_SYS_ADMIN in `init_user_ns`).
Just to be absolutely certain, I should double-check the kernel source in another place to make sure that the `FS_USERNS_MOUNT` flag isn't set for `configfs` somewhere else.
|
| 57/2 |
2026/07/31 03:49 |
tool |
grepper |
0m
Error:
Args: map[Expression:FS_USERNS_MOUNT PathPrefix:fs/configfs/]
Results:
|
| 58/2 |
2026/07/31 03:49 |
llm |
expert |
0m
Model:
Tokens:
input: 60866
output: 37
thoughts: 2589
Thoughts: **My Analysis of a Potential Deadlock in configfs**
Alright, let's break this down. First, can an unprivileged user even *get* to `configfs` to exploit anything? Nope, it can't be mounted in a user namespace. But what if it's already mounted at `/sys/kernel/config`? Okay, let's look at permissions. It's usually `drwxr-xr-x root root`, but kernel modules create the directories within. Those are created with `0755` permissions, so read and execute are allowed, but not creation. Since only root can create directories inside the mounted `configfs` instance, unprivileged users are shut out. Therefore, no unprivileged access, no user namespace access. This bug is not a privilege escalation.
Now, let's explore triggers. USB peripheral? No. Configfs configures the USB gadget *role*, not the host. Network trigger? Nope. VM guest or host? Not applicable either.
Now, I need to confirm the deadlock and I found a 3-thread scenario:
* CPU0: `rmdir /config/usb_gadget/g1`
* CPU1: `mkdir /config/target/usb_gadget/naa.../tpgt_1`
* CPU2: `mkdir /config/usb_gadget/g1/functions/tcm.usb0`
The lockdep report highlights a potential issue with `symlink`, but that might be a false positive. Let's dig in. The report shows:
* CPU0: `symlink` on `/config/usb_gadget/g1/functions/uvc.usb0/control/extensions/ext.0/str`
* CPU2: `mkdir /config/usb_gadget/g1/functions/tcm.usb0`
* CPU1: `mkdir /config/target/usb_gadget/naa.../tpgt_1`
However, the report indicates there is a blocking scenario, but in this specific `symlink` scenario, the inodes are different. This means there is NO deadlock in this `symlink` situation, but lockdep can report the issue due to how it treats locks.
But, back to my `rmdir` scenario! That's different. In my scenario, CPU0 holds `inode_lock(/config/usb_gadget)` while CPU1 tries to grab it, *and CPU1 also holds* a lock for `/config/usb_gadget`. That's a real deadlock.
Specifically:
* CPU0 calls `do_rmdir` to lock the parent directory which is `/config/usb_gadget`.
* CPU1 calls `configfs_depend_item_unlocked` which locks `/config/usb_gadget`.
The deadlock then occurs:
* CPU0: Holds `inode_lock(/config/usb_gadget)` and waits for `gadget_subsys.su_mutex`
* CPU2: Holds `gadget_subsys.su_mutex` and waits for `opts->dep_lock`
* CPU1: Holds `opts->dep_lock` and waits for `inode_lock(/config/usb_gadget)`
Now, can CPU2 run concurrently with CPU0? CPU2 is doing `mkdir /config/usb_gadget/g1/functions/tcm.usb0`. VFS path resolution for this will traverse `/config/usb_gadget/g1`, but RCU is usually lockless, so CPU2 doesn't need to hold any lock on `/config/usb_gadget/g1` and can hold its own `inode_lock(/config/usb_gadget/g1/functions)` while CPU0 holds `inode_lock(/config/usb_gadget/g1)`.
So, the deadlock is very real. Even though the lockdep report might be a false positive in *some* cases, the underlying lock class cycle is a real deadlock if triggered by `rmdir` (which it can be).
|
| 59/2 |
2026/07/31 03:50 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/configfs/dir.c Name:configfs_rmdir]
Results: map[SourceCode:1469: static int configfs_rmdir(struct inode *dir, struct dentry *dentry)
1470: {
1471: struct config_item *parent_item;
1472: struct config_item *item;
1473: struct configfs_subsystem *subsys;
1474: struct configfs_dirent *sd;
1475: struct configfs_fragment *frag;
1476: struct module *subsys_owner = NULL, *dead_item_owner = NULL;
1477: int ret;
1478:
1479: sd = dentry->d_fsdata;
1480: if (sd->s_type & CONFIGFS_USET_DEFAULT)
1481: return -EPERM;
1482:
1483: /* Get a working ref until we have the child */
1484: parent_item = configfs_get_config_item(dentry->d_parent);
1485: subsys = to_config_group(parent_item)->cg_subsys;
1486: BUG_ON(!subsys);
1487:
1488: if (!parent_item->ci_type) {
1489: config_item_put(parent_item);
1490: return -EINVAL;
1491: }
1492:
1493: /* configfs_mkdir() shouldn't have allowed this */
1494: BUG_ON(!subsys->su_group.cg_item.ci_type);
1495: subsys_owner = subsys->su_group.cg_item.ci_type->ct_owner;
1496:
1497: /*
1498: * Ensure that no racing symlink() will make detach_prep() fail while
1499: * the new link is temporarily attached
1500: */
1501: do {
1502: struct dentry *wait;
1503:
1504: mutex_lock(&configfs_symlink_mutex);
1505: spin_lock(&configfs_dirent_lock);
1506: /*
1507: * Here's where we check for dependents. We're protected by
1508: * configfs_dirent_lock.
1509: * If no dependent, atomically tag the item as dropping.
1510: */
1511: ret = sd->s_dependent_count ? -EBUSY : 0;
1512: if (!ret) {
1513: ret = configfs_detach_prep(sd, &wait);
1514: if (ret)
1515: configfs_detach_rollback(sd);
1516: }
1517: spin_unlock(&configfs_dirent_lock);
1518: mutex_unlock(&configfs_symlink_mutex);
1519:
1520: if (ret) {
1521: if (ret != -EAGAIN) {
1522: config_item_put(parent_item);
1523: return ret;
1524: }
1525:
1526: /* Wait until the racing operation terminates */
1527: inode_lock(d_inode(wait));
1528: inode_unlock(d_inode(wait));
1529: dput(wait);
1530: }
1531: } while (ret == -EAGAIN);
1532:
1533: frag = sd->s_frag;
1534: if (down_write_killable(&frag->frag_sem)) {
1535: spin_lock(&configfs_dirent_lock);
1536: configfs_detach_rollback(sd);
1537: spin_unlock(&configfs_dirent_lock);
1538: config_item_put(parent_item);
1539: return -EINTR;
1540: }
1541: frag->frag_dead = true;
1542: up_write(&frag->frag_sem);
1543:
1544: /* Get a working ref for the duration of this function */
1545: item = configfs_get_config_item(dentry);
1546:
1547: /* Drop reference from above, item already holds one. */
1548: config_item_put(parent_item);
1549:
1550: if (item->ci_type)
1551: dead_item_owner = item->ci_type->ct_owner;
1552:
1553: if (sd->s_type & CONFIGFS_USET_DIR) {
1554: configfs_detach_group(dentry);
1555:
1556: mutex_lock(&subsys->su_mutex);
1557: client_disconnect_notify(parent_item, item);
1558: unlink_group(to_config_group(item));
1559: } else {
1560: configfs_detach_item(dentry);
1561:
1562: mutex_lock(&subsys->su_mutex);
1563: client_disconnect_notify(parent_item, item);
1564: unlink_obj(item);
1565: }
1566:
1567: client_drop_item(parent_item, item);
1568: mutex_unlock(&subsys->su_mutex);
1569:
1570: /* Drop our reference from above */
1571: config_item_put(item);
1572:
1573: module_put(dead_item_owner);
1574: module_put(subsys_owner);
1575:
1576: return 0;
1577: }
SourceFile:fs/configfs/dir.c]
|
| 60/2 |
2026/07/31 03:50 |
llm |
expert |
0m
Model:
Tokens:
input: 66005
output: 38
thoughts: 308
Thoughts: **Analyzing the USB Gadget Configuration Removal**
Okay, let me think this through. `configfs_detach_prep` first checks if the directory is empty. Right, if it *isn't* empty, meaning there are children present, it returns `-ENOTEMPTY`. So, a simple `rmdir` on `g1`, for example, should fail if `g1` has children directories, like `functions`.
But hold on⦠`configfs_detach_prep` is called *before* `mutex_lock(&subsys->su_mutex)`. This is key. So if `g1` isn't empty, `configfs_rmdir` will return `-ENOTEMPTY` *before* the lock happens. Good. It won't hold up other processes.
Now, if `g1` *is* empty, CPU0 can proceed and lock `gadget_subsys.su_mutex`. But wait a minute⦠if `g1` *is* empty, CPU2 can't possibly be doing a `mkdir` operation on something like `/config/usb_gadget/g1/functions/tcm.usb0`, because the `functions` directory wouldn't exist!
Ah, right, `functions` is a *default* group that's created when `g1` is created. So `g1` will *never* be truly empty! It'll always have `functions`, `strings`, `configs`, and probably more by default.
So then, `rmdir /config/usb_gadget/g1` will call `configfs_detach_prep`, which will see that `g1` has the default groups. But does that cause a problem? Does `configfs_detach_prep` handle default groups? I need to check `configfs_detach_prep`. Let's confirm that.
|
| 61/2 |
2026/07/31 03:50 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/configfs/dir.c Name:configfs_detach_prep]
Results: map[SourceCode: 494: /*
495: * Only subdirectories count here. Files (CONFIGFS_NOT_PINNED) are
496: * attributes and are removed by rmdir(). We recurse, setting
497: * CONFIGFS_USET_DROPPING on all children that are candidates for
498: * default detach.
499: * If there is an error, the caller will reset the flags via
500: * configfs_detach_rollback().
501: */
502: static int configfs_detach_prep(struct configfs_dirent *parent_sd, struct dentry **wait)
503: {
504: struct configfs_dirent *sd;
505: int ret;
506:
507: /* Mark that we're trying to drop the group */
508: parent_sd->s_type |= CONFIGFS_USET_DROPPING;
509:
510: ret = -EBUSY;
511: if (parent_sd->s_links)
512: goto out;
513:
514: ret = 0;
515: list_for_each_entry(sd, &parent_sd->s_children, s_sibling) {
516: if (!sd->s_element ||
517: (sd->s_type & CONFIGFS_NOT_PINNED))
518: continue;
519: if (sd->s_type & CONFIGFS_USET_DEFAULT) {
520: /* Abort if racing with mkdir() */
521: if (sd->s_type & CONFIGFS_USET_IN_MKDIR) {
522: if (wait)
523: *wait= dget(sd->s_dentry);
524: return -EAGAIN;
525: }
526:
527: /*
528: * Yup, recursive. If there's a problem, blame
529: * deep nesting of default_groups
530: */
531: ret = configfs_detach_prep(sd, wait);
532: if (!ret)
533: continue;
534: } else
535: ret = -ENOTEMPTY;
536:
537: break;
538: }
539:
540: out:
541: return ret;
542: }
SourceFile:fs/configfs/dir.c]
|
| 62/2 |
2026/07/31 03:50 |
llm |
expert |
0m
Model:
Tokens:
input: 67476
output: 66
thoughts: 1378
Thoughts: **Analysis of a Potential ConfigFS Deadlock**
Alright, let's break this down. It seems like `configfs_detach_prep` can recurse into default groups. My initial thought was that if a non-default group like `tcm.usb0` exists, `rmdir /config/usb_gadget/g1` would fail with `-ENOTEMPTY` before even touching `gadget_subsys.su_mutex`. However, the concern was what happens if `tcm.usb0` *doesn't* exist yet.
CPU0 kicks off `configfs_detach_prep`, and assuming it succeeds initially, it then grabs `gadget_subsys.su_mutex`. At the same time, CPU2 tries to `mkdir /config/usb_gadget/g1/functions/tcm.usb0`. `configfs_mkdir` enters the picture, and there was a question as to when `CONFIGFS_USET_IN_MKDIR` gets set on the parent directory (`functions`).
The critical point is that if CPU2 sets `CONFIGFS_USET_IN_MKDIR` *before* CPU0 calls `configfs_detach_prep`, CPU0 will get an `-EAGAIN` and wait. But, if CPU0 calls `configfs_detach_prep` first, then it sets `CONFIGFS_USET_DROPPING` on `functions`. This is the important part because, based on the code I reviewed, `configfs_mkdir` checks `configfs_dirent_is_ready`, which in turn checks if `CONFIGFS_USET_DROPPING` is set.
The `configfs_dirent_lock` acts as a crucial serializer here. If CPU0 "wins" the race, CPU2's `mkdir` fails with `-ENOENT` and *doesn't* lock `gadget_subsys.su_mutex`. If CPU2 wins, CPU0 returns `-EAGAIN` and waits.
The bottom line is that the 3-thread deadlock scenario I was imagining is impossible because `configfs_dirent_lock` makes it so CPU0 and CPU2 *cannot* run concurrently. So my original scenario is actually prevented.
This means the lockdep report is a FALSE POSITIVE. Lockdep is incorrectly flagging this as a possible deadlock because it doesn't distinguish between different instances of `inode_lock`. In this case, `inode_lock` is being held on different directories, not the same one.
So:
- Exploitable: False
- Denial of Service: False (it's a false positive)
- Accessible From Unprivileged Processes: False
- Accessible From User Namespaces: False
Just to make sure, let's quickly eliminate other potential issues: what if CPU1 and CPU2 were the same thread? No, different `mkdir` calls. What about `symlink` calls? Even then, the lock acquisition patterns analyzed before are clear, and `inode_lock` is grabbed on different inodes, preventing any deadlock.
There's *no* real deadlock here. It's a lockdep false positive. In a production environment, this will print a warning because lockdep is disabled, so there's no actual risk of a denial-of-service condition.
Here's the summary: The report flags a "possible circular locking dependency" by lockdep. The critical locks are: `gadget_subsys.su_mutex`, `opts->dep_lock`, and `inode_lock` (specifically, the configfs directory lock: `&sb->s_type->i_mutex_key#23`).
The lock chain looks like this:
* `configfs_symlink` holds `inode_lock` of the symlink's parent dir, then acquires `gadget_subsys.su_mutex`.
* `configfs_mkdir` (for a gadget function) holds `gadget_subsys.su_mutex`, and then acquires `opts->dep_lock`.
* `configfs_mkdir` (for a target tpgt) holds `opts->dep_lock`, and then acquires `inode_lock` of the gadget subsystem's root directory.
Lockdep flags this because it sees `inode_lock` acquired while holding `opts->dep_lock`, and `gadget_subsys.su_mutex` acquired while holding `inode_lock`.
However, the key is that the `inode_lock` instances are for *different* directories. The `configfs_symlink` code locks the parent of the symlink (like `/config/usb_gadget/g1/ext.0`), while the `configfs_depend_item_unlocked` code locks the root
|
| 63/2 |
2026/07/31 03:50 |
tool |
set-results |
0m
Args: map[DenialOfService:false Exploitable:false FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:false UserNamespace:false VMGuestTrigger:false VMHostTrigger:false]
Results: map[DenialOfService:false Exploitable:false FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:false UserNamespace:false VMGuestTrigger:false VMHostTrigger:false]
|
| 64/2 |
2026/07/31 03:50 |
llm |
expert |
0m
Model:
Tokens:
input: 70009
output: 794
thoughts: 41
Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience:
**Finalizing the Lockdep Explanation**
Alright, the data's in, time to craft the final response. I need to make this crystal clear, especially regarding the lockdep false positive. I'll need to dissect precisely *why* it triggered and what the underlying cause is. Then, I need to methodically lay out the interplay of the various inode locks involved, making sure to highlight the critical paths. Finally, it's crucial to explicitly mention and reinforce the configfs root-only restriction. Leaving that out would be a serious omission. This has to be a robust and airtight explanation, anticipating every possible question or counterargument. Let's make this concise but definitive.
|
| 65/1 |
2026/07/31 03:51 |
action |
format-explanation |
0m
Results: map[Explanation:Based on the analysis of the provided lockdep report and the kernel source code,
here is the assessment of the bug:
**Analysis:**
The bug report indicates a "possible circular locking dependency detected" by
lockdep. The reported lock chain involves three locks:
1. `gadget_subsys.su_mutex`
2. `opts->dep_lock`
3. `inode_lock` (specifically the lock class `&sb->s_type->i_mutex_key#23` which
corresponds to VFS directory inode locks in configfs).
The dependency chain is formed by the following operations:
- **`inode_lock` -> `gadget_subsys.su_mutex`**: When creating a symlink via
`configfs_symlink`, the VFS locks the `inode_lock` of the symlink's parent
directory (e.g., `.../ext.0`). The configfs callback `uvcg_extension_allow_link`
then acquires `gadget_subsys.su_mutex`.
- **`gadget_subsys.su_mutex` -> `opts->dep_lock`**: When creating a new gadget
function via `configfs_mkdir`, configfs holds `gadget_subsys.su_mutex` and calls
`function_make` -> `tcm_set_name`, which acquires `opts->dep_lock`.
- **`opts->dep_lock` -> `inode_lock`**: When creating a target portal group
(tpgt) via `configfs_mkdir`, the target subsystem holds `opts->dep_lock` (in
`usbg_make_tpg`) and calls `configfs_depend_item_unlocked`, which acquires the
`inode_lock` of the gadget subsystem's root directory (e.g.,
`/config/usb_gadget`).
Lockdep flags this as a circular dependency because it tracks lock classes, not
individual lock instances. It sees `inode_lock` being acquired while holding
`opts->dep_lock`, and `gadget_subsys.su_mutex` being acquired while holding
`inode_lock`.
However, this is a **lockdep false positive**. The `inode_lock` instances
involved in the cycle belong to completely different directories (the symlink's
parent directory deep in the hierarchy vs. the subsystem's root directory).
Because they are different inodes, threads acquiring them concurrently will not
block each other, and a real deadlock will not occur. Furthermore, configfs
employs internal synchronization mechanisms (like `configfs_dirent_lock` and
state flags such as `CONFIGFS_USET_DROPPING`) that strictly serialize structural
modifications (like `rmdir` and `mkdir`), preventing any theoretical deadlocks
that might arise from reverse lock ordering.
In production kernels, lockdep is disabled, meaning this warning will not be
printed and no system halt or panic will occur. Since there is no actual
deadlock, it does not cause a Denial of Service.
Additionally, `configfs` does not have the `FS_USERNS_MOUNT` flag, meaning it
cannot be mounted inside an unprivileged user namespace. The default mount at
`/sys/kernel/config` is owned by root, and unprivileged users do not have the
write permissions required to create directories (`mkdir`) or symlinks within
it. Therefore, this code path is completely inaccessible to unprivileged users.
**Conclusion:**
- **Exploitable:** No.
- **Denial of Service:** No (it is a lockdep false positive; no actual deadlock
occurs).
- **Accessible From Unprivileged Processes:** No (requires root privileges to
modify configfs).
- **Accessible From User Namespaces:** No (configfs cannot be mounted in user
namespaces).]
|