| 0/0 |
2026/07/29 05:47 |
flow |
assessment-security |
28m
Results: map[DenialOfService:true Explanation:Based on the analysis of the bug report and the kernel source code, here is the
assessment of the vulnerability:
### Bug Mechanism
The bug is a **Use-After-Free (UAF)** vulnerability in the `configfs` subsystem,
caused by a race condition between `symlinkat()` and `rmdir()`.
1. **Thread 1 (`symlinkat`)**: Enters `configfs_symlink()` and calls
`get_target()` to resolve the target path. `get_target()` uses `kern_path()` to
find the target `dentry`, retrieves the associated `config_item` (incrementing
its refcount), and then drops the `path` (which drops the `dentry` refcount).
2. **Thread 2 (`rmdir`)**: Enters `configfs_rmdir()` on the target directory. It
detaches the item, removes the directory, and unhashes the `dentry`. Since
Thread 1 already dropped its `dentry` reference, the `dentry` refcount drops to
0.
3. **Thread 2 (`rmdir`)**: The `dentry` is killed, triggering
`configfs_d_iput()`. This drops the reference to the associated
`configfs_dirent` (`sd`). The `sd` refcount reaches 0, and the `configfs_dirent`
structure is freed back to the slab cache.
4. **Thread 1 (`symlinkat`)**: Continues execution and calls `create_link()`. It
accesses `target_item->ci_dentry`. Because `ci_dentry` is a weak pointer (it
does not hold a reference to the `dentry`), it now points to the killed
`dentry`.
5. **Thread 1 (`symlinkat`)**: `create_link()` reads `target_sd =
target_item->ci_dentry->d_fsdata` and calls `configfs_get(target_sd)`. This
accesses the freed `configfs_dirent` (UAF).
6. If the memory hasn't been reallocated, the first 8 bytes contain the slab
freelist pointer (which is `NULL` at the end of the list), making `sd->s_count`
evaluate to `0`. This triggers the `WARN_ON(!atomic_read(&sd->s_count))` seen in
the crash report. If the memory has been reallocated, `configfs_get()` will
increment the first 4 bytes of the new object, causing memory corruption.
### Security Impact Assessment
* **Exploitable: Yes**
This is a classic Use-After-Free (UAF) vulnerability. An attacker can reliably
trigger the race condition to cause the kernel to operate on freed memory. By
using heap spraying techniques, an attacker could reallocate the freed
`configfs_dirent` (or the killed `dentry` protected by `SLAB_TYPESAFE_BY_RCU`)
with controlled data, leading to memory corruption, arbitrary code execution,
and privilege escalation.
* **Denial Of Service: Yes**
Triggering the bug can easily lead to a kernel panic (e.g., a NULL pointer
dereference if `d_fsdata` becomes NULL upon reallocation) or system
instability due to memory corruption, resulting in a Denial of Service.
* **Accessible From Unprivileged Processes: No**
Configfs is typically mounted at `/sys/kernel/config` with `root:root`
ownership and `0755` permissions. Unprivileged users cannot create directories
or symlinks within configfs, which is required to trigger this bug. (Note: On
some embedded/Android systems, specific configfs subdirectories might be
`chown`ed to a dedicated user like `system`, but it remains inaccessible to a
*typical* unprivileged process).
* **Accessible From User Namespaces: No**
The `configfs` filesystem does not have the `FS_USERNS_MOUNT` flag. Therefore,
it cannot be mounted by an unprivileged user inside a user namespace.
* **Other Triggers (Network, Remote, Peripheral, Malicious FS, VM Guest/Host):
No**
The bug is strictly a local filesystem API race condition in a
pseudo-filesystem (`configfs`). It cannot be triggered over the network, via
external peripherals, by mounting a malicious disk image, or across VM
boundaries. Exploitable:true FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:false UserNamespace:false VMGuestTrigger:false VMHostTrigger:false]
|
| 1/1 |
2026/07/29 05:47 |
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>
int main(void)
{
syscall(__NR_mmap, /*addr=*/0x1ffffffff000ul, /*len=*/0x1000, /*prot=*/0ul, /*flags=MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE*/0x32ul, /*fd=*/(intptr_t)-1, /*offset=*/0ul);
syscall(__NR_mmap, /*addr=*/0x200000000000ul, /*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=*/0x200001000000ul, /*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 63 6f 6e 66 69 67 00} (length 0x9)
// }
// mode: open_mode = 0x1ff (8 bytes)
// ]
memcpy((void*)0x200000000000, "./config\000", 9);
syscall(__NR_mkdirat, /*fd=*/0xffffff9c, /*path=*/0x200000000000ul, /*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*)0x200000000080, "./config\000", 9);
memcpy((void*)0x2000000000c0, "configfs\000", 9);
syscall(__NR_mount, /*src=*/0ul, /*dst=*/0x200000000080ul, /*type=*/0x2000000000c0ul, /*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*)0x200000000140, "./config/usb_gadget/g1\000", 23);
syscall(__NR_mkdirat, /*fd=*/0xffffff9c, /*path=*/0x200000000140ul, /*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 63 6f 6e 66 69 67 73 2f 63 2e 31 00} (length 0x23)
// }
// mode: open_mode = 0x1ff (8 bytes)
// ]
memcpy((void*)0x200000000280, "./config/usb_gadget/g1/configs/c.1\000", 35);
syscall(__NR_mkdirat, /*fd=*/0xffffff9c, /*path=*/0x200000000280ul, /*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 75 61 63 32 2e 75 73 62 30 00} (length 0x2b)
// }
// mode: open_mode = 0x1ff (8 bytes)
// ]
memcpy((void*)0x2000000002c0, "./config/usb_gadget/g1/functions/uac2.usb0\000", 43);
syscall(__NR_mkdirat, /*fd=*/0xffffff9c, /*path=*/0x2000000002c0ul, /*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 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 61 63 32 2e 75 73 62 30 00} (length 0x2b)
// }
// newfd: fd_dir (resource)
// new: 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 63 6f 6e 66 69 67 73 2f 63 2e 31 2f 75 61 63 32 2e 75 73 62 30 00} (length 0x2d)
// }
// ]
memcpy((void*)0x200000000300, "./config/usb_gadget/g1/functions/uac2.usb0\000", 43);
memcpy((void*)0x200000000340, "./config/usb_gadget/g1/configs/c.1/uac2.usb0\000", 45);
syscall(__NR_symlinkat, /*old=*/0x200000000300ul, /*newfd=*/0xffffff9c, /*new=*/0x200000000340ul);
// unlinkat 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 63 6f 6e 66 69 67 73 2f 63 2e 31 2f 75 61 63 32 2e 75 73 62 30 00} (length 0x2d)
// }
// flags: unlinkat_flags = 0x0 (8 bytes)
// ]
memcpy((void*)0x200000000440, "./config/usb_gadget/g1/configs/c.1/uac2.usb0\000", 45);
syscall(__NR_unlinkat, /*fd=*/0xffffff9c, /*path=*/0x200000000440ul, /*flags=*/0ul);
// unlinkat 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 75 61 63 32 2e 75 73 62 30 00} (length 0x2b)
// }
// flags: unlinkat_flags = 0x200 (8 bytes)
// ]
memcpy((void*)0x200000000480, "./config/usb_gadget/g1/functions/uac2.usb0\000", 43);
syscall(__NR_unlinkat, /*fd=*/0xffffff9c, /*path=*/0x200000000480ul, /*flags=AT_REMOVEDIR*/0x200ul);
return 0;
}
]
|
| 2/1 |
2026/07/29 05:47 |
action |
kernel-checkouter |
0m
Results: map[KernelSrc:/app/workdir/cache/src/59323400ba46e708d687f6274371ea997808e9ce]
|
| 3/1 |
2026/07/29 05:47 |
action |
kernel-builder |
14m
Results: map[KernelObj:/app/workdir/cache/build/80d7d1b2fd9bb699a440984dbfe9e1661576f4a6]
|
| 4/1 |
2026/07/29 06:01 |
action |
codesearch-prepare |
9m
Results: map[Index:codesearch-index]
|
| 5/1 |
2026/07/29 06:10 |
agent |
expert |
4m
Model:
Results: map[DenialOfService:true Exploitable:true 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:
------------[ cut here ]------------
!atomic_read(&sd->s_count)
WARNING: fs/configfs/configfs_internal.h:146 at configfs_get fs/configfs/configfs_internal.h:146 [inline], CPU#0: syz.2.1991/9996
WARNING: fs/configfs/configfs_internal.h:146 at create_link fs/configfs/symlink.c:92 [inline], CPU#0: syz.2.1991/9996
WARNING: fs/configfs/configfs_internal.h:146 at configfs_symlink+0xdd4/0x11f0 fs/configfs/symlink.c:199, CPU#0: syz.2.1991/9996
Modules linked in:
CPU: 0 UID: 0 PID: 9996 Comm: syz.2.1991 Not tainted syzkaller #0 PREEMPT(full)
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
RIP: 0010:configfs_get fs/configfs/configfs_internal.h:146 [inline]
RIP: 0010:create_link fs/configfs/symlink.c:92 [inline]
RIP: 0010:configfs_symlink+0xdd4/0x11f0 fs/configfs/symlink.c:199
Code: 48 c1 e8 03 80 3c 10 00 0f 85 63 03 00 00 48 8b 74 24 18 48 8b 3d 8c 26 bb 18 e8 97 6e ad ff e9 4f fe ff ff e8 dd df 48 ff 90 <0f> 0b 90 e9 a6 f8 ff ff e8 cf df 48 ff 48 c7 c7 40 49 eb 8e e8 a3
RSP: 0018:ffffc900020bfcc8 EFLAGS: 00010293
RAX: 0000000000000000 RBX: ffff888039383be8 RCX: ffffffff82c1790a
RDX: ffff888034e38000 RSI: ffffffff82c18063 RDI: ffff888034e38000
RBP: 0000000000000000 R08: 0000000000000005 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000000 R12: ffff888046851c80
R13: ffff888046851d58 R14: ffff88802bdacc80 R15: ffff888058814000
FS: 00007fbd7c4276c0(0000) GS:ffff8880d5ddc000(0000) knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 00007fbd7c426ff8 CR3: 000000002d0d2000 CR4: 0000000000352ef0
Call Trace:
<TASK>
vfs_symlink fs/namei.c:5660 [inline]
vfs_symlink+0x178/0x4d0 fs/namei.c:5639
filename_symlinkat+0x2a6/0x560 fs/namei.c:5685
__do_sys_symlinkat fs/namei.c:5705 [inline]
__se_sys_symlinkat fs/namei.c:5700 [inline]
__x64_sys_symlinkat+0x9c/0xe0 fs/namei.c:5700
do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
do_syscall_64+0x115/0x870 arch/x86/entry/syscall_64.c:94
entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7fbd7b59de99
Code: ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 44 00 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 e8 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007fbd7c427028 EFLAGS: 00000246 ORIG_RAX: 000000000000010a
RAX: ffffffffffffffda RBX: 00007fbd7b825fa0 RCX: 00007fbd7b59de99
RDX: 0000200000000340 RSI: ffffffffffffff9c RDI: 0000200000000300
RBP: 00007fbd7b633eaf R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000
R13: 00007fbd7b826038 R14: 00007fbd7b825fa0 R15: 00007fff25ff7358
</TASK>
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>
int main(void)
{
syscall(__NR_mmap, /*addr=*/0x1ffffffff000ul, /*len=*/0x1000, /*prot=*/0ul, /*flags=MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE*/0x32ul, /*fd=*/(intptr_t)-1, /*offset=*/0ul);
syscall(__NR_mmap, /*addr=*/0x200000000000ul, /*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=*/0x200001000000ul, /*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 63 6f 6e 66 69 67 00} (length 0x9)
// }
// mode: open_mode = 0x1ff (8 bytes)
// ]
memcpy((void*)0x200000000000, "./config\000", 9);
syscall(__NR_mkdirat, /*fd=*/0xffffff9c, /*path=*/0x200000000000ul, /*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*)0x200000000080, "./config\000", 9);
memcpy((void*)0x2000000000c0, "configfs\000", 9);
syscall(__NR_mount, /*src=*/0ul, /*dst=*/0x200000000080ul, /*type=*/0x2000000000c0ul, /*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*)0x200000000140, "./config/usb_gadget/g1\000", 23);
syscall(__NR_mkdirat, /*fd=*/0xffffff9c, /*path=*/0x200000000140ul, /*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 63 6f 6e 66 69 67 73 2f 63 2e 31 00} (length 0x23)
// }
// mode: open_mode = 0x1ff (8 bytes)
// ]
memcpy((void*)0x200000000280, "./config/usb_gadget/g1/configs/c.1\000", 35);
syscall(__NR_mkdirat, /*fd=*/0xffffff9c, /*path=*/0x200000000280ul, /*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 75 61 63 32 2e 75 73 62 30 00} (length 0x2b)
// }
// mode: open_mode = 0x1ff (8 bytes)
// ]
memcpy((void*)0x2000000002c0, "./config/usb_gadget/g1/functions/uac2.usb0\000", 43);
syscall(__NR_mkdirat, /*fd=*/0xffffff9c, /*path=*/0x2000000002c0ul, /*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 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 61 63 32 2e 75 73 62 30 00} (length 0x2b)
// }
// newfd: fd_dir (resource)
// new: 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 63 6f 6e 66 69 67 73 2f 63 2e 31 2f 75 61 63 32 2e 75 73 62 30 00} (length 0x2d)
// }
// ]
memcpy((void*)0x200000000300, "./config/usb_gadget/g1/functions/uac2.usb0\000", 43);
memcpy((void*)0x200000000340, "./config/usb_gadget/g1/configs/c.1/uac2.usb0\000", 45);
syscall(__NR_symlinkat, /*old=*/0x200000000300ul, /*newfd=*/0xffffff9c, /*new=*/0x200000000340ul);
// unlinkat 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 63 6f 6e 66 69 67 73 2f 63 2e 31 2f 75 61 63 32 2e 75 73 62 30 00} (length 0x2d)
// }
// flags: unlinkat_flags = 0x0 (8 bytes)
// ]
memcpy((void*)0x200000000440, "./config/usb_gadget/g1/configs/c.1/uac2.usb0\000", 45);
syscall(__NR_unlinkat, /*fd=*/0xffffff9c, /*path=*/0x200000000440ul, /*flags=*/0ul);
// unlinkat 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 75 61 63 32 2e 75 73 62 30 00} (length 0x2b)
// }
// flags: unlinkat_flags = 0x200 (8 bytes)
// ]
memcpy((void*)0x200000000480, "./config/usb_gadget/g1/functions/uac2.usb0\000", 43);
syscall(__NR_unlinkat, /*fd=*/0xffffff9c, /*path=*/0x200000000480ul, /*flags=AT_REMOVEDIR*/0x200ul);
return 0;
}
Reply: Based on the analysis of the bug report and the kernel source code, here is the assessment of the vulnerability:
### Bug Mechanism
The bug is a **Use-After-Free (UAF)** vulnerability in the `configfs` subsystem, caused by a race condition between `symlinkat()` and `rmdir()`.
1. **Thread 1 (`symlinkat`)**: Enters `configfs_symlink()` and calls `get_target()` to resolve the target path. `get_target()` uses `kern_path()` to find the target `dentry`, retrieves the associated `config_item` (incrementing its refcount), and then drops the `path` (which drops the `dentry` refcount).
2. **Thread 2 (`rmdir`)**: Enters `configfs_rmdir()` on the target directory. It detaches the item, removes the directory, and unhashes the `dentry`. Since Thread 1 already dropped its `dentry` reference, the `dentry` refcount drops to 0.
3. **Thread 2 (`rmdir`)**: The `dentry` is killed, triggering `configfs_d_iput()`. This drops the reference to the associated `configfs_dirent` (`sd`). The `sd` refcount reaches 0, and the `configfs_dirent` structure is freed back to the slab cache.
4. **Thread 1 (`symlinkat`)**: Continues execution and calls `create_link()`. It accesses `target_item->ci_dentry`. Because `ci_dentry` is a weak pointer (it does not hold a reference to the `dentry`), it now points to the killed `dentry`.
5. **Thread 1 (`symlinkat`)**: `create_link()` reads `target_sd = target_item->ci_dentry->d_fsdata` and calls `configfs_get(target_sd)`. This accesses the freed `configfs_dirent` (UAF).
6. If the memory hasn't been reallocated, the first 8 bytes contain the slab freelist pointer (which is `NULL` at the end of the list), making `sd->s_count` evaluate to `0`. This triggers the `WARN_ON(!atomic_read(&sd->s_count))` seen in the crash report. If the memory has been reallocated, `configfs_get()` will increment the first 4 bytes of the new object, causing memory corruption.
### Security Impact Assessment
* **Exploitable: Yes**
This is a classic Use-After-Free (UAF) vulnerability. An attacker can reliably trigger the race condition to cause the kernel to operate on freed memory. By using heap spraying techniques, an attacker could reallocate the freed `configfs_dirent` (or the killed `dentry` protected by `SLAB_TYPESAFE_BY_RCU`) with controlled data, leading to memory corruption, arbitrary code execution, and privilege escalation.
* **Denial Of Service: Yes**
Triggering the bug can easily lead to a kernel panic (e.g., a NULL pointer dereference if `d_fsdata` becomes NULL upon reallocation) or system instability due to memory corruption, resulting in a Denial of Service.
* **Accessible From Unprivileged Processes: No**
Configfs is typically mounted at `/sys/kernel/config` with `root:root` ownership and `0755` permissions. Unprivileged users cannot create directories or symlinks within configfs, which is required to trigger this bug. (Note: On some embedded/Android systems, specific configfs subdirectories might be `chown`ed to a dedicated user like `system`, but it remains inaccessible to a *typical* unprivileged process).
* **Accessible From User Namespaces: No**
The `configfs` filesystem does not have the `FS_USERNS_MOUNT` flag. Therefore, it cannot be mounted by an unprivileged user inside a user namespace.
* **Other Triggers (Network, Remote, Peripheral, Malicious FS, VM Guest/Host): No**
The bug is strictly a local filesystem API race condition in a pseudo-filesystem (`configfs`). It cannot be triggered over the network, via external peripherals, by mounting a malicious disk image, or across VM boundaries.
|
| 6/2 |
2026/07/29 06:10 |
llm |
expert |
0m
Model:
Tokens:
input: 10574
output: 39
thoughts: 56
Thoughts: Here's my summary, as though I'm thinking through this myself:
**Investigating a ConfigFS Warning**
Okay, so I've got a warning flagged in `configfs_get` at `fs/configfs/configfs_internal.h:146`. The warning condition is `!atomic_read(&sd->s_count)`. Right, that's immediately concerning; `s_count` in this context is almost certainly the reference count for the superblock, which directly dictates whether the filesystem is still actively in use. A zero count means the filesystem shouldn't really be in use, so this check is likely aimed at guarding against some use-after-free or double-free scenarios. My initial thought is to examine the surrounding code in `configfs_get` to see what could lead to this state. The goal would be to understand the exact conditions under which the atomic read of the `s_count` is zero, despite an apparent attempt to acquire a reference. Iβm thinking there might be an issue with how the superblock is being managed. Itβs imperative to thoroughly trace the lifecycle of this superblock within the ConfigFS implementation. This is going to require me to do a lot of digging to figure out how the reference is intended to function and how it might be violated.
|
| 7/2 |
2026/07/29 06:10 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/configfs/configfs_internal.h Name:configfs_get]
Results: map[SourceCode: 143: static inline struct configfs_dirent * configfs_get(struct configfs_dirent * sd)
144: {
145: if (sd) {
146: WARN_ON(!atomic_read(&sd->s_count));
147: atomic_inc(&sd->s_count);
148: }
149: return sd;
150: }
SourceFile:fs/configfs/configfs_internal.h]
|
| 8/2 |
2026/07/29 06:10 |
llm |
expert |
0m
Model:
Tokens:
input: 11091
output: 36
thoughts: 50
Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience, and written as if they were my own:
**Investigating a ConfigFS Symlink Trigger**
Right, so the warning's coming from something related to `configfs_symlink`, specifically, it seems to be bubbling up through the call stack `create_link` and then finally to `configfs_get`. That gives me a decent starting point. The likely culprit is in the symlink creation process, so I need to check how the underlying structures are being used. Let me go directly to `create_link` function located in `fs/configfs/symlink.c`, specifically line 92, to understand exactly what's happening and what potential issues might be lurking there in the symbolic link's creation and linking process. Hopefully, the code comments offer some useful context.
|
| 9/2 |
2026/07/29 06:10 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/configfs/symlink.c Name:create_link]
Results: map[SourceCode: 77: static int create_link(struct config_item *parent_item,
78: struct config_item *item,
79: struct dentry *dentry)
80: {
81: struct configfs_dirent *target_sd = item->ci_dentry->d_fsdata;
82: char *body;
83: int ret;
84:
85: if (!configfs_dirent_is_ready(target_sd))
86: return -ENOENT;
87:
88: body = kzalloc(PAGE_SIZE, GFP_KERNEL);
89: if (!body)
90: return -ENOMEM;
91:
92: configfs_get(target_sd);
93: spin_lock(&configfs_dirent_lock);
94: if (target_sd->s_type & CONFIGFS_USET_DROPPING) {
95: spin_unlock(&configfs_dirent_lock);
96: configfs_put(target_sd);
97: kfree(body);
98: return -ENOENT;
99: }
100: target_sd->s_links++;
101: spin_unlock(&configfs_dirent_lock);
102: ret = configfs_get_target_path(parent_item, item, body);
103: if (!ret)
104: ret = configfs_create_link(target_sd, parent_item->ci_dentry,
105: dentry, body);
106: if (ret) {
107: spin_lock(&configfs_dirent_lock);
108: target_sd->s_links--;
109: spin_unlock(&configfs_dirent_lock);
110: configfs_put(target_sd);
111: kfree(body);
112: }
113: return ret;
114: }
SourceFile:fs/configfs/symlink.c]
|
| 10/2 |
2026/07/29 06:10 |
llm |
expert |
0m
Model:
Tokens:
input: 11942
output: 38
thoughts: 112
Thoughts: **Analyzing a `configfs_get` Warning**
Okay, so I've got this warning: `WARN_ON(!atomic_read(&sd->s_count))` popping up in `configfs_get`. That immediately tells me something's off β specifically, `target_sd->s_count` is 0 when this function is being called. I need to figure out how we got here.
Let's see⦠`target_sd` is derived from `item->ci_dentry->d_fsdata`, and that `item` is being passed into `create_link`. Okay, I know where I need to dig next. I'm going to jump into `configfs_symlink`, specifically line 199 in `fs/configfs/symlink.c`. That should give me the context I need to understand what's happening leading up to this point. I suspect there's a reference count issue somewhere, probably related to symlink creation.
|
| 11/2 |
2026/07/29 06:10 |
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]
|
| 12/2 |
2026/07/29 06:10 |
llm |
expert |
0m
Model:
Tokens:
input: 13574
output: 36
thoughts: 40
Thoughts: Here's a breakdown of my thought process:
**Diving into Target Acquisition**
Okay, so I've got this `target_item` variable, and it looks like the `get_target` function is responsible for populating it. The call chain suggests this `target_item` is linked to a symbolic link, judging by the `dentry` argument. My initial thought is to zoom in on `get_target`. I need to figure out *how* this `target_item` is being retrieved. What's the mechanism? I need to understand the specifics. I'm particularly interested in how the `symname` and the superblock `dentry->d_sb` play into this process. Is this a straight lookup, a complex traversal, or something else entirely? Let's see what `get_target` is doing.
|
| 13/2 |
2026/07/29 06:10 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/configfs/symlink.c Name:get_target]
Results: map[SourceCode: 117: static int get_target(const char *symname, struct config_item **target,
118: struct super_block *sb)
119: {
120: struct path path __free(path_put) = {};
121: int ret;
122:
123: ret = kern_path(symname, LOOKUP_FOLLOW|LOOKUP_DIRECTORY, &path);
124: if (ret)
125: return ret;
126: if (path.dentry->d_sb != sb)
127: return -EPERM;
128: *target = configfs_get_config_item(path.dentry);
129: if (!*target)
130: return -ENOENT;
131: return 0;
132: }
SourceFile:fs/configfs/symlink.c]
|
| 14/2 |
2026/07/29 06:10 |
llm |
expert |
0m
Model:
Tokens:
input: 14100
output: 40
thoughts: 53
Thoughts: Here's my analysis of that function call, presented as my own thought process:
**Diving into the Symlink Resolution**
Okay, so `get_target` is key here, and it's using `kern_path` to untangle that symlink's target path. *That* makes sense β `kern_path` is the standard tool for resolving kernel-level paths, handling symlinks and all that jazz. The goal is to get the *actual* path the symlink is pointing to. Now, the next step in the flow is `configfs_get_config_item(path.dentry)`. Hmm, let's zoom in on *that* part of the equation and figure out what exactly that function does with a resolved `dentry`. Gotta understand how `configfs` fits into this picture. I'm guessing it likely uses the dentry to find an item in the configfs. Let's dig deeper into the guts of `configfs_get_config_item`.
|
| 15/2 |
2026/07/29 06:11 |
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]
|
| 16/2 |
2026/07/29 06:11 |
llm |
expert |
0m
Model:
Tokens:
input: 14634
output: 37
thoughts: 626
Thoughts: **Analyzing a ConfigFS Race Condition**
Okay, first things first, `configfs_get_config_item` lives in `fs/configfs/configfs_internal.h`, good to know. Now, let's dissect this reproducer. It's creating a symlink using `symlinkat`, linking a source path like `./config/usb_gadget/g1/functions/uac2.usb0` to a target path within a configfs structure. Then, *immediately* it's unlinking the *target* with `unlinkat`. Wait a minute... that doesn't seem right. It looks like the reproducer unlinks the target *after* creating the symlink!
Checking the actual syscalls, it uses `symlinkat` followed by *two* `unlinkat` calls. Okay, this is looking like a race. The reproducer is doing `symlinkat` and `unlinkat` sequentially, but Syzkaller is likely executing this in multiple threads, leading to a race condition. It seems the reproducer details that this is from a multithreaded syzkaller execution, `syz.2.1991/9996`.
Now, that warning... `WARN_ON(!atomic_read(&sd->s_count));` Let's break this down. That means `sd->s_count` is zero, which is bad news. `sd` is `target_sd`, derived from `item->ci_dentry->d_fsdata`. `item` is `target_item`, obtained through `get_target`. This `get_target` call resolves the path, gets a dentry, and then does `configfs_get_config_item`. The `configfs_get_config_item` call performs `config_item_get(sd->s_element)`. So, `target_item` should have a reference count! But somehow `sd->s_count` is zero. How can we have a valid `target_item` with `sd->s_count` at zero? This is the crux of the problem. Time to dig into `configfs_rmdir` to figure out how the reference count gets messed up.
|
| 17/2 |
2026/07/29 06:11 |
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]
|
| 18/2 |
2026/07/29 06:11 |
llm |
expert |
0m
Model:
Tokens:
input: 17397
output: 38
thoughts: 41
Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience, and written as if they were my own immediate processing:
**Diving into the ConfigFS Removal Process**
Right, so I'm looking at `configfs_rmdir`. Immediately, my brain flags that it's going to detach something, either an item or a group. The crucial operation here is going to be either `configfs_detach_item` or `configfs_detach_group`. Let's assume for the purposes of this exercise we're looking at `configfs_detach_item` specifically, which is where my attention will focus. I need to understand its exact behavior to fully grasp what's happening during this removal.
|
| 19/2 |
2026/07/29 06:11 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/configfs/dir.c Name:configfs_detach_item]
Results: map[SourceCode: 835: /* Caller holds the mutex of the item's inode */
836: static void configfs_detach_item(struct dentry *dentry)
837: {
838: detach_attrs(dentry);
839: configfs_remove_dir(dentry);
840: }
SourceFile:fs/configfs/dir.c]
|
| 20/2 |
2026/07/29 06:11 |
llm |
expert |
0m
Model:
Tokens:
input: 17751
output: 38
thoughts: 9
Thoughts: Okay, here's my interpretation of that thought process, tailored for an expert audience, and presented as if it were my own internal monologue:
**Diving into the Removal of a ConfigFS Directory**
Alright, so the focus is on directory removal, specifically within the `configfs` framework. I immediately think: `configfs_remove_dir`. That function call is where the action *starts*. I need to understand its role in tearing down a ConfigFS object and all its constituent parts. Itβs likely the core mechanism for handling the unmount and teardown of the filesystem nodes we're dealing with. Knowing how this function handles cleanup is critical. Does it just handle removing the directory entry itself, or does it handle reference counting, data freeing, and all other resource management? Is there a cascading deletion involved, or is a separate mechanism responsible for handling child nodes? What kind of error handling is implemented? What checks are in place to prevent accidental data loss or corruption during the removal? I need to look closely at its implementation to get a full picture of the removal process.
|
| 21/2 |
2026/07/29 06:11 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/configfs/dir.c Name:configfs_remove_dir]
Results: map[SourceCode: 410: static void configfs_remove_dir(struct dentry *d)
411: {
412: struct dentry * parent = dget(d->d_parent);
413:
414: configfs_remove_dirent(d);
415:
416: if (d_really_is_positive(d)) {
417: if (unlikely(simple_rmdir(d_inode(parent), d)))
418: pr_warn("remove_dir (%pd): attributes remain", d);
419: }
420:
421: pr_debug(" o %pd removing done (%d)\n", d, d_count(d));
422:
423: dput(parent);
424: }
SourceFile:fs/configfs/dir.c]
|
| 22/2 |
2026/07/29 06:11 |
llm |
expert |
0m
Model:
Tokens:
input: 18274
output: 39
thoughts: 10
Thoughts: Okay, here's my interpretation of that thought process, tailored for an expert audience, and presented as if it were my own internal monologue:
**Diving into Filesystem Management - Deletion**
Right, so we're looking at filesystem operations. "configfs\_remove\_dirent" jumps out immediately. This is, of course, a critical function. We're talking about removing a directory entry within the configfs filesystem, and I'm instantly thinking about the implications. What kind of cleanup does this trigger? Is it atomic? What locks are in play here?
My mind races β how robust is this implementation? Is it vulnerable to race conditions if multiple processes attempt to remove the same entry concurrently? The kernel is a complex beast, and subtle bugs in functions like this can lead to disastrous consequences. Iβm thinking about the potential for dangling pointers, memory leaks, and, of course, the ever-present threat of a use-after-free scenario. I'd need to trace the code to understand its interaction with the underlying inode and dentry structures. Is there proper reference counting? Are all the necessary memory deallocations happening in the right order?
More importantly, how does it interact with the upper layer operations, for instance, the cleanup functions defined for an attribute? And what about the impact of this function on the filesystem's consistency? What about associated filesystem metadata?
This one function alone can expose so many design considerations and problems, especially in a system like the kernel. So, yep, `configfs_remove_dirent` β it's a key piece of the puzzle, and a function I'd want to scrutinize carefully.
|
| 23/2 |
2026/07/29 06:11 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/configfs/dir.c Name:configfs_remove_dirent]
Results: map[SourceCode: 271: static void configfs_remove_dirent(struct dentry *dentry)
272: {
273: struct configfs_dirent *sd = dentry->d_fsdata;
274:
275: if (!sd)
276: return;
277: spin_lock(&configfs_dirent_lock);
278: list_del_init(&sd->s_sibling);
279: spin_unlock(&configfs_dirent_lock);
280: configfs_put(sd);
281: }
SourceFile:fs/configfs/dir.c]
|
| 24/2 |
2026/07/29 06:11 |
llm |
expert |
0m
Model:
Tokens:
input: 18863
output: 39
thoughts: 26
Thoughts: Here's my understanding of the situation, given my expertise:
**My Perspective on `configfs_put`**
Okay, so I'm looking at `configfs_put`. From my understanding of the code, this is crucial for managing the lifetime of a `configfs_subsystem` (represented here as `sd`). The function's core responsibility is to decrement the reference count, `sd->s_count`, associated with the subsystem. I get that. This refcount is, naturally, key to tracking how many users or entities are currently "holding" a reference to this subsystem.
The really interesting part, and what I need to pay close attention to, is the conditional. If after the decrement, `sd->s_count` hits zero, that's the trigger. When the count is zero, it's safe to assume that no one is relying on this subsystem anymore. In that case, `configfs_put` takes the action to free the memory associated with `sd`. That means we are properly deallocating the `configfs_subsystem` instance and releasing the resources it consumed, preventing any memory leaks. It's a fundamental part of the overall subsystem lifecycle management, ensuring we don't have dangling pointers or wasted memory. So, in effect, this is our clean-up mechanism. It's really that simple: decrement the counter, check if it's zero, and if it is, *poof* β free the structure. I get it. It's elegant in its simplicity and, when implemented correctly, is a cornerstone for preventing subtle but devastating memory management errors within the system.
|
| 25/2 |
2026/07/29 06:11 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/configfs/configfs_internal.h Name:configfs_put]
Results: map[SourceCode: 152: static inline void configfs_put(struct configfs_dirent * sd)
153: {
154: WARN_ON(!atomic_read(&sd->s_count));
155: if (atomic_dec_and_test(&sd->s_count))
156: release_configfs_dirent(sd);
157: }
SourceFile:fs/configfs/configfs_internal.h]
|
| 26/2 |
2026/07/29 06:11 |
llm |
expert |
0m
Model:
Tokens:
input: 19405
output: 40
thoughts: 163
Thoughts: **Analyzing the configfs Directory Removal Process**
Okay, let's break this down. I'm looking at how `configfs_rmdir` works and something isn't quite clicking. So, I see that it calls `configfs_remove_dirent`, which then calls `configfs_put(sd)`. This, in turn, should decrement the reference count of the `sd` object, which represents the directory. That's the expected behavior when a directory is removed.
But then, I was looking at `configfs_symlink`, and I see the call to `get_target`. The important piece is that `get_target` calls `configfs_get_config_item(path.dentry)`. This is where I started to pause. I remember `configfs_get_config_item` from other parts.
The critical thing is, and this is where I need to focus, `get_target` *doesn't* seem to take a reference on `sd`, the directory entry, which is returned in the path, it only takes a reference on the `target_item`. The target item itself gets a reference via `config_item_get`. This is the thing that's hanging me up. Let me go back and double check `configfs_get_config_item` because this is the spot that feels like a potential race condition or reference counting oversight.
|
| 27/2 |
2026/07/29 06:11 |
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]
|
| 28/2 |
2026/07/29 06:11 |
llm |
expert |
0m
Model:
Tokens:
input: 20133
output: 39
thoughts: 234
Thoughts: **Race Condition Alert! Configfs Dirent Management**
Okay, so I'm looking at this `configfs_get_config_item` function, and it seems straightforward enough. It retrieves the `config_item` and bumps the reference count, but it **doesn't** touch `sd->s_count`, which is interesting. Then, I dive into `create_link`. I see the code grabbing `item->ci_dentry` and pulling out `target_sd`. Hmm, accessing `item->ci_dentry` without a lock feels a bitβ¦iffy, but let's see where this is going.
Right after that, `configfs_get(target_sd)` is called, which increments `target_sd->s_count`. That seems logical, but it's making me uneasy. My mind starts racing. What if `target_sd` is already being freed by a concurrent thread, or what if `s_count` is somehow already zero? Specifically, if `configfs_rmdir` is running at the same time, it can call `configfs_remove_dirent`, which then calls `configfs_put(sd)`.
If that `configfs_put` drops the last reference to `sd`, then `sd` is freed! My gut feeling is screaming race condition. The code is potentially accessing memory that's being freed concurrently. I know that `sd` is freed in `release_configfs_dirent`. This whole sequence feels fragile. We've got a window where the dereference to `target_sd` could lead to a use-after-free scenario. This needs a deeper dive to analyze the potential implications and find the best synchronization solution.
|
| 29/2 |
2026/07/29 06:11 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/configfs/dir.c Name:release_configfs_dirent]
Results: map[SourceCode: 134: static inline void release_configfs_dirent(struct configfs_dirent * sd)
135: {
136: if (!(sd->s_type & CONFIGFS_ROOT)) {
137: kfree(sd->s_iattr);
138: put_fragment(sd->s_frag);
139: kmem_cache_free(configfs_dir_cachep, sd);
140: }
141: }
SourceFile:fs/configfs/configfs_internal.h]
|
| 30/2 |
2026/07/29 06:11 |
llm |
expert |
0m
Model:
Tokens:
input: 20933
output: 35
thoughts: 108
Thoughts: **Investigating a Potential Memory Leak in configfs**
Okay, so I've just seen that `sd` (presumably some sort of data structure) has been freed. The red flag, however, is that `item->ci_dentry->d_fsdata` *still* seems to be pointing to it! That's... concerning. Now I'm trying to figure out when the `item->ci_dentry` association gets cleared. When does that link break?
Let's quickly re-examine `configfs_rmdir`. Right, it calls `configfs_detach_item`, which leads to `configfs_remove_dir`, and then `configfs_remove_dirent`. Ah, right, that then invokes `unlink_obj(item)`. Okay, so `unlink_obj` is the key function. I need to understand what's happening there to see if the dangling pointer is addressed. Let's dig into `unlink_obj` and see what's actually happening to that `item` and its related structures. Time to trace this.
|
| 31/2 |
2026/07/29 06:11 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/configfs/dir.c Name:unlink_obj]
Results: map[SourceCode: 764: static void unlink_obj(struct config_item *item)
765: {
766: struct config_group *group;
767:
768: group = item->ci_group;
769: if (group) {
770: list_del_init(&item->ci_entry);
771:
772: item->ci_group = NULL;
773: item->ci_parent = NULL;
774:
775: /* Drop the reference for ci_entry */
776: config_item_put(item);
777:
778: /* Drop the reference for ci_parent */
779: config_group_put(group);
780: }
781: }
SourceFile:fs/configfs/dir.c]
|
| 32/2 |
2026/07/29 06:11 |
llm |
expert |
0m
Model:
Tokens:
input: 21560
output: 19
thoughts: 42
Thoughts: Okay, here's my interpretation of those thoughts, summarized in the first person, assuming the context is code related and the user is an expert:
**Investigating a Potential Memory Leak or Dangling Pointer**
Hmm, hold on. I'm looking at this code, specifically around `item->ci_dentry`, and it's suddenly struck me that this isn't getting cleared here. That's a red flag. We need to be absolutely sure that `item->ci_dentry` is being properly managed to avoid a leak, or worse, a dangling pointer scenario. If it's not nullified at some point, we could have serious problems later on, especially if the underlying dentry is freed or reused elsewhere. So, the key question now becomes: when *is* `item->ci_dentry` cleared? I need to track down all the places where this member variable is assigned to understand its lifetime and ensure correct memory management. Time to fire up the search tools and start looking for every single instance where `ci_dentry` is actually being assigned a value.
|
| 33/2 |
2026/07/29 06:11 |
tool |
grepper |
0m
Args: map[Expression:ci_dentry =]
Results: map[Output:fs/configfs/dir.c=293=static int configfs_create_dir(struct config_item *item, struct dentry *dentry,
--
fs/configfs/dir.c-321- inode_set_mtime_to_ts(p_inode, inode_set_ctime_current(p_inode));
fs/configfs/dir.c:322: item->ci_dentry = dentry;
fs/configfs/dir.c-323- return 0;
--
fs/configfs/mount.c=63=static int configfs_fill_super(struct super_block *sb, struct fs_context *fc)
--
fs/configfs/mount.c-91- config_group_init(&configfs_root_group);
fs/configfs/mount.c:92: configfs_root_group.cg_item.ci_dentry = root;
fs/configfs/mount.c-93- root->d_fsdata = &configfs_root;
]
|
| 34/2 |
2026/07/29 06:12 |
llm |
expert |
0m
Model:
Tokens:
input: 22112
output: 30
thoughts: 104
Thoughts: **Investigating a Potential Memory Leak in configfs**
Okay, so it looks like `ci_dentry` in the `configfs` code isn't being explicitly cleared anywhere. That's a red flag. If `configfs_rmdir` removes a directory, `item->ci_dentry` is still pointing to the `dentry`, even after the operation. Hmm, the `dentry` itself gets unhashed and eventually dropped, right? But, and this is where it gets interesting, `dentry->d_fsdata` is pointing to the `sd` (presumably the superblock data or a related structure). The key question then is *when* is that `dentry->d_fsdata` cleared? I need to check the usual suspects like `configfs_d_iput` or any other function that might be responsible for releasing the dentry. This could be a significant issue if that `d_fsdata` isn't properly handled. This definitely needs more investigation.
|
| 35/2 |
2026/07/29 06:12 |
tool |
grepper |
0m
Args: map[Expression:d_fsdata = PathPrefix:fs/configfs/]
Results: map[Output:fs/configfs/dir.c=253=int configfs_make_dirent(struct configfs_dirent * parent_sd,
--
fs/configfs/dir.c-265- if (dentry)
fs/configfs/dir.c:266: dentry->d_fsdata = configfs_get(sd);
fs/configfs/dir.c-267-
--
fs/configfs/dir.c=426=static struct dentry * configfs_lookup(struct inode *dir,
--
fs/configfs/dir.c-466-
fs/configfs/dir.c:467: dentry->d_fsdata = configfs_get(sd);
fs/configfs/dir.c-468- sd->s_dentry = dentry;
--
fs/configfs/mount.c=63=static int configfs_fill_super(struct super_block *sb, struct fs_context *fc)
--
fs/configfs/mount.c-92- configfs_root_group.cg_item.ci_dentry = root;
fs/configfs/mount.c:93: root->d_fsdata = &configfs_root;
fs/configfs/mount.c-94- sb->s_root = root;
]
|
| 36/2 |
2026/07/29 06:12 |
llm |
expert |
0m
Model:
Tokens:
input: 22793
output: 38
thoughts: 421
Thoughts: **Analyzing a Potential Race Condition in configfs**
Okay, so I'm looking at this `configfs` code, and something's bugging me. It looks like `d_fsdata` is never explicitly cleared, which is a potential issue. Specifically, if `configfs_rmdir` gets called, it calls `configfs_remove_dirent`, which then calls `configfs_put(sd)`. This could eventually free the `sd` structure if its reference count, `sd->s_count`, hits zero. But here's the problem: `dentry->d_fsdata` *still* points to that freed `sd`, and `item->ci_dentry` still points to the now-dangling `dentry`.
Now, if another thread then calls `configfs_symlink`, it calls `get_target`, which finds the dentry via `kern_path`. Wait a second⦠if `configfs_rmdir` unhashed the dentry, then `kern_path` *shouldn't* find it, right? But what if `kern_path` finds it *before* `configfs_rmdir` unhashes it? That's the race!
Let's break down a potential race scenario.
* **Thread 1: `configfs_symlink`**
* **Thread 2: `configfs_rmdir`**
Here's the sequence I'm seeing:
* **Thread 1**: `get_target` calls `kern_path` and successfully finds the `dentry` representing the *target* directory (the one we're symlinking to).
* **Thread 1**: `configfs_get_config_item(dentry)` gets the `item` associated with the target directory, and increments its reference count.
* **Thread 1**: Returns the `target_item` to `configfs_symlink`.
* **Thread 1**: Calls `inode_lock(dir)` (this locks the *parent* directory of the symlink we're trying to create, not the target).
* **Thread 1**: Calls `create_link(parent_item, target_item, dentry)` and tries to create the symlink.
Meanwhile:
* **Thread 2**: `configfs_rmdir` is called on the *target* directory.
* **Thread 2**: It acquires the `mutex_lock(&configfs_symlink_mutex)`.
* **Thread 2**: It calls `configfs_detach_prep`. Does `configfs_detach_prep` call `configfs_remove_dir`? No, let me review `configfs_detach_prep` to see exactly what's happening.
|
| 37/2 |
2026/07/29 06:12 |
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]
|
| 38/2 |
2026/07/29 06:12 |
llm |
expert |
0m
Model:
Tokens:
input: 24580
output: 23
thoughts: 1084
Thoughts: **Analyzing a Potential Use-After-Free in ConfigFS Symlink Handling**
Okay, so I'm looking at how `configfs_rmdir` and `configfs_symlink` interact. I immediately see that they both use `configfs_symlink_mutex`, which implies some level of serialization. My initial thought is, "Okay, good, the mutex should prevent race conditions." But then a question pops up: If they are serialized, how can a `create_link` function end up with a freed `target_sd`? That doesn't seem right. Let's delve in.
I'm imagining a scenario: Thread 1 runs `configfs_symlink`, and Thread 2 runs `configfs_rmdir`. Thread 1 grabs the `target_item` in `get_target`. It then gets blocked waiting to acquire the `configfs_symlink_mutex` because Thread 2 is holding it.
Now, Thread 2 takes the `configfs_symlink_mutex` and proceeds. The crucial part is that `configfs_detach_prep` executes successfully. Thread 2 then releases the mutex. It then proceeds with the detachment, calling `configfs_detach_item`, which leads to `configfs_remove_dir`, and ultimately `configfs_remove_dirent`. In that function it calls `configfs_put(sd)`, and at this point, `sd` is freed because `s_count` goes to zero.
Here's the problem: Thread 1, still blocked, *then* acquires the `configfs_symlink_mutex`. Thread 1 calls `create_link`, where the `target_sd` pointer is retrieved. Now, in `create_link`, `configfs_get(target_sd)` triggers a warning: `WARN_ON(!atomic_read(&sd->s_count))`, because the reference count is zero. That's a huge red flag β the target directory *should not* have been removed by the time this gets called! In fact, we know that the sd has been freed and the reference count is now likely trash.
I see that `configfs_detach_prep` sets `CONFIGFS_USET_DROPPING` on `sd`. I remember that `create_link` checks this flag *after* it calls `configfs_get(target_sd)`. In other words, there's a potential race! We're doing a `configfs_get` on an object that may have already been freed, even if we eventually detect the dropping flag. Now the code calls `configfs_put(target_sd)` but the object has been freed in `configfs_remove_dirent` in Thread 2.
I need to confirm if `sd` is actually freed. Looking at `configfs_put`, the code calls `release_configfs_dirent` if the refcount drops to zero. That calls `kmem_cache_free(configfs_dir_cachep, sd)`. Yes, that means a use-after-free!
But why isn't KASAN catching it? I don't see any typical KASAN output, it's just a warning. This is a non-KASAN build. That explains it, and the warning is from `WARN_ON(!atomic_read(&sd->s_count))`, which triggers because the reference count on the freed `sd` is zero (or at least, *happens* to be zero).
Wait a minute, since the `sd` is freed and returned to the slab allocator, the first bytes *could* have been overwritten by the free list pointer, making it unlikely that the count is actually zero.
So, while the warning is triggered here, it indicates an issue where `sd` is *likely* freed. The real use-after-free might be in the subsequent access. I still need to verify this assumption through deeper investigation and testing.
|
| 39/2 |
2026/07/29 06:12 |
tool |
codesearch-struct-layout |
0m
Args: map[Name:configfs_dirent]
Results: map[Fields:[map[Name:s_count OffsetBits:0 SizeBits:32] map[Name:s_dependent_count OffsetBits:32 SizeBits:32] map[Name:s_sibling OffsetBits:64 SizeBits:128] map[Name:s_children OffsetBits:192 SizeBits:128] map[Name:s_links OffsetBits:320 SizeBits:32] map[Name:s_element OffsetBits:384 SizeBits:64] map[Name:s_type OffsetBits:448 SizeBits:32] map[Name:s_mode OffsetBits:480 SizeBits:16] map[Name:s_dentry OffsetBits:512 SizeBits:64] map[Name:s_iattr OffsetBits:576 SizeBits:64] map[Name:s_depth OffsetBits:640 SizeBits:32] map[Name:s_frag OffsetBits:704 SizeBits:64]]]
|
| 40/2 |
2026/07/29 06:12 |
llm |
expert |
0m
Model:
Tokens:
input: 26801
output: 38
thoughts: 96
Thoughts: **Analyzing Potential Memory Corruption in Kernel Slab Allocation**
Okay, so I'm looking at this code, and the first thing that jumps out is that `s_count` is sitting right at the beginning, offset zero, of this slab structure. That's a classic spot for a control field, but it also creates a vulnerability. If we free `sd`, and the slab allocator is using a freelist structure that starts with a pointer at offset zero, that `s_count` field is now going to contain the lower 32 bits of that freelist pointer. That's immediately problematic, because it means we could be writing a garbage value to it.
But hold on, let me think this through. If `SLAB_TYPESAFE_BY_RCU` isn't being used, the memory block could theoretically be reallocated before it gets cleaned up. This makes the potential corruption much more likely. Now, where is `configfs_dir_cachep` actually created? Knowing that is crucial because that's where the critical information for the structure is defined. That's probably the next logical step; I need to find the definition of the structure that is being allocated by `configfs_dir_cachep`.
|
| 41/2 |
2026/07/29 06:12 |
tool |
codesearch-definition-source |
0m
Error:
Args: map[ContextFile:fs/configfs/dir.c Name:configfs_dir_init]
Results: map[SourceCode: SourceFile:]
|
| 42/2 |
2026/07/29 06:12 |
llm |
expert |
0m
Model:
Tokens:
input: 27214
output: 27
thoughts: 14
Thoughts: Here's my thought process, as if I were considering this search:
**Diving into the Kernel's Configuration File System**
Okay, so I'm thinking about `configfs_dir_cachep`. This immediately suggests I'm looking into the core of how the kernel handles configuration files, specifically within the ConfigFS framework. The presence of "dir" and "cachep" in the variable name, strongly implies we're dealing with directory caching, likely at the struct level in the kernel.
I need to understand what this specific cache is used for. Is it caching directory entries themselves? Perhaps it's caching metadata about ConfigFS directories to speed up lookups or attribute retrieval. I have to figure out the use case to fully grok the code. Knowing the context will point me to the relevant kernel subsystems and modules.
I should also consider potential performance implications. Why is this caching mechanism used? Are there known performance bottlenecks or optimizations related to it? Thinking about concurrency issues is crucial; could there be race conditions with multiple processes accessing and modifying the cached data? That's a good place to start. I'm going to search to see where it's defined and used so I can understand the bigger picture of what it's all about.
|
| 43/2 |
2026/07/29 06:12 |
tool |
grepper |
0m
Args: map[Expression:kmem_cache_create.*configfs_dir_cache]
Results: map[Output:fs/configfs/mount.c=136=static int __init configfs_init(void)
--
fs/configfs/mount.c-139-
fs/configfs/mount.c:140: configfs_dir_cachep = kmem_cache_create("configfs_dir_cache",
fs/configfs/mount.c-141- sizeof(struct configfs_dirent),
]
|
| 44/2 |
2026/07/29 06:12 |
llm |
expert |
0m
Model:
Tokens:
input: 27632
output: 36
thoughts: 21
Thoughts: Here's my summary, as though I'm thinking through this process:
**Diving into the ConfigFS Initialization**
Okay, so I need to understand how ConfigFS gets off the ground. Let's start with `configfs_init` in `fs/configfs/mount.c`. That seems like the logical entry point. I'm assuming this is the function that's called when the configfs filesystem module loads or the filesystem is mounted. It's the standard pattern, really: initialization and setup happen at the mount time or module load, and the mountpoint is generally the first place I'd check for that. This function is *critical*; it's likely responsible for creating the root directory structure and the core data structures that will hold the virtual filesystem's information. I'll need to see what it's doing in terms of memory allocation, registration of filesystems and other setup activities, and any initialization of global or static variables related to the configfs structure. The kernel's mount code must be doing something to actually kick this function off, I guess I'll need to figure out where the `configfs_init` is called from.
|
| 45/2 |
2026/07/29 06:12 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/configfs/mount.c Name:configfs_init]
Results: map[SourceCode: 136: static int __init configfs_init(void)
137: {
138: int err = -ENOMEM;
139:
140: configfs_dir_cachep = kmem_cache_create("configfs_dir_cache",
141: sizeof(struct configfs_dirent),
142: 0, 0, NULL);
143: if (!configfs_dir_cachep)
144: goto out;
145:
146: err = sysfs_create_mount_point(kernel_kobj, "config");
147: if (err)
148: goto out2;
149:
150: err = register_filesystem(&configfs_fs_type);
151: if (err)
152: goto out3;
153:
154: return 0;
155: out3:
156: pr_err("Unable to register filesystem!\n");
157: sysfs_remove_mount_point(kernel_kobj, "config");
158: out2:
159: kmem_cache_destroy(configfs_dir_cachep);
160: configfs_dir_cachep = NULL;
161: out:
162: return err;
163: }
SourceFile:fs/configfs/mount.c]
|
| 46/2 |
2026/07/29 06:12 |
llm |
expert |
0m
Model:
Tokens:
input: 28350
output: 39
thoughts: 837
Thoughts: **Analyzing the Concurrency in configfs Directory Removal**
Okay, so I'm looking at this `configfs` code, and I need to figure out how `s_count` ends up being 0 when a directory is being removed. The code *doesn't* use `SLAB_TYPESAFE_BY_RCU`, so the `sd` (presumably a "super block directory") can be reused immediately after being freed. That's fine. But if it's freed, then the freelist pointer should be written to offset 0 within `sd`, right? And if the freelist pointer is NULL, that's how `s_count` would be zero, effectively signaling the end of the list. But I'm not entirely convinced it's freed!
Let's trace this removal through `configfs_rmdir`. I'm seeing this interaction with `frag->frag_sem`, and the call to `configfs_detach_rollback` if there's an interrupt. Okay, then `frag->frag_dead` becomes true. That looks like the directory removal is beginning. After the semaphore is released, `configfs_get_config_item` gets a reference. The `parent_item` is put, and then the critical part, `configfs_detach_group` or `configfs_detach_item` gets called.
I need to dig into `configfs_detach_item`. Aha! It calls `detach_attrs` and then *crucially* `configfs_remove_dir`. Looking into that, I see `configfs_remove_dirent` and that calls `configfs_put(sd)`. That's where the reference count should be dropped.
Now, my initial concern was about concurrent operations, especially if `configfs_symlink` is running concurrently, because it could block on `configfs_symlink_mutex`. But I see now that `configfs_rmdir` *releases* the `configfs_symlink_mutex` *before* it calls `configfs_detach_item`.
Let's look more closely at the code snippet provided. Yes, `configfs_rmdir` releases the mutex *before* calling anything that would detach and remove the item. So a concurrent `configfs_symlink` *could* acquire the mutex right after `configfs_rmdir` releases it. At this point, the important thing is that `configfs_rmdir` has **not yet** called `configfs_detach_item`. So `sd` is **not yet freed**!
So again, that leaves me stumped. If `sd` isn't yet freed, *why* is `sd->s_count` zero? I thought perhaps the dropping of the reference count to 0 would happen in `configfs_detach_prep`. It sets `CONFIGFS_USET_DROPPING` but it doesn't *change* `s_count`.
Okay, now it's starting to dawn on me. The `dentry` itself is holding a reference to the `sd` structure via `d_fsdata`. When `configfs_make_dirent` is called, the initial `s_count` is set to 1 (or possibly 2, depending on the details of how the dentry and item references interact). Now I think I see what's happening and I should be able to track this down.
|
| 47/2 |
2026/07/29 06:12 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/configfs/dir.c Name:configfs_new_dirent]
Results: map[SourceCode: 184: /*
185: * Allocates a new configfs_dirent and links it to the parent configfs_dirent
186: */
187: static struct configfs_dirent *configfs_new_dirent(struct configfs_dirent *parent_sd,
188: void *element, int type,
189: struct configfs_fragment *frag)
190: {
191: struct configfs_dirent * sd;
192:
193: sd = kmem_cache_zalloc(configfs_dir_cachep, GFP_KERNEL);
194: if (!sd)
195: return ERR_PTR(-ENOMEM);
196:
197: atomic_set(&sd->s_count, 1);
198: INIT_LIST_HEAD(&sd->s_children);
199: sd->s_element = element;
200: sd->s_type = type;
201: configfs_init_dirent_depth(sd);
202: spin_lock(&configfs_dirent_lock);
203: if (parent_sd->s_type & CONFIGFS_USET_DROPPING) {
204: spin_unlock(&configfs_dirent_lock);
205: kmem_cache_free(configfs_dir_cachep, sd);
206: return ERR_PTR(-ENOENT);
207: }
208: sd->s_frag = get_fragment(frag);
209:
210: /*
211: * configfs_lookup scans only for unpinned items. s_children is
212: * partitioned so that configfs_lookup can bail out early.
213: * CONFIGFS_PINNED and CONFIGFS_NOT_PINNED are not symmetrical. readdir
214: * cursors still need to be inserted at the front of the list.
215: */
216: if (sd->s_type & CONFIGFS_PINNED)
217: list_add_tail(&sd->s_sibling, &parent_sd->s_children);
218: else
219: list_add(&sd->s_sibling, &parent_sd->s_children);
220: spin_unlock(&configfs_dirent_lock);
221:
222: return sd;
223: }
SourceFile:fs/configfs/dir.c]
|
| 48/2 |
2026/07/29 06:12 |
llm |
expert |
0m
Model:
Tokens:
input: 30655
output: 39
thoughts: 151
Thoughts: **Understanding the ConfigFS Dentry Lifecycle**
Okay, let me break down this ConfigFS dentry creation process, because something seems a little off. I see that `configfs_new_dirent` initializes the `s_count` field of the `configfs_dir` (or `configfs_item`) to 1. That makes sense, it's the initial reference. Then, `configfs_make_dirent` comes along, and within that function, there's the line:
```c
if (dentry)
dentry->d_fsdata = configfs_get(sd);
```
Ah-ha! So, `configfs_get(sd)` is called, which increments the reference count. *That* explains why the count becomes 2. I was expecting the reference count to stay at 1. Wait a moment, I am just confirming my understanding here... `configfs_new_dirent` *returns* a struct with the `s_count` at 1. Then we immediately bump it up to 2 with `configfs_get()`. Fine, got it.
Okay, so where's the corresponding `configfs_put`? I know we need to balance those. Then, I remember when the `dentry` is eventually destroyed, `configfs_d_iput` will be called. Ah, and that is what will drop one of the references. So, when the dentry is released, the reference on the `configfs_dir` is reduced. That should bring it down to 1. That makes sense!
|
| 49/2 |
2026/07/29 06:12 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/configfs/dir.c Name:configfs_d_iput]
Results: map[SourceCode: 45: static void configfs_d_iput(struct dentry * dentry,
46: struct inode * inode)
47: {
48: struct configfs_dirent *sd = dentry->d_fsdata;
49:
50: if (sd) {
51: /* Coordinate with configfs_readdir */
52: spin_lock(&configfs_dirent_lock);
53: /*
54: * Set sd->s_dentry to null only when this dentry is the one
55: * that is going to be killed. Otherwise configfs_d_iput may
56: * run just after configfs_lookup and set sd->s_dentry to
57: * NULL even it's still in use.
58: */
59: if (sd->s_dentry == dentry)
60: sd->s_dentry = NULL;
61:
62: spin_unlock(&configfs_dirent_lock);
63: configfs_put(sd);
64: }
65: iput(inode);
66: }
SourceFile:fs/configfs/dir.c]
|
| 50/2 |
2026/07/29 06:12 |
llm |
expert |
0m
Model:
Tokens:
input: 31528
output: 38
thoughts: 2525
Thoughts: **Analysis of a ConfigFS Use-After-Free Vulnerability**
Okay, let's break this down. It seems the core issue revolves around a Use-After-Free (UAF) in the `configfs` filesystem, specifically when creating a symlink to a directory that's simultaneously being removed. The chain of events, from my perspective, is as follows:
First, `configfs_d_iput` calls `configfs_put(sd)` and `configfs_remove_dirent` also calls `configfs_put(sd)`. This means the `s_count` is 2: one for the dentry and the other for the s_children list. `configfs_rmdir`, through `configfs_remove_dirent`, releases the s_children list reference (decrementing s_count to 1). When the dentry is eventually destroyed, `configfs_d_iput` releases the dentry reference, and `s_count` drops to zero, and the `sd` is freed.
Now, here's where it gets interesting and where the bug lies: if `s_count` is 0, then both references have been dropped. But the `configfs_symlink` function is still active. How can it see a `s_count` of 0? This happens when `configfs_rmdir` calls `configfs_remove_dirent`, dropping the list reference, AND the dentry has been destroyed, dropping the dentry reference. The `configfs_symlink` function obtains a reference to `target_item` via `get_target` -> `configfs_get_config_item(dentry)`. Importantly, `configfs_get_config_item` *increments* `target_item->ci_kref` (a kref for the item, not the dentry!). It *doesn't* increment the dentry's refcount! `kern_path` increments the refcount, but `get_target` calls `path_put`, which immediately drops the dentry refcount.
`get_target` therefore drops the dentry reference. This implies that `configfs_symlink` only holds a reference to `target_item`, not to `dentry` or `sd`. In `create_link`, the code accesses `item->ci_dentry->d_fsdata`, which is the dangling pointer. The `ci_dentry` is set in `configfs_create_dir`, but no refcount is incremented, and no dget is called. So, `ci_dentry` is a *weak* pointer. It can point to freed memory if the dentry has been destroyed.
Since the dentry cache is `SLAB_TYPESAFE_BY_RCU`, the memory could still be valid during an RCU grace period, or it could be reallocated for another dentry. If it's reallocated, `d_fsdata` can point to anything. If `configfs_rmdir` is called, it calls `simple_rmdir`, which unhashes the dentry and drops a reference. If the last reference to the dentry is dropped, then the dentry is killed. At this point, the `sd` reference is also dropped, but the item is still alive. The `item->ci_dentry` still points to the *killed* dentry.
In this scenario, `create_link` reads `target_sd = item->ci_dentry->d_fsdata;`. Since the dentry is killed, `d_fsdata` might still point to the freed `sd` memory. When `configfs_get(target_sd)` is called, it accesses `sd->s_count`. Since the sd has been freed, the first 8 bytes of the freed `sd` can contain garbage, maybe the freelist pointer. The `s_count` will be 0 if the freelist pointer is NULL. This is the condition where `WARN_ON(!atomic_read(&sd->s_count))` triggers.
This means we've verified a Use-After-Free of `sd` (struct configfs_dirent). There's also a potential Use-After-Free of `dentry`, although the dentry structure itself is protected by RCU, its contents might have been overwritten if it was reallocated. The `d_fsdata` pointer could contain garbage, which could lead to wild pointer dereferences. The code calls `configfs_get(target_sd)`, and increments the counter, potentially corrupting the new allocated object. This is a classic Use-After-Free vulnerability, where accessing an object after it is freed leads to undefined behavior.
Now, `item->ci_dentry` is confirmed to be a
|
| 51/2 |
2026/07/29 06:13 |
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]
|
| 52/2 |
2026/07/29 06:13 |
llm |
expert |
0m
Model:
Tokens:
input: 35249
output: 32
thoughts: 38
Thoughts: Okay, here's my take on those thoughts, framed as an expert's internal monologue:
**Checking Filesystem Namespace Flags**
Alright, let's see. My initial hunch is that `.fs_flags` doesn't have the `FS_USERNS_MOUNT` bit set. I need to confirm this quickly. It's a key detail, and I don't want to make assumptions. Right, so I'm going to explicitly verify the presence of `FS_USERNS_MOUNT`. No point in guessing, time to check the flag directly.
|
| 53/2 |
2026/07/29 06:13 |
tool |
grepper |
0m
Error:
Args: map[Expression:FS_USERNS_MOUNT PathPrefix:fs/configfs/]
Results:
|
| 54/2 |
2026/07/29 06:13 |
llm |
expert |
0m
Model:
Tokens:
input: 35455
output: 38
thoughts: 123
Thoughts: **My Analysis of Configfs and Unprivileged Users**
Okay, so I'm thinking through this whole configfs situation. It's clear that, as I understand it, configfs *cannot* be mounted within a user namespace by an unprivileged user. That makes sense, but what I need to understand is the implications. It follows, therefore, that an unprivileged user can't mount it directly. But what about the common scenario where it's already mounted by root, typically at `/sys/kernel/config`? That's the real question. Can an unprivileged user actually *do* anything with it in that state?
My next step is clearly to look at the permissions. I need to see what's set on the configfs directories themselves. If I recall correctly, by default, they're owned by root and have either mode `0755` or `0775`. I need to verify that and see what that actually *means* practically for an unprivileged user. I should probably dig into the `configfs_create_dir` function to see what kind of permissions are set there, to be certain about the restrictions. It will give me the information that I need.
|
| 55/2 |
2026/07/29 06:13 |
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]
|
| 56/2 |
2026/07/29 06:13 |
llm |
expert |
0m
Model:
Tokens:
input: 36519
output: 40
thoughts: 340
Thoughts: **Analyzing the Syzkaller Reproducer's Implications**
Okay, let's break this down. The initial mode is `S_IFDIR | S_IRWXU | S_IRUGO | S_IXUGO`, which translates to `rwxr-xr-x`. That means root has write access, and everyone else can read and execute. The crucial question is, can a subsystem mess with ownership or permissions? Some do, but typically, directory creation and removal are firmly in root's domain.
Now, the syzkaller reproducer throws a wrench in this. It's using `mkdirat` and `symlinkat` on paths under `./config/usb_gadget/...`. The real kicker is the mount call:
```c
syscall(__NR_mount, /*src=*/0ul, /*dst=*/0x200000000080ul, /*type=*/0x2000000000c0ul, /*flags=*/0ul, /*data=*/0ul);
```
It's *mounting* configfs! That's a red flag. If syzkaller can mount configfs, it *has* to be running as root, or within a user namespace with `CAP_SYS_ADMIN`...but configfs shouldn't allow the latter. I'm pretty sure configfs doesn't have `FS_USERNS_MOUNT`. And if that's the case, it *must* be global root that's doing the mounting. This is huge.
So, this bug is only exploitable by global root, which is a major constraint for any sort of exploit. But let's just make sure. Let me double check if `FS_USERNS_MOUNT` was added to configfs recently. We need to be absolutely sure.
|
| 57/2 |
2026/07/29 06:13 |
tool |
git-log |
0m
Error:
Args: map[Count:5 PathPrefix:fs/configfs/mount.c SymbolName:configfs_fs_type]
Results:
|
| 58/2 |
2026/07/29 06:13 |
llm |
expert |
0m
Model:
Tokens:
input: 37319
output: 40
thoughts: 20
Thoughts: Okay, here's my summary, tailored for an expert audience, as if it were my own thought process:
**Optimizing Symbol Lookup with Source Paths**
Alright, looking at this code again, I'm thinking I should probably rethink how I'm handling symbol lookups. The current approach uses `PathPrefix` with `SymbolName`, but that feels a little⦠clunky. I bet using `SourcePath` instead would be a much more elegant solution, and probably more efficient too. The `SourcePath` context inherently provides a better, more accurate grounding for the symbol resolution, especially when dealing with complex projects or situations where relative paths become ambiguous. `PathPrefix` seems a bit like a workaround, whereas `SourcePath` seems like a more direct, and therefore potentially more performant, approach. Let's explore how to implement this!
|
| 59/2 |
2026/07/29 06:13 |
tool |
git-log |
0m
Args: map[Count:5 SourcePath:fs/configfs/mount.c SymbolName:configfs_fs_type]
Results: map[Output:b1494e6bc47c configfs, securityfs: kill_litter_super() not needed
6bc62f2067d3 vfs: Convert configfs to use the new mount API
fc14f2fef682 convert get_sb_single() users
7063fbf22611 [PATCH] configfs: User-driven configuration filesystem
]
|
| 60/2 |
2026/07/29 06:14 |
llm |
expert |
0m
Model:
Tokens:
input: 37662
output: 38
thoughts: 16
Thoughts: Okay, here's my take on those thoughts, framed as an expert's internal monologue:
**Investigating `configfs_fs_type`**
Right, let's take a quick look at that `configfs_fs_type` structure. Always good to refresh the memory before diving into anything deeper. It's the cornerstone of the ConfigFS implementation, so a thorough understanding of its definition is crucial for any meaningful work or debugging in this area. I need to make sure I have the exact structure definition fresh in my mind before I start looking at anything specific. Gotta double-check that I haven't missed any recent changes or subtle nuances. Right, let's see what the current source says...
|
| 61/2 |
2026/07/29 06:14 |
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]
|
| 62/2 |
2026/07/29 06:14 |
llm |
expert |
0m
Model:
Tokens:
input: 37999
output: 29
thoughts: 116
Thoughts: **Investigating ConfigFS Permissions**
Okay, so the initial question is clear: we're not dealing with `FS_USERNS_MOUNT` here. That means ConfigFS, as it's currently set up, is exclusively mounted by global root, which is what I suspected. Now, the key question arises: can an unprivileged user even *touch* a ConfigFS that's already mounted? Alright, I remember the answer there, they can read it. That's fine, but what about write access, directory creation, symlinks? No dice. The permissions are standard: `rwxr-xr-x`, root owns it.
Now, I need to dig deeper. Are there *any* ConfigFS subsystems that dynamically change directory ownership, granting write privileges to an unprivileged user under specific conditions? That's the real crux of this, the nuance. I need to dive into the code and see if there are any `chown` or `chmod` operations happening within ConfigFS itself. This is where the fun starts. Let's see what we can find.
|
| 63/2 |
2026/07/29 06:14 |
tool |
grepper |
0m
Args: map[Expression:i_uid = PathPrefix:fs/configfs/]
Results: map[Output:fs/configfs/inode.c=94=static inline void set_inode_attr(struct inode * inode, struct iattr * iattr)
--
fs/configfs/inode.c-96- inode->i_mode = iattr->ia_mode;
fs/configfs/inode.c:97: inode->i_uid = iattr->ia_uid;
fs/configfs/inode.c-98- inode->i_gid = iattr->ia_gid;
]
|
| 64/2 |
2026/07/29 06:14 |
llm |
expert |
0m
Model:
Tokens:
input: 38500
output: 37
thoughts: 45
Thoughts: Okay, here's my take on those thoughts, framed as an expert's internal monologue:
**Investigating ConfigFS Permissions**
Right, so `set_inode_attr` is the key entry point whenever inode attributes are modified. Interesting. The question now is, can root, through something like `chown`, reassign ownership of a `configfs` directory to a regular, unprivileged user? That's the crux of potential security implications, isn't it? Gotta dive into that. Let's start by scrutinizing `configfs_setattr`. That's where the permissions logic for `configfs` should reside. This will reveal if any checks are in place to prevent privileged users from circumventing intended restrictions. This should be where the answer lies.
|
| 65/2 |
2026/07/29 06:14 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/configfs/inode.c Name:configfs_setattr]
Results: map[SourceCode: 35: int configfs_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
36: struct iattr *iattr)
37: {
38: struct inode * inode = d_inode(dentry);
39: struct configfs_dirent * sd = dentry->d_fsdata;
40: struct iattr * sd_iattr;
41: unsigned int ia_valid = iattr->ia_valid;
42: int error;
43:
44: if (!sd)
45: return -EINVAL;
46:
47: sd_iattr = sd->s_iattr;
48: if (!sd_iattr) {
49: /* setting attributes for the first time, allocate now */
50: sd_iattr = kzalloc_obj(struct iattr);
51: if (!sd_iattr)
52: return -ENOMEM;
53: /* assign default attributes */
54: sd_iattr->ia_mode = sd->s_mode;
55: sd_iattr->ia_uid = GLOBAL_ROOT_UID;
56: sd_iattr->ia_gid = GLOBAL_ROOT_GID;
57: sd_iattr->ia_atime = sd_iattr->ia_mtime =
58: sd_iattr->ia_ctime = current_time(inode);
59: sd->s_iattr = sd_iattr;
60: }
61: /* attributes were changed atleast once in past */
62:
63: error = simple_setattr(idmap, dentry, iattr);
64: if (error)
65: return error;
66:
67: if (ia_valid & ATTR_UID)
68: sd_iattr->ia_uid = iattr->ia_uid;
69: if (ia_valid & ATTR_GID)
70: sd_iattr->ia_gid = iattr->ia_gid;
71: if (ia_valid & ATTR_ATIME)
72: sd_iattr->ia_atime = iattr->ia_atime;
73: if (ia_valid & ATTR_MTIME)
74: sd_iattr->ia_mtime = iattr->ia_mtime;
75: if (ia_valid & ATTR_CTIME)
76: sd_iattr->ia_ctime = iattr->ia_ctime;
77: if (ia_valid & ATTR_MODE) {
78: umode_t mode = iattr->ia_mode;
79:
80: if (!in_group_p(inode->i_gid) && !capable(CAP_FSETID))
81: mode &= ~S_ISGID;
82: sd_iattr->ia_mode = sd->s_mode = mode;
83: }
84:
85: return error;
86: }
SourceFile:fs/configfs/inode.c]
|
| 66/2 |
2026/07/29 06:14 |
llm |
expert |
0m
Model:
Tokens:
input: 39592
output: 29
thoughts: 2050
Thoughts: **Bug Analysis and Assessment**
Okay, let's break this down systematically. So, the initial premise is that root can `chown` a `configfs` directory to an unprivileged user, allowing them to potentially trigger a bug. However, the default setup means this isn't immediately exploitable, as `configfs` is root-owned. The question, then, is whether there are any mechanisms where unprivileged processes can interact with `configfs` in a way that leads to this.
My initial thought was, "probably not." User namespaces are out, and since `usb_gadget` is mentioned as the reproducer, I need to check the ownership of `/sys/kernel/config/usb_gadget`. Nope, root-owned. So, unprivileged users aren't hitting this by default.
Now, the important part: exploitability. It's a Use-After-Free (UAF) of `struct configfs_dirent`, triggered by root. Given it's a UAF, it's *exploitable* for memory corruption. But it does require root privileges to trigger the vulnerability. However, it's worth considering whether the vulnerability breaks lockdown or confidential computing, although it's likely not.
Given the exploit requirements, let's analyze the following:
* **Exploitable:** Yes (UAF).
* **Denial of Service:** Yes (kernel crash/warning).
* **Accessible From Unprivileged Processes:** No.
* **Accessible From User Namespaces:** No.
* **VM Guest Trigger:** No (unless the guest kernel is compromised and acts as root, then it is a bug in the guest kernel, triggered by guest root).
* **VM Host Trigger in The Confidential Computing Context:** No.
* **Ethernet Network Trigger:** No.
* **Other Remote Trigger:** No.
* **Peripheral Trigger:** No.
* **Malicious Filesystem Trigger:** No (not a block-based filesystem).
Let's double-check. Is there a setuid program messing with `configfs`? No. Can we call `configfs_symlink` indirectly? Not that I can see, `symlinkat` is a direct filesystem operation.
So, let's dive into the core of the bug. It involves two threads: one creating a symlink (`symlinkat` -> `configfs_symlink`) and another removing the target directory (`unlinkat` -> `configfs_rmdir`).
In `configfs_symlink`, `get_target` resolves the symlink target, and after retrieving the `config_item`, it gets a reference to the `dentry` via `configfs_get_config_item`. Then, after it does its work, `path_put` releases that reference.
If the second thread removes the target directory, `configfs_rmdir` detaches the item, removes the directory, and unhashes the `dentry`. Because the refcount from the initial thread has already been dropped, the refcount can drop to zero, killing the `dentry`. At this point, the `sd` is freed.
Then, the first thread continues.
In `create_link`, it accesses `target_sd = item->ci_dentry->d_fsdata;` and attempts to `configfs_get(target_sd)`. The critical question is whether `item->ci_dentry` is still valid after the directory is gone.
So, I had to trace how the `dentry` is linked to the `config_item`. It turns out that `configfs_create_dir` just assigns the `dentry` to `item->ci_dentry` without taking a reference. So, the `dentry` isn't protected from being freed.
And it doesn't appear that the `dentry` gets cleared in any of the relevant deallocation paths like `configfs_d_iput`, `configfs_remove_dir`, or `unlink_obj`. Therefore, `item->ci_dentry` becomes a dangling pointer!
So, `item->ci_dentry` is definitely dangling, which means any access to it after the dentry is killed is a UAF. `target_sd = item->ci_dentry->d_fsdata;` reads `d_fsdata` from that killed `dentry`. This area is protected by `SLAB_TYPESAFE_BY_RCU`, so the memory may not be immediately returned. However, the `d_fsdata` can point to garbage memory. Then, `configfs_get(target_sd)` touches the freed `sd`.
This is a very, very solid UAF.
|
| 67/2 |
2026/07/29 06:14 |
tool |
grepper |
0m
Args: map[Expression:ci_dentry PathPrefix:fs/configfs/]
Results: map[Output:fs/configfs/dir.c=293=static int configfs_create_dir(struct config_item *item, struct dentry *dentry,
--
fs/configfs/dir.c-321- inode_set_mtime_to_ts(p_inode, inode_set_ctime_current(p_inode));
fs/configfs/dir.c:322: item->ci_dentry = dentry;
fs/configfs/dir.c-323- return 0;
--
fs/configfs/dir.c=734=static int populate_groups(struct config_group *group,
--
fs/configfs/dir.c-736-{
fs/configfs/dir.c:737: struct dentry *parent = group->cg_item.ci_dentry;
fs/configfs/dir.c-738- struct config_group *new_group;
--
fs/configfs/dir.c=1097=static int configfs_do_depend_item(struct configfs_dirent *subsys_sd,
--
fs/configfs/dir.c-1112- */
fs/configfs/dir.c:1113: p = target->ci_dentry->d_fsdata;
fs/configfs/dir.c-1114- p->s_dependent_count += 1;
--
fs/configfs/dir.c=1193=void configfs_undepend_item(struct config_item *target)
--
fs/configfs/dir.c-1202-
fs/configfs/dir.c:1203: sd = target->ci_dentry->d_fsdata;
fs/configfs/dir.c-1204- BUG_ON(sd->s_dependent_count < 1);
--
fs/configfs/dir.c=1225=int configfs_depend_item_unlocked(struct configfs_subsystem *caller_subsys,
--
fs/configfs/dir.c-1258- */
fs/configfs/dir.c:1259: inode_lock(d_inode(root->cg_item.ci_dentry));
fs/configfs/dir.c-1260-
--
fs/configfs/dir.c-1265- subsys_sd = configfs_find_subsys_dentry(
fs/configfs/dir.c:1266: root->cg_item.ci_dentry->d_fsdata,
fs/configfs/dir.c-1267- &target_subsys->su_group.cg_item);
--
fs/configfs/dir.c-1270- } else {
fs/configfs/dir.c:1271: subsys_sd = target_subsys->su_group.cg_item.ci_dentry->d_fsdata;
fs/configfs/dir.c-1272- }
--
fs/configfs/dir.c-1282- */
fs/configfs/dir.c:1283: inode_unlock(d_inode(root->cg_item.ci_dentry));
fs/configfs/dir.c-1284-
--
fs/configfs/dir.c=1751=int configfs_register_group(struct config_group *parent_group,
--
fs/configfs/dir.c-1766-
fs/configfs/dir.c:1767: parent = parent_group->cg_item.ci_dentry;
fs/configfs/dir.c-1768-
--
fs/configfs/dir.c-1774- spin_lock(&configfs_dirent_lock);
fs/configfs/dir.c:1775: configfs_dir_set_ready(group->cg_item.ci_dentry->d_fsdata);
fs/configfs/dir.c-1776- spin_unlock(&configfs_dirent_lock);
--
fs/configfs/dir.c=1796=void configfs_unregister_group(struct config_group *group)
--
fs/configfs/dir.c-1798- struct configfs_subsystem *subsys = group->cg_subsys;
fs/configfs/dir.c:1799: struct dentry *dentry = dget(group->cg_item.ci_dentry);
fs/configfs/dir.c:1800: struct dentry *parent = group->cg_item.ci_parent->ci_dentry;
fs/configfs/dir.c-1801- struct configfs_dirent *sd = dentry->d_fsdata;
--
fs/configfs/dir.c=1934=void configfs_unregister_subsystem(struct configfs_subsystem *subsys)
--
fs/configfs/dir.c-1936- struct config_group *group = &subsys->su_group;
fs/configfs/dir.c:1937: struct dentry *dentry = dget(group->cg_item.ci_dentry);
fs/configfs/dir.c-1938- struct dentry *root = dentry->d_sb->s_root;
--
fs/configfs/file.c=446=int configfs_create_file(struct config_item * item, const struct configfs_attribute * attr)
fs/configfs/file.c-447-{
fs/configfs/file.c:448: struct dentry *dir = item->ci_dentry;
fs/configfs/file.c-449- struct configfs_dirent *parent_sd = dir->d_fsdata;
--
fs/configfs/file.c=467=int configfs_create_bin_file(struct config_item *item,
--
fs/configfs/file.c-469-{
fs/configfs/file.c:470: struct dentry *dir = item->ci_dentry;
fs/configfs/file.c-471- struct configfs_dirent *parent_sd = dir->d_fsdata;
--
fs/configfs/mount.c=63=static int configfs_fill_super(struct super_block *sb, struct fs_context *fc)
--
fs/configfs/mount.c-91- config_group_init(&configfs_root_group);
fs/configfs/mount.c:92: configfs_root_group.cg_item.ci_dentry = root;
fs/configfs/mount.c-93- root->d_fsdata = &configfs_root;
--
fs/configfs/symlink.c=77=static int create_link(struct config_item *parent_item,
--
fs/configfs/symlink.c-80-{
fs/configfs/symlink.c:81: struct configfs_dirent *target_sd = item->ci_dentry->d_fsdata;
fs/configfs/symlink.c-82- char *body;
--
fs/configfs/symlink.c-103- if (!ret)
fs/configfs/symlink.c:104: ret = configfs_create_link(target_sd, parent_item->ci_dentry,
fs/configfs/symlink.c-105- dentry, body);
]
|
| 68/2 |
2026/07/29 06:14 |
llm |
expert |
0m
Model:
Tokens:
input: 44550
output: 66
thoughts: 1899
Thoughts: **Bug Analysis and Classification**
Okay, let's break this down. It looks like we've got a Use-After-Free (UAF) vulnerability, potentially affecting both `struct configfs_dirent` and `struct dentry`. The key is this `configfs_depend_item_unlocked` function and the way it handles dependencies. If a `target`'s `dentry` gets killed while something is depending on it, that's the UAF. The reproducer leverages this via `symlinkat`, which is interesting because that means it's directly accessible from userspace.
The good news is, it's not a remote trigger. We're looking at a local privilege escalation, which is bad, but not as broadly impactful as a network-based exploit. The vulnerability is exploitable, definitely a Denial of Service. No unprivileged processes can access this, unless, of course, the sysadmin has explicitly changed ownership to allow for that, which isn't the default. And no user namespaces either, so we don't need to worry about that attack vector.
No VM Guest or Host triggers, no network or peripheral triggers, no malicious filesystem either. Now, can this be triggered by a malicious USB device? Nope, the repro uses `mkdirat` and `symlinkat` on configfs, it's a local userspace bug. The fact that `usb_gadget` is there is just because it uses configfs for configuration, any configfs subsystem supporting symlinks could be used to trigger this.
So, the attack vector is purely local, requiring write access to configfs. It's root-only by default. Now, could some Android devices change the permissions of configfs to `system` or `usb`? Yeah, that happens a lot to enable USB gadget configuration. So if a compromised `system` process can exploit this, it's a kernel code execution (root) bug, BUT, and this is important, a typical unprivileged app does not have access to configfs, even on Android. So, "Accessible From Unprivileged Processes" is No.
Now, I was looking at `configfs_get`. If `d_fsdata` is NULL, the code handles it by returning NULL, but then `create_link` dereferences the pointer, leading to a NULL pointer dereference, a kernel panic, basically a DoS. If `d_fsdata` points to freed memory, it's a UAF.
So the reproducer does `symlinkat` and then `unlinkat` (rmdir) on the same target directory. Single thread, yes, but syzkaller runs the repro in multiple threads concurrently. The race is between `symlinkat` and `unlinkat`.
The fix? Make `item->ci_dentry` hold a reference to the `dentry`, or clear it when the `dentry` is killed, or, instead of using `item->ci_dentry`, use the `dentry` that was found by `get_target`. If `get_target` returned the `dentry` (and kept its refcount), then `create_link` could use that `dentry` safely! `configfs_symlink` could just keep the `path` and pass the `dentry` to `create_link`.
**Final Classification:**
* **Exploitable:** Yes (UAF of `configfs_dirent` and potentially `dentry`).
* **Denial Of Service:** Yes (kernel panic / warning).
* **Accessible From Unprivileged Processes:** No (configfs is root-only).
* **Accessible From User Namespaces:** No (configfs cannot be mounted in userns).
* **VM Guest Trigger:** No.
* **VM Host Trigger in The Confidential Computing Context:** No.
* **Ethernet Network Trigger:** No.
* **Other Remote Trigger:** No.
* **Peripheral Trigger:** No.
* **Malicious Filesystem Trigger:** No.
|
| 69/2 |
2026/07/29 06:14 |
tool |
set-results |
0m
Args: map[DenialOfService:true Exploitable:true FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:false UserNamespace:false VMGuestTrigger:false VMHostTrigger:false]
Results: map[DenialOfService:true Exploitable:true FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:false UserNamespace:false VMGuestTrigger:false VMHostTrigger:false]
|
| 70/2 |
2026/07/29 06:14 |
llm |
expert |
0m
Model:
Tokens:
input: 47438
output: 934
thoughts: 645
Thoughts: **My Analysis of the Configfs Use-After-Free Vulnerability**
Okay, I've got the results locked down, and now it's time to craft the final response. This bug is a classic race condition with a nasty UAF at its heart. Let me walk through the core logic, as I understand it.
Essentially, it boils down to this: Thread 1, in a `symlinkat` call, is setting up a symlink within the `configfs` filesystem. This triggers `configfs_symlink`, which eventually resolves the target path. During the target path resolution with `get_target`, the code grabs a reference to a `dentry` via `kern_path`, then later *drops* that `dentry` reference before proceeding. Meanwhile, a second thread, Thread 2, calls `rmdir` on that *same* target directory. `configfs_rmdir` subsequently unhashes the `dentry` and drops its references. Since Thread 1 already dropped *its* reference, the `dentry` refcount can drop to 0, which means the kernel is likely going to free the memory.
Hereβs where it gets interesting: the `dentry` gets killed, and a call to `configfs_d_iput` is made, which drops the ref to the `configfs_dirent` or `sd` structure. Now, back in Thread 1, the code continues into `create_link`. It accesses the `target_item->ci_dentry` field, which is a *weak* pointer. Because it's weak, that `dentry` now points to a freed memory location. The code then calls `target_sd = target_item->ci_dentry->d_fsdata` and calls `configfs_get(target_sd)`. Crucially, this tries to access the memory of the already freed `configfs_dirent`. This UAF is the crux of the issue.
The outcome depends on whether the freed memory has been reallocated. If the memory hasn't been touched, it will likely hit a zero-count check in the freed memory, triggering a `WARN_ON(!atomic_read(&sd->s_count))`. But if the memory has been reallocated (more likely in a real-world scenario with heap spraying), accessing the freed `sd` memory leads to memory corruption as the first four bytes of the new object is incremented.
From a security standpoint, this is exploitable. It's a textbook UAF, and an attacker can craft a race condition between `symlinkat` and `rmdir` to trigger it. Heap spraying is a likely technique for memory layout control, allowing an attacker to control the contents of the reallocated memory. This can lead to memory corruption, which is a classic pathway to privilege escalation.
Beyond exploitation, it's also a clear Denial-of-Service vector. This bug can cause crashes, potentially through NULL pointer dereferences if the freed `d_fsdata` pointer is accessed or simply the memory corruption leading to a crash.
The good news (relatively speaking) is that unprivileged users *can't* directly trigger this vulnerability. Configfs is mounted with restrictive permissions. Unprivileged users can't create directories or symlinks within it. User namespaces are also off the table, since configfs doesn't support them.
I will make sure to present these findings clearly and concisely, highlighting the critical aspects of the vulnerability.
|
| 71/1 |
2026/07/29 06:15 |
action |
format-explanation |
0m
Results: map[Explanation:Based on the analysis of the bug report and the kernel source code, here is the
assessment of the vulnerability:
### Bug Mechanism
The bug is a **Use-After-Free (UAF)** vulnerability in the `configfs` subsystem,
caused by a race condition between `symlinkat()` and `rmdir()`.
1. **Thread 1 (`symlinkat`)**: Enters `configfs_symlink()` and calls
`get_target()` to resolve the target path. `get_target()` uses `kern_path()` to
find the target `dentry`, retrieves the associated `config_item` (incrementing
its refcount), and then drops the `path` (which drops the `dentry` refcount).
2. **Thread 2 (`rmdir`)**: Enters `configfs_rmdir()` on the target directory. It
detaches the item, removes the directory, and unhashes the `dentry`. Since
Thread 1 already dropped its `dentry` reference, the `dentry` refcount drops to
0.
3. **Thread 2 (`rmdir`)**: The `dentry` is killed, triggering
`configfs_d_iput()`. This drops the reference to the associated
`configfs_dirent` (`sd`). The `sd` refcount reaches 0, and the `configfs_dirent`
structure is freed back to the slab cache.
4. **Thread 1 (`symlinkat`)**: Continues execution and calls `create_link()`. It
accesses `target_item->ci_dentry`. Because `ci_dentry` is a weak pointer (it
does not hold a reference to the `dentry`), it now points to the killed
`dentry`.
5. **Thread 1 (`symlinkat`)**: `create_link()` reads `target_sd =
target_item->ci_dentry->d_fsdata` and calls `configfs_get(target_sd)`. This
accesses the freed `configfs_dirent` (UAF).
6. If the memory hasn't been reallocated, the first 8 bytes contain the slab
freelist pointer (which is `NULL` at the end of the list), making `sd->s_count`
evaluate to `0`. This triggers the `WARN_ON(!atomic_read(&sd->s_count))` seen in
the crash report. If the memory has been reallocated, `configfs_get()` will
increment the first 4 bytes of the new object, causing memory corruption.
### Security Impact Assessment
* **Exploitable: Yes**
This is a classic Use-After-Free (UAF) vulnerability. An attacker can reliably
trigger the race condition to cause the kernel to operate on freed memory. By
using heap spraying techniques, an attacker could reallocate the freed
`configfs_dirent` (or the killed `dentry` protected by `SLAB_TYPESAFE_BY_RCU`)
with controlled data, leading to memory corruption, arbitrary code execution,
and privilege escalation.
* **Denial Of Service: Yes**
Triggering the bug can easily lead to a kernel panic (e.g., a NULL pointer
dereference if `d_fsdata` becomes NULL upon reallocation) or system
instability due to memory corruption, resulting in a Denial of Service.
* **Accessible From Unprivileged Processes: No**
Configfs is typically mounted at `/sys/kernel/config` with `root:root`
ownership and `0755` permissions. Unprivileged users cannot create directories
or symlinks within configfs, which is required to trigger this bug. (Note: On
some embedded/Android systems, specific configfs subdirectories might be
`chown`ed to a dedicated user like `system`, but it remains inaccessible to a
*typical* unprivileged process).
* **Accessible From User Namespaces: No**
The `configfs` filesystem does not have the `FS_USERNS_MOUNT` flag. Therefore,
it cannot be mounted by an unprivileged user inside a user namespace.
* **Other Triggers (Network, Remote, Peripheral, Malicious FS, VM Guest/Host):
No**
The bug is strictly a local filesystem API race condition in a
pseudo-filesystem (`configfs`). It cannot be triggered over the network, via
external peripherals, by mounting a malicious disk image, or across VM
boundaries.]
|