| 0/0 |
2026/09/02 20:16 |
flow |
patching |
2h05m
Results: map[AckedBy:[] Fixes:map[AuthorEmail:viro@zeniv.linux.org.uk AuthorName:Al Viro Hash:351e5d869e5ac10cb40c78b5f2d7dfc816ad4587 Title:configfs: fix a deadlock in configfs_symlink()] KernelBranch:master KernelCommit:cee9395acd8043be0644b25c34bfa86623f2b935 KernelRepo:git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git PatchDescription:configfs: fix use-after-free in configfs_symlink()
In configfs_symlink(), inode_unlock(dir) is called before resolving the
symlink target via get_target() to avoid deadlocks during pathname lookup.
When configfs_symlink() operates within a default group (such as
ports/1/subsystems/ in NVMe-oF target), parent_item is an embedded default
group inside the dynamically allocated parent object (struct nvmet_port).
Because default groups are embedded within their containing structure,
holding a reference to parent_item alone does not prevent the containing
parent structure from being freed when a concurrent configfs_rmdir()
removes the root directory.
When configfs_rmdir() runs concurrently while inode_lock(dir) is dropped in
configfs_symlink(), it detaches all default groups, unlinks the root group,
sets frag_dead to true, and drops the subsystem's reference to the root
group. Once the root object's refcount drops to zero, the entire containing
structure is freed via kfree(). When configfs_symlink() re-acquires
inode_lock(dir), accessing parent_item in allow_link() accesses freed
memory, causing a slab-use-after-free:
BUG: KASAN: slab-use-after-free in nvmet_port_subsys_allow_link+0x41/0x2e0
drivers/nvme/target/configfs.c:1059
Read of size 8 at addr ffff88811c6d44c8
Call Trace:
<TASK>
kasan_report+0x117/0x150 mm/kasan/report.c:595
nvmet_port_subsys_allow_link+0x41/0x2e0
drivers/nvme/target/configfs.c:1059
configfs_symlink+0x59a/0x1030 fs/configfs/symlink.c:196
vfs_symlink+0x18b/0x330 fs/namei.c:5794
filename_symlinkat+0x1cd/0x410 fs/namei.c:5819
do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
entry_SYSCALL_64_after_hwframe+0x77/0x7f
</TASK>
Allocated by task 5847:
kasan_save_stack mm/kasan/common.c:57 [inline]
kasan_save_track+0x3e/0x80 mm/kasan/common.c:78
__kasan_kmalloc+0x93/0xb0 mm/kasan/common.c:415
__kmalloc_cache_noprof+0x321/0x600 mm/slub.c:5563
nvmet_ports_make+0xe6/0xf00 drivers/nvme/target/configfs.c:2051
configfs_mkdir+0x4f6/0x9e0 fs/configfs/dir.c:1360
vfs_mkdir+0x40c/0x620 fs/namei.c:5410
filename_mkdirat+0x285/0x510 fs/namei.c:5443
do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
entry_SYSCALL_64_after_hwframe+0x77/0x7f
Freed by task 5847:
kasan_save_stack mm/kasan/common.c:57 [inline]
kasan_save_track+0x3e/0x80 mm/kasan/common.c:78
__kasan_slab_free+0x5c/0x80 mm/kasan/common.c:285
kfree+0x1c5/0x650 mm/slub.c:6792
config_item_release+0x13a/0x2d0 fs/configfs/item.c:137
configfs_rmdir+0x885/0x950 fs/configfs/dir.c:1571
vfs_rmdir+0x3e6/0x6a0 fs/namei.c:5515
filename_rmdir+0x292/0x520 fs/namei.c:5572
do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
entry_SYSCALL_64_after_hwframe+0x77/0x7f
Fix this by:
1. Adding configfs_get_root_item() to walk up the dentry tree past any
default groups (CONFIGFS_USET_DEFAULT) while holding inode_lock(dir) to
acquire a reference to the root object (root_item), preventing the
containing structure from being freed while inode_lock(dir) is dropped.
2. Checking sd->s_frag->frag_dead after re-acquiring inode_lock(dir) to
detect if the directory was removed during the unlocked window, returning
-ENOENT instead of calling allow_link().
3. Releasing parent_item before root_item in the out_put cleanup path so
that the embedded item reference is dropped while the memory of the
containing root object remains pinned. PatchDiff:diff --git a/fs/configfs/symlink.c b/fs/configfs/symlink.c
index 31eb28b27..5fe3722bd 100644
--- a/fs/configfs/symlink.c
+++ b/fs/configfs/symlink.c
@@ -131,6 +131,19 @@ static int get_target(const char *symname, struct config_item **target,
return 0;
}
+static struct config_item *configfs_get_root_item(struct dentry *dentry)
+{
+ struct dentry *root_dentry = dentry;
+ struct configfs_dirent *sd;
+
+ while (root_dentry) {
+ sd = root_dentry->d_fsdata;
+ if (!sd || !(sd->s_type & CONFIGFS_USET_DEFAULT))
+ break;
+ root_dentry = root_dentry->d_parent;
+ }
+ return root_dentry ? configfs_get_config_item(root_dentry) : NULL;
+}
int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
struct dentry *dentry, const char *symname)
@@ -139,6 +152,7 @@ int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
struct configfs_dirent *sd;
struct config_item *parent_item;
struct config_item *target_item = NULL;
+ struct config_item *root_item = NULL;
const struct config_item_type *type;
sd = dentry->d_parent->d_fsdata;
@@ -157,6 +171,12 @@ int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
!type->ct_item_ops->allow_link)
goto out_put;
+ /*
+ * Pin the root object so that the parent_item (which may be an
+ * embedded default group) is not freed while we drop the lock.
+ */
+ root_item = configfs_get_root_item(dentry->d_parent);
+
/*
* This is really sick. What they wanted was a hybrid of
* link(2) and symlink(2) - they wanted the target resolved
@@ -192,8 +212,12 @@ int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
else
ret = inode_permission(&nop_mnt_idmap, dir,
MAY_WRITE | MAY_EXEC);
- if (!ret)
- ret = type->ct_item_ops->allow_link(parent_item, target_item);
+ if (!ret) {
+ if (sd->s_frag->frag_dead)
+ ret = -ENOENT;
+ else
+ ret = type->ct_item_ops->allow_link(parent_item, target_item);
+ }
if (!ret) {
mutex_lock(&configfs_symlink_mutex);
ret = create_link(parent_item, target_item, dentry);
@@ -207,6 +231,7 @@ int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
out_put:
config_item_put(parent_item);
+ config_item_put(root_item);
return ret;
}
Recipients:[map[Email:a.hindborg@kernel.org Name:Andreas Hindborg To:false] map[Email:leitao@debian.org Name:Breno Leitao To:true] map[Email:linux-kernel@vger.kernel.org Name: To:true] map[Email:viro@zeniv.linux.org.uk Name:Al Viro To:true]] ReportedBy:[] ReviewedBy:[] SuggestedBy:[] TestedBy:[]]
|
| 1/1 |
2026/09/02 20:16 |
action |
base-commit-picker |
0m
Results: map[KernelBranch:master KernelCommit:cee9395acd8043be0644b25c34bfa86623f2b935 KernelRepo:git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git]
|
| 2/1 |
2026/09/02 20:17 |
action |
syz-repro-to-c-repro |
0m
Results: map[SimplifiedCRepro:// Copyright 2026 syzkaller project authors. All rights reserved.
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
// IMPORTANT: Do not copy the macros or definitions below directly into your reproducer.
// Instead, add the following line to your reproducer:
// #include "race_toolkit.h"
// --- Race Condition Toolkit ---
// Macros and snippets for CPU pinning, memory barriers, and userfaultfd.
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <linux/futex.h>
#include <linux/userfaultfd.h>
#include <poll.h>
#include <pthread.h>
#include <sched.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/syscall.h>
#include <time.h>
#include <unistd.h>
// Unbuffered I/O: Ensure logs are written immediately.
#define SETUP_UNBUFFERED_IO() setvbuf(stdout, NULL, _IONBF, 0)
// CPU Pinning: Pin the current thread to a specific CPU core.
#define PIN_TO_CPU(cpu) \
do { \
cpu_set_t mask; \
CPU_ZERO(&mask); \
CPU_SET(cpu, &mask); \
if (sched_setaffinity(0, sizeof(mask), &mask) == -1) { \
perror("sched_setaffinity"); \
} \
} while (0)
// Memory Barrier: Ensure memory ordering.
#define MB() __atomic_thread_fence(__ATOMIC_SEQ_CST)
// Spin-wait Barrier: Wait until a memory location has a specific value.
// Best for tight race windows (low latency, no context switches).
#define WAIT_ON(addr, val) \
do { \
while (__atomic_load_n(addr, __ATOMIC_ACQUIRE) != (val)) \
; \
} while (0)
// Signal: Set a memory location to a specific value to release a WAIT_ON.
#define SIGNAL(addr, val) __atomic_store_n(addr, val, __ATOMIC_RELEASE)
// --- Timing Primitives ---
// Robust timing loops in VM environments (using CLOCK_MONOTONIC to avoid time(NULL) jumps).
static inline double timer_elapsed_sec(struct timespec* start)
{
struct timespec now;
if (clock_gettime(CLOCK_MONOTONIC, &now) == -1) {
perror("clock_gettime(CLOCK_MONOTONIC) elapsed");
exit(1);
}
return (double)(now.tv_sec - start->tv_sec) + (double)(now.tv_nsec - start->tv_nsec) / 1e9;
}
// Initialize a monotonic timer variable.
#define TIMER_START(t) \
struct timespec t; \
if (clock_gettime(CLOCK_MONOTONIC, &t) == -1) { \
perror("clock_gettime(CLOCK_MONOTONIC) start"); \
exit(1); \
}
// Check if the elapsed time since 't' is less than 'sec' seconds.
#define TIMER_NOT_EXPIRED(t, sec) (timer_elapsed_sec(&(t)) < (double)(sec))
// Futex-based Event: Shared with syzkaller executor.
// Best for general synchronization or longer waits to save CPU.
typedef struct {
int state;
} event_t;
static void event_init(event_t* ev)
{
ev->state = 0;
}
static void event_reset(event_t* ev)
{
ev->state = 0;
}
static void event_set(event_t* ev)
{
if (__atomic_load_n(&ev->state, __ATOMIC_ACQUIRE)) {
fprintf(stderr, "event already set\n");
exit(1);
}
__atomic_store_n(&ev->state, 1, __ATOMIC_RELEASE);
syscall(SYS_futex, &ev->state, FUTEX_WAKE | FUTEX_PRIVATE_FLAG, 1000000);
}
static void event_wait(event_t* ev)
{
while (!__atomic_load_n(&ev->state, __ATOMIC_ACQUIRE))
syscall(SYS_futex, &ev->state, FUTEX_WAIT | FUTEX_PRIVATE_FLAG, 0, 0);
}
// userfaultfd setup: Register a memory range for page fault handling.
static int setup_uffd(void* addr, size_t len)
{
int uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
if (uffd == -1)
return -1;
struct uffdio_api api = {.api = UFFD_API, .features = 0};
if (ioctl(uffd, UFFDIO_API, &api) == -1) {
close(uffd);
return -1;
}
struct uffdio_register reg = {
.range = {.start = (uintptr_t)addr, .len = len},
.mode = UFFDIO_REGISTER_MODE_MISSING};
if (ioctl(uffd, UFFDIO_REGISTER, ®) == -1) {
close(uffd);
return -1;
}
return uffd;
}
// --- Guidance on Usage ---
// 1. Use WAIT_ON/SIGNAL for tight race conditions to avoid scheduling overhead.
// 2. Use event_t (futexes) for general coordination or when waiting for longer periods.
// 3. Always use PIN_TO_CPU to increase race probability on multi-core systems.
// 4. Use setup_uffd to register a memory range for page fault handling. This allows you to
// pause a thread accessing that memory until you handle the fault, creating a reliable
// and controllable race window.
// 5. Call SETUP_UNBUFFERED_IO() at the start of main() to ensure that logs are printed
// immediately. This is essential for understanding the exact interleaving of events
// when debugging race conditions.
// 6. For timing-based loops (e.g., running a race for 10 seconds), do NOT use time(NULL)
// or loops relying on real-time clocks, as VM clocks are highly unreliable and can fail or drift.
// Instead, use the robust monotonic timing primitives TIMER_START and TIMER_NOT_EXPIRED:
// TIMER_START(start);
// while (TIMER_NOT_EXPIRED(start, 10.0)) {
// // Your race logic here
// }
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/mount.h>
#include <time.h>
#include <errno.h>
#include <string.h>
volatile int stop = 0;
void *symlink_thread(void *arg)
{
while (!__atomic_load_n(&stop, __ATOMIC_ACQUIRE)) {
if (syscall(SYS_symlinkat, "/sys/kernel/config/nvmet/subsystems/subsys1", AT_FDCWD,
"/sys/kernel/config/nvmet/ports/1/subsystems/subsys1") == 0) {
unlink("/sys/kernel/config/nvmet/ports/1/subsystems/subsys1");
}
}
return NULL;
}
void *rmdir_thread(void *arg)
{
while (!__atomic_load_n(&stop, __ATOMIC_ACQUIRE)) {
mkdir("/sys/kernel/config/nvmet/ports/1", 0777);
rmdir("/sys/kernel/config/nvmet/ports/1");
}
return NULL;
}
int main()
{
SETUP_UNBUFFERED_IO();
struct stat st;
if (stat("/sys/kernel/config", &st) < 0) {
if (mkdir("/sys/kernel/config", 0777) < 0 && errno != EEXIST) {
printf("[-] Failed to create /sys/kernel/config: %s\n", strerror(errno));
}
}
if (mount("none", "/sys/kernel/config", "configfs", 0, NULL) < 0) {
if (errno != EBUSY) {
printf("[-] Failed to mount configfs: %s\n", strerror(errno));
return 1;
}
}
printf("[+] configfs mounted or already busy.\n");
if (stat("/sys/kernel/config/nvmet", &st) < 0) {
printf("[-] nvmet subsystem not found in configfs: %s\n", strerror(errno));
return 1;
}
printf("[+] nvmet subsystem found.\n");
if (mkdir("/sys/kernel/config/nvmet/subsystems/subsys1", 0777) < 0 && errno != EEXIST) {
printf("[-] Failed to create subsys1: %s\n", strerror(errno));
return 1;
}
printf("[+] Created subsys1 successfully.\n");
pthread_t t1[4], t2[4];
for (int i = 0; i < 4; i++) {
pthread_create(&t1[i], NULL, symlink_thread, NULL);
pthread_create(&t2[i], NULL, rmdir_thread, NULL);
}
TIMER_START(start);
while (TIMER_NOT_EXPIRED(start, 5.0)) {
usleep(100000);
}
__atomic_store_n(&stop, 1, __ATOMIC_RELEASE);
for (int i = 0; i < 4; i++) {
pthread_join(t1[i], NULL);
pthread_join(t2[i], NULL);
}
printf("[+] Done.\n");
return 0;
}
]
|
| 3/1 |
2026/09/02 20:17 |
action |
kernel-checkouter |
0m
Results: map[KernelSrc:/app/workdir/cache/src/d20ee096b85bb6348b207ca05fcfd1899d452eaa]
|
| 4/1 |
2026/09/02 20:17 |
action |
kernel-builder |
25m
Results: map[KernelObj:/app/workdir/cache/build/a99dea34f9cf851e7c0066ca1b790ca09aa8c35f]
|
| 5/1 |
2026/09/02 20:42 |
action |
crash-reproducer |
1m
Results: map[OtherCrashReports:[Oops: general protection fault, probably for non-canonical address 0xdffffc0000000022: 0000 [#1] SMP KASAN NOPTI
KASAN: null-ptr-deref in range [0x0000000000000110-0x0000000000000117]
CPU: 0 UID: 0 PID: 5854 Comm: syz-executor380 Not tainted syzkaller #1 PREEMPT(full)
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
RIP: 0010:nvmet_port_subsys_allow_link+0x102/0x2e0 drivers/nvme/target/configfs.c:1076
Code: 89 6c 24 10 49 89 1c 24 48 c7 c7 40 13 94 8f e8 a4 9b 5b 05 48 89 6c 24 08 48 81 c5 10 01 00 00 49 89 ec 4c 89 e0 48 c1 e8 03 <42> 80 3c 38 00 74 08 4c 89 e7 e8 7f 4b c9 fb 4d 8b 24 24 49 39 ec
RSP: 0018:ffffc90003537c30 EFLAGS: 00010206
RAX: 0000000000000022 RBX: ffff888195763800 RCX: ffffffff9aa3f501
RDX: 0000000000000001 RSI: 0000000000000008 RDI: 00000000ffffffff
RBP: 0000000000000110 R08: ffffffff8f94134f R09: 1ffffffff1f28269
R10: dffffc0000000000 R11: fffffbfff1f2826a R12: 0000000000000110
R13: ffff888110ceb140 R14: ffff888195763990 R15: dffffc0000000000
FS: 00007f7f0c2bc6c0(0000) GS:ffff8881a4c74000(0000) knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 000055a03a15e300 CR3: 00000001fe873000 CR4: 0000000000352ef0
Call Trace:
<TASK>
configfs_symlink+0x59a/0x1030 fs/configfs/symlink.c:196
vfs_symlink+0x18b/0x330 fs/namei.c:5794
filename_symlinkat+0x1cd/0x410 fs/namei.c:5819
__do_sys_symlinkat fs/namei.c:5839 [inline]
__se_sys_symlinkat+0x4e/0x2b0 fs/namei.c:5834
do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7f7f0f2f7459
Code: c0 79 93 eb d5 48 8d 7c 1d 00 eb 99 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 d0 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007f7f0c2bc1f8 EFLAGS: 00000246 ORIG_RAX: 000000000000010a
RAX: ffffffffffffffda RBX: 00007f7f0f364028 RCX: 00007f7f0f2f7459
RDX: 00007f7f0f364028 RSI: 00000000ffffff9c RDI: 00007f7f0f364060
RBP: 00007f7f0f364060 R08: 00007f7f0c2bc6c0 R09: 00007f7f0c2bc6c0
R10: 00007f7f0c2bc6c0 R11: 0000000000000246 R12: ffffffffffffffd0
R13: 0000000000000010 R14: 00007ffe369fbe30 R15: 00007ffe369fbf18
</TASK>
Modules linked in:
---[ end trace 0000000000000000 ]---
RIP: 0010:nvmet_port_subsys_allow_link+0x102/0x2e0 drivers/nvme/target/configfs.c:1076
Code: 89 6c 24 10 49 89 1c 24 48 c7 c7 40 13 94 8f e8 a4 9b 5b 05 48 89 6c 24 08 48 81 c5 10 01 00 00 49 89 ec 4c 89 e0 48 c1 e8 03 <42> 80 3c 38 00 74 08 4c 89 e7 e8 7f 4b c9 fb 4d 8b 24 24 49 39 ec
RSP: 0018:ffffc90003537c30 EFLAGS: 00010206
RAX: 0000000000000022 RBX: ffff888195763800 RCX: ffffffff9aa3f501
RDX: 0000000000000001 RSI: 0000000000000008 RDI: 00000000ffffffff
RBP: 0000000000000110 R08: ffffffff8f94134f R09: 1ffffffff1f28269
R10: dffffc0000000000 R11: fffffbfff1f2826a R12: 0000000000000110
R13: ffff888110ceb140 R14: ffff888195763990 R15: dffffc0000000000
FS: 00007f7f0c2bc6c0(0000) GS:ffff8881a4c74000(0000) knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 000055a03a15e300 CR3: 00000001fe873000 CR4: 0000000000352ef0
----------------
Code disassembly (best guess):
0: 89 6c 24 10 mov %ebp,0x10(%rsp)
4: 49 89 1c 24 mov %rbx,(%r12)
8: 48 c7 c7 40 13 94 8f mov $0xffffffff8f941340,%rdi
f: e8 a4 9b 5b 05 call 0x55b9bb8
14: 48 89 6c 24 08 mov %rbp,0x8(%rsp)
19: 48 81 c5 10 01 00 00 add $0x110,%rbp
20: 49 89 ec mov %rbp,%r12
23: 4c 89 e0 mov %r12,%rax
26: 48 c1 e8 03 shr $0x3,%rax
* 2a: 42 80 3c 38 00 cmpb $0x0,(%rax,%r15,1) <-- trapping instruction
2f: 74 08 je 0x39
31: 4c 89 e7 mov %r12,%rdi
34: e8 7f 4b c9 fb call 0xfbc94bb8
39: 4d 8b 24 24 mov (%r12),%r12
3d: 49 39 ec cmp %rbp,%r12
] ReproducedBugTitle:KASAN: slab-use-after-free Read in nvmet_port_subsys_allow_link ReproducedCrashReport:==================================================================
BUG: KASAN: slab-use-after-free in nvmet_port_subsys_allow_link+0x41/0x2e0 drivers/nvme/target/configfs.c:1059
Read of size 8 at addr ffff88811c6d44c8 by task syz-executor266/5850
CPU: 0 UID: 0 PID: 5850 Comm: syz-executor266 Not tainted syzkaller #1 PREEMPT(full)
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
Call Trace:
<TASK>
dump_stack_lvl+0xe8/0x150 lib/dump_stack.c:120
print_address_description+0x55/0x1e0 mm/kasan/report.c:378
print_report+0x58/0x70 mm/kasan/report.c:482
kasan_report+0x117/0x150 mm/kasan/report.c:595
nvmet_port_subsys_allow_link+0x41/0x2e0 drivers/nvme/target/configfs.c:1059
configfs_symlink+0x59a/0x1030 fs/configfs/symlink.c:196
vfs_symlink+0x18b/0x330 fs/namei.c:5794
filename_symlinkat+0x1cd/0x410 fs/namei.c:5819
__do_sys_symlinkat fs/namei.c:5839 [inline]
__se_sys_symlinkat+0x4e/0x2b0 fs/namei.c:5834
do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7f648f0cc459
Code: c0 79 93 eb d5 48 8d 7c 1d 00 eb 99 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 d0 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007f648c0911f8 EFLAGS: 00000246 ORIG_RAX: 000000000000010a
RAX: ffffffffffffffda RBX: 00007f648f139028 RCX: 00007f648f0cc459
RDX: 00007f648f139028 RSI: 00000000ffffff9c RDI: 00007f648f139060
RBP: 00007f648f139060 R08: 00007f648c0916c0 R09: 00007f648c0916c0
R10: 00007f648c0916c0 R11: 0000000000000246 R12: ffffffffffffffd0
R13: 0000000000000010 R14: 00007ffe91711350 R15: 00007ffe91711438
</TASK>
Allocated by task 5847:
kasan_save_stack mm/kasan/common.c:57 [inline]
kasan_save_track+0x3e/0x80 mm/kasan/common.c:78
poison_kmalloc_redzone mm/kasan/common.c:398 [inline]
__kasan_kmalloc+0x93/0xb0 mm/kasan/common.c:415
kasan_kmalloc include/linux/kasan.h:263 [inline]
__kmalloc_cache_noprof+0x321/0x600 mm/slub.c:5563
_kmalloc_noprof include/linux/slab.h:991 [inline]
_kzalloc_noprof include/linux/slab.h:1312 [inline]
nvmet_ports_make+0xe6/0xf00 drivers/nvme/target/configfs.c:2051
configfs_mkdir+0x4f6/0x9e0 fs/configfs/dir.c:1360
vfs_mkdir+0x40c/0x620 fs/namei.c:5410
filename_mkdirat+0x285/0x510 fs/namei.c:5443
__do_sys_mkdir fs/namei.c:5470 [inline]
__se_sys_mkdir+0x34/0x150 fs/namei.c:5467
do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
entry_SYSCALL_64_after_hwframe+0x77/0x7f
Freed by task 5847:
kasan_save_stack mm/kasan/common.c:57 [inline]
kasan_save_track+0x3e/0x80 mm/kasan/common.c:78
kasan_save_free_info+0x40/0x50 mm/kasan/generic.c:584
poison_slab_object mm/kasan/common.c:253 [inline]
__kasan_slab_free+0x5c/0x80 mm/kasan/common.c:285
kasan_slab_free include/linux/kasan.h:235 [inline]
slab_free_hook mm/slub.c:2748 [inline]
slab_free mm/slub.c:6499 [inline]
kfree+0x1c5/0x650 mm/slub.c:6792
config_item_cleanup fs/configfs/item.c:128 [inline]
config_item_release+0x13a/0x2d0 fs/configfs/item.c:137
configfs_rmdir+0x885/0x950 fs/configfs/dir.c:1571
vfs_rmdir+0x3e6/0x6a0 fs/namei.c:5515
filename_rmdir+0x292/0x520 fs/namei.c:5572
__do_sys_rmdir fs/namei.c:5595 [inline]
__se_sys_rmdir+0x2e/0x140 fs/namei.c:5592
do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
entry_SYSCALL_64_after_hwframe+0x77/0x7f
The buggy address belongs to the object at ffff88811c6d4000
which belongs to the cache kmalloc-4k of size 4096
The buggy address is located 1224 bytes inside of
freed 4096-byte region [ffff88811c6d4000, ffff88811c6d5000)
The buggy address belongs to the physical page:
page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x11c6d0
head: order:3 mapcount:0 entire_mapcount:0 nr_pages_mapped:0 pincount:0
flags: 0x17ff00000000040(head|node=0|zone=2|lastcpupid=0x7ff)
page_type: f5(slab)
raw: 017ff00000000040 ffff888100042140 dead000000000100 dead000000000122
raw: 0000000000000000 0000000000040004 00000000f5000000 0000000000000000
head: 017ff00000000040 ffff888100042140 dead000000000100 dead000000000122
head: 0000000000000000 0000000000040004 00000000f5000000 0000000000000000
head: 017ff00000000003 fffffffffffffe01 00000000ffffffff 00000000ffffffff
head: ffffffffffffffff 0000000000000000 00000000ffffffff 0000000000000008
page dumped because: kasan: bad access detected
page_owner tracks the page as allocated
page last allocated via order 3, migratetype Unmovable, gfp_mask 0xd20c0(__GFP_IO|__GFP_FS|__GFP_NOWARN|__GFP_NORETRY|__GFP_COMP|__GFP_NOMEMALLOC), pid 5843, tgid 5843 (syz-executor266), ts 63759869729
set_page_owner include/linux/page_owner.h:33 [inline]
post_alloc_hook+0x1f9/0x250 mm/page_alloc.c:1871
prep_new_page mm/page_alloc.c:1879 [inline]
get_page_from_freelist+0x2209/0x2280 mm/page_alloc.c:3943
__alloc_frozen_pages_noprof+0x217/0x5a0 mm/page_alloc.c:5436
alloc_slab_page mm/slub.c:3347 [inline]
allocate_slab+0x7d/0x620 mm/slub.c:3462
new_slab mm/slub.c:3513 [inline]
refill_objects+0x2d5/0x350 mm/slub.c:7410
refill_sheaf mm/slub.c:2885 [inline]
__pcs_replace_empty_main+0x2c8/0x6c0 mm/slub.c:4774
alloc_from_pcs mm/slub.c:4850 [inline]
slab_alloc_node mm/slub.c:4984 [inline]
__kmalloc_cache_noprof+0x39b/0x600 mm/slub.c:5559
_kmalloc_noprof include/linux/slab.h:991 [inline]
do_proc_readlink fs/proc/base.c:1817 [inline]
proc_pid_readlink+0x108/0x230 fs/proc/base.c:1847
vfs_readlink+0x4bb/0x540 fs/namei.c:6407
do_readlinkat+0x218/0x510 fs/stat.c:583
__do_sys_readlinkat fs/stat.c:599 [inline]
__se_sys_readlinkat fs/stat.c:596 [inline]
__x64_sys_readlinkat+0x9a/0xb0 fs/stat.c:596
do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
entry_SYSCALL_64_after_hwframe+0x77/0x7f
page last free pid 5843 tgid 5843 ts 63751651251 stack trace:
reset_page_owner include/linux/page_owner.h:26 [inline]
__free_pages_prepare mm/page_alloc.c:1418 [inline]
__free_frozen_pages+0xc50/0xd30 mm/page_alloc.c:2962
__slab_free+0x274/0x2c0 mm/slub.c:5815
qlink_free mm/kasan/quarantine.c:163 [inline]
qlist_free_all+0x99/0x100 mm/kasan/quarantine.c:179
kasan_quarantine_reduce+0x148/0x160 mm/kasan/quarantine.c:286
__kasan_slab_alloc+0x22/0x80 mm/kasan/common.c:350
kasan_slab_alloc include/linux/kasan.h:253 [inline]
slab_post_alloc_hook mm/slub.c:4683 [inline]
slab_alloc_node mm/slub.c:4996 [inline]
__do_kmalloc_node mm/slub.c:5413 [inline]
__kmalloc_noprof+0x307/0x720 mm/slub.c:5439
_kmalloc_noprof include/linux/slab.h:995 [inline]
tomoyo_add_entry security/tomoyo/common.c:2166 [inline]
tomoyo_supervisor+0xc53/0x1560 security/tomoyo/common.c:2238
tomoyo_audit_env_log security/tomoyo/environ.c:37 [inline]
tomoyo_env_perm+0x151/0x1f0 security/tomoyo/environ.c:64
tomoyo_environ security/tomoyo/domain.c:673 [inline]
tomoyo_find_next_domain+0x1332/0x1a90 security/tomoyo/domain.c:889
tomoyo_bprm_check_security+0x11b/0x180 security/tomoyo/tomoyo.c:102
security_bprm_check+0x85/0x240 security/security.c:820
search_binary_handler fs/exec.c:1749 [inline]
exec_binprm fs/exec.c:1791 [inline]
bprm_execve+0x888/0x1590 fs/exec.c:1847
do_execveat_common+0x4f4/0x670 fs/exec.c:1945
__do_sys_execve fs/exec.c:2018 [inline]
__se_sys_execve fs/exec.c:2012 [inline]
__x64_sys_execve+0x97/0xc0 fs/exec.c:2012
do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
entry_SYSCALL_64_after_hwframe+0x77/0x7f
Memory state around the buggy address:
ffff88811c6d4380: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
ffff88811c6d4400: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
>ffff88811c6d4480: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
^
ffff88811c6d4500: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
ffff88811c6d4580: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
==================================================================
ReproducedFaultInjection:]
|
| 6/1 |
2026/09/02 20:43 |
action |
codesearch-prepare |
12m
Results: map[Index:codesearch-index]
|
| 7/1 |
2026/09/02 20:56 |
agent |
debugger |
32m
Model:
Instruction:
You are an experienced Linux kernel developer tasked with debugging a kernel crash root cause.
You need to provide a detailed explanation of the root cause for another developer to be
able to write a fix for the bug based on your explanation. Include all relevant details
into the response: function/struct/field/etc names, code snippets, line numbers,
macro/enum values, etc.
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.
Prompt:
The crash is:
==================================================================
BUG: KASAN: slab-use-after-free in nvmet_port_subsys_allow_link+0x41/0x2e0 drivers/nvme/target/configfs.c:1059
Read of size 8 at addr ffff88811c6d44c8 by task syz-executor266/5850
CPU: 0 UID: 0 PID: 5850 Comm: syz-executor266 Not tainted syzkaller #1 PREEMPT(full)
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
Call Trace:
<TASK>
dump_stack_lvl+0xe8/0x150 lib/dump_stack.c:120
print_address_description+0x55/0x1e0 mm/kasan/report.c:378
print_report+0x58/0x70 mm/kasan/report.c:482
kasan_report+0x117/0x150 mm/kasan/report.c:595
nvmet_port_subsys_allow_link+0x41/0x2e0 drivers/nvme/target/configfs.c:1059
configfs_symlink+0x59a/0x1030 fs/configfs/symlink.c:196
vfs_symlink+0x18b/0x330 fs/namei.c:5794
filename_symlinkat+0x1cd/0x410 fs/namei.c:5819
__do_sys_symlinkat fs/namei.c:5839 [inline]
__se_sys_symlinkat+0x4e/0x2b0 fs/namei.c:5834
do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7f648f0cc459
Code: c0 79 93 eb d5 48 8d 7c 1d 00 eb 99 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 d0 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007f648c0911f8 EFLAGS: 00000246 ORIG_RAX: 000000000000010a
RAX: ffffffffffffffda RBX: 00007f648f139028 RCX: 00007f648f0cc459
RDX: 00007f648f139028 RSI: 00000000ffffff9c RDI: 00007f648f139060
RBP: 00007f648f139060 R08: 00007f648c0916c0 R09: 00007f648c0916c0
R10: 00007f648c0916c0 R11: 0000000000000246 R12: ffffffffffffffd0
R13: 0000000000000010 R14: 00007ffe91711350 R15: 00007ffe91711438
</TASK>
Allocated by task 5847:
kasan_save_stack mm/kasan/common.c:57 [inline]
kasan_save_track+0x3e/0x80 mm/kasan/common.c:78
poison_kmalloc_redzone mm/kasan/common.c:398 [inline]
__kasan_kmalloc+0x93/0xb0 mm/kasan/common.c:415
kasan_kmalloc include/linux/kasan.h:263 [inline]
__kmalloc_cache_noprof+0x321/0x600 mm/slub.c:5563
_kmalloc_noprof include/linux/slab.h:991 [inline]
_kzalloc_noprof include/linux/slab.h:1312 [inline]
nvmet_ports_make+0xe6/0xf00 drivers/nvme/target/configfs.c:2051
configfs_mkdir+0x4f6/0x9e0 fs/configfs/dir.c:1360
vfs_mkdir+0x40c/0x620 fs/namei.c:5410
filename_mkdirat+0x285/0x510 fs/namei.c:5443
__do_sys_mkdir fs/namei.c:5470 [inline]
__se_sys_mkdir+0x34/0x150 fs/namei.c:5467
do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
entry_SYSCALL_64_after_hwframe+0x77/0x7f
Freed by task 5847:
kasan_save_stack mm/kasan/common.c:57 [inline]
kasan_save_track+0x3e/0x80 mm/kasan/common.c:78
kasan_save_free_info+0x40/0x50 mm/kasan/generic.c:584
poison_slab_object mm/kasan/common.c:253 [inline]
__kasan_slab_free+0x5c/0x80 mm/kasan/common.c:285
kasan_slab_free include/linux/kasan.h:235 [inline]
slab_free_hook mm/slub.c:2748 [inline]
slab_free mm/slub.c:6499 [inline]
kfree+0x1c5/0x650 mm/slub.c:6792
config_item_cleanup fs/configfs/item.c:128 [inline]
config_item_release+0x13a/0x2d0 fs/configfs/item.c:137
configfs_rmdir+0x885/0x950 fs/configfs/dir.c:1571
vfs_rmdir+0x3e6/0x6a0 fs/namei.c:5515
filename_rmdir+0x292/0x520 fs/namei.c:5572
__do_sys_rmdir fs/namei.c:5595 [inline]
__se_sys_rmdir+0x2e/0x140 fs/namei.c:5592
do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
entry_SYSCALL_64_after_hwframe+0x77/0x7f
The buggy address belongs to the object at ffff88811c6d4000
which belongs to the cache kmalloc-4k of size 4096
The buggy address is located 1224 bytes inside of
freed 4096-byte region [ffff88811c6d4000, ffff88811c6d5000)
The buggy address belongs to the physical page:
page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x11c6d0
head: order:3 mapcount:0 entire_mapcount:0 nr_pages_mapped:0 pincount:0
flags: 0x17ff00000000040(head|node=0|zone=2|lastcpupid=0x7ff)
page_type: f5(slab)
raw: 017ff00000000040 ffff888100042140 dead000000000100 dead000000000122
raw: 0000000000000000 0000000000040004 00000000f5000000 0000000000000000
head: 017ff00000000040 ffff888100042140 dead000000000100 dead000000000122
head: 0000000000000000 0000000000040004 00000000f5000000 0000000000000000
head: 017ff00000000003 fffffffffffffe01 00000000ffffffff 00000000ffffffff
head: ffffffffffffffff 0000000000000000 00000000ffffffff 0000000000000008
page dumped because: kasan: bad access detected
page_owner tracks the page as allocated
page last allocated via order 3, migratetype Unmovable, gfp_mask 0xd20c0(__GFP_IO|__GFP_FS|__GFP_NOWARN|__GFP_NORETRY|__GFP_COMP|__GFP_NOMEMALLOC), pid 5843, tgid 5843 (syz-executor266), ts 63759869729
set_page_owner include/linux/page_owner.h:33 [inline]
post_alloc_hook+0x1f9/0x250 mm/page_alloc.c:1871
prep_new_page mm/page_alloc.c:1879 [inline]
get_page_from_freelist+0x2209/0x2280 mm/page_alloc.c:3943
__alloc_frozen_pages_noprof+0x217/0x5a0 mm/page_alloc.c:5436
alloc_slab_page mm/slub.c:3347 [inline]
allocate_slab+0x7d/0x620 mm/slub.c:3462
new_slab mm/slub.c:3513 [inline]
refill_objects+0x2d5/0x350 mm/slub.c:7410
refill_sheaf mm/slub.c:2885 [inline]
__pcs_replace_empty_main+0x2c8/0x6c0 mm/slub.c:4774
alloc_from_pcs mm/slub.c:4850 [inline]
slab_alloc_node mm/slub.c:4984 [inline]
__kmalloc_cache_noprof+0x39b/0x600 mm/slub.c:5559
_kmalloc_noprof include/linux/slab.h:991 [inline]
do_proc_readlink fs/proc/base.c:1817 [inline]
proc_pid_readlink+0x108/0x230 fs/proc/base.c:1847
vfs_readlink+0x4bb/0x540 fs/namei.c:6407
do_readlinkat+0x218/0x510 fs/stat.c:583
__do_sys_readlinkat fs/stat.c:599 [inline]
__se_sys_readlinkat fs/stat.c:596 [inline]
__x64_sys_readlinkat+0x9a/0xb0 fs/stat.c:596
do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
entry_SYSCALL_64_after_hwframe+0x77/0x7f
page last free pid 5843 tgid 5843 ts 63751651251 stack trace:
reset_page_owner include/linux/page_owner.h:26 [inline]
__free_pages_prepare mm/page_alloc.c:1418 [inline]
__free_frozen_pages+0xc50/0xd30 mm/page_alloc.c:2962
__slab_free+0x274/0x2c0 mm/slub.c:5815
qlink_free mm/kasan/quarantine.c:163 [inline]
qlist_free_all+0x99/0x100 mm/kasan/quarantine.c:179
kasan_quarantine_reduce+0x148/0x160 mm/kasan/quarantine.c:286
__kasan_slab_alloc+0x22/0x80 mm/kasan/common.c:350
kasan_slab_alloc include/linux/kasan.h:253 [inline]
slab_post_alloc_hook mm/slub.c:4683 [inline]
slab_alloc_node mm/slub.c:4996 [inline]
__do_kmalloc_node mm/slub.c:5413 [inline]
__kmalloc_noprof+0x307/0x720 mm/slub.c:5439
_kmalloc_noprof include/linux/slab.h:995 [inline]
tomoyo_add_entry security/tomoyo/common.c:2166 [inline]
tomoyo_supervisor+0xc53/0x1560 security/tomoyo/common.c:2238
tomoyo_audit_env_log security/tomoyo/environ.c:37 [inline]
tomoyo_env_perm+0x151/0x1f0 security/tomoyo/environ.c:64
tomoyo_environ security/tomoyo/domain.c:673 [inline]
tomoyo_find_next_domain+0x1332/0x1a90 security/tomoyo/domain.c:889
tomoyo_bprm_check_security+0x11b/0x180 security/tomoyo/tomoyo.c:102
security_bprm_check+0x85/0x240 security/security.c:820
search_binary_handler fs/exec.c:1749 [inline]
exec_binprm fs/exec.c:1791 [inline]
bprm_execve+0x888/0x1590 fs/exec.c:1847
do_execveat_common+0x4f4/0x670 fs/exec.c:1945
__do_sys_execve fs/exec.c:2018 [inline]
__se_sys_execve fs/exec.c:2012 [inline]
__x64_sys_execve+0x97/0xc0 fs/exec.c:2012
do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
entry_SYSCALL_64_after_hwframe+0x77/0x7f
Memory state around the buggy address:
ffff88811c6d4380: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
ffff88811c6d4400: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
>ffff88811c6d4480: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
^
ffff88811c6d4500: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
ffff88811c6d4580: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
==================================================================
The following C code is a draft of the vulnerable syscall sequence. Keep in mind that
it may lack the precise threading, sandboxing, and some arguments of a working reproducer:
// Copyright 2026 syzkaller project authors. All rights reserved.
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
// IMPORTANT: Do not copy the macros or definitions below directly into your reproducer.
// Instead, add the following line to your reproducer:
// #include "race_toolkit.h"
// --- Race Condition Toolkit ---
// Macros and snippets for CPU pinning, memory barriers, and userfaultfd.
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <linux/futex.h>
#include <linux/userfaultfd.h>
#include <poll.h>
#include <pthread.h>
#include <sched.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/syscall.h>
#include <time.h>
#include <unistd.h>
// Unbuffered I/O: Ensure logs are written immediately.
#define SETUP_UNBUFFERED_IO() setvbuf(stdout, NULL, _IONBF, 0)
// CPU Pinning: Pin the current thread to a specific CPU core.
#define PIN_TO_CPU(cpu) \
do { \
cpu_set_t mask; \
CPU_ZERO(&mask); \
CPU_SET(cpu, &mask); \
if (sched_setaffinity(0, sizeof(mask), &mask) == -1) { \
perror("sched_setaffinity"); \
} \
} while (0)
// Memory Barrier: Ensure memory ordering.
#define MB() __atomic_thread_fence(__ATOMIC_SEQ_CST)
// Spin-wait Barrier: Wait until a memory location has a specific value.
// Best for tight race windows (low latency, no context switches).
#define WAIT_ON(addr, val) \
do { \
while (__atomic_load_n(addr, __ATOMIC_ACQUIRE) != (val)) \
; \
} while (0)
// Signal: Set a memory location to a specific value to release a WAIT_ON.
#define SIGNAL(addr, val) __atomic_store_n(addr, val, __ATOMIC_RELEASE)
// --- Timing Primitives ---
// Robust timing loops in VM environments (using CLOCK_MONOTONIC to avoid time(NULL) jumps).
static inline double timer_elapsed_sec(struct timespec* start)
{
struct timespec now;
if (clock_gettime(CLOCK_MONOTONIC, &now) == -1) {
perror("clock_gettime(CLOCK_MONOTONIC) elapsed");
exit(1);
}
return (double)(now.tv_sec - start->tv_sec) + (double)(now.tv_nsec - start->tv_nsec) / 1e9;
}
// Initialize a monotonic timer variable.
#define TIMER_START(t) \
struct timespec t; \
if (clock_gettime(CLOCK_MONOTONIC, &t) == -1) { \
perror("clock_gettime(CLOCK_MONOTONIC) start"); \
exit(1); \
}
// Check if the elapsed time since 't' is less than 'sec' seconds.
#define TIMER_NOT_EXPIRED(t, sec) (timer_elapsed_sec(&(t)) < (double)(sec))
// Futex-based Event: Shared with syzkaller executor.
// Best for general synchronization or longer waits to save CPU.
typedef struct {
int state;
} event_t;
static void event_init(event_t* ev)
{
ev->state = 0;
}
static void event_reset(event_t* ev)
{
ev->state = 0;
}
static void event_set(event_t* ev)
{
if (__atomic_load_n(&ev->state, __ATOMIC_ACQUIRE)) {
fprintf(stderr, "event already set\n");
exit(1);
}
__atomic_store_n(&ev->state, 1, __ATOMIC_RELEASE);
syscall(SYS_futex, &ev->state, FUTEX_WAKE | FUTEX_PRIVATE_FLAG, 1000000);
}
static void event_wait(event_t* ev)
{
while (!__atomic_load_n(&ev->state, __ATOMIC_ACQUIRE))
syscall(SYS_futex, &ev->state, FUTEX_WAIT | FUTEX_PRIVATE_FLAG, 0, 0);
}
// userfaultfd setup: Register a memory range for page fault handling.
static int setup_uffd(void* addr, size_t len)
{
int uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
if (uffd == -1)
return -1;
struct uffdio_api api = {.api = UFFD_API, .features = 0};
if (ioctl(uffd, UFFDIO_API, &api) == -1) {
close(uffd);
return -1;
}
struct uffdio_register reg = {
.range = {.start = (uintptr_t)addr, .len = len},
.mode = UFFDIO_REGISTER_MODE_MISSING};
if (ioctl(uffd, UFFDIO_REGISTER, ®) == -1) {
close(uffd);
return -1;
}
return uffd;
}
// --- Guidance on Usage ---
// 1. Use WAIT_ON/SIGNAL for tight race conditions to avoid scheduling overhead.
// 2. Use event_t (futexes) for general coordination or when waiting for longer periods.
// 3. Always use PIN_TO_CPU to increase race probability on multi-core systems.
// 4. Use setup_uffd to register a memory range for page fault handling. This allows you to
// pause a thread accessing that memory until you handle the fault, creating a reliable
// and controllable race window.
// 5. Call SETUP_UNBUFFERED_IO() at the start of main() to ensure that logs are printed
// immediately. This is essential for understanding the exact interleaving of events
// when debugging race conditions.
// 6. For timing-based loops (e.g., running a race for 10 seconds), do NOT use time(NULL)
// or loops relying on real-time clocks, as VM clocks are highly unreliable and can fail or drift.
// Instead, use the robust monotonic timing primitives TIMER_START and TIMER_NOT_EXPIRED:
// TIMER_START(start);
// while (TIMER_NOT_EXPIRED(start, 10.0)) {
// // Your race logic here
// }
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/mount.h>
#include <time.h>
#include <errno.h>
#include <string.h>
volatile int stop = 0;
void *symlink_thread(void *arg)
{
while (!__atomic_load_n(&stop, __ATOMIC_ACQUIRE)) {
if (syscall(SYS_symlinkat, "/sys/kernel/config/nvmet/subsystems/subsys1", AT_FDCWD,
"/sys/kernel/config/nvmet/ports/1/subsystems/subsys1") == 0) {
unlink("/sys/kernel/config/nvmet/ports/1/subsystems/subsys1");
}
}
return NULL;
}
void *rmdir_thread(void *arg)
{
while (!__atomic_load_n(&stop, __ATOMIC_ACQUIRE)) {
mkdir("/sys/kernel/config/nvmet/ports/1", 0777);
rmdir("/sys/kernel/config/nvmet/ports/1");
}
return NULL;
}
int main()
{
SETUP_UNBUFFERED_IO();
struct stat st;
if (stat("/sys/kernel/config", &st) < 0) {
if (mkdir("/sys/kernel/config", 0777) < 0 && errno != EEXIST) {
printf("[-] Failed to create /sys/kernel/config: %s\n", strerror(errno));
}
}
if (mount("none", "/sys/kernel/config", "configfs", 0, NULL) < 0) {
if (errno != EBUSY) {
printf("[-] Failed to mount configfs: %s\n", strerror(errno));
return 1;
}
}
printf("[+] configfs mounted or already busy.\n");
if (stat("/sys/kernel/config/nvmet", &st) < 0) {
printf("[-] nvmet subsystem not found in configfs: %s\n", strerror(errno));
return 1;
}
printf("[+] nvmet subsystem found.\n");
if (mkdir("/sys/kernel/config/nvmet/subsystems/subsys1", 0777) < 0 && errno != EEXIST) {
printf("[-] Failed to create subsys1: %s\n", strerror(errno));
return 1;
}
printf("[+] Created subsys1 successfully.\n");
pthread_t t1[4], t2[4];
for (int i = 0; i < 4; i++) {
pthread_create(&t1[i], NULL, symlink_thread, NULL);
pthread_create(&t2[i], NULL, rmdir_thread, NULL);
}
TIMER_START(start);
while (TIMER_NOT_EXPIRED(start, 5.0)) {
usleep(100000);
}
__atomic_store_n(&stop, 1, __ATOMIC_RELEASE);
for (int i = 0; i < 4; i++) {
pthread_join(t1[i], NULL);
pthread_join(t2[i], NULL);
}
printf("[+] Done.\n");
return 0;
}
Reply: An elegant and complete analysis of the root cause, along with a robust fix for this `configfs` race condition.
### 1. KASAN Crash Analysis & Offset Math
The crash is a slab-use-after-free read of size 8 at offset **1224** inside a 4096-byte region (`kmalloc-4k`).
The KASAN trace points to `nvmet_port_subsys_allow_link()`, which accesses `parent->ci_parent` where `parent` is a `struct config_item *`.
In `nvmet_ports_make()`, a `struct nvmet_port` is dynamically allocated. Let's look at its memory layout:
```c
struct nvmet_port {
struct list_head entry; // 16 bytes
struct nvmf_disc_rsp_page_entry disc_addr; // 1024 bytes
struct config_group group; // 136 bytes (offset 1040)
struct config_group subsys_group; // 136 bytes (offset 1176)
// ...
};
```
The `parent_item` passed to `allow_link` is `&port->subsys_group.cg_item`.
The offset of `subsys_group` is `16 + 1024 + 136 = 1176` bytes.
Inside `struct config_item`, the `ci_parent` pointer is at offset 48.
When `nvmet_port_subsys_allow_link` reads `parent->ci_parent`, it accesses memory at offset `1176 + 48 = 1224` bytes into the `nvmet_port` allocation. This **perfectly matches** the KASAN report:
> `The buggy address is located 1224 bytes inside of freed 4096-byte region`
### 2. The Root Cause (Race Condition)
The race occurs between Thread 1 creating a symlink in a default group (`ports/1/subsystems/`) and Thread 2 removing the parent root object (`ports/1`).
1. **Thread 1 (`symlinkat`)**:
- Enters `configfs_symlink()` with the VFS holding `inode_lock(dir)` on `ports/1/subsystems`.
- Acquires a reference to `parent_item` (the embedded `subsys_group.cg_item`).
- Drops `inode_unlock(dir)` to resolve the symlink target without deadlocking.
2. **Thread 2 (`rmdir`)**:
- Calls `configfs_rmdir()` on `ports/1`.
- Calls `configfs_detach_group()`, which recursively locks all default groups. Since Thread 1 dropped the lock on `ports/1/subsystems`, Thread 2 acquires it, marks the items as dead, detaches them, and unlocks.
- Calls `unlink_group()` on `ports/1`, dropping the subsystem's reference to the port.
- The `nvmet_port` refcount drops to 0, and the **entire `nvmet_port` struct is freed via `kfree()`**.
3. **Thread 1 Resumes**:
- Re-acquires `inode_lock(dir)`.
- Blindly calls `type->ct_item_ops->allow_link(parent_item, target_item)`.
- `parent_item` points to the `subsys_group` embedded inside the **already freed** `nvmet_port`, causing the UAF read at offset 1224.
- Even if `allow_link` wasn't called, jumping to `out_put` executes `config_item_put(parent_item)`, causing a secondary UAF on the kref.
### 3. Why Simple Fixes Fail
- **Re-acquiring `parent_item`**: You cannot safely drop `parent_item` before the unlock and re-acquire it via `configfs_get_config_item(dentry->d_parent)` after the relock. When `configfs_rmdir` detaches the item, it leaves `sd->s_element` as a dangling pointer. Re-acquiring it will dereference freed memory.
- **Holding `parent_item`**: Holding the reference to `parent_item` across the unlock is insufficient because `parent_item` is a **default group**. Default groups are embedded directly inside their parent structs (like `nvmet_port`). Their individual refcount being $> 0$ does **not** prevent the parent struct from being freed when the parent's refcount drops to 0.
### 4. The Proper Fix
To prevent the memory from being freed while the lock is dropped, `configfs_symlink()` must pin the **root object** (the dynamically allocated parent, e.g., `ports/1`) rather than just the embedded default group.
Because `configfs_symlink()` holds `inode_lock(dir)` initially, any concurrent `configfs_rmdir()` on an ancestor root object will block inside `detach_groups()` trying to acquire `inode_lock(dir)`. This guarantees the dentry tree is stable and we can safely walk up the `d_parent` chain to find the root object (the first dentry without the `CONFIGFS_USET_DEFAULT` flag) and take a reference to it.
After re-acquiring the lock, we must check `sd->s_frag->frag_dead` to see if the directory was removed while unlocked.
**Implementation for `fs/configfs/symlink.c`:**
```c
/* Add this helper function above configfs_symlink */
static struct config_item *configfs_get_root_item(struct dentry *dentry)
{
struct dentry *root_dentry = dentry;
struct configfs_dirent *sd;
while (root_dentry) {
sd = root_dentry->d_fsdata;
if (!(sd->s_type & CONFIGFS_USET_DEFAULT))
break;
root_dentry = root_dentry->d_parent;
}
return configfs_get_config_item(root_dentry);
}
int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
struct dentry *dentry, const char *symname)
{
int ret;
struct config_item *parent_item;
struct config_item *target_item = NULL;
struct config_item *root_item = NULL;
const struct config_item_type *type;
parent_item = configfs_get_config_item(dentry->d_parent);
type = parent_item->ci_type;
ret = -EPERM;
if (!type || !type->ct_item_ops ||
!type->ct_item_ops->allow_link)
goto out_put;
/*
* Pin the root object so that the parent_item (which may be an
* embedded default group) is not freed while we drop the lock.
*/
root_item = configfs_get_root_item(dentry->d_parent);
/*
* Drop the dir inode lock. The target path might be outside
* configfs, and we don't want to deadlock.
*/
inode_unlock(dir);
ret = get_target(symname, &target_item, dentry->d_sb);
inode_lock(dir);
if (ret)
goto out_put_root;
if (dentry->d_inode || d_unhashed(dentry))
ret = -EEXIST;
else
ret = inode_permission(&nop_mnt_idmap, dir,
MAY_WRITE | MAY_EXEC);
if (!ret) {
struct configfs_dirent *sd = dentry->d_parent->d_fsdata;
/* Check if the directory was removed while unlocked */
if (sd->s_frag->frag_dead) {
ret = -ENOENT;
} else {
ret = type->ct_item_ops->allow_link(parent_item, target_item);
}
}
if (!ret) {
mutex_lock(&configfs_symlink_mutex);
ret = create_link(parent_item, target_item, dentry);
mutex_unlock(&configfs_symlink_mutex);
if (ret && type->ct_item_ops->drop_link)
type->ct_item_ops->drop_link(parent_item,
target_item);
}
config_item_put(target_item);
out_put_root:
config_item_put(root_item);
out_put:
config_item_put(parent_item);
return ret;
}
```
|
| 8/2 |
2026/09/02 20:56 |
llm |
debugger |
0m
Model:
Tokens:
input: 10855
output: 25
thoughts: 141
Thoughts: **Analyzing a KASAN Slab-Use-After-Free in the NVMe Target Driver**
Okay, so I'm looking at a KASAN slab-use-after-free bug in the `nvmet_port_subsys_allow_link` function. The crash is happening within `configfs_symlink`, and that's calling into the problematic `nvmet_port_subsys_allow_link`. It looks like the object that's causing the problem is originally allocated during the creation of NVMe ports, specifically in the sequence of `nvmet_ports_make` calling `configfs_mkdir`. However, it's getting freed later when `configfs_rmdir` calls `config_item_release`, which leads to the `config_item_cleanup` and eventually `kfree`. The timing seems off and that indicates where the vulnerability lies.
Let me take a look at the code in `drivers/nvme/target/configfs.c` and zero in on the `nvmet_port_subsys_allow_link` function to better understand how this lifecycle is supposed to work and where the potential race or incorrect usage might be. Knowing the exact sequence of events, and the objects involved, is going to be key to debugging this.
|
| 9/2 |
2026/09/02 20:56 |
tool |
grepper |
0m
Args: map[Expression:nvmet_port_subsys_allow_link]
Results: map[Output:drivers/nvme/target/configfs.c=1050=static void nvmet_add_passthru_group(struct nvmet_subsys *subsys)
--
drivers/nvme/target/configfs.c-1055-
drivers/nvme/target/configfs.c:1056:static int nvmet_port_subsys_allow_link(struct config_item *parent,
drivers/nvme/target/configfs.c-1057- struct config_item *target)
--
drivers/nvme/target/configfs.c=1125=static struct configfs_item_operations nvmet_port_subsys_item_ops = {
drivers/nvme/target/configfs.c:1126: .allow_link = nvmet_port_subsys_allow_link,
drivers/nvme/target/configfs.c-1127- .drop_link = nvmet_port_subsys_drop_link,
]
|
| 10/2 |
2026/09/02 20:56 |
llm |
debugger |
0m
Model:
Tokens:
input: 11546
output: 40
thoughts: 31
Thoughts: Here's my summary, as though I'm reading and thinking about the code:
**Diving into nvmet_port_subsys_allow_link**
Okay, time to get a handle on `nvmet_port_subsys_allow_link`. I'm heading straight into `drivers/nvme/target/configfs.c` to see how this function is implemented. It's crucial, obviously, because it deals with whether or not a port is allowed to be linked to a subsystem. I need to understand the logic behind that decision-making process. I'm expecting this to be a key piece in ensuring the NVMe target's security and resource management is solid. I'm hoping to see how the connection is validated and what criteria is used to determine whether the link is permitted. Itβs likely going to be tightly integrated with the configfs structure, since it is in this specific file. Let's see what secrets this function holds.
|
| 11/2 |
2026/09/02 20:56 |
tool |
read-file |
0m
Args: map[File:drivers/nvme/target/configfs.c FirstLine:1050 LineCount:50]
Results: map[Contents:1050: static void nvmet_add_passthru_group(struct nvmet_subsys *subsys)
1051: {
1052: }
1053:
1054: #endif /* CONFIG_NVME_TARGET_PASSTHRU */
1055:
1056: static int nvmet_port_subsys_allow_link(struct config_item *parent,
1057: struct config_item *target)
1058: {
1059: struct nvmet_port *port = to_nvmet_port(parent->ci_parent);
1060: struct nvmet_subsys *subsys;
1061: struct nvmet_subsys_link *link, *p;
1062: int ret;
1063:
1064: if (target->ci_type != &nvmet_subsys_type) {
1065: pr_err("can only link subsystems into the subsystems dir.!\n");
1066: return -EINVAL;
1067: }
1068: subsys = to_subsys(target);
1069: link = kmalloc_obj(*link);
1070: if (!link)
1071: return -ENOMEM;
1072: link->subsys = subsys;
1073:
1074: down_write(&nvmet_config_sem);
1075: ret = -EEXIST;
1076: list_for_each_entry(p, &port->subsystems, entry) {
1077: if (p->subsys == subsys)
1078: goto out_free_link;
1079: }
1080:
1081: if (list_empty(&port->subsystems)) {
1082: ret = nvmet_enable_port(port);
1083: if (ret)
1084: goto out_free_link;
1085: }
1086:
1087: list_add_tail(&link->entry, &port->subsystems);
1088: nvmet_port_disc_changed(port, subsys);
1089:
1090: up_write(&nvmet_config_sem);
1091: return 0;
1092:
1093: out_free_link:
1094: up_write(&nvmet_config_sem);
1095: kfree(link);
1096: return ret;
1097: }
1098:
1099: static void nvmet_port_subsys_drop_link(struct config_item *parent,
]
|
| 12/2 |
2026/09/02 20:56 |
llm |
debugger |
0m
Model:
Tokens:
input: 12560
output: 20
thoughts: 50
Thoughts: Here's my analysis of the provided snippet, as if it were my thought process:
**Diving into a NVMe Core Dump**
Okay, so I've got a crash at line 1059. The relevant code is `struct nvmet_port *port = to_nvmet_port(parent->ci_parent);`. Right away, my radar goes up. I see a potential dereference issue, maybe a null pointer or an incorrect cast. The critical part is `to_nvmet_port`. I need to figure out how *that* function works. It's obviously designed to cast something into the nvmet_port structure type. I'll need to check the definition of `to_nvmet_port` to understand what kind of argument it expects, and what it does with the `parent->ci_parent` argument. I need to understand what `ci_parent` even *is* in this context, and what the possible values or types are. Based on the crash, something isn't aligning correctly in that cast. Let's see if the parent structure (whatever that is) might be corrupted or unexpectedly empty. Time to go dig!
|
| 13/2 |
2026/09/02 20:56 |
tool |
grepper |
0m
Args: map[Expression:to_nvmet_port]
Results: map[Output:drivers/nvme/target/configfs.c=66=static ssize_t nvmet_addr_adrfam_show(struct config_item *item, char *page)
drivers/nvme/target/configfs.c-67-{
drivers/nvme/target/configfs.c:68: u8 adrfam = to_nvmet_port(item)->disc_addr.adrfam;
drivers/nvme/target/configfs.c-69- int i;
--
drivers/nvme/target/configfs.c=80=static ssize_t nvmet_addr_adrfam_store(struct config_item *item,
--
drivers/nvme/target/configfs.c-82-{
drivers/nvme/target/configfs.c:83: struct nvmet_port *port = to_nvmet_port(item);
drivers/nvme/target/configfs.c-84- int i;
--
drivers/nvme/target/configfs.c=104=static ssize_t nvmet_addr_portid_show(struct config_item *item,
--
drivers/nvme/target/configfs.c-106-{
drivers/nvme/target/configfs.c:107: __le16 portid = to_nvmet_port(item)->disc_addr.portid;
drivers/nvme/target/configfs.c-108-
--
drivers/nvme/target/configfs.c=112=static ssize_t nvmet_addr_portid_store(struct config_item *item,
--
drivers/nvme/target/configfs.c-114-{
drivers/nvme/target/configfs.c:115: struct nvmet_port *port = to_nvmet_port(item);
drivers/nvme/target/configfs.c-116- u16 portid = 0;
--
drivers/nvme/target/configfs.c=132=static ssize_t nvmet_addr_traddr_show(struct config_item *item,
--
drivers/nvme/target/configfs.c-134-{
drivers/nvme/target/configfs.c:135: struct nvmet_port *port = to_nvmet_port(item);
drivers/nvme/target/configfs.c-136-
--
drivers/nvme/target/configfs.c=140=static ssize_t nvmet_addr_traddr_store(struct config_item *item,
--
drivers/nvme/target/configfs.c-142-{
drivers/nvme/target/configfs.c:143: struct nvmet_port *port = to_nvmet_port(item);
drivers/nvme/target/configfs.c-144-
--
drivers/nvme/target/configfs.c=171=static ssize_t nvmet_addr_treq_show(struct config_item *item, char *page)
drivers/nvme/target/configfs.c-172-{
drivers/nvme/target/configfs.c:173: u8 treq = nvmet_port_disc_addr_treq_secure_channel(to_nvmet_port(item));
drivers/nvme/target/configfs.c-174- int i;
--
drivers/nvme/target/configfs.c=185=static ssize_t nvmet_addr_treq_store(struct config_item *item,
--
drivers/nvme/target/configfs.c-187-{
drivers/nvme/target/configfs.c:188: struct nvmet_port *port = to_nvmet_port(item);
drivers/nvme/target/configfs.c-189- u8 treq = nvmet_port_disc_addr_treq_mask(port);
--
drivers/nvme/target/configfs.c=225=static ssize_t nvmet_addr_trsvcid_show(struct config_item *item,
--
drivers/nvme/target/configfs.c-227-{
drivers/nvme/target/configfs.c:228: struct nvmet_port *port = to_nvmet_port(item);
drivers/nvme/target/configfs.c-229-
--
drivers/nvme/target/configfs.c=233=static ssize_t nvmet_addr_trsvcid_store(struct config_item *item,
--
drivers/nvme/target/configfs.c-235-{
drivers/nvme/target/configfs.c:236: struct nvmet_port *port = to_nvmet_port(item);
drivers/nvme/target/configfs.c-237-
--
drivers/nvme/target/configfs.c=252=static ssize_t nvmet_param_inline_data_size_show(struct config_item *item,
--
drivers/nvme/target/configfs.c-254-{
drivers/nvme/target/configfs.c:255: struct nvmet_port *port = to_nvmet_port(item);
drivers/nvme/target/configfs.c-256-
--
drivers/nvme/target/configfs.c=260=static ssize_t nvmet_param_inline_data_size_store(struct config_item *item,
--
drivers/nvme/target/configfs.c-262-{
drivers/nvme/target/configfs.c:263: struct nvmet_port *port = to_nvmet_port(item);
drivers/nvme/target/configfs.c-264- int ret;
--
drivers/nvme/target/configfs.c=278=static ssize_t nvmet_param_max_queue_size_show(struct config_item *item,
--
drivers/nvme/target/configfs.c-280-{
drivers/nvme/target/configfs.c:281: struct nvmet_port *port = to_nvmet_port(item);
drivers/nvme/target/configfs.c-282-
--
drivers/nvme/target/configfs.c=286=static ssize_t nvmet_param_max_queue_size_store(struct config_item *item,
--
drivers/nvme/target/configfs.c-288-{
drivers/nvme/target/configfs.c:289: struct nvmet_port *port = to_nvmet_port(item);
drivers/nvme/target/configfs.c-290- int ret;
--
drivers/nvme/target/configfs.c=304=static ssize_t nvmet_param_mdts_show(struct config_item *item, char *page)
drivers/nvme/target/configfs.c-305-{
drivers/nvme/target/configfs.c:306: struct nvmet_port *port = to_nvmet_port(item);
drivers/nvme/target/configfs.c-307-
--
drivers/nvme/target/configfs.c=311=static ssize_t nvmet_param_mdts_store(struct config_item *item,
--
drivers/nvme/target/configfs.c-313-{
drivers/nvme/target/configfs.c:314: struct nvmet_port *port = to_nvmet_port(item);
drivers/nvme/target/configfs.c-315- int ret, mdts;
--
drivers/nvme/target/configfs.c=332=static ssize_t nvmet_param_pi_enable_show(struct config_item *item,
--
drivers/nvme/target/configfs.c-334-{
drivers/nvme/target/configfs.c:335: struct nvmet_port *port = to_nvmet_port(item);
drivers/nvme/target/configfs.c-336-
--
drivers/nvme/target/configfs.c=340=static ssize_t nvmet_param_pi_enable_store(struct config_item *item,
--
drivers/nvme/target/configfs.c-342-{
drivers/nvme/target/configfs.c:343: struct nvmet_port *port = to_nvmet_port(item);
drivers/nvme/target/configfs.c-344- bool val;
--
drivers/nvme/target/configfs.c=359=static ssize_t nvmet_addr_trtype_show(struct config_item *item,
--
drivers/nvme/target/configfs.c-361-{
drivers/nvme/target/configfs.c:362: struct nvmet_port *port = to_nvmet_port(item);
drivers/nvme/target/configfs.c-363- int i;
--
drivers/nvme/target/configfs.c=386=static ssize_t nvmet_addr_trtype_store(struct config_item *item,
--
drivers/nvme/target/configfs.c-388-{
drivers/nvme/target/configfs.c:389: struct nvmet_port *port = to_nvmet_port(item);
drivers/nvme/target/configfs.c-390- int i;
--
drivers/nvme/target/configfs.c=425=static ssize_t nvmet_addr_tsas_show(struct config_item *item,
--
drivers/nvme/target/configfs.c-427-{
drivers/nvme/target/configfs.c:428: struct nvmet_port *port = to_nvmet_port(item);
drivers/nvme/target/configfs.c-429- int i;
--
drivers/nvme/target/configfs.c=467=static ssize_t nvmet_addr_tsas_store(struct config_item *item,
--
drivers/nvme/target/configfs.c-469-{
drivers/nvme/target/configfs.c:470: struct nvmet_port *port = to_nvmet_port(item);
drivers/nvme/target/configfs.c-471- u8 treq = nvmet_port_disc_addr_treq_mask(port);
--
drivers/nvme/target/configfs.c=1056=static int nvmet_port_subsys_allow_link(struct config_item *parent,
--
drivers/nvme/target/configfs.c-1058-{
drivers/nvme/target/configfs.c:1059: struct nvmet_port *port = to_nvmet_port(parent->ci_parent);
drivers/nvme/target/configfs.c-1060- struct nvmet_subsys *subsys;
--
drivers/nvme/target/configfs.c=1099=static void nvmet_port_subsys_drop_link(struct config_item *parent,
--
drivers/nvme/target/configfs.c-1101-{
drivers/nvme/target/configfs.c:1102: struct nvmet_port *port = to_nvmet_port(parent->ci_parent);
drivers/nvme/target/configfs.c-1103- struct nvmet_subsys *subsys = to_subsys(target);
--
drivers/nvme/target/configfs.c=1780=static ssize_t nvmet_referral_enable_show(struct config_item *item,
--
drivers/nvme/target/configfs.c-1782-{
drivers/nvme/target/configfs.c:1783: return snprintf(page, PAGE_SIZE, "%d\n", to_nvmet_port(item)->enabled);
drivers/nvme/target/configfs.c-1784-}
--
drivers/nvme/target/configfs.c=1786=static ssize_t nvmet_referral_enable_store(struct config_item *item,
--
drivers/nvme/target/configfs.c-1788-{
drivers/nvme/target/configfs.c:1789: struct nvmet_port *parent = to_nvmet_port(item->ci_parent->ci_parent);
drivers/nvme/target/configfs.c:1790: struct nvmet_port *port = to_nvmet_port(item);
drivers/nvme/target/configfs.c-1791- bool enable;
--
drivers/nvme/target/configfs.c=1823=static void nvmet_referral_notify(struct config_group *group,
--
drivers/nvme/target/configfs.c-1825-{
drivers/nvme/target/configfs.c:1826: struct nvmet_port *parent = to_nvmet_port(item->ci_parent->ci_parent);
drivers/nvme/target/configfs.c:1827: struct nvmet_port *port = to_nvmet_port(item);
drivers/nvme/target/configfs.c-1828-
--
drivers/nvme/target/configfs.c=1832=static void nvmet_referral_release(struct config_item *item)
drivers/nvme/target/configfs.c-1833-{
drivers/nvme/target/configfs.c:1834: struct nvmet_port *port = to_nvmet_port(item);
drivers/nvme/target/configfs.c-1835-
--
drivers/nvme/target/configfs.c=2003=static void nvmet_port_release(struct config_item *item)
drivers/nvme/target/configfs.c-2004-{
drivers/nvme/target/configfs.c:2005: struct nvmet_port *port = to_nvmet_port(item);
drivers/nvme/target/configfs.c-2006-
--
drivers/nvme/target/nvmet.h=203=struct nvmet_port {
--
drivers/nvme/target/nvmet.h-224-
drivers/nvme/target/nvmet.h:225:static inline struct nvmet_port *to_nvmet_port(struct config_item *item)
drivers/nvme/target/nvmet.h-226-{
]
|
| 14/2 |
2026/09/02 20:56 |
llm |
debugger |
0m
Model:
Tokens:
input: 16492
output: 39
thoughts: 26
Thoughts: Here's my take on those thoughts, assuming I'm an NVMe target expert:
**Diving into the `nvmet.h` Header: `to_nvmet_port`**
Okay, so I'm thinking about the NVMe target driver again, specifically `drivers/nvme/target/nvmet.h`. My immediate focus is on that `to_nvmet_port` function or macro. This is probably a crucial piece of the puzzle. I'm likely interested in how it's defined, its purpose, and how it is used to understand how ports are handled. Is it an accessor? A type conversion helper? What data structure does it relate to? Knowing this will help me quickly navigate the code and understand how the target manages its various ports and the connections to them. It's almost always a vital bit of the low level details for how the nvme target works. Let's see what we can find.
|
| 15/2 |
2026/09/02 20:56 |
tool |
read-file |
0m
Args: map[File:drivers/nvme/target/nvmet.h FirstLine:220 LineCount:20]
Results: map[Contents: 220: const struct nvmet_fabrics_ops *tr_ops;
221: bool pi_enable;
222: enum nvme_ana_state ana_state[];
223: };
224:
225: static inline struct nvmet_port *to_nvmet_port(struct config_item *item)
226: {
227: return container_of(to_config_group(item), struct nvmet_port,
228: group);
229: }
230:
231: static inline struct nvmet_port *ana_groups_to_port(
232: struct config_item *item)
233: {
234: return container_of(to_config_group(item), struct nvmet_port,
235: ana_groups_group);
236: }
237:
238: static inline u8 nvmet_port_disc_addr_treq_secure_channel(struct nvmet_port *port)
239: {
]
|
| 16/2 |
2026/09/02 20:56 |
llm |
debugger |
0m
Model:
Tokens:
input: 17068
output: 37
thoughts: 548
Thoughts: **Analyzing a ConfigFS Race Condition in NVMe-oF**
Okay, let's break this down. The core issue seems to be a race condition within the NVMe-oF configfs subsystem. My initial thought is that `to_nvmet_port` is retrieving an `nvmet_port` struct from a `config_item` using `config_item->ci_parent`. This `parent` in `nvmet_port_subsys_allow_link` points to the "subsystems" directory under the "port" directory, and the parent of that (via `ci_parent`) should be the "port" itself.
The crash appears to be occurring within `nvmet_port_subsys_allow_link`. The provided code suggests that the crash likely stems from trying to dereference a freed `nvmet_port` pointer. I need to understand the timing of operations to understand *why* it's freed.
The reproducer provides the clue, with two threads: a `symlink_thread` and an `rmdir_thread`. The `rmdir_thread` is aggressively creating and deleting the "ports/1" directory. This is critical. Each removal of `ports/1` likely triggers the `nvmet_port_release` function to free the `nvmet_port` structure. This likely happens when the file is removed, so I expect that the struct is freed then.
The `symlink_thread`, concurrently, is trying to create and then immediately remove a symlink within "ports/1/subsystems/". Initially, I was confused because if `ports/1` is removed, the symlink *shouldn't* succeed... but the call to `configfs_symlink` explains it. This is probably happening because of the symlink in `subsystems`, so the lookup doesn't need to traverse the deleted directory.
The race is clear: `rmdir_thread` frees the `nvmet_port` associated with "ports/1" while `symlink_thread` attempts to use this pointer. This is a classic use-after-free scenario. This will require some careful analysis of the configfs code, specifically `configfs_symlink` to confirm the details about the lookup path, but the general problem is now obvious. I need to examine the locking around the port and subsystem structures.
|
| 17/2 |
2026/09/02 20:57 |
tool |
read-file |
0m
Args: map[File:fs/configfs/symlink.c FirstLine:180 LineCount:50]
Results: map[Contents: 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: }
212:
213: int configfs_unlink(struct inode *dir, struct dentry *dentry)
214: {
215: struct configfs_dirent *sd = dentry->d_fsdata, *target_sd;
216: struct config_item *parent_item;
217: const struct config_item_type *type;
218: int ret;
219:
220: ret = -EPERM; /* What lack-of-symlink returns */
221: if (!(sd->s_type & CONFIGFS_ITEM_LINK))
222: goto out;
223:
224: target_sd = sd->s_element;
225:
226: parent_item = configfs_get_config_item(dentry->d_parent);
227: type = parent_item->ci_type;
228:
229: spin_lock(&configfs_dirent_lock);
]
|
| 18/2 |
2026/09/02 20:57 |
llm |
debugger |
0m
Model:
Tokens:
input: 18923
output: 37
thoughts: 331
Thoughts: **My Analysis of a Potential Race Condition in `configfs_symlink`**
Okay, so I'm looking at this `configfs_symlink` function, and my initial thought is that it's doing something fundamentally reasonable. It grabs a reference to the parent item, which makes sense. I see it's calling `configfs_get_config_item` on the parent dentry. It's then checking for the existence of `allow_link` operation on the parent item's type. That all seems standard enough, and good practice.
But then, it drops the `dir` inode lock to resolve the target path, which is where things get interesting. Specifically:
```c
inode_unlock(dir);
ret = get_target(symname, &target_item, dentry->d_sb);
inode_lock(dir);
```
While that lock is dropped, I immediately see a window for a race condition. If, during that unlocked period, `rmdir` is called on the parent directory (`ports/1`, for example), that could cause problems. That `rmdir` call could remove the directory and its children, including `ports/1/subsystems/`.
And that's the kicker! When `rmdir` removes `ports/1/subsystems/`, it's going to trigger `config_item_put` on the `subsystems` item. However, `configfs_symlink` *already* has a reference to that `subsystems` item through its call to `configfs_get_config_item`. Essentially, the `subsystems` item could be freed out from under `configfs_symlink`.
Now, the critical question is whether `config_item_put` on `ports/1` actually frees the underlying `nvmet_port`? I'm going to dig into the `configfs_rmdir` function to clarify the `config_item_put`'s impact.
|
| 19/2 |
2026/09/02 20:57 |
tool |
read-file |
0m
Args: map[File:fs/configfs/dir.c FirstLine:1550 LineCount:50]
Results: map[Contents: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: }
1578:
1579: const struct inode_operations configfs_dir_inode_operations = {
1580: .mkdir = configfs_mkdir,
1581: .rmdir = configfs_rmdir,
1582: .symlink = configfs_symlink,
1583: .unlink = configfs_unlink,
1584: .lookup = configfs_lookup,
1585: .setattr = configfs_setattr,
1586: };
1587:
1588: const struct inode_operations configfs_root_inode_operations = {
1589: .lookup = configfs_lookup,
1590: .setattr = configfs_setattr,
1591: };
1592:
1593: static int configfs_dir_open(struct inode *inode, struct file *file)
1594: {
1595: struct dentry * dentry = file->f_path.dentry;
1596: struct configfs_dirent * parent_sd = dentry->d_fsdata;
1597: int err;
1598:
1599: inode_lock(d_inode(dentry));
]
|
| 20/2 |
2026/09/02 20:57 |
llm |
debugger |
0m
Model:
Tokens:
input: 20450
output: 37
thoughts: 61
Thoughts: **My Analysis of the `configfs_rmdir` Process**
Okay, so I'm digging into `configfs_rmdir`, and the key variable here, `item`, is the item we're trying to get rid of β think of it as "ports/1" in our example. The core of this removal process seems to hinge on either `configfs_detach_group` or `configfs_detach_item`. I need to figure out which is being called, because depending on if it's a group, or just a single item, this will change how it's handled. For now, let's focus on understanding `configfs_detach_group`. I'll want to inspect that function, and how it handles the detaching process.
|
| 21/2 |
2026/09/02 20:57 |
tool |
read-file |
0m
Args: map[File:fs/configfs/dir.c FirstLine:1200 LineCount:50]
Results: map[Contents:1200: */
1201: spin_lock(&configfs_dirent_lock);
1202:
1203: sd = target->ci_dentry->d_fsdata;
1204: BUG_ON(sd->s_dependent_count < 1);
1205:
1206: sd->s_dependent_count -= 1;
1207:
1208: /*
1209: * After this unlock, we cannot trust the item to stay alive!
1210: * DO NOT REFERENCE item after this unlock.
1211: */
1212: spin_unlock(&configfs_dirent_lock);
1213: }
1214: EXPORT_SYMBOL(configfs_undepend_item);
1215:
1216: /*
1217: * caller_subsys is a caller's subsystem not target's. This is used to
1218: * determine if we should lock root and check subsys or not. When we are
1219: * in the same subsystem as our target there is no need to do locking as
1220: * we know that subsys is valid and is not unregistered during this function
1221: * as we are called from callback of one of his children and VFS holds a lock
1222: * on some inode. Otherwise we have to lock our root to ensure that target's
1223: * subsystem it is not unregistered during this function.
1224: */
1225: int configfs_depend_item_unlocked(struct configfs_subsystem *caller_subsys,
1226: struct config_item *target)
1227: {
1228: struct configfs_subsystem *target_subsys;
1229: struct config_group *root, *parent;
1230: struct configfs_dirent *subsys_sd;
1231: int ret = -ENOENT;
1232:
1233: /* Disallow this function for configfs root */
1234: if (configfs_is_root(target))
1235: return -EINVAL;
1236:
1237: parent = target->ci_group;
1238: /*
1239: * This may happen when someone is trying to depend root
1240: * directory of some subsystem
1241: */
1242: if (configfs_is_root(&parent->cg_item)) {
1243: target_subsys = to_configfs_subsystem(to_config_group(target));
1244: root = parent;
1245: } else {
1246: target_subsys = parent->cg_subsys;
1247: /* Find a cofnigfs root as we may need it for locking */
1248: for (root = parent; !configfs_is_root(&root->cg_item);
1249: root = root->cg_item.ci_group)
]
|
| 22/2 |
2026/09/02 20:57 |
llm |
debugger |
0m
Model:
Tokens:
input: 21524
output: 20
thoughts: 13
Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience:
**Investigating `configfs_detach_group`**
Okay, let's dive in. I'm going to kick this off by searching for `configfs_detach_group`. I'm immediately interested in understanding where and how this function is used within the `configfs` framework. I expect to find a couple of things, at least initially: the function definition itself, of course, and then likely several call sites. My initial hypothesis is that this is responsible for cleaning up or dissociating a group of configfs objects, probably during unmounting or removal of a related subsystem. I'm curious about the specific context in which it's invoked. Is it part of the core cleanup sequence, or is it more granularly called within specific device drivers that leverage `configfs`? I'll be looking for any associated locks or synchronization primitives used alongside this function, because given the shared filesystem context, concurrency issues are always a concern. I'm also interested to see if there's any related error handling - what happens if the detachment fails? Does it propagate errors, or does it try to recover? This will give me a better understanding of the overall robustness of the system. I'll need to remember that `configfs` is a powerful tool, and so I should look for its place within the kernel's stack.
|
| 23/2 |
2026/09/02 20:57 |
tool |
grepper |
0m
Args: map[Expression:configfs_detach_group]
Results: map[Output:fs/configfs/dir.c=68=const struct dentry_operations configfs_dentry_ops = {
--
fs/configfs/dir.c-75- * Helpers to make lockdep happy with our recursive locking of default groups'
fs/configfs/dir.c:76: * inodes (see configfs_attach_group() and configfs_detach_group()).
fs/configfs/dir.c-77- * We put default groups i_mutexes in separate classes according to their depth
--
fs/configfs/dir.c=659=static int configfs_attach_group(struct config_item *item,
--
fs/configfs/dir.c-661- struct configfs_fragment *frag);
fs/configfs/dir.c:662:static void configfs_detach_group(struct dentry *dentry);
fs/configfs/dir.c-663-
fs/configfs/dir.c=664=static void detach_groups(struct dentry *dentry)
--
fs/configfs/dir.c-681-
fs/configfs/dir.c:682: configfs_detach_group(child);
fs/configfs/dir.c-683- d_inode(child)->i_flags |= S_DEAD;
--
fs/configfs/dir.c=836=static void configfs_detach_item(struct dentry *dentry)
--
fs/configfs/dir.c-851- *
fs/configfs/dir.c:852: * configfs_detach_group() and configfs_detach_item() behave similarly on
fs/configfs/dir.c-853- * the way out. They assume that the proper semaphores are held, they
--
fs/configfs/dir.c=857=static int configfs_attach_item(struct config_item *item,
--
fs/configfs/dir.c-884-/* Caller holds the mutex of the group's inode */
fs/configfs/dir.c:885:static void configfs_detach_group(struct dentry *dentry)
fs/configfs/dir.c-886-{
--
fs/configfs/dir.c=891=static int configfs_attach_group(struct config_item *item,
--
fs/configfs/dir.c-915- if (ret) {
fs/configfs/dir.c:916: configfs_detach_group(dentry);
fs/configfs/dir.c-917- d_inode(dentry)->i_flags |= S_DEAD;
--
fs/configfs/dir.c=1469=static int configfs_rmdir(struct inode *dir, struct dentry *dentry)
--
fs/configfs/dir.c-1553- if (sd->s_type & CONFIGFS_USET_DIR) {
fs/configfs/dir.c:1554: configfs_detach_group(dentry);
fs/configfs/dir.c-1555-
--
fs/configfs/dir.c=1796=void configfs_unregister_group(struct config_group *group)
--
fs/configfs/dir.c-1812-
fs/configfs/dir.c:1813: configfs_detach_group(dentry);
fs/configfs/dir.c-1814- d_inode(dentry)->i_flags |= S_DEAD;
--
fs/configfs/dir.c=1934=void configfs_unregister_subsystem(struct configfs_subsystem *subsys)
--
fs/configfs/dir.c-1960- mutex_unlock(&configfs_symlink_mutex);
fs/configfs/dir.c:1961: configfs_detach_group(dentry);
fs/configfs/dir.c-1962- d_inode(dentry)->i_flags |= S_DEAD;
]
|
| 24/2 |
2026/09/02 20:57 |
llm |
debugger |
0m
Model:
Tokens:
input: 22849
output: 36
thoughts: 23
Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience:
**Diving into `configfs_detach_group`**
Okay, so I'm looking at `fs/configfs/dir.c` and specifically, I'm focusing on the `configfs_detach_group` function. I need to understand how configfs handles the cleanup when a group is removed or detached. I'm assuming there's some sort of reference counting or cleanup mechanism going on here, given the nature of configfs and its object-oriented approach. I bet it involves iterating through the children of the group directory and probably some calls to release resources associated with those children. I'm also anticipating that it handles error conditions gracefully, perhaps returning error codes or logging messages in case something goes wrong during the detach process. I want to see how it interacts with the underlying filesystem and device drivers, if any are involved, and confirm if it handles any locking correctly, such as taking the necessary spinlocks/mutexes before operations. I'll pay close attention to the sequencing of operations and how the detach function handles potential race conditions. Basically, I need to see how robust this group detach is in practice, and its implications to other users of the configfs API.
|
| 25/2 |
2026/09/02 20:57 |
tool |
read-file |
0m
Args: map[File:fs/configfs/dir.c FirstLine:880 LineCount:50]
Results: map[Contents: 880:
881: return ret;
882: }
883:
884: /* Caller holds the mutex of the group's inode */
885: static void configfs_detach_group(struct dentry *dentry)
886: {
887: detach_groups(dentry);
888: configfs_detach_item(dentry);
889: }
890:
891: static int configfs_attach_group(struct config_item *item,
892: struct dentry *dentry,
893: struct configfs_fragment *frag)
894: {
895: int ret;
896: struct configfs_dirent *sd;
897:
898: ret = configfs_attach_item(item, dentry, frag);
899: if (!ret) {
900: sd = dentry->d_fsdata;
901: sd->s_type |= CONFIGFS_USET_DIR;
902:
903: /*
904: * FYI, we're faking mkdir in populate_groups()
905: * We must lock the group's inode to avoid races with the VFS
906: * which can already hit the inode and try to add/remove entries
907: * under it.
908: *
909: * We must also lock the inode to remove it safely in case of
910: * error, as rmdir() would.
911: */
912: inode_lock_nested(d_inode(dentry), I_MUTEX_CHILD);
913: configfs_adjust_dir_dirent_depth_before_populate(sd);
914: ret = populate_groups(to_config_group(item), frag);
915: if (ret) {
916: configfs_detach_group(dentry);
917: d_inode(dentry)->i_flags |= S_DEAD;
918: dont_mount(dentry);
919: }
920: configfs_adjust_dir_dirent_depth_after_populate(sd);
921: inode_unlock(d_inode(dentry));
922: if (ret)
923: d_delete(dentry);
924: }
925:
926: return ret;
927: }
928:
929: /*
]
|
| 26/2 |
2026/09/02 20:57 |
llm |
debugger |
0m
Model:
Tokens:
input: 23887
output: 36
thoughts: 18
Thoughts: Okay, here's my attempt at summarizing those thoughts, speaking as an expert:
**Diving into Detachment Mechanisms**
Alright, time to dig into `detach_groups` and `configfs_detach_item`. I'm guessing these are core to how we handle object removal and cleanup within the configfs framework. "Detach" is a strong word, suggesting a more aggressive removal than a simple "unregister" β potentially implying a need to break down parent-child relationships, free resources, or signal to other modules about the removal. I expect to see interplay between these two functions; likely, `detach_groups` handles the higher-level groups or directories, perhaps coordinating the detachment of multiple items, while `configfs_detach_item` deals with the nitty-gritty of removing individual configuration objects. I'm especially interested to see how they deal with error handling: are there retries? Are there specific error codes we care about? Are these functions designed to be idempotent? And finally, what synchronization primitives are in play? Spinlocks? Mutexes? RCU? The choice of synchronization is critical to avoiding race conditions in a multi-threaded or multi-process environment. I hope they are well-documented. Time to see what's what!
|
| 27/2 |
2026/09/02 20:57 |
tool |
read-file |
0m
Args: map[File:fs/configfs/dir.c FirstLine:660 LineCount:50]
Results: map[Contents: 660: struct dentry *dentry,
661: struct configfs_fragment *frag);
662: static void configfs_detach_group(struct dentry *dentry);
663:
664: static void detach_groups(struct dentry *dentry)
665: {
666: struct dentry *child;
667: struct configfs_dirent *parent_sd;
668: struct configfs_dirent *sd, *next;
669:
670: parent_sd = dentry->d_fsdata;
671: spin_lock(&configfs_dirent_lock);
672: for (sd = next_dirent(parent_sd, NULL); sd; sd = next) {
673: next = next_dirent(parent_sd, sd);
674: if (!(sd->s_type & CONFIGFS_USET_DEFAULT))
675: continue;
676:
677: child = dget(sd->s_dentry);
678: spin_unlock(&configfs_dirent_lock);
679:
680: inode_lock(d_inode(child));
681:
682: configfs_detach_group(child);
683: d_inode(child)->i_flags |= S_DEAD;
684: dont_mount(child);
685:
686: inode_unlock(d_inode(child));
687:
688: d_delete(child);
689: dput(child);
690: spin_lock(&configfs_dirent_lock);
691: }
692: spin_unlock(&configfs_dirent_lock);
693: }
694:
695: /*
696: * This fakes mkdir(2) on a default_groups[] entry. It
697: * creates a dentry, attachs it, and then does fixup
698: * on the sd->s_type.
699: *
700: * We could, perhaps, tweak our parent's ->mkdir for a minute and
701: * try using vfs_mkdir. Just a thought.
702: */
703: static int create_default_group(struct dentry *parent,
704: struct config_group *group,
705: struct configfs_fragment *frag)
706: {
707: int ret;
708: struct configfs_dirent *sd;
709: /* We trust the caller holds a reference to parent */
]
|
| 28/2 |
2026/09/02 20:57 |
llm |
debugger |
0m
Model:
Tokens:
input: 24956
output: 36
thoughts: 9
Thoughts: Okay, here's my attempt at summarizing that thought process, keeping in mind I'm speaking to a fellow expert and keeping it in the first person:
**Understanding the `configfs_detach_item` Function**
Alright, so I'm thinking about `configfs_detach_item`. Let's break it down. We're obviously dealing with the ConfigFS framework, and this function specifically handles the detaching of an item, right? So, the core function must be responsible for severing the links, freeing resources, and ensuring the clean removal of a particular ConfigFS item from the system's namespace.
I need to consider the implications of this action. What are the dependencies? Does it involve removing entries from the file system, or manipulating internal data structures? Are there associated locks that need to be acquired and released safely? It's crucial to think about race conditions here. Detaching an item shouldn't leave the system in an inconsistent state if, for example, another process tries to access it concurrently. That's a classic gotcha.
I'm probably going to want to dig into the surrounding code β what calls this function, and what does it call? Seeing the call chain is going to be key to really understanding the context of the detachment. I'd need to consider how this function interacts with other ConfigFS components, like the creation and modification functions. Knowing what data structures are modified would be the most important. I assume the item to detach is identified by some sort of handle or pointer. Is there any reference counting or garbage collection involved? And what happens if the detach fails partway through β what's the rollback strategy? Gotta make sure it's robust.
|
| 29/2 |
2026/09/02 20:57 |
tool |
read-file |
0m
Args: map[File:fs/configfs/dir.c FirstLine:830 LineCount:50]
Results: map[Contents: 830:
831: list_for_each_entry(new_group, &group->default_groups, group_entry)
832: link_group(group, new_group);
833: }
834:
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: }
841:
842: /*
843: * The goal is that configfs_attach_item() (and
844: * configfs_attach_group()) can be called from either the VFS or this
845: * module. That is, they assume that the items have been created,
846: * the dentry allocated, and the dcache is all ready to go.
847: *
848: * If they fail, they must clean up after themselves as if they
849: * had never been called. The caller (VFS or local function) will
850: * handle cleaning up the dcache bits.
851: *
852: * configfs_detach_group() and configfs_detach_item() behave similarly on
853: * the way out. They assume that the proper semaphores are held, they
854: * clean up the configfs items, and they expect their callers will
855: * handle the dcache bits.
856: */
857: static int configfs_attach_item(struct config_item *item,
858: struct dentry *dentry,
859: struct configfs_fragment *frag)
860: {
861: int ret;
862:
863: ret = configfs_create_dir(item, dentry, frag);
864: if (!ret) {
865: ret = populate_attrs(item);
866: if (ret) {
867: /*
868: * We are going to remove an inode and its dentry but
869: * the VFS may already have hit and used them. Thus,
870: * we must lock them as rmdir() would.
871: */
872: inode_lock(d_inode(dentry));
873: configfs_detach_item(dentry);
874: d_inode(dentry)->i_flags |= S_DEAD;
875: dont_mount(dentry);
876: inode_unlock(d_inode(dentry));
877: d_delete(dentry);
878: }
879: }
]
|
| 30/2 |
2026/09/02 20:57 |
llm |
debugger |
0m
Model:
Tokens:
input: 26137
output: 36
thoughts: 12
Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience:
**Diving into `configfs_remove_dir`**
Alright, let's zero in on `configfs_remove_dir`. My initial thought process is to quickly dissect this function. Given its name, I'm expecting it to be the core function responsible for, well, removing a directory structure within the ConfigFS filesystem. This is a critical operation, often involving unlinking inodes, freeing up associated resources (memory, particularly), and meticulously managing references to prevent dangling pointers or other memory corruption issues.
My immediate concerns center around its implementation details. I'll be looking for robust error handling β what happens if the directory is busy, if there are outstanding references to objects within it, or if there are permissions issues? Are there any race conditions that might arise from concurrent access to the directory being removed? I'll also want to understand the locking strategy employed β is it using fine-grained locks, or a more coarse approach, and how does that affect performance and concurrency?
Specifically, I'll be keeping an eye out for how it handles:
* **Recursive deletion:** Does it handle nested directories and files correctly? Any potential for stack overflows if the structure is deep?
* **Reference counting:** How does it deal with objects that might be referenced by multiple users or subsystems? Proper reference management is absolutely critical here.
* **Transactionality (or lack thereof):** Is this operation atomic, or can it leave the filesystem in an inconsistent state if it's interrupted?
* **Error reporting:** Does it provide enough information to diagnose failures?
* **Security:** How does it handle access control to ensure that only authorized users can perform this operation?
I'll be mentally mapping out the data structures involved, like the `configfs_dir` and `configfs_item` objects, and tracing the pointers that are manipulated during the deletion process. I'll need to understand how it interacts with the underlying VFS layer β does it use `vfs_unlink` or its equivalents, and how are these operations orchestrated?
Essentially, I'm expecting to see a carefully crafted piece of code that guards against all the potential pitfalls inherent in directory removal. I'm going in with a critical mindset, ready to scrutinize every line.
|
| 31/2 |
2026/09/02 20:57 |
tool |
read-file |
0m
Args: map[File:fs/configfs/dir.c FirstLine:400 LineCount:50]
Results: map[Contents: 400: * configfs_remove_dir - remove an config_item's directory.
401: * @d: dentry we're removing.
402: *
403: * The only thing special about this is that we remove any files in
404: * the directory before we remove the directory, and we've inlined
405: * what used to be configfs_rmdir() below, instead of calling separately.
406: *
407: * Caller holds the mutex of the item's inode
408: */
409:
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: }
425:
426: static struct dentry * configfs_lookup(struct inode *dir,
427: struct dentry *dentry,
428: unsigned int flags)
429: {
430: struct configfs_dirent * parent_sd = dentry->d_parent->d_fsdata;
431: struct configfs_dirent * sd;
432: struct inode *inode = NULL;
433:
434: if (dentry->d_name.len > NAME_MAX)
435: return ERR_PTR(-ENAMETOOLONG);
436:
437: /*
438: * Fake invisibility if dir belongs to a group/default groups hierarchy
439: * being attached
440: *
441: * This forbids userspace to read/write attributes of items which may
442: * not complete their initialization, since the dentries of the
443: * attributes won't be instantiated.
444: */
445: if (!configfs_dirent_is_ready(parent_sd))
446: return ERR_PTR(-ENOENT);
447:
448: spin_lock(&configfs_dirent_lock);
449: list_for_each_entry(sd, &parent_sd->s_children, s_sibling) {
]
|
| 32/2 |
2026/09/02 20:57 |
llm |
debugger |
0m
Model:
Tokens:
input: 27450
output: 36
thoughts: 13
Thoughts: Okay, let's dive into this `configfs_remove_dirent` function. I need to understand its role within the broader context of the ConfigFS framework. It's clear from the name that it's responsible for removing a directory entry, likely a node within the ConfigFS filesystem. So, my initial questions are: what kind of checks are performed before the removal? Are there any dependencies or data structures that need to be cleaned up first? I'm thinking about potential race conditions and locking mechanisms β what's in place to ensure data integrity during this removal process? Does it handle error conditions gracefully, and what kind of error codes are returned if something goes wrong? I also need to consider the implications of removing a directory entry that might contain other entries or data. Is it a recursive removal? If not, what's the expected behavior when attempting to remove a non-empty directory? I'll probably also want to see how this function is *called* β what other parts of the ConfigFS subsystem rely on its functionality? Understanding the calling context is crucial for grasping the overall design and any potential performance bottlenecks.
|
| 33/2 |
2026/09/02 20:57 |
tool |
read-file |
0m
Args: map[File:fs/configfs/dir.c FirstLine:350 LineCount:50]
Results: map[Contents: 350: * attached and not validated yet.
351: * @sd configfs_dirent of the directory to check
352: *
353: * @return non-zero iff the directory was validated
354: *
355: * Note: takes configfs_dirent_lock, so the result may change from false to true
356: * in two consecutive calls, but never from true to false.
357: */
358: int configfs_dirent_is_ready(struct configfs_dirent *sd)
359: {
360: int ret;
361:
362: spin_lock(&configfs_dirent_lock);
363: ret = !(sd->s_type & CONFIGFS_USET_CREATING);
364: spin_unlock(&configfs_dirent_lock);
365:
366: return ret;
367: }
368:
369: int configfs_create_link(struct configfs_dirent *target, struct dentry *parent,
370: struct dentry *dentry, char *body)
371: {
372: int err = 0;
373: umode_t mode = S_IFLNK | S_IRWXUGO;
374: struct configfs_dirent *p = parent->d_fsdata;
375: struct inode *p_inode = d_inode(parent);
376: struct inode *inode;
377:
378: err = configfs_make_dirent(p, dentry, target, mode, CONFIGFS_ITEM_LINK,
379: p->s_frag);
380: if (err)
381: return err;
382:
383: inode = configfs_create(dentry, mode);
384: if (IS_ERR(inode))
385: goto out_remove;
386:
387: inode->i_link = body;
388: inode->i_op = &configfs_symlink_inode_operations;
389: d_make_persistent(dentry, inode);
390: inode_set_mtime_to_ts(p_inode, inode_set_ctime_current(p_inode));
391: return 0;
392:
393: out_remove:
394: configfs_put(dentry->d_fsdata);
395: configfs_remove_dirent(dentry);
396: return PTR_ERR(inode);
397: }
398:
399: /**
]
|
| 34/2 |
2026/09/02 20:57 |
llm |
debugger |
0m
Model:
Tokens:
input: 28496
output: 21
thoughts: 15
Thoughts: Here's the summary, assuming I'm an expert:
**Investigating a Code Snippet: Variable Declaration Order**
Okay, hold on a second. I'm looking at this code and something's not quite right. I just spotted a potential issue. The function `configfs_remove_dirent`... isn't that defined *earlier* in the file? I'm pretty sure I saw its declaration further up. This makes me want to double-check where and how that function is being implemented; perhaps it should have been `static inline`? Or is there a forward declaration involved I'm missing? I need to quickly scan the surrounding code to ensure this apparent definition order isn't causing a compiler warning, a subtle bug, or unexpected behavior. This might just be a minor stylistic concern, but it's worth a quick look to make sure the program flow is clear and maintainable. I'd hate for someone to come along later and misunderstand the scope or availability of that function.
|
| 35/2 |
2026/09/02 20:58 |
tool |
grepper |
0m
Args: map[Expression:configfs_remove_dirent]
Results: map[Output:fs/configfs/dir.c=253=int configfs_make_dirent(struct configfs_dirent * parent_sd,
--
fs/configfs/dir.c-270-
fs/configfs/dir.c:271:static void configfs_remove_dirent(struct dentry *dentry)
fs/configfs/dir.c-272-{
--
fs/configfs/dir.c=293=static int configfs_create_dir(struct config_item *item, struct dentry *dentry,
--
fs/configfs/dir.c-326- configfs_put(dentry->d_fsdata);
fs/configfs/dir.c:327: configfs_remove_dirent(dentry);
fs/configfs/dir.c-328- return PTR_ERR(inode);
--
fs/configfs/dir.c=369=int configfs_create_link(struct configfs_dirent *target, struct dentry *parent,
--
fs/configfs/dir.c-394- configfs_put(dentry->d_fsdata);
fs/configfs/dir.c:395: configfs_remove_dirent(dentry);
fs/configfs/dir.c-396- return PTR_ERR(inode);
--
fs/configfs/dir.c=410=static void configfs_remove_dir(struct dentry *d)
--
fs/configfs/dir.c-413-
fs/configfs/dir.c:414: configfs_remove_dirent(d);
fs/configfs/dir.c-415-
]
|
| 36/2 |
2026/09/02 20:58 |
llm |
debugger |
0m
Model:
Tokens:
input: 29171
output: 36
thoughts: 13
Thoughts: Okay, let's dive into this. Here's my take on `configfs_remove_dirent`, summarized in the first person, as if these are my immediate thoughts:
**Dissecting `configfs_remove_dirent`**
Alright, so I'm looking at `configfs_remove_dirent`. My initial assumption, given the naming, is that this is the function responsible for cleaning up directory entries within the configfs filesystem. Essentially, it's the core routine for removing a directory entry, which could be anything from a simple attribute file to a more complex object like a configuration group.
I need to understand the nuances of how ConfigFS handles these removals. I'm thinking about the implications of removing an entry while other parts of the system might still have references to it. Is there proper locking involved? How does it handle potential race conditions? Is this function called in a context where sleeping is allowed, or is it expected to be atomic? What cleanup routines does it call? Does it release resources safely? I need to look closely at the memory management, and ensure there aren't any potential leaks. Does it decrement reference counts? Does it handle error conditions gracefully, and return appropriate error codes?
Specifically, I'll be looking for where and how the inode is removed, since that's what `configfs` uses for the directory entries. I want to pay attention to any cleanup callbacks associated with the dirent being removed. I'll mentally map out the different paths of execution. I'll need to look at what other files and structures it touches to fully understand the impact of calling this function. I'll also check if there's any need for synchronization. What locking is involved? What is the function's place in the broader lifecycle of the file system? I'll probably go digging deeper into the surrounding code to ensure I have all the contextual information I need to form a complete understanding. Time to dive in!
|
| 37/2 |
2026/09/02 20:58 |
tool |
read-file |
0m
Args: map[File:fs/configfs/dir.c FirstLine:270 LineCount:20]
Results: map[Contents: 270:
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: }
282:
283: /**
284: * configfs_create_dir - create a directory for an config_item.
285: * @item: config_itemwe're creating directory for.
286: * @dentry: config_item's dentry.
287: * @frag: config_item's fragment.
288: *
289: * Note: user-created entries won't be allowed under this new directory
]
|
| 38/2 |
2026/09/02 20:58 |
llm |
debugger |
0m
Model:
Tokens:
input: 29947
output: 36
thoughts: 777
Thoughts: **Vulnerability Assessment: Race Condition in configfs Symlink Handling**
Okay, let's break this down. The core issue seems to be a race condition when creating symlinks within `configfs`. I'm starting by looking at how `configfs_remove_dirent` interacts with the `s_children` list β that's the removal side of things. But then, `configfs_symlink` jumps into the picture. It's doing something rather unorthodox. It's essentially implementing a `symlinkat`-like functionality, but in a very messy way, grabbing a lock on the directory and then releasing it to resolve the target, which seems inherently problematic. They're releasing the lock on the directory to resolve the target, then reacquiring it and checking if the directory is still there.
This unlocked window is where the problem lies. While the lock is dropped during the target lookup in `get_target()`, `rmdir` could be called on a directory like `ports/1`. Let's trace that execution path: `rmdir` on `ports/1` calls `configfs_rmdir`, which in turn calls `configfs_detach_group`. This then goes through `detach_groups`, which detaches `subsystems` (a default group), calling `configfs_detach_group` again on `subsystems`. That gets us to `configfs_detach_item`, which eventually leads to `configfs_remove_dir` and, finally, `configfs_remove_dirent`. This removes `subsystems` from `ports/1`'s children list. Then `configfs_rmdir` on `ports/1` calls `config_item_put` on `ports/1`. So far, so good, it seems.
The critical point here is that calling `config_item_put` drops the reference to `ports/1`, and `ports/1` is freed. *However*, here's where my alarm bells are ringing: `configfs_symlink` holds a reference to `parent_item` (which *is* `subsystems`) *before* the directory lock is dropped. So the process still holds a reference to `subsystems`.
The lingering question is: Does holding a reference to `subsystems` prevent `ports/1` from being freed *even though* the code called `config_item_put` on `ports/1`? The key question then is how `config_item_put` interacts with the parent item. I need to dive deeper into how `config_item_put` functions in this context. Is it possible to free `ports/1` when `subsystems` is still referenced? This is a textbook race condition vulnerability. The timing between the release of the `ports/1` and the potential access of a dangling pointer via `subsystems` is where the bug resides. This looks like a serious memory corruption risk if the access occurs after `ports/1` is freed. I need to understand this `config_item_put` more clearly.
|
| 39/2 |
2026/09/02 20:58 |
tool |
read-file |
0m
Args: map[File:fs/configfs/item.c FirstLine:110 LineCount:50]
Results: map[Contents: 110: {
111: if (item && kref_get_unless_zero(&item->ci_kref))
112: return item;
113: return NULL;
114: }
115: EXPORT_SYMBOL(config_item_get_unless_zero);
116:
117: static void config_item_cleanup(struct config_item *item)
118: {
119: const struct config_item_type *t = item->ci_type;
120: struct config_group *s = item->ci_group;
121: struct config_item *parent = item->ci_parent;
122:
123: pr_debug("config_item %s: cleaning up\n", config_item_name(item));
124: if (item->ci_name != item->ci_namebuf)
125: kfree(item->ci_name);
126: item->ci_name = NULL;
127: if (t && t->ct_item_ops && t->ct_item_ops->release)
128: t->ct_item_ops->release(item);
129: if (s)
130: config_group_put(s);
131: if (parent)
132: config_item_put(parent);
133: }
134:
135: static void config_item_release(struct kref *kref)
136: {
137: config_item_cleanup(container_of(kref, struct config_item, ci_kref));
138: }
139:
140: /**
141: * config_item_put - decrement refcount for item.
142: * @item: item.
143: *
144: * Decrement the refcount, and if 0, call config_item_cleanup().
145: */
146: void config_item_put(struct config_item *item)
147: {
148: if (item)
149: kref_put(&item->ci_kref, config_item_release);
150: }
151: EXPORT_SYMBOL(config_item_put);
152:
153: /**
154: * config_group_init - initialize a group for use
155: * @group: config_group
156: */
157: void config_group_init(struct config_group *group)
158: {
159: config_item_init(&group->cg_item);
]
|
| 40/2 |
2026/09/02 20:58 |
llm |
debugger |
0m
Model:
Tokens:
input: 32167
output: 36
thoughts: 250
Thoughts: **My Line of Reasoning on the ConfigFS Cleanup**
Okay, so `config_item_cleanup` is the key function here. It executes `t->ct_item_ops->release(item)` first, then *immediately* calls `config_item_put(parent)`. That's the sequence I need to focus on. Thinking through a specific scenario, if `subsystems` is part of a default group, then its `ci_parent` is going to be something like `ports/1`.
Alright, if I call `rmdir` on `ports/1`, that's going to ultimately remove `ports/1`. Makes sense. But let's dig deeper: that `rmdir` call translates into a `configfs_rmdir` on `ports/1`. And within `configfs_rmdir`, another `config_item_put(item)` gets executed, with `item` being `ports/1`. Hold on... does that immediate `config_item_put` in `configfs_rmdir` actually *drop* the reference to `ports/1`?
Yes, thatβs exactly what happens: `configfs_rmdir` calls `config_item_put(item)`. But here's the kicker: `ports/1` has child config items, like this `subsystems` group. When `ports/1` was initially created, I'm pretty sure `subsystems` was created as a default group attached to it. So, does `subsystems` now hold a reference to `ports/1`? I need to check how that `create_default_group` function actually handles that linking.
|
| 41/2 |
2026/09/02 20:58 |
tool |
read-file |
0m
Args: map[File:fs/configfs/dir.c FirstLine:700 LineCount:50]
Results: map[Contents: 700: * We could, perhaps, tweak our parent's ->mkdir for a minute and
701: * try using vfs_mkdir. Just a thought.
702: */
703: static int create_default_group(struct dentry *parent,
704: struct config_group *group,
705: struct configfs_fragment *frag)
706: {
707: int ret;
708: struct configfs_dirent *sd;
709: /* We trust the caller holds a reference to parent */
710: struct dentry *child;
711:
712: if (!group->cg_item.ci_name)
713: group->cg_item.ci_name = group->cg_item.ci_namebuf;
714:
715: ret = -ENOMEM;
716: child = d_alloc_name(parent, group->cg_item.ci_name);
717: if (child) {
718: d_add(child, NULL);
719:
720: ret = configfs_attach_group(&group->cg_item, child, frag);
721: if (!ret) {
722: sd = child->d_fsdata;
723: sd->s_type |= CONFIGFS_USET_DEFAULT;
724: } else {
725: BUG_ON(d_inode(child));
726: d_drop(child);
727: }
728: dput(child);
729: }
730:
731: return ret;
732: }
733:
734: static int populate_groups(struct config_group *group,
735: struct configfs_fragment *frag)
736: {
737: struct dentry *parent = group->cg_item.ci_dentry;
738: struct config_group *new_group;
739:
740: list_for_each_entry(new_group, &group->default_groups, group_entry) {
741: int ret = create_default_group(parent, new_group, frag);
742: if (ret)
743: return ret;
744: }
745: return 0;
746: }
747:
748: void configfs_remove_default_groups(struct config_group *group)
749: {
]
|
| 42/2 |
2026/09/02 20:58 |
llm |
debugger |
0m
Model:
Tokens:
input: 33531
output: 36
thoughts: 60
Thoughts: Here's my take on those thoughts:
**Dissecting the ConfigFS Attachment Process**
Okay, so I'm looking at this `create_default_group` function and it's calling `configfs_attach_group`. Hang on, that makes sense β we're creating a default group so we need to, well, *attach* it. But it's calling `configfs_attach_group` with that `&group->cg_item, child, frag` structure. Right, `cg_item`, that's the core configuration item within the group. Now, `configfs_attach_group` itself calls `configfs_attach_item`. That's where the *real* attachment mechanism kicks in, so that's the one I need to focus on. Let's dig into `configfs_attach_item` and see how it handles the nitty-gritty of linking these things together. I need to understand this to have a handle on the data flow.
|
| 43/2 |
2026/09/02 20:58 |
tool |
read-file |
0m
Args: map[File:fs/configfs/dir.c FirstLine:850 LineCount:50]
Results: map[Contents: 850: * handle cleaning up the dcache bits.
851: *
852: * configfs_detach_group() and configfs_detach_item() behave similarly on
853: * the way out. They assume that the proper semaphores are held, they
854: * clean up the configfs items, and they expect their callers will
855: * handle the dcache bits.
856: */
857: static int configfs_attach_item(struct config_item *item,
858: struct dentry *dentry,
859: struct configfs_fragment *frag)
860: {
861: int ret;
862:
863: ret = configfs_create_dir(item, dentry, frag);
864: if (!ret) {
865: ret = populate_attrs(item);
866: if (ret) {
867: /*
868: * We are going to remove an inode and its dentry but
869: * the VFS may already have hit and used them. Thus,
870: * we must lock them as rmdir() would.
871: */
872: inode_lock(d_inode(dentry));
873: configfs_detach_item(dentry);
874: d_inode(dentry)->i_flags |= S_DEAD;
875: dont_mount(dentry);
876: inode_unlock(d_inode(dentry));
877: d_delete(dentry);
878: }
879: }
880:
881: return ret;
882: }
883:
884: /* Caller holds the mutex of the group's inode */
885: static void configfs_detach_group(struct dentry *dentry)
886: {
887: detach_groups(dentry);
888: configfs_detach_item(dentry);
889: }
890:
891: static int configfs_attach_group(struct config_item *item,
892: struct dentry *dentry,
893: struct configfs_fragment *frag)
894: {
895: int ret;
896: struct configfs_dirent *sd;
897:
898: ret = configfs_attach_item(item, dentry, frag);
899: if (!ret) {
]
|
| 44/2 |
2026/09/02 20:58 |
llm |
debugger |
0m
Model:
Tokens:
input: 34565
output: 36
thoughts: 26
Thoughts: Here's a summary of my thought process, as requested:
**Digging into ConfigFS Directory Creation**
Okay, so I'm looking at how directories are created within ConfigFS. My mind immediately flags `configfs_create_dir`. I know that function is key, but now I'm thinking about something subtle: I recall that `configfs_create_dir` calls `config_item_get(item)`. That seems important. I need to understand why that call is there and what it's doing with the `item`. Is it related to reference counting? Is it to ensure the item is valid? Perhaps it's grabbing a hold of the item to prevent it from being freed while the directory is in the process of being created. Gotta investigate this call, trace its use, and be sure I've got a handle on the lifecycle management. Let's start by digging in a bit deeper to see what's actually happening around that `config_item_get` call.
|
| 45/2 |
2026/09/02 20:58 |
tool |
read-file |
0m
Args: map[File:fs/configfs/dir.c FirstLine:280 LineCount:50]
Results: map[Contents: 280: configfs_put(sd);
281: }
282:
283: /**
284: * configfs_create_dir - create a directory for an config_item.
285: * @item: config_itemwe're creating directory for.
286: * @dentry: config_item's dentry.
287: * @frag: config_item's fragment.
288: *
289: * Note: user-created entries won't be allowed under this new directory
290: * until it is validated by configfs_dir_set_ready()
291: */
292:
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: }
]
|
| 46/2 |
2026/09/02 20:58 |
llm |
debugger |
0m
Model:
Tokens:
input: 35665
output: 36
thoughts: 50
Thoughts: Okay, here's my take on those thoughts, framed as my own mental processing:
**Digging into configfs Directory Creation**
Alright, hang on a second. I was under the impression that `configfs_create_dir` was responsible for incrementing the reference count on a `config_item` using `config_item_get()` when creating a directory. But, based on this, it *doesn't* do that. I need to be sure about this detail.
Okay, so if *not* `configfs_create_dir`, perhaps the directory entry creation via `configfs_make_dirent` *is* where the reference counting is handled. That would make more sense in the overall lifecycle of these configfs items. I need to take a closer look at the source for `configfs_make_dirent` to confirm exactly what's happening and where this is handled. This is likely the crucial step.
|
| 47/2 |
2026/09/02 20:58 |
tool |
read-file |
0m
Args: map[File:fs/configfs/dir.c FirstLine:250 LineCount:50]
Results: map[Contents: 250: }
251:
252:
253: int configfs_make_dirent(struct configfs_dirent * parent_sd,
254: struct dentry * dentry, void * element,
255: umode_t mode, int type, struct configfs_fragment *frag)
256: {
257: struct configfs_dirent * sd;
258:
259: sd = configfs_new_dirent(parent_sd, element, type, frag);
260: if (IS_ERR(sd))
261: return PTR_ERR(sd);
262:
263: sd->s_mode = mode;
264: sd->s_dentry = dentry;
265: if (dentry)
266: dentry->d_fsdata = configfs_get(sd);
267:
268: return 0;
269: }
270:
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: }
282:
283: /**
284: * configfs_create_dir - create a directory for an config_item.
285: * @item: config_itemwe're creating directory for.
286: * @dentry: config_item's dentry.
287: * @frag: config_item's fragment.
288: *
289: * Note: user-created entries won't be allowed under this new directory
290: * until it is validated by configfs_dir_set_ready()
291: */
292:
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);
]
|
| 48/2 |
2026/09/02 20:58 |
llm |
debugger |
0m
Model:
Tokens:
input: 36723
output: 39
thoughts: 114
Thoughts: **Investigating the `configfs_make_dirent` Call Chain**
Okay, so I'm digging into this `configfs_make_dirent` function and something's bugging me. It doesn't seem to have a call to `config_item_get`, which is... unusual, to say the least. That immediately raises the question: who *is* responsible for incrementing the reference count on the default group item? Where's the corresponding `config_item_put`?
Let me retrace the call stack. When I look at `populate_groups`, it's calling `create_default_group`, and it's passing `new_group` as an argument. Ah, right, that `new_group` is embedded within the parent item, like the `nvmet_port` structure, which I believe is the common case here. But, since `subsystems` is a default group of `nvmet_port`, it's my intention to analyze the `nvmet_port` definition now. This all hinges on how those default groups are managed and their lifecycles. I need to understand the reference counting and memory management in this specific context. Let's see what's actually going on in the `nvmet_port` definition and the surrounding infrastructure.
|
| 49/2 |
2026/09/02 20:58 |
tool |
read-file |
0m
Args: map[File:drivers/nvme/target/nvmet.h FirstLine:200 LineCount:30]
Results: map[Contents: 200: * @group: ConfigFS group for this element's folder.
201: * @priv: Private data for the transport.
202: */
203: struct nvmet_port {
204: struct list_head entry;
205: struct nvmf_disc_rsp_page_entry disc_addr;
206: struct config_group group;
207: struct config_group subsys_group;
208: struct list_head subsystems;
209: struct config_group referrals_group;
210: struct list_head referrals;
211: struct list_head global_entry;
212: struct config_group ana_groups_group;
213: struct nvmet_ana_group ana_default_group;
214: struct key *keyring;
215: void *priv;
216: bool enabled;
217: int inline_data_size;
218: int max_queue_size;
219: int mdts;
220: const struct nvmet_fabrics_ops *tr_ops;
221: bool pi_enable;
222: enum nvme_ana_state ana_state[];
223: };
224:
225: static inline struct nvmet_port *to_nvmet_port(struct config_item *item)
226: {
227: return container_of(to_config_group(item), struct nvmet_port,
228: group);
229: }
]
|
| 50/2 |
2026/09/02 20:58 |
llm |
debugger |
0m
Model:
Tokens:
input: 37625
output: 40
thoughts: 38
Thoughts: Okay, here's my interpretation of that thought process, assuming I'm already deep in the weeds of this NVMe-over-Fabric implementation:
**Decoding the `nvmet_port` Relationship**
Right, so I'm seeing this: the `subsys_group` structure is *actually* embedded right within the `nvmet_port` structure itself. Interesting. Now, I'm recalling how this all gets set up β when `nvmet_ports_make` is invoked, that's the point where an `nvmet_port` instance is allocated. That clarifies a bit how they're related in memory and how to find one from the other. Good to keep in mind for debugging later on.
|
| 51/2 |
2026/09/02 20:59 |
tool |
read-file |
0m
Args: map[File:drivers/nvme/target/configfs.c FirstLine:2040 LineCount:50]
Results: map[Contents:2040:
2041: static struct config_group *nvmet_ports_make(struct config_group *group,
2042: const char *name)
2043: {
2044: struct nvmet_port *port;
2045: u16 portid;
2046: u32 i;
2047:
2048: if (kstrtou16(name, 0, &portid))
2049: return ERR_PTR(-EINVAL);
2050:
2051: port = kzalloc_flex(*port, ana_state, NVMET_MAX_ANAGRPS + 1);
2052: if (!port)
2053: return ERR_PTR(-ENOMEM);
2054:
2055: if (IS_ENABLED(CONFIG_NVME_TARGET_TCP_TLS) && nvme_keyring_id()) {
2056: port->keyring = key_lookup(nvme_keyring_id());
2057: if (IS_ERR(port->keyring)) {
2058: pr_warn("NVMe keyring not available, disabling TLS\n");
2059: port->keyring = NULL;
2060: }
2061: }
2062:
2063: for (i = 1; i <= NVMET_MAX_ANAGRPS; i++) {
2064: if (i == NVMET_DEFAULT_ANA_GRPID)
2065: port->ana_state[1] = NVME_ANA_OPTIMIZED;
2066: else
2067: port->ana_state[i] = NVME_ANA_INACCESSIBLE;
2068: }
2069:
2070: list_add(&port->global_entry, &nvmet_ports_list);
2071:
2072: INIT_LIST_HEAD(&port->entry);
2073: INIT_LIST_HEAD(&port->subsystems);
2074: INIT_LIST_HEAD(&port->referrals);
2075: port->inline_data_size = -1; /* < 0 == let the transport choose */
2076: port->max_queue_size = -1; /* < 0 == let the transport choose */
2077: port->mdts = -1; /* < 0 == let the transport choose */
2078:
2079: port->disc_addr.trtype = NVMF_TRTYPE_MAX;
2080: port->disc_addr.portid = cpu_to_le16(portid);
2081: port->disc_addr.adrfam = NVMF_ADDR_FAMILY_MAX;
2082: port->disc_addr.treq = NVMF_TREQ_DISABLE_SQFLOW;
2083: config_group_init_type_name(&port->group, name, &nvmet_port_type);
2084:
2085: config_group_init_type_name(&port->subsys_group,
2086: "subsystems", &nvmet_port_subsys_type);
2087: configfs_add_default_group(&port->subsys_group, &port->group);
2088:
2089: config_group_init_type_name(&port->referrals_group,
]
|
| 52/2 |
2026/09/02 20:59 |
llm |
debugger |
0m
Model:
Tokens:
input: 38767
output: 35
thoughts: 176
Thoughts: **My Line of Inquiry: Refcount Propagation in ConfigFS Symlinks**
Okay, so I'm tracing through this code, and it looks like `subsys_group` is being set up as the default group for `port->group`. That's standard practice. Now, when `configfs_symlink` is called on something like `ports/1/subsystems`, the `parent_item` argument inside of `configfs_symlink` points to `&port->subsys_group.cg_item`.
The core logic of `configfs_symlink` itself relies on `configfs_get_config_item(dentry->d_parent)` to grab the configuration item of the parent. And, importantly, that call is *supposed* to increment the reference count of the item. This is crucial for resource management within ConfigFS.
But here's where my expert curiosity kicks in. We've got `&port->subsys_group.cg_item`, and we know the refcount is incremented there. *Does* this refcount change propagate upwards to `port->group`? Is there some kind of inherent relationship between the refcounts of these groups that the code implicitly relies on? I need to verify how these groups are managed. Let's dig into `config_item_get` to get the specifics. It's time to check how that call interacts with the higher-level grouping structure.
|
| 53/2 |
2026/09/02 20:59 |
tool |
read-file |
0m
Args: map[File:fs/configfs/item.c FirstLine:90 LineCount:50]
Results: map[Contents: 90: EXPORT_SYMBOL(config_item_init_type_name);
91:
92: void config_group_init_type_name(struct config_group *group, const char *name,
93: const struct config_item_type *type)
94: {
95: config_item_set_name(&group->cg_item, "%s", name);
96: group->cg_item.ci_type = type;
97: config_group_init(group);
98: }
99: EXPORT_SYMBOL(config_group_init_type_name);
100:
101: struct config_item *config_item_get(struct config_item *item)
102: {
103: if (item)
104: kref_get(&item->ci_kref);
105: return item;
106: }
107: EXPORT_SYMBOL(config_item_get);
108:
109: struct config_item *config_item_get_unless_zero(struct config_item *item)
110: {
111: if (item && kref_get_unless_zero(&item->ci_kref))
112: return item;
113: return NULL;
114: }
115: EXPORT_SYMBOL(config_item_get_unless_zero);
116:
117: static void config_item_cleanup(struct config_item *item)
118: {
119: const struct config_item_type *t = item->ci_type;
120: struct config_group *s = item->ci_group;
121: struct config_item *parent = item->ci_parent;
122:
123: pr_debug("config_item %s: cleaning up\n", config_item_name(item));
124: if (item->ci_name != item->ci_namebuf)
125: kfree(item->ci_name);
126: item->ci_name = NULL;
127: if (t && t->ct_item_ops && t->ct_item_ops->release)
128: t->ct_item_ops->release(item);
129: if (s)
130: config_group_put(s);
131: if (parent)
132: config_item_put(parent);
133: }
134:
135: static void config_item_release(struct kref *kref)
136: {
137: config_item_cleanup(container_of(kref, struct config_item, ci_kref));
138: }
139:
]
|
| 54/2 |
2026/09/02 20:59 |
llm |
debugger |
0m
Model:
Tokens:
input: 40063
output: 36
thoughts: 125
Thoughts: **Analyzing Refcount Behavior in Configuration Item Management**
Okay, so looking at this `config_item_get` function, it's clear that it's only bumping the reference count of the `item` itself, which in this case, would be something like `subsys_group.cg_item`. It *doesn't* touch the refcount of the parent item, which is `item->ci_parent`, and in this specific scenario, that would be `port->group.cg_item`. That's important. I need to understand when and how the parent's refcount *is* incremented.
I remember thinking the parentβs refcount would be incremented when the child is created using `config_item_init`. Or, maybe it happens when the child and parent are linked? No wait, thatβs not right. The `ci_parent` is held butβ¦ I need to clarify that.
I should trace back through the code and figure out exactly where that `ci_parent` pointer is actually assigned and if a reference is taken during that assignment. Thatβs the critical piece I'm missing here.
|
| 55/2 |
2026/09/02 20:59 |
tool |
read-file |
0m
Args: map[File:fs/configfs/dir.c FirstLine:800 LineCount:50]
Results: map[Contents: 800: * cg_children
801: */
802: config_item_get(item);
803: }
804:
805: static void unlink_group(struct config_group *group)
806: {
807: struct config_group *new_group;
808:
809: list_for_each_entry(new_group, &group->default_groups, group_entry)
810: unlink_group(new_group);
811:
812: group->cg_subsys = NULL;
813: unlink_obj(&group->cg_item);
814: }
815:
816: static void link_group(struct config_group *parent_group, struct config_group *group)
817: {
818: struct config_group *new_group;
819: struct configfs_subsystem *subsys = NULL; /* gcc is a turd */
820:
821: link_obj(&parent_group->cg_item, &group->cg_item);
822:
823: if (parent_group->cg_subsys)
824: subsys = parent_group->cg_subsys;
825: else if (configfs_is_root(&parent_group->cg_item))
826: subsys = to_configfs_subsystem(group);
827: else
828: BUG();
829: group->cg_subsys = subsys;
830:
831: list_for_each_entry(new_group, &group->default_groups, group_entry)
832: link_group(group, new_group);
833: }
834:
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: }
841:
842: /*
843: * The goal is that configfs_attach_item() (and
844: * configfs_attach_group()) can be called from either the VFS or this
845: * module. That is, they assume that the items have been created,
846: * the dentry allocated, and the dcache is all ready to go.
847: *
848: * If they fail, they must clean up after themselves as if they
849: * had never been called. The caller (VFS or local function) will
]
|
| 56/2 |
2026/09/02 20:59 |
llm |
debugger |
0m
Model:
Tokens:
input: 41232
output: 36
thoughts: 9
Thoughts: Okay, I'm ready. Here's how I'll summarize those thoughts, keeping in mind I'm speaking to an expert:
**Analyzing the `link_obj` Object**
Alright, let's take a look at this `link_obj`. My initial thought is to assess its core properties. What's the data structure? Is it a dictionary, a custom class instance, perhaps a database connection object? I need to quickly identify the primary attributes and methods associated with it. From there, I'll want to trace its origin and understand how this `link_obj` was instantiated. Where does it fit into the broader system? Is it a globally accessible object, or is it specific to a particular module or function? Knowing its scope will be important for understanding any potential side effects.
Furthermore, I should probably check for any attached documentation or comments; even a well-named object can benefit from clear design commentary. Does this object have any inherent design patterns associated with it? Are there any obvious areas for potential optimization or potential security vulnerabilities?
Finally, I'll need to figure out the intended usage. What operations does it support? What's the typical workflow? Is it a data source, a consumer, or both? What are the expected inputs and outputs? Grasping its role within the system is crucial. By examining these aspects, I'll begin to form a comprehensive understanding of this `link_obj`'s function and significance.
|
| 57/2 |
2026/09/02 20:59 |
tool |
read-file |
0m
Args: map[File:fs/configfs/dir.c FirstLine:780 LineCount:50]
Results: map[Contents: 780: }
781: }
782:
783: static void link_obj(struct config_item *parent_item, struct config_item *item)
784: {
785: /*
786: * Parent seems redundant with group, but it makes certain
787: * traversals much nicer.
788: */
789: item->ci_parent = parent_item;
790:
791: /*
792: * We hold a reference on the parent for the child's ci_parent
793: * link.
794: */
795: item->ci_group = config_group_get(to_config_group(parent_item));
796: list_add_tail(&item->ci_entry, &item->ci_group->cg_children);
797:
798: /*
799: * We hold a reference on the child for ci_entry on the parent's
800: * cg_children
801: */
802: config_item_get(item);
803: }
804:
805: static void unlink_group(struct config_group *group)
806: {
807: struct config_group *new_group;
808:
809: list_for_each_entry(new_group, &group->default_groups, group_entry)
810: unlink_group(new_group);
811:
812: group->cg_subsys = NULL;
813: unlink_obj(&group->cg_item);
814: }
815:
816: static void link_group(struct config_group *parent_group, struct config_group *group)
817: {
818: struct config_group *new_group;
819: struct configfs_subsystem *subsys = NULL; /* gcc is a turd */
820:
821: link_obj(&parent_group->cg_item, &group->cg_item);
822:
823: if (parent_group->cg_subsys)
824: subsys = parent_group->cg_subsys;
825: else if (configfs_is_root(&parent_group->cg_item))
826: subsys = to_configfs_subsystem(group);
827: else
828: BUG();
829: group->cg_subsys = subsys;
]
|
| 58/2 |
2026/09/02 20:59 |
llm |
debugger |
0m
Model:
Tokens:
input: 42307
output: 36
thoughts: 972
Thoughts: **Analyzing a Use-After-Free in NVMe-oF Configuration**
Okay, let's break this down. It seems we have a use-after-free issue, and I need to figure out the sequence of events leading to it.
`link_obj` is called, and it sets up the parent-child relationship by incrementing the reference count on the parent (`parent_item`, in this case `port->group`). This creates a reference from the child (`subsys_group`) to the parent. Now, when `unlink_obj` gets called, it carefully decrements this reference. The key here is that `unlink_obj` drops the reference to the parent.
The trigger appears to be `configfs_rmdir` on `/ports/1`, which eventually calls `unlink_obj` on `subsys_group`. The critical bit is that this removes the `subsys_group`'s reference to `ports/1` (which is our `parent_item`!). So, at this point, `ports/1` is eligible for being freed because its reference count drops to zero.
However, a dangling reference remains. `configfs_symlink` holds a reference to `subsys_group` through `configfs_get_config_item(dentry->d_parent)`. This reference is kept alive.
Then, `configfs_symlink` calls `type->ct_item_ops->allow_link`, passing `subsys_group` as `parent_item`. Inside `nvmet_port_subsys_allow_link`, it attempts to retrieve the `nvmet_port` object. But it does this using `to_nvmet_port(parent->ci_parent)`. The puzzle is: *why isn't* `parent->ci_parent` `NULL`? If it were, then the code would not use the address inside of the freed object. If `parent->ci_parent` was `NULL`, we would have a null pointer dereference, but the bug is a use-after-free of the `nvmet_port` object.
The crash report confirms a use-after-free within the `nvmet_port` object, not a null pointer dereference. The address `ffff88811c6d44c8` is clearly inside the previously freed `nvmet_port` object. This implies the parent is not NULL, but pointing to the freed object! The sequence of events must lead to the parent pointer not being NULL but pointing to the freed memory.
I need to meticulously review the `unlink_obj` function again.
|
| 59/2 |
2026/09/02 20:59 |
tool |
read-file |
0m
Args: map[File:fs/configfs/dir.c FirstLine:760 LineCount:50]
Results: map[Contents: 760: * All of link_obj/unlink_obj/link_group/unlink_group require that
761: * subsys->su_mutex is held.
762: */
763:
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: }
782:
783: static void link_obj(struct config_item *parent_item, struct config_item *item)
784: {
785: /*
786: * Parent seems redundant with group, but it makes certain
787: * traversals much nicer.
788: */
789: item->ci_parent = parent_item;
790:
791: /*
792: * We hold a reference on the parent for the child's ci_parent
793: * link.
794: */
795: item->ci_group = config_group_get(to_config_group(parent_item));
796: list_add_tail(&item->ci_entry, &item->ci_group->cg_children);
797:
798: /*
799: * We hold a reference on the child for ci_entry on the parent's
800: * cg_children
801: */
802: config_item_get(item);
803: }
804:
805: static void unlink_group(struct config_group *group)
806: {
807: struct config_group *new_group;
808:
809: list_for_each_entry(new_group, &group->default_groups, group_entry)
]
|
| 60/2 |
2026/09/02 20:59 |
llm |
debugger |
0m
Model:
Tokens:
input: 44562
output: 37
thoughts: 70
Thoughts: **Tracing the Unlink Process**
Okay, hold on a second. It seems I was mistaken β `unlink_obj` *does* actually set `item->ci_parent = NULL`. That's good to clarify. But, now I'm thinking about the bigger picture. `configfs_rmdir` calls `unlink_group` on the specific port, like `ports/1`. So the important question now is, does `configfs_rmdir` also call `unlink_group` on the broader `subsys_group`? I need to get a better handle on the control flow here. I really need to trace the execution path of `configfs_rmdir` to be certain. I'm going to follow the logic step by step to see where `unlink_group` is getting called and on what groups. This is a critical detail in understanding the entire removal process.
|
| 61/2 |
2026/09/02 20:59 |
tool |
read-file |
0m
Args: map[File:fs/configfs/dir.c FirstLine:1540 LineCount:50]
Results: map[Contents: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: }
1578:
1579: const struct inode_operations configfs_dir_inode_operations = {
1580: .mkdir = configfs_mkdir,
1581: .rmdir = configfs_rmdir,
1582: .symlink = configfs_symlink,
1583: .unlink = configfs_unlink,
1584: .lookup = configfs_lookup,
1585: .setattr = configfs_setattr,
1586: };
1587:
1588: const struct inode_operations configfs_root_inode_operations = {
1589: .lookup = configfs_lookup,
]
|
| 62/2 |
2026/09/02 20:59 |
llm |
debugger |
0m
Model:
Tokens:
input: 45578
output: 36
thoughts: 846
Thoughts: **Analyzing a Potential Race Condition in configfs**
Okay, so I'm looking at these `configfs` functions, specifically `configfs_rmdir` and `configfs_symlink`. My initial thought was that a race condition might exist because of lock contention, but I've drilled down further.
`configfs_rmdir` calls `configfs_detach_group`, which in turn calls `detach_groups`, which locks the `dentry` representing the directory being removed (e.g. `ports/1/subsystems`). Meanwhile, `configfs_symlink` is attempting to create a symbolic link, and it *drops* its inode lock and then reacquires it. My initial thought was that this lock drop would create a window. But the locks involved are on different directories entirely, and `detach_groups` is being performed on the child. So, no race, right?
But *hang on*. `configfs_rmdir` works on `ports`, the parent of `ports/1`. However, `configfs_detach_group` works on `ports/1`, and specifically, *its* child, the directory `ports/1/subsystems`. And the `dir` lock that `configfs_symlink` *drops* is indeed the `ports/1/subsystems` directory. So it seemed like `detach_groups` might block waiting for `configfs_symlink` to release the lock, but no, it *releases* the lock!
So, the potential sequence is: `configfs_rmdir` gets called on `ports/1`. The subsystem then starts creating a symlink in `ports/1/subsystems`. `configfs_rmdir` calls `detach_groups` which *can* get the lock on `ports/1/subsystems`. Then it proceeds, calls `configfs_detach_group`, and `configfs_detach_group` calls `unlink_group`. Now, `unlink_group` *should* set the parent of the item to `NULL`, which means *ports/1* is no longer a parent.
Okay, so `configfs_rmdir` calls `unlink_group`. Then, after that's done, `configfs_symlink` can re-acquire the lock on `ports/1/subsystems`. So far, so good, because the parent should have been set to `NULL`.
Here's the problem. If `unlink_group` sets `item->ci_parent = NULL`, then how can `parent->ci_parent` still point to the *freed* `ports/1`? Because `ports/1` is *gone*. I have to figure out why this is, or it's a memory corruption bug.
Let's look at `unlink_group` again, specifically the context where it's called. Hmm, `configfs_rmdir` calls `unlink_group` *under* the `subsys->su_mutex`. And `configfs_symlink` does *not* hold this mutex. So `unlink_group` *can* run.
Okay, but if `unlink_group` sets `item->ci_parent = NULL`, why isn't `parent->ci_parent` also `NULL`? I need to check `unlink_group` *again*. This doesn't make sense. I've got a dangling pointer *somewhere*. Time to go deeper.
|
| 63/2 |
2026/09/02 20:59 |
tool |
read-file |
0m
Args: map[File:fs/configfs/dir.c FirstLine:805 LineCount:20]
Results: map[Contents: 805: static void unlink_group(struct config_group *group)
806: {
807: struct config_group *new_group;
808:
809: list_for_each_entry(new_group, &group->default_groups, group_entry)
810: unlink_group(new_group);
811:
812: group->cg_subsys = NULL;
813: unlink_obj(&group->cg_item);
814: }
815:
816: static void link_group(struct config_group *parent_group, struct config_group *group)
817: {
818: struct config_group *new_group;
819: struct configfs_subsystem *subsys = NULL; /* gcc is a turd */
820:
821: link_obj(&parent_group->cg_item, &group->cg_item);
822:
823: if (parent_group->cg_subsys)
824: subsys = parent_group->cg_subsys;
]
|
| 64/2 |
2026/09/02 20:59 |
llm |
debugger |
0m
Model:
Tokens:
input: 47514
output: 36
thoughts: 88
Thoughts: **Analyzing the nvmet Codebase**
Okay, so I'm diving back into this `nvmet` code. I'm focusing on the group management logic, specifically the part dealing with default groups. I've noticed that `unlink_group` seems to be iterating through `group->default_groups`. Now, a crucial piece of the puzzle is where these default groups are being populated. I remember seeing that `subsys_group` is added to `port->group.default_groups` during `nvmet_ports_make`, specifically through the call `configfs_add_default_group(&port->subsys_group, &port->group);`.
Given what I know of this code, the sequence seems to be: ports are created via `nvmet_ports_make`, the port creates a subsystem group, then that `subsys_group` is added as a default group to the overall `port->group`. But, I need to understand that `configfs_add_default_group` function completely. I need to make sure I am accounting for the full code.
|
| 65/2 |
2026/09/02 20:59 |
tool |
read-file |
0m
Args: map[File:fs/configfs/item.c FirstLine:160 LineCount:20]
Results: map[Contents: 160: INIT_LIST_HEAD(&group->cg_children);
161: INIT_LIST_HEAD(&group->default_groups);
162: }
163: EXPORT_SYMBOL(config_group_init);
164:
165: /**
166: * config_group_find_item - search for item in group.
167: * @group: group we're looking in.
168: * @name: item's name.
169: *
170: * Iterate over @group->cg_list, looking for a matching config_item.
171: * If matching item is found take a reference and return the item.
172: * Caller must have locked group via @group->cg_subsys->su_mtx.
173: */
174: struct config_item *config_group_find_item(struct config_group *group,
175: const char *name)
176: {
177: struct list_head *entry;
178: struct config_item *ret = NULL;
179:
]
|
| 66/2 |
2026/09/02 20:59 |
llm |
debugger |
0m
Model:
Tokens:
input: 48198
output: 22
thoughts: 15
Thoughts: Here's a summary of my thought process, as requested:
**Investigating `configfs_add_default_group`**
Okay, let's dive into this. The function name `configfs_add_default_group` immediately suggests we're dealing with ConfigFS, which is always an interesting area to explore. Given my expertise, I suspect this has to do with setting up a default group for newly created configuration objects or attributes within ConfigFS. My initial hunch is that it's likely called during the initialization or setup phase of a ConfigFS filesystem or its individual nodes, rather than during runtime operations. I want to understand: what context calls this, what it does precisely, and if there are any potential race conditions or security implications inherent in the way it handles default group settings. It will be helpful to trace the code path for a typical scenario. I'm going to start by searching for it to see where it's defined and used. I'm also going to check if there are any related functions with similar names or related functionalities.
|
| 67/2 |
2026/09/02 21:00 |
tool |
grepper |
0m
Args: map[Expression:configfs_add_default_group]
Results: map[Output:Documentation/filesystems/configfs.rst=449=A configfs subsystem specifies default groups by adding them using the
Documentation/filesystems/configfs.rst:450:configfs_add_default_group() function to the parent config_group
Documentation/filesystems/configfs.rst-451-structure. Each added group is populated in the configfs tree at the same
--
drivers/block/null_blk/main.c=667=static void nullb_add_fault_config(struct nullb_device *dev)
--
drivers/block/null_blk/main.c-672-
drivers/block/null_blk/main.c:673: configfs_add_default_group(&dev->timeout_config.group, &dev->group);
drivers/block/null_blk/main.c:674: configfs_add_default_group(&dev->requeue_config.group, &dev->group);
drivers/block/null_blk/main.c:675: configfs_add_default_group(&dev->init_hctx_fault_config.group, &dev->group);
drivers/block/null_blk/main.c-676-}
--
drivers/gpu/drm/vkms/vkms_configfs.c=358=static struct config_group *make_plane_group(struct config_group *group,
--
drivers/gpu/drm/vkms/vkms_configfs.c-388- &plane_possible_crtcs_group_type);
drivers/gpu/drm/vkms/vkms_configfs.c:389: configfs_add_default_group(&plane->possible_crtcs_group,
drivers/gpu/drm/vkms/vkms_configfs.c-390- &plane->group);
--
drivers/gpu/drm/vkms/vkms_configfs.c=474=static struct config_group *make_encoder_group(struct config_group *group,
--
drivers/gpu/drm/vkms/vkms_configfs.c-505- &encoder_possible_crtcs_group_type);
drivers/gpu/drm/vkms/vkms_configfs.c:506: configfs_add_default_group(&encoder->possible_crtcs_group,
drivers/gpu/drm/vkms/vkms_configfs.c-507- &encoder->group);
--
drivers/gpu/drm/vkms/vkms_configfs.c=641=static struct config_group *make_connector_group(struct config_group *group,
--
drivers/gpu/drm/vkms/vkms_configfs.c-672- &connector_possible_encoders_group_type);
drivers/gpu/drm/vkms/vkms_configfs.c:673: configfs_add_default_group(&connector->possible_encoders_group,
drivers/gpu/drm/vkms/vkms_configfs.c-674- &connector->group);
--
drivers/gpu/drm/vkms/vkms_configfs.c=763=static struct config_group *make_device_group(struct config_group *group,
--
drivers/gpu/drm/vkms/vkms_configfs.c-787- &plane_group_type);
drivers/gpu/drm/vkms/vkms_configfs.c:788: configfs_add_default_group(&dev->planes_group, &dev->group);
drivers/gpu/drm/vkms/vkms_configfs.c-789-
--
drivers/gpu/drm/vkms/vkms_configfs.c-791- &crtc_group_type);
drivers/gpu/drm/vkms/vkms_configfs.c:792: configfs_add_default_group(&dev->crtcs_group, &dev->group);
drivers/gpu/drm/vkms/vkms_configfs.c-793-
--
drivers/gpu/drm/vkms/vkms_configfs.c-795- &encoder_group_type);
drivers/gpu/drm/vkms/vkms_configfs.c:796: configfs_add_default_group(&dev->encoders_group, &dev->group);
drivers/gpu/drm/vkms/vkms_configfs.c-797-
--
drivers/gpu/drm/vkms/vkms_configfs.c-799- &connector_group_type);
drivers/gpu/drm/vkms/vkms_configfs.c:800: configfs_add_default_group(&dev->connectors_group, &dev->group);
drivers/gpu/drm/vkms/vkms_configfs.c-801-
--
drivers/gpu/drm/xe/xe_configfs.c=1025=static struct config_group *xe_config_make_device_group(struct config_group *group,
--
drivers/gpu/drm/xe/xe_configfs.c-1091- config_group_init_type_name(&dev->sriov, "sriov", &xe_config_sriov_type);
drivers/gpu/drm/xe/xe_configfs.c:1092: configfs_add_default_group(&dev->sriov, &dev->group);
drivers/gpu/drm/xe/xe_configfs.c-1093- }
--
drivers/hwtracing/coresight/coresight-syscfg-configfs.c=178=static int cscfg_add_preset_groups(struct cscfg_fs_config *cfg_view)
--
drivers/hwtracing/coresight/coresight-syscfg-configfs.c-199- &cscfg_config_preset_type);
drivers/hwtracing/coresight/coresight-syscfg-configfs.c:200: configfs_add_default_group(&cfg_fs_preset->group, &cfg_view->group);
drivers/hwtracing/coresight/coresight-syscfg-configfs.c-201- }
--
drivers/hwtracing/coresight/coresight-syscfg-configfs.c=322=static int cscfg_create_params_group_items(struct cscfg_feature_desc *feat_desc,
--
drivers/hwtracing/coresight/coresight-syscfg-configfs.c-338- &cscfg_param_view_type);
drivers/hwtracing/coresight/coresight-syscfg-configfs.c:339: configfs_add_default_group(¶m_item->group, params_group);
drivers/hwtracing/coresight/coresight-syscfg-configfs.c-340- }
--
drivers/hwtracing/coresight/coresight-syscfg-configfs.c=344=static struct config_group *cscfg_create_feature_group(struct cscfg_feature_desc *feat_desc)
--
drivers/hwtracing/coresight/coresight-syscfg-configfs.c-374- config_group_init_type_name(params_group, "params", params_group_type);
drivers/hwtracing/coresight/coresight-syscfg-configfs.c:375: configfs_add_default_group(params_group, &feat_view->group);
drivers/hwtracing/coresight/coresight-syscfg-configfs.c-376- item_err = cscfg_create_params_group_items(feat_desc, params_group);
--
drivers/hwtracing/coresight/coresight-syscfg-configfs.c=451=int cscfg_configfs_init(struct cscfg_manager *cscfg_mgr)
--
drivers/hwtracing/coresight/coresight-syscfg-configfs.c-471- config_group_init(&cscfg_configs_grp);
drivers/hwtracing/coresight/coresight-syscfg-configfs.c:472: configfs_add_default_group(&cscfg_configs_grp, &subsys->su_group);
drivers/hwtracing/coresight/coresight-syscfg-configfs.c-473-
drivers/hwtracing/coresight/coresight-syscfg-configfs.c-474- config_group_init(&cscfg_features_grp);
drivers/hwtracing/coresight/coresight-syscfg-configfs.c:475: configfs_add_default_group(&cscfg_features_grp, &subsys->su_group);
drivers/hwtracing/coresight/coresight-syscfg-configfs.c-476-
--
drivers/infiniband/core/cma_configfs.c=203=static int make_cma_ports(struct cma_dev_group *cma_dev_group,
--
drivers/infiniband/core/cma_configfs.c-230- &cma_port_group_type);
drivers/infiniband/core/cma_configfs.c:231: configfs_add_default_group(&ports[i].group,
drivers/infiniband/core/cma_configfs.c-232- &cma_dev_group->ports_group);
--
drivers/infiniband/core/cma_configfs.c=280=static struct config_group *make_cma_dev(struct config_group *group,
--
drivers/infiniband/core/cma_configfs.c-308- &cma_device_group_type);
drivers/infiniband/core/cma_configfs.c:309: configfs_add_default_group(&cma_dev_group->ports_group,
drivers/infiniband/core/cma_configfs.c-310- &cma_dev_group->device_group);
--
drivers/net/netconsole.c=1712=static void init_target_config_group(struct netconsole_target *nt,
--
drivers/net/netconsole.c-1717- &userdata_type);
drivers/net/netconsole.c:1718: configfs_add_default_group(&nt->userdata_group, &nt->group);
drivers/net/netconsole.c-1719-}
--
drivers/nvme/target/configfs.c=1040=static void nvmet_add_passthru_group(struct nvmet_subsys *subsys)
--
drivers/nvme/target/configfs.c-1043- "passthru", &nvmet_passthru_type);
drivers/nvme/target/configfs.c:1044: configfs_add_default_group(&subsys->passthru_group,
drivers/nvme/target/configfs.c-1045- &subsys->group);
--
drivers/nvme/target/configfs.c=1736=static struct config_group *nvmet_subsys_make(struct config_group *group,
--
drivers/nvme/target/configfs.c-1758- "namespaces", &nvmet_namespaces_type);
drivers/nvme/target/configfs.c:1759: configfs_add_default_group(&subsys->namespaces_group, &subsys->group);
drivers/nvme/target/configfs.c-1760-
--
drivers/nvme/target/configfs.c-1762- "allowed_hosts", &nvmet_allowed_hosts_type);
drivers/nvme/target/configfs.c:1763: configfs_add_default_group(&subsys->allowed_hosts_group,
drivers/nvme/target/configfs.c-1764- &subsys->group);
--
drivers/nvme/target/configfs.c=2041=static struct config_group *nvmet_ports_make(struct config_group *group,
--
drivers/nvme/target/configfs.c-2086- "subsystems", &nvmet_port_subsys_type);
drivers/nvme/target/configfs.c:2087: configfs_add_default_group(&port->subsys_group, &port->group);
drivers/nvme/target/configfs.c-2088-
--
drivers/nvme/target/configfs.c-2090- "referrals", &nvmet_referrals_type);
drivers/nvme/target/configfs.c:2091: configfs_add_default_group(&port->referrals_group, &port->group);
drivers/nvme/target/configfs.c-2092-
--
drivers/nvme/target/configfs.c-2094- "ana_groups", &nvmet_ana_groups_type);
drivers/nvme/target/configfs.c:2095: configfs_add_default_group(&port->ana_groups_group, &port->group);
drivers/nvme/target/configfs.c-2096-
--
drivers/nvme/target/configfs.c-2101- &nvmet_ana_group_type);
drivers/nvme/target/configfs.c:2102: configfs_add_default_group(&port->ana_default_group.group,
drivers/nvme/target/configfs.c-2103- &port->ana_groups_group);
--
drivers/nvme/target/configfs.c=2365=int __init nvmet_init_configfs(void)
--
drivers/nvme/target/configfs.c-2373- "subsystems", &nvmet_subsystems_type);
drivers/nvme/target/configfs.c:2374: configfs_add_default_group(&nvmet_subsystems_group,
drivers/nvme/target/configfs.c-2375- &nvmet_configfs_subsystem.su_group);
--
drivers/nvme/target/configfs.c-2378- "ports", &nvmet_ports_type);
drivers/nvme/target/configfs.c:2379: configfs_add_default_group(&nvmet_ports_group,
drivers/nvme/target/configfs.c-2380- &nvmet_configfs_subsystem.su_group);
--
drivers/nvme/target/configfs.c-2383- "hosts", &nvmet_hosts_type);
drivers/nvme/target/configfs.c:2384: configfs_add_default_group(&nvmet_hosts_group,
drivers/nvme/target/configfs.c-2385- &nvmet_configfs_subsystem.su_group);
--
drivers/pci/endpoint/pci-ep-cfs.c=97=static struct config_group
--
drivers/pci/endpoint/pci-ep-cfs.c-104- &pci_secondary_epc_type);
drivers/pci/endpoint/pci-ep-cfs.c:105: configfs_add_default_group(secondary_epc_group, &epf_group->group);
drivers/pci/endpoint/pci-ep-cfs.c-106-
--
drivers/pci/endpoint/pci-ep-cfs.c=161=static struct config_group
--
drivers/pci/endpoint/pci-ep-cfs.c-167- &pci_primary_epc_type);
drivers/pci/endpoint/pci-ep-cfs.c:168: configfs_add_default_group(primary_epc_group, &epf_group->group);
drivers/pci/endpoint/pci-ep-cfs.c-169-
--
drivers/pci/endpoint/pci-ep-cfs.c=558=static void pci_ep_cfs_add_type_group(struct pci_epf_group *epf_group)
--
drivers/pci/endpoint/pci-ep-cfs.c-572-
drivers/pci/endpoint/pci-ep-cfs.c:573: configfs_add_default_group(group, &epf_group->group);
drivers/pci/endpoint/pci-ep-cfs.c-574-}
--
drivers/target/iscsi/iscsi_target_configfs.c=725=static int lio_target_init_nodeacl(struct se_node_acl *se_nacl,
--
drivers/target/iscsi/iscsi_target_configfs.c-731- "iscsi_sess_stats", &iscsi_stat_sess_cit);
drivers/target/iscsi/iscsi_target_configfs.c:732: configfs_add_default_group(&acl->node_stat_grps.iscsi_sess_stats_group,
drivers/target/iscsi/iscsi_target_configfs.c-733- &se_nacl->acl_fabric_stat_group);
--
drivers/target/iscsi/iscsi_target_configfs.c=1202=static void lio_target_add_wwn_groups(struct se_wwn *wwn)
--
drivers/target/iscsi/iscsi_target_configfs.c-1207- "iscsi_instance", &iscsi_stat_instance_cit);
drivers/target/iscsi/iscsi_target_configfs.c:1208: configfs_add_default_group(&tiqn->tiqn_stat_grps.iscsi_instance_group,
drivers/target/iscsi/iscsi_target_configfs.c-1209- &tiqn->tiqn_wwn.fabric_stat_group);
--
drivers/target/iscsi/iscsi_target_configfs.c-1212- "iscsi_sess_err", &iscsi_stat_sess_err_cit);
drivers/target/iscsi/iscsi_target_configfs.c:1213: configfs_add_default_group(&tiqn->tiqn_stat_grps.iscsi_sess_err_group,
drivers/target/iscsi/iscsi_target_configfs.c-1214- &tiqn->tiqn_wwn.fabric_stat_group);
--
drivers/target/iscsi/iscsi_target_configfs.c-1217- "iscsi_tgt_attr", &iscsi_stat_tgt_attr_cit);
drivers/target/iscsi/iscsi_target_configfs.c:1218: configfs_add_default_group(&tiqn->tiqn_stat_grps.iscsi_tgt_attr_group,
drivers/target/iscsi/iscsi_target_configfs.c-1219- &tiqn->tiqn_wwn.fabric_stat_group);
--
drivers/target/iscsi/iscsi_target_configfs.c-1222- "iscsi_login_stats", &iscsi_stat_login_cit);
drivers/target/iscsi/iscsi_target_configfs.c:1223: configfs_add_default_group(&tiqn->tiqn_stat_grps.iscsi_login_stats_group,
drivers/target/iscsi/iscsi_target_configfs.c-1224- &tiqn->tiqn_wwn.fabric_stat_group);
--
drivers/target/iscsi/iscsi_target_configfs.c-1227- "iscsi_logout_stats", &iscsi_stat_logout_cit);
drivers/target/iscsi/iscsi_target_configfs.c:1228: configfs_add_default_group(&tiqn->tiqn_stat_grps.iscsi_logout_stats_group,
drivers/target/iscsi/iscsi_target_configfs.c-1229- &tiqn->tiqn_wwn.fabric_stat_group);
--
drivers/target/target_core_configfs.c=182=static struct config_group *target_core_register_fabric(
--
drivers/target/target_core_configfs.c-256- &tf->tf_discovery_cit);
drivers/target/target_core_configfs.c:257: configfs_add_default_group(&tf->tf_disc_group, &tf->tf_group);
drivers/target/target_core_configfs.c-258-
--
drivers/target/target_core_configfs.c=3422=static struct config_group *target_core_make_subdev(
--
drivers/target/target_core_configfs.c-3444- &tb->tb_dev_action_cit);
drivers/target/target_core_configfs.c:3445: configfs_add_default_group(&dev->dev_action_group, &dev->dev_group);
drivers/target/target_core_configfs.c-3446-
--
drivers/target/target_core_configfs.c-3448- &tb->tb_dev_attrib_cit);
drivers/target/target_core_configfs.c:3449: configfs_add_default_group(&dev->dev_attrib.da_group, &dev->dev_group);
drivers/target/target_core_configfs.c-3450-
--
drivers/target/target_core_configfs.c-3452- &tb->tb_dev_pr_cit);
drivers/target/target_core_configfs.c:3453: configfs_add_default_group(&dev->dev_pr_group, &dev->dev_group);
drivers/target/target_core_configfs.c-3454-
--
drivers/target/target_core_configfs.c-3456- &tb->tb_dev_wwn_cit);
drivers/target/target_core_configfs.c:3457: configfs_add_default_group(&dev->t10_wwn.t10_wwn_group,
drivers/target/target_core_configfs.c-3458- &dev->dev_group);
--
drivers/target/target_core_configfs.c-3461- "alua", &tb->tb_dev_alua_tg_pt_gps_cit);
drivers/target/target_core_configfs.c:3462: configfs_add_default_group(&dev->t10_alua.alua_tg_pt_gps_group,
drivers/target/target_core_configfs.c-3463- &dev->dev_group);
--
drivers/target/target_core_configfs.c-3466- "statistics", &tb->tb_dev_stat_cit);
drivers/target/target_core_configfs.c:3467: configfs_add_default_group(&dev->dev_stat_grps.stat_group,
drivers/target/target_core_configfs.c-3468- &dev->dev_group);
--
drivers/target/target_core_configfs.c-3479- "default_tg_pt_gp", &target_core_alua_tg_pt_gp_cit);
drivers/target/target_core_configfs.c:3480: configfs_add_default_group(&tg_pt_gp->tg_pt_gp_group,
drivers/target/target_core_configfs.c-3481- &dev->t10_alua.alua_tg_pt_gps_group);
--
drivers/target/target_core_configfs.c=3744=static int __init target_core_init_configfs(void)
--
drivers/target/target_core_configfs.c-3765- &target_core_cit);
drivers/target/target_core_configfs.c:3766: configfs_add_default_group(&target_core_hbagroup, &subsys->su_group);
drivers/target/target_core_configfs.c-3767-
--
drivers/target/target_core_configfs.c-3771- config_group_init_type_name(&alua_group, "alua", &target_core_alua_cit);
drivers/target/target_core_configfs.c:3772: configfs_add_default_group(&alua_group, &target_core_hbagroup);
drivers/target/target_core_configfs.c-3773-
--
drivers/target/target_core_configfs.c-3779- &target_core_alua_lu_gps_cit);
drivers/target/target_core_configfs.c:3780: configfs_add_default_group(&alua_lu_gps_group, &alua_group);
drivers/target/target_core_configfs.c-3781-
--
drivers/target/target_core_configfs.c-3792- &target_core_alua_lu_gp_cit);
drivers/target/target_core_configfs.c:3793: configfs_add_default_group(&lu_gp->lu_gp_group, &alua_lu_gps_group);
drivers/target/target_core_configfs.c-3794-
--
drivers/target/target_core_fabric_configfs.c=265=static struct config_group *target_fabric_make_mappedlun(
--
drivers/target/target_core_fabric_configfs.c-312- "statistics", &tf->tf_tpg_mappedlun_stat_cit);
drivers/target/target_core_fabric_configfs.c:313: configfs_add_default_group(&lacl->ml_stat_grps.stat_group,
drivers/target/target_core_fabric_configfs.c-314- &lacl->se_lun_group);
--
drivers/target/target_core_fabric_configfs.c=373=static struct config_group *target_fabric_make_nodeacl(
--
drivers/target/target_core_fabric_configfs.c-390- &tf->tf_tpg_nacl_attrib_cit);
drivers/target/target_core_fabric_configfs.c:391: configfs_add_default_group(&se_nacl->acl_attrib_group,
drivers/target/target_core_fabric_configfs.c-392- &se_nacl->acl_group);
--
drivers/target/target_core_fabric_configfs.c-395- &tf->tf_tpg_nacl_auth_cit);
drivers/target/target_core_fabric_configfs.c:396: configfs_add_default_group(&se_nacl->acl_auth_group,
drivers/target/target_core_fabric_configfs.c-397- &se_nacl->acl_group);
--
drivers/target/target_core_fabric_configfs.c-400- &tf->tf_tpg_nacl_param_cit);
drivers/target/target_core_fabric_configfs.c:401: configfs_add_default_group(&se_nacl->acl_param_group,
drivers/target/target_core_fabric_configfs.c-402- &se_nacl->acl_group);
--
drivers/target/target_core_fabric_configfs.c-405- "fabric_statistics", &tf->tf_tpg_nacl_stat_cit);
drivers/target/target_core_fabric_configfs.c:406: configfs_add_default_group(&se_nacl->acl_fabric_stat_group,
drivers/target/target_core_fabric_configfs.c-407- &se_nacl->acl_group);
--
drivers/target/target_core_fabric_configfs.c=748=static struct config_group *target_fabric_make_lun(
--
drivers/target/target_core_fabric_configfs.c-776- "statistics", &tf->tf_tpg_port_stat_cit);
drivers/target/target_core_fabric_configfs.c:777: configfs_add_default_group(&lun->port_stat_grps.stat_group,
drivers/target/target_core_fabric_configfs.c-778- &lun->lun_group);
--
drivers/target/target_core_fabric_configfs.c=934=static struct config_group *target_fabric_make_tpg(
--
drivers/target/target_core_fabric_configfs.c-955- &tf->tf_tpg_lun_cit);
drivers/target/target_core_fabric_configfs.c:956: configfs_add_default_group(&se_tpg->tpg_lun_group,
drivers/target/target_core_fabric_configfs.c-957- &se_tpg->tpg_group);
--
drivers/target/target_core_fabric_configfs.c-960- &tf->tf_tpg_np_cit);
drivers/target/target_core_fabric_configfs.c:961: configfs_add_default_group(&se_tpg->tpg_np_group,
drivers/target/target_core_fabric_configfs.c-962- &se_tpg->tpg_group);
--
drivers/target/target_core_fabric_configfs.c-965- &tf->tf_tpg_nacl_cit);
drivers/target/target_core_fabric_configfs.c:966: configfs_add_default_group(&se_tpg->tpg_acl_group,
drivers/target/target_core_fabric_configfs.c-967- &se_tpg->tpg_group);
--
drivers/target/target_core_fabric_configfs.c-970- &tf->tf_tpg_attrib_cit);
drivers/target/target_core_fabric_configfs.c:971: configfs_add_default_group(&se_tpg->tpg_attrib_group,
drivers/target/target_core_fabric_configfs.c-972- &se_tpg->tpg_group);
--
drivers/target/target_core_fabric_configfs.c-975- &tf->tf_tpg_auth_cit);
drivers/target/target_core_fabric_configfs.c:976: configfs_add_default_group(&se_tpg->tpg_auth_group,
drivers/target/target_core_fabric_configfs.c-977- &se_tpg->tpg_group);
--
drivers/target/target_core_fabric_configfs.c-980- &tf->tf_tpg_param_cit);
drivers/target/target_core_fabric_configfs.c:981: configfs_add_default_group(&se_tpg->tpg_param_group,
drivers/target/target_core_fabric_configfs.c-982- &se_tpg->tpg_group);
--
drivers/target/target_core_fabric_configfs.c=1133=static struct config_group *target_fabric_make_wwn(
--
drivers/target/target_core_fabric_configfs.c-1156- &tf->tf_wwn_fabric_stats_cit);
drivers/target/target_core_fabric_configfs.c:1157: configfs_add_default_group(&wwn->fabric_stat_group, &wwn->wwn_group);
drivers/target/target_core_fabric_configfs.c-1158-
--
drivers/target/target_core_fabric_configfs.c-1160- &tf->tf_wwn_param_cit);
drivers/target/target_core_fabric_configfs.c:1161: configfs_add_default_group(&wwn->param_group, &wwn->wwn_group);
drivers/target/target_core_fabric_configfs.c-1162-
--
drivers/target/target_core_stat.c=396=void target_stat_setup_dev_default_groups(struct se_device *dev)
--
drivers/target/target_core_stat.c-399- "scsi_dev", &target_stat_scsi_dev_cit);
drivers/target/target_core_stat.c:400: configfs_add_default_group(&dev->dev_stat_grps.scsi_dev_group,
drivers/target/target_core_stat.c-401- &dev->dev_stat_grps.stat_group);
--
drivers/target/target_core_stat.c-404- "scsi_tgt_dev", &target_stat_scsi_tgt_dev_cit);
drivers/target/target_core_stat.c:405: configfs_add_default_group(&dev->dev_stat_grps.scsi_tgt_dev_group,
drivers/target/target_core_stat.c-406- &dev->dev_stat_grps.stat_group);
--
drivers/target/target_core_stat.c-409- "scsi_lu", &target_stat_scsi_lu_cit);
drivers/target/target_core_stat.c:410: configfs_add_default_group(&dev->dev_stat_grps.scsi_lu_group,
drivers/target/target_core_stat.c-411- &dev->dev_stat_grps.stat_group);
--
drivers/target/target_core_stat.c=803=void target_stat_setup_port_default_groups(struct se_lun *lun)
--
drivers/target/target_core_stat.c-806- "scsi_port", &target_stat_scsi_port_cit);
drivers/target/target_core_stat.c:807: configfs_add_default_group(&lun->port_stat_grps.scsi_port_group,
drivers/target/target_core_stat.c-808- &lun->port_stat_grps.stat_group);
--
drivers/target/target_core_stat.c-811- "scsi_tgt_port", &target_stat_scsi_tgt_port_cit);
drivers/target/target_core_stat.c:812: configfs_add_default_group(&lun->port_stat_grps.scsi_tgt_port_group,
drivers/target/target_core_stat.c-813- &lun->port_stat_grps.stat_group);
--
drivers/target/target_core_stat.c-816- "scsi_transport", &target_stat_scsi_transport_cit);
drivers/target/target_core_stat.c:817: configfs_add_default_group(&lun->port_stat_grps.scsi_transport_group,
drivers/target/target_core_stat.c-818- &lun->port_stat_grps.stat_group);
--
drivers/target/target_core_stat.c=1301=void target_stat_setup_mappedlun_default_groups(struct se_lun_acl *lacl)
--
drivers/target/target_core_stat.c-1304- "scsi_auth_intr", &target_stat_scsi_auth_intr_cit);
drivers/target/target_core_stat.c:1305: configfs_add_default_group(&lacl->ml_stat_grps.scsi_auth_intr_group,
drivers/target/target_core_stat.c-1306- &lacl->ml_stat_grps.stat_group);
--
drivers/target/target_core_stat.c-1309- "scsi_att_intr_port", &target_stat_scsi_att_intr_port_cit);
drivers/target/target_core_stat.c:1310: configfs_add_default_group(&lacl->ml_stat_grps.scsi_att_intr_port_group,
drivers/target/target_core_stat.c-1311- &lacl->ml_stat_grps.stat_group);
--
drivers/usb/gadget/configfs.c=696=static struct config_group *config_desc_make(
--
drivers/usb/gadget/configfs.c-748- &gadget_config_name_strings_type);
drivers/usb/gadget/configfs.c:749: configfs_add_default_group(&cfg->strings_group, &cfg->group);
drivers/usb/gadget/configfs.c-750-
--
drivers/usb/gadget/configfs.c=1525=struct config_group *usb_os_desc_prepare_interf_dir(
--
drivers/usb/gadget/configfs.c-1549- config_group_init_type_name(os_desc_group, "os_desc", os_desc_type);
drivers/usb/gadget/configfs.c:1550: configfs_add_default_group(os_desc_group, parent);
drivers/usb/gadget/configfs.c-1551-
--
drivers/usb/gadget/configfs.c-1563- names[n_interf]);
drivers/usb/gadget/configfs.c:1564: configfs_add_default_group(&d->group, os_desc_group);
drivers/usb/gadget/configfs.c-1565- }
--
drivers/usb/gadget/configfs.c=1988=static struct config_group *gadgets_make(
--
drivers/usb/gadget/configfs.c-2001- &functions_type);
drivers/usb/gadget/configfs.c:2002: configfs_add_default_group(&gi->functions_group, &gi->group);
drivers/usb/gadget/configfs.c-2003-
--
drivers/usb/gadget/configfs.c-2005- &config_desc_type);
drivers/usb/gadget/configfs.c:2006: configfs_add_default_group(&gi->configs_group, &gi->group);
drivers/usb/gadget/configfs.c-2007-
--
drivers/usb/gadget/configfs.c-2009- &gadget_language_strings_type);
drivers/usb/gadget/configfs.c:2010: configfs_add_default_group(&gi->strings_group, &gi->group);
drivers/usb/gadget/configfs.c-2011-
--
drivers/usb/gadget/configfs.c-2013- &os_desc_type);
drivers/usb/gadget/configfs.c:2014: configfs_add_default_group(&gi->os_desc_group, &gi->group);
drivers/usb/gadget/configfs.c-2015-
--
drivers/usb/gadget/configfs.c-2017- &webusb_type);
drivers/usb/gadget/configfs.c:2018: configfs_add_default_group(&gi->webusb_group, &gi->group);
drivers/usb/gadget/configfs.c-2019-
--
drivers/usb/gadget/function/f_mass_storage.c=3493=static struct usb_function_instance *fsg_alloc_inst(void)
--
drivers/usb/gadget/function/f_mass_storage.c-3527- config_group_init_type_name(&opts->lun0.group, "lun.0", &fsg_lun_type);
drivers/usb/gadget/function/f_mass_storage.c:3528: configfs_add_default_group(&opts->lun0.group, &opts->func_inst.group);
drivers/usb/gadget/function/f_mass_storage.c-3529-
--
drivers/usb/gadget/function/f_midi2.c=2649=static struct usb_function_instance *f_midi2_alloc_inst(void)
--
drivers/usb/gadget/function/f_midi2.c-2689- &f_midi2_ep_type);
drivers/usb/gadget/function/f_midi2.c:2690: configfs_add_default_group(&ep_opts->group, &opts->func_inst.group);
drivers/usb/gadget/function/f_midi2.c-2691-
--
drivers/usb/gadget/function/f_midi2.c-2693- &f_midi2_block_type);
drivers/usb/gadget/function/f_midi2.c:2694: configfs_add_default_group(&block_opts->group, &ep_opts->group);
drivers/usb/gadget/function/f_midi2.c-2695-
--
drivers/usb/gadget/function/uvc_configfs.c=156=static int uvcg_config_create_group(struct config_group *parent,
--
drivers/usb/gadget/function/uvc_configfs.c-165- config_group_init_type_name(group, type->name, &type->type);
drivers/usb/gadget/function/uvc_configfs.c:166: configfs_add_default_group(group, parent);
drivers/usb/gadget/function/uvc_configfs.c-167-
--
drivers/usb/gadget/function/uvc_configfs.c=1432=static int uvcg_control_class_create_children(struct config_group *parent)
--
drivers/usb/gadget/function/uvc_configfs.c-1447- &uvcg_control_class_type);
drivers/usb/gadget/function/uvc_configfs.c:1448: configfs_add_default_group(&group->group, parent);
drivers/usb/gadget/function/uvc_configfs.c-1449- }
--
drivers/usb/gadget/function/uvc_configfs.c=3096=static int uvcg_color_matching_create_children(struct config_group *parent)
--
drivers/usb/gadget/function/uvc_configfs.c-3112- &uvcg_color_matching_type);
drivers/usb/gadget/function/uvc_configfs.c:3113: configfs_add_default_group(&color_match->group, parent);
drivers/usb/gadget/function/uvc_configfs.c-3114-
--
drivers/usb/gadget/function/uvc_configfs.c=3548=static int uvcg_streaming_class_create_children(struct config_group *parent)
--
drivers/usb/gadget/function/uvc_configfs.c-3563- &uvcg_streaming_class_type);
drivers/usb/gadget/function/uvc_configfs.c:3564: configfs_add_default_group(&group->group, parent);
drivers/usb/gadget/function/uvc_configfs.c-3565- }
--
fs/dlm/config.c=422=static struct config_group *make_cluster(struct config_group *g,
--
fs/dlm/config.c-442-
fs/dlm/config.c:443: configfs_add_default_group(&sps->ss_group, &cl->group);
fs/dlm/config.c:444: configfs_add_default_group(&cms->cs_group, &cl->group);
fs/dlm/config.c-445-
--
fs/dlm/config.c=478=static struct config_group *make_space(struct config_group *g, const char *name)
--
fs/dlm/config.c-491- config_group_init_type_name(&nds->ns_group, "nodes", &nodes_type);
fs/dlm/config.c:492: configfs_add_default_group(&nds->ns_group, &sp->group);
fs/dlm/config.c-493-
--
fs/ocfs2/cluster/nodemanager.c=691=static struct config_group *o2nm_cluster_group_make_group(struct config_group *group,
--
fs/ocfs2/cluster/nodemanager.c-710- &o2nm_cluster_type);
fs/ocfs2/cluster/nodemanager.c:711: configfs_add_default_group(&ns->ns_group, &cluster->cl_group);
fs/ocfs2/cluster/nodemanager.c-712-
--
fs/ocfs2/cluster/nodemanager.c-714- &o2nm_node_group_type);
fs/ocfs2/cluster/nodemanager.c:715: configfs_add_default_group(o2hb_group, &cluster->cl_group);
fs/ocfs2/cluster/nodemanager.c-716-
--
include/linux/configfs.h=105=extern struct config_item *config_group_find_item(struct config_group *,
--
include/linux/configfs.h-108-
include/linux/configfs.h:109:static inline void configfs_add_default_group(struct config_group *new_group,
include/linux/configfs.h-110- struct config_group *group)
]
|
| 68/2 |
2026/09/02 21:00 |
llm |
debugger |
0m
Model:
Tokens:
input: 60108
output: 36
thoughts: 25
Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience:
**Diving into configfs_add_default_group**
Okay, let's pull up the header file, specifically `include/linux/configfs.h`. I need a refresher on the mechanics of `configfs_add_default_group`. I'm going to quickly skim the definition to ensure I grasp its role within the configfs framework. I'm especially interested in how it interacts with the underlying data structures, likely looking for a `configfs_group` structure declaration or usage within the function definition to understand its function signature and return values. I'm guessing it probably handles the creation and initialization of a default group within configfs, likely linking it to a parent directory or object. I'll need to check the function's parameters to confirm my assumptions and ensure I'm up to date with the recent kernel code. Knowing the specifics of this function will clarify its role in the configuration space of the system.
|
| 69/2 |
2026/09/02 21:00 |
tool |
read-file |
0m
Args: map[File:include/linux/configfs.h FirstLine:100 LineCount:20]
Results: map[Contents: 100: static inline void config_group_put(struct config_group *group)
101: {
102: config_item_put(&group->cg_item);
103: }
104:
105: extern struct config_item *config_group_find_item(struct config_group *,
106: const char *);
107:
108:
109: static inline void configfs_add_default_group(struct config_group *new_group,
110: struct config_group *group)
111: {
112: list_add_tail(&new_group->group_entry, &group->default_groups);
113: }
114:
115: struct configfs_attribute {
116: const char *ca_name;
117: struct module *ca_owner;
118: umode_t ca_mode;
119: ssize_t (*show)(struct config_item *, char *);
]
|
| 70/2 |
2026/09/02 21:00 |
llm |
debugger |
0m
Model:
Tokens:
input: 60684
output: 22
thoughts: 296
Thoughts: **My Analysis of the configfs Group Removal**
Alright, let me break this down. So, `configfs_add_default_group` straightforwardly appends to `group->default_groups`. Now, `unlink_group` seems to traverse `group->default_groups`, recursively calling `unlink_group` on each item.
Okay, so when I get a `configfs_rmdir` call on something like `ports/1`, that kicks off `unlink_group` on `ports/1`. Crucially, this then calls `unlink_group` on `subsys_group`. That's where things get interesting. `unlink_group` on `subsys_group` subsequently calls `unlink_obj` on `subsys_group`. `unlink_obj` *should* set `item->ci_parent = NULL`, and it drops the reference to `ports/1`.
Hold on... if `item->ci_parent` is null, here's the problem: in `nvmet_port_subsys_allow_link`, we have the following:
```c
struct nvmet_port *port = to_nvmet_port(parent->ci_parent);
```
Here, `parent` is actually `subsys_group.cg_item`. So `parent->ci_parent` is null. And since `to_nvmet_port` gets called on NULL, that leads to a value of `NULL - offsetof(struct nvmet_port, group)`. I need to double-check the offset of `group` within `struct nvmet_port` to be sure that I'm reading this correctly.
|
| 71/2 |
2026/09/02 21:00 |
tool |
codesearch-struct-layout |
0m
Args:
Results: map[Fields:[map[Name:entry OffsetBits:0 SizeBits:128] map[Name:disc_addr OffsetBits:128 SizeBits:8192] map[Name:group OffsetBits:8320 SizeBits:1088] map[Name:subsys_group OffsetBits:9408 SizeBits:1088] map[Name:subsystems OffsetBits:10496 SizeBits:128] map[Name:referrals_group OffsetBits:10624 SizeBits:1088] map[Name:referrals OffsetBits:11712 SizeBits:128] map[Name:global_entry OffsetBits:11840 SizeBits:128] map[Name:ana_groups_group OffsetBits:11968 SizeBits:1088] map[Name:ana_default_group OffsetBits:13056 SizeBits:1216] map[Name:keyring OffsetBits:14272 SizeBits:64] map[Name:priv OffsetBits:14336 SizeBits:64] map[Name:enabled OffsetBits:14400 SizeBits:8] map[Name:inline_data_size OffsetBits:14432 SizeBits:32] map[Name:max_queue_size OffsetBits:14464 SizeBits:32] map[Name:mdts OffsetBits:14496 SizeBits:32] map[Name:tr_ops OffsetBits:14528 SizeBits:64] map[Name:pi_enable OffsetBits:14592 SizeBits:8] map[Name:ana_state OffsetBits:14624 SizeBits:0]]]
|
| 72/2 |
2026/09/02 21:00 |
llm |
debugger |
0m
Model:
Tokens:
input: 61813
output: 21
thoughts: 385
Thoughts: **My Analysis of the NVMe Target Crash**
Okay, let me break this down. It seems we've got a tricky NULL pointer dereference situation that's manifesting as a KASAN slab-use-after-free error. Let's trace it through.
First, I see the offset of `group` is 8320 bits, which is 1040 bytes. Then, `to_nvmet_port(NULL)` results in `0 - 1040 = -1040`. Right, a negative offset, which will be important. Then there's an `if` statement, if `parent->ci_parent` is `NULL`, then `to_nvmet_port(NULL)` is -1040. So far so good.
Next, it's accessing `port->subsystems`. The offset of `subsystems` is 10496 bits, or 1312 bytes. That means `&port->subsystems` evaluates to `-1040 + 1312 = 272`. Aha! That looks like a problem. Accessing address 272, which is an offset from a NULL pointer, will lead to a NULL pointer dereference. The kernel is likely trying to read from address 272. However, the crash is not a direct null pointer dereference, it's a slab-use-after-free.
The KASAN report specifies: `BUG: KASAN: slab-use-after-free in nvmet_port_subsys_allow_link+0x41/0x2e0 drivers/nvme/target/configfs.c:1059`. It's reading an 8-byte value at `ffff88811c6d44c8` by task `syz-executor266/5850`. This address is offset from a known base address. The address, `ffff88811c6d44c8`, is `ffff88811c6d4000 + 1224`, meaning it's 1224 bytes from the base. Now, 1224 bytes is inside a freed 4096-byte region, which is why we're getting this KASAN error. I will need to check what offset `ci_parent` has from `config_item`.
The puzzle pieces are starting to fit... the initial negative offset, the access to `subsystems`, and the KASAN report all point to a memory corruption issue that needs careful investigation. Let me look closer at the `config_item` structure now.
|
| 73/2 |
2026/09/02 21:00 |
tool |
codesearch-struct-layout |
0m
Args:
Results: map[Fields:[map[Name:ci_name OffsetBits:0 SizeBits:64] map[Name:ci_namebuf OffsetBits:64 SizeBits:160] map[Name:ci_kref OffsetBits:224 SizeBits:32] map[Name:ci_entry OffsetBits:256 SizeBits:128] map[Name:ci_parent OffsetBits:384 SizeBits:64] map[Name:ci_group OffsetBits:448 SizeBits:64] map[Name:ci_type OffsetBits:512 SizeBits:64] map[Name:ci_dentry OffsetBits:576 SizeBits:64]]]
|
| 74/2 |
2026/09/02 21:00 |
llm |
debugger |
0m
Model:
Tokens:
input: 62986
output: 40
thoughts: 447
Thoughts: **Debugging a Memory Access Violation**
Okay, let's break this down. First, I calculated that the `ci_parent` offset is 48 bytes. Then I realized `parent` is actually a pointer to `port->subsys_group.cg_item`. Now, the offset of `subsys_group` is 1176 bytes, and `cg_item` starts at zero. That means the `parent` pointer itself is at offset 1176 within the `nvmet_port` structure. Therefore, accessing `parent->ci_parent` should be at offset 1176 + 48 = 1224 bytes.
Aha! The crash report shows a read at exactly offset 1224 within freed memory. The report is very helpful here.
So, it's NOT a NULL dereference of `ci_parent`, as I initially suspected. It's the read of the `parent->ci_parent` itself that's causing the issue. This is crucial. Now, let's step back, `parent` is within the `nvmet_port` object. If `nvmet_port` is freed, then accessing `parent` is a problem. However, the `configfs_symlink` holds a reference to `parent` (which is `subsys_group.cg_item`). It has a non-zero refcount. The `nvmet_port` shouldn't be freed!
Wait a minute, `nvmet_port` is allocated as a single block using `kzalloc_flex`. It is freed when the `port->group.cg_item`'s refcount drops to zero! This means the structure is freed based on the refcount of a different component. Something is likely releasing the `nvmet_port` structure prematurely. I need to look closer at the `nvmet_port_release` function to figure out where that refcount is being managed.
|
| 75/2 |
2026/09/02 21:00 |
tool |
read-file |
0m
Args: map[File:drivers/nvme/target/configfs.c FirstLine:2000 LineCount:20]
Results: map[Contents:2000: /*
2001: * Ports definitions.
2002: */
2003: static void nvmet_port_release(struct config_item *item)
2004: {
2005: struct nvmet_port *port = to_nvmet_port(item);
2006:
2007: /* Let inflight controllers teardown complete */
2008: flush_workqueue(nvmet_wq);
2009: list_del(&port->global_entry);
2010:
2011: key_put(port->keyring);
2012: kfree(port);
2013: }
2014:
2015: static struct configfs_attribute *nvmet_port_attrs[] = {
2016: &nvmet_attr_addr_adrfam,
2017: &nvmet_attr_addr_treq,
2018: &nvmet_attr_addr_traddr,
2019: &nvmet_attr_addr_trsvcid,
]
|
| 76/2 |
2026/09/02 21:00 |
llm |
debugger |
0m
Model:
Tokens:
input: 64189
output: 36
thoughts: 131
Thoughts: **The Perils of Embedded Objects and Conflicting Lifetimes**
Okay, so `nvmet_port_release` definitely handles freeing up the `port` object itself, that much is clear. But here's where the problem arises: the `subsys_group` is actually *embedded* within that `port` structure! If there's still a lingering reference to `subsys_group.cg_item`, we're potentially looking at a double-free scenario because that memory is going to disappear along with the rest of the `port`. This reeks of a classic lifetime mismatch β the object's parent and default groups' lifetimes are not properly managed. The `subsys_group` is, in essence, a default group belonging to `port->group`. Now, the crucial question is: Does `configfs` have a guarantee, within its mechanics, that the parent's `release` is delayed until *all* of its default groups' `release` functions have also been invoked? We need to verify that `config_item_cleanup` will handle this for us. Let's dig into that and make sure.
|
| 77/2 |
2026/09/02 21:00 |
tool |
read-file |
0m
Args: map[File:fs/configfs/item.c FirstLine:115 LineCount:20]
Results: map[Contents: 115: EXPORT_SYMBOL(config_item_get_unless_zero);
116:
117: static void config_item_cleanup(struct config_item *item)
118: {
119: const struct config_item_type *t = item->ci_type;
120: struct config_group *s = item->ci_group;
121: struct config_item *parent = item->ci_parent;
122:
123: pr_debug("config_item %s: cleaning up\n", config_item_name(item));
124: if (item->ci_name != item->ci_namebuf)
125: kfree(item->ci_name);
126: item->ci_name = NULL;
127: if (t && t->ct_item_ops && t->ct_item_ops->release)
128: t->ct_item_ops->release(item);
129: if (s)
130: config_group_put(s);
131: if (parent)
132: config_item_put(parent);
133: }
134:
]
|
| 78/2 |
2026/09/02 21:00 |
llm |
debugger |
0m
Model:
Tokens:
input: 64932
output: 39
thoughts: 1208
Thoughts: **Understanding a Potential Use-After-Free in ConfigFS with Embedded Default Groups**
Okay, so I'm digging into this `configfs` interaction, specifically with how default groups behave when they're embedded within a parent object. The scenario is this: a cleanup process is triggered on a `ports/1` object (which is a `port->group.cg_item`). This cleanup, through `config_item_cleanup`, leads to `nvmet_port_release`, which is designed to free the `port` object itself. The concern arises with these "default groups" like `subsys_group`.
The crux of the problem seems to be `unlink_obj`, the function that's called when a configfs item (like `subsys_group`) is detached. This `unlink_obj` function *immediately* breaks the link between the `subsys_group` and its parent, `ports/1`. It does this by setting both `item->ci_group` and `item->ci_parent` to `NULL`, and crucially, it *immediately* calls `config_group_put(group)` to drop a reference to the `ports/1`.
The immediate consequence of this is that `ports/1` can be freed (via `nvmet_port_release`), if its refcount drops to 0, *even if* something else (like a symbolic link referring to the `subsys_group.cg_item`) still holds a reference to the child `subsys_group`.
In essence, `unlink_obj` is dropping the parent's reference *before* the child is actually released. This is a potential use-after-free situation, because the child (the default group) is embedded within the parent's memory space. So, the parent is freed while the child is still referencing the parent!
The fundamental flaw is that `configfs` assumes that default groups are independent, and don't take into account that they can be embedded in a parent object. The standard `configfs` practice is that the default group's `release` function frees the default group, or the parent's `release` function frees the parent object when the default group is embedded. However, it's designed so that the parent is only released when all of its embedded children have a refcount of 0. But, `configfs`'s `unlink_obj` function explicitly breaks this relationship by dropping the parent reference before the embedded child is released.
To handle this safely, when the child (default group) is initialized, it needs to take an extra reference to the parent. This reference should be dropped *only* in the child's `release` function, ensuring the parent stays alive until the child is also released. `configfs` doesn't provide a standard way to implement this safety measure.
This looks like a classic race condition or use-after-free waiting to happen. It's time to check how other subsystems are handling this problem. Let's see how `nullb_add_fault_config` is implemented.
|
| 79/2 |
2026/09/02 21:00 |
tool |
read-file |
0m
Args: map[File:drivers/block/null_blk/main.c FirstLine:660 LineCount:20]
Results: map[Contents: 660: .ct_item_ops = &nullb_device_ops,
661: .ct_attrs = nullb_device_attrs,
662: .ct_owner = THIS_MODULE,
663: };
664:
665: #ifdef CONFIG_BLK_DEV_NULL_BLK_FAULT_INJECTION
666:
667: static void nullb_add_fault_config(struct nullb_device *dev)
668: {
669: fault_config_init(&dev->timeout_config, "timeout_inject");
670: fault_config_init(&dev->requeue_config, "requeue_inject");
671: fault_config_init(&dev->init_hctx_fault_config, "init_hctx_fault_inject");
672:
673: configfs_add_default_group(&dev->timeout_config.group, &dev->group);
674: configfs_add_default_group(&dev->requeue_config.group, &dev->group);
675: configfs_add_default_group(&dev->init_hctx_fault_config.group, &dev->group);
676: }
677:
678: static void nullb_del_fault_config(struct nullb_device *dev)
679: {
]
|
| 80/2 |
2026/09/02 21:00 |
llm |
debugger |
0m
Model:
Tokens:
input: 67212
output: 39
thoughts: 12
Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience:
**Analyzing the `nullb_device_release` Function**
Alright, time to dive into this `nullb_device_release` function. My initial thought is to immediately start probing its implications. Given its naming convention, Iβm assuming itβs responsible for the proper release of resources associated with a βnull block deviceβ or some equivalent construct, likely within a storage or virtualization context. The term "release" is intentionally vague - does it deal with deallocating memory, releasing locks, signaling completion, or deregistering from a system? Understanding those specifics are key to avoiding subtle bugs like memory leaks, use-after-free conditions, or orphaned data structures. I need to understand what resources are being held by this 'null' device and how they are handled. My next question is, what are the possible implications of improper release. If I understand the functions purpose I can properly use the function in my project.
|
| 81/2 |
2026/09/02 21:01 |
tool |
read-file |
0m
Args: map[File:drivers/block/null_blk/main.c FirstLine:610 LineCount:20]
Results: map[Contents: 610: &nullb_device_attr_blocking,
611: &nullb_device_attr_blocksize,
612: &nullb_device_attr_cache_size,
613: &nullb_device_attr_completion_nsec,
614: &nullb_device_attr_discard,
615: &nullb_device_attr_fua,
616: &nullb_device_attr_home_node,
617: &nullb_device_attr_hw_queue_depth,
618: &nullb_device_attr_index,
619: &nullb_device_attr_irqmode,
620: &nullb_device_attr_max_sectors,
621: &nullb_device_attr_mbps,
622: &nullb_device_attr_memory_backed,
623: &nullb_device_attr_no_sched,
624: &nullb_device_attr_poll_queues,
625: &nullb_device_attr_power,
626: &nullb_device_attr_queue_mode,
627: &nullb_device_attr_rotational,
628: &nullb_device_attr_shared_tag_bitmap,
629: &nullb_device_attr_shared_tags,
]
|
| 82/2 |
2026/09/02 21:01 |
llm |
debugger |
0m
Model:
Tokens:
input: 67858
output: 20
thoughts: 13
Thoughts: Here's my thought process, summarized as you requested:
**Searching for `nullb_device_release`**
Okay, so I need to find something related to `nullb_device_release`. My initial approach would be to start with the obvious. I'll probably be aiming for a code search, most likely within my usual IDE or a version control system like Git. I need to understand what this function or macro is responsible for. Is it a driver function? A kernel-space utility? Is it part of a specific framework or subsystem? The name suggests some sort of resource deallocation related to a "null" or uninitialized device, but I have to verify that. I'll refine my search terms if necessary, perhaps by adding context like "kernel," "driver," or the name of the operating system if it is a specific OS. I'm expecting some pretty specific results, since the name seems relatively unique. Ideally, I'm looking for the definition, any calls to this function, and any comments associated with it. That should give me a good understanding of its purpose, usage, and any associated potential issues. After I look at the results, I can go from there.
|
| 83/2 |
2026/09/02 21:01 |
tool |
grepper |
0m
Args: map[Expression:nullb_device_release]
Results: map[Output:drivers/block/null_blk/main.c=606=static struct configfs_attribute *nullb_device_attrs[] = {
--
drivers/block/null_blk/main.c-646-
drivers/block/null_blk/main.c:647:static void nullb_device_release(struct config_item *item)
drivers/block/null_blk/main.c-648-{
--
drivers/block/null_blk/main.c=655=static const struct configfs_item_operations nullb_device_ops = {
drivers/block/null_blk/main.c:656: .release = nullb_device_release,
drivers/block/null_blk/main.c-657-};
]
|
| 84/2 |
2026/09/02 21:01 |
llm |
debugger |
0m
Model:
Tokens:
input: 68337
output: 39
thoughts: 12
Thoughts: Okay, here's my interpretation of that thought process, tailored for an expert audience, and written in the first person:
**Initial Assessment: Deconstructing `nullb_device_release`**
Alright, let's dive into this. The mental pointer is definitely on `nullb_device_release`. My immediate assumption is that we're dealing with a function or mechanism related to releasing resources associated with a null block device, or perhaps a simulated one. Knowing the context will be key, but the naming convention strongly suggests this. I'll need to immediately consider how the underlying data structures are managed, especially if this involves any locking or reference counting. The potential for resource leaks is always top of mind with device release routines, so I'll be looking for explicit zeroing and memory deallocation. It's also likely this release interacts with the kernel's block device interface, so I'll be keeping an eye out for how that's handled. Is it a clean unregistration, or a more involved sequence? The devil's always in the details with these low-level operations. Now, let's get into the specifics.
|
| 85/2 |
2026/09/02 21:01 |
tool |
read-file |
0m
Args: map[File:drivers/block/null_blk/main.c FirstLine:645 LineCount:20]
Results: map[Contents: 645: };
646:
647: static void nullb_device_release(struct config_item *item)
648: {
649: struct nullb_device *dev = to_nullb_device(item);
650:
651: null_free_device_storage(dev, false);
652: null_free_dev(dev);
653: }
654:
655: static const struct configfs_item_operations nullb_device_ops = {
656: .release = nullb_device_release,
657: };
658:
659: static const struct config_item_type nullb_device_type = {
660: .ct_item_ops = &nullb_device_ops,
661: .ct_attrs = nullb_device_attrs,
662: .ct_owner = THIS_MODULE,
663: };
664:
]
|
| 86/2 |
2026/09/02 21:01 |
llm |
debugger |
0m
Model:
Tokens:
input: 68920
output: 40
thoughts: 2049
Thoughts: **Analyzing a Kernel Race Condition in ConfigFS Symlink Handling**
Okay, so I've been digging into this crash, and it's quite a fascinating race condition. It boils down to a use-after-free issue when dealing with symlinks in `configfs` specifically related to the `nvmet` subsystem. Let me break down my thought process.
It starts with the basic idea: a symlink can free the device, causing a crash. I initially wondered about symlinks created within `timeout_inject`, but quickly realized that operation doesn't have the `allow_link` operation, which is critical. The key lies in `configfs_symlink` and its behavior. It has a specific check:
```c
ret = -EPERM;
if (!type || !type->ct_item_ops ||
!type->ct_item_ops->allow_link)
goto out_put;
```
It fails *before* releasing the `dir` lock. So the issue is when `allow_link` is present, `configfs_symlink` *does* release the `dir` lock.
```c
inode_unlock(dir);
ret = get_target(symname, &target_item, dentry->d_sb);
inode_lock(dir);
```
The race only exists if the default group has an `allow_link` operation, which is the case for `subsys_group` in `nvmet` (`nvmet_port_subsys_allow_link`). That means `configfs_symlink` will drop the lock, allowing `rmdir` to proceed.
Here's where the critical sequence unfolds: `rmdir` unlinks the `subsys_group`, which in turn drops the reference to `ports/1`. Then, `ports/1` is freed. Afterward, `configfs_symlink` reacquires the lock and calls `allow_link(parent_item, target_item)`. The `parent_item` is `&port->subsys_group.cg_item`.
Inside `allow_link`, there's a crucial call: `to_nvmet_port(parent->ci_parent)`. But `parent->ci_parent` is now `NULL` because the parent reference was set to NULL when the group was unlinked. However, the system is trying to read this value at offset 1224 of the `nvmet_port` object. Since the object is freed, this is a use-after-free.
The crash report confirms this exactly: the read at offset 1224 within the freed memory. The root cause is the `subsys_group` is embedded inside `nvmet_port`, but is not tied to the lifetime correctly.
Let me lay it out as a clear sequence:
1. A calls `symlinkat` to create a symlink in `ports/1/subsystems/`.
2. `configfs_symlink` is called.
3. `configfs_symlink` gets the `config_item` for `ports/1/subsystems/`, which is `&port->subsys_group.cg_item`. This increments the refcount of `subsys_group.cg_item`.
4. `configfs_symlink` checks if `allow_link` is present. It is.
5. `configfs_symlink` drops the `dir` inode lock.
6. B calls `rmdir` on `ports/1`.
7. `configfs_rmdir` is called.
8. `configfs_rmdir` calls `unlink_group` on `ports/1`.
9. `unlink_group` calls `unlink_group` on `subsys_group`.
10. `unlink_group` on `subsys_group` calls `unlink_obj` on `subsys_group`.
11. `unlink_obj` sets `subsys_group.cg_item.ci_parent = NULL` and calls `config_group_put` on `ports/1`.
12. The refcount of `ports/1` drops to 0, calling `nvmet_port_release`, which frees the `nvmet_port` object.
13. Thread A resumes, re-acquires the `dir` inode lock.
14. Thread A calls `allow_link(parent_item, target_item)`. `parent_item` is `&port->subsys_group.cg_item`.
15. `nvmet_port_subsys_allow_link` reads `parent->ci_parent`.
|
| 87/2 |
2026/09/02 21:01 |
tool |
read-file |
0m
Args: map[File:drivers/nvme/target/configfs.c FirstLine:1120 LineCount:20]
Results: map[Contents:1120: nvmet_disable_port(port);
1121: up_write(&nvmet_config_sem);
1122: kfree(p);
1123: }
1124:
1125: static struct configfs_item_operations nvmet_port_subsys_item_ops = {
1126: .allow_link = nvmet_port_subsys_allow_link,
1127: .drop_link = nvmet_port_subsys_drop_link,
1128: };
1129:
1130: static const struct config_item_type nvmet_port_subsys_type = {
1131: .ct_item_ops = &nvmet_port_subsys_item_ops,
1132: .ct_owner = THIS_MODULE,
1133: };
1134:
1135: static int nvmet_allowed_hosts_allow_link(struct config_item *parent,
1136: struct config_item *target)
1137: {
1138: struct nvmet_subsys *subsys = to_subsys(parent->ci_parent);
1139: struct nvmet_host *host;
]
|
| 88/2 |
2026/09/02 21:01 |
llm |
debugger |
0m
Model:
Tokens:
input: 72378
output: 17
thoughts: 573
Thoughts: **My Thought Process on NVMe-oF Port Release**
Okay, so I'm thinking about how to manage the lifecycle of an NVMe-oF port, specifically in relation to the `subsys_group` it's associated with. The current code has some potentially fragile reference counting that needs careful handling.
My initial thought was, "Hey, we can just add a `release` function to `nvmet_port_subsys_type` and drop the reference to the parent there." Sounds simple enough. But then I remembered, `nvmet_port_subsys_type` is used for `subsys_group`, which complicates things. `subsys_group` might outlive the port, and we don't want to free the port before it's released.
So, the next idea is to take a reference to `port->group.cg_item` when the `subsys_group` is initialized, and then drop it in `subsys_group`'s `release` function. That way, the `port`'s memory is managed correctly.
Wait a second, there's `config_item_cleanup` that runs already, right? It might be doing the same thing. I recall it does something with the parent:
```c
if (parent)
config_item_put(parent);
```
But, `unlink_obj` sets `item->ci_parent = NULL`. This means that `config_item_cleanup` *won't* actually drop the reference to the parent because `parent` will be `NULL`. Dang.
So, how do we keep the parent alive? I could either store a pointer to the parent in the child's private data, or use `container_of` to get the parent and call `config_group_put` on it. I prefer the latter.
Now, if I go with the `container_of` approach to get the `port` and call `config_item_put(&port->group.cg_item)` in `subsys_group`'s `release` function, I *also* need to take a reference to `port->group.cg_item` when `subsys_group` is initialized.
But hold on, where is `subsys_group` initialized? Ah, right, in `nvmet_ports_make`:
```c
config_group_init_type_name(&port->subsys_group,
"subsystems", &nvmet_port_subsys_type);
configfs_add_default_group(&port->subsys_group, &port->group);
```
So, if I add `config_item_get(&port->group.cg_item)` right here, and then do `config_item_put(&port->group.cg_item)` in `subsys_group`'s `release` function, `port` will be kept alive as long as `subsys_group` is active. That seems pretty solid.
But wait a minute⦠the `port` structure has *other* default groups, like `subsys_group`, `referrals_group`, and `ana_groups_group`. All of these are embedded in the `port` structure. If *any* of those are accessed after the `port` has been unlinked⦠it's a use-after-free situation! That's bad.
Okay, I need to make sure `referrals_group` and `ana_groups_group` don't have `allow_link` enabled, so that those groups cannot outlive their parents. Let me check the code.
|
| 89/2 |
2026/09/02 21:01 |
tool |
grepper |
0m
Args: map[Expression:allow_link]
Results: map[Output:Documentation/filesystems/configfs.rst=179=struct config_item_type
--
Documentation/filesystems/configfs.rst-185- void (*release)(struct config_item *);
Documentation/filesystems/configfs.rst:186: int (*allow_link)(struct config_item *src,
Documentation/filesystems/configfs.rst-187- struct config_item *target);
--
Documentation/filesystems/configfs.rst=407=symlink(2).
Documentation/filesystems/configfs.rst-408-
Documentation/filesystems/configfs.rst:409:A config_item may provide the ct_item_ops->allow_link() and
Documentation/filesystems/configfs.rst:410:ct_item_ops->drop_link() methods. If the ->allow_link() method exists,
Documentation/filesystems/configfs.rst-411-symlink(2) may be called with the config_item as the source of the link.
--
Documentation/filesystems/configfs.rst=413=symlink(2) attempt outside the configfs filesystem will be denied.
Documentation/filesystems/configfs.rst-414-
Documentation/filesystems/configfs.rst:415:When symlink(2) is called, the source config_item's ->allow_link()
Documentation/filesystems/configfs.rst-416-method is called with itself and a target item. If the source item
--
drivers/gpu/drm/vkms/vkms_configfs.c=238=static const struct config_item_type crtc_group_type = {
--
drivers/gpu/drm/vkms/vkms_configfs.c-242-
drivers/gpu/drm/vkms/vkms_configfs.c:243:static int plane_possible_crtcs_allow_link(struct config_item *src,
drivers/gpu/drm/vkms/vkms_configfs.c-244- struct config_item *target)
--
drivers/gpu/drm/vkms/vkms_configfs.c=279=static struct configfs_item_operations plane_possible_crtcs_item_operations = {
drivers/gpu/drm/vkms/vkms_configfs.c:280: .allow_link = plane_possible_crtcs_allow_link,
drivers/gpu/drm/vkms/vkms_configfs.c-281- .drop_link = plane_possible_crtcs_drop_link,
--
drivers/gpu/drm/vkms/vkms_configfs.c=400=static const struct config_item_type plane_group_type = {
--
drivers/gpu/drm/vkms/vkms_configfs.c-404-
drivers/gpu/drm/vkms/vkms_configfs.c:405:static int encoder_possible_crtcs_allow_link(struct config_item *src,
drivers/gpu/drm/vkms/vkms_configfs.c-406- struct config_item *target)
--
drivers/gpu/drm/vkms/vkms_configfs.c=441=static struct configfs_item_operations encoder_possible_crtcs_item_operations = {
drivers/gpu/drm/vkms/vkms_configfs.c:442: .allow_link = encoder_possible_crtcs_allow_link,
drivers/gpu/drm/vkms/vkms_configfs.c-443- .drop_link = encoder_possible_crtcs_drop_link,
--
drivers/gpu/drm/vkms/vkms_configfs.c=586=static const struct config_item_type connector_item_type = {
--
drivers/gpu/drm/vkms/vkms_configfs.c-591-
drivers/gpu/drm/vkms/vkms_configfs.c:592:static int connector_possible_encoders_allow_link(struct config_item *src,
drivers/gpu/drm/vkms/vkms_configfs.c-593- struct config_item *target)
--
drivers/gpu/drm/vkms/vkms_configfs.c=631=static struct configfs_item_operations connector_possible_encoders_item_operations = {
drivers/gpu/drm/vkms/vkms_configfs.c:632: .allow_link = connector_possible_encoders_allow_link,
drivers/gpu/drm/vkms/vkms_configfs.c-633- .drop_link = connector_possible_encoders_drop_link,
--
drivers/nvme/target/configfs.c=1050=static void nvmet_add_passthru_group(struct nvmet_subsys *subsys)
--
drivers/nvme/target/configfs.c-1055-
drivers/nvme/target/configfs.c:1056:static int nvmet_port_subsys_allow_link(struct config_item *parent,
drivers/nvme/target/configfs.c-1057- struct config_item *target)
--
drivers/nvme/target/configfs.c=1125=static struct configfs_item_operations nvmet_port_subsys_item_ops = {
drivers/nvme/target/configfs.c:1126: .allow_link = nvmet_port_subsys_allow_link,
drivers/nvme/target/configfs.c-1127- .drop_link = nvmet_port_subsys_drop_link,
--
drivers/nvme/target/configfs.c=1130=static const struct config_item_type nvmet_port_subsys_type = {
--
drivers/nvme/target/configfs.c-1134-
drivers/nvme/target/configfs.c:1135:static int nvmet_allowed_hosts_allow_link(struct config_item *parent,
drivers/nvme/target/configfs.c-1136- struct config_item *target)
--
drivers/nvme/target/configfs.c=1200=static struct configfs_item_operations nvmet_allowed_hosts_item_ops = {
drivers/nvme/target/configfs.c:1201: .allow_link = nvmet_allowed_hosts_allow_link,
drivers/nvme/target/configfs.c-1202- .drop_link = nvmet_allowed_hosts_drop_link,
--
drivers/pci/endpoint/pci-ep-cfs.c=87=static const struct configfs_item_operations pci_secondary_epc_item_ops = {
drivers/pci/endpoint/pci-ep-cfs.c:88: .allow_link = pci_secondary_epc_epf_link,
drivers/pci/endpoint/pci-ep-cfs.c-89- .drop_link = pci_secondary_epc_epf_unlink,
--
drivers/pci/endpoint/pci-ep-cfs.c=151=static const struct configfs_item_operations pci_primary_epc_item_ops = {
drivers/pci/endpoint/pci-ep-cfs.c:152: .allow_link = pci_primary_epc_epf_link,
drivers/pci/endpoint/pci-ep-cfs.c-153- .drop_link = pci_primary_epc_epf_unlink,
--
drivers/pci/endpoint/pci-ep-cfs.c=259=static const struct configfs_item_operations pci_epc_item_ops = {
drivers/pci/endpoint/pci-ep-cfs.c:260: .allow_link = pci_epc_epf_link,
drivers/pci/endpoint/pci-ep-cfs.c-261- .drop_link = pci_epc_epf_unlink,
--
drivers/pci/endpoint/pci-ep-cfs.c=510=static const struct configfs_item_operations pci_epf_ops = {
drivers/pci/endpoint/pci-ep-cfs.c:511: .allow_link = pci_epf_vepf_link,
drivers/pci/endpoint/pci-ep-cfs.c-512- .drop_link = pci_epf_vepf_unlink,
--
drivers/target/target_core_fabric_configfs.c=222=static const struct configfs_item_operations target_fabric_mappedlun_item_ops = {
drivers/target/target_core_fabric_configfs.c-223- .release = target_fabric_mappedlun_release,
drivers/target/target_core_fabric_configfs.c:224: .allow_link = target_fabric_mappedlun_link,
drivers/target/target_core_fabric_configfs.c-225- .drop_link = target_fabric_mappedlun_unlink,
--
drivers/target/target_core_fabric_configfs.c=711=static const struct configfs_item_operations target_fabric_port_item_ops = {
drivers/target/target_core_fabric_configfs.c-712- .release = target_fabric_port_release,
drivers/target/target_core_fabric_configfs.c:713: .allow_link = target_fabric_port_link,
drivers/target/target_core_fabric_configfs.c-714- .drop_link = target_fabric_port_unlink,
--
drivers/usb/gadget/configfs.c=517=static const struct configfs_item_operations gadget_config_item_ops = {
drivers/usb/gadget/configfs.c-518- .release = gadget_config_attr_release,
drivers/usb/gadget/configfs.c:519: .allow_link = config_usb_cfg_link,
drivers/usb/gadget/configfs.c-520- .drop_link = config_usb_cfg_unlink,
--
drivers/usb/gadget/configfs.c=1269=static const struct configfs_item_operations os_desc_ops = {
drivers/usb/gadget/configfs.c:1270: .allow_link = os_desc_link,
drivers/usb/gadget/configfs.c-1271- .drop_link = os_desc_unlink,
--
drivers/usb/gadget/function/uvc_configfs.c=1179=static void uvcg_extension_release(struct config_item *item)
--
drivers/usb/gadget/function/uvc_configfs.c-1185-
drivers/usb/gadget/function/uvc_configfs.c:1186:static int uvcg_extension_allow_link(struct config_item *src, struct config_item *tgt)
drivers/usb/gadget/function/uvc_configfs.c-1187-{
--
drivers/usb/gadget/function/uvc_configfs.c=1236=static const struct configfs_item_operations uvcg_extension_item_ops = {
drivers/usb/gadget/function/uvc_configfs.c-1237- .release = uvcg_extension_release,
drivers/usb/gadget/function/uvc_configfs.c:1238: .allow_link = uvcg_extension_allow_link,
drivers/usb/gadget/function/uvc_configfs.c-1239- .drop_link = uvcg_extension_drop_link,
--
drivers/usb/gadget/function/uvc_configfs.c=1324=static inline struct uvc_descriptor_header
--
drivers/usb/gadget/function/uvc_configfs.c-1339-
drivers/usb/gadget/function/uvc_configfs.c:1340:static int uvcg_control_class_allow_link(struct config_item *src,
drivers/usb/gadget/function/uvc_configfs.c-1341- struct config_item *target)
--
drivers/usb/gadget/function/uvc_configfs.c=1417=static const struct configfs_item_operations uvcg_control_class_item_ops = {
drivers/usb/gadget/function/uvc_configfs.c-1418- .release = uvcg_config_item_release,
drivers/usb/gadget/function/uvc_configfs.c:1419: .allow_link = uvcg_control_class_allow_link,
drivers/usb/gadget/function/uvc_configfs.c-1420- .drop_link = uvcg_control_class_drop_link,
--
drivers/usb/gadget/function/uvc_configfs.c=1580=uvcg_format_get_default_color_match(struct config_item *streaming)
--
drivers/usb/gadget/function/uvc_configfs.c-1601-
drivers/usb/gadget/function/uvc_configfs.c:1602:static int uvcg_format_allow_link(struct config_item *src, struct config_item *tgt)
drivers/usb/gadget/function/uvc_configfs.c-1603-{
--
drivers/usb/gadget/function/uvc_configfs.c=1667=static const struct configfs_item_operations uvcg_format_item_operations = {
drivers/usb/gadget/function/uvc_configfs.c-1668- .release = uvcg_config_item_release,
drivers/usb/gadget/function/uvc_configfs.c:1669: .allow_link = uvcg_format_allow_link,
drivers/usb/gadget/function/uvc_configfs.c-1670- .drop_link = uvcg_format_drop_link,
--
drivers/usb/gadget/function/uvc_configfs.c=1736=static void uvcg_format_set_indices(struct config_group *fmt);
drivers/usb/gadget/function/uvc_configfs.c-1737-
drivers/usb/gadget/function/uvc_configfs.c:1738:static int uvcg_streaming_header_allow_link(struct config_item *src,
drivers/usb/gadget/function/uvc_configfs.c-1739- struct config_item *target)
--
drivers/usb/gadget/function/uvc_configfs.c=1843=static const struct configfs_item_operations uvcg_streaming_header_item_ops = {
drivers/usb/gadget/function/uvc_configfs.c-1844- .release = uvcg_config_item_release,
drivers/usb/gadget/function/uvc_configfs.c:1845: .allow_link = uvcg_streaming_header_allow_link,
drivers/usb/gadget/function/uvc_configfs.c-1846- .drop_link = uvcg_streaming_header_drop_link,
--
drivers/usb/gadget/function/uvc_configfs.c=3322=static int __uvcg_fill_strm(void *priv1, void *priv2, void *priv3, int n,
--
drivers/usb/gadget/function/uvc_configfs.c-3419-
drivers/usb/gadget/function/uvc_configfs.c:3420:static int uvcg_streaming_class_allow_link(struct config_item *src,
drivers/usb/gadget/function/uvc_configfs.c-3421- struct config_item *target)
--
drivers/usb/gadget/function/uvc_configfs.c=3533=static const struct configfs_item_operations uvcg_streaming_class_item_ops = {
drivers/usb/gadget/function/uvc_configfs.c-3534- .release = uvcg_config_item_release,
drivers/usb/gadget/function/uvc_configfs.c:3535: .allow_link = uvcg_streaming_class_allow_link,
drivers/usb/gadget/function/uvc_configfs.c-3536- .drop_link = uvcg_streaming_class_drop_link,
--
drivers/usb/gadget/function/uvc_configfs.c=3635=static void uvc_func_item_release(struct config_item *item)
--
drivers/usb/gadget/function/uvc_configfs.c-3642-
drivers/usb/gadget/function/uvc_configfs.c:3643:static int uvc_func_allow_link(struct config_item *src, struct config_item *tgt)
drivers/usb/gadget/function/uvc_configfs.c-3644-{
--
drivers/usb/gadget/function/uvc_configfs.c=3701=static const struct configfs_item_operations uvc_func_item_ops = {
drivers/usb/gadget/function/uvc_configfs.c-3702- .release = uvc_func_item_release,
drivers/usb/gadget/function/uvc_configfs.c:3703: .allow_link = uvc_func_allow_link,
drivers/usb/gadget/function/uvc_configfs.c-3704- .drop_link = uvc_func_drop_link,
--
fs/configfs/symlink.c=135=int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
--
fs/configfs/symlink.c-156- if (!type || !type->ct_item_ops ||
fs/configfs/symlink.c:157: !type->ct_item_ops->allow_link)
fs/configfs/symlink.c-158- goto out_put;
--
fs/configfs/symlink.c-195- if (!ret)
fs/configfs/symlink.c:196: ret = type->ct_item_ops->allow_link(parent_item, target_item);
fs/configfs/symlink.c-197- if (!ret) {
--
include/linux/configfs.h=189=static struct configfs_bin_attribute _pfx##attr_##_name = { \
--
include/linux/configfs.h-200-/*
include/linux/configfs.h:201: * If allow_link() exists, the item can symlink(2) out to other
include/linux/configfs.h-202- * items. If the item is a group, it may support mkdir(2).
--
include/linux/configfs.h=212=struct configfs_item_operations {
include/linux/configfs.h-213- void (*release)(struct config_item *);
include/linux/configfs.h:214: int (*allow_link)(struct config_item *src, struct config_item *target);
include/linux/configfs.h-215- void (*drop_link)(struct config_item *src, struct config_item *target);
--
rust/kernel/configfs.rs=434=impl<Data> ItemOperationsVTable<Group<Data>, Data>
--
rust/kernel/configfs.rs-460- release: Some(Self::release),
rust/kernel/configfs.rs:461: allow_link: None,
rust/kernel/configfs.rs-462- drop_link: None,
--
rust/kernel/configfs.rs=470=impl<Data> ItemOperationsVTable<Subsystem<Data>, Data> {
--
rust/kernel/configfs.rs-472- release: None,
rust/kernel/configfs.rs:473: allow_link: None,
rust/kernel/configfs.rs-474- drop_link: None,
]
|
| 90/2 |
2026/09/02 21:01 |
llm |
debugger |
0m
Model:
Tokens:
input: 78875
output: 37
thoughts: 2504
Thoughts: **Analysis of `configfs` Race Conditions and Vulnerabilities**
Okay, so I've been digging into potential race conditions within the `configfs` file system, particularly when dealing with the linking of items and the removal of their parent directories. The initial issue I encountered stems from how `nvmet` uses default groups and the implications on `allow_link` functions. Specifically, if someone calls `rmdir` on a subsystem while `symlink` is called on `allowed_hosts`, it will crash due to the shared parent and memory management.
I initially looked at the `target_fabric_mappedlun_link` function, which, while interesting, uses `mkdir` to create the parent and therefore isn't vulnerable to the same problem. This is because its refcount is held by `configfs_symlink` and its memory isn't freed prematurely like the default groups are.
I then realized this isn't isolated to `nvmet`. The critical issue lies within `configfs` itself, and it has to do with how `allow_link` functions access the parent of an item and whether it's a default group embedded in its parent. If a default group is created with `configfs_add_default_group` and its parent directory is removed, the `ci_parent` pointer of that default group becomes invalid, leading to a crash. I found examples in `nullb_device`, `usb_os_desc_prepare_interf_dir`, and `uvcg_control_class_allow_link` where this vulnerability exists. The `uvcg` example is particularly revealing because it highlights how accessing a default group's grandparent via `ci_parent` can lead to a dereference of a `NULL` pointer. Although the memory is not immediately freed, because `configfs_symlink` holds a reference to the `fs` default group the `fs->ci_parent` will be `NULL`.
So, it seems that `configfs`'s behavior of setting `ci_parent = NULL` in `unlink_obj` breaks `allow_link` for ALL default groups!
Finally, I got to the heart of the problem with `configfs_symlink`. The function drops the lock on the directory to call `get_target`. This is done to perform the target lookup. After re-acquiring the lock, it checks `d_unhashed` on the *symlink* dentry. However, the directory can be removed and its dentry unhashed in the interim. The current implementation of `configfs_symlink` *doesn't* check if the *directory* is still valid after the lock is re-acquired. The lock is reacquired even if `dir` is dead, e.g., removed by `rmdir`. A quick check of the vfs shows that this is an important step. `configfs_rmdir` calls `configfs_detach_group`, and `configfs_detach_group` calls `configfs_detach_item` which removes the directory and sets `S_DEAD` on the directory inode. The critical takeaway is that `configfs_symlink` should check `IS_DEADDIR` or similar on `dir` after re-acquiring the lock to prevent the crash.
|
| 91/2 |
2026/09/02 21:02 |
tool |
read-file |
0m
Args: map[File:fs/configfs/symlink.c FirstLine:180 LineCount:20]
Results: map[Contents: 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);
]
|
| 92/2 |
2026/09/02 21:02 |
llm |
debugger |
0m
Model:
Tokens:
input: 82438
output: 18
thoughts: 46
Thoughts: **Debugging a Potential Filesystem Bug**
Okay, so I'm wrestling with this filesystem bug, and I'm wondering if a particular code modification might address it. The problem seems related to a dead directory, and I'm thinking, "If I insert the check `if (IS_DEADDIR(dir)) ret = -ENOENT;` immediately *after* the `inode_lock(dir);` call, would that effectively resolve the issue?" It seems logical, right? We're catching the dead directory condition early, preventing further operations.
Now, before I commit to anything, I need to make sure I understand `IS_DEADDIR` completely. I'll need to double-check its definition and behavior within the broader context of the filesystem's locking and error handling. That's the next step, I suppose. Gotta be sure that's the right condition to test.
|
| 93/2 |
2026/09/02 21:02 |
tool |
grepper |
0m
Args: map[Expression:IS_DEADDIR]
Results: map[Output:fs/cachefiles/namei.c=268=int cachefiles_bury_object(struct cachefiles_cache *cache,
--
fs/cachefiles/namei.c-316- return ret;
fs/cachefiles/namei.c:317: if (d_unhashed(rep) || rep->d_parent != dir || IS_DEADDIR(d_inode(rep))) {
fs/cachefiles/namei.c-318- /* the entry was probably culled when we dropped the parent dir
--
fs/crypto/policy.c=522=int fscrypt_ioctl_set_policy(struct file *filp, const void __user *arg)
--
fs/crypto/policy.c-553- ret = -ENOTDIR;
fs/crypto/policy.c:554: else if (IS_DEADDIR(inode))
fs/crypto/policy.c-555- ret = -ENOENT;
--
fs/fat/dir.c=800=static int fat_ioctl_readdir(struct inode *inode, struct file *file,
--
fs/fat/dir.c-814- ret = -ENOENT;
fs/fat/dir.c:815: if (!IS_DEADDIR(inode)) {
fs/fat/dir.c-816- ret = __fat_readdir(inode, file, &buf.ctx,
--
fs/namei.c=1790=static struct dentry *lookup_one_qstr_excl(const struct qstr *name,
--
fs/namei.c-1802- dir = base->d_inode;
fs/namei.c:1803: if (unlikely(IS_DEADDIR(dir)))
fs/namei.c-1804- return ERR_PTR(-ENOENT);
--
fs/namei.c=1893=static struct dentry *__lookup_slow(const struct qstr *name,
--
fs/namei.c-1900- /* Don't go there if it's already dead */
fs/namei.c:1901: if (unlikely(IS_DEADDIR(inode)))
fs/namei.c-1902- return ERR_PTR(-ENOENT);
--
fs/namei.c=3560=struct dentry *start_creating_dentry(struct dentry *parent,
--
fs/namei.c-3563- inode_lock_nested(parent->d_inode, I_MUTEX_PARENT);
fs/namei.c:3564: if (unlikely(IS_DEADDIR(parent->d_inode) ||
fs/namei.c-3565- child->d_parent != parent ||
--
fs/namei.c=3593=struct dentry *start_removing_dentry(struct dentry *parent,
--
fs/namei.c-3596- inode_lock_nested(parent->d_inode, I_MUTEX_PARENT);
fs/namei.c:3597: if (unlikely(IS_DEADDIR(parent->d_inode) ||
fs/namei.c-3598- child->d_parent != parent ||
--
fs/namei.c=3678=int may_delete_dentry(struct mnt_idmap *idmap, struct inode *dir,
--
fs/namei.c-3713- return -EISDIR;
fs/namei.c:3714: if (IS_DEADDIR(dir))
fs/namei.c-3715- return -ENOENT;
--
fs/namei.c=3731=int may_create_dentry(struct mnt_idmap *idmap,
--
fs/namei.c-3736- return -EEXIST;
fs/namei.c:3737: if (IS_DEADDIR(dir))
fs/namei.c-3738- return -ENOENT;
--
fs/namei.c=4431=static struct dentry *lookup_open(struct nameidata *nd, struct file *file,
--
fs/namei.c-4461-
fs/namei.c:4462: if (unlikely(IS_DEADDIR(dir_inode))) {
fs/namei.c-4463- dentry = ERR_PTR(-ENOENT);
--
fs/ocfs2/refcounttree.c=4355=static inline int ocfs2_may_create(struct inode *dir, struct dentry *child)
--
fs/ocfs2/refcounttree.c-4358- return -EEXIST;
fs/ocfs2/refcounttree.c:4359: if (IS_DEADDIR(dir))
fs/ocfs2/refcounttree.c-4360- return -ENOENT;
--
fs/overlayfs/inode.c=1119=bool ovl_lookup_trap_inode(struct super_block *sb, struct dentry *dir)
--
fs/overlayfs/inode.c-1128-
fs/overlayfs/inode.c:1129: res = IS_DEADDIR(trap) && !ovl_inode_upper(trap) &&
fs/overlayfs/inode.c-1130- !ovl_inode_lower(trap);
--
fs/readdir.c=33=int wrap_directory_iterator(struct file *file,
--
fs/readdir.c-65- ret = -ENOENT;
fs/readdir.c:66: if (!IS_DEADDIR(inode))
fs/readdir.c-67- ret = iter(file, ctx);
--
fs/readdir.c=87=int iterate_dir(struct file *file, struct dir_context *ctx)
--
fs/readdir.c-107- res = -ENOENT;
fs/readdir.c:108: if (!IS_DEADDIR(inode)) {
fs/readdir.c-109- ctx->pos = file->f_pos;
--
include/linux/fs.h=2089=extern loff_t vfs_dedupe_file_range_one(struct file *src_file, loff_t src_pos,
--
include/linux/fs.h-2153-
include/linux/fs.h:2154:#define IS_DEADDIR(inode) ((inode)->i_flags & S_DEAD)
include/linux/fs.h-2155-#define IS_NOCMTIME(inode) ((inode)->i_flags & S_NOCMTIME)
--
include/linux/fs.h=3633=static inline bool dir_relax(struct inode *inode)
--
include/linux/fs.h-3636- inode_lock(inode);
include/linux/fs.h:3637: return !IS_DEADDIR(inode);
include/linux/fs.h-3638-}
--
include/linux/fs.h=3640=static inline bool dir_relax_shared(struct inode *inode)
--
include/linux/fs.h-3643- inode_lock_shared(inode);
include/linux/fs.h:3644: return !IS_DEADDIR(inode);
include/linux/fs.h-3645-}
]
|
| 94/2 |
2026/09/02 21:02 |
llm |
debugger |
0m
Model:
Tokens:
input: 84539
output: 37
thoughts: 192
Thoughts: **Analyzing the Directory Detachment and Symlink Creation**
Okay, so `IS_DEADDIR(dir)` relies on the `S_DEAD` flag, that much is clear. And right here, I see that `configfs_detach_group` is setting `S_DEAD` on the directory's inode. That's good, that's what I expect. Let's trace this a bit.
Hang on. `configfs_detach_group` is applied to `child`, and `child` represents `/ports/1/subsystems` in this context. It sets the `S_DEAD` flag on that specific directory. Aha! I think I see a potential race condition here. When `configfs_symlink` later comes along and re-acquires the lock on `dir` - which *is* `/ports/1/subsystems` - `IS_DEADDIR(dir)` will evaluate to true! That's... problematic. I need to go back and re-examine `configfs_symlink` and make sure it handles this correctly. Need to look more deeply at that code path.
|
| 95/2 |
2026/09/02 21:02 |
tool |
read-file |
0m
Args: map[File:fs/configfs/symlink.c FirstLine:180 LineCount:20]
Results: map[Contents: 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);
]
|
| 96/2 |
2026/09/02 21:02 |
llm |
debugger |
0m
Model:
Tokens:
input: 85336
output: 36
thoughts: 817
Thoughts: **Analyzing a Potential Race Condition in configfs**
Okay, so I'm looking at this potential race condition. My initial thought was that adding `if (IS_DEADDIR(dir)) ret = -ENOENT;` after `inode_lock(dir);` in `configfs_symlink` would be a clean way to prevent `allow_link` from being called on a directory that's being removed. The idea is simple: if the directory is marked as dead, we short-circuit and return an error.
But then the question hits me: could `configfs_rmdir` have *started* the removal process but *not* yet set the `S_DEAD` flag? I need to understand the sequence of events.
Let's trace it. `configfs_rmdir` locks the parent of `ports/1`, which is `ports`. Then it calls `configfs_detach_group` on `ports/1`. That function, in turn, calls `detach_groups`, and this is where things get interesting. `detach_groups` locks the *child* of `ports/1` (let's say `ports/1/subsystems`), then calls `configfs_detach_group` on the child, sets `S_DEAD`, and unlocks the child.
So, *crucially*, `detach_groups` must acquire the lock on `ports/1/subsystems` before it can set `S_DEAD`. That's the key. Now, if `configfs_symlink` *already* holds that lock on `ports/1/subsystems`, then `detach_groups` will block until `configfs_symlink` releases it.
Here's how I see the timing: `configfs_symlink` initially holds the lock on `ports/1/subsystems`. When it drops the lock (`inode_unlock(dir);`), `detach_groups` acquires it, sets `S_DEAD`, and unlocks it. Then, `configfs_symlink` re-acquires the lock (`inode_lock(dir);`). At this point, `IS_DEADDIR(dir)` *will* be true! That means the check after `inode_lock(dir)` is indeed safe, because it will correctly detect the removed directory. It correctly avoids calling `allow_link` on a dead directory.
But hold on, what if `configfs_rmdir` is called on `ports/1/subsystems` itself? Since `subsystems` is a default group, `rmdir` on it will error out with `-EPERM` or `-ENOENT`. But `rmdir` on `ports/1` *is* allowed.
Let's lay it out:
1. `configfs_symlink` locks `dir` (which is `ports/1/subsystems`).
2. `configfs_symlink` drops the lock.
3. `configfs_rmdir` on `ports/1` calls `detach_groups`, which locks `dir`, sets `S_DEAD`, and unlocks `dir`.
4. `configfs_rmdir` calls `unlink_group` on `ports/1`, which unlinks `subsystems`, dropping the reference to `ports/1`.
5. `ports/1` is freed.
6. `configfs_symlink` re-acquires the lock.
7. `configfs_symlink` checks `IS_DEADDIR(dir)`. It's true!
8. `configfs_symlink` returns `-ENOENT` (or `-EPERM`), skipping `allow_link`.
9. `configfs_symlink` drops the reference to `parent_item` (`subsystems`).
10. `subsystems` is freed (since its refcount drops to 0).
Wait a minute... Is `subsystems` really freed? I need to make sure that's correct. `config_item_put(parent_item)` will drop the last reference to `subsystems`. But here is the critical part: `subsystems` is actually embedded inside `ports/1`! If `ports/1` was already freed in step 5, then the `config_item_put` in step 9 is going to access freed memory! I must check what happens in `config_item_put` to ensure the program doesn't crash here.
|
| 97/2 |
2026/09/02 21:02 |
tool |
read-file |
0m
Args: map[File:fs/configfs/item.c FirstLine:135 LineCount:20]
Results: map[Contents: 135: static void config_item_release(struct kref *kref)
136: {
137: config_item_cleanup(container_of(kref, struct config_item, ci_kref));
138: }
139:
140: /**
141: * config_item_put - decrement refcount for item.
142: * @item: item.
143: *
144: * Decrement the refcount, and if 0, call config_item_cleanup().
145: */
146: void config_item_put(struct config_item *item)
147: {
148: if (item)
149: kref_put(&item->ci_kref, config_item_release);
150: }
151: EXPORT_SYMBOL(config_item_put);
152:
153: /**
154: * config_group_init - initialize a group for use
]
|
| 98/2 |
2026/09/02 21:02 |
llm |
debugger |
0m
Model:
Tokens:
input: 87440
output: 29
thoughts: 966
Thoughts: **Analyzing a Use-After-Free Vulnerability in configfs**
Okay, so I've zeroed in on the problem: `config_item_put` is accessing freed memory. Specifically, the scenario is this: `item` is `subsys_group.cg_item`, which is embedded inside `ports/1`. If `ports/1` is freed, and then `config_item_put(parent_item)` is called, we've got a use-after-free situation because we are now trying to access memory that has already been released.
I initially thought that the `IS_DEADDIR(dir)` check in `allow_link` was the solution, but no, it prevents the crash from `allow_link` but *doesn't* prevent the use-after-free when we call `config_item_put(parent_item)`. Let's confirm: if `ports/1` is freed, the associated memory for `subsys_group` is freed, which means any pointer we have to it is now invalid. If `configfs_symlink` has a pointer to `parent_item` (which is `&port->subsys_group.cg_item`), then when `configfs_symlink` calls `config_item_put(parent_item)`, it dereferences `parent_item->ci_kref`. Boom, use-after-free.
The core of the issue is that `ports/1` is freed *while* `subsys_group` still has a non-zero reference count. This points to a fundamental flaw in `configfs`' design: how it handles the parent reference in `unlink_obj`, particularly when dealing with embedded default groups.
So, how do we fix this? I need to make sure the parent is *not* freed prematurely. To stop the parent from being freed, the child MUST hold a reference to the parent that is only dropped when the child is released. But `configfs` doesn't inherently understand these embedded relationships. The subsystem, like `nvmet`, does. Therefore, `nvmet` should take a reference to `ports/1` when `subsys_group` is initialized and release it when `subsys_group` is released.
Wait a second. The `nvmet_port_subsys_type` doesn't *have* a `release` function. Okay, we'll need to add one to `nvmet_port_subsys_type`, and that's where we'll drop the reference to `ports/1`. But then, I remember that `subsys_group` is actually initialized in `nvmet_ports_make`:
```c
config_group_init_type_name(&port->subsys_group,
"subsystems", &nvmet_port_subsys_type);
```
So, if we do a `config_item_get(&port->group.cg_item)` right here, then `port` will have an extra reference. Then, when `subsys_group` is released, we can call `config_item_put(&port->group.cg_item)`. Now, `port` won't be freed until `subsys_group` is released!
However, *there are multiple default groups* embedded in `port`: `subsys_group`, `referrals_group`, `ana_groups_group`, and `ana_default_group`. *Each* of them is embedded in `port`. If any of them can be accessed after `port` is unlinked, we need a reference for *each* of them! Is this the correct approach?
Wait, maybe `configfs` itself has a mechanism for this. Does `configfs_add_default_group` take a reference to the parent? Nope, it doesn't. But wait... `link_obj` takes a reference to the parent:
```c
item->ci_group = config_group_get(to_config_group(parent_item));
```
And `unlink_obj` drops it:
```c
/* Drop reference taken for ci_parent */
config_group_put(group);
```
Why is `unlink_obj` dropping it? Because it's removing the child from the parent's `cg_children` list. If the child is no longer in the list, it *shouldn't* hold a reference. But... if the child is *embedded* in the parent, the child's memory is intrinsically linked to the parent's! If `unlink_obj` drops the parent reference, the parent can be freed, and that frees the child's memory even if the child's refcount is still non-zero.
This is a known
|
| 99/2 |
2026/09/02 21:02 |
tool |
git-log |
10m
Error:
Args: map[CodeRegexp:config_item_get.*parent Count:10]
Results:
|
| 100/2 |
2026/09/02 21:12 |
llm |
debugger |
0m
Model:
Tokens:
input: 89495
output: 32
thoughts: 24
Thoughts: Here's my thought process, summarized as you requested:
**Hunting Down Potential Memory Corruption in ConfigFS**
Okay, so I'm looking into potential vulnerabilities related to `configfs`. The first thing I need to do is efficiently find relevant information. My gut feeling tells me that the combination of `configfs` and "use-after-free" or "UAF" is a red flag worth investigating. I'll initiate a search for commits in the kernel source repository that mention *both* `configfs` *and* either `use-after-free` or `UAF`. This is the most direct path to uncovering potentially exploitable bugs, given these specific keywords. I expect this initial search will give me a manageable dataset of commits to analyze, which I can then dig into for more details.
|
| 101/2 |
2026/09/02 21:13 |
tool |
git-log |
0m
Args: map[Count:10 MessageRegexps:[configfs use-after-free]]
Results: map[Output:011291b70ba4 ocfs2: o2hb: quiesce negotiate handlers and timeout work
3e326f3bf165 ocfs2: synchronize heartbeat callbacks with o2net teardown
79e2d75725c8 usb: gadget: f_tcm: synchronize delayed set_alt with teardown
67e511d2989e usb: gadget: udc: Fix use-after-free in gadget_match_driver
30c073cab97a gpio: aggregator: fix a potential use-after-free
eba2936bbe6b usb: gadget: uvc: fix NULL pointer dereference during unbind race
0d6c8144ca4d usb: gadget: f_ncm: Fix atomic context locking issue
53ad4a948a45 gpio: virtuser: fix UAF in configfs release path
56a512a9b410 usb: gadget: f_ncm: align net_device lifecycle with bind/unbind
151c0aa896c4 usb: gadget : fix use-after-free in composite_dev_cleanup()
]
|
| 102/2 |
2026/09/02 21:13 |
llm |
debugger |
0m
Model:
Tokens:
input: 90006
output: 26
thoughts: 30
Thoughts: Okay, let's dive into this commit, `53ad4a948a45 gpio: virtuser: fix UAF in configfs release path`. Right, so a User-After-Free (UAF) bug in the GPIO virtual user driver, specifically within the configfs release path. That's never good, especially in something as fundamental as GPIO. I'm immediately thinking about the potential impact; this could lead to all sorts of nasty consequences β memory corruption, denial of service, even privilege escalation if exploited correctly. The configfs connection makes me wonder about how the configuration and release processes interact, and where the error is likely to have occurred in that sequence. Let's dig in and figure out how they managed to free memory that was still in use. I'll need to understand the exact conditions and triggers that lead to the UAF. The release path is where resources are cleaned up, so the bug likely involves a timing issue or a race condition where something is being accessed after it's been deallocated. I'll need to see the code changes to pinpoint the root cause and understand the fix thoroughly.
|
| 103/2 |
2026/09/02 21:13 |
tool |
git-show |
0m
Args:
Results: map[Output:commit 53ad4a948a4586359b841d607c08fb16c5503230
Author: Yuhao Huang <nekowong743@gmail.com>
Date: Mon Jan 26 12:03:48 2026 +0800
gpio: virtuser: fix UAF in configfs release path
The gpio-virtuser configfs release path uses guard(mutex) to protect
the device structure. However, the device is freed before the guard
cleanup runs, causing mutex_unlock() to operate on freed memory.
Specifically, gpio_virtuser_device_config_group_release() destroys
the mutex and frees the device while still inside the guard(mutex)
scope. When the function returns, the guard cleanup invokes
mutex_unlock(&dev->lock), resulting in a slab use-after-free.
Limit the mutex lifetime by using a scoped_guard() only around the
activation check, so that the lock is released before mutex_destroy()
and kfree() are called.
Fixes: 91581c4b3f29 ("gpio: virtuser: new virtual testing driver for the GPIO API")
Signed-off-by: Yuhao Huang <nekowong743@gmail.com>
Link: https://lore.kernel.org/r/20260126040348.11167-1-yuhaohuang@YuhaodeMacBook-Pro.local
Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
diff --git a/drivers/gpio/gpio-virtuser.c b/drivers/gpio/gpio-virtuser.c
index 37f2ce20f1ae..098e67d70ffa 100644
--- a/drivers/gpio/gpio-virtuser.c
+++ b/drivers/gpio/gpio-virtuser.c
@@ -1682,10 +1682,10 @@ static void gpio_virtuser_device_config_group_release(struct config_item *item)
{
struct gpio_virtuser_device *dev = to_gpio_virtuser_device(item);
- guard(mutex)(&dev->lock);
-
- if (gpio_virtuser_device_is_live(dev))
- gpio_virtuser_device_deactivate(dev);
+ scoped_guard(mutex, &dev->lock) {
+ if (gpio_virtuser_device_is_live(dev))
+ gpio_virtuser_device_deactivate(dev);
+ }
mutex_destroy(&dev->lock);
ida_free(&gpio_virtuser_ida, dev->id);
]
|
| 104/2 |
2026/09/02 21:13 |
llm |
debugger |
0m
Model:
Tokens:
input: 90997
output: 27
thoughts: 26
Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience:
**Analyzing the Query**
Hmm, that initial prompt feels a bit off-base, unrelated to what I'm focused on. Okay, let's cut through that noise. My immediate thought is to pivot the search. The real area of interest, based on my understanding of the landscape, is likely to be related to `configfs_symlink` and associated potential memory vulnerabilities. I'm thinking specifically about the implications of `use-after-free` within the context of symbolic link handling within the configfs subsystem. My assumption is that the original prompt was likely looking for security-related issues.
|
| 105/2 |
2026/09/02 21:13 |
tool |
git-log |
0m
Args: map[Count:10 MessageRegexps:[configfs_symlink]]
Results: map[Output:79e2d75725c8 usb: gadget: f_tcm: synchronize delayed set_alt with teardown
0da63230d3ec PCI: endpoint: pci-epf-vntb: Remove duplicate resource teardown
323a80a1a5ac usb: gadget: uvc: Initialize frame-based format color matching descriptor
6613bc2301ba PCI: endpoint: Fix NULL pointer dereference for ->get_features()
e8d5f92b8d30 usb: gadget: function: printer: fix use-after-free in __lock_acquire
351e5d869e5a configfs: fix a deadlock in configfs_symlink()
0ff875498126 target: Add link_magic for fabric allow_link destination target_items
3c48f23adada configfs: Fix Trivial Warning in fs/configfs/symlink.c
4768e9b18dc6 [PATCH] configfs: Fix symlink() to a removing item
]
|
| 106/2 |
2026/09/02 21:13 |
llm |
debugger |
0m
Model:
Tokens:
input: 91457
output: 26
thoughts: 31
Thoughts: Okay, let's dive into this commit, `4768e9b18dc6 [PATCH] configfs: Fix symlink() to a removing item`. Right away, this patch description tells me we're dealing with a `configfs` issue, and specifically, something related to the `symlink()` function. Given my experience, the phrase "to a removing item" immediately flags a potential race condition or a dangling pointer problem. This is classic territory when dealing with concurrent operations within a filesystem. I expect we're likely looking at a scenario where a symbolic link is being created or manipulated just as the target file or directory it points to is being removed. This often leads to errors when the system tries to follow that link later, referencing invalid memory or an unavailable object. My intuition is that this fix probably involves some careful locking, reference counting, or perhaps a more robust check to ensure the target is still valid before allowing the `symlink()` operation to complete or before the link is used. I'm keen to see the details of the patch to understand the precise mechanism they've implemented to address the issue. I'll need to look at the commit to confirm, but this initial read has triggered all the usual flags around filesystem concurrency.
|
| 107/2 |
2026/09/02 21:13 |
tool |
git-show |
0m
Args:
Results: map[Output:commit 4768e9b18dc63719209c68920d4ae52dc49b6161
Author: Louis Rilling <louis.rilling@kerlabs.com>
Date: Mon Jun 23 14:16:17 2008 +0200
[PATCH] configfs: Fix symlink() to a removing item
The rule for configfs symlinks is that symlinks always point to valid
config_items, and prevent the target from being removed. However,
configfs_symlink() only checks that it can grab a reference on the target item,
without ensuring that it remains alive until the symlink is correctly attached.
This patch makes configfs_symlink() fail whenever the target is being removed,
using the CONFIGFS_USET_DROPPING flag set by configfs_detach_prep() and
protected by configfs_dirent_lock.
This patch introduces a similar (weird?) behavior as with mkdir failures making
rmdir fail: if symlink() races with rmdir() of the parent directory (or its
youngest user-created ancestor if parent is a default group) or rmdir() of the
target directory, and then fails in configfs_create(), this can make the racing
rmdir() fail despite the concerned directory having no user-created entry (resp.
no symlink pointing to it or one of its default groups) in the end.
This behavior is fixed in later patches.
Signed-off-by: Louis Rilling <louis.rilling@kerlabs.com>
Signed-off-by: Joel Becker <joel.becker@oracle.com>
Signed-off-by: Mark Fasheh <mfasheh@suse.com>
diff --git a/fs/configfs/dir.c b/fs/configfs/dir.c
index 2495f23e33f4..cb5ea44846af 100644
--- a/fs/configfs/dir.c
+++ b/fs/configfs/dir.c
@@ -370,6 +370,9 @@ static int configfs_detach_prep(struct dentry *dentry, struct mutex **wait_mutex
struct configfs_dirent *sd;
int ret;
+ /* Mark that we're trying to drop the group */
+ parent_sd->s_type |= CONFIGFS_USET_DROPPING;
+
ret = -EBUSY;
if (!list_empty(&parent_sd->s_links))
goto out;
@@ -385,8 +388,6 @@ static int configfs_detach_prep(struct dentry *dentry, struct mutex **wait_mutex
*wait_mutex = &sd->s_dentry->d_inode->i_mutex;
return -EAGAIN;
}
- /* Mark that we're trying to drop the group */
- sd->s_type |= CONFIGFS_USET_DROPPING;
/*
* Yup, recursive. If there's a problem, blame
@@ -414,12 +415,11 @@ static void configfs_detach_rollback(struct dentry *dentry)
struct configfs_dirent *parent_sd = dentry->d_fsdata;
struct configfs_dirent *sd;
- list_for_each_entry(sd, &parent_sd->s_children, s_sibling) {
- if (sd->s_type & CONFIGFS_USET_DEFAULT) {
+ parent_sd->s_type &= ~CONFIGFS_USET_DROPPING;
+
+ list_for_each_entry(sd, &parent_sd->s_children, s_sibling)
+ if (sd->s_type & CONFIGFS_USET_DEFAULT)
configfs_detach_rollback(sd->s_dentry);
- sd->s_type &= ~CONFIGFS_USET_DROPPING;
- }
- }
}
static void detach_attrs(struct config_item * item)
diff --git a/fs/configfs/symlink.c b/fs/configfs/symlink.c
index 0004d18c40ac..c12801a12c34 100644
--- a/fs/configfs/symlink.c
+++ b/fs/configfs/symlink.c
@@ -78,6 +78,12 @@ static int create_link(struct config_item *parent_item,
if (sl) {
sl->sl_target = config_item_get(item);
spin_lock(&configfs_dirent_lock);
+ if (target_sd->s_type & CONFIGFS_USET_DROPPING) {
+ spin_unlock(&configfs_dirent_lock);
+ config_item_put(item);
+ kfree(sl);
+ return -ENOENT;
+ }
list_add(&sl->sl_list, &target_sd->s_links);
spin_unlock(&configfs_dirent_lock);
ret = configfs_create_link(sl, parent_item->ci_dentry,
]
|
| 108/2 |
2026/09/02 21:13 |
llm |
debugger |
0m
Model:
Tokens:
input: 93087
output: 37
thoughts: 138
Thoughts: **Analyzing a Tricky ConfigFS Bug**
Alright, let's break this down. We've got a situation where the target is being removed β that much is clear. But our bug? It's all about the parent being removed! Hmm, the `configfs_symlink` function specifically checks if the target is being deleted, but what about the parent directory itself?
That's the key, isn't it? When `configfs_symlink` releases the lock on the parent directory (`dir`), there's a window where that parent could be removed entirely. And if the parent goes away, `configfs_rmdir` will mark its inode with `S_DEAD`. So, the solution is, after we re-acquire the lock on `dir`, we *must* check `IS_DEADDIR(dir)`! That should solve the race condition. Now, the next step is to go through `configfs_symlink` and make sure it performs this crucial check.
|
| 109/2 |
2026/09/02 21:13 |
tool |
read-file |
0m
Args: map[File:fs/configfs/symlink.c FirstLine:180 LineCount:20]
Results: map[Contents: 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);
]
|
| 110/2 |
2026/09/02 21:13 |
llm |
debugger |
0m
Model:
Tokens:
input: 93802
output: 36
thoughts: 727
Thoughts: **Root Cause Analysis of the configfs Use-After-Free Vulnerability**
Okay, so I was initially thinking that adding a check for `IS_DEADDIR(dir)` before the dentry checks would fix the potential use-after-free, but I've realized that's just a band-aid. It doesn't address the core problem, even if it might prevent the crash in `allow_link`. I was tracing the execution and I see the problem clearly now.
If `IS_DEADDIR(dir)` is true, the function returns `-ENOENT` and jumps to the `out_put` label, ultimately calling `config_item_put` on both `target_item` and `parent_item`. But here's the kicker: `parent_item` is embedded within, for example, `ports/1`. And if `ports/1` was removed by `configfs_rmdir`, then `config_item_put(parent_item)` still accesses freed memory because `parent_item` points to `&port->subsys_group.cg_item`. The `port` itself has already been freed, even though `subsys_group` is still being used! This is because `configfs_rmdir` calls `unlink_group` which drops the reference to `ports/1` - which frees it. But `configfs_symlink` *still* holds a reference to `subsys_group`, meaning `subsys_group` gets freed while its reference count is greater than zero!
This is where the vulnerability lies: `configfs` allows a parent to be freed while a child (specifically the default group) is still active with references. The child *must* maintain a reference to the parent that's only released when the child is released, and I see now how this is not happening.
Then I examined the `unlink_obj` function. It explicitly drops the parent reference! That's the problem.
```c
static void unlink_obj(struct config_item *item)
{
struct config_group *group;
group = item->ci_group;
if (group) {
list_del_init(&item->ci_entry);
item->ci_group = NULL;
item->ci_parent = NULL;
/* Drop reference taken for ci_entry */
config_item_put(item);
/* Drop reference taken for ci_parent */
config_group_put(group);
}
}
```
The parent's reference count drops to zero here. If the child is still alive, the parent's memory gets freed if the child is embedded in the parent. I can see that this is a fundamental flaw in `configfs` specifically for embedded default groups.
So, the fix? I'm convinced: `configfs` *should not* drop the parent reference in `unlink_obj` if the child is still alive. Instead, that parent reference should be dropped during `config_item_cleanup` and *not* `unlink_obj`. I'll need to check the exact `config_item_cleanup` function...
|
| 111/2 |
2026/09/02 21:14 |
tool |
read-file |
0m
Args: map[File:fs/configfs/item.c FirstLine:115 LineCount:20]
Results: map[Contents: 115: EXPORT_SYMBOL(config_item_get_unless_zero);
116:
117: static void config_item_cleanup(struct config_item *item)
118: {
119: const struct config_item_type *t = item->ci_type;
120: struct config_group *s = item->ci_group;
121: struct config_item *parent = item->ci_parent;
122:
123: pr_debug("config_item %s: cleaning up\n", config_item_name(item));
124: if (item->ci_name != item->ci_namebuf)
125: kfree(item->ci_name);
126: item->ci_name = NULL;
127: if (t && t->ct_item_ops && t->ct_item_ops->release)
128: t->ct_item_ops->release(item);
129: if (s)
130: config_group_put(s);
131: if (parent)
132: config_item_put(parent);
133: }
134:
]
|
| 112/2 |
2026/09/02 21:14 |
llm |
debugger |
0m
Model:
Tokens:
input: 95584
output: 35
thoughts: 698
Thoughts: **Understanding the Reference Counting in Configuration Item Management**
Okay, so I'm diving into this code again, trying to get a solid grasp on how the reference counting works, specifically around parent-child relationships. Initially, I thought that `config_item_cleanup` was the primary place where the parent reference was dropped, and then I realized that `unlink_obj` *also* does it, and *earlier*. This immediately raises a red flag. Why the redundancy? Why the early drop?
The code seems to indicate `unlink_obj` drops the reference early because it is called when the item is being removed from the configuration tree. My first instinct was to check for a potential reference cycle, but the parent doesn't directly hold a reference to the child; it's the other way around. However, then I remembered `link_obj`. It explicitly takes a reference on the *child* for `ci_entry` (for the linked list within the group) and stores a reference to the *parent* for `ci_group`. So, `unlink_obj` *needs* to drop that reference on the *child* for `ci_entry`! Makes sense.
But then I see that `unlink_obj` also drops the reference on the *parent* for `ci_parent`. This is where I got tripped up. The code says it drops the *parent* reference, which means that `config_item_cleanup` will *not* drop it, because `item->ci_parent` is nullified by `unlink_obj`! This led to more questions. Why does `unlink_obj` drop the parent reference?
The explanation seems to be that if it didn't, the parent would be kept alive until the child is released, and that's not what we want. This made sense. But then I have to think about default groups, which are embedded in the parent. So the parent *must* be kept alive until the child is released! With user-created groups the scenario is similar. So keeping the parent alive until the child is released is correct!
So, if `unlink_obj` *didn't* drop the parent reference, when would it be dropped? It would be dropped in `config_item_cleanup`.
I keep coming back to `link_obj`. It takes a reference on `parent_item` as a group and stores it in `ci_group`. It doesn't take a direct reference on `ci_parent`. Then there's `config_item_cleanup`, which seems to be dropping references. So, *why* does `config_item_cleanup` drop *two* references if `link_obj` only takes one?
I need to circle back to `config_item_init`. Something must be getting missed here.
|
| 113/2 |
2026/09/02 21:14 |
tool |
read-file |
0m
Args: map[File:fs/configfs/item.c FirstLine:60 LineCount:20]
Results: map[Contents: 60: need = vsnprintf(item->ci_namebuf, limit, fmt, args);
61: va_end(args);
62: if (need < limit)
63: name = item->ci_namebuf;
64: else {
65: va_start(args, fmt);
66: name = kvasprintf(GFP_KERNEL, fmt, args);
67: va_end(args);
68: if (!name)
69: return -ENOMEM;
70: }
71:
72: /* Free the old name, if necessary. */
73: if (item->ci_name && item->ci_name != item->ci_namebuf)
74: kfree(item->ci_name);
75:
76: /* Now, set the new name */
77: item->ci_name = name;
78: return 0;
79: }
]
|
| 114/2 |
2026/09/02 21:14 |
llm |
debugger |
0m
Model:
Tokens:
input: 97181
output: 19
thoughts: 11
Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience, and written as if they were my own:
**Initial Assessment of `config_item_init`**
Alright, time to dive in. Let's see what's happening with `config_item_init`. My immediate thought is, let's just make sure we're not missing anything critical in the initialization process. Are we correctly setting up all the necessary data structures? Are there any potential race conditions that could arise during this stage, especially if it involves any multi-threaded operations? What about error handling; is it robust enough to catch any unexpected states and gracefully fail, or perhaps log useful debugging information? I'm also wondering if this function's scope is appropriate. Could it be broken down further to improve readability and maintainability? Let's check the code itself and see if these initial questions are adequately addressed. I want to ensure the configuration items are set up right from the get-go; it's a foundation, so we need it solid.
|
| 115/2 |
2026/09/02 21:14 |
tool |
grepper |
0m
Args: map[Expression:config_item_init]
Results: map[Output:Documentation/filesystems/configfs.rst=138=struct config_item
--
Documentation/filesystems/configfs.rst-153-
Documentation/filesystems/configfs.rst:154: void config_item_init(struct config_item *);
Documentation/filesystems/configfs.rst:155: void config_item_init_type_name(struct config_item *,
Documentation/filesystems/configfs.rst-156- const char *name,
--
drivers/acpi/acpi_configfs.c=208=static struct config_item *acpi_table_make_item(struct config_group *group,
--
drivers/acpi/acpi_configfs.c-216-
drivers/acpi/acpi_configfs.c:217: config_item_init_type_name(&table->cfg, name, &acpi_table_type);
drivers/acpi/acpi_configfs.c-218- return &table->cfg;
--
drivers/gpio/gpio-sim.c=1399=gpio_sim_line_config_make_hog_item(struct config_group *group, const char *name)
--
drivers/gpio/gpio-sim.c-1413-
drivers/gpio/gpio-sim.c:1414: config_item_init_type_name(&hog->item, name,
drivers/gpio/gpio-sim.c-1415- &gpio_sim_hog_config_type);
--
drivers/most/configfs.c=423=static struct config_item *most_common_make_item(struct config_group *group,
--
drivers/most/configfs.c-436- }
drivers/most/configfs.c:437: config_item_init_type_name(&mdev_link->item, name,
drivers/most/configfs.c-438- &mdev_link_type);
--
drivers/most/configfs.c=524=static struct config_item *most_snd_grp_make_item(struct config_group *group,
--
drivers/most/configfs.c-532-
drivers/most/configfs.c:533: config_item_init_type_name(&mdev_link->item, name, &mdev_link_type);
drivers/most/configfs.c-534- mdev_link->create_link = false;
--
drivers/net/netconsole.c=1606=static struct config_item *userdatum_make_item(struct config_group *group,
--
drivers/net/netconsole.c-1624-
drivers/net/netconsole.c:1625: config_item_init_type_name(&udm->item, name, &userdatum_type);
drivers/net/netconsole.c-1626- return &udm->item;
--
drivers/usb/gadget/configfs.c=865=static struct config_item *gadget_language_string_make(struct config_group *group,
--
drivers/usb/gadget/configfs.c-880-
drivers/usb/gadget/configfs.c:881: config_item_init_type_name(&string->item, name, &gadget_string_type);
drivers/usb/gadget/configfs.c-882-
--
drivers/usb/gadget/configfs.c=1398=static struct config_item *ext_prop_make(
--
drivers/usb/gadget/configfs.c-1422-
drivers/usb/gadget/configfs.c:1423: config_item_init_type_name(&ext_prop->item, name, ext_prop_type);
drivers/usb/gadget/configfs.c-1424-
--
drivers/usb/gadget/function/uvc_configfs.c=268=static struct config_item *uvcg_control_header_make(struct config_group *group,
--
drivers/usb/gadget/function/uvc_configfs.c-282-
drivers/usb/gadget/function/uvc_configfs.c:283: config_item_init_type_name(&h->item, name, &uvcg_control_header_type);
drivers/usb/gadget/function/uvc_configfs.c-284-
--
drivers/usb/gadget/function/uvc_configfs.c=1267=static struct config_item *uvcg_extension_make(struct config_group *group, const char *name)
--
drivers/usb/gadget/function/uvc_configfs.c-1292-
drivers/usb/gadget/function/uvc_configfs.c:1293: config_item_init_type_name(&xu->item, name, &uvcg_extension_type);
drivers/usb/gadget/function/uvc_configfs.c-1294- list_add_tail(&xu->list, &opts->extension_units);
--
drivers/usb/gadget/function/uvc_configfs.c=1897=static struct config_item
--
drivers/usb/gadget/function/uvc_configfs.c-1911-
drivers/usb/gadget/function/uvc_configfs.c:1912: config_item_init_type_name(&h->item, name, &uvcg_streaming_header_type);
drivers/usb/gadget/function/uvc_configfs.c-1913-
--
drivers/usb/gadget/function/uvc_configfs.c=2157=static struct config_item *uvcg_frame_make(struct config_group *group,
--
drivers/usb/gadget/function/uvc_configfs.c-2212- if (fmt->type == UVCG_FRAMEBASED)
drivers/usb/gadget/function/uvc_configfs.c:2213: config_item_init_type_name(&h->item, name, &uvcg_frame_type2);
drivers/usb/gadget/function/uvc_configfs.c-2214- else
drivers/usb/gadget/function/uvc_configfs.c:2215: config_item_init_type_name(&h->item, name, &uvcg_frame_type1);
drivers/usb/gadget/function/uvc_configfs.c-2216-
--
drivers/virt/coco/guest/report.c=422=static struct config_item *tsm_report_make_item(struct config_group *group,
--
drivers/virt/coco/guest/report.c-435- atomic_inc(&provider.count);
drivers/virt/coco/guest/report.c:436: config_item_init_type_name(&state->cfg, name, &tsm_report_type);
drivers/virt/coco/guest/report.c-437- return &state->cfg;
--
fs/configfs/item.c=28=static void config_item_release(struct kref *kref);
--
fs/configfs/item.c-30-/**
fs/configfs/item.c:31: * config_item_init - initialize item.
fs/configfs/item.c-32- * @item: item in question.
fs/configfs/item.c-33- */
fs/configfs/item.c:34:static void config_item_init(struct config_item *item)
fs/configfs/item.c-35-{
--
fs/configfs/item.c=80=EXPORT_SYMBOL(config_item_set_name);
fs/configfs/item.c-81-
fs/configfs/item.c:82:void config_item_init_type_name(struct config_item *item,
fs/configfs/item.c-83- const char *name,
--
fs/configfs/item.c-87- item->ci_type = type;
fs/configfs/item.c:88: config_item_init(item);
fs/configfs/item.c-89-}
fs/configfs/item.c:90:EXPORT_SYMBOL(config_item_init_type_name);
fs/configfs/item.c-91-
--
fs/configfs/item.c=157=void config_group_init(struct config_group *group)
fs/configfs/item.c-158-{
fs/configfs/item.c:159: config_item_init(&group->cg_item);
fs/configfs/item.c-160- INIT_LIST_HEAD(&group->cg_children);
--
fs/dlm/config.c=524=static struct config_item *make_comm(struct config_group *g, const char *name)
--
fs/dlm/config.c-537-
fs/dlm/config.c:538: config_item_init_type_name(&cm->item, name, &comm_type);
fs/dlm/config.c-539-
--
fs/dlm/config.c=568=static struct config_item *make_node(struct config_group *g, const char *name)
--
fs/dlm/config.c-583-
fs/dlm/config.c:584: config_item_init_type_name(&nd->item, name, &node_type);
fs/dlm/config.c-585- nd->nodeid = nodeid;
--
fs/ocfs2/cluster/heartbeat.c=2119=static struct config_item *o2hb_heartbeat_group_make_item(struct config_group *group,
--
fs/ocfs2/cluster/heartbeat.c-2149-
fs/ocfs2/cluster/heartbeat.c:2150: config_item_init_type_name(®->hr_item, name, &o2hb_region_type);
fs/ocfs2/cluster/heartbeat.c-2151-
--
fs/ocfs2/cluster/nodemanager.c=587=static struct config_item *o2nm_node_group_make_item(struct config_group *group,
--
fs/ocfs2/cluster/nodemanager.c-599- strscpy(node->nd_name, name); /* use item.ci_namebuf instead? */
fs/ocfs2/cluster/nodemanager.c:600: config_item_init_type_name(&node->nd_item, name, &o2nm_node_type);
fs/ocfs2/cluster/nodemanager.c-601- spin_lock_init(&node->nd_lock);
--
include/linux/configfs.h=52=static inline char *config_item_name(struct config_item * item)
--
include/linux/configfs.h-56-
include/linux/configfs.h:57:extern void config_item_init_type_name(struct config_item *item,
include/linux/configfs.h-58- const char *name,
--
kernel/crash_dump_dm_crypt.c=251=static struct config_item *config_keys_make_item(struct config_group *group,
--
kernel/crash_dump_dm_crypt.c-264-
kernel/crash_dump_dm_crypt.c:265: config_item_init_type_name(&config_key->item, name, &config_key_type);
kernel/crash_dump_dm_crypt.c-266-
--
samples/configfs/configfs_sample.c=181=static struct config_item *simple_children_make_item(struct config_group *group,
--
samples/configfs/configfs_sample.c-189-
samples/configfs/configfs_sample.c:190: config_item_init_type_name(&simple_child->item, name,
samples/configfs/configfs_sample.c-191- &simple_child_type);
]
|
| 116/2 |
2026/09/02 21:14 |
llm |
debugger |
0m
Model:
Tokens:
input: 100491
output: 36
thoughts: 297
Thoughts: **My Analysis of ConfigFS Item Reference Counting**
Okay, let me break this down. First, `config_item_init` sets the initial reference count (`ci_kref`) of a `config_item` to 1. Got it. So, when the `subsys_group` is initialized, its `ci_kref` starts at 1. Then, when `link_obj` is invoked, `config_item_get(item)` is called, incrementing the count to 2. Seems logical. Later, when `unlink_obj` runs, we have `config_item_put(item)`, dropping the count back down to 1.
Here's where things get interesting. The parent item, like 'ports/1', when cleaned up calls `config_item_cleanup` which calls `nvmet_port_release`. However, the `subsys_group.cg_item` is still sitting there with a `ci_kref` of 1! That's... unusual. I need to understand where the *initial* reference of `subsys_group` is being released. Is it being released at all?
Aha! The initial reference of a *default* group is never dropped! That's the key. If it's never dropped, `config_item_cleanup` will *never* be called for default groups. This has some serious implications for the cleanup process. Time to check `configfs_remove_default_groups` and see how this is handled. This might be a subtle but critical resource leak if not handled correctly.
|
| 117/2 |
2026/09/02 21:14 |
tool |
read-file |
0m
Args: map[File:fs/configfs/dir.c FirstLine:740 LineCount:20]
Results: map[Contents: 740: list_for_each_entry(new_group, &group->default_groups, group_entry) {
741: int ret = create_default_group(parent, new_group, frag);
742: if (ret)
743: return ret;
744: }
745: return 0;
746: }
747:
748: void configfs_remove_default_groups(struct config_group *group)
749: {
750: struct config_group *g, *n;
751:
752: list_for_each_entry_safe(g, n, &group->default_groups, group_entry) {
753: list_del(&g->group_entry);
754: config_item_put(&g->cg_item);
755: }
756: }
757: EXPORT_SYMBOL(configfs_remove_default_groups);
758:
759: /*
]
|
| 118/2 |
2026/09/02 21:14 |
llm |
debugger |
0m
Model:
Tokens:
input: 101460
output: 22
thoughts: 48
Thoughts: Okay, here's my take on those thoughts, framed as my own:
**Investigating a ConfigFS Reference Count Issue**
Right, so `configfs_remove_default_groups` is dropping that initial reference. That's good to know, but... *who* is actually calling this function? I need to figure out the call chain. It's time to dig into the code and see where `configfs_remove_default_groups` is being invoked. Let's do a quick search and trace that function's usage. Gotta understand the complete context to see if this reference management is correct.
|
| 119/2 |
2026/09/02 21:14 |
tool |
grepper |
0m
Args: map[Expression:configfs_remove_default_groups]
Results: map[Output:drivers/infiniband/core/cma_configfs.c=322=static void drop_cma_dev(struct config_group *cgroup, struct config_item *item)
--
drivers/infiniband/core/cma_configfs.c-328-
drivers/infiniband/core/cma_configfs.c:329: configfs_remove_default_groups(&cma_dev_group->ports_group);
drivers/infiniband/core/cma_configfs.c:330: configfs_remove_default_groups(&cma_dev_group->device_group);
drivers/infiniband/core/cma_configfs.c-331- config_item_put(item);
--
drivers/target/target_core_configfs.c=267=static void target_core_deregister_fabric(
--
drivers/target/target_core_configfs.c-283-
drivers/target/target_core_configfs.c:284: configfs_remove_default_groups(&tf->tf_group);
drivers/target/target_core_configfs.c-285- config_item_put(item);
--
drivers/target/target_core_configfs.c=3502=static void target_core_drop_subdev(
--
drivers/target/target_core_configfs.c-3514-
drivers/target/target_core_configfs.c:3515: configfs_remove_default_groups(&dev->dev_stat_grps.stat_group);
drivers/target/target_core_configfs.c:3516: configfs_remove_default_groups(&dev->t10_alua.alua_tg_pt_gps_group);
drivers/target/target_core_configfs.c-3517-
--
drivers/target/target_core_configfs.c-3523-
drivers/target/target_core_configfs.c:3524: configfs_remove_default_groups(dev_cg);
drivers/target/target_core_configfs.c-3525-
--
drivers/target/target_core_configfs.c=3843=static void __exit target_core_exit_configfs(void)
drivers/target/target_core_configfs.c-3844-{
drivers/target/target_core_configfs.c:3845: configfs_remove_default_groups(&alua_lu_gps_group);
drivers/target/target_core_configfs.c:3846: configfs_remove_default_groups(&alua_group);
drivers/target/target_core_configfs.c:3847: configfs_remove_default_groups(&target_core_hbagroup);
drivers/target/target_core_configfs.c-3848-
--
drivers/target/target_core_fabric_configfs.c=326=static void target_fabric_drop_mappedlun(
--
drivers/target/target_core_fabric_configfs.c-332-
drivers/target/target_core_fabric_configfs.c:333: configfs_remove_default_groups(&lacl->ml_stat_grps.stat_group);
drivers/target/target_core_fabric_configfs.c:334: configfs_remove_default_groups(&lacl->se_lun_group);
drivers/target/target_core_fabric_configfs.c-335-
--
drivers/target/target_core_fabric_configfs.c=339=static void target_fabric_nacl_base_release(struct config_item *item)
--
drivers/target/target_core_fabric_configfs.c-343-
drivers/target/target_core_fabric_configfs.c:344: configfs_remove_default_groups(&se_nacl->acl_fabric_stat_group);
drivers/target/target_core_fabric_configfs.c-345- core_tpg_del_initiator_node_acl(se_nacl);
--
drivers/target/target_core_fabric_configfs.c=373=static struct config_group *target_fabric_make_nodeacl(
--
drivers/target/target_core_fabric_configfs.c-411- if (ret) {
drivers/target/target_core_fabric_configfs.c:412: configfs_remove_default_groups(&se_nacl->acl_fabric_stat_group);
drivers/target/target_core_fabric_configfs.c-413- core_tpg_del_initiator_node_acl(se_nacl);
--
drivers/target/target_core_fabric_configfs.c=421=static void target_fabric_drop_nodeacl(
--
drivers/target/target_core_fabric_configfs.c-427-
drivers/target/target_core_fabric_configfs.c:428: configfs_remove_default_groups(&se_nacl->acl_group);
drivers/target/target_core_fabric_configfs.c-429-
--
drivers/target/target_core_fabric_configfs.c=785=static void target_fabric_drop_lun(
--
drivers/target/target_core_fabric_configfs.c-791-
drivers/target/target_core_fabric_configfs.c:792: configfs_remove_default_groups(&lun->port_stat_grps.stat_group);
drivers/target/target_core_fabric_configfs.c:793: configfs_remove_default_groups(&lun->lun_group);
drivers/target/target_core_fabric_configfs.c-794-
--
drivers/target/target_core_fabric_configfs.c=987=static void target_fabric_drop_tpg(
--
drivers/target/target_core_fabric_configfs.c-993-
drivers/target/target_core_fabric_configfs.c:994: configfs_remove_default_groups(&se_tpg->tpg_group);
drivers/target/target_core_fabric_configfs.c-995- config_item_put(item);
--
drivers/target/target_core_fabric_configfs.c=998=static void target_fabric_release_wwn(struct config_item *item)
--
drivers/target/target_core_fabric_configfs.c-1003-
drivers/target/target_core_fabric_configfs.c:1004: configfs_remove_default_groups(&wwn->fabric_stat_group);
drivers/target/target_core_fabric_configfs.c:1005: configfs_remove_default_groups(&wwn->param_group);
drivers/target/target_core_fabric_configfs.c-1006- tf->tf_ops->fabric_drop_wwn(wwn);
--
drivers/target/target_core_fabric_configfs.c=1168=static void target_fabric_drop_wwn(
--
drivers/target/target_core_fabric_configfs.c-1174-
drivers/target/target_core_fabric_configfs.c:1175: configfs_remove_default_groups(&wwn->wwn_group);
drivers/target/target_core_fabric_configfs.c-1176- config_item_put(item);
--
drivers/usb/gadget/function/f_midi2.c=2472=static void f_midi2_ep_opts_release(struct config_item *item)
--
drivers/usb/gadget/function/f_midi2.c-2475-
drivers/usb/gadget/function/f_midi2.c:2476: configfs_remove_default_groups(&opts->group);
drivers/usb/gadget/function/f_midi2.c-2477- kfree(opts->info.ep_name);
--
drivers/usb/gadget/function/f_midi2.c=2637=static void f_midi2_free_inst(struct usb_function_instance *f)
--
drivers/usb/gadget/function/f_midi2.c-2642-
drivers/usb/gadget/function/f_midi2.c:2643: configfs_remove_default_groups(&opts->func_inst.group);
drivers/usb/gadget/function/f_midi2.c-2644- kfree(opts->info.iface_name);
--
fs/configfs/dir.c=734=static int populate_groups(struct config_group *group,
--
fs/configfs/dir.c-747-
fs/configfs/dir.c:748:void configfs_remove_default_groups(struct config_group *group)
fs/configfs/dir.c-749-{
--
fs/configfs/dir.c-756-}
fs/configfs/dir.c:757:EXPORT_SYMBOL(configfs_remove_default_groups);
fs/configfs/dir.c-758-
--
fs/dlm/config.c=457=static void drop_cluster(struct config_group *g, struct config_item *i)
--
fs/dlm/config.c-460-
fs/dlm/config.c:461: configfs_remove_default_groups(&cl->group);
fs/dlm/config.c-462-
--
fs/dlm/config.c=507=static void drop_space(struct config_group *g, struct config_item *i)
--
fs/dlm/config.c-512-
fs/dlm/config.c:513: configfs_remove_default_groups(&sp->group);
fs/dlm/config.c-514- config_item_put(i);
--
fs/ocfs2/cluster/nodemanager.c=738=static void o2nm_cluster_group_drop_item(struct config_group *group, struct config_item *item)
--
fs/ocfs2/cluster/nodemanager.c-744-
fs/ocfs2/cluster/nodemanager.c:745: configfs_remove_default_groups(&cluster->cl_group);
fs/ocfs2/cluster/nodemanager.c-746- config_item_put(item);
--
include/linux/configfs.h=245=void configfs_unregister_group(struct config_group *group);
include/linux/configfs.h-246-
include/linux/configfs.h:247:void configfs_remove_default_groups(struct config_group *group);
include/linux/configfs.h-248-
]
|
| 120/2 |
2026/09/02 21:14 |
llm |
debugger |
0m
Model:
Tokens:
input: 104387
output: 40
thoughts: 33
Thoughts: Okay, here's my take on those thoughts, framed as an expert's internal monologue:
**Investigating a Subtle NVMe-over-Fabric Configuration Oversight**
Hmm, hold on a second. I was under the impression that the `nvmet` subsystem handled default group removal via `configfs_remove_default_groups`. But that doesn't seem to be the case! That's a crucial piece of the puzzle I need to verify. My focus needs to shift; I need to drill down and double check the logic in `nvmet_port_release`. That's where I believe the proper cleanup is being handled. This is something I need to quickly and comprehensively confirm. There might be some specific conditions at play in `nvmet_port_release` that I didn't immediately consider.
|
| 121/2 |
2026/09/02 21:14 |
tool |
read-file |
0m
Args: map[File:drivers/nvme/target/configfs.c FirstLine:2000 LineCount:20]
Results: map[Contents:2000: /*
2001: * Ports definitions.
2002: */
2003: static void nvmet_port_release(struct config_item *item)
2004: {
2005: struct nvmet_port *port = to_nvmet_port(item);
2006:
2007: /* Let inflight controllers teardown complete */
2008: flush_workqueue(nvmet_wq);
2009: list_del(&port->global_entry);
2010:
2011: key_put(port->keyring);
2012: kfree(port);
2013: }
2014:
2015: static struct configfs_attribute *nvmet_port_attrs[] = {
2016: &nvmet_attr_addr_adrfam,
2017: &nvmet_attr_addr_treq,
2018: &nvmet_attr_addr_traddr,
2019: &nvmet_attr_addr_trsvcid,
]
|
| 122/2 |
2026/09/02 21:14 |
llm |
debugger |
0m
Model:
Tokens:
input: 104928
output: 36
thoughts: 1686
Thoughts: **Unraveling a ConfigFS Conundrum: The Embedded Default Group Use-After-Free**
Okay, so here's the issue. It seems `nvmet_port_release` doesn't call `configfs_remove_default_groups`, which is a key observation. This means the initial reference count on the default groups, specifically the `ci_kref`, never drops to zero. But since these groups are *embedded* within the `port` structure, their memory is effectively freed when the `port` is freed, which is the problem.
`configfs_symlink` holds a reference to `subsys_group`. If that drops to zero, `config_item_cleanup` will eventually be called. But because `configfs_remove_default_groups` isn't called, the initial refcount (from `config_item_init`) is *never* dropped. So, when the refcount drops, it goes from 2 to 1 and then never goes to 0! Consequently, `config_item_cleanup` on `subsys_group` is *never* called, despite it being embedded in a `port` that *is* freed!
This is where the use-after-free manifests. Because `config_item_put` in `configfs_symlink` accesses the freed memory (specifically `ci_kref` within `subsys_group`). It's a classic case.
Now, a seemingly logical fix would be to have `nvmet_port_release` call `configfs_remove_default_groups`. That seems like it would solve the problem by dropping the initial reference on `subsys_group`. However, that doesn't fix it. The group would still be accessed after the parent (port) is freed.
Therefore, the only true fix is to prevent `nvmet_port_release` from running if `subsys_group` still has a non-zero refcount. `nvmet_port_release` is called when `port->group.cg_item`'s refcount drops to 0. It is the `unlink_obj` function that drops the reference! So the key is that `configfs` explicitly allows the parent to be freed while the child exists!
The core problem, then, lies in how `configfs` handles embedded default groups, which seems fundamentally broken. The only way is to ensure the child holds a reference to the parent that's only dropped in the child's `release` function. This way, the parent's refcount *won't* drop to zero until the child is released, and `nvmet_port_release` won't be called prematurely.
Let's see how this works with the existing code structure. `nvmet_ports_make` initializes the `subsys_group`. The `nvmet_port_subsys_type` needs a `release` function. My initial thought was to use `item->ci_parent` in the release function, but it appears `item->ci_parent` is set to NULL by `unlink_obj`, so that won't work.
But `subsys_group` is embedded in the `nvmet_port`. So I can use `container_of` in the release function to get a pointer to the `nvmet_port` structure and then call `config_item_put` on the parent group. However, if this works, I'll need to increment the refcount when `subsys_group` is initialized, by calling `config_item_get`.
This feels a bit cumbersome, and what if there's a better way to implement this? Let's consider `configfs_symlink`. Can we modify it to check if the directory is dead before calling `allow_link` to prevent the crash? Unfortunately, even if `configfs_symlink` checks `IS_DEADDIR(dir)`, it still calls `config_item_put(parent_item)`, which accesses freed memory, so that does *not* fix the issue.
I'm starting to realize that the fundamental problem is that `configfs` allows the parent to be freed while the child is still alive. *Embedding* the child in the parent is simply *invalid* in this configuration, at least in the default way `configfs` is configured. This is seen in other configfs code, like `target_core_fabric_configfs.c`, `usb/gadget/configfs.c`, and `pci/endpoint/pci-ep-cfs.c`. If any of these default groups are accessed concurrently with the parent's removal, they will also exhibit the use-after-free issue.
So the potential concurrency point is through `configfs_symlink` (which drops the
|
| 123/2 |
2026/09/02 21:15 |
tool |
read-file |
0m
Args: map[File:fs/configfs/file.c FirstLine:380 LineCount:20]
Results: map[Contents: 380: {
381: struct configfs_buffer *buffer = filp->private_data;
382:
383: module_put(buffer->owner);
384: kfree(buffer->page);
385: mutex_destroy(&buffer->mutex);
386: kfree(buffer);
387: return 0;
388: }
389:
390: static int configfs_open_file(struct inode *inode, struct file *filp)
391: {
392: return __configfs_open_file(inode, filp, CONFIGFS_ITEM_ATTR);
393: }
394:
395: static int configfs_open_bin_file(struct inode *inode, struct file *filp)
396: {
397: return __configfs_open_file(inode, filp, CONFIGFS_ITEM_BIN_ATTR);
398: }
399:
]
|
| 124/2 |
2026/09/02 21:15 |
llm |
debugger |
0m
Model:
Tokens:
input: 107967
output: 36
thoughts: 13
Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience:
**Diving into `__configfs_open_file`**
Alright, let's get into the weeds. My immediate focus is on that `__configfs_open_file` function. I need to understand its role within the broader context of the ConfigFS implementation. I'm assuming it's handling the low-level file opening operations within the virtual filesystem, but I need to confirm that. My instincts tell me this is a critical point of potential vulnerability or performance bottleneck, so it warrants a thorough examination. I need to consider things like:
* **Error Handling:** How robust is the error handling within this function? Are there any obvious race conditions or issues with concurrency that could lead to unexpected behavior or security exploits? I'll be looking for potential integer overflows, buffer overflows, or incorrect permission checks.
* **Security Context:** Does this function properly handle security contexts, particularly around user and group IDs? Are there any places where it might inadvertently expose sensitive information or allow privilege escalation?
* **Resource Management:** How efficiently does it manage resources like file descriptors, memory allocation, and locking primitives? Are there any potential memory leaks or deadlocks?
* **Performance:** Given the likely high volume of file operations in ConfigFS, I need to evaluate the potential performance implications of this function. Any unnecessary overhead here could significantly impact the overall system responsiveness. I want to look for areas for optimization. Are there any caching mechanisms I need to understand? Is the locking strategy optimal for concurrent access?
* **Callers/Callees:** I need to trace the calling path and the functions it calls to understand the broader context of its operation. What are the dependencies? I'll be looking for anything unexpected or unusual that could be a point of concern.
* **File Permissions & Attributes:** What permissions and attributes are being set (and checked) on the created files? Are they being handled correctly to ensure the system's integrity?
This initial assessment will guide my deeper dive. I'll probably start by looking at the kernel source code, specifically the implementation of `__configfs_open_file`. From there I'll likely use `tracepoints`, `kprobes`, or other debugging tools, to dynamically analyze its behavior under different loads and corner cases. This is where the fun begins.
|
| 125/2 |
2026/09/02 21:15 |
tool |
read-file |
0m
Args: map[File:fs/configfs/file.c FirstLine:320 LineCount:50]
Results: map[Contents: 320: } else {
321: buffer->attr = attr;
322: }
323:
324: buffer->owner = attr->ca_owner;
325: /* Grab the module reference for this attribute if we have one */
326: error = -ENODEV;
327: if (!try_module_get(buffer->owner))
328: goto out_free_buffer;
329:
330: error = -EACCES;
331: if (!buffer->item->ci_type)
332: goto out_put_module;
333:
334: buffer->ops = buffer->item->ci_type->ct_item_ops;
335:
336: /* File needs write support.
337: * The inode's perms must say it's ok,
338: * and we must have a store method.
339: */
340: if (file->f_mode & FMODE_WRITE) {
341: if (!(inode->i_mode & S_IWUGO))
342: goto out_put_module;
343: if ((type & CONFIGFS_ITEM_ATTR) && !attr->store)
344: goto out_put_module;
345: if ((type & CONFIGFS_ITEM_BIN_ATTR) && !buffer->bin_attr->write)
346: goto out_put_module;
347: }
348:
349: /* File needs read support.
350: * The inode's perms must say it's ok, and we there
351: * must be a show method for it.
352: */
353: if (file->f_mode & FMODE_READ) {
354: if (!(inode->i_mode & S_IRUGO))
355: goto out_put_module;
356: if ((type & CONFIGFS_ITEM_ATTR) && !attr->show)
357: goto out_put_module;
358: if ((type & CONFIGFS_ITEM_BIN_ATTR) && !buffer->bin_attr->read)
359: goto out_put_module;
360: }
361:
362: mutex_init(&buffer->mutex);
363: buffer->needs_read_fill = 1;
364: buffer->read_in_progress = false;
365: buffer->write_in_progress = false;
366: file->private_data = buffer;
367: up_read(&frag->frag_sem);
368: return 0;
369:
]
|
| 126/2 |
2026/09/02 21:15 |
llm |
debugger |
0m
Model:
Tokens:
input: 109283
output: 36
thoughts: 87
Thoughts: Here's my thought process, as if I were working through this code:
**Deeper Dive into `__configfs_open_file`**
Okay, hold on a second. I need to make sure I'm following this correctly. I initially thought `__configfs_open_file` was responsible for holding a reference directly to the `config_item`, but now I'm seeing it differently. It seems like it's actually referencing the `configfs_fragment` structure (`frag`), not the item itself. That's a crucial distinction; I've got to keep that in mind.
Now let me re-examine what's happening. I see that `buffer->item` gets assigned by `configfs_get_config_item(dentry->d_parent)`. This is a point of reference. I'm going to step back and re-analyze the entire `__configfs_open_file` function from the very beginning to ensure I have a complete and accurate understanding of how the references are managed and how the structures are related. It seems I missed something the first time through. I need to ensure I grasp the intended relationships between these components, the fragment, the item and the parent.
|
| 127/2 |
2026/09/02 21:15 |
tool |
read-file |
0m
Args: map[File:fs/configfs/file.c FirstLine:280 LineCount:40]
Results: map[Contents: 280: }
281:
282: len = copy_from_iter(buffer->bin_buffer + iocb->ki_pos,
283: buffer->bin_buffer_size - iocb->ki_pos, from);
284: iocb->ki_pos += len;
285: out:
286: mutex_unlock(&buffer->mutex);
287: return len ? : -EFAULT;
288: }
289:
290: static int __configfs_open_file(struct inode *inode, struct file *file, int type)
291: {
292: struct dentry *dentry = file->f_path.dentry;
293: struct configfs_fragment *frag = to_frag(file);
294: struct configfs_attribute *attr;
295: struct configfs_buffer *buffer;
296: int error;
297:
298: error = -ENOMEM;
299: buffer = kzalloc_obj(struct configfs_buffer);
300: if (!buffer)
301: goto out;
302:
303: error = -ENOENT;
304: down_read(&frag->frag_sem);
305: if (unlikely(frag->frag_dead))
306: goto out_free_buffer;
307:
308: error = -EINVAL;
309: buffer->item = to_item(dentry->d_parent);
310: if (!buffer->item)
311: goto out_free_buffer;
312:
313: attr = to_attr(dentry);
314: if (!attr)
315: goto out_free_buffer;
316:
317: if (type & CONFIGFS_ITEM_BIN_ATTR) {
318: buffer->bin_attr = to_bin_attr(dentry);
319: buffer->cb_max_size = buffer->bin_attr->cb_max_size;
]
|
| 128/2 |
2026/09/02 21:15 |
llm |
debugger |
0m
Model:
Tokens:
input: 110264
output: 36
thoughts: 158
Thoughts: **My Initial Concerns Regarding ConfigFS Item Lifecycles**
Okay, so I'm looking at this code and trying to grasp how the `config_item` manages its lifecycle in this ConfigFS environment. I see this line: `buffer->item = to_item(dentry->d_parent);` I immediately note that `to_item` simply retrieves the `config_item` from the `dentry`'s `fsdata`. Critically, *it doesn't increment the reference count.* That's a red flag. If we're not incrementing the refcount when we grab the `config_item` pointer, how is that `config_item` *guaranteed* to stay valid for the duration the file is open, while the file is open?
My mind then jumps to `configfs_rmdir`. I recall it calls `configfs_detach_item`, which in turn leads to `configfs_remove_dir` and finally down to the `simple_rmdir` operation. But, hold on a second! I also remember that `configfs_rmdir` *also* calls `configfs_detach_prep`. And *that* function is supposed to check for open files! So, the flow seems to be that if there are any open files, `configfs_rmdir` doesn't proceed. If there are no open files, `configfs_detach_item` proceeds. Let me quickly examine `configfs_detach_prep` to make sure my understanding is correct and to understand the precise logic in this crucial check.
|
| 129/2 |
2026/09/02 21:15 |
tool |
read-file |
0m
Args: map[File:fs/configfs/dir.c FirstLine:370 LineCount:50]
Results: map[Contents: 370: struct dentry *dentry, char *body)
371: {
372: int err = 0;
373: umode_t mode = S_IFLNK | S_IRWXUGO;
374: struct configfs_dirent *p = parent->d_fsdata;
375: struct inode *p_inode = d_inode(parent);
376: struct inode *inode;
377:
378: err = configfs_make_dirent(p, dentry, target, mode, CONFIGFS_ITEM_LINK,
379: p->s_frag);
380: if (err)
381: return err;
382:
383: inode = configfs_create(dentry, mode);
384: if (IS_ERR(inode))
385: goto out_remove;
386:
387: inode->i_link = body;
388: inode->i_op = &configfs_symlink_inode_operations;
389: d_make_persistent(dentry, inode);
390: inode_set_mtime_to_ts(p_inode, inode_set_ctime_current(p_inode));
391: return 0;
392:
393: out_remove:
394: configfs_put(dentry->d_fsdata);
395: configfs_remove_dirent(dentry);
396: return PTR_ERR(inode);
397: }
398:
399: /**
400: * configfs_remove_dir - remove an config_item's directory.
401: * @d: dentry we're removing.
402: *
403: * The only thing special about this is that we remove any files in
404: * the directory before we remove the directory, and we've inlined
405: * what used to be configfs_rmdir() below, instead of calling separately.
406: *
407: * Caller holds the mutex of the item's inode
408: */
409:
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: }
]
|
| 130/2 |
2026/09/02 21:15 |
llm |
debugger |
0m
Model:
Tokens:
input: 111571
output: 20
thoughts: 46
Thoughts: **Investigating the `configfs_detach_prep` Function**
Okay, so I see that `configfs_detach_prep` was introduced or modified in patch `4768e9b18dc6`. That's a good starting point for understanding its role in recent changes. Now, I need to get a handle on its current implementation within the kernel. Let's fire up a search β I'll need to know where it's being called, how it's defined, and what surrounding code contexts it's involved in *right now*. I'm going to dig into the current kernel source to get a clear picture of its behavior. Knowing the present context is key to understanding its past, and any potential issues or modifications that might have arisen.
|
| 131/2 |
2026/09/02 21:15 |
tool |
grepper |
0m
Args: map[Expression:configfs_detach_prep]
Results: map[Output:fs/configfs/dir.c=426=static struct dentry * configfs_lookup(struct inode *dir,
--
fs/configfs/dir.c-501- */
fs/configfs/dir.c:502:static int configfs_detach_prep(struct configfs_dirent *parent_sd, struct dentry **wait)
fs/configfs/dir.c-503-{
--
fs/configfs/dir.c-530- */
fs/configfs/dir.c:531: ret = configfs_detach_prep(sd, wait);
fs/configfs/dir.c-532- if (!ret)
--
fs/configfs/dir.c=992=static int configfs_dump(struct configfs_dirent *sd, int level)
--
fs/configfs/dir.c-1046- * Only subdirectories count here. Files (CONFIGFS_NOT_PINNED) are
fs/configfs/dir.c:1047: * attributes. This is similar but not the same to configfs_detach_prep().
fs/configfs/dir.c:1048: * Note that configfs_detach_prep() expects the parent to be locked when it
fs/configfs/dir.c-1049- * is called, but we lock the parent *inside* configfs_depend_prep(). We
--
fs/configfs/dir.c=1289=static struct dentry *configfs_mkdir(struct mnt_idmap *idmap, struct inode *dir,
--
fs/configfs/dir.c-1417- spin_lock(&configfs_dirent_lock);
fs/configfs/dir.c:1418: /* This will make configfs_detach_prep() fail */
fs/configfs/dir.c-1419- sd->s_type |= CONFIGFS_USET_IN_MKDIR;
--
fs/configfs/dir.c=1469=static int configfs_rmdir(struct inode *dir, struct dentry *dentry)
--
fs/configfs/dir.c-1512- if (!ret) {
fs/configfs/dir.c:1513: ret = configfs_detach_prep(sd, &wait);
fs/configfs/dir.c-1514- if (ret)
--
fs/configfs/dir.c=1796=void configfs_unregister_group(struct config_group *group)
--
fs/configfs/dir.c-1809- spin_lock(&configfs_dirent_lock);
fs/configfs/dir.c:1810: configfs_detach_prep(sd, NULL);
fs/configfs/dir.c-1811- spin_unlock(&configfs_dirent_lock);
--
fs/configfs/dir.c=1934=void configfs_unregister_subsystem(struct configfs_subsystem *subsys)
--
fs/configfs/dir.c-1955- spin_lock(&configfs_dirent_lock);
fs/configfs/dir.c:1956: if (configfs_detach_prep(sd, NULL)) {
fs/configfs/dir.c-1957- pr_err("Tried to unregister non-empty subsystem!\n");
]
|
| 132/2 |
2026/09/02 21:15 |
llm |
debugger |
0m
Model:
Tokens:
input: 112639
output: 36
thoughts: 12
Thoughts: Okay, let's dive into `configfs_detach_prep`. My initial thought is to understand the context of this function. Given my expertise, I already have a pretty good mental model of the configfs subsystem. I know it's about virtual file systems that expose kernel objects. So, `detach_prep` immediately suggests a preparatory stage for removing or disconnecting something. I'm guessing this function is involved in cleaning up resources, validating states, or perhaps signalling to other parts of the system that an object is about to be detached. I need to figure out what precisely this prepares for and from what.
I'm expecting this is going to be a key function to ensure a clean teardown, so it needs to be robust, potentially handling a variety of error conditions. I also anticipate it needs to manage locks correctly β concurrency is always a big concern within any file system. Iβll be looking closely at the locking strategy used here. I'm also anticipating that depending on the nature of the specific object being detached it will need to interact with other kernel subsystems so I will be looking out for any calls that reference those. I will be looking to see what the state of the object is before it is deallocated. And I know I need to be careful to look out for race conditions, especially if there are any asynchronous operations involved.
|
| 133/2 |
2026/09/02 21:15 |
tool |
read-file |
0m
Args: map[File:fs/configfs/dir.c FirstLine:500 LineCount:50]
Results: map[Contents: 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: }
543:
544: /*
545: * Walk the tree, resetting CONFIGFS_USET_DROPPING wherever it was
546: * set.
547: */
548: static void configfs_detach_rollback(struct configfs_dirent *parent_sd)
549: {
]
|
| 134/2 |
2026/09/02 21:15 |
llm |
debugger |
0m
Model:
Tokens:
input: 113653
output: 36
thoughts: 437
Thoughts: **Analyzing a Potential Race Condition in configfs**
Okay, so I'm digging into this `configfs` code, and something's bugging me. I see that `configfs_detach_prep` checks the `s_links` field of the parent directory. But, hang on... it *doesn't* seem to check for open files. And, confirming my suspicions, `configfs_rmdir` also doesn't check for open files. This is a red flag.
My concern is this: if a file *is* open within a directory, `configfs_rmdir` will happily succeed in removing the directory and detaching the `config_item`. If that `config_item` happens to be a default group, its parent gets freed. Then, the open file is going to try to access that freed parent during a read/write! That's a classic use-after-free scenario, and a nasty one at that.
Is my logic sound? I need to verify this... Let's look at `__configfs_open_file`. Ah, it gets the item from `dentry->d_parent`:
```c
buffer->item = to_item(dentry->d_parent);
```
Okay, it does not take a reference to the item itself. But wait... `configfs_open_file` *does* take a reference to the `configfs_fragment` (`frag`).
```c
down_read(&frag->frag_sem);
if (unlikely(frag->frag_dead))
goto out_free_buffer;
```
And `configfs_rmdir` sets `frag->frag_dead = true` and then calls `up_write(&frag->frag_sem)`.
```c
down_write(&frag->frag_sem);
frag->frag_dead = true;
up_write(&frag->frag_sem);
```
Now, hold on... if I go into `configfs_read_file`:
```c
down_read(&frag->frag_sem);
if (unlikely(frag->frag_dead))
goto out;
```
Aha! `configfs_read_file` and `configfs_write_file` *both* check `frag->frag_dead`! If `frag->frag_dead` is true, then the read or write aborts and does *not* access `buffer->item`! It is a safeguard. Okay, I'm going to verify this thoroughly, but it looks like the race condition is correctly handled through the `frag->frag_dead` flag and associated semaphore usage.
|
| 135/2 |
2026/09/02 21:15 |
tool |
read-file |
0m
Args: map[File:fs/configfs/file.c FirstLine:140 LineCount:50]
Results: map[Contents: 140: }
141:
142: buffer->bin_buffer = vmalloc(len);
143: if (buffer->bin_buffer == NULL) {
144: retval = -ENOMEM;
145: goto out;
146: }
147: buffer->bin_buffer_size = len;
148:
149: /* perform second read to fill buffer */
150: down_read(&frag->frag_sem);
151: if (!frag->frag_dead)
152: len = buffer->bin_attr->read(buffer->item,
153: buffer->bin_buffer, len);
154: else
155: len = -ENOENT;
156: up_read(&frag->frag_sem);
157: if (len < 0) {
158: retval = len;
159: vfree(buffer->bin_buffer);
160: buffer->bin_buffer_size = 0;
161: buffer->bin_buffer = NULL;
162: goto out;
163: }
164:
165: buffer->needs_read_fill = 0;
166: }
167:
168: if (iocb->ki_pos >= buffer->bin_buffer_size)
169: goto out;
170: retval = copy_to_iter(buffer->bin_buffer + iocb->ki_pos,
171: buffer->bin_buffer_size - iocb->ki_pos, to);
172: iocb->ki_pos += retval;
173: if (retval == 0)
174: retval = -EFAULT;
175: out:
176: mutex_unlock(&buffer->mutex);
177: return retval;
178: }
179:
180: /* Fill @buffer with data coming from @from. */
181: static int fill_write_buffer(struct configfs_buffer *buffer,
182: struct iov_iter *from)
183: {
184: int copied;
185:
186: if (!buffer->page)
187: buffer->page = kmalloc(PAGE_SIZE, GFP_KERNEL);
188: if (!buffer->page)
189: return -ENOMEM;
]
|
| 136/2 |
2026/09/02 21:15 |
llm |
debugger |
0m
Model:
Tokens:
input: 115403
output: 36
thoughts: 349
Thoughts: **My Analysis of the `configfs` Vulnerability**
Okay, so I've been digging into this `configfs` code, and I think I've got a handle on a potential vulnerability. It looks like the system is using `frag->frag_dead` to prevent accessing a buffer after a directory is removed. That's a solid defense β it keeps open files safe, which is the main concern here.
But then I saw that `configfs_symlink` doesn't explicitly check `frag->frag_dead`! My initial thought was that this could be a problem, but then I remember that `configfs_symlink` *does* check `d_unhashed(dentry)`. However, the `dentry` there refers to the symlink itself, not the directory it's linking to. Hmm...
Now, it occurs to me that `configfs_symlink` drops the directory lock, and if `configfs_rmdir` were to run concurrently on the parent of the linked directory, that call will set the linked directory's fragment to `frag->frag_dead = true`! So, if we can add a check of the `frag->frag_dead` flag after the lock is re-acquired in `configfs_symlink`, we should be able to detect if the directory has been removed, right? Let's look at the specific code in `configfs_symlink`:
```c
struct configfs_dirent *sd = dentry->d_parent->d_fsdata;
struct configfs_fragment *frag = sd->s_frag;
```
I think this could be our solution! After getting the parent directory's `fsdata` and fragment, if we re-acquire the lock and check `frag->frag_dead`, we should be able to detect the removal. That sounds promising.
But there's something else I need to consider. `configfs_rmdir` marks the *item being removed* as dead, setting `frag->frag_dead` to true. If something like, say, `ports/1` is removed, its associated `frag` will be marked as dead. But what about its children? I'm wondering if `ports/1/subsystems` shares the *same* `frag`? If so, this could be more complex than I initially thought. I need to investigate if default groups share the same `frag` as their parent. That's where I need to focus my efforts next.
|
| 137/2 |
2026/09/02 21:16 |
tool |
read-file |
0m
Args: map[File:fs/configfs/dir.c FirstLine:700 LineCount:50]
Results: map[Contents: 700: * We could, perhaps, tweak our parent's ->mkdir for a minute and
701: * try using vfs_mkdir. Just a thought.
702: */
703: static int create_default_group(struct dentry *parent,
704: struct config_group *group,
705: struct configfs_fragment *frag)
706: {
707: int ret;
708: struct configfs_dirent *sd;
709: /* We trust the caller holds a reference to parent */
710: struct dentry *child;
711:
712: if (!group->cg_item.ci_name)
713: group->cg_item.ci_name = group->cg_item.ci_namebuf;
714:
715: ret = -ENOMEM;
716: child = d_alloc_name(parent, group->cg_item.ci_name);
717: if (child) {
718: d_add(child, NULL);
719:
720: ret = configfs_attach_group(&group->cg_item, child, frag);
721: if (!ret) {
722: sd = child->d_fsdata;
723: sd->s_type |= CONFIGFS_USET_DEFAULT;
724: } else {
725: BUG_ON(d_inode(child));
726: d_drop(child);
727: }
728: dput(child);
729: }
730:
731: return ret;
732: }
733:
734: static int populate_groups(struct config_group *group,
735: struct configfs_fragment *frag)
736: {
737: struct dentry *parent = group->cg_item.ci_dentry;
738: struct config_group *new_group;
739:
740: list_for_each_entry(new_group, &group->default_groups, group_entry) {
741: int ret = create_default_group(parent, new_group, frag);
742: if (ret)
743: return ret;
744: }
745: return 0;
746: }
747:
748: void configfs_remove_default_groups(struct config_group *group)
749: {
]
|
| 138/2 |
2026/09/02 21:16 |
llm |
debugger |
0m
Model:
Tokens:
input: 117048
output: 37
thoughts: 837
Thoughts: **Analyzing a Potential Use-After-Free Vulnerability**
Okay, so `populate_groups` correctly passes the `frag` to `create_default_group`. Excellent. That `frag` is the fragment of the parent, in this case, `ports/1`. This is important: `ports/1` and `ports/1/subsystems` now share the *same* `frag`. That makes sense for tracking directory state.
The core of the issue emerges with `configfs_rmdir`. When it's called on `ports/1`, it sets `frag->frag_dead = true`. My initial thought was that if `configfs_symlink` checks `frag->frag_dead` after re-acquiring the lock, we've got a valid protection against the race. Brilliant!
But, let's examine `configfs_symlink`. It looks like this:
```c
inode_unlock(dir);
ret = get_target(symname, &target_item, dentry->d_sb);
inode_lock(dir);
if (ret)
goto out_put;
if (dentry->d_inode || d_unhashed(dentry))
ret = -EEXIST;
else
ret = inode_permission(&nop_mnt_idmap, dir,
MAY_WRITE | MAY_EXEC);
if (!ret)
ret = type->ct_item_ops->allow_link(parent_item, target_item);
```
My instinct was correct, adding `if (frag->frag_dead) ret = -ENOENT;` *before* the `allow_link` check *would* skip the check. That's a good start. But... it still calls `config_item_put(parent_item)` at the end!
```c
out_put:
config_item_put(parent_item);
```
And here's the problem. If `ports/1` has been freed by `configfs_rmdir`, then `parent_item`, which is `&port->subsys_group.cg_item`, is ALSO freed! The `config_item_put(parent_item)` call would then be accessing freed memory! This is a textbook use-after-free. I need to be more precise in my analysis of the `configfs_rmdir` and the impact of the call to `unlink_group`.
`configfs_rmdir` does the following:
```c
down_write(&frag->frag_sem);
frag->frag_dead = true;
up_write(&frag->frag_sem);
/* Get a working ref for the duration of this function */
item = configfs_get_config_item(dentry);
/* Drop reference from above, item already holds one. */
config_item_put(parent_item);
if (item->ci_type)
dead_item_owner = item->ci_type->ct_owner;
if (sd->s_type & CONFIGFS_USET_DIR) {
configfs_detach_group(dentry);
mutex_lock(&subsys->su_mutex);
client_disconnect_notify(parent_item, item);
unlink_group(to_config_group(item));
}
```
Yes, `configfs_rmdir` calls `unlink_group`, which *drops* the reference to `ports/1`. Then `ports/1` is freed. I'm right. If `configfs_symlink` *is* holding a reference to `parent_item` (the `subsys_group.cg_item`) and `configfs_rmdir` frees `ports/1`, then `parent_item` is also freed! Adding the check on `frag->frag_dead` *doesn't* prevent the use-after-free in `config_item_put(parent_item)`!
I need to circle back and look at another codepath. The goal is to analyze the codepaths to determine why `configfs_open_file` does not have a similar vulnerability. It seems there *must* be some difference in the way it handles references to be safe. `configfs_open_file` *doesn't* hold a reference to `buffer->item`, it only holds a reference to `frag`. I should inspect the code in `configfs_mkdir` to determine exactly where the `frag` is allocated, to determine whether this difference in reference is sufficient to protect against the UAF, and what implications it might have for race conditions. I believe that it is
|
| 139/2 |
2026/09/02 21:16 |
tool |
read-file |
0m
Args: map[File:fs/configfs/dir.c FirstLine:1280 LineCount:50]
Results: map[Contents:1280: * We were called from subsystem other than our target so we
1281: * took some locks so now it's time to release them
1282: */
1283: inode_unlock(d_inode(root->cg_item.ci_dentry));
1284:
1285: return ret;
1286: }
1287: EXPORT_SYMBOL(configfs_depend_item_unlocked);
1288:
1289: static struct dentry *configfs_mkdir(struct mnt_idmap *idmap, struct inode *dir,
1290: struct dentry *dentry, umode_t mode)
1291: {
1292: int ret = 0;
1293: int module_got = 0;
1294: struct config_group *group = NULL;
1295: struct config_item *item = NULL;
1296: struct config_item *parent_item;
1297: struct configfs_subsystem *subsys;
1298: struct configfs_dirent *sd;
1299: const struct config_item_type *type;
1300: struct module *subsys_owner = NULL, *new_item_owner = NULL;
1301: struct configfs_fragment *frag;
1302: struct name_snapshot n;
1303: const char *name;
1304:
1305: take_dentry_name_snapshot(&n, dentry);
1306: name = n.name.name;
1307:
1308: sd = dentry->d_parent->d_fsdata;
1309:
1310: /*
1311: * Fake invisibility if dir belongs to a group/default groups hierarchy
1312: * being attached
1313: */
1314: if (!configfs_dirent_is_ready(sd)) {
1315: ret = -ENOENT;
1316: goto out;
1317: }
1318:
1319: if (!(sd->s_type & CONFIGFS_USET_DIR)) {
1320: ret = -EPERM;
1321: goto out;
1322: }
1323:
1324: frag = new_fragment();
1325: if (!frag) {
1326: ret = -ENOMEM;
1327: goto out;
1328: }
1329:
]
|
| 140/2 |
2026/09/02 21:16 |
llm |
debugger |
0m
Model:
Tokens:
input: 119650
output: 36
thoughts: 39
Thoughts: Okay, here's my interpretation of that thought process, assuming I'm well-versed in memory allocation and kernel internals:
**Analyzing `frag` Allocation and Reference Counting**
Alright, so the initial thought is confirming that `frag`, this thing we're working with, is allocated using `new_fragment()`. I know `new_fragment()` implies a specific allocation strategy, typically within the kernel. The use of `kzalloc` is key here. That means the allocated memory is zeroed out, a crucial detail to remember for initialization and avoiding potential security vulnerabilities.
Next, the thought pivots to reference counting. Is `frag` reference counted? This is a really important question for memory management and preventing use-after-free bugs or memory leaks. Reference counting, if present, is essential for correctly managing the lifetime of this fragment.
Therefore, the only sensible next step is to examine the `new_fragment()` function itself. That's where I will find the crucial details around the initialization of the allocated memory, if it is reference counted, and how. That will give me the answer I need. I'll understand how the memory is handled, from the very point of allocation.
|
| 141/2 |
2026/09/02 21:16 |
tool |
read-file |
0m
Args: map[File:fs/configfs/dir.c FirstLine:140 LineCount:20]
Results: map[Contents: 140:
141: static void configfs_set_dir_dirent_depth(struct configfs_dirent *parent_sd,
142: struct configfs_dirent *sd)
143: {
144: }
145:
146: static void
147: configfs_adjust_dir_dirent_depth_before_populate(struct configfs_dirent *sd)
148: {
149: }
150:
151: static void
152: configfs_adjust_dir_dirent_depth_after_populate(struct configfs_dirent *sd)
153: {
154: }
155:
156: #endif /* CONFIG_LOCKDEP */
157:
158: static struct configfs_fragment *new_fragment(void)
159: {
]
|
| 142/2 |
2026/09/02 21:16 |
llm |
debugger |
0m
Model:
Tokens:
input: 120243
output: 36
thoughts: 11
Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience:
**Analyzing the `new_fragment` Implementation**
Okay, time to dive into this `new_fragment` implementation. I need to make sure this is doing what I expect it to. First, I'm going to quickly refresh my memory on the established patterns for this component. I'm looking for potential areas of concern right away. Specifically, I'm thinking about:
* **Memory Management**: Are we handling allocations and deallocations appropriately, especially in edge cases or if we have deeply nested data structures? Any potential memory leaks or dangling pointers?
* **Concurrency**: If this code is running in a multi-threaded environment (and frankly, given the nature of the work, it very well might be), what's the thread safety like? Any race conditions or data corruption risks? I'll be looking for mutex usage, atomic operations, and any assumptions made about the execution context.
* **Error Handling**: How robust is the error handling? Does it properly account for all possible error conditions (memory allocation failures, invalid inputs, unexpected states), and does it provide meaningful feedback and recovery mechanisms?
* **Performance**: Does the code perform as expected for large inputs? Are there any unnecessary copies or computations that could be optimized? Are we using the right data structures to achieve maximum efficiency?
* **Side Effects**: Are there any unexpected side effects this function is causing? It should do *exactly* what it says on the tin, and no more. I'll need to confirm that this function behaves in an isolated manner.
I'll be paying close attention to these areas and then compare the code to the standards for this component. I will be looking for:
* Adherence to coding style
* Unit Test coverage
* Integration with existing frameworks and libraries
Let's start digging. I'm going to start with the obvious stuff - the public interface, parameters, return types - and then start following execution paths.
|
| 143/2 |
2026/09/02 21:16 |
tool |
read-file |
0m
Args: map[File:fs/configfs/dir.c FirstLine:158 LineCount:20]
Results: map[Contents: 158: static struct configfs_fragment *new_fragment(void)
159: {
160: struct configfs_fragment *p;
161:
162: p = kmalloc_obj(struct configfs_fragment);
163: if (p) {
164: atomic_set(&p->frag_count, 1);
165: init_rwsem(&p->frag_sem);
166: p->frag_dead = false;
167: }
168: return p;
169: }
170:
171: void put_fragment(struct configfs_fragment *frag)
172: {
173: if (frag && atomic_dec_and_test(&frag->frag_count))
174: kfree(frag);
175: }
176:
177: struct configfs_fragment *get_fragment(struct configfs_fragment *frag)
]
|
| 144/2 |
2026/09/02 21:16 |
llm |
debugger |
0m
Model:
Tokens:
input: 121002
output: 36
thoughts: 3426
Thoughts: **Deep Dive into a ConfigFS Symlink Vulnerability**
Okay, so `frag` uses reference counting, that's clear. `configfs_open_file` gets a reference to `frag`, not `config_item`. That's why even after `configfs_rmdir` frees the `config_item`, the open file is safe; it checks `frag->frag_dead` and bails if the frag is no longer valid. So far, so good.
But `configfs_symlink` is a problem. It grabs a reference to the `config_item` representing the parent directory using `configfs_get_config_item`. And it *does* call `config_item_put` on that parent at the end. If that parent is something like the `ports/1` group and `ports/1` gets removed, calling `config_item_put` accesses freed memory β boom, a use-after-free!
Why doesn't the reference in `configfs_symlink` prevent the parent from being freed? Because `configfs_rmdir` calls `unlink_group`, which drops the parent's reference, letting the parent be deleted. If `configfs_symlink` *held* a parent reference, we'd be safe. But it holds a reference to the child group, which doesn't protect the parent, the root cause of the issue.
The first fix idea is simple: have `configfs_symlink` reference the `frag` and check its `frag_dead` status. This prevents an `allow_link` call. But we still need `config_item_put(parent_item)`, accessing a freed memory address. If `configfs_symlink` held a `frag` reference, why not *not* hold a `parent_item` reference? `configfs_symlink` needs the `parent_item` for `allow_link` which requires a valid `parent_item`.
So, the next thought: `configfs` *must* not free the parent item if the reference count is greater than zero! But `configfs` doesn't know that the `parent_item` is embedded within a `port`. Only `nvmet` knows this relationship. Thus, `nvmet` *must* prevent the free of a `port` when a child group has a reference. How can `nvmet` know the child's refcount?
If the child holds a reference to `port`! `unlink_obj` drops the `link_obj` reference. Let's make `nvmet` take *another* reference to the `port` when the `subsys_group` is initialized, and release this extra reference in `subsys_group`'s `release` function. This *should* prevent the port from being freed until the group is released.
Let's test this in `nvmet_ports_make` and `nvmet_port_subsys_type`. We'll add a release function that calls `config_item_put` on the port's group item. The port will now have an extra reference. This extra reference will keep `ports/1` alive. After `configfs_symlink` re-acquires the lock and attempts `allow_link` and `config_item_put(parent_item)`, the extra ref is released after the `subsys_group` ref becomes 0. Then `nvmet_port_release` and the port is freed.
This seems perfect! But, what if `configfs_symlink` *isn't* running? `configfs_rmdir` will call `unlink_group` on the parent, which call `unlink_group` on the `subsys_group`. The `unlink_obj` function will drop the reference on `ports/1`. Then `configfs_rmdir` calls `config_item_put` on the parent group. However, the port will have the extra reference!
If `configfs_symlink` is not running, who drops the `subsys_group` ref to zero? `configfs_remove_default_groups` is not called by `nvmet_port_release`! Thus, the initial ref from `config_item_init` is *never* dropped.
So, adding the extra ref, we *must* also call `configfs_remove_default_groups`. If we call this in `nvmet_port_release`, the initial refs are removed! If `configfs_symlink` isn't running, the sub system group's ref is zero and the group's release function is called. The release function should do nothing because all groups are embedded in `port` and that would cause a double free or underflow.
|
| 145/2 |
2026/09/02 21:17 |
tool |
read-file |
0m
Args: map[File:include/linux/configfs.h FirstLine:210 LineCount:20]
Results: map[Contents: 210: * children (disallowing mkdir(2)), it need not provide either function.
211: */
212: struct configfs_item_operations {
213: void (*release)(struct config_item *);
214: int (*allow_link)(struct config_item *src, struct config_item *target);
215: void (*drop_link)(struct config_item *src, struct config_item *target);
216: };
217:
218: struct configfs_group_operations {
219: struct config_item *(*make_item)(struct config_group *group, const char *name);
220: struct config_group *(*make_group)(struct config_group *group, const char *name);
221: void (*disconnect_notify)(struct config_group *group, struct config_item *item);
222: void (*drop_item)(struct config_group *group, struct config_item *item);
223: bool (*is_visible)(struct config_item *item, struct configfs_attribute *attr, int n);
224: bool (*is_bin_visible)(struct config_item *item, struct configfs_bin_attribute *attr,
225: int n);
226: };
227:
228: struct configfs_subsystem {
229: struct config_group su_group;
]
|
| 146/2 |
2026/09/02 21:17 |
llm |
debugger |
0m
Model:
Tokens:
input: 125879
output: 40
thoughts: 97
Thoughts: **Investigating the Configuration File System (configfs) and NVMe Port Removal**
Okay, so I'm looking at how we handle port removal within `configfs`, and something feels a little off. I see that `disconnect_notify` and `drop_item` are part of `configfs_group_operations`. The code says that `drop_item` is called when a child is removed. Now, the `port` is the child being removed, right? But the parent is the `ports` subsystem! This means that it is the `ports`'s `drop_item` function that's being called when a `port` is removed. That seems right, but let's double-check. I need to make sure this is what is supposed to happen and that our assumptions are correct. I need to dive into `nvmet_ports_type` and specifically examine its `drop_item` function to verify that we're handling the port removal correctly and that the subsystem is taking proper action. I need to make sure we're not missing any cleanup or potentially leaking resources.
|
| 147/2 |
2026/09/02 21:17 |
tool |
read-file |
0m
Args: map[File:drivers/nvme/target/configfs.c FirstLine:2100 LineCount:30]
Results: map[Contents:2100: __stringify(NVMET_DEFAULT_ANA_GRPID),
2101: &nvmet_ana_group_type);
2102: configfs_add_default_group(&port->ana_default_group.group,
2103: &port->ana_groups_group);
2104:
2105: return &port->group;
2106: }
2107:
2108: static struct configfs_group_operations nvmet_ports_group_ops = {
2109: .make_group = nvmet_ports_make,
2110: };
2111:
2112: static const struct config_item_type nvmet_ports_type = {
2113: .ct_group_ops = &nvmet_ports_group_ops,
2114: .ct_owner = THIS_MODULE,
2115: };
2116:
2117: static struct config_group nvmet_subsystems_group;
2118: static struct config_group nvmet_ports_group;
2119:
2120: #ifdef CONFIG_NVME_TARGET_AUTH
2121: static ssize_t nvmet_host_dhchap_key_show(struct config_item *item,
2122: char *page)
2123: {
2124: u8 *dhchap_secret;
2125: ssize_t ret;
2126:
2127: down_read(&nvmet_config_sem);
2128: dhchap_secret = to_host(item)->dhchap_secret;
2129: if (!dhchap_secret)
]
|
| 148/2 |
2026/09/02 21:17 |
llm |
debugger |
0m
Model:
Tokens:
input: 126717
output: 37
thoughts: 564
Thoughts: **My Analysis of a Potential Memory Leak in the NVMe-oF Configuration Code**
Okay, let's break this down. I'm looking at `nvmet_ports_group_ops` and immediately see that it *doesn't* have a `drop_item` function. My initial thought is, "We need a `drop_item` function here." I've sketched out a potential implementation, the `nvmet_ports_drop` function, which calls `configfs_remove_default_groups` on several of the `nvmet_port`'s associated groups.
Then I realized something subtle but critical. `configfs_remove_default_groups` is the key. It removes these groups from the parent's default group list and, crucially, it *drops the initial reference* to these groups. But then I remembered that `configfs_rmdir` already calls `unlink_group` on the `port`, and `unlink_group` is called on all the default groups! Ah wait, `unlink_group` only unlinks them from the tree and drops the *parent* reference, but it *doesn't* drop the initial reference. This means if `nvmet` *never* calls `configfs_remove_default_groups`, then the initial reference is never dropped. The `ci_kref` for those default groups never hits zero!
This is where the alarm bells go off. If the reference count never goes to zero, the `release` function for those groups isn't called. This is a classic recipe for a memory leak, especially if those groups were dynamically allocated. However, in this case, the groups are embedded within the `port` structure itself. So, their memory *is* freed when the `port` is freed. So, the code relies on this, which violates `configfs`'s lifetime rules, and that leads to a use-after-free issue.
So, how do we fix this? The fundamental problem is that we need to ensure the reference count on the default groups hits zero so that their release functions are called.
There are two major fixes to handle. First, `nvmet` MUST call `configfs_remove_default_groups` for *all* default groups. Second, the default groups should probably hold a reference to the `port` itself. If the groups hold a reference, the `port` will not be freed until the groups are released. But wait a moment. If `nvmet` calls `configfs_remove_default_groups` in the `nvmet_port_release` function, it's too late! The `port` is already in the process of being freed. So, the correct place to call `configfs_remove_default_groups` is in `drop_item`. But when *is* `drop_item` called? I need to track this down precisely. I need to know the circumstances where the `port` is actually being removed from the `ports` structure. Time to dive deeper into the code...
|
| 149/2 |
2026/09/02 21:17 |
tool |
read-file |
0m
Args: map[File:fs/configfs/dir.c FirstLine:1550 LineCount:20]
Results: map[Contents: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:
]
|
| 150/2 |
2026/09/02 21:17 |
llm |
debugger |
0m
Model:
Tokens:
input: 128265
output: 36
thoughts: 424
Thoughts: **Refining the Item Drop Logic**
Okay, so `client_drop_item` is the function that initiates the item removal process, and it ultimately calls the `drop_item` function, which is exactly what I expected. The code confirms that if the `drop_item` method is defined within the `config_item_type`, it will be invoked. Importantly, the documentation states that if a `drop_item` function is implemented, it MUST call `config_item_put(item)` at the end. Got it.
Looking at the example for the `nvmet_ports_drop` function, I notice some redundancy. It explicitly calls `configfs_remove_default_groups` for a series of group members. However, it seems a little excessive. We are working with the `port` which is an item in the `ports` config group.
Now I remember what `configfs_remove_default_groups` does. It recursively removes all default groups. Therefore, instead of calling `configfs_remove_default_groups` on each individual group, it should be enough to call it once on the top-level group `port->group`. It will take care of removing all the sub-groups automatically. This should simplify the code and make it more efficient. I'm going to double check `configfs_remove_default_groups`'s behavior just to be absolutely sure, but I believe this is the correct optimization.
|
| 151/2 |
2026/09/02 21:17 |
tool |
read-file |
0m
Args: map[File:fs/configfs/dir.c FirstLine:745 LineCount:20]
Results: map[Contents: 745: return 0;
746: }
747:
748: void configfs_remove_default_groups(struct config_group *group)
749: {
750: struct config_group *g, *n;
751:
752: list_for_each_entry_safe(g, n, &group->default_groups, group_entry) {
753: list_del(&g->group_entry);
754: config_item_put(&g->cg_item);
755: }
756: }
757: EXPORT_SYMBOL(configfs_remove_default_groups);
758:
759: /*
760: * All of link_obj/unlink_obj/link_group/unlink_group require that
761: * subsys->su_mutex is held.
762: */
763:
764: static void unlink_obj(struct config_item *item)
]
|
| 152/2 |
2026/09/02 21:17 |
llm |
debugger |
0m
Model:
Tokens:
input: 129324
output: 17
thoughts: 1391
Thoughts: **The Eureka Moment and the Realization of a Fix**
Okay, so I've been staring at this memory management issue, and I think I've cracked it. The problem is a classic use-after-free scenario. The `subsys_group` is still alive while the `port` is freed. `configfs_remove_default_groups` is going to iterate over the default groups, including the nested ones, to drop their initial references, that makes sense. But, if we call that in the `drop_item`, those initial references will be gone!
Now, if `configfs_symlink` holds a reference to `subsys_group`, its refcount will drop to 1. Then, calling `config_item_put(item)` will drop the `port`'s refcount to 0, which triggers `nvmet_port_release`, which frees the `port`. But, this happens while `subsys_group` still has a refcount of 1! And that's the problem.
The key is that the `port` MUST NOT be freed before `subsys_group` is released. Therefore, `subsys_group` needs to hold a reference to `port`, but that creates a reference cycle with the initial ref held by `port`. However, if the initial references are dropped in `drop_item`, this cycle is broken. Now, `subsys_group` will be released when `configfs_symlink` drops its reference, its `release` function then dropping the ref to `port`, and then finally `port` will be freed! **Perfect!**
Alright, let's nail this down.
1. In `nvmet_ports_make`, we need to ensure that each default group holds a reference to the `port`.
We do this with:
```c
config_group_init_type_name(&port->subsys_group,
"subsystems", &nvmet_port_subsys_type);
configfs_add_default_group(&port->subsys_group, &port->group);
config_item_get(&port->group.cg_item); // subsys_group holds a ref to port
```
We need to call `config_item_get()` on all the initial references. So: `subsys_group`, `referrals_group`, `ana_groups_group`, `ana_default_group`.
2. Next, we add `release` functions to all their types: `nvmet_port_subsys_type`, `nvmet_referrals_type`, `nvmet_ana_groups_type`, `nvmet_ana_group_type`. Inside those `release` functions, we drop the reference to `port`:
```c
static void nvmet_port_subsys_release(struct config_item *item)
{
struct nvmet_port *port = container_of(to_config_group(item), struct nvmet_port, subsys_group);
config_item_put(&port->group.cg_item);
}
```
3. Finally, implement `drop_item` for `nvmet_ports_type`:
```c
static void nvmet_ports_drop(struct config_group *group, struct config_item *item)
{
struct nvmet_port *port = to_nvmet_port(item);
configfs_remove_default_groups(&port->ana_groups_group);
configfs_remove_default_groups(&port->group);
config_item_put(item);
}
```
Okay, so I have all that boiler-plate code. And then I think, is there an easier way? What if `configfs_symlink` just checks `frag->frag_dead` and we don't fix the lifetime issue? But that doesn't work. `configfs_symlink` still calls `config_item_put(parent_item)`, and if `port` is freed, `parent_item` is freed memory, and that's going to crash! So, that's a no-go.
So, is there ANY way to avoid crashing without fixing the lifetime issue? No, there is not!
So, can we make `configfs_symlink` NOT hold a reference to `parent_item`? If we do that, we avoid the whole problem. What if it references the PARENT of `parent_item`, which is `port`? That would work! It does that to keep the memory alive. But, how can `configfs
|
| 153/2 |
2026/09/02 21:17 |
tool |
grepper |
0m
Args: map[Expression:drop_item]
Results: map[Output:Documentation/filesystems/configfs.rst=285=config_item_type::
--
Documentation/filesystems/configfs.rst-293- struct config_item *item);
Documentation/filesystems/configfs.rst:294: void (*drop_item)(struct config_group *group,
Documentation/filesystems/configfs.rst-295- struct config_item *item);
--
Documentation/filesystems/configfs.rst=309=Finally, when userspace calls rmdir(2) on the item or group,
Documentation/filesystems/configfs.rst:310:ct_group_ops->drop_item() is called. As a config_group is also a
Documentation/filesystems/configfs.rst-311-config_item, it is not necessary for a separate drop_group() method.
--
Documentation/filesystems/configfs.rst=313=upon item allocation. If a subsystem has no work to do, it may omit
Documentation/filesystems/configfs.rst:314:the ct_group_ops->drop_item() method, and configfs will call
Documentation/filesystems/configfs.rst-315-config_item_put() on the item on behalf of the subsystem.
--
Documentation/filesystems/configfs.rst=317=Important:
Documentation/filesystems/configfs.rst:318: drop_item() is void, and as such cannot fail. When rmdir(2)
Documentation/filesystems/configfs.rst-319- is called, configfs WILL remove the item from the filesystem tree
--
Documentation/filesystems/configfs.rst-325-
Documentation/filesystems/configfs.rst:326:When drop_item() is called, the item's linkage has already been torn
Documentation/filesystems/configfs.rst-327-down. It no longer has a reference on its parent and has no place in
--
Documentation/filesystems/configfs.rst=331=configfs has removed the item from the filesystem view but before the
Documentation/filesystems/configfs.rst:332:item is removed from its parent group. Like drop_item(),
Documentation/filesystems/configfs.rst-333-disconnect_notify() is void and cannot fail. Client subsystems should
Documentation/filesystems/configfs.rst:334:not drop any references here, as they still must do it in drop_item().
Documentation/filesystems/configfs.rst-335-
Documentation/filesystems/configfs.rst=336=A config_group cannot be removed while it still has child items. This
Documentation/filesystems/configfs.rst:337:is implemented in the configfs rmdir(2) code. ->drop_item() will not be
Documentation/filesystems/configfs.rst-338-called, as the item has not been dropped. rmdir(2) will fail, as the
--
Documentation/filesystems/configfs.rst=421=When unlink(2) is called on the symbolic link, the source item is
Documentation/filesystems/configfs.rst:422:notified via the ->drop_link() method. Like the ->drop_item() method,
Documentation/filesystems/configfs.rst-423-this is a void function and cannot return failure. The subsystem is
--
Documentation/filesystems/configfs.rst=452=time as the parent group. Similarly, they are removed at the same time
Documentation/filesystems/configfs.rst:453:as the parent. No extra notification is provided. When a ->drop_item()
Documentation/filesystems/configfs.rst-454-method call notifies the subsystem the parent group is going away, it
--
drivers/acpi/acpi_configfs.c=208=static struct config_item *acpi_table_make_item(struct config_group *group,
--
drivers/acpi/acpi_configfs.c-220-
drivers/acpi/acpi_configfs.c:221:static void acpi_table_drop_item(struct config_group *group,
drivers/acpi/acpi_configfs.c-222- struct config_item *cfg)
--
drivers/acpi/acpi_configfs.c=231=static struct configfs_group_operations acpi_table_group_ops = {
drivers/acpi/acpi_configfs.c-232- .make_item = acpi_table_make_item,
drivers/acpi/acpi_configfs.c:233: .drop_item = acpi_table_drop_item,
drivers/acpi/acpi_configfs.c-234-};
--
drivers/block/null_blk/main.c=714=static void
drivers/block/null_blk/main.c:715:nullb_group_drop_item(struct config_group *group, struct config_item *item)
drivers/block/null_blk/main.c-716-{
--
drivers/block/null_blk/main.c=761=static const struct configfs_group_operations nullb_group_ops = {
drivers/block/null_blk/main.c-762- .make_group = nullb_group_make_group,
drivers/block/null_blk/main.c:763: .drop_item = nullb_group_drop_item,
drivers/block/null_blk/main.c-764-};
--
drivers/hwtracing/stm/policy.c=273=static struct configfs_group_operations stp_policy_node_group_ops = {
drivers/hwtracing/stm/policy.c-274- .make_group = stp_policy_node_make,
drivers/hwtracing/stm/policy.c:275: .drop_item = stp_policy_node_drop,
drivers/hwtracing/stm/policy.c-276-};
--
drivers/iio/industrialio-sw-device.c=151=static const struct configfs_group_operations device_ops = {
drivers/iio/industrialio-sw-device.c-152- .make_group = &device_make_group,
drivers/iio/industrialio-sw-device.c:153: .drop_item = &device_drop_group,
drivers/iio/industrialio-sw-device.c-154-};
--
drivers/iio/industrialio-sw-trigger.c=155=static const struct configfs_group_operations trigger_ops = {
drivers/iio/industrialio-sw-trigger.c-156- .make_group = &trigger_make_group,
drivers/iio/industrialio-sw-trigger.c:157: .drop_item = &trigger_drop_group,
drivers/iio/industrialio-sw-trigger.c-158-};
--
drivers/infiniband/core/cma_configfs.c=334=static const struct configfs_group_operations cma_subsys_group_ops = {
drivers/infiniband/core/cma_configfs.c-335- .make_group = make_cma_dev,
drivers/infiniband/core/cma_configfs.c:336: .drop_item = drop_cma_dev,
drivers/infiniband/core/cma_configfs.c-337-};
--
drivers/net/netconsole.c=1651=static const struct configfs_group_operations userdata_ops = {
drivers/net/netconsole.c-1652- .make_item = userdatum_make_item,
drivers/net/netconsole.c:1653: .drop_item = userdatum_drop,
drivers/net/netconsole.c-1654-};
--
drivers/net/netconsole.c=1828=static const struct configfs_group_operations netconsole_subsys_group_ops = {
drivers/net/netconsole.c-1829- .make_group = make_netconsole_target,
drivers/net/netconsole.c:1830: .drop_item = drop_netconsole_target,
drivers/net/netconsole.c-1831-};
--
drivers/pci/endpoint/pci-ep-cfs.c=666=static const struct configfs_group_operations pci_epf_group_ops = {
drivers/pci/endpoint/pci-ep-cfs.c-667- .make_group = &pci_epf_make,
drivers/pci/endpoint/pci-ep-cfs.c:668: .drop_item = &pci_epf_drop,
drivers/pci/endpoint/pci-ep-cfs.c-669-};
--
drivers/target/target_core_configfs.c=182=static struct config_group *target_core_register_fabric(
--
drivers/target/target_core_configfs.c-264-/*
drivers/target/target_core_configfs.c:265: * Called from struct target_core_group_ops->drop_item()
drivers/target/target_core_configfs.c-266- */
--
drivers/target/target_core_configfs.c=288=static const struct configfs_group_operations target_core_fabric_group_ops = {
drivers/target/target_core_configfs.c-289- .make_group = &target_core_register_fabric,
drivers/target/target_core_configfs.c:290: .drop_item = &target_core_deregister_fabric,
drivers/target/target_core_configfs.c-291-};
--
drivers/target/target_core_configfs.c=2939=static const struct configfs_group_operations target_core_alua_lu_gps_group_ops = {
drivers/target/target_core_configfs.c-2940- .make_group = &target_core_alua_create_lu_gp,
drivers/target/target_core_configfs.c:2941: .drop_item = &target_core_alua_drop_lu_gp,
drivers/target/target_core_configfs.c-2942-};
--
drivers/target/target_core_configfs.c=3370=static const struct configfs_group_operations target_core_alua_tg_pt_gps_group_ops = {
drivers/target/target_core_configfs.c-3371- .make_group = &target_core_alua_create_tg_pt_gp,
drivers/target/target_core_configfs.c:3372: .drop_item = &target_core_alua_drop_tg_pt_gp,
drivers/target/target_core_configfs.c-3373-};
--
drivers/target/target_core_configfs.c=3411=static const struct configfs_group_operations target_core_stat_group_ops = {
drivers/target/target_core_configfs.c-3412- .make_group = &target_core_stat_mkdir,
drivers/target/target_core_configfs.c:3413: .drop_item = &target_core_stat_rmdir,
drivers/target/target_core_configfs.c-3414-};
--
drivers/target/target_core_configfs.c=3538=static const struct configfs_group_operations target_core_hba_group_ops = {
drivers/target/target_core_configfs.c-3539- .make_group = target_core_make_subdev,
drivers/target/target_core_configfs.c:3540: .drop_item = target_core_drop_subdev,
drivers/target/target_core_configfs.c-3541-};
--
drivers/target/target_core_configfs.c=3698=static const struct configfs_group_operations target_core_group_ops = {
drivers/target/target_core_configfs.c-3699- .make_group = target_core_call_addhbatotarget,
drivers/target/target_core_configfs.c:3700: .drop_item = target_core_call_delhbafromtarget,
drivers/target/target_core_configfs.c-3701-};
--
drivers/target/target_core_fabric_configfs.c=249=static const struct configfs_group_operations target_fabric_mappedlun_stat_group_ops = {
drivers/target/target_core_fabric_configfs.c-250- .make_group = target_core_mappedlun_stat_mkdir,
drivers/target/target_core_fabric_configfs.c:251: .drop_item = target_core_mappedlun_stat_rmdir,
drivers/target/target_core_fabric_configfs.c-252-};
--
drivers/target/target_core_fabric_configfs.c=352=static const struct configfs_group_operations target_fabric_nacl_base_group_ops = {
drivers/target/target_core_fabric_configfs.c-353- .make_group = target_fabric_make_mappedlun,
drivers/target/target_core_fabric_configfs.c:354: .drop_item = target_fabric_drop_mappedlun,
drivers/target/target_core_fabric_configfs.c-355-};
--
drivers/target/target_core_fabric_configfs.c=436=static const struct configfs_group_operations target_fabric_nacl_group_ops = {
drivers/target/target_core_fabric_configfs.c-437- .make_group = target_fabric_make_nodeacl,
drivers/target/target_core_fabric_configfs.c:438: .drop_item = target_fabric_drop_nodeacl,
drivers/target/target_core_fabric_configfs.c-439-};
--
drivers/target/target_core_fabric_configfs.c=502=static const struct configfs_group_operations target_fabric_np_group_ops = {
drivers/target/target_core_fabric_configfs.c-503- .make_group = &target_fabric_make_np,
drivers/target/target_core_fabric_configfs.c:504: .drop_item = &target_fabric_drop_np,
drivers/target/target_core_fabric_configfs.c-505-};
--
drivers/target/target_core_fabric_configfs.c=737=static const struct configfs_group_operations target_fabric_port_stat_group_ops = {
drivers/target/target_core_fabric_configfs.c-738- .make_group = target_core_port_stat_mkdir,
drivers/target/target_core_fabric_configfs.c:739: .drop_item = target_core_port_stat_rmdir,
drivers/target/target_core_fabric_configfs.c-740-};
--
drivers/target/target_core_fabric_configfs.c=798=static const struct configfs_group_operations target_fabric_lun_group_ops = {
drivers/target/target_core_fabric_configfs.c-799- .make_group = &target_fabric_make_lun,
drivers/target/target_core_fabric_configfs.c:800: .drop_item = &target_fabric_drop_lun,
drivers/target/target_core_fabric_configfs.c-801-};
--
drivers/target/target_core_fabric_configfs.c=1013=static const struct configfs_group_operations target_fabric_tpg_group_ops = {
drivers/target/target_core_fabric_configfs.c-1014- .make_group = target_fabric_make_tpg,
drivers/target/target_core_fabric_configfs.c:1015: .drop_item = target_fabric_drop_tpg,
drivers/target/target_core_fabric_configfs.c-1016-};
--
drivers/target/target_core_fabric_configfs.c=1179=static const struct configfs_group_operations target_fabric_wwn_group_ops = {
drivers/target/target_core_fabric_configfs.c-1180- .make_group = target_fabric_make_wwn,
drivers/target/target_core_fabric_configfs.c:1181: .drop_item = target_fabric_drop_wwn,
drivers/target/target_core_fabric_configfs.c-1182-};
--
drivers/thunderbolt/stream.c=1544=static void
drivers/thunderbolt/stream.c:1545:tbstream_dev_drop_item(struct config_group *group, struct config_item *item)
drivers/thunderbolt/stream.c-1546-{
--
drivers/thunderbolt/stream.c=1559=static struct configfs_group_operations tbstream_dev_group_ops = {
drivers/thunderbolt/stream.c-1560- .make_group = tbstream_dev_make_group,
drivers/thunderbolt/stream.c:1561: .drop_item = tbstream_dev_drop_item,
drivers/thunderbolt/stream.c-1562-};
--
drivers/usb/gadget/configfs.c=666=static const struct configfs_group_operations functions_ops = {
drivers/usb/gadget/configfs.c-667- .make_group = &function_make,
drivers/usb/gadget/configfs.c:668: .drop_item = &function_drop,
drivers/usb/gadget/configfs.c-669-};
--
drivers/usb/gadget/configfs.c=769=static const struct configfs_group_operations config_desc_ops = {
drivers/usb/gadget/configfs.c-770- .make_group = &config_desc_make,
drivers/usb/gadget/configfs.c:771: .drop_item = &config_desc_drop,
drivers/usb/gadget/configfs.c-772-};
--
drivers/usb/gadget/configfs.c=904=static const struct configfs_group_operations gadget_language_langid_group_ops = {
drivers/usb/gadget/configfs.c-905- .make_item = gadget_language_string_make,
drivers/usb/gadget/configfs.c:906: .drop_item = gadget_language_string_drop,
drivers/usb/gadget/configfs.c-907-};
--
drivers/usb/gadget/configfs.c=963=static const struct configfs_group_operations gadget_language_group_ops = {
drivers/usb/gadget/configfs.c-964- .make_group = &gadget_language_make,
drivers/usb/gadget/configfs.c:965: .drop_item = &gadget_language_drop,
drivers/usb/gadget/configfs.c-966-};
--
drivers/usb/gadget/configfs.c=1459=static const struct configfs_group_operations interf_grp_ops = {
drivers/usb/gadget/configfs.c-1460- .make_item = &ext_prop_make,
drivers/usb/gadget/configfs.c:1461: .drop_item = &ext_prop_drop,
drivers/usb/gadget/configfs.c-1462-};
--
drivers/usb/gadget/configfs.c=2063=static const struct configfs_group_operations gadgets_ops = {
drivers/usb/gadget/configfs.c-2064- .make_group = &gadgets_make,
drivers/usb/gadget/configfs.c:2065: .drop_item = &gadgets_drop,
drivers/usb/gadget/configfs.c-2066-};
--
drivers/usb/gadget/function/f_mass_storage.c=3472=static const struct configfs_group_operations fsg_group_ops = {
drivers/usb/gadget/function/f_mass_storage.c-3473- .make_group = fsg_lun_make,
drivers/usb/gadget/function/f_mass_storage.c:3474: .drop_item = fsg_lun_drop,
drivers/usb/gadget/function/f_mass_storage.c-3475-};
--
drivers/usb/gadget/function/f_midi2.c=2368=f_midi2_opts_block_make(struct config_group *group, const char *name)
--
drivers/usb/gadget/function/f_midi2.c-2395-
drivers/usb/gadget/function/f_midi2.c:2396:/* drop_item callback for a block */
drivers/usb/gadget/function/f_midi2.c-2397-static void
--
drivers/usb/gadget/function/f_midi2.c=2486=static const struct configfs_group_operations f_midi2_ep_group_ops = {
drivers/usb/gadget/function/f_midi2.c-2487- .make_group = f_midi2_opts_block_make,
drivers/usb/gadget/function/f_midi2.c:2488: .drop_item = f_midi2_opts_block_drop,
drivers/usb/gadget/function/f_midi2.c-2489-};
--
drivers/usb/gadget/function/f_midi2.c=2523=f_midi2_opts_ep_make(struct config_group *group, const char *name)
--
drivers/usb/gadget/function/f_midi2.c-2548-
drivers/usb/gadget/function/f_midi2.c:2549:/* drop_item callback for an EP */
drivers/usb/gadget/function/f_midi2.c-2550-static void
--
drivers/usb/gadget/function/f_midi2.c=2625=static const struct configfs_group_operations f_midi2_group_ops = {
drivers/usb/gadget/function/f_midi2.c-2626- .make_group = f_midi2_opts_ep_make,
drivers/usb/gadget/function/f_midi2.c:2627: .drop_item = f_midi2_opts_ep_drop,
drivers/usb/gadget/function/f_midi2.c-2628-};
--
drivers/usb/gadget/function/uvc_configfs.c=1301=static const struct configfs_group_operations uvcg_extensions_grp_ops = {
drivers/usb/gadget/function/uvc_configfs.c-1302- .make_item = uvcg_extension_make,
drivers/usb/gadget/function/uvc_configfs.c:1303: .drop_item = uvcg_extension_drop,
drivers/usb/gadget/function/uvc_configfs.c-1304-};
--
drivers/usb/gadget/function/uvc_configfs.c=2264=static const struct configfs_group_operations uvcg_uncompressed_group_ops = {
drivers/usb/gadget/function/uvc_configfs.c-2265- .make_item = uvcg_frame_make,
drivers/usb/gadget/function/uvc_configfs.c:2266: .drop_item = uvcg_frame_drop,
drivers/usb/gadget/function/uvc_configfs.c-2267-};
--
drivers/usb/gadget/function/uvc_configfs.c=2528=static const struct configfs_group_operations uvcg_mjpeg_group_ops = {
drivers/usb/gadget/function/uvc_configfs.c-2529- .make_item = uvcg_frame_make,
drivers/usb/gadget/function/uvc_configfs.c:2530: .drop_item = uvcg_frame_drop,
drivers/usb/gadget/function/uvc_configfs.c-2531-};
--
drivers/usb/gadget/function/uvc_configfs.c=2718=static const struct configfs_group_operations uvcg_framebased_group_ops = {
drivers/usb/gadget/function/uvc_configfs.c-2719- .make_item = uvcg_frame_make,
drivers/usb/gadget/function/uvc_configfs.c:2720: .drop_item = uvcg_frame_drop,
drivers/usb/gadget/function/uvc_configfs.c-2721-};
--
drivers/virt/coco/guest/report.c=422=static struct config_item *tsm_report_make_item(struct config_group *group,
--
drivers/virt/coco/guest/report.c-439-
drivers/virt/coco/guest/report.c:440:static void tsm_report_drop_item(struct config_group *group, struct config_item *item)
drivers/virt/coco/guest/report.c-441-{
--
drivers/virt/coco/guest/report.c=446=static struct configfs_group_operations tsm_report_group_ops = {
drivers/virt/coco/guest/report.c-447- .make_item = tsm_report_make_item,
drivers/virt/coco/guest/report.c:448: .drop_item = tsm_report_drop_item,
drivers/virt/coco/guest/report.c-449-};
--
fs/configfs/dir.c=938=static void client_disconnect_notify(struct config_item *parent_item,
--
fs/configfs/dir.c-956- */
fs/configfs/dir.c:957:static void client_drop_item(struct config_item *parent_item,
fs/configfs/dir.c-958- struct config_item *item)
--
fs/configfs/dir.c-965- /*
fs/configfs/dir.c:966: * If ->drop_item() exists, it is responsible for the
fs/configfs/dir.c-967- * config_item_put().
fs/configfs/dir.c-968- */
fs/configfs/dir.c:969: if (type->ct_group_ops && type->ct_group_ops->drop_item)
fs/configfs/dir.c:970: type->ct_group_ops->drop_item(to_config_group(parent_item),
fs/configfs/dir.c-971- item);
--
fs/configfs/dir.c=1289=static struct dentry *configfs_mkdir(struct mnt_idmap *idmap, struct inode *dir,
--
fs/configfs/dir.c-1442- unlink_obj(item);
fs/configfs/dir.c:1443: client_drop_item(parent_item, item);
fs/configfs/dir.c-1444-
--
fs/configfs/dir.c=1469=static int configfs_rmdir(struct inode *dir, struct dentry *dentry)
--
fs/configfs/dir.c-1566-
fs/configfs/dir.c:1567: client_drop_item(parent_item, item);
fs/configfs/dir.c-1568- mutex_unlock(&subsys->su_mutex);
--
fs/configfs/symlink.c=213=int configfs_unlink(struct inode *dir, struct dentry *dentry)
--
fs/configfs/symlink.c-237- * decrementing target's ->s_links, so that the order of
fs/configfs/symlink.c:238: * drop_link(this, target) and drop_item(target) is preserved.
fs/configfs/symlink.c-239- */
--
fs/dlm/config.c=327=static const struct configfs_group_operations clusters_ops = {
fs/dlm/config.c-328- .make_group = make_cluster,
fs/dlm/config.c:329: .drop_item = drop_cluster,
fs/dlm/config.c-330-};
--
fs/dlm/config.c=336=static const struct configfs_group_operations spaces_ops = {
fs/dlm/config.c-337- .make_group = make_space,
fs/dlm/config.c:338: .drop_item = drop_space,
fs/dlm/config.c-339-};
--
fs/dlm/config.c=345=static const struct configfs_group_operations comms_ops = {
fs/dlm/config.c-346- .make_item = make_comm,
fs/dlm/config.c:347: .drop_item = drop_comm,
fs/dlm/config.c-348-};
--
fs/dlm/config.c=354=static const struct configfs_group_operations nodes_ops = {
fs/dlm/config.c-355- .make_item = make_node,
fs/dlm/config.c:356: .drop_item = drop_node,
fs/dlm/config.c-357-};
--
fs/ocfs2/cluster/heartbeat.c=909=static void o2hb_set_quorum_device(struct o2hb_region *reg)
--
fs/ocfs2/cluster/heartbeat.c-913-
fs/ocfs2/cluster/heartbeat.c:914: /* Prevent race with o2hb_heartbeat_group_drop_item() */
fs/ocfs2/cluster/heartbeat.c-915- if (kthread_should_stop())
--
fs/ocfs2/cluster/heartbeat.c=1583=static void o2hb_unmap_slot_data(struct o2hb_region *reg)
--
fs/ocfs2/cluster/heartbeat.c-1606-
fs/ocfs2/cluster/heartbeat.c:1607:/* drop_item only drops its ref after killing the thread, nothing should
fs/ocfs2/cluster/heartbeat.c-1608- * be using the region anymore. this has to clean up any state that
--
fs/ocfs2/cluster/heartbeat.c=1870=static ssize_t o2hb_region_dev_store(struct config_item *item,
--
fs/ocfs2/cluster/heartbeat.c-1998-
fs/ocfs2/cluster/heartbeat.c:1999: /* Ok, we were woken. Make sure it wasn't by drop_item() */
fs/ocfs2/cluster/heartbeat.c-2000- spin_lock(&o2hb_live_lock);
--
fs/ocfs2/cluster/heartbeat.c=2119=static struct config_item *o2hb_heartbeat_group_make_item(struct config_group *group,
--
fs/ocfs2/cluster/heartbeat.c-2193-
fs/ocfs2/cluster/heartbeat.c:2194:static void o2hb_heartbeat_group_drop_item(struct config_group *group,
fs/ocfs2/cluster/heartbeat.c-2195- struct config_item *item)
--
fs/ocfs2/cluster/heartbeat.c=2326=static const struct configfs_group_operations o2hb_heartbeat_group_group_ops = {
fs/ocfs2/cluster/heartbeat.c-2327- .make_item = o2hb_heartbeat_group_make_item,
fs/ocfs2/cluster/heartbeat.c:2328: .drop_item = o2hb_heartbeat_group_drop_item,
fs/ocfs2/cluster/heartbeat.c-2329-};
--
fs/ocfs2/cluster/nodemanager.c=587=static struct config_item *o2nm_node_group_make_item(struct config_group *group,
--
fs/ocfs2/cluster/nodemanager.c-607-
fs/ocfs2/cluster/nodemanager.c:608:static void o2nm_node_group_drop_item(struct config_group *group,
fs/ocfs2/cluster/nodemanager.c-609- struct config_item *item)
--
fs/ocfs2/cluster/nodemanager.c=646=static const struct configfs_group_operations o2nm_node_group_group_ops = {
fs/ocfs2/cluster/nodemanager.c-647- .make_item = o2nm_node_group_make_item,
fs/ocfs2/cluster/nodemanager.c:648: .drop_item = o2nm_node_group_drop_item,
fs/ocfs2/cluster/nodemanager.c-649-};
--
fs/ocfs2/cluster/nodemanager.c=691=static struct config_group *o2nm_cluster_group_make_group(struct config_group *group,
--
fs/ocfs2/cluster/nodemanager.c-737-
fs/ocfs2/cluster/nodemanager.c:738:static void o2nm_cluster_group_drop_item(struct config_group *group, struct config_item *item)
fs/ocfs2/cluster/nodemanager.c-739-{
--
fs/ocfs2/cluster/nodemanager.c=749=static const struct configfs_group_operations o2nm_cluster_group_group_ops = {
fs/ocfs2/cluster/nodemanager.c-750- .make_group = o2nm_cluster_group_make_group,
fs/ocfs2/cluster/nodemanager.c:751: .drop_item = o2nm_cluster_group_drop_item,
fs/ocfs2/cluster/nodemanager.c-752-};
--
include/linux/configfs.h=218=struct configfs_group_operations {
--
include/linux/configfs.h-221- void (*disconnect_notify)(struct config_group *group, struct config_item *item);
include/linux/configfs.h:222: void (*drop_item)(struct config_group *group, struct config_item *item);
include/linux/configfs.h-223- bool (*is_visible)(struct config_item *item, struct configfs_attribute *attr, int n);
--
include/linux/usb/gadget_configfs.h=89=static const struct configfs_group_operations struct_in##_strings_ops = { \
include/linux/usb/gadget_configfs.h-90- .make_group = &struct_in##_strings_make, \
include/linux/usb/gadget_configfs.h:91: .drop_item = &struct_in##_strings_drop, \
include/linux/usb/gadget_configfs.h-92-}; \
--
kernel/crash_dump_dm_crypt.c=306=static struct configfs_attribute *config_keys_attrs[] = {
--
kernel/crash_dump_dm_crypt.c-312-/*
kernel/crash_dump_dm_crypt.c:313: * Note that, since no extra work is required on ->drop_item(),
kernel/crash_dump_dm_crypt.c:314: * no ->drop_item() is provided.
kernel/crash_dump_dm_crypt.c-315- */
--
rust/kernel/configfs.rs=325=impl<Parent, Child> GroupOperationsVTable<Parent, Child>
--
rust/kernel/configfs.rs-388- /// `bindings::config_group` within a `Group<Child>`.
rust/kernel/configfs.rs:389: unsafe extern "C" fn drop_item(
rust/kernel/configfs.rs-390- this: *mut bindings::config_group,
--
rust/kernel/configfs.rs-408-
rust/kernel/configfs.rs:409: Parent::drop_item(parent_data, arc.as_arc_borrow());
rust/kernel/configfs.rs-410- arc.into_raw();
--
rust/kernel/configfs.rs-421- disconnect_notify: None,
rust/kernel/configfs.rs:422: drop_item: Some(Self::drop_item),
rust/kernel/configfs.rs-423- is_visible: None,
--
rust/kernel/configfs.rs=486=pub trait GroupOperations {
--
rust/kernel/configfs.rs-512- /// name is inherited from the callback name in the underlying C code.
rust/kernel/configfs.rs:513: fn drop_item(&self, _child: ArcBorrow<'_, Group<Self::Child>>) {
rust/kernel/configfs.rs-514- kernel::build_error!(kernel::error::VTABLE_DEFAULT_ERROR)
--
samples/configfs/configfs_sample.c=218=static const struct configfs_item_operations simple_children_item_ops = {
--
samples/configfs/configfs_sample.c-222-/*
samples/configfs/configfs_sample.c:223: * Note that, since no extra work is required on ->drop_item(),
samples/configfs/configfs_sample.c:224: * no ->drop_item() is provided.
samples/configfs/configfs_sample.c-225- */
--
samples/configfs/configfs_sample.c=286=static struct configfs_attribute *group_children_attrs[] = {
--
samples/configfs/configfs_sample.c-291-/*
samples/configfs/configfs_sample.c:292: * Note that, since no extra work is required on ->drop_item(),
samples/configfs/configfs_sample.c:293: * no ->drop_item() is provided.
samples/configfs/configfs_sample.c-294- */
]
|
| 154/2 |
2026/09/02 21:17 |
llm |
debugger |
0m
Model:
Tokens:
input: 141657
output: 36
thoughts: 3141
Thoughts: **Analyzing and Fixing the ConfigFS Symlink Vulnerability**
Okay, here's what's running through my head: I was initially thrown by the thought that `target_core_fabric_configfs.c` *does* have `drop_item` functions, such as `target_fabric_drop_tpg` and `target_fabric_drop_lun`. And yes, those functions call `configfs_remove_default_groups`! So, `target_core` *does* correctly remove its default group references during item deletion. That's a check mark.
However, I'm now realizing this potentially sets up a use-after-free scenario. If a `configfs_symlink` holds a reference to a default group, and then the parent (like a LUN) is removed, `configfs_remove_default_groups` drops the *initial* reference, and ultimately `config_item_put` will decrement `lun`'s reference count. Then, we are left with the symlink referencing a group belonging to the now-freed `lun` memory, if `lun`'s refcount drops to 0. It is starting to look like `target_core` also has the same problem. This could extend to `nvmet` and `usb/gadget`. The root cause is likely an incompatibility between the way `configfs_symlink` handles references and the existence of embedded default groups. This is a significant issue.
I need to come up with a fix for `configfs_symlink`. Looking at the code for `configfs_symlink`, I'm focusing on that crucial section where it obtains a reference to the parent item, drops the lock, and then tries to link to a target.
Here's a thought: What if we *don't* hold a reference to the parent item while the lock is dropped? I'm thinking, if we drop the reference before releasing the lock, then re-acquire the reference after re-acquiring the lock, then any concurrent `configfs_rmdir` operation could remove the parent while the lock is dropped. If that happens, after we re-acquire the lock, then we can check if the directory was removed. If it was, we simply return an error, rather than trying to get the parent item. That avoids any use-after-free. This seems like a promising path.
Now, let's look at the implementation: I see the code calls `config_item_put(parent_item)` before unlocking, and `parent_item = NULL` is set to drop the reference. Then, `inode_unlock(dir)` releases the lock. The code then reacquires the lock with `inode_lock(dir)`. It checks if the directory was removed by checking the `frag->frag_dead` flag. If it was, it returns `-ENOENT`, which is correct.
Now, I'm thinking, does the `dentry->d_parent->d_fsdata` check for validity cause any issue, given the lock drop? We have to look at how the VFS and ConfigFS interact. A deep dive is in order.
The code uses `dentry->d_parent->d_fsdata`, which is the `configfs_dirent`. If `configfs_remove_dir` is called on the directory that is pointed to by `dentry->d_parent`, then `configfs_remove_dirent` is called, and the `configfs_dirent` is removed.
However, `configfs_remove_dirent` only removes the `configfs_dirent` from the sibling list and calls `configfs_put(sd)`. `configfs_put` will decrease `sd` refcount. So, there is a race there: If the refcount drops to zero *before* we check `sd->s_frag->frag_dead`, we might access freed memory!
However, the VFS will hold a reference to `dentry->d_parent`, and `dentry->d_fsdata` is therefore kept alive, so `sd` is safe to access. I have to verify that the `dentry` and `configfs_dirent`'s lifetime is tied together properly. Ah, I see `configfs_d_iput`, which is called when the dentry is freed. And it calls `configfs_put(sd)`. So `sd` is kept alive as long as the dentry is alive. That means `dentry->d_parent->d_fsdata` is safe! Also, `sd->s_frag` is safe to access because `frag` is freed when its refcount drops to 0.
So, it looks like this proposed fix is solid. By temporarily dropping the reference to the parent item
|
| 155/2 |
2026/09/02 21:18 |
tool |
read-file |
0m
Args: map[File:fs/configfs/dir.c FirstLine:200 LineCount:20]
Results: map[Contents: 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);
]
|
| 156/2 |
2026/09/02 21:18 |
llm |
debugger |
0m
Model:
Tokens:
input: 146236
output: 37
thoughts: 749
Thoughts: **My Analysis of a Potential Use-After-Free Vulnerability**
Okay, so I've got this `sd` struct, and I'm assigning `get_fragment(frag)` to `sd->s_frag`. So `sd` is now holding a pointer to `frag`. I know that `sd` is kept alive because of `dentry->d_parent`. The cool thing is that means that checking `sd->s_frag->frag_dead` is perfectly safe, I won't have any use-after-free errors. That feels good, that feels correct.
Now, a tricky point. I'm wondering if there's an issue with dropping `parent_item` before `inode_unlock(dir)`. If I drop it before the unlock, it's refcount could drop to 0 and get freed. But wait, `parent_item` is the directory where I'm *creating* the symlink. If the directory is still in the filesystem tree, which it must be because I'm holding the `dir` lock, then its refcount can't possibly drop to zero. Okay, so that's good. Now, if it's the default group or a user-created group, its refcount is at least 1, the initial reference. So dropping the reference from `configfs_get_config_item` won't free it. It's only freed if `configfs_rmdir` removes it. And even if `configfs_rmdir` removes it while I've dropped the lock, it's safe because I don't have a reference to it anymore. Then, when I re-acquire the lock, I check `frag->frag_dead`. If it's true, I know it was removed and I don't try to get a reference again. That completely avoids the use-after-free issue. That makes a lot of sense!
Let me see if I can use `IS_DEADDIR(dir)` instead of `frag->frag_dead` in `configfs_symlink`. `IS_DEADDIR(dir)` just checks if `dir->i_flags & S_DEAD` is set. Okay, let's see how `S_DEAD` is set. Ah, in `configfs_detach_group`, it *does* set `S_DEAD` on the directory's inode. That's good, so `IS_DEADDIR(dir)` is exactly what I need and it's using standard VFS mechanisms! Now let me check `configfs_detach_item`. Wait, `configfs_detach_item` itself doesn't set `S_DEAD`, it's the caller that does. Hmmm. Okay, I need to check `configfs_rmdir`. Wait, actually `configfs_rmdir` doesn't set `S_DEAD` on `dentry`! Okay, I need to check `configfs_rmdir` again.
|
| 157/2 |
2026/09/02 21:18 |
tool |
read-file |
0m
Args: map[File:fs/configfs/dir.c FirstLine:1540 LineCount:30]
Results: map[Contents: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:
]
|
| 158/2 |
2026/09/02 21:18 |
llm |
debugger |
0m
Model:
Tokens:
input: 148099
output: 37
thoughts: 1726
Thoughts: **Analyzing a Potential Use-After-Free Vulnerability in configfs**
Okay, so I've been digging into this `configfs` code, and I think I've spotted a potential use-after-free issue. It stems from how directories are removed and how symlinks are created. Initially, I was concerned because `configfs_rmdir` doesn't set `S_DEAD` on the item being removed directly. However, it does call `configfs_detach_group`, which *does* set `S_DEAD` on the default groups. But crucially, *not* the item itself. The problem arises if `configfs_symlink` is called on the removed item directly, `IS_DEADDIR` will be false. However, then I saw that `configfs_rmdir` sets `frag->frag_dead = true`! So, whenever the item is removed, `frag->frag_dead` is *always* true! Perfect, `frag->frag_dead` is the reliable flag to check for directory removal.
Alright, let's break down the `configfs_symlink` function and verify the fix:
```c
int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
struct dentry *dentry, const char *symname)
{
int ret;
struct config_item *parent_item;
struct config_item *target_item = NULL;
const struct config_item_type *type;
parent_item = configfs_get_config_item(dentry->d_parent);
type = parent_item->ci_type;
ret = -EPERM;
if (!type || !type->ct_item_ops ||
!type->ct_item_ops->allow_link)
goto out_put;
/*
* Drop the reference on parent_item before unlocking dir.
* We will re-acquire it after locking dir, if the directory
* was not removed in the meantime.
*/
config_item_put(parent_item);
parent_item = NULL;
inode_unlock(dir);
ret = get_target(symname, &target_item, dentry->d_sb);
inode_lock(dir);
if (ret)
goto out_put;
if (dentry->d_inode || d_unhashed(dentry))
ret = -EEXIST;
else
ret = inode_permission(&nop_mnt_idmap, dir,
MAY_WRITE | MAY_EXEC);
if (!ret) {
struct configfs_dirent *sd = dentry->d_parent->d_fsdata;
if (sd->s_frag->frag_dead) {
ret = -ENOENT;
} else {
parent_item = configfs_get_config_item(dentry->d_parent);
ret = type->ct_item_ops->allow_link(parent_item, target_item);
}
}
if (!ret) {
mutex_lock(&configfs_symlink_mutex);
ret = create_link(parent_item, target_item, dentry);
mutex_unlock(&configfs_symlink_mutex);
if (ret && type->ct_item_ops->drop_link)
type->ct_item_ops->drop_link(parent_item,
target_item);
}
config_item_put(target_item);
out_put:
if (parent_item)
config_item_put(parent_item);
return ret;
}
```
Let's check the code path: First, if `get_target` fails, `ret` is non-zero, and we jump to `out_put`. `parent_item` is `NULL`, so the `config_item_put` is safe. Good. If `get_target` succeeds, then `target_item` is set. If `dentry->d_inode || d_unhashed(dentry)` is true, `ret = -EEXIST`, then it correctly skips checking the rest of the code path to avoid the double put, which is important to consider. If `inode_permission` fails, `ret` is non-zero, so it skips the later code paths. If `sd->s_frag->frag_dead` is true, then we return `-ENOENT`. Then we skip the rest, which is correct. If `sd
|
| 159/2 |
2026/09/02 21:19 |
tool |
read-file |
0m
Args: map[File:fs/configfs/dir.c FirstLine:1330 LineCount:50]
Results: map[Contents:1330: /* Get a working ref for the duration of this function */
1331: parent_item = configfs_get_config_item(dentry->d_parent);
1332: type = parent_item->ci_type;
1333: subsys = to_config_group(parent_item)->cg_subsys;
1334: BUG_ON(!subsys);
1335:
1336: if (!type || !type->ct_group_ops ||
1337: (!type->ct_group_ops->make_group &&
1338: !type->ct_group_ops->make_item)) {
1339: ret = -EPERM; /* Lack-of-mkdir returns -EPERM */
1340: goto out_put;
1341: }
1342:
1343: /*
1344: * The subsystem may belong to a different module than the item
1345: * being created. We don't want to safely pin the new item but
1346: * fail to pin the subsystem it sits under.
1347: */
1348: if (!subsys->su_group.cg_item.ci_type) {
1349: ret = -EINVAL;
1350: goto out_put;
1351: }
1352: subsys_owner = subsys->su_group.cg_item.ci_type->ct_owner;
1353: if (!try_module_get(subsys_owner)) {
1354: ret = -EINVAL;
1355: goto out_put;
1356: }
1357:
1358: mutex_lock(&subsys->su_mutex);
1359: if (type->ct_group_ops->make_group) {
1360: group = type->ct_group_ops->make_group(to_config_group(parent_item), name);
1361: if (!group)
1362: group = ERR_PTR(-ENOMEM);
1363: if (!IS_ERR(group)) {
1364: link_group(to_config_group(parent_item), group);
1365: item = &group->cg_item;
1366: } else
1367: ret = PTR_ERR(group);
1368: } else {
1369: item = type->ct_group_ops->make_item(to_config_group(parent_item), name);
1370: if (!item)
1371: item = ERR_PTR(-ENOMEM);
1372: if (!IS_ERR(item))
1373: link_obj(parent_item, item);
1374: else
1375: ret = PTR_ERR(item);
1376: }
1377: mutex_unlock(&subsys->su_mutex);
1378:
1379: if (ret) {
]
|
| 160/2 |
2026/09/02 21:19 |
llm |
debugger |
0m
Model:
Tokens:
input: 151703
output: 37
thoughts: 101
Thoughts: **My Immediate Thoughts on Configfs and Locking**
Okay, hold on a second. I just realized something critical about `configfs_mkdir`. I was under the impression it released the `dir` lock, but that's not the case at all! Where did I get that idea? Did I misread the code? Let me think...
Oh, right! I was digging into `configfs_depend_item_unlocked` earlier. My brain must have gotten wires crossed. But now it's clear: `configfs_mkdir` **does not** drop the `dir` lock. So, based on what I can see right now, `configfs_mkdir` is actually safe in the context of my current understanding.
Now, the question that is top of mind is what about `configfs_depend_item_unlocked`?
|
| 161/2 |
2026/09/02 21:19 |
tool |
read-file |
0m
Args: map[File:fs/configfs/dir.c FirstLine:1260 LineCount:30]
Results: map[Contents:1260:
1261: /*
1262: * As we are trying to depend item from other subsystem
1263: * we have to check if this subsystem is still registered
1264: */
1265: subsys_sd = configfs_find_subsys_dentry(
1266: root->cg_item.ci_dentry->d_fsdata,
1267: &target_subsys->su_group.cg_item);
1268: if (!subsys_sd)
1269: goto out_root_unlock;
1270: } else {
1271: subsys_sd = target_subsys->su_group.cg_item.ci_dentry->d_fsdata;
1272: }
1273:
1274: /* Now we can execute core of depend item */
1275: ret = configfs_do_depend_item(subsys_sd, target);
1276:
1277: if (target_subsys != caller_subsys)
1278: out_root_unlock:
1279: /*
1280: * We were called from subsystem other than our target so we
1281: * took some locks so now it's time to release them
1282: */
1283: inode_unlock(d_inode(root->cg_item.ci_dentry));
1284:
1285: return ret;
1286: }
1287: EXPORT_SYMBOL(configfs_depend_item_unlocked);
1288:
1289: static struct dentry *configfs_mkdir(struct mnt_idmap *idmap, struct inode *dir,
]
|
| 162/2 |
2026/09/02 21:19 |
llm |
debugger |
0m
Model:
Tokens:
input: 152494
output: 26
thoughts: 1792
Thoughts: **Analyzing a Potential Use-After-Free Vulnerability in configfs_symlink**
Okay, so I'm digging into this `configfs_symlink` function, and it seems like there might be a subtle but critical race condition. My initial thought process is centered around the lock acquisition and release within this function. The core concern revolves around the `dir` lock and its implications for how long `parent_item` is valid and accessible. Specifically, the fact that `configfs_symlink` *only* drops the `dir` lock while holding a reference to `parent_item` is screaming "potential vulnerability" at me.
I need to understand the fix and the possible conditions. The patch itself is interesting: the main change is that they are immediately dropping the `parent_item` reference, which initially struck me as a good thing. I realize, that in case of a failure during the link creation, we need to re-acquire the `parent_item` reference. However, the `config_item_put(NULL)` will be a no-op, which means we do not need to check for `NULL`.
Now, I'm worrying about the `type` pointer, which is derived from `parent_item`. `type` is typically a struct from a module, so the module must stay loaded. I'm focusing on whether the module owning `type` can be unloaded while we have a dangling reference to it through `parent_item` or, more importantly, after we drop the `dir` lock. A key question is whether `configfs_symlink` holds a module reference. Nope, it does not. However, the VFS keeps a reference to the directory, so that should prevent the module from being unloaded. The lock drops are still worrying me, so the issue that is addressed is to check for the `frag_dead` flag. If the flag is set, the module cannot be unloaded.
The fix introduced an explicit check for `frag_dead`. It's a way to detect whether the directory has been removed, in which case the module's reference might be gone. So if `frag_dead` is true, we exit early with an error, preventing access to the now-potentially-invalid `type`. If `frag_dead` is false, it means the directory is still valid, and the module reference is safe. Even if `create_link` fails and `drop_link` is called, the directory is still alive if `frag_dead` is false, so `type` is safe there as well.
I'm now satisfied that the current logic correctly handles the potential for module unloading. Now, I have to ensure there are no additional issues with the `dentry->d_parent`. The VFS holds a reference to that.
The vulnerability stems from the use-after-free situation, in this case, `type->ct_item_ops->allow_link` in `configfs_symlink`, when `frag_dead` is false, meaning the directory has not been removed, but has already been freed. My analysis of the code confirms the potential for a use-after-free.
I need to make sure the crash report matches. I see that the crash in `nvmet_port_subsys_allow_link` matches perfectly. The stack trace indicates the exact line where the crash occurs in `configfs_symlink`.
I'm starting to get to the root cause of this: The key to this bug is a use-after-free, where a module structure (`nvmet_port` likely in this case) is freed, and the code later attempts to access it through a pointer stored in the freed memory. The code accesses a struct through `type`, which is a pointer to the freed memory. Looking at the KASAN report, I need to check the exact allocation to understand what is being accessed.
|
| 163/2 |
2026/09/02 21:19 |
tool |
grepper |
0m
Args: map[Expression:#define NVMET_MAX_ANAGRPS]
Results: map[Output:drivers/nvme/target/nvmet.h=674=void nvmet_add_async_event(struct nvmet_ctrl *ctrl, u8 event_type,
--
drivers/nvme/target/nvmet.h-694- */
drivers/nvme/target/nvmet.h:695:#define NVMET_MAX_ANAGRPS 128
drivers/nvme/target/nvmet.h-696-#define NVMET_DEFAULT_ANA_GRPID 1
]
|
| 164/2 |
2026/09/02 21:19 |
llm |
debugger |
0m
Model:
Tokens:
input: 155263
output: 83
thoughts: 1459
Thoughts: **Root Cause Analysis of the NVMe-oF Crash**
Okay, let's break this down. First, I confirmed the size calculations. `NVMET_MAX_ANAGRPS` is 128, so the initial calculations for the ancillary data (516 bytes) and the approximate size of `struct nvmet_port` (1828 bytes) are correct. Added together (2344 bytes) plus slab padding, that neatly fits within the `kmalloc-4k` cache, just as the crash report indicates.
Next, I needed to understand the offset of `parent->ci_parent`. Looking at the `nvmet_port` structure, `subsys_group` seems to be the key element. After re-evaluating the offsets, using the sizes of `nvmf_disc_rsp_page_entry`, `config_group`, and `config_item`, I precisely calculated the offset of `parent->ci_parent` within the `nvmet_port` structure. The offset is 1224 bytes, and that *exactly* aligns with the information in the crash report. This confirms my understanding of how the structures are laid out.
The core of the problem stems from a race condition within the `configfs` framework. `configfs_symlink` holds a reference to `parent_item` (which is `subsys_group.cg_item`) but *temporarily* drops the directory lock. During that unprotected window, `configfs_rmdir` can potentially remove the parent directory, `ports/1`. When `configfs_rmdir` is executed, it calls `unlink_group`, which removes `subsys_group`, dropping its reference to the directory. This frees the memory associated with `subsys_group`, which is embedded in the freed directory.
When the lock is re-acquired, `configfs_symlink` then uses `parent_item`, which points to the freed memory. The `allow_link` call reads from the freed memory. Additionally, `configfs_symlink` calls `config_item_put(parent_item)`, causing another access to the freed memory, leading to the crash.
The proposed fix is clear: drop the reference to `parent_item` *before* releasing the directory lock, and then re-acquire the reference *after* re-acquiring the lock. We can check if the directory was removed using `sd->s_frag->frag_dead`. Now, I need to finalize the explanation.
|
| 165/2 |
2026/09/02 21:19 |
tool |
codeexpert |
2m
Args: map[Question:Can you verify if `configfs_symlink` dropping the reference to `parent_item` before `inode_unlock(dir)` and re-acquiring it after `inode_lock(dir)` is a safe and complete fix for the use-after-free caused by `configfs_rmdir` freeing the parent object?]
Results: map[Answer:Dropping the reference to `parent_item` before `inode_unlock(dir)` and re-acquiring it after `inode_lock(dir)` is **neither safe nor complete**.
### 1. Why it is not safe (Introduces an immediate Use-After-Free)
* In `configfs_symlink()`, if the reference to `parent_item` is dropped prior to `inode_unlock(dir)`:
* When a concurrent `rmdir` executes on `dir`, `configfs_rmdir()` detaches the item and drops its own reference with `config_item_put(item)`.
* With no other references remaining, the `parent_item`'s refcount drops to zero and its release callback runs (freeing the memory of `parent_item` and its container struct via `kfree`).
* When `configfs_symlink()` re-locks `dir` (`inode_lock(dir)`) and attempts to re-acquire the item via `configfs_get_config_item(dentry->d_parent)`:
```c
static inline struct config_item *configfs_get_config_item(struct dentry *dentry)
{
struct config_item * item = NULL;
spin_lock(&dentry->d_lock);
if (!d_unhashed(dentry)) {
struct configfs_dirent * sd = dentry->d_fsdata;
item = config_item_get(sd->s_element);
}
spin_unlock(&dentry->d_lock);
return item;
}
```
* In `vfs_rmdir()`, `inode_unlock(dentry->d_inode)` is called *before* `d_delete(dentry)`. Consequently, at the point `configfs_symlink()` re-acquires `inode_lock(dir)`, `dentry->d_parent` may still not be unhashed (`!d_unhashed(dentry->d_parent)` is true).
* `sd->s_element` is now a dangling pointer to the already-freed `parent_item`. Calling `config_item_get(sd->s_element)` (which does `kref_get(&item->ci_kref)`) dereferences and increments the refcount of freed memory, causing an immediate use-after-free and memory corruption.
### 2. Why it is not complete (Fails to handle deletion/lifecycle state)
* Dropping and re-acquiring the reference does nothing to detect or handle whether the parent directory was deleted while `dir` was unlocked.
* Even if an external reference kept `parent_item` memory alive, `configfs_rmdir()` will have already:
* Unlinked the item (`unlink_group()` / `unlink_obj()`).
* Notified and torn down driver structures via `client_drop_item(parent_item, item)`.
* Marked the directory inode as dead (`dir->i_flags |= S_DEAD`).
* Merely re-fetching `parent_item` without validating directory state will cause `configfs_symlink()` to continue execution:
* Calling `type->ct_item_ops->allow_link(parent_item, target_item)` on an object that the subsystem/driver already tore down in `drop_item()` (causing driver-level UAF or corruption).
* Calling `create_link()` and attaching a symlink into an orphaned/dead directory hierarchy.
* Calling `type->ct_item_ops->drop_link()` on failure on a dropped object.
### 3. What the proper fix requires
1. **Hold the reference across the unlock/relock**: `configfs_symlink()` must keep its reference to `parent_item` to pin the memory structure.
2. **Revalidate parent state after `inode_lock(dir)`**: After re-acquiring `inode_lock(dir)`, check whether the parent directory was deleted or is dropping during the unlocked window (e.g., checking `IS_DEADDIR(dir)`, `d_unhashed(dentry->d_parent)`, or `CONFIGFS_USET_DROPPING` on the parent's `configfs_dirent` under `configfs_dirent_lock`). If dead/dropping, `configfs_symlink()` must abort early with an error (such as `-ENOENT`) and release the reference.]
|
| 166/3 |
2026/09/02 21:19 |
agent |
codeexpert |
2m
Model:
Results: map[Answer:Dropping the reference to `parent_item` before `inode_unlock(dir)` and re-acquiring it after `inode_lock(dir)` is **neither safe nor complete**.
### 1. Why it is not safe (Introduces an immediate Use-After-Free)
* In `configfs_symlink()`, if the reference to `parent_item` is dropped prior to `inode_unlock(dir)`:
* When a concurrent `rmdir` executes on `dir`, `configfs_rmdir()` detaches the item and drops its own reference with `config_item_put(item)`.
* With no other references remaining, the `parent_item`'s refcount drops to zero and its release callback runs (freeing the memory of `parent_item` and its container struct via `kfree`).
* When `configfs_symlink()` re-locks `dir` (`inode_lock(dir)`) and attempts to re-acquire the item via `configfs_get_config_item(dentry->d_parent)`:
```c
static inline struct config_item *configfs_get_config_item(struct dentry *dentry)
{
struct config_item * item = NULL;
spin_lock(&dentry->d_lock);
if (!d_unhashed(dentry)) {
struct configfs_dirent * sd = dentry->d_fsdata;
item = config_item_get(sd->s_element);
}
spin_unlock(&dentry->d_lock);
return item;
}
```
* In `vfs_rmdir()`, `inode_unlock(dentry->d_inode)` is called *before* `d_delete(dentry)`. Consequently, at the point `configfs_symlink()` re-acquires `inode_lock(dir)`, `dentry->d_parent` may still not be unhashed (`!d_unhashed(dentry->d_parent)` is true).
* `sd->s_element` is now a dangling pointer to the already-freed `parent_item`. Calling `config_item_get(sd->s_element)` (which does `kref_get(&item->ci_kref)`) dereferences and increments the refcount of freed memory, causing an immediate use-after-free and memory corruption.
### 2. Why it is not complete (Fails to handle deletion/lifecycle state)
* Dropping and re-acquiring the reference does nothing to detect or handle whether the parent directory was deleted while `dir` was unlocked.
* Even if an external reference kept `parent_item` memory alive, `configfs_rmdir()` will have already:
* Unlinked the item (`unlink_group()` / `unlink_obj()`).
* Notified and torn down driver structures via `client_drop_item(parent_item, item)`.
* Marked the directory inode as dead (`dir->i_flags |= S_DEAD`).
* Merely re-fetching `parent_item` without validating directory state will cause `configfs_symlink()` to continue execution:
* Calling `type->ct_item_ops->allow_link(parent_item, target_item)` on an object that the subsystem/driver already tore down in `drop_item()` (causing driver-level UAF or corruption).
* Calling `create_link()` and attaching a symlink into an orphaned/dead directory hierarchy.
* Calling `type->ct_item_ops->drop_link()` on failure on a dropped object.
### 3. What the proper fix requires
1. **Hold the reference across the unlock/relock**: `configfs_symlink()` must keep its reference to `parent_item` to pin the memory structure.
2. **Revalidate parent state after `inode_lock(dir)`**: After re-acquiring `inode_lock(dir)`, check whether the parent directory was deleted or is dropping during the unlocked window (e.g., checking `IS_DEADDIR(dir)`, `d_unhashed(dentry->d_parent)`, or `CONFIGFS_USET_DROPPING` on the parent's `configfs_dirent` under `configfs_dirent_lock`). If dead/dropping, `configfs_symlink()` must abort early with an error (such as `-ENOENT`) and release the reference.]
Instruction:
You are a capable Linux kernel developer tasked with researching complex questions
about kernel source code. You will be given a concrete question, and need to provide
a concrete answer.
Use tools extensively while researching the question. Don't make assumptions,
or rely on your previous knowledge about the kernel source code, use available tools
to access the actual source code.
Use all available sources of information:
- kernel source code
- documentation in the Documentation dir in the source tree
- git commits descriptions, git blame
Do not guess file names or file paths and attempt to read them without
verifying their existence first using content search or directory listing tools.
If a file, symbol, or directory is not found via content search (grepper) or
directory listing (codesearch-dir-index), treat it as completely absent.
Do not attempt to guess alternative names, extensions, or directories.
Tool Selection Guidelines:
1. For C functions, structs, and variables: Prefer codesearch-definition-source
or codesearch-struct-layout FIRST to retrieve clean, exact definitions
without line-number guessing.
2. If symbol lookup fails (e.g., preprocessor macros, macro-generated code, or
disabled #ifdef branches), fall back to read-file or grepper.
3. For file headers, #include directives, preprocessor macro definitions, and
non-C files (Kconfig, Makefiles, docs): Use read-file or grepper directly.
Do NOT use git-log to search for the presence or existence of files in the
repository. git-log is only for tracing commit history of files that are
already present in the current checkout. If a file does not exist in the
current checkout, it cannot be used for reproduction.
Avoid running broad git-log queries (such as searches on the entire repo)
without a specific 'PathPrefix' to restrict the scope, as these are very
expensive and will time out.
If a git-log tool call times out, do not retry the query with the same broad
scope. You must specify a tighter, narrower 'PathPrefix' for subsequent queries.
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: Can you verify if `configfs_symlink` dropping the reference to `parent_item` before `inode_unlock(dir)` and re-acquiring it after `inode_lock(dir)` is a safe and complete fix for the use-after-free caused by `configfs_rmdir` freeing the parent object?
|
| 167/4 |
2026/09/02 21:19 |
llm |
codeexpert |
0m
Model:
Tokens:
input: 4026
output: 58
thoughts: 187
Thoughts: **Analyzing a Potential Configfs Fix**
Okay, so the user wants me to vet a proposed fix for a use-after-free vulnerability in `configfs`, specifically related to `configfs_symlink` and `configfs_rmdir`. The core question: is dropping the `parent_item` reference before unlocking the directory inode and re-acquiring it after locking the inode a complete and safe fix?
I need to dive into the `configfs` code. My plan is to thoroughly investigate the relevant parts: `configfs_symlink`, `configfs_rmdir`, and the intricate locking mechanisms in `configfs` β both the inode locks and any configfs-specific subsystem locks. I also need to track down relevant commit logs, bug reports, and any prior fixes addressing the use-after-free issue.
I'll use tools like `codesearch-definition-source` or `grepper` to efficiently inspect the source code of `configfs_symlink` and `configfs_rmdir` within the `fs/configfs/` directory. I'll be looking for where these functions operate on the `parent_item`, how it's referenced, and precisely how locking is managed around these operations. I want to understand the timing of the reference dropping, the unlock, the relock, and any potential race conditions that might still exist. My goal is to determine if this refactoring indeed addresses the root cause and prevents the UAF. This will require a deep dive into the code and understanding the nuances of the locking and reference counting in this subsystem.
|
| 168/4 |
2026/09/02 21:19 |
tool |
grepper |
0m
Args: map[Expression:configfs_symlink PathPrefix:fs/configfs]
Results: map[Output:fs/configfs/configfs_internal.h=30=struct configfs_dirent {
--
fs/configfs/configfs_internal.h-60-
fs/configfs/configfs_internal.h:61:extern struct mutex configfs_symlink_mutex;
fs/configfs/configfs_internal.h-62-extern spinlock_t configfs_dirent_lock;
--
fs/configfs/configfs_internal.h=89=extern const struct inode_operations configfs_root_inode_operations;
fs/configfs/configfs_internal.h:90:extern const struct inode_operations configfs_symlink_inode_operations;
fs/configfs/configfs_internal.h-91-extern const struct dentry_operations configfs_dentry_ops;
fs/configfs/configfs_internal.h-92-
fs/configfs/configfs_internal.h:93:extern int configfs_symlink(struct mnt_idmap *idmap,
fs/configfs/configfs_internal.h-94- struct inode *dir, struct dentry *dentry,
--
fs/configfs/dir.c=369=int configfs_create_link(struct configfs_dirent *target, struct dentry *parent,
--
fs/configfs/dir.c-387- inode->i_link = body;
fs/configfs/dir.c:388: inode->i_op = &configfs_symlink_inode_operations;
fs/configfs/dir.c-389- d_make_persistent(dentry, inode);
--
fs/configfs/dir.c=1469=static int configfs_rmdir(struct inode *dir, struct dentry *dentry)
--
fs/configfs/dir.c-1503-
fs/configfs/dir.c:1504: mutex_lock(&configfs_symlink_mutex);
fs/configfs/dir.c-1505- spin_lock(&configfs_dirent_lock);
--
fs/configfs/dir.c-1517- spin_unlock(&configfs_dirent_lock);
fs/configfs/dir.c:1518: mutex_unlock(&configfs_symlink_mutex);
fs/configfs/dir.c-1519-
--
fs/configfs/dir.c=1579=const struct inode_operations configfs_dir_inode_operations = {
--
fs/configfs/dir.c-1581- .rmdir = configfs_rmdir,
fs/configfs/dir.c:1582: .symlink = configfs_symlink,
fs/configfs/dir.c-1583- .unlink = configfs_unlink,
--
fs/configfs/dir.c=1934=void configfs_unregister_subsystem(struct configfs_subsystem *subsys)
--
fs/configfs/dir.c-1953- inode_lock_nested(d_inode(dentry), I_MUTEX_CHILD);
fs/configfs/dir.c:1954: mutex_lock(&configfs_symlink_mutex);
fs/configfs/dir.c-1955- spin_lock(&configfs_dirent_lock);
--
fs/configfs/dir.c-1959- spin_unlock(&configfs_dirent_lock);
fs/configfs/dir.c:1960: mutex_unlock(&configfs_symlink_mutex);
fs/configfs/dir.c-1961- configfs_detach_group(dentry);
--
fs/configfs/symlink.c-19-/* Protects attachments of new symlinks */
fs/configfs/symlink.c:20:DEFINE_MUTEX(configfs_symlink_mutex);
fs/configfs/symlink.c-21-
--
fs/configfs/symlink.c=117=static int get_target(const char *symname, struct config_item **target,
--
fs/configfs/symlink.c-134-
fs/configfs/symlink.c:135:int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
fs/configfs/symlink.c-136- struct dentry *dentry, const char *symname)
--
fs/configfs/symlink.c-197- if (!ret) {
fs/configfs/symlink.c:198: mutex_lock(&configfs_symlink_mutex);
fs/configfs/symlink.c-199- ret = create_link(parent_item, target_item, dentry);
fs/configfs/symlink.c:200: mutex_unlock(&configfs_symlink_mutex);
fs/configfs/symlink.c-201- if (ret && type->ct_item_ops->drop_link)
--
fs/configfs/symlink.c=213=int configfs_unlink(struct inode *dir, struct dentry *dentry)
--
fs/configfs/symlink.c-257-
fs/configfs/symlink.c:258:const struct inode_operations configfs_symlink_inode_operations = {
fs/configfs/symlink.c-259- .get_link = simple_get_link,
]
|
| 169/4 |
2026/09/02 21:19 |
tool |
grepper |
0m
Args: map[Expression:configfs_rmdir PathPrefix:fs/configfs]
Results: map[Output:fs/configfs/dir.c=369=int configfs_create_link(struct configfs_dirent *target, struct dentry *parent,
--
fs/configfs/dir.c-404- * the directory before we remove the directory, and we've inlined
fs/configfs/dir.c:405: * what used to be configfs_rmdir() below, instead of calling separately.
fs/configfs/dir.c-406- *
--
fs/configfs/dir.c=992=static int configfs_dump(struct configfs_dirent *sd, int level)
--
fs/configfs/dir.c-1029- * VFS->configfs path. So it must take VFS locks to prevent the
fs/configfs/dir.c:1030: * VFS->configfs stuff (configfs_mkdir(), configfs_rmdir(), etc). This is
fs/configfs/dir.c-1031- * why you can't call these functions underneath configfs callbacks.
--
fs/configfs/dir.c=1289=static struct dentry *configfs_mkdir(struct mnt_idmap *idmap, struct inode *dir,
--
fs/configfs/dir.c-1468-
fs/configfs/dir.c:1469:static int configfs_rmdir(struct inode *dir, struct dentry *dentry)
fs/configfs/dir.c-1470-{
--
fs/configfs/dir.c=1579=const struct inode_operations configfs_dir_inode_operations = {
fs/configfs/dir.c-1580- .mkdir = configfs_mkdir,
fs/configfs/dir.c:1581: .rmdir = configfs_rmdir,
fs/configfs/dir.c-1582- .symlink = configfs_symlink,
]
|
| 170/4 |
2026/09/02 21:19 |
llm |
codeexpert |
0m
Model:
Tokens:
input: 6443
output: 75
thoughts: 69
Thoughts: Here's my summary, as I delve into this:
**Deep Dive into configfs Symlinks and Directories**
Alright, let's get down to brass tacks. I need to refresh my understanding of how `configfs_symlink` and `configfs_rmdir` are implemented and, more importantly, how they interact. My plan is to start by directly examining the source code for `configfs_symlink` in `fs/configfs/symlink.c` and `configfs_rmdir` in `fs/configfs/dir.c`. I want to see the nitty-gritty details of the data structures and control flow.
Beyond just the current code, I also need to understand the evolution of these functions. So, I'll absolutely be digging into the Git history for both `configfs_symlink` and `configfs_rmdir`. This will help me trace back any potential design decisions, bug fixes, or performance optimizations, and give me context that the current code alone might not. I need to understand *why* things are done the way they are, not just *what* is being done. Armed with both current implementation and the historical context, I should be able to form a complete understanding of how these functions behave.
|
| 171/4 |
2026/09/02 21:20 |
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]
|
| 172/4 |
2026/09/02 21:20 |
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]
|
| 173/4 |
2026/09/02 21:20 |
llm |
codeexpert |
0m
Model:
Tokens:
input: 9750
output: 25
thoughts: 23
Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience:
**Analyzing `configfs_symlink` and `configfs_rmdir` Commit History**
Okay, I'm diving in to understand the evolution of those two specific functions, `configfs_symlink` and `configfs_rmdir`. The goal here isn't a surface-level overview, but a deep dive to potentially uncover any subtle changes, related bug fixes, performance improvements, or perhaps architectural shifts in how these crucial configfs components operate over time. I want to see if there were any significant refactorings, or if these functions have always co-existed harmoniously. I'm looking for the *why* behind the *what* in the commit logs β what motivated the changes and what were the broader implications. I'm expecting to find insights relating to the implementation nuances of symlinks and directory removal within the configfs framework, and how these functions' designs evolved in relation to each other. Are there any interdependencies that I should be aware of? Were any edge cases handled in a specific commit that is relevant now? Let's get to it.
|
| 174/4 |
2026/09/02 21:20 |
tool |
git-log |
0m
Args: map[Count:20 PathPrefix:fs/configfs]
Results: map[Output:d53ac61b6120 create_default_group(): pass parent's dentry instead of config_group
ad235a65dfff configfs_attach_group(): drop the unused parent_item argument
d4152c7562a8 configs_attach_item(): drop unused parent_item argument
967c898d9def configfs_create(): lift parent timestamp updates into callers
43b45755e80e kill configfs_drop_dentry()
1387aedb391a configfs: mark pinned dentries persistent
1a29e25799bc configfs: dentry refcount needs to be pinned only once
abe91a2aa9c4 switch configfs_detach_{group,item}() to passing dentry
05a4d3d1b676 configfs_remove_dir(), detach_attrs(): switch to passing dentry
34a62943762c populate_attrs(): move cleanup to the sole caller
17bee4d214a4 populate_group(): move cleanup on failure to the sole caller
acef053618d1 configfs_detach_rollback(): pass configfs_dirent instead of dentry
cbd6bbd48732 configfs_do_depend_item(): pass configfs_dirent instead of dentry
764682e01184 configfs_depend_prep(): pass configfs_dirent instead of dentry
96551d7f9f7b configfs_detach_prep(): pass configfs_dirent instead of dentry
5cefe23f6374 configfs_mkdir(): use take_dentry_name_snapshot()
9b9e8bb81c41 configfs: fix lockless traversals of ->s_children
0c23a964a0f9 configfs_lookup(): switch to d_splice_alias()
10da12d352b7 configfs_lookup(): don't leave ->s_dentry dangling on failure
1b27d11b17b7 configfs: replace __get_free_pages() with kzalloc()
]
|
| 175/4 |
2026/09/02 21:20 |
llm |
codeexpert |
0m
Model:
Tokens:
input: 10571
output: 30
thoughts: 27
Thoughts: Here's my thought process, as if I were considering this issue:
**Delving into the ConfigFS Symlink Mystery**
Okay, so we need to figure out how symlinks are behaving in the `fs/configfs` area. My initial approach is always to go straight to the source: the Git history. It's the most reliable way to understand the evolution of this particular feature and pinpoint any specific changes related to symlinks within ConfigFS.
My search terms are precise because I want to cut through the noise. I'm starting with a direct search for "symlink" within the `fs/configfs` directory. That should immediately flag any commits that explicitly touch symlink functionality there. Then, I'll broaden the net. I'll search commit messages for mentions of "symlink" more generally, in case the actual code changes are elsewhere but the discussion centers on symlink behavior within that system. Finally, to ensure I'm getting at all the possible areas, even if indirectly, I'll add "parent_item" to the search. This term is important in configfs and symlink interactions, since symlinks inherit the properties of their parent item. This allows me to see related changes.
By carefully selecting these search terms, I'm aiming to quickly surface the most relevant commits for review and analysis. Let's see what the Git history reveals.
|
| 176/4 |
2026/09/02 21:20 |
tool |
git-log |
0m
Args: map[CodeRegexp:parent_item PathPrefix:fs/configfs]
Results: map[Output:ad235a65dfff configfs_attach_group(): drop the unused parent_item argument
d4152c7562a8 configs_attach_item(): drop unused parent_item argument
8aebfffacfa3 configfs: fix config_item refcnt leak in configfs_rmdir()
e2f238f7d5a1 configfs: calculate the depth of parent item
e9c03af21cc7 configfs: calculate the symlink target only once
351e5d869e5a configfs: fix a deadlock in configfs_symlink()
47320fbe11a6 configfs: new object reprsenting tree fragments
f89ab8619e53 Revert "configfs: Allow ->make_item() and ->make_group() to return detailed errors."
e75206517504 configfs: call drop_link() to cleanup after create_link() failure
11c3b7921839 configfs: Allow ->make_item() and ->make_group() to return detailed errors.
]
|
| 177/4 |
2026/09/02 21:20 |
llm |
codeexpert |
0m
Model:
Tokens:
input: 11192
output: 27
thoughts: 25
Thoughts: Okay, here's my interpretation of that prompt, delivered as requested:
**Examining Symlink Commits**
Right, let's dive in. Commit `351e5d869e5a`... that's the starting point. I need to get a clear picture of how those symlinks are behaving in this context. I'm going to start with that specific commit, but I'll need to expand my view to include other relevant symlink commits. My experience tells me that these often come as a series; one commit might create the symlink, another might modify it, and another might remove it. I'll need to trace the complete chain of events related to any of these linked files. The devil's in the details with symlinks, and I'll need to analyze the specific implementation details, paying close attention to paths, targets, and any nuances in the way the symbolic links are being handled by the system. I anticipate needing to understand the intent behind the creation and management of these symlinks as well. My goal is a full understanding of the changes made, and their implications.
|
| 178/4 |
2026/09/02 21:20 |
tool |
git-show |
0m
Args:
Results: map[Output:commit 351e5d869e5ac10cb40c78b5f2d7dfc816ad4587
Author: Al Viro <viro@zeniv.linux.org.uk>
Date: Sat Aug 3 11:51:18 2019 -0400
configfs: fix a deadlock in configfs_symlink()
Configfs abuses symlink(2). Unlike the normal filesystems, it
wants the target resolved at symlink(2) time, like link(2) would've
done. The problem is that ->symlink() is called with the parent
directory locked exclusive, so resolving the target inside the
->symlink() is easily deadlocked.
Short of really ugly games in sys_symlink() itself, all we can
do is to unlock the parent before resolving the target and
relock it after. However, that invalidates the checks done
by the caller of ->symlink(), so we have to
* check that dentry is still where it used to be
(it couldn't have been moved, but it could've been unhashed)
* recheck that it's still negative (somebody else
might've successfully created a symlink with the same name
while we were looking the target up)
* recheck the permissions on the parent directory.
Cc: stable@vger.kernel.org
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
Signed-off-by: Christoph Hellwig <hch@lst.de>
diff --git a/fs/configfs/symlink.c b/fs/configfs/symlink.c
index 91eac6c55e07..f3881e4caedd 100644
--- a/fs/configfs/symlink.c
+++ b/fs/configfs/symlink.c
@@ -143,11 +143,42 @@ int configfs_symlink(struct inode *dir, struct dentry *dentry, const char *symna
!type->ct_item_ops->allow_link)
goto out_put;
+ /*
+ * This is really sick. What they wanted was a hybrid of
+ * link(2) and symlink(2) - they wanted the target resolved
+ * at syscall time (as link(2) would've done), be a directory
+ * (which link(2) would've refused to do) *AND* be a deep
+ * fucking magic, making the target busy from rmdir POV.
+ * symlink(2) is nothing of that sort, and the locking it
+ * gets matches the normal symlink(2) semantics. Without
+ * attempts to resolve the target (which might very well
+ * not even exist yet) done prior to locking the parent
+ * directory. This perversion, OTOH, needs to resolve
+ * the target, which would lead to obvious deadlocks if
+ * attempted with any directories locked.
+ *
+ * Unfortunately, that garbage is userland ABI and we should've
+ * said "no" back in 2005. Too late now, so we get to
+ * play very ugly games with locking.
+ *
+ * Try *ANYTHING* of that sort in new code, and you will
+ * really regret it. Just ask yourself - what could a BOFH
+ * do to me and do I want to find it out first-hand?
+ *
+ * AV, a thoroughly annoyed bastard.
+ */
+ inode_unlock(dir);
ret = get_target(symname, &path, &target_item, dentry->d_sb);
+ inode_lock(dir);
if (ret)
goto out_put;
- ret = type->ct_item_ops->allow_link(parent_item, target_item);
+ if (dentry->d_inode || d_unhashed(dentry))
+ ret = -EEXIST;
+ else
+ ret = inode_permission(dir, MAY_WRITE | MAY_EXEC);
+ if (!ret)
+ ret = type->ct_item_ops->allow_link(parent_item, target_item);
if (!ret) {
mutex_lock(&configfs_symlink_mutex);
ret = create_link(parent_item, target_item, dentry);
]
|
| 179/4 |
2026/09/02 21:20 |
llm |
codeexpert |
0m
Model:
Tokens:
input: 12592
output: 32
thoughts: 1238
Thoughts: **Analyzing the `configfs_symlink` and `configfs_rmdir` Race Condition**
Okay, let's break down this proposed fix. The core of the problem, as I understand it, revolves around a potential use-after-free vulnerability arising from a race condition between `configfs_symlink` and `configfs_rmdir`. The goal here is to determine whether moving the reference dropping in `configfs_symlink` before `inode_unlock` and re-acquiring it after `inode_lock` is sufficient to address this.
First, I need to visualize the sequence of events. In `configfs_symlink`, we grab a reference to `parent_item`, unlock the directory (`inode_unlock(dir)`), and then re-lock it (`inode_lock(dir)`). The critical window is that unlocked period. While the parent directory `dir` is unlocked, another process could execute `rmdir` on the parent directory.
Let's trace that scenario: `rmdir` locks the grandparent and then the `parent_dir` inode. Critically, `rmdir` then calls `configfs_rmdir`. This means `configfs_rmdir` could be running concurrently with `configfs_symlink`!
In `configfs_rmdir`, the item is detached/removed and a "client_drop_item" callback is triggered (or `config_item_put` gets called). That's where it gets interesting, because depending on the implementation in that callback, the parent item could be freed.
The current code in `configfs_symlink` holds a reference to `parent_item` across that `inode_unlock/inode_lock` window, protecting the `parent_item` itself.
The question then is, is this enough? If we were to move the reference dropping before `inode_unlock`, then the potential use-after-free wouldn't occur on `parent_item`, because the refcount would have been dropped before a concurrent `configfs_rmdir` could potentially free it.
But, now that I think about it, the question isn't only about the `parent_item`'s refcount reaching zero. We need to consider what happens during the concurrent `rmdir` operation. What if the `client_drop_item` callback or the driver's own `drop_item` function, or the `unlink_group`/`unlink_obj` function in configfs, did something that invalidates, destroys, frees, or detaches the `parent_item` data (even if the refcount doesn't hit 0)? It's not just the refcount hitting zero, but any operation that could invalidate the memory!
If this other call frees/invalidates `parent_item`'s data, and `configfs_symlink` still has a pointer to an invalid `parent_item` after the `inode_unlock/inode_lock` pair, we'd still have a use-after-free. I need to understand what `configfs_get_config_item` returns, and what that data means.
So, in conclusion, the suggested change *might* fix the refcount issue, but it doesn't necessarily prevent the use-after-free if the `client_drop_item` callback or other configfs function detaches/invalidates the underlying `parent_item` data while it's unlocked in `configfs_symlink`. I need more information on how `configfs_get_config_item` works and what `client_drop_item` and similar callbacks do to be certain.
|
| 180/4 |
2026/09/02 21:20 |
tool |
grepper |
0m
Args: map[Expression:configfs_get_config_item PathPrefix:fs/configfs]
Results: map[Output:fs/configfs/configfs_internal.h=113=static inline struct configfs_bin_attribute *to_bin_attr(struct dentry *dentry)
--
fs/configfs/configfs_internal.h-119-
fs/configfs/configfs_internal.h:120:static inline struct config_item *configfs_get_config_item(struct dentry *dentry)
fs/configfs/configfs_internal.h-121-{
--
fs/configfs/dir.c=1289=static struct dentry *configfs_mkdir(struct mnt_idmap *idmap, struct inode *dir,
--
fs/configfs/dir.c-1330- /* Get a working ref for the duration of this function */
fs/configfs/dir.c:1331: parent_item = configfs_get_config_item(dentry->d_parent);
fs/configfs/dir.c-1332- type = parent_item->ci_type;
--
fs/configfs/dir.c=1469=static int configfs_rmdir(struct inode *dir, struct dentry *dentry)
--
fs/configfs/dir.c-1483- /* Get a working ref until we have the child */
fs/configfs/dir.c:1484: parent_item = configfs_get_config_item(dentry->d_parent);
fs/configfs/dir.c-1485- subsys = to_config_group(parent_item)->cg_subsys;
--
fs/configfs/dir.c-1544- /* Get a working ref for the duration of this function */
fs/configfs/dir.c:1545: item = configfs_get_config_item(dentry);
fs/configfs/dir.c-1546-
--
fs/configfs/symlink.c=117=static int get_target(const char *symname, struct config_item **target,
--
fs/configfs/symlink.c-127- return -EPERM;
fs/configfs/symlink.c:128: *target = configfs_get_config_item(path.dentry);
fs/configfs/symlink.c-129- if (!*target)
--
fs/configfs/symlink.c=135=int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
--
fs/configfs/symlink.c-151-
fs/configfs/symlink.c:152: parent_item = configfs_get_config_item(dentry->d_parent);
fs/configfs/symlink.c-153- type = parent_item->ci_type;
--
fs/configfs/symlink.c=213=int configfs_unlink(struct inode *dir, struct dentry *dentry)
--
fs/configfs/symlink.c-225-
fs/configfs/symlink.c:226: parent_item = configfs_get_config_item(dentry->d_parent);
fs/configfs/symlink.c-227- type = parent_item->ci_type;
]
|
| 181/4 |
2026/09/02 21:20 |
llm |
codeexpert |
0m
Model:
Tokens:
input: 15451
output: 43
thoughts: 28
Thoughts: Here's my summary, as if I were thinking these thoughts:
**Diving into the ConfigFS Code**
Okay, time to get my hands dirty. I need to understand how the configuration item retrieval works within ConfigFS. So, let's start by digging into `fs/configfs/configfs_internal.h` and take a close look at the `configfs_get_config_item` function. This is where the core of the action happens, I'm sure of it. I'll be able to see how items are requested and retrieved from the ConfigFS structure. Hopefully, the definition includes some comments and relevant data structures that will give me a clear picture of the whole process. I'm especially interested in how it handles error conditions and resource management - that's often where the real insights lie, even if the basic retrieval logic seems straightforward. Let's see what's what.
|
| 182/4 |
2026/09/02 21:20 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/configfs/configfs_internal.h 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]
|
| 183/4 |
2026/09/02 21:20 |
llm |
codeexpert |
0m
Model:
Tokens:
input: 15941
output: 29
thoughts: 244
Thoughts: **Examining the ConfigFS Item Retrieval**
Okay, let's dive into this `configfs_get_config_item` function. I see it's an inline function, which makes sense for performance in this context. It's designed to retrieve a `config_item` given a `dentry`. The core of the logic is within that spinlock, which is typical for concurrent access to data structures. I see it's grabbing `sd->s_element` β that's the key. `sd` is a `configfs_dirent` and `s_element` is the item. Now, I need to understand the lifecycle of `sd` and `s_element`.
The question that immediately pops into my head is: what happens to `sd->s_element`, or even the `sd` itself, when a directory is removed using `configfs_rmdir`, or when items/groups are detached using `configfs_detach_group` or `configfs_detach_item`? What's the memory management like? I need to know if the `config_item` is properly released or if there's a potential for a use-after-free scenario. This is a critical point.
Alright, time to get digging. I'm going to search the codebase for usages of `sd->s_element` and also look for those crucial functions: `configfs_detach_group`, `configfs_detach_item`, `client_drop_item`, and `unlink_group`. I need to track down how these functions handle their associated `config_item` and its corresponding `dentry` and `configfs_dirent`. I'm looking for clues about reference counting, item destruction, and any potential race conditions. This is going to be fun!
|
| 184/4 |
2026/09/02 21:20 |
tool |
grepper |
0m
Args: map[Expression:configfs_detach_ PathPrefix:fs/configfs]
Results: map[Output:fs/configfs/dir.c=68=const struct dentry_operations configfs_dentry_ops = {
--
fs/configfs/dir.c-75- * Helpers to make lockdep happy with our recursive locking of default groups'
fs/configfs/dir.c:76: * inodes (see configfs_attach_group() and configfs_detach_group()).
fs/configfs/dir.c-77- * We put default groups i_mutexes in separate classes according to their depth
--
fs/configfs/dir.c=426=static struct dentry * configfs_lookup(struct inode *dir,
--
fs/configfs/dir.c-499- * If there is an error, the caller will reset the flags via
fs/configfs/dir.c:500: * configfs_detach_rollback().
fs/configfs/dir.c-501- */
fs/configfs/dir.c:502:static int configfs_detach_prep(struct configfs_dirent *parent_sd, struct dentry **wait)
fs/configfs/dir.c-503-{
--
fs/configfs/dir.c-530- */
fs/configfs/dir.c:531: ret = configfs_detach_prep(sd, wait);
fs/configfs/dir.c-532- if (!ret)
--
fs/configfs/dir.c-547- */
fs/configfs/dir.c:548:static void configfs_detach_rollback(struct configfs_dirent *parent_sd)
fs/configfs/dir.c-549-{
--
fs/configfs/dir.c-555- if (sd->s_type & CONFIGFS_USET_DEFAULT)
fs/configfs/dir.c:556: configfs_detach_rollback(sd);
fs/configfs/dir.c-557-}
--
fs/configfs/dir.c=659=static int configfs_attach_group(struct config_item *item,
--
fs/configfs/dir.c-661- struct configfs_fragment *frag);
fs/configfs/dir.c:662:static void configfs_detach_group(struct dentry *dentry);
fs/configfs/dir.c-663-
fs/configfs/dir.c=664=static void detach_groups(struct dentry *dentry)
--
fs/configfs/dir.c-681-
fs/configfs/dir.c:682: configfs_detach_group(child);
fs/configfs/dir.c-683- d_inode(child)->i_flags |= S_DEAD;
--
fs/configfs/dir.c=816=static void link_group(struct config_group *parent_group, struct config_group *group)
--
fs/configfs/dir.c-835-/* Caller holds the mutex of the item's inode */
fs/configfs/dir.c:836:static void configfs_detach_item(struct dentry *dentry)
fs/configfs/dir.c-837-{
--
fs/configfs/dir.c-851- *
fs/configfs/dir.c:852: * configfs_detach_group() and configfs_detach_item() behave similarly on
fs/configfs/dir.c-853- * the way out. They assume that the proper semaphores are held, they
--
fs/configfs/dir.c=857=static int configfs_attach_item(struct config_item *item,
--
fs/configfs/dir.c-872- inode_lock(d_inode(dentry));
fs/configfs/dir.c:873: configfs_detach_item(dentry);
fs/configfs/dir.c-874- d_inode(dentry)->i_flags |= S_DEAD;
--
fs/configfs/dir.c-884-/* Caller holds the mutex of the group's inode */
fs/configfs/dir.c:885:static void configfs_detach_group(struct dentry *dentry)
fs/configfs/dir.c-886-{
fs/configfs/dir.c-887- detach_groups(dentry);
fs/configfs/dir.c:888: configfs_detach_item(dentry);
fs/configfs/dir.c-889-}
--
fs/configfs/dir.c=891=static int configfs_attach_group(struct config_item *item,
--
fs/configfs/dir.c-915- if (ret) {
fs/configfs/dir.c:916: configfs_detach_group(dentry);
fs/configfs/dir.c-917- d_inode(dentry)->i_flags |= S_DEAD;
--
fs/configfs/dir.c=992=static int configfs_dump(struct configfs_dirent *sd, int level)
--
fs/configfs/dir.c-1046- * Only subdirectories count here. Files (CONFIGFS_NOT_PINNED) are
fs/configfs/dir.c:1047: * attributes. This is similar but not the same to configfs_detach_prep().
fs/configfs/dir.c:1048: * Note that configfs_detach_prep() expects the parent to be locked when it
fs/configfs/dir.c-1049- * is called, but we lock the parent *inside* configfs_depend_prep(). We
--
fs/configfs/dir.c=1289=static struct dentry *configfs_mkdir(struct mnt_idmap *idmap, struct inode *dir,
--
fs/configfs/dir.c-1417- spin_lock(&configfs_dirent_lock);
fs/configfs/dir.c:1418: /* This will make configfs_detach_prep() fail */
fs/configfs/dir.c-1419- sd->s_type |= CONFIGFS_USET_IN_MKDIR;
--
fs/configfs/dir.c=1469=static int configfs_rmdir(struct inode *dir, struct dentry *dentry)
--
fs/configfs/dir.c-1512- if (!ret) {
fs/configfs/dir.c:1513: ret = configfs_detach_prep(sd, &wait);
fs/configfs/dir.c-1514- if (ret)
fs/configfs/dir.c:1515: configfs_detach_rollback(sd);
fs/configfs/dir.c-1516- }
--
fs/configfs/dir.c-1535- spin_lock(&configfs_dirent_lock);
fs/configfs/dir.c:1536: configfs_detach_rollback(sd);
fs/configfs/dir.c-1537- spin_unlock(&configfs_dirent_lock);
--
fs/configfs/dir.c-1553- if (sd->s_type & CONFIGFS_USET_DIR) {
fs/configfs/dir.c:1554: configfs_detach_group(dentry);
fs/configfs/dir.c-1555-
--
fs/configfs/dir.c-1559- } else {
fs/configfs/dir.c:1560: configfs_detach_item(dentry);
fs/configfs/dir.c-1561-
--
fs/configfs/dir.c=1796=void configfs_unregister_group(struct config_group *group)
--
fs/configfs/dir.c-1809- spin_lock(&configfs_dirent_lock);
fs/configfs/dir.c:1810: configfs_detach_prep(sd, NULL);
fs/configfs/dir.c-1811- spin_unlock(&configfs_dirent_lock);
fs/configfs/dir.c-1812-
fs/configfs/dir.c:1813: configfs_detach_group(dentry);
fs/configfs/dir.c-1814- d_inode(dentry)->i_flags |= S_DEAD;
--
fs/configfs/dir.c=1934=void configfs_unregister_subsystem(struct configfs_subsystem *subsys)
--
fs/configfs/dir.c-1955- spin_lock(&configfs_dirent_lock);
fs/configfs/dir.c:1956: if (configfs_detach_prep(sd, NULL)) {
fs/configfs/dir.c-1957- pr_err("Tried to unregister non-empty subsystem!\n");
--
fs/configfs/dir.c-1960- mutex_unlock(&configfs_symlink_mutex);
fs/configfs/dir.c:1961: configfs_detach_group(dentry);
fs/configfs/dir.c-1962- d_inode(dentry)->i_flags |= S_DEAD;
]
|
| 185/4 |
2026/09/02 21:20 |
llm |
codeexpert |
0m
Model:
Tokens:
input: 18913
output: 76
thoughts: 44
Thoughts: Here's my analysis of these functions related to ConfigFS detachment, based on the assumption I have:
**Dissecting the ConfigFS Detachment Functions**
Okay, so I'm looking at these ConfigFS detachment functions: `configfs_detach_prep`, `configfs_detach_rollback`, `configfs_detach_item`, and `configfs_detach_group`. Given my understanding of ConfigFS's architecture and the overall system, I immediately recognize these as key players in the process of gracefully removing (detaching) objects and groups from the ConfigFS filesystem.
My initial thought is to dissect what each one specifically does. I anticipate `configfs_detach_prep` probably handles the initial setup or checks before any actual removal takes place. Perhaps it validates dependencies, prepares data structures, or checks for held references that might prevent a clean detachment. I suspect it's a vital pre-flight check.
Then, logically, `configfs_detach_rollback` probably addresses the scenario where the detachment process encounters an error or is somehow interrupted. It must have mechanisms in place to cleanly revert any partial changes, ensuring the filesystem remains in a consistent state and no resources are leaked. This function is essential to data integrity and fault tolerance.
`configfs_detach_item` has to be the workhorse. This one is likely focused on detaching individual "items" or objects within ConfigFS. From my previous work, this is where you'd find the core logic for freeing resources associated with an object, unlinking it from the filesystem hierarchy, and ensuring it's properly de-registered or unregistered.
Finally, `configfs_detach_group` will most likely deal with removing a group of items, possibly a container. This is where I'd anticipate seeing iteration through items, calling detach on each one, and perhaps managing the cleanup of the group's directory itself. This is all assuming the general approach follows the common pattern of recursive cleanup.
|
| 186/4 |
2026/09/02 21:20 |
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]
|
| 187/4 |
2026/09/02 21:20 |
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]
|
| 188/4 |
2026/09/02 21:20 |
llm |
codeexpert |
0m
Model:
Tokens:
input: 20287
output: 38
thoughts: 24
Thoughts: Here's my analysis of that function call, presented as my own thought process:
**Diving into `configfs_remove_dir(dentry)`**
Okay, so we're looking at `configfs_remove_dir(dentry)`. Time to dig in and understand what's happening. My initial thought is to recall what context we're in, specifically what the `configfs` filesystem is all about. This is likely an important operation within that file system's structure. It's safe to assume that `dentry` is a pointer to a directory entry within the ConfigFS namespace. I need to refresh my memory on the semantics of removing a directory within ConfigFS - are we dealing with special restrictions like read-only attributes, or any custom permissions? What are the implications if this directory isn't empty? Let's assume this means removing a directory within the configfs hierarchy. I will now examine the definition of the function itself to clarify any implicit details in its operation.
|
| 189/4 |
2026/09/02 21:20 |
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]
|
| 190/4 |
2026/09/02 21:20 |
llm |
codeexpert |
0m
Model:
Tokens:
input: 20811
output: 39
thoughts: 23
Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience and written in the first person:
**Analyzing `configfs_remove_dirent(d)` and Its Impact on `sd`**
Okay, so I'm looking at `configfs_remove_dirent(d)`. Right, I know this is the function responsible for removing a directory entry from the ConfigFS filesystem. The key thing I'm immediately interested in is how this removal interacts with the associated ConfigFS data structure, specifically `sd`. I need to understand the connection between these two. I need to be sure that the removal is handled correctly, i.e., I need to consider, how the `sd` is correctly released, i.e., deallocated and zeroed out, or properly handled in the case of reference-counting.
I'm thinking about the possible scenarios here. Does `configfs_remove_dirent()` invalidate the `sd` pointer directly, or does it trigger a chain of events that eventually lead to the structure being freed? If it's the latter, what are those events? Are there any race conditions I need to worry about if multiple threads are accessing this entry at the same time? How are locks handled to ensure that a removal doesn't occur while data is being actively accessed?
I'm mentally stepping through the likely code path, tracing how the removal proceeds and checking whether there is a dangling pointer when the sd structure is used later. I am considering edge cases, like what if `sd` is still in use by some other subsystem referencing it via a list entry? I'm anticipating the potential for use-after-free bugs and trying to identify any obvious vulnerabilities in this part of the ConfigFS implementation. I'm focusing on the mechanics of the unlinking and memory management.
|
| 191/4 |
2026/09/02 21:20 |
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]
|
| 192/4 |
2026/09/02 21:20 |
llm |
codeexpert |
0m
Model:
Tokens:
input: 21445
output: 25
thoughts: 1631
Thoughts: **Analyzing a Potential Race Condition in `configfs_rmdir` and Symlink Creation**
Okay, so I'm digging into a possible race condition that arises when you combine `configfs_rmdir` with the creation of symlinks within a ConfigFS directory. Let's walk through the scenario I've got in mind.
We've got a setup with a directory `/config/A/B`. Process 1 is attempting to create a symlink named "link" inside B using `symlink("...", "/config/A/B/link")`. During the `configfs_symlink` function call, the `dir` argument refers to the inode for B, `dentry` points to the negative dentry of the link.
Now, here's the crucial point: in the `configfs_symlink` flow, the inode lock on B (`inode_unlock(dir)`) gets released. This is done before the time consuming work to get the target path for the symlink takes place. Right after this happens, the inode lock is reacquired.
And this creates a window of opportunity: during that time, *another* process, Process 2, could potentially call `rmdir("/config/A/B")`. And it seems like this will happen because Process 2 successfully acquires the lock on B while it is unlocked by Process 1.
Process 2's `rmdir` operation then goes through the standard VFS `vfs_rmdir` and then on to our ConfigFS-specific `configfs_rmdir`. A very important thing happens here. `configfs_detach_prep` iterates over the directory's children to check for any existing items. But if the `create_link` function has not yet been executed, there are no children, even though there is this negative dentry from Process 1.
The `configfs_rmdir` function then progresses through the detachment of the directory B, and the important thing here is the `client_drop_item` callback. This is where drivers might free data structures associated with B, making it *gone*.
At the end of Process 2, we have an empty inode for B and a "dead" `dentry`.
Now, Process 1, unaware of the directory B's demise, wakes up or finishes `get_target` and attempts to re-acquire the inode lock on B using `inode_lock(dir)`. This may or may not succeed, but let's see what happens if it does. And here's where the problem shows up: `dentry`'s state. When `dentry` gets checked in `configfs_symlink`, it is checked if it is unhashed, and also whether its inode exists.
So, the critical question is: what is the state of that negative dentry for the symlink, `/config/A/B/link`, after the `rmdir`? Specifically, did the removal of B unhash the child dentry? The code is checking to see if `dentry` is unhashed using `d_unhashed(dentry)`. When B is removed with `rmdir`, does this unhash that negative dentry? Did something happen to the `dir` that means the subsequent call to `inode_permission` will fail?
So now, let's look closer at those checks: `if (dentry->d_inode || d_unhashed(dentry))`. And further, will `inode_permission` fail on a "dead" directory? I need to check those details. This seems like a race, but if either of these checks correctly detect the removal of the directory, it's safe. If not, then we could have an issue.
|
| 193/4 |
2026/09/02 21:20 |
tool |
grepper |
0m
Args: map[Expression:inode_permission PathPrefix:fs/]
Results: map[Output:fs/attr.c=372=int may_setattr(struct mnt_idmap *idmap, struct inode *inode,
--
fs/attr.c-390- if (!inode_owner_or_capable(idmap, inode)) {
fs/attr.c:391: error = inode_permission(idmap, inode, MAY_WRITE);
fs/attr.c-392- if (error)
--
fs/bad_inode.c=86=static int bad_inode_readlink(struct dentry *dentry, char __user *buffer,
--
fs/bad_inode.c-91-
fs/bad_inode.c:92:static int bad_inode_permission(struct mnt_idmap *idmap,
fs/bad_inode.c-93- struct inode *inode, int mask)
--
fs/bad_inode.c=163=static const struct inode_operations bad_inode_ops =
--
fs/bad_inode.c-174- .readlink = bad_inode_readlink,
fs/bad_inode.c:175: .permission = bad_inode_permission,
fs/bad_inode.c-176- .getattr = bad_inode_getattr,
--
fs/bpf_fs_kfuncs.c=103=static int bpf_xattr_read_permission(const char *name, struct inode *inode)
--
fs/bpf_fs_kfuncs.c-112-
fs/bpf_fs_kfuncs.c:113: return inode_permission(&nop_mnt_idmap, inode, MAY_READ);
fs/bpf_fs_kfuncs.c-114-}
--
fs/bpf_fs_kfuncs.c=173=static int bpf_xattr_write_permission(const char *name, struct inode *inode)
--
fs/bpf_fs_kfuncs.c-181-
fs/bpf_fs_kfuncs.c:182: return inode_permission(&nop_mnt_idmap, inode, MAY_WRITE);
fs/bpf_fs_kfuncs.c-183-}
--
fs/btrfs/ioctl.c=1729=static int btrfs_search_path_in_tree_user(struct mnt_idmap *idmap,
--
fs/btrfs/ioctl.c-1808- /* Check the read+exec permission of this directory. */
fs/btrfs/ioctl.c:1809: ret = inode_permission(idmap, &temp_inode->vfs_inode,
fs/btrfs/ioctl.c-1810- MAY_READ | MAY_EXEC);
--
fs/btrfs/ioctl.c=1867=static noinline int btrfs_ioctl_ino_lookup(struct btrfs_root *root,
--
fs/btrfs/ioctl.c-1917- *
fs/btrfs/ioctl.c:1918: * 1. Read + Exec permission will be checked using inode_permission() during
fs/btrfs/ioctl.c-1919- * path construction. -EACCES will be returned in case of failure.
--
fs/btrfs/ioctl.c=2216=static noinline int btrfs_ioctl_snap_destroy(struct file *file,
--
fs/btrfs/ioctl.c-2396-
fs/btrfs/ioctl.c:2397: ret = inode_permission(idmap, inode, MAY_WRITE | MAY_EXEC);
fs/btrfs/ioctl.c-2398- if (ret)
--
fs/btrfs/ioctl.c=2430=static int btrfs_ioctl_defrag(struct file *file, void __user *argp)
--
fs/btrfs/ioctl.c-2460- if (!capable(CAP_SYS_ADMIN) &&
fs/btrfs/ioctl.c:2461: inode_permission(&nop_mnt_idmap, inode, MAY_WRITE)) {
fs/btrfs/ioctl.c-2462- ret = -EPERM;
--
fs/configfs/symlink.c=135=int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
--
fs/configfs/symlink.c-192- else
fs/configfs/symlink.c:193: ret = inode_permission(&nop_mnt_idmap, dir,
fs/configfs/symlink.c-194- MAY_WRITE | MAY_EXEC);
--
fs/ecryptfs/inode.c=852=ecryptfs_permission(struct mnt_idmap *idmap, struct inode *inode,
--
fs/ecryptfs/inode.c-854-{
fs/ecryptfs/inode.c:855: return inode_permission(&nop_mnt_idmap,
fs/ecryptfs/inode.c-856- ecryptfs_inode_to_lower(inode), mask);
--
fs/exec.c=1339=void would_dump(struct linux_binprm *bprm, struct file *file)
--
fs/exec.c-1342- struct mnt_idmap *idmap = file_mnt_idmap(file);
fs/exec.c:1343: if (inode_permission(idmap, inode, MAY_READ) < 0) {
fs/exec.c-1344- struct user_namespace *old, *user_ns;
--
fs/exec.c=1623=static void bprm_fill_uid(struct linux_binprm *bprm, struct file *file)
--
fs/exec.c-1651- vfsgid = i_gid_into_vfsgid(idmap, inode);
fs/exec.c:1652: err = inode_permission(idmap, inode, MAY_EXEC);
fs/exec.c-1653- inode_unlock(inode);
--
fs/namei.c=565=EXPORT_SYMBOL(generic_permission);
--
fs/namei.c-567-/**
fs/namei.c:568: * do_inode_permission - UNIX permission checking
fs/namei.c-569- * @idmap: idmap of the mount the inode was found from
--
fs/namei.c-577- */
fs/namei.c:578:static inline int do_inode_permission(struct mnt_idmap *idmap,
fs/namei.c-579- struct inode *inode, int mask)
--
fs/namei.c-600- *
fs/namei.c:601: * Note: lookup_inode_permission_may_exec() does not call here. If you add
fs/namei.c-602- * MAY_EXEC checks, adjust it.
--
fs/namei.c=604=static int sb_permission(struct super_block *sb, struct inode *inode, int mask)
--
fs/namei.c-616-/**
fs/namei.c:617: * inode_permission - Check for access rights to a given inode
fs/namei.c-618- * @idmap: idmap of the mount the inode was found from
--
fs/namei.c-627- */
fs/namei.c:628:int inode_permission(struct mnt_idmap *idmap,
fs/namei.c-629- struct inode *inode, int mask)
--
fs/namei.c-652-
fs/namei.c:653: retval = do_inode_permission(idmap, inode, mask);
fs/namei.c-654- if (unlikely(retval))
--
fs/namei.c-656-
fs/namei.c:657: retval = devcgroup_inode_permission(inode, mask);
fs/namei.c-658- if (unlikely(retval))
--
fs/namei.c-660-
fs/namei.c:661: return security_inode_permission(inode, mask);
fs/namei.c-662-}
fs/namei.c:663:EXPORT_SYMBOL(inode_permission);
fs/namei.c-664-
fs/namei.c-665-/*
fs/namei.c:666: * lookup_inode_permission_may_exec - Check traversal right for given inode
fs/namei.c-667- *
fs/namei.c-668- * This is a special case routine for may_lookup() making assumptions specific
fs/namei.c:669: * to path traversal. Use inode_permission() if you are doing something else.
fs/namei.c-670- *
fs/namei.c:671: * Work is shaved off compared to inode_permission() as follows:
fs/namei.c-672- * - we know for a fact there is no MAY_WRITE to worry about
--
fs/namei.c-682- */
fs/namei.c:683:static __always_inline int lookup_inode_permission_may_exec(struct mnt_idmap *idmap,
fs/namei.c-684- struct inode *inode, int mask)
--
fs/namei.c-692- if (unlikely(!(inode->i_opflags & (IOP_FASTPERM | IOP_FASTPERM_MAY_EXEC))))
fs/namei.c:693: return inode_permission(idmap, inode, mask);
fs/namei.c-694-
fs/namei.c-695- if (unlikely(((inode->i_mode & 0111) != 0111) || !no_acl_inode(inode)))
fs/namei.c:696: return inode_permission(idmap, inode, mask);
fs/namei.c-697-
fs/namei.c:698: return security_inode_permission(inode, mask);
fs/namei.c-699-}
--
fs/namei.c=1317=static bool safe_hardlink_source(struct mnt_idmap *idmap,
--
fs/namei.c-1334- /* Hardlinking to unreadable or unwritable sources is dangerous. */
fs/namei.c:1335: if (inode_permission(idmap, inode, MAY_READ | MAY_WRITE))
fs/namei.c-1336- return false;
--
fs/namei.c=1955=static inline int may_lookup(struct mnt_idmap *idmap,
--
fs/namei.c-1960- mask = nd->flags & LOOKUP_RCU ? MAY_NOT_BLOCK : 0;
fs/namei.c:1961: err = lookup_inode_permission_may_exec(idmap, nd->inode, mask);
fs/namei.c-1962- if (likely(!err))
--
fs/namei.c-1975-
fs/namei.c:1976: return lookup_inode_permission_may_exec(idmap, nd->inode, 0);
fs/namei.c-1977-}
--
fs/namei.c=3114=static int lookup_one_common(struct mnt_idmap *idmap,
--
fs/namei.c-3120- return err;
fs/namei.c:3121: return inode_permission(idmap, base->d_inode, MAY_EXEC);
fs/namei.c-3122-}
--
fs/namei.c=3678=int may_delete_dentry(struct mnt_idmap *idmap, struct inode *dir,
--
fs/namei.c-3696-
fs/namei.c:3697: error = inode_permission(idmap, dir, MAY_WRITE | MAY_EXEC);
fs/namei.c-3698- if (error)
--
fs/namei.c=3731=int may_create_dentry(struct mnt_idmap *idmap,
--
fs/namei.c-3741-
fs/namei.c:3742: return inode_permission(idmap, dir, MAY_WRITE | MAY_EXEC);
fs/namei.c-3743-}
--
fs/namei.c=4231=static int may_open(struct mnt_idmap *idmap, const struct path *path,
--
fs/namei.c-4268-
fs/namei.c:4269: error = inode_permission(idmap, inode, MAY_OPEN | acc_mode);
fs/namei.c-4270- if (error)
--
fs/namei.c=4315=static int may_o_create(struct mnt_idmap *idmap,
--
fs/namei.c-4325-
fs/namei.c:4326: error = inode_permission(idmap, dir->dentry->d_inode,
fs/namei.c-4327- MAY_WRITE | MAY_EXEC);
--
fs/namei.c=4866=int vfs_tmpfile(struct mnt_idmap *idmap,
--
fs/namei.c-4880- /* we want directory to be writable */
fs/namei.c:4881: error = inode_permission(idmap, dir, MAY_WRITE | MAY_EXEC);
fs/namei.c-4882- if (error)
--
fs/namei.c=6075=int vfs_rename(struct renamedata *rd)
--
fs/namei.c-6122- if (is_dir) {
fs/namei.c:6123: error = inode_permission(rd->mnt_idmap, source,
fs/namei.c-6124- MAY_WRITE);
--
fs/namei.c-6128- if ((flags & RENAME_EXCHANGE) && new_is_dir) {
fs/namei.c:6129: error = inode_permission(rd->mnt_idmap, target,
fs/namei.c-6130- MAY_WRITE);
--
fs/nfsd/nfsfh.c=31=static int nfsd_acceptable(void *expv, struct dentry *dentry)
--
fs/nfsd/nfsfh.c-45- parent = dget_parent(tdentry);
fs/nfsd/nfsfh.c:46: err = inode_permission(&nop_mnt_idmap,
fs/nfsd/nfsfh.c-47- d_inode(parent), MAY_EXEC);
--
fs/nfsd/vfs.c=2843=nfsd_permission(struct svc_cred *cred, struct svc_export *exp,
--
fs/nfsd/vfs.c-2902- /* This assumes NFSD_MAY_{READ,WRITE,EXEC} == MAY_{READ,WRITE,EXEC} */
fs/nfsd/vfs.c:2903: err = inode_permission(&nop_mnt_idmap, inode,
fs/nfsd/vfs.c-2904- acc & (MAY_READ | MAY_WRITE | MAY_EXEC));
--
fs/nfsd/vfs.c-2909- (acc & (NFSD_MAY_OWNER_OVERRIDE | NFSD_MAY_READ_IF_EXEC))))
fs/nfsd/vfs.c:2910: err = inode_permission(&nop_mnt_idmap, inode, MAY_EXEC);
fs/nfsd/vfs.c-2911-
--
fs/ocfs2/file.c=1352=int ocfs2_permission(struct mnt_idmap *idmap, struct inode *inode,
--
fs/ocfs2/file.c-1369- * may_open()
fs/ocfs2/file.c:1370: * inode_permission()
fs/ocfs2/file.c-1371- * ocfs2_permission()
--
fs/ocfs2/refcounttree.c=4355=static inline int ocfs2_may_create(struct inode *dir, struct dentry *child)
--
fs/ocfs2/refcounttree.c-4360- return -ENOENT;
fs/ocfs2/refcounttree.c:4361: return inode_permission(&nop_mnt_idmap, dir, MAY_WRITE | MAY_EXEC);
fs/ocfs2/refcounttree.c-4362-}
--
fs/ocfs2/refcounttree.c=4372=static int ocfs2_vfs_reflink(struct dentry *old_dentry, struct inode *dir,
--
fs/ocfs2/refcounttree.c-4414- if (!preserve) {
fs/ocfs2/refcounttree.c:4415: error = inode_permission(&nop_mnt_idmap, inode, MAY_READ);
fs/ocfs2/refcounttree.c-4416- if (error)
--
fs/open.c=73=int vfs_truncate(const struct path *path, loff_t length)
--
fs/open.c-87- idmap = mnt_idmap(path->mnt);
fs/open.c:88: error = inode_permission(idmap, inode, MAY_WRITE);
fs/open.c-89- if (error)
--
fs/open.c=463=static int do_faccessat(int dfd, const char __user *filename, int mode, int flags)
--
fs/open.c-503-
fs/open.c:504: res = inode_permission(mnt_idmap(path.mnt), inode, mode | MAY_ACCESS);
fs/open.c-505- /* SuS v2 requires we report a read only fs too */
--
fs/overlayfs/file.c=28=static struct file *ovl_open_realfile(const struct file *file,
--
fs/overlayfs/file.c-43- real_idmap = mnt_idmap(realpath->mnt);
fs/overlayfs/file.c:44: err = inode_permission(real_idmap, realinode, MAY_OPEN | acc_mode);
fs/overlayfs/file.c-45- if (err) {
--
fs/overlayfs/inode.c=306=int ovl_permission(struct mnt_idmap *idmap,
--
fs/overlayfs/inode.c-336- with_ovl_creds(inode->i_sb)
fs/overlayfs/inode.c:337: return inode_permission(mnt_idmap(realpath.mnt), realinode, mask);
fs/overlayfs/inode.c-338-}
--
fs/overlayfs/util.c=657=struct file *ovl_path_open(const struct path *path, int flags)
--
fs/overlayfs/util.c-676-
fs/overlayfs/util.c:677: err = inode_permission(real_idmap, inode, acc_mode | MAY_OPEN);
fs/overlayfs/util.c-678- if (err)
--
fs/remap_range.c=416=static bool may_dedupe_file(struct file *file)
--
fs/remap_range.c-426- return true;
fs/remap_range.c:427: if (!inode_permission(idmap, inode, MAY_WRITE))
fs/remap_range.c-428- return true;
--
fs/smb/server/smb2pdu.c=4080=int smb2_open(struct ksmbd_work *work)
--
fs/smb/server/smb2pdu.c-4617- } else if (!already_permitted) {
fs/smb/server/smb2pdu.c:4618: /* FILE_READ_ATTRIBUTE is allowed without inode_permission,
fs/smb/server/smb2pdu.c-4619- * because execute(search) permission on a parent directory,
--
fs/smb/server/smb2pdu.c-4622- if (daccess & ~(FILE_READ_ATTRIBUTES_LE | FILE_READ_CONTROL_LE)) {
fs/smb/server/smb2pdu.c:4623: rc = inode_permission(idmap,
fs/smb/server/smb2pdu.c-4624- d_inode(path.dentry),
--
fs/smb/server/smb2pdu.c-4630- (req->CreateOptions & FILE_DELETE_ON_CLOSE_LE)) {
fs/smb/server/smb2pdu.c:4631: rc = inode_permission(idmap,
fs/smb/server/smb2pdu.c-4632- d_inode(path.dentry->d_parent),
--
fs/smb/server/smb2pdu.c=6008=int smb2_query_dir(struct ksmbd_work *work)
--
fs/smb/server/smb2pdu.c-6061- if (!(dir_fp->daccess & FILE_LIST_DIRECTORY_LE) ||
fs/smb/server/smb2pdu.c:6062: inode_permission(file_mnt_idmap(dir_fp->filp),
fs/smb/server/smb2pdu.c-6063- file_inode(dir_fp->filp),
--
fs/smb/server/vfs.c=118=void ksmbd_vfs_query_maximal_access(struct mnt_idmap *idmap,
--
fs/smb/server/vfs.c-122-
fs/smb/server/vfs.c:123: if (!inode_permission(idmap, d_inode(dentry), MAY_OPEN | MAY_WRITE))
fs/smb/server/vfs.c-124- *daccess |= cpu_to_le32(WRITE_DAC | WRITE_OWNER | SYNCHRONIZE |
--
fs/smb/server/vfs.c-128-
fs/smb/server/vfs.c:129: if (!inode_permission(idmap, d_inode(dentry), MAY_OPEN | MAY_READ))
fs/smb/server/vfs.c-130- *daccess |= FILE_READ_DATA_LE | FILE_READ_EA_LE;
fs/smb/server/vfs.c-131-
fs/smb/server/vfs.c:132: if (!inode_permission(idmap, d_inode(dentry), MAY_OPEN | MAY_EXEC))
fs/smb/server/vfs.c-133- *daccess |= FILE_EXECUTE_LE;
fs/smb/server/vfs.c-134-
fs/smb/server/vfs.c:135: if (!inode_permission(idmap, d_inode(dentry->d_parent), MAY_EXEC | MAY_WRITE))
fs/smb/server/vfs.c-136- *daccess |= FILE_DELETE_LE;
--
fs/xattr.c=126=xattr_permission(struct mnt_idmap *idmap, struct inode *inode,
--
fs/xattr.c-177-
fs/xattr.c:178: return inode_permission(idmap, inode, mask);
fs/xattr.c-179-}
]
|
| 194/4 |
2026/09/02 21:20 |
llm |
codeexpert |
0m
Model:
Tokens:
input: 29982
output: 36
thoughts: 896
Thoughts: **Diving into `configfs_symlink`**
Okay, let's break this down. First, I see `configfs_symlink`, and I need to understand it from the ground up, line by line. It's clear that this function is creating a symbolic link within the ConfigFS filesystem. I can see the usual suspects: `mnt_idmap`, the parent directory `inode`, the new `dentry`, and the symbolic link name `symname`.
The first thing that grabs my attention is the check for `configfs_dirent_is_ready`. This suggests a potential "invisibility" mechanism for directories that are part of a group or default group hierarchy during attachment. That's a good initial filter, so I'll keep that in mind. Then, I see the retrieval of the `parent_item` and its `ci_type`. This is where the core logic begins.
The core of this function is a minefield of potential issues, stemming from a deeply flawed design decision (as I read the comments). It appears that ConfigFS attempts to emulate a hybrid between `link(2)` and `symlink(2)`. This means it needs to resolve the target at syscall time (like `link(2)`), allow the target to be a directory (unlike `link(2)`), and create deep locking magic to make it appear busy from a `rmdir` point of view. The comments are VERY unhappy with this.
The code then checks if the parent's item type has an `allow_link` operation, which it then calls. The function then unlocks the directory and attempts to resolve the symbolic link target using `get_target`. Once the target has been found, it checks to see if the directory has the proper permissions to create the link.
Next, I see a bit of a dance with the mutex, `configfs_symlink_mutex`, which seems necessary given the unconventional design. A successful link creation calls `create_link`, and if creation fails, there is a possibility of dropping the link via `drop_link`.
After that, the code puts `target_item` and `parent_item`, and returns.
Now, I really need to understand `create_link`. I need to figure out how it's defined and how it works. That's the next critical piece of this puzzle.
|
| 195/4 |
2026/09/02 21:20 |
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]
|
| 196/4 |
2026/09/02 21:20 |
llm |
codeexpert |
0m
Model:
Tokens:
input: 31993
output: 28
thoughts: 1713
Thoughts: **Analyzing the Potential Fix for a Use-After-Free Vulnerability**
Okay, so the question is, can we fix the use-after-free in `configfs_symlink` by dropping the reference to `parent_item` before `inode_unlock(dir)` and reacquiring it after `inode_lock(dir)`? Let's break this down.
The core issue seems to be a race condition. If `configfs_rmdir` frees the `parent_item` object while `configfs_symlink` is running, we're in trouble. Specifically, if `rmdir` is called on the parent directory (`dentry->d_parent`) while `configfs_symlink` has unlocked the directory (`dir`), then `configfs_rmdir` could be running concurrently. `configfs_rmdir` then unlinks the item/group, potentially calls a driver callback (`client_drop_item`), and crucially, this callback *might* free the memory associated with the `parent_item` (or at least partially tear it down). Then, when `inode_lock(dir)` returns in `configfs_symlink`, we might end up accessing freed memory in `allow_link`, `create_link`, or `drop_link`.
So, what if we drop the reference and then re-acquire it? The proposed code change looks like this:
```c
// before inode_unlock:
config_item_put(parent_item);
inode_unlock(dir);
ret = get_target(symname, &target_item, dentry->d_sb);
inode_lock(dir);
parent_item = configfs_get_config_item(dentry->d_parent);
```
Let's trace that. If `rmdir` runs concurrently while the `parent_item` is unlocked, `configfs_rmdir` might be running. By dropping the reference before unlocking, the refcount for `parent_item` will hit 0. If that happens, the `parent_item`'s release function (`ci_type->ct_item_ops->release`) gets called. This means the `parent_item` (and its entire underlying structure) is freed using `kfree` *before* the directory is locked again!
Now, the critical piece: what's in `dentry->d_parent->d_fsdata` (the `sd` or `configfs_dirent`)? `configfs_remove_dirent` calls `configfs_put(sd)`. Inside `configfs_dirent`, `sd->s_element` points to `parent_item`. When `parent_item` is freed, `sd->s_element` becomes a dangling pointer!
The problem is within `configfs_get_config_item`:
```c
static inline struct config_item *configfs_get_config_item(struct dentry *dentry)
{
struct config_item * item = NULL;
spin_lock(&dentry->d_lock);
if (!d_unhashed(dentry)) {
struct configfs_dirent * sd = dentry->d_fsdata;
item = config_item_get(sd->s_element);
}
spin_unlock(&dentry->d_lock);
return item;
}
```
If `parent_item` was freed, `sd->s_element` is a pointer to freed memory. Calling `config_item_get(sd->s_element)` (which in turn calls `kref_get`) now results in an *immediate* use-after-free! We're incrementing the refcount on memory that no longer belongs to us.
Furthermore, we must note `d_unhashed(dentry->d_parent)`. `dentry->d_parent` would be unhashed by `d_drop` or `d_delete` called within `vfs_rmdir` that is called by `configfs_rmdir`. So we must be mindful when `rmdir` runs and when it unhashes the parent.
Therefore, this "fix" actually *introduces* a use-after-free vulnerability! We've made the situation worse, not better, by freeing the `parent_item` before re-acquiring it.
|
| 197/4 |
2026/09/02 21:21 |
tool |
grepper |
0m
Args: map[Expression:int vfs_rmdir PathPrefix:fs/]
Results: map[Output:fs/namei.c=5467=SYSCALL_DEFINE2(mkdir, const char __user *, pathname, umode_t, mode)
--
fs/namei.c-5487- */
fs/namei.c:5488:int vfs_rmdir(struct mnt_idmap *idmap, struct inode *dir,
fs/namei.c-5489- struct dentry *dentry, struct delegated_inode *delegated_inode)
]
|
| 198/4 |
2026/09/02 21:21 |
llm |
codeexpert |
0m
Model:
Tokens:
input: 34810
output: 35
thoughts: 20
Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience:
**Diving into `vfs_rmdir`**
Alright, let's get down to it. Time to dissect `vfs_rmdir` in `fs/namei.c`. This is where the magic (or the potential headaches, depending on the implementation) of directory removal happens within the VFS layer. I need to understand how this function interfaces with the underlying filesystem operations. I'm especially interested in how it handles the checks for an empty directory, potential race conditions, and error propagation. I'll be looking for the lock acquisition strategies, reference counting manipulation (crucial!), and how it interacts with the inode and dentry structures involved. I'm anticipating some calls to filesystem-specific functions through the `inode->i_op` table, so I'll be keeping an eye out for those. It's also important to consider if it has any relation with the `..` and `.` entries. And if there are any specific locking arrangements, especially with respect to the parent directory. From what I'm expecting, I want to ensure it handles scenarios such as directory deletion across multiple processes and systems with distributed file systems. I might need to cross-reference this with relevant documentation, the kernel's synchronization primitives, and perhaps even some related patches or commit messages to get the full picture. Let's see what we've got.
|
| 199/4 |
2026/09/02 21:21 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/namei.c Name:vfs_rmdir]
Results: map[SourceCode:5473: /**
5474: * vfs_rmdir - remove directory
5475: * @idmap: idmap of the mount the inode was found from
5476: * @dir: inode of the parent directory
5477: * @dentry: dentry of the child directory
5478: * @delegated_inode: returns parent inode, if it's delegated.
5479: *
5480: * Remove a directory.
5481: *
5482: * If the inode has been found through an idmapped mount the idmap of
5483: * the vfsmount must be passed through @idmap. This function will then take
5484: * care to map the inode according to @idmap before checking permissions.
5485: * On non-idmapped mounts or if permission checking is to be performed on the
5486: * raw inode simply pass @nop_mnt_idmap.
5487: */
5488: int vfs_rmdir(struct mnt_idmap *idmap, struct inode *dir,
5489: struct dentry *dentry, struct delegated_inode *delegated_inode)
5490: {
5491: int error = may_delete_dentry(idmap, dir, dentry, true);
5492:
5493: if (error)
5494: return error;
5495:
5496: if (!dir->i_op->rmdir)
5497: return -EPERM;
5498:
5499: dget(dentry);
5500: inode_lock(dentry->d_inode);
5501:
5502: error = -EBUSY;
5503: if (is_local_mountpoint(dentry) ||
5504: (dentry->d_inode->i_flags & S_KERNEL_FILE))
5505: goto out;
5506:
5507: error = security_inode_rmdir(dir, dentry);
5508: if (error)
5509: goto out;
5510:
5511: error = try_break_deleg(dir, LEASE_BREAK_DIR_DELETE, delegated_inode);
5512: if (error)
5513: goto out;
5514:
5515: error = dir->i_op->rmdir(dir, dentry);
5516: if (error)
5517: goto out;
5518:
5519: shrink_dcache_parent(dentry);
5520: dentry->d_inode->i_flags |= S_DEAD;
5521: dont_mount(dentry);
5522: detach_mounts(dentry);
5523:
5524: out:
5525: inode_unlock(dentry->d_inode);
5526: dput(dentry);
5527: if (!error)
5528: d_delete_notify(dir, dentry);
5529: return error;
5530: }
SourceFile:fs/namei.c]
|
| 200/4 |
2026/09/02 21:21 |
llm |
codeexpert |
0m
Model:
Tokens:
input: 36050
output: 28
thoughts: 617
Thoughts: **My Analysis of a Potential Use-After-Free Vulnerability**
Okay, let's break this down. I'm looking at `vfs_rmdir` and tracing its execution. It calls `configfs_rmdir`, then a bunch of other cleanup functions. The critical part is the *order* of operations, specifically the locking and unlocking sequence.
First, `vfs_rmdir` calls the `rmdir` method which in this case is `configfs_rmdir`. After that, `vfs_rmdir` calls `inode_unlock` on `dentry->d_inode`. Fine, standard VFS stuff. But then, a few lines later, *after* that unlock, it calls `d_delete_notify`, which eventually calls `d_delete`.
Here's where the problem arises: **`inode_unlock` happens BEFORE `d_delete`**. That's the key. While the inode is unlocked, *another* process, let's say one running `configfs_symlink`, could attempt to acquire the *same* inode lock (`dir`)!
Now, picture this: `configfs_rmdir` has finished. It likely freed some resources, dropped its references. Then, the other process grabs the same inode lock *before* `d_delete` has run. Since `d_delete` hasn't run, the `dentry->d_parent` is *still* hashed. This means that the check `!d_unhashed(dentry->d_parent)` will return true. This is the first step on the path to disaster.
Then, `configfs_get_config_item(dentry->d_parent)` is called, it enters the `if (!d_unhashed(dentry))` branch. Inside that branch, `config_item_get` is called on `sd->s_element` which is the reference to an element that may have already been freed in `configfs_rmdir`! *That's* the crucial moment: `kref_get` on `sd->s_element`.
Because `configfs_rmdir` probably already dropped its reference and finished, and if `configfs_symlink` also dropped its reference before `inode_unlock`, the refcount will have dropped to zero, and the memory has likely been `kfree`d!
So `config_item_get()` will attempt a `kref_get()` on a freed memory location! That's a guaranteed Use-After-Free, an invalid refcount increment on a dangling pointer! I need to double-check `config_item_get`'s definition to confirm this, but based on the overall flow, I'm pretty certain this is a serious vulnerability.
|
| 201/4 |
2026/09/02 21:21 |
tool |
grepper |
0m
Args: map[Expression:config_item_get PathPrefix:include/linux]
Results: map[Output:include/linux/configfs.h=57=extern void config_item_init_type_name(struct config_item *item,
--
include/linux/configfs.h-60-
include/linux/configfs.h:61:extern struct config_item *config_item_get(struct config_item *);
include/linux/configfs.h:62:extern struct config_item *config_item_get_unless_zero(struct config_item *);
include/linux/configfs.h-63-extern void config_item_put(struct config_item *);
--
include/linux/configfs.h=95=static inline struct config_group *config_group_get(struct config_group *group)
include/linux/configfs.h-96-{
include/linux/configfs.h:97: return group ? to_config_group(config_item_get(&group->cg_item)) : NULL;
include/linux/configfs.h-98-}
]
|
| 202/4 |
2026/09/02 21:21 |
llm |
codeexpert |
0m
Model:
Tokens:
input: 37537
output: 37
thoughts: 37
Thoughts: Okay, let's dive into this. I need to refresh my memory on how `config_item_get` and `config_item_get_unless_zero` are implemented within the `fs/configfs/item.c` file. This is where the core logic for retrieving config items likely resides. I'm assuming that examining the definitions of these functions, especially their differences in handling zero values, will give me a good insight into the nuances of how the configfs subsystem manages and exposes its data. Let's see how they handle reference counting and potential error conditions, as that's often a key consideration when dealing with kernel-level resource management. Understanding the exact semantics of these functions is crucial for any code that interacts with configfs.
|
| 203/4 |
2026/09/02 21:21 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/configfs/item.c Name:config_item_get]
Results: map[SourceCode: 101: struct config_item *config_item_get(struct config_item *item)
102: {
103: if (item)
104: kref_get(&item->ci_kref);
105: return item;
106: }
SourceFile:fs/configfs/item.c]
|
| 204/4 |
2026/09/02 21:21 |
llm |
codeexpert |
0m
Model:
Tokens:
input: 37879
output: 37
thoughts: 154
Thoughts: **Analyzing Potential UAF in ConfigFS Symlink Handling**
Okay, so I'm digging into this `config_item_get` function. That initial `kref_get(&item->ci_kref)` is the crucial point, right? Because `kref_get` can trip up in a few ways. It'll hit either `WARN_ON_ONCE(!refcount_inc_not_zero(&kref->refcount))` or just a straight `refcount_inc`. The problem is, if the reference count is zero, or if it's somehow accessing freed memory, we're talking about a classic use-after-free situation, and that's going to trigger a BUG or at least a warning.
Wait a second, let's not jump to conclusions. Before I dive too deep, I need to get some context. I need to check the git history, see if this area has seen any fixes, patches, or even just discussions on the LKML. And I'm going to look for related bug reports β particularly around anything to do with `configfs_symlink` and potential use-after-free issues when it comes to the parent item, specifically around calls to `configfs_rmdir`. I'm thinking I'll need to search the logs with grep and Git to make sure I don't miss anything, looking for keywords like `configfs_symlink`, `d_unhashed`, `parent_item`, or `configfs_rmdir`. Gotta get a feel for whether this is a known issue.
|
| 205/4 |
2026/09/02 21:21 |
tool |
git-log |
0m
Args: map[CodeRegexp:configfs_symlink Count:10 PathPrefix:fs/configfs]
Results: map[Output:7a77db95511c fs: port ->symlink() to pass mnt_idmap
549c7297717c fs: make helpers idmap mount aware
e9c03af21cc7 configfs: calculate the symlink target only once
2743c515a123 configfs: make configfs_create() return inode
de6bf18e9ce0 [PATCH] configfs: Consolidate locking around configfs_detach_prep() in configfs_rmdir()
9a73d78cda75 [PATCH] configfs: Fix failing symlink() making rmdir() fail
754661f143e7 [PATCH] mark struct inode_operations const 1
7063fbf22611 [PATCH] configfs: User-driven configuration filesystem
]
|
| 206/4 |
2026/09/02 21:21 |
llm |
codeexpert |
0m
Model:
Tokens:
input: 38617
output: 33
thoughts: 2083
Thoughts: **Analysis of the `configfs_symlink` Vulnerability and Proposed "Fix"**
Alright, let's dissect this. My primary concern here is that `configfs_symlink` is vulnerable to a classic use-after-free scenario. The core problem boils down to a race condition between creating a symlink and the removal of the parent directory using `rmdir`. I'm seeing a scenario where a reference to the `parent_item` is acquired, the directory inode unlocked, and then a race begins where a separate `configfs_rmdir` can potentially tear down the `parent_item` while `configfs_symlink` is still running.
The proposed "fix" β dropping and re-acquiring the reference to `parent_item` before and after the inode lock β initially seems like a reasonable approach. However, I've got to be meticulous here. Based on my understanding of this system, this "fix" is neither safe nor complete, for the following reasons:
1. **Use-After-Free during Re-Acquisition:** If the `parent_item` is completely freed between the drop and re-acquisition, the re-acquisition itself becomes a use-after-free. `configfs_get_config_item` uses a dangling pointer to access the freed memory, leading to an immediate crash. This invalidates the entire premise.
2. **Detached Item Scenario:** Even if we prevent the complete free, what if `rmdir` detaches the item and calls `client_drop_item`? The system might not immediately free memory, but the item's internal state could be invalidated. The `parent_item` that `configfs_symlink` re-acquires is now detached or "dead." Subsequent operations operate on invalid data.
3. **Inode/Dentry State Validation:** The code lacks critical checks for the state of the directory inode and parent dentry. It doesn't verify if the parent directory has been marked as `S_DEAD`, `d_unhashed(dentry->d_parent)` or `CONFIGFS_USET_DROPPING` after an `rmdir` operation. This absence means the symlink could be created inside a deleted directory β a clear violation of VFS invariants.
4. **Configfs Lifecycle Invariants:** `configfs` has strong invariants related to the lifecycle of items. `configfs_symlink` essentially bypasses these safeguards. It allows operations on an item that is in the process of being detached, dropped, or potentially freed. This violates the established subsystem and driver interaction in `configfs`.
So, the issue is not just about pointer safety, but about the *state* of the object in question, and whether it is still valid within the configfs subsystem. This proposal misses the point. The "fix" is unsafe and incomplete. It doesn't solve the underlying data race, and it overlooks other state-related issues with the parent directory that are not immediately obvious. We need a more fundamental solution that addresses the concurrent operations and the validity of objects during directory removal, perhaps by synchronizing critical sections and adding better reference counting, as well as making sure all these checks are in place. Let's see how similar issues are handled elsewhere in the kernel to understand the best approach.
|
| 207/4 |
2026/09/02 21:21 |
tool |
grepper |
0m
Args: map[Expression:CONFIGFS_USET_DROPPING PathPrefix:fs/configfs]
Results: map[Output:fs/configfs/configfs_internal.h=30=struct configfs_dirent {
--
fs/configfs/configfs_internal.h-53-#define CONFIGFS_USET_DEFAULT 0x0080
fs/configfs/configfs_internal.h:54:#define CONFIGFS_USET_DROPPING 0x0100
fs/configfs/configfs_internal.h-55-#define CONFIGFS_USET_IN_MKDIR 0x0200
--
fs/configfs/dir.c-30- *
fs/configfs/dir.c:31: * Protects setting of CONFIGFS_USET_DROPPING: checking the flag
fs/configfs/dir.c-32- * unlocked is not reliable unless in detach_groups() called from
--
fs/configfs/dir.c=187=static struct configfs_dirent *configfs_new_dirent(struct configfs_dirent *parent_sd,
--
fs/configfs/dir.c-202- spin_lock(&configfs_dirent_lock);
fs/configfs/dir.c:203: if (parent_sd->s_type & CONFIGFS_USET_DROPPING) {
fs/configfs/dir.c-204- spin_unlock(&configfs_dirent_lock);
--
fs/configfs/dir.c=426=static struct dentry * configfs_lookup(struct inode *dir,
--
fs/configfs/dir.c-496- * attributes and are removed by rmdir(). We recurse, setting
fs/configfs/dir.c:497: * CONFIGFS_USET_DROPPING on all children that are candidates for
fs/configfs/dir.c-498- * default detach.
--
fs/configfs/dir.c=502=static int configfs_detach_prep(struct configfs_dirent *parent_sd, struct dentry **wait)
--
fs/configfs/dir.c-507- /* Mark that we're trying to drop the group */
fs/configfs/dir.c:508: parent_sd->s_type |= CONFIGFS_USET_DROPPING;
fs/configfs/dir.c-509-
--
fs/configfs/dir.c-544-/*
fs/configfs/dir.c:545: * Walk the tree, resetting CONFIGFS_USET_DROPPING wherever it was
fs/configfs/dir.c-546- * set.
--
fs/configfs/dir.c=548=static void configfs_detach_rollback(struct configfs_dirent *parent_sd)
--
fs/configfs/dir.c-551-
fs/configfs/dir.c:552: parent_sd->s_type &= ~CONFIGFS_USET_DROPPING;
fs/configfs/dir.c-553-
--
fs/configfs/dir.c=977=static void configfs_dump_one(struct configfs_dirent *sd, int level)
--
fs/configfs/dir.c-987- type_print(CONFIGFS_USET_DEFAULT);
fs/configfs/dir.c:988: type_print(CONFIGFS_USET_DROPPING);
fs/configfs/dir.c-989-#undef type_print
--
fs/configfs/dir.c=1071=static int configfs_depend_prep(struct configfs_dirent *sd,
--
fs/configfs/dir.c-1081- if ((child_sd->s_type & CONFIGFS_DIR) &&
fs/configfs/dir.c:1082: !(child_sd->s_type & CONFIGFS_USET_DROPPING) &&
fs/configfs/dir.c-1083- !(child_sd->s_type & CONFIGFS_USET_CREATING)) {
--
fs/configfs/dir.c=1289=static struct dentry *configfs_mkdir(struct mnt_idmap *idmap, struct inode *dir,
--
fs/configfs/dir.c-1412- * Make racing rmdir() fail if it did not tag parent with
fs/configfs/dir.c:1413: * CONFIGFS_USET_DROPPING
fs/configfs/dir.c:1414: * Note: if CONFIGFS_USET_DROPPING is already set, attach_group() will
fs/configfs/dir.c-1415- * fail and let rmdir() terminate correctly
--
fs/configfs/symlink.c=77=static int create_link(struct config_item *parent_item,
--
fs/configfs/symlink.c-93- spin_lock(&configfs_dirent_lock);
fs/configfs/symlink.c:94: if (target_sd->s_type & CONFIGFS_USET_DROPPING) {
fs/configfs/symlink.c-95- spin_unlock(&configfs_dirent_lock);
]
|
| 208/4 |
2026/09/02 21:21 |
llm |
codeexpert |
0m
Model:
Tokens:
input: 42710
output: 30
thoughts: 1796
Thoughts: **Analysis of a Potential ConfigFS Race Condition and Why a Proposed Fix Fails**
Okay, let's break this down. I'm looking at a potential race condition within `configfs_symlink` related to directory removal and symlink creation. My concern revolves around the interaction of locking, reference counting, and the `CONFIGFS_USET_DROPPING` flag.
First, I'm tracing what happens in `create_link`. I see `create_link` checks the `CONFIGFS_USET_DROPPING` flag on the `target_sd` under a lock (`configfs_dirent_lock`). It also increments `s_links`. In `configfs_rmdir`, a similar lock is held. `configfs_detach_prep` checks `parent_sd->s_links`. In `create_link` locks `configfs_symlink_mutex` and `configfs_dirent_lock` to check `s_type & CONFIGFS_USET_DROPPING`.
Now, the core issue seems to stem from the fact that `configfs_symlink` unlocks the `dir` (the parent directory of the symlink) between operations. This opens a window for `rmdir` to run on the parent directory, potentially removing it. This is where it gets tricky. In `configfs_rmdir`, I see it checks `sd->s_dependent_count`. Then `configfs_detach_prep(sd)` sets the `CONFIGFS_USET_DROPPING` flag. Once the directory is set to `DROPPING`, the directory drops and detaches items.
Now, if we consider `configfs_symlink` re-acquiring a reference to the `parent_item` after it re-locks `dir`: If the refcount WAS held, we have a potential UAF situation. `parent_item = configfs_get_config_item(dentry->d_parent)` refcount holds memory alive via `parent_item = configfs_get_config_item(dentry->d_parent)`. `parent_item` struct memory itself is kept alive by `parent_item = configfs_get_config_item(dentry->d_parent)`.
Even if the refcount is held, the driver object might have had its resources freed. Specifically, the steps are that `parent_item` was detached and `client_drop_item(grandparent_item, parent_item)` was called, allowing the underlying driver object to have its resources freed (UAF in `allow_link` or other driver callbacks). Also, the directory, `dir`, has the `S_DEAD` flag set. Finally, `configfs_symlink` doesn't check `d_unhashed(dentry->d_parent)`, `IS_DEADDIR(dir)`, or `sd->s_type & CONFIGFS_USET_DROPPING`.
Now, if a solution is proposed to drop the reference to `parent_item` *before* `inode_unlock(dir)` and re-acquire it after `inode_lock(dir)`, I see big problems.
This *won't work*, and actually introduces worse problems! Here's why:
1. **Immediate Use-After-Free (UAF) during reacquisition:** If the reference is dropped before unlocking, and `configfs_rmdir` runs concurrently, it could free `parent_item`'s memory *immediately* because the dropped reference caused its refcount to reach zero. When `configfs_symlink` tries to re-acquire the item via `configfs_get_config_item`, it's attempting to use a dangling pointer in `sd->s_element` to access the now-freed `parent_item`'s memory via `kref_get(&item->ci_kref)`. That's a direct UAF.
2. **Incompleteness: Doesn't prevent operating on a removed parent:** Even if the parent isn't *fully* freed, re-acquiring it might succeed, but the directory is already dead (`S_DEAD`, unlinked). The re-acquired `parent_item` *doesn't* tell us if the directory was deleted. `configfs_symlink` would blindly continue, calling `allow_link`, `create_link`, etc., operating on a deleted directory and invoking driver callbacks on a dropped config\_item.
3. **Wrong Synchronization Model**: The current fundamental issue is that unlocking `inode_lock(dir)` opens the window for concurrent removal. Dropping the refcount *before* unlocking and re-acquiring *after* creates an *unconditional* lifecycle violation. The parent object can be freed from under us when unlocked, leaving a dangling pointer in
|
| 209/4 |
2026/09/02 21:21 |
tool |
grepper |
0m
Args: map[Expression:CONFIGFS_USET_ PathPrefix:fs/configfs]
Results: map[Output:fs/configfs/configfs_internal.h=30=struct configfs_dirent {
--
fs/configfs/configfs_internal.h-51-#define CONFIGFS_ITEM_LINK 0x0020
fs/configfs/configfs_internal.h:52:#define CONFIGFS_USET_DIR 0x0040
fs/configfs/configfs_internal.h:53:#define CONFIGFS_USET_DEFAULT 0x0080
fs/configfs/configfs_internal.h:54:#define CONFIGFS_USET_DROPPING 0x0100
fs/configfs/configfs_internal.h:55:#define CONFIGFS_USET_IN_MKDIR 0x0200
fs/configfs/configfs_internal.h:56:#define CONFIGFS_USET_CREATING 0x0400
fs/configfs/configfs_internal.h-57-#define CONFIGFS_NOT_PINNED (CONFIGFS_ITEM_ATTR | CONFIGFS_ITEM_BIN_ATTR)
--
fs/configfs/dir.c-30- *
fs/configfs/dir.c:31: * Protects setting of CONFIGFS_USET_DROPPING: checking the flag
fs/configfs/dir.c-32- * unlocked is not reliable unless in detach_groups() called from
--
fs/configfs/dir.c=187=static struct configfs_dirent *configfs_new_dirent(struct configfs_dirent *parent_sd,
--
fs/configfs/dir.c-202- spin_lock(&configfs_dirent_lock);
fs/configfs/dir.c:203: if (parent_sd->s_type & CONFIGFS_USET_DROPPING) {
fs/configfs/dir.c-204- spin_unlock(&configfs_dirent_lock);
--
fs/configfs/dir.c=293=static int configfs_create_dir(struct config_item *item, struct dentry *dentry,
--
fs/configfs/dir.c-304- error = configfs_make_dirent(p->d_fsdata, dentry, item, mode,
fs/configfs/dir.c:305: CONFIGFS_DIR | CONFIGFS_USET_CREATING,
fs/configfs/dir.c-306- frag);
--
fs/configfs/dir.c=338=static void configfs_dir_set_ready(struct configfs_dirent *sd)
--
fs/configfs/dir.c-341-
fs/configfs/dir.c:342: sd->s_type &= ~CONFIGFS_USET_CREATING;
fs/configfs/dir.c-343- list_for_each_entry(child_sd, &sd->s_children, s_sibling)
fs/configfs/dir.c:344: if (child_sd->s_type & CONFIGFS_USET_CREATING)
fs/configfs/dir.c-345- configfs_dir_set_ready(child_sd);
--
fs/configfs/dir.c=358=int configfs_dirent_is_ready(struct configfs_dirent *sd)
--
fs/configfs/dir.c-362- spin_lock(&configfs_dirent_lock);
fs/configfs/dir.c:363: ret = !(sd->s_type & CONFIGFS_USET_CREATING);
fs/configfs/dir.c-364- spin_unlock(&configfs_dirent_lock);
--
fs/configfs/dir.c=426=static struct dentry * configfs_lookup(struct inode *dir,
--
fs/configfs/dir.c-496- * attributes and are removed by rmdir(). We recurse, setting
fs/configfs/dir.c:497: * CONFIGFS_USET_DROPPING on all children that are candidates for
fs/configfs/dir.c-498- * default detach.
--
fs/configfs/dir.c=502=static int configfs_detach_prep(struct configfs_dirent *parent_sd, struct dentry **wait)
--
fs/configfs/dir.c-507- /* Mark that we're trying to drop the group */
fs/configfs/dir.c:508: parent_sd->s_type |= CONFIGFS_USET_DROPPING;
fs/configfs/dir.c-509-
--
fs/configfs/dir.c-518- continue;
fs/configfs/dir.c:519: if (sd->s_type & CONFIGFS_USET_DEFAULT) {
fs/configfs/dir.c-520- /* Abort if racing with mkdir() */
fs/configfs/dir.c:521: if (sd->s_type & CONFIGFS_USET_IN_MKDIR) {
fs/configfs/dir.c-522- if (wait)
--
fs/configfs/dir.c-544-/*
fs/configfs/dir.c:545: * Walk the tree, resetting CONFIGFS_USET_DROPPING wherever it was
fs/configfs/dir.c-546- * set.
--
fs/configfs/dir.c=548=static void configfs_detach_rollback(struct configfs_dirent *parent_sd)
--
fs/configfs/dir.c-551-
fs/configfs/dir.c:552: parent_sd->s_type &= ~CONFIGFS_USET_DROPPING;
fs/configfs/dir.c-553-
fs/configfs/dir.c-554- list_for_each_entry(sd, &parent_sd->s_children, s_sibling)
fs/configfs/dir.c:555: if (sd->s_type & CONFIGFS_USET_DEFAULT)
fs/configfs/dir.c-556- configfs_detach_rollback(sd);
--
fs/configfs/dir.c=664=static void detach_groups(struct dentry *dentry)
--
fs/configfs/dir.c-673- next = next_dirent(parent_sd, sd);
fs/configfs/dir.c:674: if (!(sd->s_type & CONFIGFS_USET_DEFAULT))
fs/configfs/dir.c-675- continue;
--
fs/configfs/dir.c=703=static int create_default_group(struct dentry *parent,
--
fs/configfs/dir.c-722- sd = child->d_fsdata;
fs/configfs/dir.c:723: sd->s_type |= CONFIGFS_USET_DEFAULT;
fs/configfs/dir.c-724- } else {
--
fs/configfs/dir.c=891=static int configfs_attach_group(struct config_item *item,
--
fs/configfs/dir.c-900- sd = dentry->d_fsdata;
fs/configfs/dir.c:901: sd->s_type |= CONFIGFS_USET_DIR;
fs/configfs/dir.c-902-
--
fs/configfs/dir.c=977=static void configfs_dump_one(struct configfs_dirent *sd, int level)
--
fs/configfs/dir.c-985- type_print(CONFIGFS_ITEM_LINK);
fs/configfs/dir.c:986: type_print(CONFIGFS_USET_DIR);
fs/configfs/dir.c:987: type_print(CONFIGFS_USET_DEFAULT);
fs/configfs/dir.c:988: type_print(CONFIGFS_USET_DROPPING);
fs/configfs/dir.c-989-#undef type_print
--
fs/configfs/dir.c=992=static int configfs_dump(struct configfs_dirent *sd, int level)
--
fs/configfs/dir.c-1058- * Note: subdirectories in the middle of attachment start with s_type =
fs/configfs/dir.c:1059: * CONFIGFS_DIR|CONFIGFS_USET_CREATING set by create_dir(). When
fs/configfs/dir.c:1060: * CONFIGFS_USET_CREATING is set, we ignore the item. The actual set of
fs/configfs/dir.c-1061- * s_type is in configfs_new_dirent(), which has configfs_dirent_lock.
--
fs/configfs/dir.c=1071=static int configfs_depend_prep(struct configfs_dirent *sd,
--
fs/configfs/dir.c-1081- if ((child_sd->s_type & CONFIGFS_DIR) &&
fs/configfs/dir.c:1082: !(child_sd->s_type & CONFIGFS_USET_DROPPING) &&
fs/configfs/dir.c:1083: !(child_sd->s_type & CONFIGFS_USET_CREATING)) {
fs/configfs/dir.c-1084- ret = configfs_depend_prep(child_sd, target);
--
fs/configfs/dir.c=1289=static struct dentry *configfs_mkdir(struct mnt_idmap *idmap, struct inode *dir,
--
fs/configfs/dir.c-1318-
fs/configfs/dir.c:1319: if (!(sd->s_type & CONFIGFS_USET_DIR)) {
fs/configfs/dir.c-1320- ret = -EPERM;
--
fs/configfs/dir.c-1412- * Make racing rmdir() fail if it did not tag parent with
fs/configfs/dir.c:1413: * CONFIGFS_USET_DROPPING
fs/configfs/dir.c:1414: * Note: if CONFIGFS_USET_DROPPING is already set, attach_group() will
fs/configfs/dir.c-1415- * fail and let rmdir() terminate correctly
--
fs/configfs/dir.c-1418- /* This will make configfs_detach_prep() fail */
fs/configfs/dir.c:1419: sd->s_type |= CONFIGFS_USET_IN_MKDIR;
fs/configfs/dir.c-1420- spin_unlock(&configfs_dirent_lock);
--
fs/configfs/dir.c-1427- spin_lock(&configfs_dirent_lock);
fs/configfs/dir.c:1428: sd->s_type &= ~CONFIGFS_USET_IN_MKDIR;
fs/configfs/dir.c-1429- if (!ret)
--
fs/configfs/dir.c=1469=static int configfs_rmdir(struct inode *dir, struct dentry *dentry)
--
fs/configfs/dir.c-1479- sd = dentry->d_fsdata;
fs/configfs/dir.c:1480: if (sd->s_type & CONFIGFS_USET_DEFAULT)
fs/configfs/dir.c-1481- return -EPERM;
--
fs/configfs/dir.c-1552-
fs/configfs/dir.c:1553: if (sd->s_type & CONFIGFS_USET_DIR) {
fs/configfs/dir.c-1554- configfs_detach_group(dentry);
--
fs/configfs/symlink.c=77=static int create_link(struct config_item *parent_item,
--
fs/configfs/symlink.c-93- spin_lock(&configfs_dirent_lock);
fs/configfs/symlink.c:94: if (target_sd->s_type & CONFIGFS_USET_DROPPING) {
fs/configfs/symlink.c-95- spin_unlock(&configfs_dirent_lock);
]
|
| 210/4 |
2026/09/02 21:21 |
llm |
codeexpert |
0m
Model:
Tokens:
input: 48657
output: 36
thoughts: 30
Thoughts: Okay, here's my take on those thoughts, framed as an expert's internal monologue:
**Investigating a ConfigFS Race Condition**
Alright, let's dive into this. I've been hearing whispers of potential race conditions in `configfs`, specifically around directory creation and deletion. My initial thought is to zoom in on `configfs_mkdir`. The name itself is a clue; it's the function responsible for creating directories within the ConfigFS file system. My hypothesis is that the interaction between `mkdir` (creation) and `rmdir` (deletion) is the likely source of this instability. That's the area I want to focus on. So, the first step is clear: I need to scrutinize `configfs_mkdir` itself. I need to understand exactly how it handles concurrent calls, specifically where it's vulnerable to being interrupted by the removal of the directory it's trying to create. I want to read the code with a critical eye, looking for locks, atomic operations, and any potential window of vulnerability where a deletion could slip in undetected, causing a crash, corruption, or unexpected behavior. This has the hallmarks of a classic race, and I need to prove it or eliminate the possibility.
|
| 211/4 |
2026/09/02 21:21 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/configfs/dir.c Name:configfs_mkdir]
Results: map[SourceCode:1289: static struct dentry *configfs_mkdir(struct mnt_idmap *idmap, struct inode *dir,
1290: struct dentry *dentry, umode_t mode)
1291: {
1292: int ret = 0;
1293: int module_got = 0;
1294: struct config_group *group = NULL;
1295: struct config_item *item = NULL;
1296: struct config_item *parent_item;
1297: struct configfs_subsystem *subsys;
1298: struct configfs_dirent *sd;
1299: const struct config_item_type *type;
1300: struct module *subsys_owner = NULL, *new_item_owner = NULL;
1301: struct configfs_fragment *frag;
1302: struct name_snapshot n;
1303: const char *name;
1304:
1305: take_dentry_name_snapshot(&n, dentry);
1306: name = n.name.name;
1307:
1308: sd = dentry->d_parent->d_fsdata;
1309:
1310: /*
1311: * Fake invisibility if dir belongs to a group/default groups hierarchy
1312: * being attached
1313: */
1314: if (!configfs_dirent_is_ready(sd)) {
1315: ret = -ENOENT;
1316: goto out;
1317: }
1318:
1319: if (!(sd->s_type & CONFIGFS_USET_DIR)) {
1320: ret = -EPERM;
1321: goto out;
1322: }
1323:
1324: frag = new_fragment();
1325: if (!frag) {
1326: ret = -ENOMEM;
1327: goto out;
1328: }
1329:
1330: /* Get a working ref for the duration of this function */
1331: parent_item = configfs_get_config_item(dentry->d_parent);
1332: type = parent_item->ci_type;
1333: subsys = to_config_group(parent_item)->cg_subsys;
1334: BUG_ON(!subsys);
1335:
1336: if (!type || !type->ct_group_ops ||
1337: (!type->ct_group_ops->make_group &&
1338: !type->ct_group_ops->make_item)) {
1339: ret = -EPERM; /* Lack-of-mkdir returns -EPERM */
1340: goto out_put;
1341: }
1342:
1343: /*
1344: * The subsystem may belong to a different module than the item
1345: * being created. We don't want to safely pin the new item but
1346: * fail to pin the subsystem it sits under.
1347: */
1348: if (!subsys->su_group.cg_item.ci_type) {
1349: ret = -EINVAL;
1350: goto out_put;
1351: }
1352: subsys_owner = subsys->su_group.cg_item.ci_type->ct_owner;
1353: if (!try_module_get(subsys_owner)) {
1354: ret = -EINVAL;
1355: goto out_put;
1356: }
1357:
1358: mutex_lock(&subsys->su_mutex);
1359: if (type->ct_group_ops->make_group) {
1360: group = type->ct_group_ops->make_group(to_config_group(parent_item), name);
1361: if (!group)
1362: group = ERR_PTR(-ENOMEM);
1363: if (!IS_ERR(group)) {
1364: link_group(to_config_group(parent_item), group);
1365: item = &group->cg_item;
1366: } else
1367: ret = PTR_ERR(group);
1368: } else {
1369: item = type->ct_group_ops->make_item(to_config_group(parent_item), name);
1370: if (!item)
1371: item = ERR_PTR(-ENOMEM);
1372: if (!IS_ERR(item))
1373: link_obj(parent_item, item);
1374: else
1375: ret = PTR_ERR(item);
1376: }
1377: mutex_unlock(&subsys->su_mutex);
1378:
1379: if (ret) {
1380: /*
1381: * If ret != 0, then link_obj() was never called.
1382: * There are no extra references to clean up.
1383: */
1384: goto out_subsys_put;
1385: }
1386:
1387: /*
1388: * link_obj() has been called (via link_group() for groups).
1389: * From here on out, errors must clean that up.
1390: */
1391:
1392: type = item->ci_type;
1393: if (!type) {
1394: ret = -EINVAL;
1395: goto out_unlink;
1396: }
1397:
1398: new_item_owner = type->ct_owner;
1399: if (!try_module_get(new_item_owner)) {
1400: ret = -EINVAL;
1401: goto out_unlink;
1402: }
1403:
1404: /*
1405: * I hate doing it this way, but if there is
1406: * an error, module_put() probably should
1407: * happen after any cleanup.
1408: */
1409: module_got = 1;
1410:
1411: /*
1412: * Make racing rmdir() fail if it did not tag parent with
1413: * CONFIGFS_USET_DROPPING
1414: * Note: if CONFIGFS_USET_DROPPING is already set, attach_group() will
1415: * fail and let rmdir() terminate correctly
1416: */
1417: spin_lock(&configfs_dirent_lock);
1418: /* This will make configfs_detach_prep() fail */
1419: sd->s_type |= CONFIGFS_USET_IN_MKDIR;
1420: spin_unlock(&configfs_dirent_lock);
1421:
1422: if (group)
1423: ret = configfs_attach_group(item, dentry, frag);
1424: else
1425: ret = configfs_attach_item(item, dentry, frag);
1426:
1427: spin_lock(&configfs_dirent_lock);
1428: sd->s_type &= ~CONFIGFS_USET_IN_MKDIR;
1429: if (!ret)
1430: configfs_dir_set_ready(dentry->d_fsdata);
1431: spin_unlock(&configfs_dirent_lock);
1432:
1433: out_unlink:
1434: if (ret) {
1435: /* Tear down everything we built up */
1436: mutex_lock(&subsys->su_mutex);
1437:
1438: client_disconnect_notify(parent_item, item);
1439: if (group)
1440: unlink_group(group);
1441: else
1442: unlink_obj(item);
1443: client_drop_item(parent_item, item);
1444:
1445: mutex_unlock(&subsys->su_mutex);
1446:
1447: if (module_got)
1448: module_put(new_item_owner);
1449: }
1450:
1451: out_subsys_put:
1452: if (ret)
1453: module_put(subsys_owner);
1454:
1455: out_put:
1456: /*
1457: * link_obj()/link_group() took a reference from child->parent,
1458: * so the parent is safely pinned. We can drop our working
1459: * reference.
1460: */
1461: config_item_put(parent_item);
1462: put_fragment(frag);
1463:
1464: out:
1465: release_dentry_name_snapshot(&n);
1466: return ERR_PTR(ret);
1467: }
SourceFile:fs/configfs/dir.c]
|
| 212/4 |
2026/09/02 21:21 |
llm |
codeexpert |
0m
Model:
Tokens:
input: 51570
output: 27
thoughts: 1329
Thoughts: **Analyzing the Safety of a Proposed Fix for `configfs_symlink` and `configfs_rmdir` Race**
Okay, let's break this down. We're looking at a potential use-after-free issue in `configfs_symlink`, specifically how it interacts with `configfs_rmdir`. The core problem, as I see it, is that `configfs_symlink` temporarily unlocks the parent directory (`dir`) to resolve the symlink target. This exposes a window where `configfs_rmdir` can swoop in, remove the parent, and potentially free the underlying `parent_item` before `configfs_symlink` re-acquires it.
The proposed fix β dropping the `parent_item` reference before unlocking, and re-acquiring it after relocking β *sounds* like it could work on the surface, but a closer examination reveals it's fundamentally flawed.
First, it's *not* safe. If we drop the `parent_item` reference before unlocking `dir`, there's a very real chance that `configfs_rmdir` can free that item immediately. When `configfs_symlink` tries to get the item again *after* relocking `dir`, it'll access a dangling pointer to freed memory within `configfs_get_config_item()` β a guaranteed crash. It's a classic use-after-free scenario, leading to a kernel panic. Specifically, the line `config_item_get(sd->s_element)` becomes the culprit as it attempts to increment the refcount of freed memory and leads to a crash.
Second, it's not even a *complete* fix. Even if the `parent_item` miraculously survives the unlock/relock cycle, the proposed solution doesn't account for the lifecycle of the parent directory. `configfs_rmdir` doesn't just remove the directory; it also detaches it, notifies clients (with `client_disconnect_notify()` and `client_drop_item()`), and marks the inode as `S_DEAD`. So, just re-acquiring a reference *doesn't* magically resurrect the directory. `configfs_symlink` would happily continue, potentially calling `allow_link()` on an item that has already been torn down by the client driver. This could lead to *driver-specific* use-after-free issues or state corruption within the driver, because the item's client specific setup will have changed, but symlink will be unaware. We'd then proceed to create the symlink in an orphaned dentry hierarchy. If the link fails, we'd then try to drop the link on an item that has already been dropped, which is also bad.
So, what's the real solution?
We need to keep the `parent_item` reference alive (or use a proper pin mechanism) across the unlock/relock cycle, so the memory remains valid. Crucially, after relocking, we *must* check if the parent directory has been removed (or marked as dead). The easiest check might be `d_unhashed(dentry->d_parent)`. We could also check `dir->i_flags` for `S_DEAD`, or perhaps inspect `sd->s_type` with a bitwise `CONFIGFS_USET_DROPPING` on `configfs_dirent_lock`. If we detect that the parent is gone or dead, we need to abort with an error (like `-ENOENT` or `-EEXIST`) and clean up gracefully. That's the only way to avoid the race.
Let me just double-check, let's search for `IS_DEADDIR` to confirm that's the way to go, though.
|
| 213/4 |
2026/09/02 21:21 |
tool |
grepper |
0m
Args: map[Expression:IS_DEADDIR PathPrefix:include/linux]
Results: map[Output:include/linux/fs.h=2089=extern loff_t vfs_dedupe_file_range_one(struct file *src_file, loff_t src_pos,
--
include/linux/fs.h-2153-
include/linux/fs.h:2154:#define IS_DEADDIR(inode) ((inode)->i_flags & S_DEAD)
include/linux/fs.h-2155-#define IS_NOCMTIME(inode) ((inode)->i_flags & S_NOCMTIME)
--
include/linux/fs.h=3633=static inline bool dir_relax(struct inode *inode)
--
include/linux/fs.h-3636- inode_lock(inode);
include/linux/fs.h:3637: return !IS_DEADDIR(inode);
include/linux/fs.h-3638-}
--
include/linux/fs.h=3640=static inline bool dir_relax_shared(struct inode *inode)
--
include/linux/fs.h-3643- inode_lock_shared(inode);
include/linux/fs.h:3644: return !IS_DEADDIR(inode);
include/linux/fs.h-3645-}
]
|
| 214/4 |
2026/09/02 21:21 |
llm |
codeexpert |
0m
Model:
Tokens:
input: 54051
output: 25
thoughts: 149
Thoughts: **Analyzing Directory Inode Locking and Death Flags**
Okay, so I'm diving into this `dir_relax` function in `include/linux/fs.h`. It's pretty straightforward, but the implications are interesting. I see that it's designed to relax the locking on a directory inode. It does this by unlocking and then immediately relocking the inode, a common pattern. The key part is that it returns `!IS_DEADDIR(inode)`. That means it's checking if the directory is *not* a "dead" directory after this unlock/relock cycle.
Now, `IS_DEADDIR(inode)` itself is the real focus. It essentially boils down to checking a single flag: `((inode)->i_flags & S_DEAD)`. So, the function is designed to check the directory inodeβs "death" flag and conditionally return a value based on the flag's status. It signals a condition.
This implies there's some asynchronous process or race condition where a directory might be marked as dead between the unlock and relock. So, I need to see *why* and *where* this death flag is set. That means it's time to follow the usages of `dir_relax` or `IS_DEADDIR` throughout the VFS. I need to understand the context of the lock relaxation and the directory's "death" state. Let's see how that flag is being used and set.
|
| 215/4 |
2026/09/02 21:21 |
tool |
grepper |
0m
Args: map[Expression:dir_relax PathPrefix:fs/]
Results: map[Output:fs/ext4/dir.c=130=static int ext4_readdir(struct file *file, struct dir_context *ctx)
--
fs/ext4/dir.c-330-next_block:
fs/ext4/dir.c:331: if ((ctx->pos < inode->i_size) && !dir_relax_shared(inode))
fs/ext4/dir.c-332- goto done;
]
|
| 216/4 |
2026/09/02 21:21 |
llm |
codeexpert |
0m
Model:
Tokens:
input: 54652
output: 1000
thoughts: 1021
Thoughts: **Analyzing the Proposed Configfs Symlink Modification**
Okay, so I've been looking at this proposed change to how symlinks are handled in `configfs`, specifically around reference counting and lock management during the `create_link` function. My initial thought is that the suggested approach of dropping and re-acquiring the reference to `parent_item` before and after `inode_unlock(dir)` is *not* a clean solution. I'm seeing a potential for a serious use-after-free or reference count corruption, and the proposed changes are also incomplete in terms of covering all the possible failure states.
My concern is centered around this: if `configfs_symlink` releases its reference to `parent_item` before unlocking the inode, a concurrent `rmdir` operation on the parent directory could completely remove it from the system. `configfs_rmdir` would run, decrement the reference count, and potentially free the memory associated with `parent_item`. Then, when the `symlink` operation relocks the inode and attempts to reacquire the parent item via `configfs_get_config_item(dentry->d_parent)`, it's reading from a freed memory location, leading to a UAF or corrupted reference count. This is exacerbated because `vfs_rmdir` unlocks the parent directory *before* it unhashes the dentry, so the dentry may still be present even after `rmdir` has finished.
Also, it doesn't account for the directory itself being in a state of deletion. Even if the item itself remains, the directory might be marked as dead (`S_DEAD`) or might be in the process of being unhashed. My feeling is that `configfs_symlink` needs to detect this state after re-acquiring the lock. Without those additional checks, even if reacquisition of the reference is technically successful, it might involve invoking methods like `allow_link(parent_item, target_item)` on an object that's in the process of teardown. That would be problematic.
What I think is needed here is a much stricter approach to reference management. The reference to `parent_item` absolutely needs to be held *throughout* the lock release and reacquisition phase. And, after regaining the inode lock, there's a need to validate that the parent directory hasn't been removed or marked for deletion by checking `IS_DEADDIR(dir)`, `d_unhashed(dentry->d_parent)`, and/or `CONFIGFS_USET_DROPPING`. If either of these states are found, `configfs_symlink` needs to abort early with an error code and release its reference. This is a much safer, more robust approach. I'll need to call `set-results` tool now.
|
| 217/4 |
2026/09/02 21:22 |
tool |
set-results |
0m
Args: map[Answer:Dropping the reference to `parent_item` before `inode_unlock(dir)` and re-acquiring it after `inode_lock(dir)` is **neither safe nor complete**.
### 1. Why it is not safe (Introduces an immediate Use-After-Free)
* In `configfs_symlink()`, if the reference to `parent_item` is dropped prior to `inode_unlock(dir)`:
* When a concurrent `rmdir` executes on `dir`, `configfs_rmdir()` detaches the item and drops its own reference with `config_item_put(item)`.
* With no other references remaining, the `parent_item`'s refcount drops to zero and its release callback runs (freeing the memory of `parent_item` and its container struct via `kfree`).
* When `configfs_symlink()` re-locks `dir` (`inode_lock(dir)`) and attempts to re-acquire the item via `configfs_get_config_item(dentry->d_parent)`:
```c
static inline struct config_item *configfs_get_config_item(struct dentry *dentry)
{
struct config_item * item = NULL;
spin_lock(&dentry->d_lock);
if (!d_unhashed(dentry)) {
struct configfs_dirent * sd = dentry->d_fsdata;
item = config_item_get(sd->s_element);
}
spin_unlock(&dentry->d_lock);
return item;
}
```
* In `vfs_rmdir()`, `inode_unlock(dentry->d_inode)` is called *before* `d_delete(dentry)`. Consequently, at the point `configfs_symlink()` re-acquires `inode_lock(dir)`, `dentry->d_parent` may still not be unhashed (`!d_unhashed(dentry->d_parent)` is true).
* `sd->s_element` is now a dangling pointer to the already-freed `parent_item`. Calling `config_item_get(sd->s_element)` (which does `kref_get(&item->ci_kref)`) dereferences and increments the refcount of freed memory, causing an immediate use-after-free and memory corruption.
### 2. Why it is not complete (Fails to handle deletion/lifecycle state)
* Dropping and re-acquiring the reference does nothing to detect or handle whether the parent directory was deleted while `dir` was unlocked.
* Even if an external reference kept `parent_item` memory alive, `configfs_rmdir()` will have already:
* Unlinked the item (`unlink_group()` / `unlink_obj()`).
* Notified and torn down driver structures via `client_drop_item(parent_item, item)`.
* Marked the directory inode as dead (`dir->i_flags |= S_DEAD`).
* Merely re-fetching `parent_item` without validating directory state will cause `configfs_symlink()` to continue execution:
* Calling `type->ct_item_ops->allow_link(parent_item, target_item)` on an object that the subsystem/driver already tore down in `drop_item()` (causing driver-level UAF or corruption).
* Calling `create_link()` and attaching a symlink into an orphaned/dead directory hierarchy.
* Calling `type->ct_item_ops->drop_link()` on failure on a dropped object.
### 3. What the proper fix requires
1. **Hold the reference across the unlock/relock**: `configfs_symlink()` must keep its reference to `parent_item` to pin the memory structure.
2. **Revalidate parent state after `inode_lock(dir)`**: After re-acquiring `inode_lock(dir)`, check whether the parent directory was deleted or is dropping during the unlocked window (e.g., checking `IS_DEADDIR(dir)`, `d_unhashed(dentry->d_parent)`, or `CONFIGFS_USET_DROPPING` on the parent's `configfs_dirent` under `configfs_dirent_lock`). If dead/dropping, `configfs_symlink()` must abort early with an error (such as `-ENOENT`) and release the reference.]
Results: map[Answer:Dropping the reference to `parent_item` before `inode_unlock(dir)` and re-acquiring it after `inode_lock(dir)` is **neither safe nor complete**.
### 1. Why it is not safe (Introduces an immediate Use-After-Free)
* In `configfs_symlink()`, if the reference to `parent_item` is dropped prior to `inode_unlock(dir)`:
* When a concurrent `rmdir` executes on `dir`, `configfs_rmdir()` detaches the item and drops its own reference with `config_item_put(item)`.
* With no other references remaining, the `parent_item`'s refcount drops to zero and its release callback runs (freeing the memory of `parent_item` and its container struct via `kfree`).
* When `configfs_symlink()` re-locks `dir` (`inode_lock(dir)`) and attempts to re-acquire the item via `configfs_get_config_item(dentry->d_parent)`:
```c
static inline struct config_item *configfs_get_config_item(struct dentry *dentry)
{
struct config_item * item = NULL;
spin_lock(&dentry->d_lock);
if (!d_unhashed(dentry)) {
struct configfs_dirent * sd = dentry->d_fsdata;
item = config_item_get(sd->s_element);
}
spin_unlock(&dentry->d_lock);
return item;
}
```
* In `vfs_rmdir()`, `inode_unlock(dentry->d_inode)` is called *before* `d_delete(dentry)`. Consequently, at the point `configfs_symlink()` re-acquires `inode_lock(dir)`, `dentry->d_parent` may still not be unhashed (`!d_unhashed(dentry->d_parent)` is true).
* `sd->s_element` is now a dangling pointer to the already-freed `parent_item`. Calling `config_item_get(sd->s_element)` (which does `kref_get(&item->ci_kref)`) dereferences and increments the refcount of freed memory, causing an immediate use-after-free and memory corruption.
### 2. Why it is not complete (Fails to handle deletion/lifecycle state)
* Dropping and re-acquiring the reference does nothing to detect or handle whether the parent directory was deleted while `dir` was unlocked.
* Even if an external reference kept `parent_item` memory alive, `configfs_rmdir()` will have already:
* Unlinked the item (`unlink_group()` / `unlink_obj()`).
* Notified and torn down driver structures via `client_drop_item(parent_item, item)`.
* Marked the directory inode as dead (`dir->i_flags |= S_DEAD`).
* Merely re-fetching `parent_item` without validating directory state will cause `configfs_symlink()` to continue execution:
* Calling `type->ct_item_ops->allow_link(parent_item, target_item)` on an object that the subsystem/driver already tore down in `drop_item()` (causing driver-level UAF or corruption).
* Calling `create_link()` and attaching a symlink into an orphaned/dead directory hierarchy.
* Calling `type->ct_item_ops->drop_link()` on failure on a dropped object.
### 3. What the proper fix requires
1. **Hold the reference across the unlock/relock**: `configfs_symlink()` must keep its reference to `parent_item` to pin the memory structure.
2. **Revalidate parent state after `inode_lock(dir)`**: After re-acquiring `inode_lock(dir)`, check whether the parent directory was deleted or is dropping during the unlocked window (e.g., checking `IS_DEADDIR(dir)`, `d_unhashed(dentry->d_parent)`, or `CONFIGFS_USET_DROPPING` on the parent's `configfs_dirent` under `configfs_dirent_lock`). If dead/dropping, `configfs_symlink()` must abort early with an error (such as `-ENOENT`) and release the reference.]
|
| 218/2 |
2026/09/02 21:22 |
llm |
debugger |
0m
Model:
Tokens:
input: 158339
output: 34
thoughts: 2288
Thoughts: **Root Cause Analysis and Potential Solutions for a configfs Use-After-Free Vulnerability**
Okay, so I've been diving deep into this `configfs` issue, and it's starting to make a bit more sense. It seems the problem boils down to `configfs_get_config_item` accessing a dangling pointer, specifically `sd->s_element`, which points to freed memory. This happens because of the way `configfs_symlink` interacts with the underlying memory structure and the subsystem's (e.g., `nvmet`) handling of references.
Initially, I thought holding a reference across the unlock/relock cycle in `configfs_symlink` would solve it, but it doesn't. Specifically, `parent_item` is not freed and is `subsys_group.cg_item`, which is embedded in the `nvmet_port` structure. The refcount of the `subsys_group` structure is > 0, but the memory for the `subsys_group` structure is freed. This is the root cause!
I considered that `configfs_symlink` could hold a reference to `parent_item`, but this doesn't protect the underlying `ports/1` memory because `ports/1` can be freed by `configfs_rmdir`. This is due to the subsystem freeing the memory before all references are released.
The "expert" suggested that `configfs_symlink()` keep its reference to `parent_item` to pin the memory structure, but this is clearly insufficient. The real issue is that `configfs` allows a parent object to be freed even when its default groups (like `parent_item`) have non-zero refcounts.
My initial thought was to modify `configfs_symlink` to check if `parent_item` is a default group and get a reference to its parent:
```c
if (parent_item->ci_type && parent_item->ci_parent)
config_item_get(parent_item->ci_parent);
```
However, `parent_item->ci_parent` is set to NULL by `unlink_obj`.
I realized that we could make this work if we get a reference to `parent_item->ci_parent` *before* dropping the lock.
```c
struct config_item *p = NULL;
if (parent_item->ci_parent)
p = config_item_get(parent_item->ci_parent);
inode_unlock(dir);
...
inode_lock(dir);
...
if (p)
config_item_put(p);
```
If we pin `parent_item->ci_parent`, we can avoid the memory being freed while the lock is dropped. Upon re-acquiring the lock, `configfs_symlink` can check for `IS_DEADDIR(dir)` or `frag->frag_dead` and abort if necessary, then releasing the references. This seemed to be a possible solution.
However, after more thought, I noticed a potential problem. If `parent_item` is a default group of a default group, then pinning `parent_item->ci_parent` (which is the *intermediate* default group) may not be sufficient. In `nvmet`, `ana_default_group` is a default group of `ana_groups_group`, which is a default group of `port`. Pinning `ana_groups_group` does NOT pin `port`! So pinning `ci_parent` is not a general solution.
The general solution is that the parent object MUST NOT be freed if any of its default groups are still in use. This means the subsystem must implement `drop_item`, call `configfs_remove_default_groups`, and the default groups MUST hold a reference to the parent object. This seems like the *only* correct way to handle embedded reference-counted objects.
But that's a HUGE change to existing subsystems. Could we fix `configfs_symlink` itself? Could we avoid dropping the lock entirely? The reason for dropping the lock is the call to `kern_path` and `get_target`, which could block. But, `vfs_symlink` *already* holds the directory lock when calling `configfs_symlink`. Therefore, it's safe to enter `configfs_symlink` with the lock held.
The so-called "expert" noted that `config_item_get(sd->s_element)` results in a use-after-free, where `sd` is the directory's `configfs_dirent`. If the directory is removed, `configfs_rmdir` calls `configfs_detach_group`, which then calls `config
|
| 219/2 |
2026/09/02 21:22 |
tool |
read-file |
0m
Args: map[File:fs/libfs.c FirstLine:350 LineCount:20]
Results: map[Contents: 350: */
351: void simple_offset_rename(struct inode *old_dir, struct dentry *old_dentry,
352: struct inode *new_dir, struct dentry *new_dentry)
353: {
354: struct offset_ctx *old_ctx = old_dir->i_op->get_offset_ctx(old_dir);
355: struct offset_ctx *new_ctx = new_dir->i_op->get_offset_ctx(new_dir);
356: long new_offset = dentry2offset(new_dentry);
357:
358: if (WARN_ON(!new_offset))
359: return;
360:
361: simple_offset_remove(old_ctx, old_dentry);
362: offset_set(new_dentry, 0);
363: WARN_ON(simple_offset_replace(new_ctx, old_dentry, new_offset));
364: }
365:
366: /**
367: * simple_offset_rename_exchange - exchange rename with directory offsets
368: * @old_dir: parent of dentry being moved
369: * @old_dentry: dentry being moved
]
|
| 220/2 |
2026/09/02 21:22 |
llm |
debugger |
0m
Model:
Tokens:
input: 162051
output: 18
thoughts: 11
Thoughts: Okay, here's my attempt at summarizing that thought process, tailored for an expert audience:
**Analyzing the Search Query: `simple_rmdir`**
Alright, the initial prompt is pretty straightforward. My first instinct, as an expert in system programming and potentially file system interactions, is to immediately parse that search query: `simple_rmdir`. This implies a few things right off the bat. The presence of the `rmdir` part suggests we're dealing with directory removal, a fundamental operation. The "simple" modifier, however, is the key point. It implies a desire for a basic, perhaps minimal, implementation of that function, or an implementation that is readily understood. This likely means avoiding complex features like error handling for permissions issues, or advanced features such as recursively deleting a non-empty directory. I am going to focus my search on simplicity and readability over robustness. It could also suggest a desire for a function that does *only* what `rmdir` is supposed to do, nothing else. Therefore, I need to look for code examples that are easy to dissect. Given the 'simple' nature, I will be more skeptical of overly complicated implementations and look for source code that's concise.
|
| 221/2 |
2026/09/02 21:22 |
tool |
grepper |
0m
Args: map[Expression:simple_rmdir]
Results: map[Output:fs/configfs/dir.c=410=static void configfs_remove_dir(struct dentry *d)
--
fs/configfs/dir.c-416- if (d_really_is_positive(d)) {
fs/configfs/dir.c:417: if (unlikely(simple_rmdir(d_inode(parent), d)))
fs/configfs/dir.c-418- pr_warn("remove_dir (%pd): attributes remain", d);
--
fs/hugetlbfs/inode.c=1217=static const struct inode_operations hugetlbfs_dir_inode_operations = {
--
fs/hugetlbfs/inode.c-1223- .mkdir = hugetlbfs_mkdir,
fs/hugetlbfs/inode.c:1224: .rmdir = simple_rmdir,
fs/hugetlbfs/inode.c-1225- .mknod = hugetlbfs_mknod,
--
fs/libfs.c=795=EXPORT_SYMBOL(__simple_unlink);
fs/libfs.c-796-
fs/libfs.c:797:void __simple_rmdir(struct inode *dir, struct dentry *dentry)
fs/libfs.c-798-{
--
fs/libfs.c-802-}
fs/libfs.c:803:EXPORT_SYMBOL(__simple_rmdir);
fs/libfs.c-804-
--
fs/libfs.c=811=EXPORT_SYMBOL(simple_unlink);
fs/libfs.c-812-
fs/libfs.c:813:int simple_rmdir(struct inode *dir, struct dentry *dentry)
fs/libfs.c-814-{
--
fs/libfs.c-817-
fs/libfs.c:818: __simple_rmdir(dir, dentry);
fs/libfs.c-819- d_make_discardable(dentry);
--
fs/libfs.c-821-}
fs/libfs.c:822:EXPORT_SYMBOL(simple_rmdir);
fs/libfs.c-823-
--
fs/ocfs2/dlmfs/dlmfs.c=536=static const struct inode_operations dlmfs_root_inode_operations = {
--
fs/ocfs2/dlmfs/dlmfs.c-538- .mkdir = dlmfs_mkdir,
fs/ocfs2/dlmfs/dlmfs.c:539: .rmdir = simple_rmdir,
fs/ocfs2/dlmfs/dlmfs.c-540-};
--
fs/ramfs/inode.c=189=static const struct inode_operations ramfs_dir_inode_operations = {
--
fs/ramfs/inode.c-195- .mkdir = ramfs_mkdir,
fs/ramfs/inode.c:196: .rmdir = simple_rmdir,
fs/ramfs/inode.c-197- .mknod = ramfs_mknod,
--
include/linux/fs.h=3271=extern int simple_unlink(struct inode *, struct dentry *);
include/linux/fs.h:3272:extern int simple_rmdir(struct inode *, struct dentry *);
include/linux/fs.h-3273-extern void __simple_unlink(struct inode *, struct dentry *);
include/linux/fs.h:3274:extern void __simple_rmdir(struct inode *, struct dentry *);
include/linux/fs.h-3275-void simple_rename_timestamp(struct inode *old_dir, struct dentry *old_dentry,
--
kernel/bpf/inode.c=463=static const struct inode_operations bpf_dir_iops = {
--
kernel/bpf/inode.c-466- .symlink = bpf_symlink,
kernel/bpf/inode.c:467: .rmdir = simple_rmdir,
kernel/bpf/inode.c-468- .rename = simple_rename,
--
security/apparmor/apparmorfs.c=418=static void aafs_remove(struct dentry *dentry)
--
security/apparmor/apparmorfs.c-430- if (!WARN_ON(!simple_empty(dentry))) {
security/apparmor/apparmorfs.c:431: __simple_rmdir(dir, dentry);
security/apparmor/apparmorfs.c-432- dput(dentry);
]
|
| 222/2 |
2026/09/02 21:22 |
llm |
debugger-compressor |
0m
Model:
Reply: ### Comprehensive Investigation Summary
#### 1. Bug Description & KASAN Crash Analysis
- **Crash Type:** KASAN slab-use-after-free (read of size 8 at offset 1224 inside a 4096-byte region in cache `kmalloc-4k`).
- **Call Trace:**
- `configfs_symlink` (`fs/configfs/symlink.c:196`) calls `type->ct_item_ops->allow_link(parent_item, target_item)` -> `nvmet_port_subsys_allow_link` (`drivers/nvme/target/configfs.c:1059`).
- Read access occurs at `struct nvmet_port *port = to_nvmet_port(parent->ci_parent);`.
- **Allocation Trace:** `nvmet_ports_make` (`drivers/nvme/target/configfs.c:2051`) -> `kzalloc_flex` of `struct nvmet_port`.
- **Free Trace:** `configfs_rmdir` (`fs/configfs/dir.c:1571`) -> `config_item_release` -> `config_item_cleanup` -> `nvmet_port_release` (`drivers/nvme/target/configfs.c:2012`) -> `kfree(port)`.
- **Offset Arithmetic:**
- `struct nvmet_port` layout:
- `group` at offset 1040 bytes (8320 bits)
- `subsys_group` at offset 1176 bytes (9408 bits)
- `subsystems` list at offset 1312 bytes (10496 bits)
- `struct config_item` layout:
- `ci_parent` at offset 48 bytes (384 bits)
- When `parent` is `&port->subsys_group.cg_item`, reading `parent->ci_parent` accesses offset `1176 + 48 = 1224` bytes into `struct nvmet_port`.
- This perfectly matches the crash report: `The buggy address is located 1224 bytes inside of freed 4096-byte region [ffff88811c6d4000, ffff88811c6d5000)`.
---
#### 2. Root Cause & Detailed Race Condition Mechanism
1. **The Race Sequence:**
- **Thread 1 (`symlinkat`):**
- User calls `symlinkat("/sys/kernel/config/nvmet/subsystems/subsys1", AT_FDCWD, "/sys/kernel/config/nvmet/ports/1/subsystems/subsys1")`.
- `configfs_symlink` is invoked on the directory `ports/1/subsystems/`.
- `parent_item` is fetched via `configfs_get_config_item(dentry->d_parent)` (incrementing refcount of `port->subsys_group.cg_item`).
- `configfs_symlink` drops the `dir` inode lock:
```c
inode_unlock(dir);
ret = get_target(symname, &target_item, dentry->d_sb);
inode_lock(dir);
```
- **Thread 2 (`rmdir`):**
- Concurrently, `rmdir("/sys/kernel/config/nvmet/ports/1")` is called.
- `configfs_rmdir` runs on `ports/1`.
- It calls `configfs_detach_group(dentry)` -> `detach_groups(dentry)`:
- Since `dir` (`ports/1/subsystems`) is unlocked by Thread 1, `detach_groups` acquires `inode_lock` on `ports/1/subsystems`, sets `d_inode(child)->i_flags |= S_DEAD`, detaches dirents, and unlocks it.
- `configfs_rmdir` calls `unlink_group` on `ports/1`.
- `unlink_group` iterates over default groups, calling `unlink_group(&port->subsys_group)` -> `unlink_obj(&port->subsys_group.cg_item)`:
```c
static void unlink_obj(struct config_item *item)
{
struct config_group *group;
group = item->ci_group;
if (group) {
list_del_init(&item->ci_entry);
item->ci_group = NULL;
item->ci_parent = NULL;
config_item_put(item); /* drops ref taken for ci_entry */
config_group_put(group); /* drops ref taken for ci_parent */
}
}
```
- `unlink_obj` clears `ci_parent` and drops `subsys_group`'s reference to `ports/1`.
- `configfs_rmdir` drops the last reference on `ports/1` (`config_item_put(item)`).
- `ports/1`'s refcount drops to 0 -> `nvmet_port_release` is called -> `kfree(port)` frees the whole `nvmet_port` object.
- **Thread 1 resumes:**
- Re-acquires `inode_lock(dir)`.
- Does NOT check if `dir` is dead or if `frag->frag_dead` is true.
- Calls `type->ct_item_ops->allow_link(parent_item, target_item)` (`nvmet_port_subsys_allow_link`).
- `parent_item` (`&port->subsys_group.cg_item`) is located inside the freed `port` memory.
- Accessing `parent->ci_parent` causes a slab-use-after-free read at offset 1224.
- Furthermore, even if `allow_link` was skipped, `configfs_symlink` would call `config_item_put(parent_item)` at `out_put:`, dereferencing `parent_item->ci_kref` inside freed memory (secondary use-after-free).
2. **Core Architectural Problem:**
- Default groups (such as `subsys_group`, `referrals_group`, `ana_groups_group`, `allowed_hosts_group`) are embedded directly inside their parent structures (`nvmet_port`, `nvmet_subsys`, etc.).
- `unlink_obj` severs the child-to-parent reference upon tree unlinking rather than upon child destruction.
- `configfs_symlink` drops the directory inode lock while holding a reference ONLY to the embedded child `config_item` (`subsys_group`), not the parent container (`port`).
- Consequently, unlinking the parent frees the container while an external reference to the embedded child is still held across the dropped lock window.
---
#### 3. Critical Files, Functions, and Data Structures
- **Files:**
- `fs/configfs/symlink.c` (functions: `configfs_symlink`, `configfs_unlink`)
- `fs/configfs/dir.c` (functions: `unlink_obj`, `unlink_group`, `detach_groups`, `configfs_rmdir`, `configfs_detach_prep`, `new_fragment`, `put_fragment`)
- `fs/configfs/item.c` (functions: `config_item_cleanup`, `config_item_put`, `config_item_get`)
- `drivers/nvme/target/configfs.c` (functions: `nvmet_port_subsys_allow_link`, `nvmet_port_release`, `nvmet_ports_make`, `nvmet_allowed_hosts_allow_link`)
- `drivers/nvme/target/nvmet.h` (structures: `struct nvmet_port`, `to_nvmet_port`)
- **Key Structs:**
- `struct configfs_fragment`: contains `frag_sem` and `frag_dead` (shared between parent group and default children via `populate_groups` -> `create_default_group`).
- `struct configfs_dirent`: contains `s_frag`, `s_type`, `s_links`.
- `struct config_item`: contains `ci_kref`, `ci_parent`, `ci_group`, `ci_type`, `ci_dentry`.
---
#### 4. Approaches Tried & Fix Analysis
- **Attempt 1: Check `IS_DEADDIR(dir)` or `frag->frag_dead` in `configfs_symlink`**
- Checking `IS_DEADDIR(dir)` or `frag->frag_dead` after `inode_lock(dir)` detects that the directory was removed while unlocked.
- *Caveat:* If `configfs_symlink` held `parent_item` across `inode_unlock(dir)`, jumping to `out_put` still executes `config_item_put(parent_item)`, which dereferences `parent_item->ci_kref` on freed memory.
- **Attempt 2: Drop `parent_item` before unlocking in `configfs_symlink` and re-acquire after checking `frag_dead` / `IS_DEADDIR`**
- In `configfs_symlink`:
1. Inspect `parent_item` for `allow_link` operation while holding lock.
2. Drop the `parent_item` reference (`config_item_put(parent_item); parent_item = NULL;`) before calling `inode_unlock(dir)`.
3. `inode_unlock(dir)` -> `get_target(...)` -> `inode_lock(dir)`.
4. Check if directory was removed (`IS_DEADDIR(dir)` or `sd->s_frag->frag_dead`). If dead, abort immediately (put `target_item`, return `-ENOENT`) without accessing `parent_item`.
5. If alive, re-acquire `parent_item = configfs_get_config_item(dentry->d_parent)` and invoke `allow_link`.
6. `dentry->d_parent->d_fsdata` (`sd`) is guaranteed to stay valid because VFS holds `dentry->d_parent` and `configfs_d_iput` keeps `sd` alive with the dentry.
- **Attempt 3: Extra Parent Refcounting in Subsystems (`nvmet`)**
- Having embedded default groups take a refcount on the parent and dropping it in custom `release` ops requires implementing `drop_item` to call `configfs_remove_default_groups`.
- This is fragile and requires changes across many subsystems (`nvme-target`, `target_core`, `usb_gadget`, etc.), whereas fixing `configfs_symlink` fixes the race generically at the root filesystem level.
---
#### 5. Recommended Next Steps / Potential Reasoning Traps to Avoid
- Avoid relying solely on subsystem-level fixes in `drivers/nvme/target/` because identical patterns exist in `target_core` and `usb/gadget/uvc`.
- In `fs/configfs/symlink.c`, ensure that error handling properly balances `target_item` and `parent_item` depending on whether `get_target` succeeded and whether `parent_item` was re-acquired.
- Validate that `dentry->d_parent->d_fsdata` access is safe after re-acquiring `inode_lock(dir)` under all VFS rmdir conditions.
Tokens:
input: 173078
output: 2645
thoughts: 0
|
| 223/2 |
2026/09/02 21:22 |
llm |
debugger |
0m
Model:
Tokens:
input: 30791
output: 34
thoughts: 27
Thoughts: Here's my take on those thoughts, tailored for an expert audience:
**Diving into `fs/libfs.c` - Checking `__simple_rmdir` and `simple_rmdir`**
Alright, time to dig into the directory removal routines. I'm focusing on `fs/libfs.c` because it's where the core, relatively low-level file system abstractions usually reside, and it makes sense to start there. I want to understand how the system *actually* handles the removal of empty directories. Specifically, I need a close look at `__simple_rmdir` and `simple_rmdir`. I'm guessing that `__simple_rmdir` is likely the more primitive, internal function, the one that does the actual work of modifying the directory's data structures or whatever internal data are used. My hypothesis is that `simple_rmdir` is probably the higher-level wrapper, perhaps handling things like error checking, permission validation, locking, and maybe even cleanup or resource management. I'm keen to see if my assumption is correct. It's a key spot to verify how the VFS interacts with the underlying file system. I want to see if this has a potential race condition or maybe something that needs improvement.
|
| 224/2 |
2026/09/02 21:22 |
tool |
read-file |
0m
Args: map[File:fs/libfs.c FirstLine:790 LineCount:35]
Results: map[Contents: 790:
791: inode_set_mtime_to_ts(dir,
792: inode_set_ctime_to_ts(dir, inode_set_ctime_current(inode)));
793: drop_nlink(inode);
794: }
795: EXPORT_SYMBOL(__simple_unlink);
796:
797: void __simple_rmdir(struct inode *dir, struct dentry *dentry)
798: {
799: drop_nlink(d_inode(dentry));
800: __simple_unlink(dir, dentry);
801: drop_nlink(dir);
802: }
803: EXPORT_SYMBOL(__simple_rmdir);
804:
805: int simple_unlink(struct inode *dir, struct dentry *dentry)
806: {
807: __simple_unlink(dir, dentry);
808: d_make_discardable(dentry);
809: return 0;
810: }
811: EXPORT_SYMBOL(simple_unlink);
812:
813: int simple_rmdir(struct inode *dir, struct dentry *dentry)
814: {
815: if (!simple_empty(dentry))
816: return -ENOTEMPTY;
817:
818: __simple_rmdir(dir, dentry);
819: d_make_discardable(dentry);
820: return 0;
821: }
822: EXPORT_SYMBOL(simple_rmdir);
823:
824: /**
]
|
| 225/2 |
2026/09/02 21:22 |
llm |
debugger |
0m
Model:
Tokens:
input: 31608
output: 20
thoughts: 617
Thoughts: **My Thought Process on Dentry Unhashing**
Okay, let's break this down. I'm seeing a chain of function calls related to removing a directory, and I need to understand when a dentry gets unhashed. First, `simple_rmdir` is involved, and it calls `d_make_discardable`. My initial thought is that `d_make_discardable` unhashes the dentry, but a quick check reveals that's **not** the case. Hmm, so `d_make_discardable` doesn't do the unhashing. Where does that happen?
Then I recall `d_delete` is the function that unhashes a dentry. But does `simple_rmdir` call `d_delete`? No, it doesn't. Aha! `vfs_rmdir` *does* call `d_delete`. And I see the relevant code snippet confirms this. I can see the call clearly: `if (!error) d_delete(dentry);`.
Now, digging deeper into `d_delete`, I see there's a conditional: if the dentry's reference count is 1, then it calls `dentry_unlink_inode`; otherwise, it calls `__d_drop(dentry)`. Importantly, both these code paths unhash the dentry. In the `__d_drop` path, I'm confident that the dentry will be unhashed and `d_unhashed(dentry)` will return true, right?
Alright, now the tricky part. I'm focusing on a specific scenario involving `ports/1`. `vfs_rmdir` is called on `ports/1`, so its dentry *is* unhashed. But then, `configfs_symlink` is called on `ports/1/subsystems`. Is *that* dentry now unhashed too? This is where the control flow gets complex.
I see `configfs_rmdir` eventually calls `simple_rmdir`, which calls `d_make_discardable` on the dentry representing `ports/1/subsystems`. *Again*, I remember that `d_make_discardable` does *not* unhash the dentry. So the dentry for `ports/1/subsystems` is **not** unhashed. Interesting. I'll need to double check the code around `configfs_rmdir`, but I think I have the sequence of calls figured out.
|
| 226/2 |
2026/09/02 21:23 |
tool |
grepper |
0m
Args: map[Expression:d_make_discardable]
Results: map[Output:fs/autofs/root.c=628=static int autofs_dir_unlink(struct inode *dir, struct dentry *dentry)
--
fs/autofs/root.c-634- p_ino->count--;
fs/autofs/root.c:635: d_make_discardable(dentry);
fs/autofs/root.c-636-
--
fs/autofs/root.c=696=static int autofs_dir_rmdir(struct inode *dir, struct dentry *dentry)
--
fs/autofs/root.c-716- p_ino->count--;
fs/autofs/root.c:717: d_make_discardable(dentry);
fs/autofs/root.c-718- d_inode(dentry)->i_size = 0;
--
fs/dcache.c=1044=EXPORT_SYMBOL(dput);
fs/dcache.c-1045-
fs/dcache.c:1046:void d_make_discardable(struct dentry *dentry)
fs/dcache.c-1047-{
--
fs/dcache.c-1053-}
fs/dcache.c:1054:EXPORT_SYMBOL(d_make_discardable);
fs/dcache.c-1055-
--
fs/devpts/inode.c=553=void devpts_pty_kill(struct dentry *dentry)
--
fs/devpts/inode.c-560- fsnotify_unlink(d_inode(dentry->d_parent), dentry);
fs/devpts/inode.c:561: d_make_discardable(dentry);
fs/devpts/inode.c-562-}
--
fs/libfs.c=597=static void __simple_recursive_removal(struct dentry *dentry,
--
fs/libfs.c-624- fsnotify_delete(inode, d_inode(victim), victim);
fs/libfs.c:625: d_make_discardable(victim);
fs/libfs.c-626- }
--
fs/libfs.c=805=int simple_unlink(struct inode *dir, struct dentry *dentry)
--
fs/libfs.c-807- __simple_unlink(dir, dentry);
fs/libfs.c:808: d_make_discardable(dentry);
fs/libfs.c-809- return 0;
--
fs/libfs.c=813=int simple_rmdir(struct inode *dir, struct dentry *dentry)
--
fs/libfs.c-818- __simple_rmdir(dir, dentry);
fs/libfs.c:819: d_make_discardable(dentry);
fs/libfs.c-820- return 0;
--
fs/tracefs/event_inode.c=891=void eventfs_remove_events_dir(struct eventfs_inode *ei)
--
fs/tracefs/event_inode.c-911- d_invalidate(dentry);
fs/tracefs/event_inode.c:912: d_make_discardable(dentry);
fs/tracefs/event_inode.c-913-}
--
include/linux/dcache.h=645=struct dentry *d_make_persistent(struct dentry *, struct inode *);
include/linux/dcache.h:646:void d_make_discardable(struct dentry *dentry);
include/linux/dcache.h-647-
]
|
| 227/2 |
2026/09/02 21:23 |
llm |
debugger |
0m
Model:
Tokens:
input: 33703
output: 35
thoughts: 21
Thoughts: Here's my take on the thought process, as if it were my own:
**Investigating `d_make_discardable` in `fs/dcache.c`**
Okay, time to dive in. My initial thought is to head straight to `fs/dcache.c` and specifically pinpoint the function `d_make_discardable`. It sounds like this is the likely candidate for the part of the code that handles making dcache entries eligible for discarding, which is probably what I need to understand better for this particular task/problem. I'm going to want to look at how it's called, what it does in detail, and how it interacts with the rest of the dcache subsystem. Iβm expecting to see things like reference counting manipulations, flags being set, and interactions with the LRU lists or other eviction mechanisms. Understanding its core logic is the key to figuring out how the system decides what's safe to throw away. Let's see what we can find.
|
| 228/2 |
2026/09/02 21:23 |
tool |
read-file |
0m
Args: map[File:fs/dcache.c FirstLine:1040 LineCount:20]
Results: map[Contents:1040: if (likely(fast_dput(dentry)))
1041: return;
1042: finish_dput(dentry);
1043: }
1044: EXPORT_SYMBOL(dput);
1045:
1046: void d_make_discardable(struct dentry *dentry)
1047: {
1048: spin_lock(&dentry->d_lock);
1049: WARN_ON(!(dentry->d_flags & DCACHE_PERSISTENT));
1050: dentry->d_flags &= ~DCACHE_PERSISTENT;
1051: dentry->d_lockref.count--;
1052: finish_dput(dentry);
1053: }
1054: EXPORT_SYMBOL(d_make_discardable);
1055:
1056: /**
1057: * __move_to_shrink_list - try to place a dentry into a shrink list
1058: * @dentry: dentry to try putting into shrink list
1059: * @list: the list to put @dentry into.
]
|
| 229/2 |
2026/09/02 21:23 |
llm |
debugger |
0m
Model:
Tokens:
input: 34291
output: 36
thoughts: 1387
Thoughts: **Analyzing the ConfigFS Symlink Issue**
Okay, here's the breakdown of my thought process regarding this `configfs_symlink` issue. It started by tracing the refcounting behavior:
I realized that `d_make_discardable` doesn't actually unhash the dentry if the count is greater than zero, and `configfs_symlink` holds a reference to the dentry's parent (via `vfs_symlink`). Therefore, the dentry representing the directory "ports/1/subsystems" isn't being unhashed, which means a subsequent call to `configfs_get_config_item` *will* return the dangling `sd->s_element`! My initial thought was that a reference had to be held but then the parent object could be freed.
I then asked myself, if we have a reference, how can we prevent "ports/1" from being freed? My initial thought was to grab a reference to "ports/1" before dropping the lock. "ports/1" can be pinned by getting a reference to `parent_item->ci_parent`. However, what if `parent_item` is *not* a default group?
Then I started to think through the details. I realized default groups are *always* embedded in their parent. Therefore, pinning the parent of a default group would not necessarily prevent memory from being freed if the parent is also a default group. We would have to pin all the way up the tree.
Next, I looked at whether there was a way to resolve the target without dropping the lock. However, `get_target` calls `kern_path`, which could deadlock with `inode_lock(dir)` if the target path is inside the same directory. This led me to understand *why* the lock is dropped.
This led me to the question of what to do. The solution had to involve safely holding onto something, because the lock must be dropped. The key insight was that if the parent's memory is freed, it's because the subsystem freed it. The subsystem frees it when the root object's refcount drops to zero (via `configfs_rmdir` calling `unlink_group`, which calls `config_item_put`).
This made me ask, how can `configfs_symlink` prevent the root object from being freed? I thought about finding the root object by walking up the `ci_parent` chain.
Then I realized there is no explicit flag for a default group but that `configfs_rmdir` calls `configfs_detach_group` on the *root* object. Then I noticed that `configfs_detach_group` sets `frag->frag_dead = true` on the root object's fragment, and *all* default groups share the same fragment as the root object! I need to check to be sure, by looking at `create_default_group`.
|
| 230/2 |
2026/09/02 21:23 |
tool |
read-file |
0m
Args: map[File:fs/configfs/dir.c FirstLine:810 LineCount:30]
Results: map[Contents: 810: unlink_group(new_group);
811:
812: group->cg_subsys = NULL;
813: unlink_obj(&group->cg_item);
814: }
815:
816: static void link_group(struct config_group *parent_group, struct config_group *group)
817: {
818: struct config_group *new_group;
819: struct configfs_subsystem *subsys = NULL; /* gcc is a turd */
820:
821: link_obj(&parent_group->cg_item, &group->cg_item);
822:
823: if (parent_group->cg_subsys)
824: subsys = parent_group->cg_subsys;
825: else if (configfs_is_root(&parent_group->cg_item))
826: subsys = to_configfs_subsystem(group);
827: else
828: BUG();
829: group->cg_subsys = subsys;
830:
831: list_for_each_entry(new_group, &group->default_groups, group_entry)
832: link_group(group, new_group);
833: }
834:
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);
]
|
| 231/2 |
2026/09/02 21:23 |
llm |
debugger |
0m
Model:
Tokens:
input: 36797
output: 19
thoughts: 11
Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience, presented in the first person:
**Analyzing the `create_default_group` Function**
Alright, let's dive into this. The first thing I'm doing is zeroing in on the `create_default_group` function. I need to understand its behavior and what it's doing. This is a common starting point for debugging or evaluating any function, and especially so when I'm dealing with default configurations. I need to ascertain if itβs truly setting things to defaults, or if there's any behavior that might be unexpected under certain conditions. So the first step is to dig into the function.
|
| 232/2 |
2026/09/02 21:23 |
tool |
grepper |
0m
Args: map[Expression:create_default_group]
Results: map[Output:fs/configfs/dir.c=664=static void detach_groups(struct dentry *dentry)
--
fs/configfs/dir.c-702- */
fs/configfs/dir.c:703:static int create_default_group(struct dentry *parent,
fs/configfs/dir.c-704- struct config_group *group,
--
fs/configfs/dir.c=734=static int populate_groups(struct config_group *group,
--
fs/configfs/dir.c-740- list_for_each_entry(new_group, &group->default_groups, group_entry) {
fs/configfs/dir.c:741: int ret = create_default_group(parent, new_group, frag);
fs/configfs/dir.c-742- if (ret)
--
fs/configfs/dir.c=1751=int configfs_register_group(struct config_group *parent_group,
--
fs/configfs/dir.c-1769- inode_lock_nested(d_inode(parent), I_MUTEX_PARENT);
fs/configfs/dir.c:1770: ret = create_default_group(parent, group, frag);
fs/configfs/dir.c-1771- if (ret)
]
|
| 233/2 |
2026/09/02 21:23 |
llm |
debugger |
0m
Model:
Tokens:
input: 37322
output: 36
thoughts: 22
Thoughts: Here's my take on those thoughts, tailored for an expert audience:
**Diving into `create_default_group`**
Okay, so I need to refresh my memory on `create_default_group`. I'm going to jump straight into `fs/configfs/dir.c` β that's where I'd expect to find the implementation. It's a fundamental part of how configfs establishes its default permissions and group ownership for newly created configfs directories. Iβm thinking there might be some nuanced logic in there around the default gid assignment, maybe related to some security context or inheritance rules. I need to pinpoint the exact mechanism to understand how it interacts with the rest of the configfs infrastructure and, potentially, if it could lead to any security or permission-related issues down the line. I want to look closely at its interactions with the `security` subsystem, and whether there are any potential race conditions, although I'm not immediately expecting any. Let's see whatβs what.
|
| 234/2 |
2026/09/02 21:23 |
tool |
read-file |
0m
Args: map[File:fs/configfs/dir.c FirstLine:700 LineCount:30]
Results: map[Contents: 700: * We could, perhaps, tweak our parent's ->mkdir for a minute and
701: * try using vfs_mkdir. Just a thought.
702: */
703: static int create_default_group(struct dentry *parent,
704: struct config_group *group,
705: struct configfs_fragment *frag)
706: {
707: int ret;
708: struct configfs_dirent *sd;
709: /* We trust the caller holds a reference to parent */
710: struct dentry *child;
711:
712: if (!group->cg_item.ci_name)
713: group->cg_item.ci_name = group->cg_item.ci_namebuf;
714:
715: ret = -ENOMEM;
716: child = d_alloc_name(parent, group->cg_item.ci_name);
717: if (child) {
718: d_add(child, NULL);
719:
720: ret = configfs_attach_group(&group->cg_item, child, frag);
721: if (!ret) {
722: sd = child->d_fsdata;
723: sd->s_type |= CONFIGFS_USET_DEFAULT;
724: } else {
725: BUG_ON(d_inode(child));
726: d_drop(child);
727: }
728: dput(child);
729: }
]
|
| 235/2 |
2026/09/02 21:23 |
llm |
debugger |
0m
Model:
Tokens:
input: 38052
output: 21
thoughts: 951
Thoughts: **The Solution: Holding a Root Object Reference**
Okay, so I've got this scenario with `configfs_rmdir` and `configfs_symlink`, and I need to ensure that a dangling pointer situation doesn't occur. Let's break this down.
First, I realize that `create_default_group` takes `frag` as an argument, and `populate_groups` *uses the same* `frag` for all the default groups. This means the root object and the default groups all share the same `frag`. Crucially, `configfs_rmdir` sets `frag->frag_dead = true` *before* it calls `unlink_group`. So, if `configfs_symlink` checks `frag->frag_dead`, it *should* detect the root object being removed.
However, the core issue is the dangling pointer: `sd->s_element` becomes a pointer to already freed memory if we call `configfs_get_config_item(dentry->d_parent)` after relocking. I CAN'T dereference `sd->s_element`. So, I CANNOT call `configfs_get_config_item(dentry->d_parent)`.
So, what can I do? I need to hold a reference to `parent_item` AND prevent it from being freed. The memory is freed when the root object's refcount drops to zero. That refcount drops to zero when `configfs_rmdir` calls `config_item_put(item)` on the root object.
The key is preventing that `config_item_put` on the root object. I need to hold a reference to it. The challenge is, I don't know the specific object that *is* the root.
Here's the breakthrough. `configfs_symlink` is called on `dentry->d_parent`. The directory of the symlink. I can walk up the dentry tree from `dentry->d_parent` until I find a dentry where `sd->s_type` does *not* have `CONFIGFS_USET_DEFAULT`! That's my root dentry.
Then, I can get the root item using `root_item = configfs_get_config_item(root_dentry);`. By holding this `root_item` reference, the root object will *not* be freed. Because it won't be freed, `parent_item`'s memory will remain valid. I can hold that root reference and then relock the lock later on.
I know this is safe because `configfs_rmdir` will still call `unlink_group` on the root object. But crucially, the *memory* of the root object (and the embedded default groups) will NOT be freed until I drop that `root_item`! Therefore, `parent_item` is still valid!
Then, when I re-acquire the lock, I can safely check `frag->frag_dead`. If it's dead, I abort. If not, I proceed! This seems to have addressed the problem.
Now, let's just make sure `CONFIGFS_USET_DEFAULT` is the correct flag.
|
| 236/2 |
2026/09/02 21:23 |
tool |
grepper |
0m
Args: map[Expression:CONFIGFS_USET_DEFAULT]
Results: map[Output:fs/configfs/configfs_internal.h=30=struct configfs_dirent {
--
fs/configfs/configfs_internal.h-52-#define CONFIGFS_USET_DIR 0x0040
fs/configfs/configfs_internal.h:53:#define CONFIGFS_USET_DEFAULT 0x0080
fs/configfs/configfs_internal.h-54-#define CONFIGFS_USET_DROPPING 0x0100
--
fs/configfs/dir.c=502=static int configfs_detach_prep(struct configfs_dirent *parent_sd, struct dentry **wait)
--
fs/configfs/dir.c-518- continue;
fs/configfs/dir.c:519: if (sd->s_type & CONFIGFS_USET_DEFAULT) {
fs/configfs/dir.c-520- /* Abort if racing with mkdir() */
--
fs/configfs/dir.c=548=static void configfs_detach_rollback(struct configfs_dirent *parent_sd)
--
fs/configfs/dir.c-554- list_for_each_entry(sd, &parent_sd->s_children, s_sibling)
fs/configfs/dir.c:555: if (sd->s_type & CONFIGFS_USET_DEFAULT)
fs/configfs/dir.c-556- configfs_detach_rollback(sd);
--
fs/configfs/dir.c=664=static void detach_groups(struct dentry *dentry)
--
fs/configfs/dir.c-673- next = next_dirent(parent_sd, sd);
fs/configfs/dir.c:674: if (!(sd->s_type & CONFIGFS_USET_DEFAULT))
fs/configfs/dir.c-675- continue;
--
fs/configfs/dir.c=703=static int create_default_group(struct dentry *parent,
--
fs/configfs/dir.c-722- sd = child->d_fsdata;
fs/configfs/dir.c:723: sd->s_type |= CONFIGFS_USET_DEFAULT;
fs/configfs/dir.c-724- } else {
--
fs/configfs/dir.c=977=static void configfs_dump_one(struct configfs_dirent *sd, int level)
--
fs/configfs/dir.c-986- type_print(CONFIGFS_USET_DIR);
fs/configfs/dir.c:987: type_print(CONFIGFS_USET_DEFAULT);
fs/configfs/dir.c-988- type_print(CONFIGFS_USET_DROPPING);
--
fs/configfs/dir.c=1469=static int configfs_rmdir(struct inode *dir, struct dentry *dentry)
--
fs/configfs/dir.c-1479- sd = dentry->d_fsdata;
fs/configfs/dir.c:1480: if (sd->s_type & CONFIGFS_USET_DEFAULT)
fs/configfs/dir.c-1481- return -EPERM;
]
|
| 237/2 |
2026/09/02 21:23 |
llm |
debugger |
1m
Model:
Tokens:
input: 40544
output: 164
thoughts: 2048
Thoughts: **Deep Dive into `configfs_symlink` Concurrency**
Okay, so I've been digging into the `configfs_symlink` function and how it interacts with potential concurrent deletions. The goal is clear: ensure the code is thread-safe, particularly when dealing with default groups and root object manipulations.
Initially, I zeroed in on the `CONFIGFS_USET_DEFAULT` flag and how `configfs_rmdir` behaves. The core insight is that `configfs_rmdir` *only* operates on non-default groups. This is the key. By tracing up the dentry tree, using the `configfs_get_root_item` helper, I can get to the *exact* object `configfs_rmdir` is operating on and hold a reference to it. This means `configfs_rmdir` *cannot* free it during its execution. Perfect. I needed to verify the function's safety, especially when the `root_dentry` is the configfs root. After inspecting the code, this all looked safe.
Now, let's step through `configfs_symlink` in detail. The function grabs the `parent_item`, pins the root object using `configfs_get_root_item`, unlocks the inode, gets the target, re-locks the inode, performs checks, acquires a mutex, creates the link, and finally cleans up by releasing the acquired objects.
A critical point emerged when I considered the root object pinning with `root_item` and what happens if `configfs_rmdir` is called. It became clear that the act of unlinking the default groups is safe. `config_item_put` on the `root_item` is the point where the actual memory is freed. The `parent_item` will not be freed early and is still a valid pointer. The `sd->s_frag` pointer also remains safe since `dentry->d_parent` and `sd` are kept alive.
The next concern was regarding the validity of the `root_dentry` in `configfs_get_root_item`. The while loop looked correct, and the `configfs_get_config_item` call looks safe since it is locking the specific dentry it is passed. However, I needed to confirm there were no race conditions. Could `root_dentry` be unhashed *between* calls to `configfs_get_config_item` and `configfs_get_root_item`? If so, the `parent_item` *could* be freed after our initial call.
Then I realized something crucial. `configfs_symlink` holds `inode_lock(dir)`, and `dir` is `dentry->d_parent->d_inode`. `configfs_rmdir` will take `inode_lock` on the parent of the root object. Since the root object is a default group, `configfs_rmdir` is going to *block* on `inode_lock(dir)`. Therefore, the root object is *guaranteed* to be alive and hashed when `configfs_get_root_item` is called, and `configfs_symlink` *is* holding `inode_lock(dir)`. It all makes sense!
|
| 238/2 |
2026/09/02 21:25 |
tool |
codeexpert |
2m
Args: map[Question:In `configfs_symlink`, before dropping `inode_unlock(dir)`, we want to pin the root object to prevent the embedded `parent_item` from being freed.
Since `configfs_symlink` holds `inode_lock(dir)`, and `dir` is the inode of `dentry->d_parent` (which might be a default group), does `inode_lock(dir)` prevent a concurrent `configfs_rmdir` on the root object from proceeding past `detach_groups()`?
If so, is it guaranteed that we can safely walk up the `d_parent` chain to find the root object and get a reference to it before dropping `inode_unlock(dir)`?]
Results: map[Answer:Yes to both questions.
### 1. Does `inode_lock(dir)` prevent a concurrent `configfs_rmdir` on the root object from proceeding past `detach_groups()`?
**Yes.**
When `configfs_rmdir()` runs on a root object (or when `configfs_unregister_group()` / `configfs_unregister_subsystem()` runs):
1. It prepares detachment by marking default groups with `CONFIGFS_USET_DROPPING` in `configfs_detach_prep()`.
2. It then calls `configfs_detach_group(dentry)`, which directly invokes `detach_groups(dentry)`.
3. `detach_groups()` iterates recursively over all child default groups (`CONFIGFS_USET_DEFAULT`) in the hierarchy and explicitly executes:
```c
inode_lock(d_inode(child));
configfs_detach_group(child);
...
inode_unlock(d_inode(child));
```
4. If `dir` is the inode of `dentry->d_parent` (a default group anywhere in the hierarchy under the root object), `detach_groups()` will attempt to acquire `inode_lock(d_inode(child))` on `dir` and will **block** as long as `configfs_symlink()` holds `inode_lock(dir)`. (If `dir` is the root object itself, VFS `vfs_rmdir()` blocks on `inode_lock(dir)` before `configfs_rmdir()` can even begin).
5. Only **after** `detach_groups()` finishes can `configfs_rmdir()` proceed to `unlink_group()`, `client_disconnect_notify()`, `client_drop_item()`, and `config_item_put()`.
Therefore, holding `inode_lock(dir)` strictly blocks any concurrent `configfs_rmdir` on an ancestor root object from proceeding past `detach_groups()`.
---
### 2. Is it guaranteed that we can safely walk up the `d_parent` chain to find the root object and get a reference to it before dropping `inode_unlock(dir)`?
**Yes.**
- **Dentry Stability:** In the VFS dcache, each dentry pins its parent (`d_count` refcount), so all ancestors from `dentry->d_parent` up to the root are pinned in memory. Configfs does not support rename/move operations, making the `d_parent` chain immutable. Furthermore, dentries are only unhashed (`d_delete()`) in `detach_groups()` after releasing the child's inode lock.
- **Reference Count & Memory Validity:** Because `configfs_rmdir` is blocked prior to `unlink_group()` and `client_drop_item()`, the root object and all intermediate default groups have not been unlinked, and their initial references remain active (refcount $\ge 1$).
- **Safe Root Pinning:** Walking up `d_parent` until reaching the root non-default group / subsystem (identifiable by checking that `sd->s_type & CONFIGFS_USET_DEFAULT` is false) and obtaining a reference with `configfs_get_config_item()` / `config_item_get()` is completely safe. Pinning the root object ensures that even after `inode_unlock(dir)` is dropped and a concurrent `rmdir` proceeds, the underlying memory structure hosting the embedded `parent_item` cannot be freed until `configfs_symlink()` releases its reference on the root object.]
|
| 239/3 |
2026/09/02 21:25 |
agent |
codeexpert |
2m
Model:
Results: map[Answer:Yes to both questions.
### 1. Does `inode_lock(dir)` prevent a concurrent `configfs_rmdir` on the root object from proceeding past `detach_groups()`?
**Yes.**
When `configfs_rmdir()` runs on a root object (or when `configfs_unregister_group()` / `configfs_unregister_subsystem()` runs):
1. It prepares detachment by marking default groups with `CONFIGFS_USET_DROPPING` in `configfs_detach_prep()`.
2. It then calls `configfs_detach_group(dentry)`, which directly invokes `detach_groups(dentry)`.
3. `detach_groups()` iterates recursively over all child default groups (`CONFIGFS_USET_DEFAULT`) in the hierarchy and explicitly executes:
```c
inode_lock(d_inode(child));
configfs_detach_group(child);
...
inode_unlock(d_inode(child));
```
4. If `dir` is the inode of `dentry->d_parent` (a default group anywhere in the hierarchy under the root object), `detach_groups()` will attempt to acquire `inode_lock(d_inode(child))` on `dir` and will **block** as long as `configfs_symlink()` holds `inode_lock(dir)`. (If `dir` is the root object itself, VFS `vfs_rmdir()` blocks on `inode_lock(dir)` before `configfs_rmdir()` can even begin).
5. Only **after** `detach_groups()` finishes can `configfs_rmdir()` proceed to `unlink_group()`, `client_disconnect_notify()`, `client_drop_item()`, and `config_item_put()`.
Therefore, holding `inode_lock(dir)` strictly blocks any concurrent `configfs_rmdir` on an ancestor root object from proceeding past `detach_groups()`.
---
### 2. Is it guaranteed that we can safely walk up the `d_parent` chain to find the root object and get a reference to it before dropping `inode_unlock(dir)`?
**Yes.**
- **Dentry Stability:** In the VFS dcache, each dentry pins its parent (`d_count` refcount), so all ancestors from `dentry->d_parent` up to the root are pinned in memory. Configfs does not support rename/move operations, making the `d_parent` chain immutable. Furthermore, dentries are only unhashed (`d_delete()`) in `detach_groups()` after releasing the child's inode lock.
- **Reference Count & Memory Validity:** Because `configfs_rmdir` is blocked prior to `unlink_group()` and `client_drop_item()`, the root object and all intermediate default groups have not been unlinked, and their initial references remain active (refcount $\ge 1$).
- **Safe Root Pinning:** Walking up `d_parent` until reaching the root non-default group / subsystem (identifiable by checking that `sd->s_type & CONFIGFS_USET_DEFAULT` is false) and obtaining a reference with `configfs_get_config_item()` / `config_item_get()` is completely safe. Pinning the root object ensures that even after `inode_unlock(dir)` is dropped and a concurrent `rmdir` proceeds, the underlying memory structure hosting the embedded `parent_item` cannot be freed until `configfs_symlink()` releases its reference on the root object.]
Instruction:
You are a capable Linux kernel developer tasked with researching complex questions
about kernel source code. You will be given a concrete question, and need to provide
a concrete answer.
Use tools extensively while researching the question. Don't make assumptions,
or rely on your previous knowledge about the kernel source code, use available tools
to access the actual source code.
Use all available sources of information:
- kernel source code
- documentation in the Documentation dir in the source tree
- git commits descriptions, git blame
Do not guess file names or file paths and attempt to read them without
verifying their existence first using content search or directory listing tools.
If a file, symbol, or directory is not found via content search (grepper) or
directory listing (codesearch-dir-index), treat it as completely absent.
Do not attempt to guess alternative names, extensions, or directories.
Tool Selection Guidelines:
1. For C functions, structs, and variables: Prefer codesearch-definition-source
or codesearch-struct-layout FIRST to retrieve clean, exact definitions
without line-number guessing.
2. If symbol lookup fails (e.g., preprocessor macros, macro-generated code, or
disabled #ifdef branches), fall back to read-file or grepper.
3. For file headers, #include directives, preprocessor macro definitions, and
non-C files (Kconfig, Makefiles, docs): Use read-file or grepper directly.
Do NOT use git-log to search for the presence or existence of files in the
repository. git-log is only for tracing commit history of files that are
already present in the current checkout. If a file does not exist in the
current checkout, it cannot be used for reproduction.
Avoid running broad git-log queries (such as searches on the entire repo)
without a specific 'PathPrefix' to restrict the scope, as these are very
expensive and will time out.
If a git-log tool call times out, do not retry the query with the same broad
scope. You must specify a tighter, narrower 'PathPrefix' for subsequent queries.
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: In `configfs_symlink`, before dropping `inode_unlock(dir)`, we want to pin the root object to prevent the embedded `parent_item` from being freed.
Since `configfs_symlink` holds `inode_lock(dir)`, and `dir` is the inode of `dentry->d_parent` (which might be a default group), does `inode_lock(dir)` prevent a concurrent `configfs_rmdir` on the root object from proceeding past `detach_groups()`?
If so, is it guaranteed that we can safely walk up the `d_parent` chain to find the root object and get a reference to it before dropping `inode_unlock(dir)`?
|
| 240/4 |
2026/09/02 21:25 |
llm |
codeexpert |
0m
Model:
Tokens:
input: 4107
output: 85
thoughts: 274
Thoughts: **My Concurrency Concerns with ConfigFS and `configfs_symlink`**
Okay, so the user's question has me thinking about a subtle but potentially important concurrency issue within the Linux kernel, specifically within `configfs`. They're digging into `configfs_symlink`, and I see the need to pin the root object to prevent freeing the `parent_item` before dropping the `inode_unlock(dir)`. It's a standard pattern for ensuring data integrity during operations.
Here's the crux: `configfs_symlink` holds `inode_lock(dir)`, and `dir` is the inode of `dentry->d_parent`. The tricky part is `d_parent` *could* point to a default group. My question is, does holding `inode_lock(dir)` truly prevent a concurrent `configfs_rmdir` operation on the *root* object from getting past the `detach_groups()` stage? That's the real worry. If `configfs_rmdir` *could* proceed past that point concurrently, then we have a potential race.
If this lock *does* prevent it, which I'm hoping is the case, *then* is it guaranteed that I can safely walk up the `d_parent` chain to the root object and get a solid reference to it before I drop `inode_unlock(dir)`? Ensuring this is critical.
I'd classify this as a kernel internals analysis, focused on concurrency and locking within `configfs`. It's probably benign in terms of causing immediate crashes, but unaddressed, this could lead to subtle data corruption or kernel vulnerabilities down the road. It's definitely kernel code review territory, and locking analysis is the name of the game.
Time to dive into the kernel source code. I need to understand how `configfs_symlink`, `configfs_rmdir`, `detach_groups`, default group handling, and the locking hierarchy interplay. I'll be using code search tools to understand the code. Let's see what's what!
|
| 241/4 |
2026/09/02 21:26 |
tool |
grepper |
0m
Args: map[Expression:configfs_symlink PathPrefix:fs/configfs]
Results: map[Output:fs/configfs/configfs_internal.h=30=struct configfs_dirent {
--
fs/configfs/configfs_internal.h-60-
fs/configfs/configfs_internal.h:61:extern struct mutex configfs_symlink_mutex;
fs/configfs/configfs_internal.h-62-extern spinlock_t configfs_dirent_lock;
--
fs/configfs/configfs_internal.h=89=extern const struct inode_operations configfs_root_inode_operations;
fs/configfs/configfs_internal.h:90:extern const struct inode_operations configfs_symlink_inode_operations;
fs/configfs/configfs_internal.h-91-extern const struct dentry_operations configfs_dentry_ops;
fs/configfs/configfs_internal.h-92-
fs/configfs/configfs_internal.h:93:extern int configfs_symlink(struct mnt_idmap *idmap,
fs/configfs/configfs_internal.h-94- struct inode *dir, struct dentry *dentry,
--
fs/configfs/dir.c=369=int configfs_create_link(struct configfs_dirent *target, struct dentry *parent,
--
fs/configfs/dir.c-387- inode->i_link = body;
fs/configfs/dir.c:388: inode->i_op = &configfs_symlink_inode_operations;
fs/configfs/dir.c-389- d_make_persistent(dentry, inode);
--
fs/configfs/dir.c=1469=static int configfs_rmdir(struct inode *dir, struct dentry *dentry)
--
fs/configfs/dir.c-1503-
fs/configfs/dir.c:1504: mutex_lock(&configfs_symlink_mutex);
fs/configfs/dir.c-1505- spin_lock(&configfs_dirent_lock);
--
fs/configfs/dir.c-1517- spin_unlock(&configfs_dirent_lock);
fs/configfs/dir.c:1518: mutex_unlock(&configfs_symlink_mutex);
fs/configfs/dir.c-1519-
--
fs/configfs/dir.c=1579=const struct inode_operations configfs_dir_inode_operations = {
--
fs/configfs/dir.c-1581- .rmdir = configfs_rmdir,
fs/configfs/dir.c:1582: .symlink = configfs_symlink,
fs/configfs/dir.c-1583- .unlink = configfs_unlink,
--
fs/configfs/dir.c=1934=void configfs_unregister_subsystem(struct configfs_subsystem *subsys)
--
fs/configfs/dir.c-1953- inode_lock_nested(d_inode(dentry), I_MUTEX_CHILD);
fs/configfs/dir.c:1954: mutex_lock(&configfs_symlink_mutex);
fs/configfs/dir.c-1955- spin_lock(&configfs_dirent_lock);
--
fs/configfs/dir.c-1959- spin_unlock(&configfs_dirent_lock);
fs/configfs/dir.c:1960: mutex_unlock(&configfs_symlink_mutex);
fs/configfs/dir.c-1961- configfs_detach_group(dentry);
--
fs/configfs/symlink.c-19-/* Protects attachments of new symlinks */
fs/configfs/symlink.c:20:DEFINE_MUTEX(configfs_symlink_mutex);
fs/configfs/symlink.c-21-
--
fs/configfs/symlink.c=117=static int get_target(const char *symname, struct config_item **target,
--
fs/configfs/symlink.c-134-
fs/configfs/symlink.c:135:int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
fs/configfs/symlink.c-136- struct dentry *dentry, const char *symname)
--
fs/configfs/symlink.c-197- if (!ret) {
fs/configfs/symlink.c:198: mutex_lock(&configfs_symlink_mutex);
fs/configfs/symlink.c-199- ret = create_link(parent_item, target_item, dentry);
fs/configfs/symlink.c:200: mutex_unlock(&configfs_symlink_mutex);
fs/configfs/symlink.c-201- if (ret && type->ct_item_ops->drop_link)
--
fs/configfs/symlink.c=213=int configfs_unlink(struct inode *dir, struct dentry *dentry)
--
fs/configfs/symlink.c-257-
fs/configfs/symlink.c:258:const struct inode_operations configfs_symlink_inode_operations = {
fs/configfs/symlink.c-259- .get_link = simple_get_link,
]
|
| 242/4 |
2026/09/02 21:26 |
tool |
grepper |
0m
Args: map[Expression:configfs_rmdir PathPrefix:fs/configfs]
Results: map[Output:fs/configfs/dir.c=369=int configfs_create_link(struct configfs_dirent *target, struct dentry *parent,
--
fs/configfs/dir.c-404- * the directory before we remove the directory, and we've inlined
fs/configfs/dir.c:405: * what used to be configfs_rmdir() below, instead of calling separately.
fs/configfs/dir.c-406- *
--
fs/configfs/dir.c=992=static int configfs_dump(struct configfs_dirent *sd, int level)
--
fs/configfs/dir.c-1029- * VFS->configfs path. So it must take VFS locks to prevent the
fs/configfs/dir.c:1030: * VFS->configfs stuff (configfs_mkdir(), configfs_rmdir(), etc). This is
fs/configfs/dir.c-1031- * why you can't call these functions underneath configfs callbacks.
--
fs/configfs/dir.c=1289=static struct dentry *configfs_mkdir(struct mnt_idmap *idmap, struct inode *dir,
--
fs/configfs/dir.c-1468-
fs/configfs/dir.c:1469:static int configfs_rmdir(struct inode *dir, struct dentry *dentry)
fs/configfs/dir.c-1470-{
--
fs/configfs/dir.c=1579=const struct inode_operations configfs_dir_inode_operations = {
fs/configfs/dir.c-1580- .mkdir = configfs_mkdir,
fs/configfs/dir.c:1581: .rmdir = configfs_rmdir,
fs/configfs/dir.c-1582- .symlink = configfs_symlink,
]
|
| 243/4 |
2026/09/02 21:26 |
tool |
grepper |
0m
Args: map[Expression:detach_groups PathPrefix:fs/configfs]
Results: map[Output:fs/configfs/dir.c-31- * Protects setting of CONFIGFS_USET_DROPPING: checking the flag
fs/configfs/dir.c:32: * unlocked is not reliable unless in detach_groups() called from
fs/configfs/dir.c-33- * rmdir()/unregister() and from configfs_attach_group()
--
fs/configfs/dir.c=662=static void configfs_detach_group(struct dentry *dentry);
fs/configfs/dir.c-663-
fs/configfs/dir.c:664:static void detach_groups(struct dentry *dentry)
fs/configfs/dir.c-665-{
--
fs/configfs/dir.c=885=static void configfs_detach_group(struct dentry *dentry)
fs/configfs/dir.c-886-{
fs/configfs/dir.c:887: detach_groups(dentry);
fs/configfs/dir.c-888- configfs_detach_item(dentry);
]
|
| 244/4 |
2026/09/02 21:26 |
llm |
codeexpert |
0m
Model:
Tokens:
input: 7018
output: 36
thoughts: 33
Thoughts: Here's my take on the process, considering I'm probably dealing with some tricky configuration file system nuances:
**Analyzing ConfigFS Symlink and Directory Implementation**
Okay, so I need to get a handle on how ConfigFS handles symbolic links and directories. To really understand the underlying mechanisms and potential gotchas, I need to dive into the core implementation. I'll start by examining the code directly. Specifically, I should look at `fs/configfs/symlink.c` to see how symbolic links are created, resolved, and managed within the ConfigFS context. Then, I also need to delve into `fs/configfs/dir.c` to understand the directory structure, how directory entries are handled, and how symlinks interact within the directory hierarchy. This is where the magic (or the problems!) are likely to be. Time to get my hands dirty.
|
| 245/4 |
2026/09/02 21:26 |
tool |
read-file |
0m
Args: map[File:fs/configfs/symlink.c FirstLine:1 LineCount:100]
Results: map[Contents: 1: // SPDX-License-Identifier: GPL-2.0-or-later
2: /*
3: * symlink.c - operations for configfs symlinks.
4: *
5: * Based on sysfs:
6: * sysfs is Copyright (C) 2001, 2002, 2003 Patrick Mochel
7: *
8: * configfs Copyright (C) 2005 Oracle. All rights reserved.
9: */
10:
11: #include <linux/fs.h>
12: #include <linux/module.h>
13: #include <linux/namei.h>
14: #include <linux/slab.h>
15:
16: #include <linux/configfs.h>
17: #include "configfs_internal.h"
18:
19: /* Protects attachments of new symlinks */
20: DEFINE_MUTEX(configfs_symlink_mutex);
21:
22: static int item_depth(struct config_item * item)
23: {
24: struct config_item * p = item;
25: int depth = 0;
26: do { depth++; } while ((p = p->ci_parent) && !configfs_is_root(p));
27: return depth;
28: }
29:
30: static int item_path_length(struct config_item * item)
31: {
32: struct config_item * p = item;
33: int length = 1;
34: do {
35: length += strlen(config_item_name(p)) + 1;
36: p = p->ci_parent;
37: } while (p && !configfs_is_root(p));
38: return length;
39: }
40:
41: static void fill_item_path(struct config_item * item, char * buffer, int length)
42: {
43: struct config_item * p;
44:
45: --length;
46: for (p = item; p && !configfs_is_root(p); p = p->ci_parent) {
47: int cur = strlen(config_item_name(p));
48:
49: /* back up enough to print this bus id with '/' */
50: length -= cur;
51: memcpy(buffer + length, config_item_name(p), cur);
52: *(buffer + --length) = '/';
53: }
54: }
55:
56: static int configfs_get_target_path(struct config_item *item,
57: struct config_item *target, char *path)
58: {
59: int depth, size;
60: char *s;
61:
62: depth = item_depth(item);
63: size = item_path_length(target) + depth * 3 - 1;
64: if (size > PATH_MAX)
65: return -ENAMETOOLONG;
66:
67: pr_debug("%s: depth = %d, size = %d\n", __func__, depth, size);
68:
69: for (s = path; depth--; s += 3)
70: strcpy(s,"../");
71:
72: fill_item_path(target, path, size);
73: pr_debug("%s: path = '%s'\n", __func__, path);
74: return 0;
75: }
76:
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++;
]
|
| 246/4 |
2026/09/02 21:26 |
llm |
codeexpert |
0m
Model:
Tokens:
input: 8641
output: 38
thoughts: 18
Thoughts: Okay, here we go...
**Diving into `fs/configfs/symlink.c`**
Alright, let's get into the nitty-gritty of how configfs handles symbolic links. I'm already anticipating some interesting design choices here, especially given the flexibility configfs offers in creating and managing virtual file systems. I'm keen to see how they've implemented symlink creation, resolution, and deletion. There are always tricky parts with symlinks, especially regarding security and preventing infinite loops. I'm curious if they've integrated any specific checks or safeguards beyond what the standard VFS provides. I'm looking for the core logic that ties together the VFS operations with the configfs-specific metadata and structures. What's the link between the underlying filesystem and the symlink itself? I want to see how the target path is stored and resolved, and if there are any specific locking mechanisms in place to handle concurrent access. Let's see how well it's all put together!
|
| 247/4 |
2026/09/02 21:26 |
tool |
read-file |
0m
Args: map[File:fs/configfs/symlink.c FirstLine:101 LineCount:100]
Results: map[Contents: 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: }
115:
116:
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: }
133:
134:
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);
]
|
| 248/4 |
2026/09/02 21:26 |
llm |
codeexpert |
0m
Model:
Tokens:
input: 10500
output: 37
thoughts: 29
Thoughts: Here's my take on the section of `fs/configfs/symlink.c`, lines 201-260:
**Diving Deeper into the Symlink Handling**
Okay, let's get into the nitty-gritty. I'm focusing on the next chunk of `fs/configfs/symlink.c` β specifically lines 201 to 260. I need to understand how symlinks are being managed within the configfs framework, so I can think about how to best implement changes. I'm particularly interested in how they're created, resolved, and what sort of error handling is in place. I want to see if there are any potential bottlenecks or areas for optimization regarding the symlink implementation. Perhaps there are a bunch of unnecessary calls here that could be batched, or some logic that could be optimized with a different data structure? Are there potential race conditions in the context of the larger ConfigFS implementation that need to be addressed? Time to dig in!
|
| 249/4 |
2026/09/02 21:26 |
tool |
read-file |
0m
Args: map[File:fs/configfs/symlink.c FirstLine:201 LineCount:60]
Results: map[Contents: 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: }
212:
213: int configfs_unlink(struct inode *dir, struct dentry *dentry)
214: {
215: struct configfs_dirent *sd = dentry->d_fsdata, *target_sd;
216: struct config_item *parent_item;
217: const struct config_item_type *type;
218: int ret;
219:
220: ret = -EPERM; /* What lack-of-symlink returns */
221: if (!(sd->s_type & CONFIGFS_ITEM_LINK))
222: goto out;
223:
224: target_sd = sd->s_element;
225:
226: parent_item = configfs_get_config_item(dentry->d_parent);
227: type = parent_item->ci_type;
228:
229: spin_lock(&configfs_dirent_lock);
230: list_del_init(&sd->s_sibling);
231: spin_unlock(&configfs_dirent_lock);
232: configfs_put(sd);
233: simple_unlink(dir, dentry);
234:
235: /*
236: * drop_link() must be called before
237: * decrementing target's ->s_links, so that the order of
238: * drop_link(this, target) and drop_item(target) is preserved.
239: */
240: if (type && type->ct_item_ops &&
241: type->ct_item_ops->drop_link)
242: type->ct_item_ops->drop_link(parent_item,
243: target_sd->s_element);
244:
245: spin_lock(&configfs_dirent_lock);
246: target_sd->s_links--;
247: spin_unlock(&configfs_dirent_lock);
248: configfs_put(target_sd);
249:
250: config_item_put(parent_item);
251:
252: ret = 0;
253:
254: out:
255: return ret;
256: }
257:
258: const struct inode_operations configfs_symlink_inode_operations = {
259: .get_link = simple_get_link,
260: .setattr = configfs_setattr,
]
|
| 250/4 |
2026/09/02 21:26 |
llm |
codeexpert |
0m
Model:
Tokens:
input: 11666
output: 35
thoughts: 44
Thoughts: Okay, let's dive into `fs/configfs/dir.c`. I need a solid grasp of how the core directory operations function within ConfigFS. I want to thoroughly understand the interplay between items, groups, the notion of default groups, and how `rmdir`, `mkdir`, unregistering elements, and detaching groups are implemented in this context. It's crucial to grasp the nuances of how these fundamental directory management operations work to really understand the file system's structure and behavior. This deeper understanding will provide a much better overview of the entire project. I know what to look for, and I'm ready to learn.
|
| 251/4 |
2026/09/02 21:26 |
tool |
read-file |
0m
Args: map[File:fs/configfs/dir.c FirstLine:1 LineCount:100]
Results: map[Contents: 1: // SPDX-License-Identifier: GPL-2.0-or-later
2: /*
3: * dir.c - Operations for configfs directories.
4: *
5: * Based on sysfs:
6: * sysfs is Copyright (C) 2001, 2002, 2003 Patrick Mochel
7: *
8: * configfs Copyright (C) 2005 Oracle. All rights reserved.
9: */
10:
11: #undef DEBUG
12:
13: #include <linux/fs.h>
14: #include <linux/fsnotify.h>
15: #include <linux/mount.h>
16: #include <linux/module.h>
17: #include <linux/slab.h>
18: #include <linux/err.h>
19:
20: #include <linux/configfs.h>
21: #include "configfs_internal.h"
22:
23: /*
24: * Protects mutations of configfs_dirent linkage together with proper i_mutex
25: * Also protects mutations of symlinks linkage to target configfs_dirent
26: * Mutators of configfs_dirent linkage must *both* have the proper inode locked
27: * and configfs_dirent_lock locked, in that order.
28: * This allows one to safely traverse configfs_dirent trees and symlinks without
29: * having to lock inodes.
30: *
31: * Protects setting of CONFIGFS_USET_DROPPING: checking the flag
32: * unlocked is not reliable unless in detach_groups() called from
33: * rmdir()/unregister() and from configfs_attach_group()
34: */
35: DEFINE_SPINLOCK(configfs_dirent_lock);
36:
37: /*
38: * All of link_obj/unlink_obj/link_group/unlink_group require that
39: * subsys->su_mutex is held.
40: * But parent configfs_subsystem is NULL when config_item is root.
41: * Use this mutex when config_item is root.
42: */
43: static DEFINE_MUTEX(configfs_subsystem_mutex);
44:
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: }
67:
68: const struct dentry_operations configfs_dentry_ops = {
69: .d_iput = configfs_d_iput,
70: };
71:
72: #ifdef CONFIG_LOCKDEP
73:
74: /*
75: * Helpers to make lockdep happy with our recursive locking of default groups'
76: * inodes (see configfs_attach_group() and configfs_detach_group()).
77: * We put default groups i_mutexes in separate classes according to their depth
78: * from the youngest non-default group ancestor.
79: *
80: * For a non-default group A having default groups A/B, A/C, and A/C/D, default
81: * groups A/B and A/C will have their inode's mutex in class
82: * default_group_class[0], and default group A/C/D will be in
83: * default_group_class[1].
84: *
85: * The lock classes are declared and assigned in inode.c, according to the
86: * s_depth value.
87: * The s_depth value is initialized to -1, adjusted to >= 0 when attaching
88: * default groups, and reset to -1 when all default groups are attached. During
89: * attachment, if configfs_create() sees s_depth > 0, the lock class of the new
90: * inode's mutex is set to default_group_class[s_depth - 1].
91: */
92:
93: static void configfs_init_dirent_depth(struct configfs_dirent *sd)
94: {
95: sd->s_depth = -1;
96: }
97:
98: static void configfs_set_dir_dirent_depth(struct configfs_dirent *parent_sd,
99: struct configfs_dirent *sd)
100: {
]
|
| 252/4 |
2026/09/02 21:26 |
llm |
codeexpert |
0m
Model:
Tokens:
input: 13398
output: 37
thoughts: 16
Thoughts: Okay, here's my interpretation of that thought, framed as an expert's internal monologue:
**Diving Back into `fs/configfs/dir.c`**
Alright, time to get back into the weeds a bit. I need to refresh my understanding, or maybe even uncover a subtle detail I missed previously. The `fs/configfs/dir.c` file is a core component of how the configfs filesystem works, specifically dealing with the directory structure and the interactions within it. I want to revisit it with fresh eyes, see if I can spot any new implications. Perhaps I'm looking for a specific optimization opportunity, or maybe just want to strengthen my overall grasp of the data structures and how they're managed. This particular file is critical for understanding the file-system's behavior, especially related to the attribute handling. Gotta stay sharp! Let's get to reading.
|
| 253/4 |
2026/09/02 21:26 |
tool |
read-file |
0m
Args: map[File:fs/configfs/dir.c FirstLine:650 LineCount:100]
Results: map[Contents: 650: error = configfs_create_bin_file(item, bin_attr);
651: if (error)
652: return error;
653: }
654: }
655:
656: return 0;
657: }
658:
659: static int configfs_attach_group(struct config_item *item,
660: struct dentry *dentry,
661: struct configfs_fragment *frag);
662: static void configfs_detach_group(struct dentry *dentry);
663:
664: static void detach_groups(struct dentry *dentry)
665: {
666: struct dentry *child;
667: struct configfs_dirent *parent_sd;
668: struct configfs_dirent *sd, *next;
669:
670: parent_sd = dentry->d_fsdata;
671: spin_lock(&configfs_dirent_lock);
672: for (sd = next_dirent(parent_sd, NULL); sd; sd = next) {
673: next = next_dirent(parent_sd, sd);
674: if (!(sd->s_type & CONFIGFS_USET_DEFAULT))
675: continue;
676:
677: child = dget(sd->s_dentry);
678: spin_unlock(&configfs_dirent_lock);
679:
680: inode_lock(d_inode(child));
681:
682: configfs_detach_group(child);
683: d_inode(child)->i_flags |= S_DEAD;
684: dont_mount(child);
685:
686: inode_unlock(d_inode(child));
687:
688: d_delete(child);
689: dput(child);
690: spin_lock(&configfs_dirent_lock);
691: }
692: spin_unlock(&configfs_dirent_lock);
693: }
694:
695: /*
696: * This fakes mkdir(2) on a default_groups[] entry. It
697: * creates a dentry, attachs it, and then does fixup
698: * on the sd->s_type.
699: *
700: * We could, perhaps, tweak our parent's ->mkdir for a minute and
701: * try using vfs_mkdir. Just a thought.
702: */
703: static int create_default_group(struct dentry *parent,
704: struct config_group *group,
705: struct configfs_fragment *frag)
706: {
707: int ret;
708: struct configfs_dirent *sd;
709: /* We trust the caller holds a reference to parent */
710: struct dentry *child;
711:
712: if (!group->cg_item.ci_name)
713: group->cg_item.ci_name = group->cg_item.ci_namebuf;
714:
715: ret = -ENOMEM;
716: child = d_alloc_name(parent, group->cg_item.ci_name);
717: if (child) {
718: d_add(child, NULL);
719:
720: ret = configfs_attach_group(&group->cg_item, child, frag);
721: if (!ret) {
722: sd = child->d_fsdata;
723: sd->s_type |= CONFIGFS_USET_DEFAULT;
724: } else {
725: BUG_ON(d_inode(child));
726: d_drop(child);
727: }
728: dput(child);
729: }
730:
731: return ret;
732: }
733:
734: static int populate_groups(struct config_group *group,
735: struct configfs_fragment *frag)
736: {
737: struct dentry *parent = group->cg_item.ci_dentry;
738: struct config_group *new_group;
739:
740: list_for_each_entry(new_group, &group->default_groups, group_entry) {
741: int ret = create_default_group(parent, new_group, frag);
742: if (ret)
743: return ret;
744: }
745: return 0;
746: }
747:
748: void configfs_remove_default_groups(struct config_group *group)
749: {
]
|
| 254/4 |
2026/09/02 21:26 |
llm |
codeexpert |
0m
Model:
Tokens:
input: 15064
output: 36
thoughts: 31
Thoughts: Here's my analysis of the provided code snippet's likely thought process, rendered as a first-person summary, keeping in mind my expertise:
**Diving into the ConfigFS Directory Management**
Okay, so I'm focusing on `fs/configfs/dir.c` β specifically, I'm honing in on the crucial `configfs_rmdir` and `configfs_detach_group` functions. These are key for understanding how ConfigFS handles directory removal and, critically, detaching groups. I need to get a clear picture of the lifecycle of these operations. What's the control flow? What locking is involved? I'm anticipating potential concurrency issues if not handled carefully, especially when you're dealing with a system that can be dynamically reconfigured.
I know I'm looking at directory removal, which means I should be seeing the mechanisms for cleaning up the directory entries, any associated data, and, depending on the implementation, maybe even unregistering associated objects. I need to understand how the system ensures that children are properly detached or cleaned up *before* the parent directory is removed. Thatβs always a gotcha in a filesystem. How is the underlying data structure handled? Is reference counting involved? What protections are in place to prevent use-after-free scenarios?
With the detach group, I expect to see the code that severs the logical connection between the group and whatever objects it manages. Is there a distinction made between a "soft" detach (allowing for re-attachment) versus a full cleanup? What resources are relinquished, and in what order? Iβll be paying close attention to any memory management aspects associated with a detached group, to make sure it is handled safely. The system's robustness here is critical. I'm thinking about potential issues where a group might be detached while something still holds a reference, leading to memory leaks or worse.
I'm ready to dig in and understand the details β I have a good idea of what I'm looking for, and I'll be focused on these core functionalities to fully understand the impact on the ConfigFS system.
|
| 255/4 |
2026/09/02 21:26 |
tool |
read-file |
0m
Args: map[File:fs/configfs/dir.c FirstLine:870 LineCount:80]
Results: map[Contents: 870: * we must lock them as rmdir() would.
871: */
872: inode_lock(d_inode(dentry));
873: configfs_detach_item(dentry);
874: d_inode(dentry)->i_flags |= S_DEAD;
875: dont_mount(dentry);
876: inode_unlock(d_inode(dentry));
877: d_delete(dentry);
878: }
879: }
880:
881: return ret;
882: }
883:
884: /* Caller holds the mutex of the group's inode */
885: static void configfs_detach_group(struct dentry *dentry)
886: {
887: detach_groups(dentry);
888: configfs_detach_item(dentry);
889: }
890:
891: static int configfs_attach_group(struct config_item *item,
892: struct dentry *dentry,
893: struct configfs_fragment *frag)
894: {
895: int ret;
896: struct configfs_dirent *sd;
897:
898: ret = configfs_attach_item(item, dentry, frag);
899: if (!ret) {
900: sd = dentry->d_fsdata;
901: sd->s_type |= CONFIGFS_USET_DIR;
902:
903: /*
904: * FYI, we're faking mkdir in populate_groups()
905: * We must lock the group's inode to avoid races with the VFS
906: * which can already hit the inode and try to add/remove entries
907: * under it.
908: *
909: * We must also lock the inode to remove it safely in case of
910: * error, as rmdir() would.
911: */
912: inode_lock_nested(d_inode(dentry), I_MUTEX_CHILD);
913: configfs_adjust_dir_dirent_depth_before_populate(sd);
914: ret = populate_groups(to_config_group(item), frag);
915: if (ret) {
916: configfs_detach_group(dentry);
917: d_inode(dentry)->i_flags |= S_DEAD;
918: dont_mount(dentry);
919: }
920: configfs_adjust_dir_dirent_depth_after_populate(sd);
921: inode_unlock(d_inode(dentry));
922: if (ret)
923: d_delete(dentry);
924: }
925:
926: return ret;
927: }
928:
929: /*
930: * After the item has been detached from the filesystem view, we are
931: * ready to tear it out of the hierarchy. Notify the client before
932: * we do that so they can perform any cleanup that requires
933: * navigating the hierarchy. A client does not need to provide this
934: * callback. The subsystem semaphore MUST be held by the caller, and
935: * references must be valid for both items. It also assumes the
936: * caller has validated ci_type.
937: */
938: static void client_disconnect_notify(struct config_item *parent_item,
939: struct config_item *item)
940: {
941: const struct config_item_type *type;
942:
943: type = parent_item->ci_type;
944: BUG_ON(!type);
945:
946: if (type->ct_group_ops && type->ct_group_ops->disconnect_notify)
947: type->ct_group_ops->disconnect_notify(to_config_group(parent_item),
948: item);
949: }
]
|
| 256/4 |
2026/09/02 21:26 |
llm |
codeexpert |
0m
Model:
Tokens:
input: 16766
output: 37
thoughts: 35
Thoughts: Here's my take on the process, thinking through the provided prompt:
**Delving into configfs_rmdir in dir.c**
Alright, let's dive into this `configfs_rmdir` function. I'm going to pull up lines 1450 to 1550 of `fs/configfs/dir.c` β that's where the magic should be happening. Knowing how ConfigFS works, and having worked with it extensively, my mind immediately starts building a mental model of how a directory removal would be handled.
I expect to see the core logic for detaching the directory from the filesystem's namespace. This means unlinking it from the parent, handling any associated data structures, and ensuring a clean removal, especially when dealing with nested structures, which ConfigFS often employs for its design. I anticipate checks for any remaining entries within the directory; if it's not empty, it probably wouldn't allow a removal (unless there's a force option, which I'll be looking out for). I'm guessing there'll also be reference counting, making certain no other parts of the system are still holding onto it. The handling of the associated inode is something I will keep a close eye on, to see if its lifetime gets extended after the removal or if its lifecycle is wrapped.
I'm keen to see how `configfs_rmdir` interacts with the underlying VFS calls β are they direct VFS function calls or is there a ConfigFS-specific layer? And I'll be assessing how it deals with potential error conditions and resources; are there adequate checks for memory leaks or other race conditions? I'm anticipating that a thorough implementation will involve handling all of these aspects, preventing corruption of the FS by incomplete operation, so I can see what is being done.
This will be interesting, and Iβm ready to dissect the code and think critically about the corner cases, the error handling, and the overall design. Let's see what we've got.
|
| 257/4 |
2026/09/02 21:26 |
tool |
read-file |
0m
Args: map[File:fs/configfs/dir.c FirstLine:1460 LineCount:80]
Results: map[Contents:1460: */
1461: config_item_put(parent_item);
1462: put_fragment(frag);
1463:
1464: out:
1465: release_dentry_name_snapshot(&n);
1466: return ERR_PTR(ret);
1467: }
1468:
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;
]
|
| 258/4 |
2026/09/02 21:26 |
llm |
codeexpert |
0m
Model:
Tokens:
input: 18446
output: 37
thoughts: 27
Thoughts: Okay, let's dive into this `configfs_rmdir` function, specifically lines 1540 through 1600. I need to understand its behavior thoroughly within the context of the larger ConfigFS implementation. I'm expecting this function, being a "remove directory" operation, to perform some critical cleanup tasks. I'll need to pay close attention to things like error handling, reference counting, and how it handles potential race conditions. I'm looking for the nitty-gritty details of how it deallocates resources, unlinks entries from the filesystem tree, and maybe even handles notifications or callbacks related to directory removal. Understanding this function is pivotal for building a solid mental model of how ConfigFS handles directory structures.
|
| 259/4 |
2026/09/02 21:26 |
tool |
read-file |
0m
Args: map[File:fs/configfs/dir.c FirstLine:1540 LineCount:50]
Results: map[Contents: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: }
1578:
1579: const struct inode_operations configfs_dir_inode_operations = {
1580: .mkdir = configfs_mkdir,
1581: .rmdir = configfs_rmdir,
1582: .symlink = configfs_symlink,
1583: .unlink = configfs_unlink,
1584: .lookup = configfs_lookup,
1585: .setattr = configfs_setattr,
1586: };
1587:
1588: const struct inode_operations configfs_root_inode_operations = {
1589: .lookup = configfs_lookup,
]
|
| 260/4 |
2026/09/02 21:26 |
llm |
codeexpert |
0m
Model:
Tokens:
input: 19386
output: 30
thoughts: 65
Thoughts: Okay, here's my take on those initial thoughts, framed as a concise internal monologue:
**Investigating ConfigFS Detachment**
Alright, time to dive into this `configfs_detach_prep` situation. My first step is to quickly refresh my memory on the core functions and related areas. I need to understand how the detachment process is architected. I'll start by checking the surrounding functions, `detach_groups`, and `configfs_detach_group`. I'll also bring up the git history, particularly focusing on commits involving `configfs_symlink` and those that concern the default group handling; I suspect there might be clues related to the current behavior in how these features interact. My goal is to map out the call flow and identify the key points within the detachment sequence. I'll search the git logs for any relevant discussions or changes regarding `configfs_detach_prep` itself, or I might need to walk through the code directly to trace how that function operates. The goal here is to establish a strong understanding of the "why" and "how" of the detachment procedure.
|
| 261/4 |
2026/09/02 21:26 |
tool |
grepper |
0m
Args: map[Expression:configfs_detach_prep PathPrefix:fs/configfs]
Results: map[Output:fs/configfs/dir.c=426=static struct dentry * configfs_lookup(struct inode *dir,
--
fs/configfs/dir.c-501- */
fs/configfs/dir.c:502:static int configfs_detach_prep(struct configfs_dirent *parent_sd, struct dentry **wait)
fs/configfs/dir.c-503-{
--
fs/configfs/dir.c-530- */
fs/configfs/dir.c:531: ret = configfs_detach_prep(sd, wait);
fs/configfs/dir.c-532- if (!ret)
--
fs/configfs/dir.c=992=static int configfs_dump(struct configfs_dirent *sd, int level)
--
fs/configfs/dir.c-1046- * Only subdirectories count here. Files (CONFIGFS_NOT_PINNED) are
fs/configfs/dir.c:1047: * attributes. This is similar but not the same to configfs_detach_prep().
fs/configfs/dir.c:1048: * Note that configfs_detach_prep() expects the parent to be locked when it
fs/configfs/dir.c-1049- * is called, but we lock the parent *inside* configfs_depend_prep(). We
--
fs/configfs/dir.c=1289=static struct dentry *configfs_mkdir(struct mnt_idmap *idmap, struct inode *dir,
--
fs/configfs/dir.c-1417- spin_lock(&configfs_dirent_lock);
fs/configfs/dir.c:1418: /* This will make configfs_detach_prep() fail */
fs/configfs/dir.c-1419- sd->s_type |= CONFIGFS_USET_IN_MKDIR;
--
fs/configfs/dir.c=1469=static int configfs_rmdir(struct inode *dir, struct dentry *dentry)
--
fs/configfs/dir.c-1512- if (!ret) {
fs/configfs/dir.c:1513: ret = configfs_detach_prep(sd, &wait);
fs/configfs/dir.c-1514- if (ret)
--
fs/configfs/dir.c=1796=void configfs_unregister_group(struct config_group *group)
--
fs/configfs/dir.c-1809- spin_lock(&configfs_dirent_lock);
fs/configfs/dir.c:1810: configfs_detach_prep(sd, NULL);
fs/configfs/dir.c-1811- spin_unlock(&configfs_dirent_lock);
--
fs/configfs/dir.c=1934=void configfs_unregister_subsystem(struct configfs_subsystem *subsys)
--
fs/configfs/dir.c-1955- spin_lock(&configfs_dirent_lock);
fs/configfs/dir.c:1956: if (configfs_detach_prep(sd, NULL)) {
fs/configfs/dir.c-1957- pr_err("Tried to unregister non-empty subsystem!\n");
]
|
| 262/4 |
2026/09/02 21:26 |
llm |
codeexpert |
0m
Model:
Tokens:
input: 20551
output: 36
thoughts: 26
Thoughts: Here's my summary, as if I were thinking these thoughts:
**Dissecting `configfs_detach_prep` and its Context**
Okay, time to dig into the `configfs_detach_prep` function. I need to understand exactly what happens here, especially in relation to the broader directory operations within the configfs framework. I'm heading straight to `fs/configfs/dir.c`. I need to see the function in context. What are the calling points? What other functions are closely related or called by `configfs_detach_prep`? What is the purpose of this preparation stage anyway? Is it setting up some sort of clean-up, resource release, or validation of the structure to detach from configfs? It is likely a critical step before removing something from the configuration space. I'll need to pay close attention to the arguments it receives and how they're used. I'll also be looking for any potential race conditions or locking mechanisms involved; this is almost certainly a sensitive operation within a file system. I expect that I should know how things are prepared before detaching. Time to dive in.
|
| 263/4 |
2026/09/02 21:26 |
tool |
read-file |
0m
Args: map[File:fs/configfs/dir.c FirstLine:480 LineCount:80]
Results: map[Contents: 480: inode->i_size = 0;
481: inode->i_fop = &configfs_bin_file_operations;
482: } else {
483: inode->i_size = PAGE_SIZE;
484: inode->i_fop = &configfs_file_operations;
485: }
486: goto done;
487: }
488: }
489: spin_unlock(&configfs_dirent_lock);
490: done:
491: return d_splice_alias(inode, dentry);
492: }
493:
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: }
543:
544: /*
545: * Walk the tree, resetting CONFIGFS_USET_DROPPING wherever it was
546: * set.
547: */
548: static void configfs_detach_rollback(struct configfs_dirent *parent_sd)
549: {
550: struct configfs_dirent *sd;
551:
552: parent_sd->s_type &= ~CONFIGFS_USET_DROPPING;
553:
554: list_for_each_entry(sd, &parent_sd->s_children, s_sibling)
555: if (sd->s_type & CONFIGFS_USET_DEFAULT)
556: configfs_detach_rollback(sd);
557: }
558:
559: /*
]
|
| 264/4 |
2026/09/02 21:26 |
llm |
codeexpert |
0m
Model:
Tokens:
input: 21987
output: 34
thoughts: 263
Thoughts: **My Analysis of a Potential Race Condition in `configfs_symlink`**
Okay, let's break this down meticulously. The core question is whether the `inode_lock(dir)` held in `configfs_symlink` *guarantees* that a concurrent `configfs_rmdir` operation on the root object is blocked until we can safely walk up the parent chain and grab a reference. We're concerned about freeing the embedded `parent_item` prematurely.
First, I need to refresh my understanding of how configfs structures itself. I'm thinking about the hierarchy: configfs items, configfs groups, the concept of "default groups," and, critically, how the root object (which could be created explicitly via `mkdir` or implicitly as a default group) fits into this. It's easy to get lost in the weeds with configfs.
Next, I need to drill down into the creation and destruction mechanisms for both standard and default groups. How are these entities brought into existence and, more importantly, how are they removed? What are the synchronization primitives involved, particularly around `detach_groups()`? This is where the potential race lives.
To get a clearer picture, I'm going to scan the git log. I'll search for commits that mention `configfs_symlink`, `configfs_rmdir`, and the interaction with "default groups." I might also try broader searches to capture any relevant changes around the locking of the `d_parent` and the root object. I want to see if any previous discussions or fixes shed light on this scenario. I'm particularly interested in seeing commits related to the `parent_item` and its management and whether there are any related known bugs. I need to be *absolutely* sure about the assumptions here before I proceed.
|
| 265/4 |
2026/09/02 21:26 |
tool |
git-log |
0m
Args: map[MessageRegexps:[symlink rmdir] PathPrefix:fs/configfs]
Results: map[Output:ba80aa909c99 configfs: Fix race between create_link and configfs_rmdir
81d44ed159e3 configfs: don't bother with checks for mkdir/rmdir/unlink/symlink in root
e74cc06df3b0 configfs: Silence lockdep on mkdir() and rmdir()
0e0333429a62 configfs: Silence lockdep on mkdir(), rmdir() and configfs_depend_item()
9a73d78cda75 [PATCH] configfs: Fix failing symlink() making rmdir() fail
4768e9b18dc6 [PATCH] configfs: Fix symlink() to a removing item
b3e76af87441 configfs: Fix deadlock with racing rmdir() and rename()
5301a77da2da configfs: Protect configfs_dirent s_links list mutations
]
|
| 266/4 |
2026/09/02 21:26 |
llm |
codeexpert |
0m
Model:
Tokens:
input: 22899
output: 25
thoughts: 42
Thoughts: Okay, let's dive into these commits: ba80aa909c99, 4768e9b18dc6, and 9a73d78cda75.
**Analyzing Recent Commits**
Right, let me pull up the diffs for those. I need to quickly grasp the scope of these changes, see what regressions or improvements are made, whether they are in the same module, which files they touch. I'm focusing on their potential impact on performance, stability, and, of course, the integration with the existing codebase. If these look like straightforward bug fixes, I'll assess how thoroughly the testing was done. If there are changes to any core algorithm, I'll be looking for detailed justification and the presence of thorough unit tests, and regression testing is vital. I am going to see if any of these commits potentially introduce a new performance bottleneck. I am looking for the potential for parallelization.
|
| 267/4 |
2026/09/02 21:26 |
tool |
git-show |
0m
Args:
Results: map[Output:commit ba80aa909c99802c428682c352b0ee0baac0acd3
Author: Nicholas Bellinger <nab@linux-iscsi.org>
Date: Thu Jun 8 04:51:54 2017 +0000
configfs: Fix race between create_link and configfs_rmdir
This patch closes a long standing race in configfs between
the creation of a new symlink in create_link(), while the
symlink target's config_item is being concurrently removed
via configfs_rmdir().
This can happen because the symlink target's reference
is obtained by config_item_get() in create_link() before
the CONFIGFS_USET_DROPPING bit set by configfs_detach_prep()
during configfs_rmdir() shutdown is actually checked..
This originally manifested itself on ppc64 on v4.8.y under
heavy load using ibmvscsi target ports with Novalink API:
[ 7877.289863] rpadlpar_io: slot U8247.22L.212A91A-V1-C8 added
[ 7879.893760] ------------[ cut here ]------------
[ 7879.893768] WARNING: CPU: 15 PID: 17585 at ./include/linux/kref.h:46 config_item_get+0x7c/0x90 [configfs]
[ 7879.893811] CPU: 15 PID: 17585 Comm: targetcli Tainted: G O 4.8.17-customv2.22 #12
[ 7879.893812] task: c00000018a0d3400 task.stack: c0000001f3b40000
[ 7879.893813] NIP: d000000002c664ec LR: d000000002c60980 CTR: c000000000b70870
[ 7879.893814] REGS: c0000001f3b43810 TRAP: 0700 Tainted: G O (4.8.17-customv2.22)
[ 7879.893815] MSR: 8000000000029033 <SF,EE,ME,IR,DR,RI,LE> CR: 28222242 XER: 00000000
[ 7879.893820] CFAR: d000000002c664bc SOFTE: 1
GPR00: d000000002c60980 c0000001f3b43a90 d000000002c70908 c0000000fbc06820
GPR04: c0000001ef1bd900 0000000000000004 0000000000000001 0000000000000000
GPR08: 0000000000000000 0000000000000001 d000000002c69560 d000000002c66d80
GPR12: c000000000b70870 c00000000e798700 c0000001f3b43ca0 c0000001d4949d40
GPR16: c00000014637e1c0 0000000000000000 0000000000000000 c0000000f2392940
GPR20: c0000001f3b43b98 0000000000000041 0000000000600000 0000000000000000
GPR24: fffffffffffff000 0000000000000000 d000000002c60be0 c0000001f1dac490
GPR28: 0000000000000004 0000000000000000 c0000001ef1bd900 c0000000f2392940
[ 7879.893839] NIP [d000000002c664ec] config_item_get+0x7c/0x90 [configfs]
[ 7879.893841] LR [d000000002c60980] check_perm+0x80/0x2e0 [configfs]
[ 7879.893842] Call Trace:
[ 7879.893844] [c0000001f3b43ac0] [d000000002c60980] check_perm+0x80/0x2e0 [configfs]
[ 7879.893847] [c0000001f3b43b10] [c000000000329770] do_dentry_open+0x2c0/0x460
[ 7879.893849] [c0000001f3b43b70] [c000000000344480] path_openat+0x210/0x1490
[ 7879.893851] [c0000001f3b43c80] [c00000000034708c] do_filp_open+0xfc/0x170
[ 7879.893853] [c0000001f3b43db0] [c00000000032b5bc] do_sys_open+0x1cc/0x390
[ 7879.893856] [c0000001f3b43e30] [c000000000009584] system_call+0x38/0xec
[ 7879.893856] Instruction dump:
[ 7879.893858] 409d0014 38210030 e8010010 7c0803a6 4e800020 3d220000 e94981e0 892a0000
[ 7879.893861] 2f890000 409effe0 39200001 992a0000 <0fe00000> 4bffffd0 60000000 60000000
[ 7879.893866] ---[ end trace 14078f0b3b5ad0aa ]---
To close this race, go ahead and obtain the symlink's target
config_item reference only after the existing CONFIGFS_USET_DROPPING
check succeeds.
This way, if configfs_rmdir() wins create_link() will return -ENONET,
and if create_link() wins configfs_rmdir() will return -EBUSY.
Reported-by: Bryant G. Ly <bryantly@linux.vnet.ibm.com>
Tested-by: Bryant G. Ly <bryantly@linux.vnet.ibm.com>
Signed-off-by: Nicholas Bellinger <nab@linux-iscsi.org>
Signed-off-by: Christoph Hellwig <hch@lst.de>
Cc: stable@vger.kernel.org
diff --git a/fs/configfs/symlink.c b/fs/configfs/symlink.c
index a6ab012a2c6a..c8aabba502f6 100644
--- a/fs/configfs/symlink.c
+++ b/fs/configfs/symlink.c
@@ -83,14 +83,13 @@ static int create_link(struct config_item *parent_item,
ret = -ENOMEM;
sl = kmalloc(sizeof(struct configfs_symlink), GFP_KERNEL);
if (sl) {
- sl->sl_target = config_item_get(item);
spin_lock(&configfs_dirent_lock);
if (target_sd->s_type & CONFIGFS_USET_DROPPING) {
spin_unlock(&configfs_dirent_lock);
- config_item_put(item);
kfree(sl);
return -ENOENT;
}
+ sl->sl_target = config_item_get(item);
list_add(&sl->sl_list, &target_sd->s_links);
spin_unlock(&configfs_dirent_lock);
ret = configfs_create_link(sl, parent_item->ci_dentry,
]
|
| 268/4 |
2026/09/02 21:26 |
llm |
codeexpert |
0m
Model:
Tokens:
input: 25776
output: 26
thoughts: 30
Thoughts: Here's my take on those recent commits:
**Diving into ConfigFS Symlink and Directory Changes**
Alright, let's get down to brass tacks. I need to get a handle on what's been happening with the `fs/configfs/symlink.c` and `fs/configfs/dir.c` files lately. Specifically, I want to drill into the commits that have touched those. This is important because any modifications to symlink or directory handling in ConfigFS can have wide-ranging implications for how it functions, especially in complex configurations or under heavy load. I'm looking for clues about potential bug fixes, performance improvements, security enhancements, or even refactoring that might alter the established behavior I'm used to. Time to fire up the commit log and dig in. I'm expecting to see pretty low-level changes, but I know how subtle even small tweaks can ripple through a system like this. This is where the fun begins.
|
| 269/4 |
2026/09/02 21:26 |
tool |
git-log |
0m
Args: map[Count:20 PathPrefix:fs/configfs/]
Results: map[Output:d53ac61b6120 create_default_group(): pass parent's dentry instead of config_group
ad235a65dfff configfs_attach_group(): drop the unused parent_item argument
d4152c7562a8 configs_attach_item(): drop unused parent_item argument
967c898d9def configfs_create(): lift parent timestamp updates into callers
43b45755e80e kill configfs_drop_dentry()
1387aedb391a configfs: mark pinned dentries persistent
1a29e25799bc configfs: dentry refcount needs to be pinned only once
abe91a2aa9c4 switch configfs_detach_{group,item}() to passing dentry
05a4d3d1b676 configfs_remove_dir(), detach_attrs(): switch to passing dentry
34a62943762c populate_attrs(): move cleanup to the sole caller
17bee4d214a4 populate_group(): move cleanup on failure to the sole caller
acef053618d1 configfs_detach_rollback(): pass configfs_dirent instead of dentry
cbd6bbd48732 configfs_do_depend_item(): pass configfs_dirent instead of dentry
764682e01184 configfs_depend_prep(): pass configfs_dirent instead of dentry
96551d7f9f7b configfs_detach_prep(): pass configfs_dirent instead of dentry
5cefe23f6374 configfs_mkdir(): use take_dentry_name_snapshot()
9b9e8bb81c41 configfs: fix lockless traversals of ->s_children
0c23a964a0f9 configfs_lookup(): switch to d_splice_alias()
10da12d352b7 configfs_lookup(): don't leave ->s_dentry dangling on failure
1b27d11b17b7 configfs: replace __get_free_pages() with kzalloc()
]
|
| 270/4 |
2026/09/02 21:26 |
llm |
codeexpert |
0m
Model:
Tokens:
input: 26566
output: 26
thoughts: 31
Thoughts: Okay, here's my interpretation of those thoughts, presented as a personal thought process, as requested:
**Peering into the ConfigFS Architecture**
Alright, time to dig in. To really get a grasp on how this configfs thing is structured, I need to go straight to the source. Let's start with the most recent commits β specifically, I'll examine commit `1a29e25799bc`. Looking at the history should quickly reveal the key design decisions, implementation choices, and the general evolution of the configuration file system. I anticipate that recent commits will be invaluable to understanding its function, as recent changes usually involve refactoring, bug fixes, or new features which can show how its meant to work. I can also look at other recent commits alongside this, to give a comprehensive view of the surrounding code. This context will be critical. Understanding the design is essential before any changes or even understanding of the configuration system can be achieved.
|
| 271/4 |
2026/09/02 21:26 |
tool |
git-show |
0m
Args:
Results: map[Output:commit 1a29e25799bcd7c3b2285f46e2c630b62d969b25
Author: Al Viro <viro@zeniv.linux.org.uk>
Date: Tue May 12 03:19:41 2026 -0400
configfs: dentry refcount needs to be pinned only once
currently we have a weird situation where
* symlinks and roots of subtrees created by mkdir are pinned once
* subdirectories of subtrees created by mkdir are pinned twice
* roots of subtrees created by register_{group,subsystem} are pinned
twice.
It makes things harder to follow for no good reason. The goal is to
encapsulate the unbalanced dget/dput into d_{make,discard}_persisitent()
and, preferably, allow a use of simple_recursive_removal() or analogue
thereof. So let's regularize that and pin things only once.
create_default_group() and configfs_register_subsystem() don't need to
keep their reference around on success - configfs_create_dir() has pinned
the sucker already. So we can drop the reference passed to
configfs_create_dir() (via configfs_attach_group(), etc.) both on success
and on failure. On the removal side we no longer have the double references,
so we need an explicit dget() to compensate.
Reviewed-by: Jan Kara <jack@suse.cz>
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
diff --git a/fs/configfs/dir.c b/fs/configfs/dir.c
index 87a0847d88b9..4836ffb3b70f 100644
--- a/fs/configfs/dir.c
+++ b/fs/configfs/dir.c
@@ -657,7 +657,7 @@ static void detach_groups(struct dentry *dentry)
if (!(sd->s_type & CONFIGFS_USET_DEFAULT))
continue;
- child = sd->s_dentry;
+ child = dget(sd->s_dentry);
spin_unlock(&configfs_dirent_lock);
inode_lock(d_inode(child));
@@ -708,8 +708,8 @@ static int create_default_group(struct config_group *parent_group,
} else {
BUG_ON(d_inode(child));
d_drop(child);
- dput(child);
}
+ dput(child);
}
return ret;
@@ -1781,7 +1781,7 @@ EXPORT_SYMBOL(configfs_register_group);
void configfs_unregister_group(struct config_group *group)
{
struct configfs_subsystem *subsys = group->cg_subsys;
- struct dentry *dentry = group->cg_item.ci_dentry;
+ struct dentry *dentry = dget(group->cg_item.ci_dentry);
struct dentry *parent = group->cg_item.ci_parent->ci_dentry;
struct configfs_dirent *sd = dentry->d_fsdata;
struct configfs_fragment *frag = sd->s_frag;
@@ -1896,12 +1896,12 @@ int configfs_register_subsystem(struct configfs_subsystem *subsys)
if (err) {
BUG_ON(d_inode(dentry));
d_drop(dentry);
- dput(dentry);
} else {
spin_lock(&configfs_dirent_lock);
configfs_dir_set_ready(dentry->d_fsdata);
spin_unlock(&configfs_dirent_lock);
}
+ dput(dentry);
}
inode_unlock(d_inode(root));
@@ -1920,7 +1920,7 @@ int configfs_register_subsystem(struct configfs_subsystem *subsys)
void configfs_unregister_subsystem(struct configfs_subsystem *subsys)
{
struct config_group *group = &subsys->su_group;
- struct dentry *dentry = group->cg_item.ci_dentry;
+ struct dentry *dentry = dget(group->cg_item.ci_dentry);
struct dentry *root = dentry->d_sb->s_root;
struct configfs_dirent *sd = dentry->d_fsdata;
struct configfs_fragment *frag = sd->s_frag;
]
|
| 272/4 |
2026/09/02 21:26 |
llm |
codeexpert |
0m
Model:
Tokens:
input: 28039
output: 29
thoughts: 3193
Thoughts: **Analyzing Concurrency in `configfs_symlink` and `configfs_rmdir`**
Okay, let's break this down. The core concern is ensuring the safety of a symlink creation within a potentially concurrently deleted configfs structure. The scenario revolves around `configfs_symlink` holding a lock on a directory (`dir`) that might be a default group, and a simultaneous `configfs_rmdir` operation on the root object, which then attempts to detach and remove these default groups.
Initially, `configfs_symlink` is called from VFS, holding `inode_lock(dir)`, where `dir` is the inode of the parent dentry. This parent dentry can be a regular user-created group/item or, critically, a "default group". These default groups are auto-created when a parent group is created with `populate_groups()`. I'm focusing on the case where the symlink is created *within* a default group, or within a nested default group.
The key question is whether `inode_lock(dir)` held by `configfs_symlink` prevents `configfs_rmdir` on the root object from proceeding past `detach_groups()`.
`configfs_rmdir` takes the root object's inode, and VFS calls `inode_lock(dir)` and `inode_lock_nested()` on the target being removed.
`configfs_rmdir` calls `detach_groups`, which is where the concurrency concern arises. In `detach_groups`, it iterates through child default groups, acquiring an `inode_lock()` on each one. This is the crucial step. If the `dir` held by `configfs_symlink` is one of these children (or a nested ancestor!), the `configfs_rmdir` operation will block on `inode_lock(dir)`, because it will be held. This prevents `detach_groups()` from completing, thus preventing the root rmdir from progressing past the point of attempting to detach our "pinned" parent group.
Therefore, the `inode_lock(dir)` *does* prevent the rmdir from proceeding past the default group. It will block until the lock held by `configfs_symlink` is released.
Finally, regarding the second part, since `detach_groups()` will block until `configfs_symlink` unlocks, it is *guaranteed* that the walk up the `d_parent` chain to find and reference the root object before releasing `inode_unlock(dir)` is safe. The rmdir cannot proceed until the symlink operation releases the lock. The rmdir will block until `configfs_symlink` unlocks the inode. The critical section in `configfs_symlink` guarantees that we can safely acquire the root object reference before releasing the parent inode lock.
My conclusion: the locking scheme provides the necessary protection, and the "pinning" of the root object is therefore safe because `inode_lock` blocks the rmdir from releasing the reference prematurely.
|
| 273/4 |
2026/09/02 21:27 |
tool |
grepper |
0m
Args: map[Expression:client_drop_item PathPrefix:fs/configfs]
Results: map[Output:fs/configfs/dir.c=938=static void client_disconnect_notify(struct config_item *parent_item,
--
fs/configfs/dir.c-956- */
fs/configfs/dir.c:957:static void client_drop_item(struct config_item *parent_item,
fs/configfs/dir.c-958- struct config_item *item)
--
fs/configfs/dir.c=1289=static struct dentry *configfs_mkdir(struct mnt_idmap *idmap, struct inode *dir,
--
fs/configfs/dir.c-1442- unlink_obj(item);
fs/configfs/dir.c:1443: client_drop_item(parent_item, item);
fs/configfs/dir.c-1444-
--
fs/configfs/dir.c=1469=static int configfs_rmdir(struct inode *dir, struct dentry *dentry)
--
fs/configfs/dir.c-1566-
fs/configfs/dir.c:1567: client_drop_item(parent_item, item);
fs/configfs/dir.c-1568- mutex_unlock(&subsys->su_mutex);
]
|
| 274/4 |
2026/09/02 21:27 |
llm |
codeexpert |
0m
Model:
Tokens:
input: 32235
output: 36
thoughts: 28
Thoughts: Okay, here's my interpretation of those thoughts, tailored for an expert audience:
**Diving into ConfigFS Directory Operations**
Right, let's pull up `fs/configfs/dir.c`. I need to get my head around these directory-related operations, specifically `client_drop_item` and `unlink_group`. These are critical for managing the lifecycle of ConfigFS objects. `client_drop_item` likely handles the removal of a client's specific attribute or child object within the ConfigFS namespace, and I need to understand the locking, reference counting, and any potential side effects like resource cleanup that are involved. It's particularly important to ensure there aren't any race conditions or dangling pointers when a client removes an item it no longer needs.
Then there's `unlink_group`. This seems to be the function responsible for completely removing a ConfigFS group or a subtree. I'm especially interested in how it handles nested groups, whether it traverses the directory structure recursively, and how it manages the teardown of any associated resources or underlying file system structures. I'll need to pay close attention to the order of operations here; it's a common area for potential errors if things aren't done correctly. I want to make sure the unlink operation is robust and doesn't leave the system in an inconsistent state. Let's see how these are implemented in detail, specifically the implications for locking, reference counting, and error handling. I need to refresh my understanding of how ConfigFS handles atomicity and data consistency across potentially concurrent operations.
|
| 275/4 |
2026/09/02 21:27 |
tool |
read-file |
0m
Args: map[File:fs/configfs/dir.c FirstLine:950 LineCount:40]
Results: map[Contents: 950:
951: /*
952: * Drop the initial reference from make_item()/make_group()
953: * This function assumes that reference is held on item
954: * and that item holds a valid reference to the parent. Also, it
955: * assumes the caller has validated ci_type.
956: */
957: static void client_drop_item(struct config_item *parent_item,
958: struct config_item *item)
959: {
960: const struct config_item_type *type;
961:
962: type = parent_item->ci_type;
963: BUG_ON(!type);
964:
965: /*
966: * If ->drop_item() exists, it is responsible for the
967: * config_item_put().
968: */
969: if (type->ct_group_ops && type->ct_group_ops->drop_item)
970: type->ct_group_ops->drop_item(to_config_group(parent_item),
971: item);
972: else
973: config_item_put(item);
974: }
975:
976: #ifdef DEBUG
977: static void configfs_dump_one(struct configfs_dirent *sd, int level)
978: {
979: pr_info("%*s\"%s\":\n", level, " ", configfs_get_name(sd));
980:
981: #define type_print(_type) if (sd->s_type & _type) pr_info("%*s %s\n", level, " ", #_type)
982: type_print(CONFIGFS_ROOT);
983: type_print(CONFIGFS_DIR);
984: type_print(CONFIGFS_ITEM_ATTR);
985: type_print(CONFIGFS_ITEM_LINK);
986: type_print(CONFIGFS_USET_DIR);
987: type_print(CONFIGFS_USET_DEFAULT);
988: type_print(CONFIGFS_USET_DROPPING);
989: #undef type_print
]
|
| 276/4 |
2026/09/02 21:27 |
llm |
codeexpert |
0m
Model:
Tokens:
input: 33275
output: 27
thoughts: 17
Thoughts: Here's my take on those thoughts:
**Analyzing `unlink_group` and `unlink_obj`**
Okay, so we're diving into `unlink_group` and `unlink_obj`. Clearly, these functions are critical for resource management, particularly in situations where we need to sever connections or relationships between objects or within a group. My initial instinct is to consider their roles in garbage collection or memory deallocation, but I know it's probably more nuanced than just that. Are these direct operations, or do they trigger more complex cascading effects? Thinking about potential edge cases is key here. What happens if a group is partially unlinked? What about concurrent access scenarios? We must also consider the object's lifecycle. Does `unlink` simply remove a pointer, or does it handle the underlying object itself. I need to understand the full context. Let's dig deeper and get this right.
|
| 277/4 |
2026/09/02 21:27 |
tool |
grepper |
0m
Args: map[Expression:unlink_group PathPrefix:fs/configfs]
Results: map[Output:fs/configfs/dir.c=35=DEFINE_SPINLOCK(configfs_dirent_lock);
--
fs/configfs/dir.c-37-/*
fs/configfs/dir.c:38: * All of link_obj/unlink_obj/link_group/unlink_group require that
fs/configfs/dir.c-39- * subsys->su_mutex is held.
--
fs/configfs/dir.c=757=EXPORT_SYMBOL(configfs_remove_default_groups);
--
fs/configfs/dir.c-759-/*
fs/configfs/dir.c:760: * All of link_obj/unlink_obj/link_group/unlink_group require that
fs/configfs/dir.c-761- * subsys->su_mutex is held.
--
fs/configfs/dir.c=783=static void link_obj(struct config_item *parent_item, struct config_item *item)
--
fs/configfs/dir.c-804-
fs/configfs/dir.c:805:static void unlink_group(struct config_group *group)
fs/configfs/dir.c-806-{
--
fs/configfs/dir.c-809- list_for_each_entry(new_group, &group->default_groups, group_entry)
fs/configfs/dir.c:810: unlink_group(new_group);
fs/configfs/dir.c-811-
--
fs/configfs/dir.c=1289=static struct dentry *configfs_mkdir(struct mnt_idmap *idmap, struct inode *dir,
--
fs/configfs/dir.c-1439- if (group)
fs/configfs/dir.c:1440: unlink_group(group);
fs/configfs/dir.c-1441- else
--
fs/configfs/dir.c=1469=static int configfs_rmdir(struct inode *dir, struct dentry *dentry)
--
fs/configfs/dir.c-1557- client_disconnect_notify(parent_item, item);
fs/configfs/dir.c:1558: unlink_group(to_config_group(item));
fs/configfs/dir.c-1559- } else {
--
fs/configfs/dir.c=1751=int configfs_register_group(struct config_group *parent_group,
--
fs/configfs/dir.c-1782- mutex_lock(&subsys->su_mutex);
fs/configfs/dir.c:1783: unlink_group(group);
fs/configfs/dir.c-1784- mutex_unlock(&subsys->su_mutex);
--
fs/configfs/dir.c=1796=void configfs_unregister_group(struct config_group *group)
--
fs/configfs/dir.c-1822- mutex_lock(&subsys->su_mutex);
fs/configfs/dir.c:1823: unlink_group(group);
fs/configfs/dir.c-1824- mutex_unlock(&subsys->su_mutex);
--
fs/configfs/dir.c=1872=int configfs_register_subsystem(struct configfs_subsystem *subsys)
--
fs/configfs/dir.c-1924- mutex_lock(&configfs_subsystem_mutex);
fs/configfs/dir.c:1925: unlink_group(group);
fs/configfs/dir.c-1926- mutex_unlock(&configfs_subsystem_mutex);
--
fs/configfs/dir.c=1934=void configfs_unregister_subsystem(struct configfs_subsystem *subsys)
--
fs/configfs/dir.c-1973- mutex_lock(&configfs_subsystem_mutex);
fs/configfs/dir.c:1974: unlink_group(group);
fs/configfs/dir.c-1975- mutex_unlock(&configfs_subsystem_mutex);
]
|
| 278/4 |
2026/09/02 21:27 |
llm |
codeexpert |
0m
Model:
Tokens:
input: 34564
output: 36
thoughts: 37
Thoughts: Okay, let's dive into this. I'm focusing in on `unlink_group` and `unlink_obj`, specifically within the `fs/configfs/dir.c` file, around lines 780 to 830. I'm assuming there's some interesting behavior related to unlinking within the ConfigFS implementation that I need to understand better. I suspect it may be an area of potential issues, especially concerning resource management and concurrency. I need to see how these functions handle the removal of ConfigFS groups and objects. I want to check for potential race conditions or resource leaks. Are there any subtle issues with how they handle references or dependencies? My goal here is a deep understanding of how unlink operations work in ConfigFS, which is crucial for overall system stability and correctness, especially in complex configurations. Let's see what the code actually *does*.
|
| 279/4 |
2026/09/02 21:27 |
tool |
read-file |
0m
Args: map[File:fs/configfs/dir.c FirstLine:780 LineCount:50]
Results: map[Contents: 780: }
781: }
782:
783: static void link_obj(struct config_item *parent_item, struct config_item *item)
784: {
785: /*
786: * Parent seems redundant with group, but it makes certain
787: * traversals much nicer.
788: */
789: item->ci_parent = parent_item;
790:
791: /*
792: * We hold a reference on the parent for the child's ci_parent
793: * link.
794: */
795: item->ci_group = config_group_get(to_config_group(parent_item));
796: list_add_tail(&item->ci_entry, &item->ci_group->cg_children);
797:
798: /*
799: * We hold a reference on the child for ci_entry on the parent's
800: * cg_children
801: */
802: config_item_get(item);
803: }
804:
805: static void unlink_group(struct config_group *group)
806: {
807: struct config_group *new_group;
808:
809: list_for_each_entry(new_group, &group->default_groups, group_entry)
810: unlink_group(new_group);
811:
812: group->cg_subsys = NULL;
813: unlink_obj(&group->cg_item);
814: }
815:
816: static void link_group(struct config_group *parent_group, struct config_group *group)
817: {
818: struct config_group *new_group;
819: struct configfs_subsystem *subsys = NULL; /* gcc is a turd */
820:
821: link_obj(&parent_group->cg_item, &group->cg_item);
822:
823: if (parent_group->cg_subsys)
824: subsys = parent_group->cg_subsys;
825: else if (configfs_is_root(&parent_group->cg_item))
826: subsys = to_configfs_subsystem(group);
827: else
828: BUG();
829: group->cg_subsys = subsys;
]
|
| 280/4 |
2026/09/02 21:27 |
llm |
codeexpert |
0m
Model:
Tokens:
input: 35545
output: 36
thoughts: 18
Thoughts: Here's my take on those thoughts, tailored for an expert like yourself:
**Analyzing `unlink_obj`**
Okay, so we're circling back to `unlink_obj`, specifically around line 830. I'm immediately interested in *why* we're focusing on this particular function at this point in the codebase. What triggered this examination? Is it related to a specific bug report, a performance bottleneck, or perhaps just a general code review sweep? My initial thought is to quickly mentally reconstruct the surrounding code context. I'll be looking for calls to `unlink_obj`, potential data races, memory management nuances, and any indications of how this function interacts with other core components, especially those handling object lifecycles and reference counting. I want to see if this is potentially a focal point for a use-after-free vulnerability, or some kind of object corruption, given our interest. The line number is a good starting point, but I'm mentally tracing the paths that lead into `unlink_obj` right now.
|
| 281/4 |
2026/09/02 21:27 |
tool |
read-file |
0m
Args: map[File:fs/configfs/dir.c FirstLine:830 LineCount:40]
Results: map[Contents: 830:
831: list_for_each_entry(new_group, &group->default_groups, group_entry)
832: link_group(group, new_group);
833: }
834:
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: }
841:
842: /*
843: * The goal is that configfs_attach_item() (and
844: * configfs_attach_group()) can be called from either the VFS or this
845: * module. That is, they assume that the items have been created,
846: * the dentry allocated, and the dcache is all ready to go.
847: *
848: * If they fail, they must clean up after themselves as if they
849: * had never been called. The caller (VFS or local function) will
850: * handle cleaning up the dcache bits.
851: *
852: * configfs_detach_group() and configfs_detach_item() behave similarly on
853: * the way out. They assume that the proper semaphores are held, they
854: * clean up the configfs items, and they expect their callers will
855: * handle the dcache bits.
856: */
857: static int configfs_attach_item(struct config_item *item,
858: struct dentry *dentry,
859: struct configfs_fragment *frag)
860: {
861: int ret;
862:
863: ret = configfs_create_dir(item, dentry, frag);
864: if (!ret) {
865: ret = populate_attrs(item);
866: if (ret) {
867: /*
868: * We are going to remove an inode and its dentry but
869: * the VFS may already have hit and used them. Thus,
]
|
| 282/4 |
2026/09/02 21:27 |
llm |
codeexpert |
0m
Model:
Tokens:
input: 36453
output: 27
thoughts: 13
Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience:
**Tracing the Source of `unlink_obj`**
Okay, so I need to understand the behavior of this `unlink_obj` function. My first instinct is to pinpoint its definition. Where is it actually declared? Is it a standard library call, a module-level function within the current project, or maybe even something defined in a third-party library? That's the crucial first step. I need to know the origin to understand its purpose and how it fits into the broader picture. Let's start by quickly tracking down where it's *defined*. Time to leverage my usual tools, likely a combination of grep, IDE search, or maybe even some cross-referencing within the codebase. Understanding the surrounding context is going to be key, which is why tracing the definition is my top priority.
|
| 283/4 |
2026/09/02 21:27 |
tool |
grepper |
0m
Args: map[Expression:unlink_obj PathPrefix:fs/configfs]
Results: map[Output:fs/configfs/dir.c=35=DEFINE_SPINLOCK(configfs_dirent_lock);
--
fs/configfs/dir.c-37-/*
fs/configfs/dir.c:38: * All of link_obj/unlink_obj/link_group/unlink_group require that
fs/configfs/dir.c-39- * subsys->su_mutex is held.
--
fs/configfs/dir.c=757=EXPORT_SYMBOL(configfs_remove_default_groups);
--
fs/configfs/dir.c-759-/*
fs/configfs/dir.c:760: * All of link_obj/unlink_obj/link_group/unlink_group require that
fs/configfs/dir.c-761- * subsys->su_mutex is held.
--
fs/configfs/dir.c-763-
fs/configfs/dir.c:764:static void unlink_obj(struct config_item *item)
fs/configfs/dir.c-765-{
--
fs/configfs/dir.c=805=static void unlink_group(struct config_group *group)
--
fs/configfs/dir.c-812- group->cg_subsys = NULL;
fs/configfs/dir.c:813: unlink_obj(&group->cg_item);
fs/configfs/dir.c-814-}
--
fs/configfs/dir.c=1289=static struct dentry *configfs_mkdir(struct mnt_idmap *idmap, struct inode *dir,
--
fs/configfs/dir.c-1441- else
fs/configfs/dir.c:1442: unlink_obj(item);
fs/configfs/dir.c-1443- client_drop_item(parent_item, item);
--
fs/configfs/dir.c=1469=static int configfs_rmdir(struct inode *dir, struct dentry *dentry)
--
fs/configfs/dir.c-1563- client_disconnect_notify(parent_item, item);
fs/configfs/dir.c:1564: unlink_obj(item);
fs/configfs/dir.c-1565- }
]
|
| 284/4 |
2026/09/02 21:27 |
llm |
codeexpert |
0m
Model:
Tokens:
input: 37260
output: 36
thoughts: 30
Thoughts: Here's my summary, as though I'm reading and thinking about the code:
**Diving into `unlink_obj`**
Okay, let's get down to brass tacks and dissect `unlink_obj`, specifically from lines 760 to 785 in `fs/configfs/dir.c`. I need to understand exactly how the filesystem handles the deletion of objects within a configfs directory. This function is likely the heart of that operation. I expect it to handle the cleanup and reference counting, making sure no resources are leaked when an object is removed. It's probably called when a user space process issues an `unlink()` or `rmdir()` on a configfs node. Knowing how this function works is critical for understanding object lifetime management and the overall stability of the configfs implementation. I'm going to look for how it handles:
* **Error checking:** What happens if the object doesn't exist, or if there are permissions issues?
* **Object de-registration:** How does it remove the object from the parent directory's internal data structures?
* **Resource freeing:** Are resources associated with the object properly freed (e.g., memory, device nodes)?
* **Reference counting:** This is critical; I'm assuming configfs uses reference counting to manage object lifetimes. It must handle decrementing the refcount, and possibly freeing the object if it hits zero.
* **Synchronization:** How does it protect against race conditions, particularly concurrent access from multiple processes? I'll be looking for mutexes or other locking mechanisms.
|
| 285/4 |
2026/09/02 21:27 |
tool |
read-file |
0m
Args: map[File:fs/configfs/dir.c FirstLine:760 LineCount:30]
Results: map[Contents: 760: * All of link_obj/unlink_obj/link_group/unlink_group require that
761: * subsys->su_mutex is held.
762: */
763:
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: }
782:
783: static void link_obj(struct config_item *parent_item, struct config_item *item)
784: {
785: /*
786: * Parent seems redundant with group, but it makes certain
787: * traversals much nicer.
788: */
789: item->ci_parent = parent_item;
]
|
| 286/4 |
2026/09/02 21:27 |
llm |
codeexpert |
0m
Model:
Tokens:
input: 38065
output: 38
thoughts: 2866
Thoughts: **Analyzing the Locking in `configfs_symlink` and `configfs_rmdir`**
Okay, so I'm diving deep into how `configfs` handles symlinks and directory removals, specifically focusing on the locking mechanisms and potential race conditions. I see the core issue: during `configfs_symlink`, we unlock the directory inode (`dir`) to call `get_target`, which traverses paths. This is a potential window for a concurrent `configfs_rmdir` on the "root" object (the user-created group or subsystem) to trigger memory freeing of embedded default groups if they are allocated as part of the root object's data structure.
The core of the problem stems from this: Default groups in `configfs` are frequently embedded within the data structures of the parent group or subsystem, or allocated together. If `configfs_rmdir` runs concurrently, it can unlink and free these structures, invalidating the default groups we may be referencing, even if we've incremented their reference counts.
The prompt poses two key questions. First, let's explore if having `inode_lock(dir)` held by `configfs_symlink` actually prevents the concurrent `configfs_rmdir` from proceeding past its `detach_groups` phase. After careful tracing, I've concluded that it does. When `configfs_symlink` holds `inode_lock(dir)` and a concurrent `configfs_rmdir` is called, the recursive calls within `detach_groups` will eventually attempt to acquire *the same* lock on a default group that is either a direct child or a nested descendant of `dir`. This means that `configfs_rmdir` *blocks* on the lock, preventing it from proceeding to the critical `client_disconnect_notify` and structure freeing steps until the symlink operation releases the lock!
Second, what if `dentry->d_parent` is directly the root object? The VFS lock acquired *before* calling `configfs_rmdir` in this situation ensures that concurrent removals aren't even *attempted* on the root object while we have a symlink operation in progress.
So, the lock on the directory, acquired by `configfs_symlink`, acts as a sufficient, albeit maybe not ideal, protection mechanism!
|
| 287/4 |
2026/09/02 21:27 |
tool |
read-file |
0m
Args: map[File:fs/configfs/dir.c FirstLine:1790 LineCount:100]
Results: map[Contents:1790: /**
1791: * configfs_unregister_group() - unregisters a child group from its parent
1792: * @group: parent group to be unregistered
1793: *
1794: * Undoes configfs_register_group()
1795: */
1796: void configfs_unregister_group(struct config_group *group)
1797: {
1798: struct configfs_subsystem *subsys = group->cg_subsys;
1799: struct dentry *dentry = dget(group->cg_item.ci_dentry);
1800: struct dentry *parent = group->cg_item.ci_parent->ci_dentry;
1801: struct configfs_dirent *sd = dentry->d_fsdata;
1802: struct configfs_fragment *frag = sd->s_frag;
1803:
1804: down_write(&frag->frag_sem);
1805: frag->frag_dead = true;
1806: up_write(&frag->frag_sem);
1807:
1808: inode_lock_nested(d_inode(parent), I_MUTEX_PARENT);
1809: spin_lock(&configfs_dirent_lock);
1810: configfs_detach_prep(sd, NULL);
1811: spin_unlock(&configfs_dirent_lock);
1812:
1813: configfs_detach_group(dentry);
1814: d_inode(dentry)->i_flags |= S_DEAD;
1815: dont_mount(dentry);
1816: d_drop(dentry);
1817: fsnotify_rmdir(d_inode(parent), dentry);
1818: inode_unlock(d_inode(parent));
1819:
1820: dput(dentry);
1821:
1822: mutex_lock(&subsys->su_mutex);
1823: unlink_group(group);
1824: mutex_unlock(&subsys->su_mutex);
1825: }
1826: EXPORT_SYMBOL(configfs_unregister_group);
1827:
1828: /**
1829: * configfs_register_default_group() - allocates and registers a child group
1830: * @parent_group: parent group
1831: * @name: child group name
1832: * @item_type: child item type description
1833: *
1834: * boilerplate to allocate and register a child group with its parent. We need
1835: * kzalloc'ed memory because child's default_group is initially empty.
1836: *
1837: * Return: allocated config group or ERR_PTR() on error
1838: */
1839: struct config_group *
1840: configfs_register_default_group(struct config_group *parent_group,
1841: const char *name,
1842: const struct config_item_type *item_type)
1843: {
1844: int ret;
1845: struct config_group *group;
1846:
1847: group = kzalloc_obj(*group);
1848: if (!group)
1849: return ERR_PTR(-ENOMEM);
1850: config_group_init_type_name(group, name, item_type);
1851:
1852: ret = configfs_register_group(parent_group, group);
1853: if (ret) {
1854: kfree(group);
1855: return ERR_PTR(ret);
1856: }
1857: return group;
1858: }
1859: EXPORT_SYMBOL(configfs_register_default_group);
1860:
1861: /**
1862: * configfs_unregister_default_group() - unregisters and frees a child group
1863: * @group: the group to act on
1864: */
1865: void configfs_unregister_default_group(struct config_group *group)
1866: {
1867: configfs_unregister_group(group);
1868: kfree(group);
1869: }
1870: EXPORT_SYMBOL(configfs_unregister_default_group);
1871:
1872: int configfs_register_subsystem(struct configfs_subsystem *subsys)
1873: {
1874: int err;
1875: struct config_group *group = &subsys->su_group;
1876: struct dentry *dentry;
1877: struct dentry *root;
1878: struct configfs_dirent *sd;
1879: struct configfs_fragment *frag;
1880:
1881: frag = new_fragment();
1882: if (!frag)
1883: return -ENOMEM;
1884:
1885: root = configfs_pin_fs();
1886: if (IS_ERR(root)) {
1887: put_fragment(frag);
1888: return PTR_ERR(root);
1889: }
]
|
| 288/4 |
2026/09/02 21:27 |
llm |
codeexpert |
0m
Model:
Tokens:
input: 42937
output: 37
thoughts: 13
Thoughts: Here's a summary of my thoughts, as though I'm processing `configfs_unregister_subsystem`:
**Diving into `configfs_unregister_subsystem`**
Okay, let's pull apart `configfs_unregister_subsystem`. Right, so this is the function responsible forβ¦ the opposite of registration, obviously. It's the unregistration mechanism for a configfs subsystem. My initial thought is, what's the graceful teardown procedure? What cleanup is *essential* here? I'm thinking about resource management β memory allocated, files created, any kernel objects. This is critical to prevent leaks. Is it taking any locks? That's the first thing I'd check - how it's handled when the subsystem is actually unregistered.
I immediately want to understand the dependencies. What other functions are likely calling this? Is it called directly, or via some intermediate function pointer or event trigger? Understanding the call paths will give me context for any potential race conditions and the broader impact.
I'm also curious about the potential failure modes. What might cause this unregistration to fail? Are there error codes returned, and how are those handled? Are there any specific conditions, like, maybe, if the subsystem is currently in use, or perhaps has pending operations? The code should have checks for this. Failure has got to be a crucial consideration in such a pivotal operation.
I'd want to then analyze the actual data structures being cleaned up, and what is *being* unregistered in practice. Are there any callbacks involved? Are resources released in a specific order? Is there any graceful "shutdown" behavior that is handled here? And of course, the actual sequence of operations that take place to make the filesystem work correctly, on unregistration. I'm going to want to make sure the implementation is safe, and memory-safe too.
Let's see what's in there... I bet there is more than meets the eye!
|
| 289/4 |
2026/09/02 21:27 |
tool |
read-file |
0m
Args: map[File:fs/configfs/dir.c FirstLine:1920 LineCount:60]
Results: map[Contents:1920:
1921: inode_unlock(d_inode(root));
1922:
1923: if (err) {
1924: mutex_lock(&configfs_subsystem_mutex);
1925: unlink_group(group);
1926: mutex_unlock(&configfs_subsystem_mutex);
1927: configfs_release_fs();
1928: }
1929: put_fragment(frag);
1930:
1931: return err;
1932: }
1933:
1934: void configfs_unregister_subsystem(struct configfs_subsystem *subsys)
1935: {
1936: struct config_group *group = &subsys->su_group;
1937: struct dentry *dentry = dget(group->cg_item.ci_dentry);
1938: struct dentry *root = dentry->d_sb->s_root;
1939: struct configfs_dirent *sd = dentry->d_fsdata;
1940: struct configfs_fragment *frag = sd->s_frag;
1941:
1942: if (dentry->d_parent != root) {
1943: pr_err("Tried to unregister non-subsystem!\n");
1944: return;
1945: }
1946:
1947: down_write(&frag->frag_sem);
1948: frag->frag_dead = true;
1949: up_write(&frag->frag_sem);
1950:
1951: inode_lock_nested(d_inode(root),
1952: I_MUTEX_PARENT);
1953: inode_lock_nested(d_inode(dentry), I_MUTEX_CHILD);
1954: mutex_lock(&configfs_symlink_mutex);
1955: spin_lock(&configfs_dirent_lock);
1956: if (configfs_detach_prep(sd, NULL)) {
1957: pr_err("Tried to unregister non-empty subsystem!\n");
1958: }
1959: spin_unlock(&configfs_dirent_lock);
1960: mutex_unlock(&configfs_symlink_mutex);
1961: configfs_detach_group(dentry);
1962: d_inode(dentry)->i_flags |= S_DEAD;
1963: dont_mount(dentry);
1964: inode_unlock(d_inode(dentry));
1965:
1966: d_drop(dentry);
1967: fsnotify_rmdir(d_inode(root), dentry);
1968:
1969: inode_unlock(d_inode(root));
1970:
1971: dput(dentry);
1972:
1973: mutex_lock(&configfs_subsystem_mutex);
1974: unlink_group(group);
1975: mutex_unlock(&configfs_subsystem_mutex);
1976: configfs_release_fs();
1977: }
1978:
1979: EXPORT_SYMBOL(configfs_register_subsystem);
]
|
| 290/4 |
2026/09/02 21:27 |
llm |
codeexpert |
0m
Model:
Tokens:
input: 44303
output: 25
thoughts: 522
Thoughts: **Analyzing the Locking Order in ConfigFS for Safe Root Object Access**
Okay, so I'm thinking about this issue in ConfigFS. I've got `configfs_rmdir`, `configfs_unregister_group`, and `configfs_unregister_subsystem`, and they all seem to call `configfs_detach_group`, which then uses `detach_groups`. The interesting bit is that `detach_groups` iterates through every default group in the hierarchy and calls `inode_lock` on each child's inode. That's key. It means that if the directory I'm trying to remove (`dir` in this case) *is* a default group (or anywhere under one in the hierarchy), `detach_groups` *must* acquire `inode_lock(dir)` before it can detach that group or anything above it. And that lock must be held before calling `unlink_group` or `client_drop_item`!
This basically blocks `configfs_rmdir` at `detach_groups` as long as `configfs_symlink` is holding `inode_lock(dir)`. That makes sense.
Now, the second part of the question: *can* I safely walk up the `d_parent` chain to find the root object and get a reference to it before I drop `inode_unlock(dir)`? Let's break this down systematically.
Let's look at the dcache. The `dentry` is the symlink being created. Its `d_parent` is the directory the symlink goes into, let's call it `P_0`. The `d_parent` of `P_0` is `P_1`, the `d_parent` of `P_1` is `P_2`... this goes up the chain to `P_k`, which is either the root of the configfs mount or some other configured root object.
The question is, how does this dcache / dentry hierarchy behave when I'm locking `P_0`? Will it allow safe walking up the parent chain to find the root object? Is that chain stable? Could dentries in the ancestor chain be moved, renamed, or unlinked while I'm doing this?
And in the ConfigFS context: Does configfs even *allow* renames or moves (like with `vfs_rename`) on its directories? I'll need to check if there are any `rename` calls in `fs/configfs`. That's going to be the deciding factor. If it doesn't allow renaming or cross-directory moves, the chain is stable and I can likely get a reference to the root object safely.
|
| 291/4 |
2026/09/02 21:27 |
tool |
grepper |
0m
Error:
Args: map[Expression:rename PathPrefix:fs/configfs]
Results:
|
| 292/4 |
2026/09/02 21:27 |
llm |
codeexpert |
0m
Model:
Tokens:
input: 45433
output: 27
thoughts: 43
Thoughts: Okay, here's my take on those thoughts, framed as my own reasoning:
**Configfs and the Elusive Rename Operation**
Alright, so the immediate assumption is that Configfs doesn't support the `.rename` inode operation. That's a key function for moving or renaming directories and items, right? It's pretty fundamental, and if it's missing, it's a huge limitation. But hold on, let's not jump to conclusions. I need to be absolutely sure about this. Memory can play tricks, and my gut feeling isn't always reliable, especially when it comes to the nuances of a filesystem implementation like Configfs. Before I fully integrate this assumption into my mental model, I need to *verify* if Configfs actually includes `.rename` in its inode operations. Let's dig in and make sure. It could be that my knowledge is out-of-date, or that my understanding of Configfs's limitations is incomplete. Time to check the documentation or source code to confirm the behavior.
|
| 293/4 |
2026/09/02 21:27 |
tool |
grepper |
0m
Args: map[Expression:inode_operations PathPrefix:fs/configfs]
Results: map[Output:fs/configfs/configfs_internal.h=87=extern const struct file_operations configfs_bin_file_operations;
fs/configfs/configfs_internal.h:88:extern const struct inode_operations configfs_dir_inode_operations;
fs/configfs/configfs_internal.h:89:extern const struct inode_operations configfs_root_inode_operations;
fs/configfs/configfs_internal.h:90:extern const struct inode_operations configfs_symlink_inode_operations;
fs/configfs/configfs_internal.h-91-extern const struct dentry_operations configfs_dentry_ops;
--
fs/configfs/dir.c=293=static int configfs_create_dir(struct config_item *item, struct dentry *dentry,
--
fs/configfs/dir.c-314-
fs/configfs/dir.c:315: inode->i_op = &configfs_dir_inode_operations;
fs/configfs/dir.c-316- inode->i_fop = &configfs_dir_operations;
--
fs/configfs/dir.c=369=int configfs_create_link(struct configfs_dirent *target, struct dentry *parent,
--
fs/configfs/dir.c-387- inode->i_link = body;
fs/configfs/dir.c:388: inode->i_op = &configfs_symlink_inode_operations;
fs/configfs/dir.c-389- d_make_persistent(dentry, inode);
--
fs/configfs/dir.c=1469=static int configfs_rmdir(struct inode *dir, struct dentry *dentry)
--
fs/configfs/dir.c-1578-
fs/configfs/dir.c:1579:const struct inode_operations configfs_dir_inode_operations = {
fs/configfs/dir.c-1580- .mkdir = configfs_mkdir,
--
fs/configfs/dir.c-1587-
fs/configfs/dir.c:1588:const struct inode_operations configfs_root_inode_operations = {
fs/configfs/dir.c-1589- .lookup = configfs_lookup,
--
fs/configfs/inode.c=28=static struct lock_class_key default_group_class[MAX_LOCK_DEPTH];
--
fs/configfs/inode.c-30-
fs/configfs/inode.c:31:static const struct inode_operations configfs_inode_operations ={
fs/configfs/inode.c-32- .setattr = configfs_setattr,
--
fs/configfs/inode.c=104=struct inode *configfs_new_inode(umode_t mode, struct configfs_dirent *sd,
--
fs/configfs/inode.c-110- inode->i_mapping->a_ops = &ram_aops;
fs/configfs/inode.c:111: inode->i_op = &configfs_inode_operations;
fs/configfs/inode.c-112-
--
fs/configfs/mount.c=63=static int configfs_fill_super(struct super_block *sb, struct fs_context *fc)
--
fs/configfs/mount.c-76- if (inode) {
fs/configfs/mount.c:77: inode->i_op = &configfs_root_inode_operations;
fs/configfs/mount.c-78- inode->i_fop = &configfs_dir_operations;
--
fs/configfs/symlink.c=213=int configfs_unlink(struct inode *dir, struct dentry *dentry)
--
fs/configfs/symlink.c-257-
fs/configfs/symlink.c:258:const struct inode_operations configfs_symlink_inode_operations = {
fs/configfs/symlink.c-259- .get_link = simple_get_link,
]
|
| 294/4 |
2026/09/02 21:27 |
llm |
codeexpert |
0m
Model:
Tokens:
input: 46751
output: 41
thoughts: 295
Thoughts: **My Investigation into Configfs Dentry Management**
Okay, so I'm diving into the nitty-gritty of configfs, specifically the implications of *not* having rename support. I know from experience how these things can cascade. First things first: no rename. Got it. Now, let's look at the dentry reference counting and lifetimes within this context.
In VFS, here's how it shakes out: Any dentry that's *within* configfs β meaning it has a parent in that filesystem β behaves like this: it maintains a reference (`d_count`) on its parent. So, my dentry has a reference to its `d_parent`. That parent, in turn, has a reference to *its* parent, and this chain continues all the way up to the filesystem's superblock root (`sb->s_root`). The crucial thing here is that the `d_parent` pointers are, in effect, immutable in configfs because we can't rename, and as long as a child dentry exists (reference count > 0), all its ancestors are pinned in memory. This "pinning" means they can't be freed or reclaimed by the system, which is a key consideration for overall memory management. Furthermore, the persistent nature of these dentries within configfs (`d_make_persistent`) and fixed parent-child relations solidify this structure.
Now, what about the `config_item` that's associated with each of these dentries? I need to understand how *that* gets managed. Okay, I'm going to look at `configfs_get_config_item(struct dentry *dentry)`. Let's get the definition and see what happens.
|
| 295/4 |
2026/09/02 21:27 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/configfs/symlink.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]
|
| 296/4 |
2026/09/02 21:27 |
llm |
codeexpert |
0m
Model:
Tokens:
input: 47677
output: 38
thoughts: 181
Thoughts: **Examining the ConfigFS Item Retrieval**
Okay, let's break this down. I'm looking at `configfs_get_config_item(struct dentry *dentry)`. The goal here is clearly to retrieve the `config_item` associated with a given `dentry`. Seems straightforward enough:
```c
static inline struct config_item *configfs_get_config_item(struct dentry *dentry)
{
struct config_item * item = NULL;
spin_lock(&dentry->d_lock);
if (!d_unhashed(dentry)) {
struct configfs_dirent * sd = dentry->d_fsdata;
item = config_item_get(sd->s_element);
}
spin_unlock(&dentry->d_lock);
return item;
}
```
Alright, the core logic is inside the `if` statement, which checks if the `dentry` is hashed. This suggests that the configuration item is only accessible when the directory entry is part of the file system's structure. If it is, the code gets the `configfs_dirent` from the `dentry`'s `d_fsdata` field and then retrieves the `config_item` using `config_item_get`, which is, I'd bet, some form of reference counting or locking mechanism. The whole thing is wrapped in a spinlock on `dentry->d_lock`, which is expected.
Now, wait a minute... let's not make assumptions. I need to understand what `config_item_get` actually *does*. I'll need to check the definition of `config_item_get` to ensure this is as safe as it looks. Let's see how that's defined.
|
| 297/4 |
2026/09/02 21:27 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/configfs/symlink.c Name:config_item_get]
Results: map[SourceCode: 101: struct config_item *config_item_get(struct config_item *item)
102: {
103: if (item)
104: kref_get(&item->ci_kref);
105: return item;
106: }
SourceFile:fs/configfs/item.c]
|
| 298/4 |
2026/09/02 21:27 |
llm |
codeexpert |
0m
Model:
Tokens:
input: 48402
output: 31
thoughts: 1150
Thoughts: **Analyzing the Safety of `config_item_get` in ConfigFS Under Concurrency**
Okay, let's break this down. My initial concern revolves around the safety of calling `config_item_get` when a config item might have reached a refcount of zero. We know `kref_get` is used internally and that it will WARN or crash if the refcount is zero.
The core question is: how can the refcount of an item reach zero, especially while `inode_lock(dir)` is held? Let's trace it.
We need to understand when and how the root item's refcount can drop to zero. It can only occur after `client_drop_item(parent_item, item)` and/or `unlink_group`/`unlink_obj` are executed within `configfs_rmdir`.
But here's the crucial insight: While `inode_lock(dir)` is held, `configfs_rmdir` cannot possibly reach `unlink_group` or `client_drop_item`. Why? Because `configfs_rmdir` must run `configfs_detach_group(root_dentry)`, which in turn calls `detach_groups(root_dentry)`. `detach_groups()` iterates through each default group in the subtree and attempts to acquire `inode_lock(d_inode(child))`. However, because `configfs_symlink` holds `inode_lock(dir)`, `detach_groups` is blocked!
So, as long as `inode_lock(dir)` is held:
* `detach_groups` cannot complete.
* `configfs_detach_group` cannot complete.
* `unlink_group` cannot be called.
* `client_drop_item` cannot be called.
* No `config_item_put` on the root item or any default group in the hierarchy can be called by `rmdir`.
* The root item (and all default groups) remain alive with their initial references, and their refcounts are guaranteed to be greater than zero.
* The dentries along the `d_parent` chain up to the root cannot be unhashed or deleted (because `d_delete` is called only after `inode_unlock` in `detach_groups` or `configfs_rmdir`).
Let's confirm this step-by-step:
We see that `d_delete(child)` is called *after* `inode_unlock(d_inode(child))`. `configfs_detach_group(child)` is called while holding `inode_lock(d_inode(child))`. This means, with `configfs_symlink` holding `inode_lock(dir)`:
* `d_delete(dentry->d_parent)` has not been called.
* `d_delete` on any ancestor of `dentry->d_parent` has not been called.
* No dentries along the `d_parent` chain up to the root have been unhashed or deleted by `rmdir`.
* No items along the `d_parent` chain have been unlinked.
* No item references have been dropped by `client_drop_item`.
Okay, what about walking up the `d_parent` chain? Starting from `dentry->d_parent`, each `dentry->d_parent` is a valid pointer in the dentry hierarchy until we reach the root object. Since `dentry` holds a refcount on its parent, and each parent holds a refcount on its own parent, the entire dentry chain is pinned in memory by dcache refcounts. Also, dentries in configfs cannot be renamed. This makes walking `dentry->d_parent` to the root completely stable.
Now, how do we identify the root object along this chain? Let's check the code for how ConfigFS distinguishes default groups from user-created groups/subsystems. We'll search for `CONFIGFS_USET_DEFAULT` and `configfs_is_root` in the code to get that.
|
| 299/4 |
2026/09/02 21:27 |
tool |
grepper |
0m
Args: map[Expression:CONFIGFS_USET_DEFAULT PathPrefix:fs/configfs]
Results: map[Output:fs/configfs/configfs_internal.h=30=struct configfs_dirent {
--
fs/configfs/configfs_internal.h-52-#define CONFIGFS_USET_DIR 0x0040
fs/configfs/configfs_internal.h:53:#define CONFIGFS_USET_DEFAULT 0x0080
fs/configfs/configfs_internal.h-54-#define CONFIGFS_USET_DROPPING 0x0100
--
fs/configfs/dir.c=502=static int configfs_detach_prep(struct configfs_dirent *parent_sd, struct dentry **wait)
--
fs/configfs/dir.c-518- continue;
fs/configfs/dir.c:519: if (sd->s_type & CONFIGFS_USET_DEFAULT) {
fs/configfs/dir.c-520- /* Abort if racing with mkdir() */
--
fs/configfs/dir.c=548=static void configfs_detach_rollback(struct configfs_dirent *parent_sd)
--
fs/configfs/dir.c-554- list_for_each_entry(sd, &parent_sd->s_children, s_sibling)
fs/configfs/dir.c:555: if (sd->s_type & CONFIGFS_USET_DEFAULT)
fs/configfs/dir.c-556- configfs_detach_rollback(sd);
--
fs/configfs/dir.c=664=static void detach_groups(struct dentry *dentry)
--
fs/configfs/dir.c-673- next = next_dirent(parent_sd, sd);
fs/configfs/dir.c:674: if (!(sd->s_type & CONFIGFS_USET_DEFAULT))
fs/configfs/dir.c-675- continue;
--
fs/configfs/dir.c=703=static int create_default_group(struct dentry *parent,
--
fs/configfs/dir.c-722- sd = child->d_fsdata;
fs/configfs/dir.c:723: sd->s_type |= CONFIGFS_USET_DEFAULT;
fs/configfs/dir.c-724- } else {
--
fs/configfs/dir.c=977=static void configfs_dump_one(struct configfs_dirent *sd, int level)
--
fs/configfs/dir.c-986- type_print(CONFIGFS_USET_DIR);
fs/configfs/dir.c:987: type_print(CONFIGFS_USET_DEFAULT);
fs/configfs/dir.c-988- type_print(CONFIGFS_USET_DROPPING);
--
fs/configfs/dir.c=1469=static int configfs_rmdir(struct inode *dir, struct dentry *dentry)
--
fs/configfs/dir.c-1479- sd = dentry->d_fsdata;
fs/configfs/dir.c:1480: if (sd->s_type & CONFIGFS_USET_DEFAULT)
fs/configfs/dir.c-1481- return -EPERM;
]
|
| 300/4 |
2026/09/02 21:27 |
llm |
codeexpert |
0m
Model:
Tokens:
input: 51294
output: 27
thoughts: 188
Thoughts: **My Analysis of the `configfs_rmdir` Function**
Okay, let's break this down. In the `configfs_rmdir` function, the code snippet focuses on preventing the removal of default configuration groups. The logic hinges on checking the `dentry->d_fsdata` which I'm assuming holds our configfs-specific data. I can see that `sd->s_type & CONFIGFS_USET_DEFAULT` is the key. If this bit is set, the function returns `-EPERM`, which means "permission denied".
So, the crux of the matter is identifying these "default" groups. From the code, it seems a default group will have this bit set, whereas groups created via `mkdir` or those representing a registered subsystem or group, will *not* have that bit set. Excellent.
But, I'm thinking, is that the only way to identify a default group? Could we navigate the configfs hierarchy to determine if we're dealing with a default or user-created item? My instinct is to explore the parent relationships. Maybe we can check using `configfs_is_root(item)` to see if it is the root of the file system. And also, it seems like we *might* be able to traverse upwards through `ci_parent` (the configfs item's parent, if any) or the dentry's parent `d_parent`.
I need to confirm if these parent pointers are reliable for distinguishing default groups. The relationship between `ci_parent` and `d_parent` needs clarification. Let me search through the `fs/configfs` directory. I'll need to figure out what parent pointer to look for! Let's see what the code shows...
|
| 301/4 |
2026/09/02 21:28 |
tool |
grepper |
0m
Args: map[Expression:ci_parent PathPrefix:fs/configfs]
Results: map[Output:fs/configfs/dir.c=764=static void unlink_obj(struct config_item *item)
--
fs/configfs/dir.c-772- item->ci_group = NULL;
fs/configfs/dir.c:773: item->ci_parent = NULL;
fs/configfs/dir.c-774-
--
fs/configfs/dir.c-777-
fs/configfs/dir.c:778: /* Drop the reference for ci_parent */
fs/configfs/dir.c-779- config_group_put(group);
--
fs/configfs/dir.c=783=static void link_obj(struct config_item *parent_item, struct config_item *item)
--
fs/configfs/dir.c-788- */
fs/configfs/dir.c:789: item->ci_parent = parent_item;
fs/configfs/dir.c-790-
fs/configfs/dir.c-791- /*
fs/configfs/dir.c:792: * We hold a reference on the parent for the child's ci_parent
fs/configfs/dir.c-793- * link.
--
fs/configfs/dir.c=1796=void configfs_unregister_group(struct config_group *group)
--
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/item.c=117=static void config_item_cleanup(struct config_item *item)
--
fs/configfs/item.c-120- struct config_group *s = item->ci_group;
fs/configfs/item.c:121: struct config_item *parent = item->ci_parent;
fs/configfs/item.c-122-
--
fs/configfs/symlink.c=22=static int item_depth(struct config_item * item)
--
fs/configfs/symlink.c-25- int depth = 0;
fs/configfs/symlink.c:26: do { depth++; } while ((p = p->ci_parent) && !configfs_is_root(p));
fs/configfs/symlink.c-27- return depth;
--
fs/configfs/symlink.c=30=static int item_path_length(struct config_item * item)
--
fs/configfs/symlink.c-35- length += strlen(config_item_name(p)) + 1;
fs/configfs/symlink.c:36: p = p->ci_parent;
fs/configfs/symlink.c-37- } while (p && !configfs_is_root(p));
--
fs/configfs/symlink.c=41=static void fill_item_path(struct config_item * item, char * buffer, int length)
--
fs/configfs/symlink.c-45- --length;
fs/configfs/symlink.c:46: for (p = item; p && !configfs_is_root(p); p = p->ci_parent) {
fs/configfs/symlink.c-47- int cur = strlen(config_item_name(p));
]
|
| 302/4 |
2026/09/02 21:28 |
llm |
codeexpert |
0m
Model:
Tokens:
input: 52803
output: 789
thoughts: 4164
Thoughts: **My Analysis of `configfs_symlink` Concurrency and Root Object Safety**
Okay, so I've been diving deep into how `configfs_symlink` interacts with concurrent `rmdir` operations and how we can ensure the root object isn't prematurely freed. Let me summarize my findings, specifically focusing on the critical locking and reference count management.
Initially, I wanted to understand what happens to the root object when we call `unlink_obj`. It turns out that `item->ci_parent = NULL` is set during `unlink_group`/`client_drop_item`, and this happens *after* `configfs_detach_group`. The crucial point is that `configfs_detach_group` is blocked by `inode_lock(dir)` and that the dentry tree (specifically `dentry->d_parent`) is immutable in this context.
My major concern was about refcount dropping and potential use-after-free scenarios.
1. **Refcount Management and Object Lifetime**: The core insight is that while `configfs_rmdir` or other unregister processes attempt to tear down the configfs hierarchy, if we hold `inode_lock(dir)`, it's safe to take a reference to the root object using `config_item_get` (or `configfs_get_config_item(root_dentry)`). This is because `detach_groups()` is blocked on acquiring `inode_lock(dir)`. While that lock is held, the root object's refcount will *not* be dropped by the `rmdir` process. Even if we drop `inode_unlock(dir)`, the root object's refcount will stay above zero and its memory will not be freed due to the extra reference.
2. **Concurrency Block and Locking**: The `inode_lock(dir)` call within `configfs_symlink` is critical. It *blocks* any concurrent `configfs_rmdir`, `configfs_unregister_group`, or `configfs_unregister_subsystem` from proceeding past `detach_groups()`. I verified this by tracing the execution path of `configfs_rmdir`: it's designed to recursively traverse and detach default groups. However, each recursive call to `detach_groups()` has to acquire an `inode_lock` on each directory (default groups) in the hierarchy and, because `configfs_symlink` is holding the lock, that lock is blocked and prevents further `rmdir` execution. This ensures that the root object and the entire configfs hierarchy remains valid until `inode_unlock` in the `configfs_symlink` path.
3. **Safe Reference Acquisition**: The fact that the locking mechanism blocks tear-down enables me to safely walk up the `d_parent` chain, find the root object, and safely take a reference using `config_item_get()` *before* releasing the `inode_lock`. I've confirmed that dentry stability (immutable `d_parent` links) guarantees the integrity of the object tree and their memory allocations. The root object's backing memory canβt be freed while we have a valid reference, and that refcount is safe, because `detach_groups()` is still blocked!
4. **Edge Cases**:
* **Root object directly created**: When `dir` is the root object itself (no intermediate default groups), the same principle applies, but the `parent_item` is the root object itself. However, even if no default groups exist, a race condition is still mitigated by `inode_lock`.
* **Nested groups**: The important consideration here is the fact that default groups, which are statically embedded within the root object, would be freed if we dropped the root object's refcount *before* a separate reference had been obtained.
The critical takeaway: `inode_lock(dir)` during `configfs_symlink` *prevents* the unlinking process from completing, thereby making it safe to take a reference to the root object. When the root object's refcount is > 0, the memory will not be freed. Thus, the allocation can't be freed while it still has a valid reference. This logic guarantees the creation of our symlink to the root object.
|
| 303/4 |
2026/09/02 21:28 |
tool |
set-results |
0m
Args: map[Answer:Yes to both questions.
### 1. Does `inode_lock(dir)` prevent a concurrent `configfs_rmdir` on the root object from proceeding past `detach_groups()`?
**Yes.**
When `configfs_rmdir()` runs on a root object (or when `configfs_unregister_group()` / `configfs_unregister_subsystem()` runs):
1. It prepares detachment by marking default groups with `CONFIGFS_USET_DROPPING` in `configfs_detach_prep()`.
2. It then calls `configfs_detach_group(dentry)`, which directly invokes `detach_groups(dentry)`.
3. `detach_groups()` iterates recursively over all child default groups (`CONFIGFS_USET_DEFAULT`) in the hierarchy and explicitly executes:
```c
inode_lock(d_inode(child));
configfs_detach_group(child);
...
inode_unlock(d_inode(child));
```
4. If `dir` is the inode of `dentry->d_parent` (a default group anywhere in the hierarchy under the root object), `detach_groups()` will attempt to acquire `inode_lock(d_inode(child))` on `dir` and will **block** as long as `configfs_symlink()` holds `inode_lock(dir)`. (If `dir` is the root object itself, VFS `vfs_rmdir()` blocks on `inode_lock(dir)` before `configfs_rmdir()` can even begin).
5. Only **after** `detach_groups()` finishes can `configfs_rmdir()` proceed to `unlink_group()`, `client_disconnect_notify()`, `client_drop_item()`, and `config_item_put()`.
Therefore, holding `inode_lock(dir)` strictly blocks any concurrent `configfs_rmdir` on an ancestor root object from proceeding past `detach_groups()`.
---
### 2. Is it guaranteed that we can safely walk up the `d_parent` chain to find the root object and get a reference to it before dropping `inode_unlock(dir)`?
**Yes.**
- **Dentry Stability:** In the VFS dcache, each dentry pins its parent (`d_count` refcount), so all ancestors from `dentry->d_parent` up to the root are pinned in memory. Configfs does not support rename/move operations, making the `d_parent` chain immutable. Furthermore, dentries are only unhashed (`d_delete()`) in `detach_groups()` after releasing the child's inode lock.
- **Reference Count & Memory Validity:** Because `configfs_rmdir` is blocked prior to `unlink_group()` and `client_drop_item()`, the root object and all intermediate default groups have not been unlinked, and their initial references remain active (refcount $\ge 1$).
- **Safe Root Pinning:** Walking up `d_parent` until reaching the root non-default group / subsystem (identifiable by checking that `sd->s_type & CONFIGFS_USET_DEFAULT` is false) and obtaining a reference with `configfs_get_config_item()` / `config_item_get()` is completely safe. Pinning the root object ensures that even after `inode_unlock(dir)` is dropped and a concurrent `rmdir` proceeds, the underlying memory structure hosting the embedded `parent_item` cannot be freed until `configfs_symlink()` releases its reference on the root object.]
Results: map[Answer:Yes to both questions.
### 1. Does `inode_lock(dir)` prevent a concurrent `configfs_rmdir` on the root object from proceeding past `detach_groups()`?
**Yes.**
When `configfs_rmdir()` runs on a root object (or when `configfs_unregister_group()` / `configfs_unregister_subsystem()` runs):
1. It prepares detachment by marking default groups with `CONFIGFS_USET_DROPPING` in `configfs_detach_prep()`.
2. It then calls `configfs_detach_group(dentry)`, which directly invokes `detach_groups(dentry)`.
3. `detach_groups()` iterates recursively over all child default groups (`CONFIGFS_USET_DEFAULT`) in the hierarchy and explicitly executes:
```c
inode_lock(d_inode(child));
configfs_detach_group(child);
...
inode_unlock(d_inode(child));
```
4. If `dir` is the inode of `dentry->d_parent` (a default group anywhere in the hierarchy under the root object), `detach_groups()` will attempt to acquire `inode_lock(d_inode(child))` on `dir` and will **block** as long as `configfs_symlink()` holds `inode_lock(dir)`. (If `dir` is the root object itself, VFS `vfs_rmdir()` blocks on `inode_lock(dir)` before `configfs_rmdir()` can even begin).
5. Only **after** `detach_groups()` finishes can `configfs_rmdir()` proceed to `unlink_group()`, `client_disconnect_notify()`, `client_drop_item()`, and `config_item_put()`.
Therefore, holding `inode_lock(dir)` strictly blocks any concurrent `configfs_rmdir` on an ancestor root object from proceeding past `detach_groups()`.
---
### 2. Is it guaranteed that we can safely walk up the `d_parent` chain to find the root object and get a reference to it before dropping `inode_unlock(dir)`?
**Yes.**
- **Dentry Stability:** In the VFS dcache, each dentry pins its parent (`d_count` refcount), so all ancestors from `dentry->d_parent` up to the root are pinned in memory. Configfs does not support rename/move operations, making the `d_parent` chain immutable. Furthermore, dentries are only unhashed (`d_delete()`) in `detach_groups()` after releasing the child's inode lock.
- **Reference Count & Memory Validity:** Because `configfs_rmdir` is blocked prior to `unlink_group()` and `client_drop_item()`, the root object and all intermediate default groups have not been unlinked, and their initial references remain active (refcount $\ge 1$).
- **Safe Root Pinning:** Walking up `d_parent` until reaching the root non-default group / subsystem (identifiable by checking that `sd->s_type & CONFIGFS_USET_DEFAULT` is false) and obtaining a reference with `configfs_get_config_item()` / `config_item_get()` is completely safe. Pinning the root object ensures that even after `inode_unlock(dir)` is dropped and a concurrent `rmdir` proceeds, the underlying memory structure hosting the embedded `parent_item` cannot be freed until `configfs_symlink()` releases its reference on the root object.]
|
| 304/2 |
2026/09/02 21:28 |
llm |
debugger |
0m
Model:
Tokens:
input: 44238
output: 35
thoughts: 898
Thoughts: **Analysis and Implementation of a ConfigFS Symlink Fix**
Okay, this is excellent! The code expert nailed it; their analysis perfectly aligns with my understanding. This is *exactly* the fix we need. I'm going to write it down clearly.
The primary change involves ensuring that our root item retrieval mechanism is safe to integrate into `fs/configfs/symlink.c`. I need to ensure this is sound. I'm going to pull in `configfs_get_root_item` into `fs/configfs/symlink.c`.
It looks like `configfs_get_root_item` relies solely on `struct configfs_dirent`, and thankfully, that's defined in `fs/configfs/configfs_internal.h`. The critical thing is to verify that `fs/configfs/symlink.c` *does* indeed include `"configfs_internal.h"`, which it does. With that in mind, I'm confident we can proceed with this change.
|
| 305/2 |
2026/09/02 21:28 |
tool |
read-file |
0m
Args: map[File:fs/configfs/symlink.c FirstLine:1 LineCount:30]
Results: map[Contents: 1: // SPDX-License-Identifier: GPL-2.0-or-later
2: /*
3: * symlink.c - operations for configfs symlinks.
4: *
5: * Based on sysfs:
6: * sysfs is Copyright (C) 2001, 2002, 2003 Patrick Mochel
7: *
8: * configfs Copyright (C) 2005 Oracle. All rights reserved.
9: */
10:
11: #include <linux/fs.h>
12: #include <linux/module.h>
13: #include <linux/namei.h>
14: #include <linux/slab.h>
15:
16: #include <linux/configfs.h>
17: #include "configfs_internal.h"
18:
19: /* Protects attachments of new symlinks */
20: DEFINE_MUTEX(configfs_symlink_mutex);
21:
22: static int item_depth(struct config_item * item)
23: {
24: struct config_item * p = item;
25: int depth = 0;
26: do { depth++; } while ((p = p->ci_parent) && !configfs_is_root(p));
27: return depth;
28: }
29:
30: static int item_path_length(struct config_item * item)
]
|
| 306/2 |
2026/09/02 21:28 |
llm |
debugger |
0m
Model:
Tokens:
input: 45813
output: 2065
thoughts: 2106
Thoughts: **Analysis and Proposed Fix for a ConfigFS Race Condition**
Okay, let's break this down. We're looking at a potential use-after-free vulnerability in `configfs_symlink` due to a race condition with `configfs_rmdir`. The core of the problem stems from accessing a potentially freed `parent_item` after releasing and re-acquiring the inode lock (`inode_lock(dir)`).
First, the bug. We know the crash involves accessing memory at an offset of 1224 bytes from a freed region. Let's verify that. We're dealing with `nvmet_port`, which contains a `subsys_group`, which in turn contains a `config_item` called `cg_item`. The problematic pointer is `parent->ci_parent`, which, in this case, is the `&port->subsys_group.cg_item`. Through a bit of offset calculation based on the sizes of these structures, and considering that the buggy address is located 1224 bytes inside of a freed 4096-byte region, the math checks out perfectly. The UAF is happening because `configfs_rmdir` detaches the default groups from the configfs root, unlinks it and frees the memory. Subsequently, `configfs_symlink` later re-acquires the lock, and attempts to use the `parent_item`, which has been freed.
The race occurs because `configfs_symlink` drops the lock to call `get_target`, which could take a while. During this time, `configfs_rmdir` could be running, detach the default groups, and free the root object. When `configfs_symlink` re-acquires the lock, the memory pointed to by `parent_item` is now invalid. We also see that we CANNOT simply hold a reference to `parent_item` because the `sd->s_element` becomes a dangling pointer after the item is detached.
The key to the fix is pinning the *root* object. We need a way to ensure the memory containing the parent item doesn't get freed during the lock-release window. We need to find the root object within the configfs hierarchy and get a reference to *it*. That root object will prevent the free and ensure the validity of `parent_item` during the critical section.
Here's my proposed fix, which is based on finding the root object. It has to be more robust than what we have now:
First, here's the helper function to walk up the directory tree and find that root object:
```c
static struct config_item *configfs_get_root_item(struct dentry *dentry)
{
struct dentry *root_dentry = dentry;
struct configfs_dirent *sd;
while (root_dentry) {
sd = root_dentry->d_fsdata;
if (!(sd->s_type & CONFIGFS_USET_DEFAULT))
break;
root_dentry = root_dentry->d_parent;
}
return configfs_get_config_item(root_dentry);
}
```
Then, here's the modified `configfs_symlink` to incorporate the root object pinning and `frag_dead` check:
```c
struct config_item *root_item = NULL;
...
/*
* Pin the root object so that the parent_item (which may be an
* embedded default group) is not freed while we drop the lock.
*/
root_item = configfs_get_root_item(dentry->d_parent);
/*
* Drop the dir inode lock. The target path might be outside
* configfs, and we don't want to deadlock.
*/
inode_unlock(dir);
ret = get_target(symname, &target_item, dentry->d_sb);
inode_lock(dir);
if (ret)
goto out_put_root;
if (dentry->d_inode || d_unhashed(dentry))
ret = -EEXIST;
else
ret = inode_permission(&nop_mnt_idmap, dir,
MAY_WRITE | MAY_EXEC);
if (!ret) {
struct configfs_dirent *sd = dentry->d_parent->d_fsdata;
if (sd->s_frag->frag_dead) {
ret = -ENOENT;
} else {
ret = type->ct_item_ops->allow
|
| 307/1 |
2026/09/02 21:29 |
agent |
history-explorer |
4m
Model:
Instruction:
You are an experienced Linux kernel developer researching prior art for fixing a kernel bug.
You are given a bug explanation. This explanation details the root cause of the bug resulting
from debugging, but does not provide the final fix strategy. Your goal is to explore how
similar bugs were fixed in the past in the same subsystem or files.
CRITICAL: Do NOT attempt to debug the issue further or write a patch for it yourself.
Your ONLY objective is to research and provide the necessary historical context.
Use the git-log tool with the Since parameter set to "3 years" to focus on recent history.
Search for commits that address issues with similar root causes (e.g. similar missing locks,
incorrect refcounting, or similar error path bugs) in the affected files.
Your final reply must summarize your findings: what idioms, locking rules, or common patterns
should be followed when writing a fix for this bug based on how previous similar bugs were addressed.
If you find no relevant past fixes, clearly state that.
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.
Prompt:
The crash is:
==================================================================
BUG: KASAN: slab-use-after-free in nvmet_port_subsys_allow_link+0x41/0x2e0 drivers/nvme/target/configfs.c:1059
Read of size 8 at addr ffff88811c6d44c8 by task syz-executor266/5850
CPU: 0 UID: 0 PID: 5850 Comm: syz-executor266 Not tainted syzkaller #1 PREEMPT(full)
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
Call Trace:
<TASK>
dump_stack_lvl+0xe8/0x150 lib/dump_stack.c:120
print_address_description+0x55/0x1e0 mm/kasan/report.c:378
print_report+0x58/0x70 mm/kasan/report.c:482
kasan_report+0x117/0x150 mm/kasan/report.c:595
nvmet_port_subsys_allow_link+0x41/0x2e0 drivers/nvme/target/configfs.c:1059
configfs_symlink+0x59a/0x1030 fs/configfs/symlink.c:196
vfs_symlink+0x18b/0x330 fs/namei.c:5794
filename_symlinkat+0x1cd/0x410 fs/namei.c:5819
__do_sys_symlinkat fs/namei.c:5839 [inline]
__se_sys_symlinkat+0x4e/0x2b0 fs/namei.c:5834
do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7f648f0cc459
Code: c0 79 93 eb d5 48 8d 7c 1d 00 eb 99 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 d0 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007f648c0911f8 EFLAGS: 00000246 ORIG_RAX: 000000000000010a
RAX: ffffffffffffffda RBX: 00007f648f139028 RCX: 00007f648f0cc459
RDX: 00007f648f139028 RSI: 00000000ffffff9c RDI: 00007f648f139060
RBP: 00007f648f139060 R08: 00007f648c0916c0 R09: 00007f648c0916c0
R10: 00007f648c0916c0 R11: 0000000000000246 R12: ffffffffffffffd0
R13: 0000000000000010 R14: 00007ffe91711350 R15: 00007ffe91711438
</TASK>
Allocated by task 5847:
kasan_save_stack mm/kasan/common.c:57 [inline]
kasan_save_track+0x3e/0x80 mm/kasan/common.c:78
poison_kmalloc_redzone mm/kasan/common.c:398 [inline]
__kasan_kmalloc+0x93/0xb0 mm/kasan/common.c:415
kasan_kmalloc include/linux/kasan.h:263 [inline]
__kmalloc_cache_noprof+0x321/0x600 mm/slub.c:5563
_kmalloc_noprof include/linux/slab.h:991 [inline]
_kzalloc_noprof include/linux/slab.h:1312 [inline]
nvmet_ports_make+0xe6/0xf00 drivers/nvme/target/configfs.c:2051
configfs_mkdir+0x4f6/0x9e0 fs/configfs/dir.c:1360
vfs_mkdir+0x40c/0x620 fs/namei.c:5410
filename_mkdirat+0x285/0x510 fs/namei.c:5443
__do_sys_mkdir fs/namei.c:5470 [inline]
__se_sys_mkdir+0x34/0x150 fs/namei.c:5467
do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
entry_SYSCALL_64_after_hwframe+0x77/0x7f
Freed by task 5847:
kasan_save_stack mm/kasan/common.c:57 [inline]
kasan_save_track+0x3e/0x80 mm/kasan/common.c:78
kasan_save_free_info+0x40/0x50 mm/kasan/generic.c:584
poison_slab_object mm/kasan/common.c:253 [inline]
__kasan_slab_free+0x5c/0x80 mm/kasan/common.c:285
kasan_slab_free include/linux/kasan.h:235 [inline]
slab_free_hook mm/slub.c:2748 [inline]
slab_free mm/slub.c:6499 [inline]
kfree+0x1c5/0x650 mm/slub.c:6792
config_item_cleanup fs/configfs/item.c:128 [inline]
config_item_release+0x13a/0x2d0 fs/configfs/item.c:137
configfs_rmdir+0x885/0x950 fs/configfs/dir.c:1571
vfs_rmdir+0x3e6/0x6a0 fs/namei.c:5515
filename_rmdir+0x292/0x520 fs/namei.c:5572
__do_sys_rmdir fs/namei.c:5595 [inline]
__se_sys_rmdir+0x2e/0x140 fs/namei.c:5592
do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
entry_SYSCALL_64_after_hwframe+0x77/0x7f
The buggy address belongs to the object at ffff88811c6d4000
which belongs to the cache kmalloc-4k of size 4096
The buggy address is located 1224 bytes inside of
freed 4096-byte region [ffff88811c6d4000, ffff88811c6d5000)
The buggy address belongs to the physical page:
page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x11c6d0
head: order:3 mapcount:0 entire_mapcount:0 nr_pages_mapped:0 pincount:0
flags: 0x17ff00000000040(head|node=0|zone=2|lastcpupid=0x7ff)
page_type: f5(slab)
raw: 017ff00000000040 ffff888100042140 dead000000000100 dead000000000122
raw: 0000000000000000 0000000000040004 00000000f5000000 0000000000000000
head: 017ff00000000040 ffff888100042140 dead000000000100 dead000000000122
head: 0000000000000000 0000000000040004 00000000f5000000 0000000000000000
head: 017ff00000000003 fffffffffffffe01 00000000ffffffff 00000000ffffffff
head: ffffffffffffffff 0000000000000000 00000000ffffffff 0000000000000008
page dumped because: kasan: bad access detected
page_owner tracks the page as allocated
page last allocated via order 3, migratetype Unmovable, gfp_mask 0xd20c0(__GFP_IO|__GFP_FS|__GFP_NOWARN|__GFP_NORETRY|__GFP_COMP|__GFP_NOMEMALLOC), pid 5843, tgid 5843 (syz-executor266), ts 63759869729
set_page_owner include/linux/page_owner.h:33 [inline]
post_alloc_hook+0x1f9/0x250 mm/page_alloc.c:1871
prep_new_page mm/page_alloc.c:1879 [inline]
get_page_from_freelist+0x2209/0x2280 mm/page_alloc.c:3943
__alloc_frozen_pages_noprof+0x217/0x5a0 mm/page_alloc.c:5436
alloc_slab_page mm/slub.c:3347 [inline]
allocate_slab+0x7d/0x620 mm/slub.c:3462
new_slab mm/slub.c:3513 [inline]
refill_objects+0x2d5/0x350 mm/slub.c:7410
refill_sheaf mm/slub.c:2885 [inline]
__pcs_replace_empty_main+0x2c8/0x6c0 mm/slub.c:4774
alloc_from_pcs mm/slub.c:4850 [inline]
slab_alloc_node mm/slub.c:4984 [inline]
__kmalloc_cache_noprof+0x39b/0x600 mm/slub.c:5559
_kmalloc_noprof include/linux/slab.h:991 [inline]
do_proc_readlink fs/proc/base.c:1817 [inline]
proc_pid_readlink+0x108/0x230 fs/proc/base.c:1847
vfs_readlink+0x4bb/0x540 fs/namei.c:6407
do_readlinkat+0x218/0x510 fs/stat.c:583
__do_sys_readlinkat fs/stat.c:599 [inline]
__se_sys_readlinkat fs/stat.c:596 [inline]
__x64_sys_readlinkat+0x9a/0xb0 fs/stat.c:596
do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
entry_SYSCALL_64_after_hwframe+0x77/0x7f
page last free pid 5843 tgid 5843 ts 63751651251 stack trace:
reset_page_owner include/linux/page_owner.h:26 [inline]
__free_pages_prepare mm/page_alloc.c:1418 [inline]
__free_frozen_pages+0xc50/0xd30 mm/page_alloc.c:2962
__slab_free+0x274/0x2c0 mm/slub.c:5815
qlink_free mm/kasan/quarantine.c:163 [inline]
qlist_free_all+0x99/0x100 mm/kasan/quarantine.c:179
kasan_quarantine_reduce+0x148/0x160 mm/kasan/quarantine.c:286
__kasan_slab_alloc+0x22/0x80 mm/kasan/common.c:350
kasan_slab_alloc include/linux/kasan.h:253 [inline]
slab_post_alloc_hook mm/slub.c:4683 [inline]
slab_alloc_node mm/slub.c:4996 [inline]
__do_kmalloc_node mm/slub.c:5413 [inline]
__kmalloc_noprof+0x307/0x720 mm/slub.c:5439
_kmalloc_noprof include/linux/slab.h:995 [inline]
tomoyo_add_entry security/tomoyo/common.c:2166 [inline]
tomoyo_supervisor+0xc53/0x1560 security/tomoyo/common.c:2238
tomoyo_audit_env_log security/tomoyo/environ.c:37 [inline]
tomoyo_env_perm+0x151/0x1f0 security/tomoyo/environ.c:64
tomoyo_environ security/tomoyo/domain.c:673 [inline]
tomoyo_find_next_domain+0x1332/0x1a90 security/tomoyo/domain.c:889
tomoyo_bprm_check_security+0x11b/0x180 security/tomoyo/tomoyo.c:102
security_bprm_check+0x85/0x240 security/security.c:820
search_binary_handler fs/exec.c:1749 [inline]
exec_binprm fs/exec.c:1791 [inline]
bprm_execve+0x888/0x1590 fs/exec.c:1847
do_execveat_common+0x4f4/0x670 fs/exec.c:1945
__do_sys_execve fs/exec.c:2018 [inline]
__se_sys_execve fs/exec.c:2012 [inline]
__x64_sys_execve+0x97/0xc0 fs/exec.c:2012
do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
entry_SYSCALL_64_after_hwframe+0x77/0x7f
Memory state around the buggy address:
ffff88811c6d4380: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
ffff88811c6d4400: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
>ffff88811c6d4480: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
^
ffff88811c6d4500: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
ffff88811c6d4580: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
==================================================================
The explanation of the root cause of the bug is:
An elegant and complete analysis of the root cause, along with a robust fix for this `configfs` race condition.
### 1. KASAN Crash Analysis & Offset Math
The crash is a slab-use-after-free read of size 8 at offset **1224** inside a 4096-byte region (`kmalloc-4k`).
The KASAN trace points to `nvmet_port_subsys_allow_link()`, which accesses `parent->ci_parent` where `parent` is a `struct config_item *`.
In `nvmet_ports_make()`, a `struct nvmet_port` is dynamically allocated. Let's look at its memory layout:
```c
struct nvmet_port {
struct list_head entry; // 16 bytes
struct nvmf_disc_rsp_page_entry disc_addr; // 1024 bytes
struct config_group group; // 136 bytes (offset 1040)
struct config_group subsys_group; // 136 bytes (offset 1176)
// ...
};
```
The `parent_item` passed to `allow_link` is `&port->subsys_group.cg_item`.
The offset of `subsys_group` is `16 + 1024 + 136 = 1176` bytes.
Inside `struct config_item`, the `ci_parent` pointer is at offset 48.
When `nvmet_port_subsys_allow_link` reads `parent->ci_parent`, it accesses memory at offset `1176 + 48 = 1224` bytes into the `nvmet_port` allocation. This **perfectly matches** the KASAN report:
> `The buggy address is located 1224 bytes inside of freed 4096-byte region`
### 2. The Root Cause (Race Condition)
The race occurs between Thread 1 creating a symlink in a default group (`ports/1/subsystems/`) and Thread 2 removing the parent root object (`ports/1`).
1. **Thread 1 (`symlinkat`)**:
- Enters `configfs_symlink()` with the VFS holding `inode_lock(dir)` on `ports/1/subsystems`.
- Acquires a reference to `parent_item` (the embedded `subsys_group.cg_item`).
- Drops `inode_unlock(dir)` to resolve the symlink target without deadlocking.
2. **Thread 2 (`rmdir`)**:
- Calls `configfs_rmdir()` on `ports/1`.
- Calls `configfs_detach_group()`, which recursively locks all default groups. Since Thread 1 dropped the lock on `ports/1/subsystems`, Thread 2 acquires it, marks the items as dead, detaches them, and unlocks.
- Calls `unlink_group()` on `ports/1`, dropping the subsystem's reference to the port.
- The `nvmet_port` refcount drops to 0, and the **entire `nvmet_port` struct is freed via `kfree()`**.
3. **Thread 1 Resumes**:
- Re-acquires `inode_lock(dir)`.
- Blindly calls `type->ct_item_ops->allow_link(parent_item, target_item)`.
- `parent_item` points to the `subsys_group` embedded inside the **already freed** `nvmet_port`, causing the UAF read at offset 1224.
- Even if `allow_link` wasn't called, jumping to `out_put` executes `config_item_put(parent_item)`, causing a secondary UAF on the kref.
### 3. Why Simple Fixes Fail
- **Re-acquiring `parent_item`**: You cannot safely drop `parent_item` before the unlock and re-acquire it via `configfs_get_config_item(dentry->d_parent)` after the relock. When `configfs_rmdir` detaches the item, it leaves `sd->s_element` as a dangling pointer. Re-acquiring it will dereference freed memory.
- **Holding `parent_item`**: Holding the reference to `parent_item` across the unlock is insufficient because `parent_item` is a **default group**. Default groups are embedded directly inside their parent structs (like `nvmet_port`). Their individual refcount being $> 0$ does **not** prevent the parent struct from being freed when the parent's refcount drops to 0.
### 4. The Proper Fix
To prevent the memory from being freed while the lock is dropped, `configfs_symlink()` must pin the **root object** (the dynamically allocated parent, e.g., `ports/1`) rather than just the embedded default group.
Because `configfs_symlink()` holds `inode_lock(dir)` initially, any concurrent `configfs_rmdir()` on an ancestor root object will block inside `detach_groups()` trying to acquire `inode_lock(dir)`. This guarantees the dentry tree is stable and we can safely walk up the `d_parent` chain to find the root object (the first dentry without the `CONFIGFS_USET_DEFAULT` flag) and take a reference to it.
After re-acquiring the lock, we must check `sd->s_frag->frag_dead` to see if the directory was removed while unlocked.
**Implementation for `fs/configfs/symlink.c`:**
```c
/* Add this helper function above configfs_symlink */
static struct config_item *configfs_get_root_item(struct dentry *dentry)
{
struct dentry *root_dentry = dentry;
struct configfs_dirent *sd;
while (root_dentry) {
sd = root_dentry->d_fsdata;
if (!(sd->s_type & CONFIGFS_USET_DEFAULT))
break;
root_dentry = root_dentry->d_parent;
}
return configfs_get_config_item(root_dentry);
}
int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
struct dentry *dentry, const char *symname)
{
int ret;
struct config_item *parent_item;
struct config_item *target_item = NULL;
struct config_item *root_item = NULL;
const struct config_item_type *type;
parent_item = configfs_get_config_item(dentry->d_parent);
type = parent_item->ci_type;
ret = -EPERM;
if (!type || !type->ct_item_ops ||
!type->ct_item_ops->allow_link)
goto out_put;
/*
* Pin the root object so that the parent_item (which may be an
* embedded default group) is not freed while we drop the lock.
*/
root_item = configfs_get_root_item(dentry->d_parent);
/*
* Drop the dir inode lock. The target path might be outside
* configfs, and we don't want to deadlock.
*/
inode_unlock(dir);
ret = get_target(symname, &target_item, dentry->d_sb);
inode_lock(dir);
if (ret)
goto out_put_root;
if (dentry->d_inode || d_unhashed(dentry))
ret = -EEXIST;
else
ret = inode_permission(&nop_mnt_idmap, dir,
MAY_WRITE | MAY_EXEC);
if (!ret) {
struct configfs_dirent *sd = dentry->d_parent->d_fsdata;
/* Check if the directory was removed while unlocked */
if (sd->s_frag->frag_dead) {
ret = -ENOENT;
} else {
ret = type->ct_item_ops->allow_link(parent_item, target_item);
}
}
if (!ret) {
mutex_lock(&configfs_symlink_mutex);
ret = create_link(parent_item, target_item, dentry);
mutex_unlock(&configfs_symlink_mutex);
if (ret && type->ct_item_ops->drop_link)
type->ct_item_ops->drop_link(parent_item,
target_item);
}
config_item_put(target_item);
out_put_root:
config_item_put(root_item);
out_put:
config_item_put(parent_item);
return ret;
}
```
Reply: Based on a review of the `fs/configfs/` history and past fixes for similar race conditions and use-after-free (UAF) bugs, here are the idioms, locking rules, and common patterns that should be followed when fixing this bug:
### 1. The Origin of the Race
The race condition was made possible by commit **`351e5d869e5a ("configfs: fix a deadlock in configfs_symlink()")`**. This commit introduced the `inode_unlock(dir)` and `inode_lock(dir)` around the target resolution in `configfs_symlink()` to prevent deadlocks. However, dropping this lock creates a window where a concurrent `configfs_rmdir()` can remove the parent directory.
### 2. Idiom: Pinning the Dynamically Allocated Root Object
In `configfs`, default groups (like `subsystems`) are embedded directly inside their parent structures (like `nvmet_port`). Holding a reference to an embedded default group does **not** prevent the parent structure from being freed when the parent's own refcount drops to 0.
To safely bridge the unlocked window, you must pin the dynamically allocated parent (the "root object"). The established pattern in `configfs` to distinguish between dynamically allocated items and embedded default groups is to check the **`CONFIGFS_USET_DEFAULT`** flag in `sd->s_type`.
* Functions like `detach_groups()` and `configfs_rmdir()` already use `!(sd->s_type & CONFIGFS_USET_DEFAULT)` to identify the boundaries of default groups.
* Walking up the `d_parent` chain until you hit a dentry without `CONFIGFS_USET_DEFAULT` and pinning it via `configfs_get_config_item()` is the correct and idiomatic way to ensure the entire memory allocation survives the unlocked window.
### 3. Idiom: Checking `frag_dead` for Concurrent Removals
After re-acquiring `inode_lock(dir)`, you must verify that the directory wasn't removed while the lock was dropped. The standard `configfs` idiom for this was introduced in commit **`b0841eefd969 ("configfs: provide exclusion between IO and removals")`**.
* When `configfs_rmdir()` or `configfs_unregister_group()` removes an item, it sets **`sd->s_frag->frag_dead = true`** (often while holding `frag_sem` for write).
* Throughout `fs/configfs/file.c`, operations (like `read`, `write`, `open`, `release`) check `if (!frag->frag_dead)` before proceeding.
* Applying this exact same check (`if (sd->s_frag->frag_dead) return -ENOENT;`) in `configfs_symlink()` after relocking perfectly aligns with the subsystem's established lifecycle rules.
### 4. Prior Art: Symlink Target Races
For historical context, a very similar race involving the symlink *target* (rather than the parent) was fixed in commit **`ba80aa909c99 ("configfs: Fix race between create_link and configfs_rmdir")`**.
* In that bug, the target was being removed concurrently. The fix was to check the `CONFIGFS_USET_DROPPING` flag under the `configfs_dirent_lock` before taking a reference to the target item.
* While that fix handled the *target* side of the symlink equation, your proposed fix correctly handles the *parent* side by combining the root object pinning with the `frag_dead` check.
### Summary
The proposed fix strategy in the bug report is completely sound and aligns with `configfs` design patterns. You should implement the `configfs_get_root_item()` helper to walk the dentry tree checking for `CONFIGFS_USET_DEFAULT`, pin that root object across the `inode_unlock/lock` boundary, and finally check `sd->s_frag->frag_dead` before proceeding with `allow_link()`.
|
| 308/2 |
2026/09/02 21:29 |
llm |
history-explorer |
0m
Model:
Tokens:
input: 10488
output: 33
thoughts: 287
Thoughts: **Analyzing the ConfigFS Race Condition Bug**
Okay, so I've got this bug report about a nasty slab-use-after-free in `nvmet_port_subsys_allow_link`, triggered through `configfs_symlink`. The issue boils down to a classic race: `configfs_symlink` briefly releasing the directory inode lock while resolving the symlink target, and then `configfs_rmdir` potentially sweeping in and removing the parent directory, thereby freeing the `nvmet_port` structure before `configfs_symlink` re-acquires the lock.
The suggested fix β walking the dentry tree, grabbing a reference to the root object, and checking `sd->s_frag->frag_dead` after re-acquiring the lock β sounds reasonable, but I need to be *sure*. Before I dive into implementing this, I need to understand what's been done in the past, and make sure my solution fits with prior art in `fs/configfs/`. My goal is to extract any relevant patterns or best-practices related to this kind of race condition or file destruction that I can leverage to make a robust fix.
To that end, Iβm firing up `git-log`, specifically looking at changes in `fs/configfs/symlink.c` and `fs/configfs/dir.c` over the last three years. I'm hoping to uncover some common idioms, locking strategies, or other preventative measures used in this subsystem to address similar issues. If I find something, I'll analyze how the previous developers approached the problem to make sure I don't reinvent the wheel, or inadvertently break existing code.
If, after this thorough examination, I come up empty, I'll need to clearly state that there were no prior examples of this kind of race in this code base. This will help inform the design of my solution. I'll still leverage general principles from the kernel to ensure the safety and correctness of my proposed fix.
|
| 309/2 |
2026/09/02 21:29 |
tool |
git-log |
0m
Args: map[Count:20 PathPrefix:fs/configfs/ Since:3 years]
Results: map[Output:d53ac61b6120 create_default_group(): pass parent's dentry instead of config_group
ad235a65dfff configfs_attach_group(): drop the unused parent_item argument
d4152c7562a8 configs_attach_item(): drop unused parent_item argument
967c898d9def configfs_create(): lift parent timestamp updates into callers
43b45755e80e kill configfs_drop_dentry()
1387aedb391a configfs: mark pinned dentries persistent
1a29e25799bc configfs: dentry refcount needs to be pinned only once
abe91a2aa9c4 switch configfs_detach_{group,item}() to passing dentry
05a4d3d1b676 configfs_remove_dir(), detach_attrs(): switch to passing dentry
34a62943762c populate_attrs(): move cleanup to the sole caller
17bee4d214a4 populate_group(): move cleanup on failure to the sole caller
acef053618d1 configfs_detach_rollback(): pass configfs_dirent instead of dentry
cbd6bbd48732 configfs_do_depend_item(): pass configfs_dirent instead of dentry
764682e01184 configfs_depend_prep(): pass configfs_dirent instead of dentry
96551d7f9f7b configfs_detach_prep(): pass configfs_dirent instead of dentry
5cefe23f6374 configfs_mkdir(): use take_dentry_name_snapshot()
9b9e8bb81c41 configfs: fix lockless traversals of ->s_children
0c23a964a0f9 configfs_lookup(): switch to d_splice_alias()
10da12d352b7 configfs_lookup(): don't leave ->s_dentry dangling on failure
1b27d11b17b7 configfs: replace __get_free_pages() with kzalloc()
]
|
| 310/2 |
2026/09/02 21:29 |
llm |
history-explorer |
0m
Model:
Tokens:
input: 11758
output: 41
thoughts: 52
Thoughts: Okay, let's dive into this. Here's how I'd approach that problem, thinking like a seasoned developer:
**Investigating Potential Race Conditions in ConfigFS Symlinks**
Alright, I need to understand what's been happening with `configfs_symlink` or anything related to similar race conditions. Time to get my hands dirty. My first instinct is to leverage the power of `git log`. I can construct a focused search to unearth relevant commits. I'll need to use `MessageRegexps` to be effective here. Let's think about the keywords: "race," obviously, and anything referencing "configfs" since that's the area of concern. I'll also want to look for "symlink" as a separate search term. This should cast a wide but hopefully not *too* wide net to capture all the potentially related commits. I'll construct a command to search the commit logs with these regexps. That should give me a solid foundation for tracing the history and hopefully pinpointing the source of any issues.
|
| 311/2 |
2026/09/02 21:29 |
tool |
git-log |
0m
Args: map[Count:20 MessageRegexps:[race] PathPrefix:fs/configfs/ Since:5 years]
Results: map[Output:88d5baf69082 Change inode_operations.mkdir to return struct dentry *
c65234b283a6 configfs: fix possible memory leak in configfs_create_dir()
84ec758fb2da configfs: fix a race in configfs_{,un}register_subsystem()
29044dae2e74 fsnotify: fix fsnotify hooks in pseudo filesystems
]
|
| 312/2 |
2026/09/02 21:29 |
llm |
history-explorer |
0m
Model:
Tokens:
input: 12187
output: 38
thoughts: 13
Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience:
**Investigating `configfs_symlink` History**
Okay, time to dig into this. I'm thinking, "Alright, let's pull up the history for `configfs_symlink`." I need to understand the evolution of that specific function or feature within `configfs`. It's likely that a past commit or series of commits introduced, modified, or perhaps even deprecated the use of symbolic links within the `configfs` framework, and I need to pinpoint the relevant changes. My goal is to quickly understand the rationale behind its current implementation and see if there might be any clues there as to how it might be acting in this particular scenario. Understanding this history will help me determine if there might be a regression, a design issue I'm unaware of, or if some new behavior has been introduced. I'm expecting this history to provide critical context for any troubleshooting, debugging, or design analysis related to `configfs` and its symlink handling.
|
| 313/2 |
2026/09/02 21:29 |
tool |
git-log |
0m
Args: map[Count:20 PathPrefix:fs/configfs/symlink.c Since:10 years]
Results: map[Output:1387aedb391a configfs: mark pinned dentries persistent
1b25dea3867a configfs:get_target() - release path as soon as we grab configfs_item reference
4609e1f18e19 fs: port ->permission() to pass mnt_idmap
7a77db95511c fs: port ->symlink() to pass mnt_idmap
fa60ce2cb450 treewide: remove editor modelines and cruft
549c7297717c fs: make helpers idmap mount aware
47291baa8ddf namei: make permission helpers idmapped mount aware
e2f238f7d5a1 configfs: calculate the depth of parent item
e9c03af21cc7 configfs: calculate the symlink target only once
351e5d869e5a configfs: fix a deadlock in configfs_symlink()
328970de0e39 treewide: Replace GPLv2 boilerplate/reference with SPDX - rule 145
1823342a1f2b configfs: replace strncpy with memcpy
aa293583f0fe configfs: make ci_type field, some pointers and function arguments const
ba80aa909c99 configfs: Fix race between create_link and configfs_rmdir
dfeef68862ed vfs: remove ".readlink = generic_readlink" assignments
]
|
| 314/2 |
2026/09/02 21:29 |
llm |
history-explorer |
0m
Model:
Tokens:
input: 12832
output: 27
thoughts: 59
Thoughts: Here's my take on those commits, framed as my own thought process, given my background:
**Analyzing Potential Deadlock and Race Conditions in ConfigFS Symlinks**
Alright, let's dive into these two ConfigFS patches. Seeing `351e5d869e5a configfs: fix a deadlock in configfs_symlink()` immediately grabs my attention. Deadlocks within a filesystem are nasty. I know from experience that they can lead to complete system freezes, so understanding the underlying cause is paramount. I'll need to carefully dissect the code to trace the locking order and see where the circular dependency was introduced. Was it a mutex acquisition order issue? Or perhaps a spinlock contention problem? The patch itself will be crucial; I'll be looking for subtle reordering of lock operations or maybe a subtle use of a recursive lock that was causing problems. Given my familiarity with the intricacies of ConfigFS's internal data structures, I'm already anticipating the typical suspects: inode/dentry operations, reference counting, and potential interactions with the VFS layer. I'll need to consider how the symlink creation interacts with concurrent directory operations, and what the potential impact of this fix is on overall performance and scalability.
Next, I'll move onto `ba80aa909c99 configfs: Fix race between create_link and configfs_rmdir`. Ah, a classic race condition! The description itself suggests a race between symlink creation and directory removal. These types of bugs are insidious; they often manifest as intermittent issues, making them challenging to debug. The kernel's concurrency model, with its various locking mechanisms and interrupt contexts, adds a layer of complexity. Here, I'll focus on how the `create_link` function interacts with `configfs_rmdir`. I need to think about which actions could potentially be happening at the same time, and whether some actions are not synchronized properly with other operations. The potential for the link to be created and then removed before the filesystem is updated will be my primary area of focus. I'll be looking at how the reference counts are handled and how the file system tracks directories and files, and will consider whether an atomic operation is needed to fix the race. From experience, I know that even a seemingly small change in locking order can have profound effects on the reliability of the filesystem. I expect the fix probably involves some careful use of spinlocks, mutexes, or perhaps some atomic operations to ensure that these operations are performed in a consistent and correct manner.
|
| 315/2 |
2026/09/02 21:29 |
tool |
git-show |
0m
Args:
Results: map[Output:commit 351e5d869e5ac10cb40c78b5f2d7dfc816ad4587
Author: Al Viro <viro@zeniv.linux.org.uk>
Date: Sat Aug 3 11:51:18 2019 -0400
configfs: fix a deadlock in configfs_symlink()
Configfs abuses symlink(2). Unlike the normal filesystems, it
wants the target resolved at symlink(2) time, like link(2) would've
done. The problem is that ->symlink() is called with the parent
directory locked exclusive, so resolving the target inside the
->symlink() is easily deadlocked.
Short of really ugly games in sys_symlink() itself, all we can
do is to unlock the parent before resolving the target and
relock it after. However, that invalidates the checks done
by the caller of ->symlink(), so we have to
* check that dentry is still where it used to be
(it couldn't have been moved, but it could've been unhashed)
* recheck that it's still negative (somebody else
might've successfully created a symlink with the same name
while we were looking the target up)
* recheck the permissions on the parent directory.
Cc: stable@vger.kernel.org
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
Signed-off-by: Christoph Hellwig <hch@lst.de>
diff --git a/fs/configfs/symlink.c b/fs/configfs/symlink.c
index 91eac6c55e07..f3881e4caedd 100644
--- a/fs/configfs/symlink.c
+++ b/fs/configfs/symlink.c
@@ -143,11 +143,42 @@ int configfs_symlink(struct inode *dir, struct dentry *dentry, const char *symna
!type->ct_item_ops->allow_link)
goto out_put;
+ /*
+ * This is really sick. What they wanted was a hybrid of
+ * link(2) and symlink(2) - they wanted the target resolved
+ * at syscall time (as link(2) would've done), be a directory
+ * (which link(2) would've refused to do) *AND* be a deep
+ * fucking magic, making the target busy from rmdir POV.
+ * symlink(2) is nothing of that sort, and the locking it
+ * gets matches the normal symlink(2) semantics. Without
+ * attempts to resolve the target (which might very well
+ * not even exist yet) done prior to locking the parent
+ * directory. This perversion, OTOH, needs to resolve
+ * the target, which would lead to obvious deadlocks if
+ * attempted with any directories locked.
+ *
+ * Unfortunately, that garbage is userland ABI and we should've
+ * said "no" back in 2005. Too late now, so we get to
+ * play very ugly games with locking.
+ *
+ * Try *ANYTHING* of that sort in new code, and you will
+ * really regret it. Just ask yourself - what could a BOFH
+ * do to me and do I want to find it out first-hand?
+ *
+ * AV, a thoroughly annoyed bastard.
+ */
+ inode_unlock(dir);
ret = get_target(symname, &path, &target_item, dentry->d_sb);
+ inode_lock(dir);
if (ret)
goto out_put;
- ret = type->ct_item_ops->allow_link(parent_item, target_item);
+ if (dentry->d_inode || d_unhashed(dentry))
+ ret = -EEXIST;
+ else
+ ret = inode_permission(dir, MAY_WRITE | MAY_EXEC);
+ if (!ret)
+ ret = type->ct_item_ops->allow_link(parent_item, target_item);
if (!ret) {
mutex_lock(&configfs_symlink_mutex);
ret = create_link(parent_item, target_item, dentry);
]
|
| 316/2 |
2026/09/02 21:29 |
llm |
history-explorer |
0m
Model:
Tokens:
input: 14559
output: 25
thoughts: 193
Thoughts: **Analyzing the ConfigFS Race Condition**
Okay, so I'm looking at this `configfs` issue, and it's pretty clear where the problem lies. The introduction of `inode_unlock(dir)` and `inode_lock(dir)` within `configfs_symlink()` in that commit, `351e5d869e5a`, that's the culprit. That unlock-relock sequence is *textbook* for introducing a race condition, and the bug report nailed it. The scenario is classic: Thread 1 creates a symlink within a default group (like `ports/1/subsystems/`), while Thread 2 is trying to remove the parent object (say, `ports/1`). The race is that the parent object could be freed while the lock is dropped.
The bug report hits the nail on the head: to actually fix this, we need to pin the *root* object (the dynamically allocated parent object, `ports/1` in the example) and not just the embedded default group within the parent, during the symlink creation. That's the key. Now, let me see what's in that `ba80aa909c99 configfs: Fix race between create_link and configfs_rmdir` commit to see if it makes the right changes.
|
| 317/2 |
2026/09/02 21:29 |
tool |
git-show |
0m
Args:
Results: map[Output:commit ba80aa909c99802c428682c352b0ee0baac0acd3
Author: Nicholas Bellinger <nab@linux-iscsi.org>
Date: Thu Jun 8 04:51:54 2017 +0000
configfs: Fix race between create_link and configfs_rmdir
This patch closes a long standing race in configfs between
the creation of a new symlink in create_link(), while the
symlink target's config_item is being concurrently removed
via configfs_rmdir().
This can happen because the symlink target's reference
is obtained by config_item_get() in create_link() before
the CONFIGFS_USET_DROPPING bit set by configfs_detach_prep()
during configfs_rmdir() shutdown is actually checked..
This originally manifested itself on ppc64 on v4.8.y under
heavy load using ibmvscsi target ports with Novalink API:
[ 7877.289863] rpadlpar_io: slot U8247.22L.212A91A-V1-C8 added
[ 7879.893760] ------------[ cut here ]------------
[ 7879.893768] WARNING: CPU: 15 PID: 17585 at ./include/linux/kref.h:46 config_item_get+0x7c/0x90 [configfs]
[ 7879.893811] CPU: 15 PID: 17585 Comm: targetcli Tainted: G O 4.8.17-customv2.22 #12
[ 7879.893812] task: c00000018a0d3400 task.stack: c0000001f3b40000
[ 7879.893813] NIP: d000000002c664ec LR: d000000002c60980 CTR: c000000000b70870
[ 7879.893814] REGS: c0000001f3b43810 TRAP: 0700 Tainted: G O (4.8.17-customv2.22)
[ 7879.893815] MSR: 8000000000029033 <SF,EE,ME,IR,DR,RI,LE> CR: 28222242 XER: 00000000
[ 7879.893820] CFAR: d000000002c664bc SOFTE: 1
GPR00: d000000002c60980 c0000001f3b43a90 d000000002c70908 c0000000fbc06820
GPR04: c0000001ef1bd900 0000000000000004 0000000000000001 0000000000000000
GPR08: 0000000000000000 0000000000000001 d000000002c69560 d000000002c66d80
GPR12: c000000000b70870 c00000000e798700 c0000001f3b43ca0 c0000001d4949d40
GPR16: c00000014637e1c0 0000000000000000 0000000000000000 c0000000f2392940
GPR20: c0000001f3b43b98 0000000000000041 0000000000600000 0000000000000000
GPR24: fffffffffffff000 0000000000000000 d000000002c60be0 c0000001f1dac490
GPR28: 0000000000000004 0000000000000000 c0000001ef1bd900 c0000000f2392940
[ 7879.893839] NIP [d000000002c664ec] config_item_get+0x7c/0x90 [configfs]
[ 7879.893841] LR [d000000002c60980] check_perm+0x80/0x2e0 [configfs]
[ 7879.893842] Call Trace:
[ 7879.893844] [c0000001f3b43ac0] [d000000002c60980] check_perm+0x80/0x2e0 [configfs]
[ 7879.893847] [c0000001f3b43b10] [c000000000329770] do_dentry_open+0x2c0/0x460
[ 7879.893849] [c0000001f3b43b70] [c000000000344480] path_openat+0x210/0x1490
[ 7879.893851] [c0000001f3b43c80] [c00000000034708c] do_filp_open+0xfc/0x170
[ 7879.893853] [c0000001f3b43db0] [c00000000032b5bc] do_sys_open+0x1cc/0x390
[ 7879.893856] [c0000001f3b43e30] [c000000000009584] system_call+0x38/0xec
[ 7879.893856] Instruction dump:
[ 7879.893858] 409d0014 38210030 e8010010 7c0803a6 4e800020 3d220000 e94981e0 892a0000
[ 7879.893861] 2f890000 409effe0 39200001 992a0000 <0fe00000> 4bffffd0 60000000 60000000
[ 7879.893866] ---[ end trace 14078f0b3b5ad0aa ]---
To close this race, go ahead and obtain the symlink's target
config_item reference only after the existing CONFIGFS_USET_DROPPING
check succeeds.
This way, if configfs_rmdir() wins create_link() will return -ENONET,
and if create_link() wins configfs_rmdir() will return -EBUSY.
Reported-by: Bryant G. Ly <bryantly@linux.vnet.ibm.com>
Tested-by: Bryant G. Ly <bryantly@linux.vnet.ibm.com>
Signed-off-by: Nicholas Bellinger <nab@linux-iscsi.org>
Signed-off-by: Christoph Hellwig <hch@lst.de>
Cc: stable@vger.kernel.org
diff --git a/fs/configfs/symlink.c b/fs/configfs/symlink.c
index a6ab012a2c6a..c8aabba502f6 100644
--- a/fs/configfs/symlink.c
+++ b/fs/configfs/symlink.c
@@ -83,14 +83,13 @@ static int create_link(struct config_item *parent_item,
ret = -ENOMEM;
sl = kmalloc(sizeof(struct configfs_symlink), GFP_KERNEL);
if (sl) {
- sl->sl_target = config_item_get(item);
spin_lock(&configfs_dirent_lock);
if (target_sd->s_type & CONFIGFS_USET_DROPPING) {
spin_unlock(&configfs_dirent_lock);
- config_item_put(item);
kfree(sl);
return -ENOENT;
}
+ sl->sl_target = config_item_get(item);
list_add(&sl->sl_list, &target_sd->s_links);
spin_unlock(&configfs_dirent_lock);
ret = configfs_create_link(sl, parent_item->ci_dentry,
]
|
| 318/2 |
2026/09/02 21:29 |
llm |
history-explorer |
0m
Model:
Tokens:
input: 17667
output: 42
thoughts: 153
Thoughts: **Analyzing a Race Condition in ConfigFS Symlink Creation**
Okay, so this bug report is pointing to a nasty race condition. Thread 1 is creating a symbolic link within a default group, and Thread 2 is yanking the rug out from under it by removing the parent root object. The core issue, as I understand it, is that `configfs_symlink()` isn't correctly handling the object lifetime of that root object during the symlink creation. It seems like the critical fix is to ensure the **root object**, that dynamically allocated parent directory, is pinned in memory to prevent its premature freeing. Instead of just pinning the embedded default group, the entire `ports/1` directory needs to be kept alive.
To get a handle on how this might be tackled in the code, my first instinct is to dig through the commit history. I need to find previous attempts to address similar concerns around object pinning and `configfs`, especially anything mentioning the "root object". I'll lean on `git log` to help me, focusing on commits within the `fs/configfs/` directory and searching for keywords like "pin". Hopefully, this will lead me to relevant patches or discussions that offer clues about the proper way to manage the object lifetimes in this area of the filesystem. I am hoping to find an existing solution or at least a related approach that I can leverage.
|
| 319/2 |
2026/09/02 21:29 |
tool |
git-log |
0m
Args: map[Count:20 MessageRegexps:[pin] PathPrefix:fs/configfs/ Since:10 years]
Results: map[Output:1387aedb391a configfs: mark pinned dentries persistent
1a29e25799bc configfs: dentry refcount needs to be pinned only once
abe91a2aa9c4 switch configfs_detach_{group,item}() to passing dentry
05a4d3d1b676 configfs_remove_dir(), detach_attrs(): switch to passing dentry
10da12d352b7 configfs_lookup(): don't leave ->s_dentry dangling on failure
eb028c33451a d_make_discardable(): warn if given a non-persistent dentry
b1494e6bc47c configfs, securityfs: kill_litter_super() not needed
84147f4e84c4 configfs: improve item creation performance
eb31e2f63d85 utimes: Clamp the timestamps in notify_change()
35399f87e271 configfs: fix possible use-after-free in configfs_register_group
ba80aa909c99 configfs: Fix race between create_link and configfs_rmdir
]
|
| 320/2 |
2026/09/02 21:30 |
llm |
history-explorer |
0m
Model:
Tokens:
input: 18441
output: 27
thoughts: 35
Thoughts: Okay, let's dive into this commit, `35399f87e271`, focusing on that `configfs: fix possible use-after-free in configfs_register_group` fix. I need to understand what triggered this, the nature of the vulnerability, and the actual fix implemented. I'm assuming, given the "use-after-free" flag, that there's a memory management issue at play here. ConfigFS, being a virtual filesystem, is notorious for tricky race conditions and reference counting pitfalls. I'm particularly interested in the `configfs_register_group` function; itβs likely where the allocation and potentially the deallocation or referencing errors are happening. I want to know if they were not correctly accounting for references, leading to an object being freed prematurely and later accessed. This sounds like an area ripe for careful analysis. Let's see how they addressed this in the patch.
|
| 321/2 |
2026/09/02 21:30 |
tool |
git-show |
0m
Args:
Results: map[Output:commit 35399f87e271f7cf3048eab00a421a6519ac8441
Author: YueHaibing <yuehaibing@huawei.com>
Date: Sun May 5 11:03:12 2019 +0800
configfs: fix possible use-after-free in configfs_register_group
In configfs_register_group(), if create_default_group() failed, we
forget to unlink the group. It will left a invalid item in the parent list,
which may trigger the use-after-free issue seen below:
BUG: KASAN: use-after-free in __list_add_valid+0xd4/0xe0 lib/list_debug.c:26
Read of size 8 at addr ffff8881ef61ae20 by task syz-executor.0/5996
CPU: 1 PID: 5996 Comm: syz-executor.0 Tainted: G C 5.0.0+ #5
Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.10.2-1ubuntu1 04/01/2014
Call Trace:
__dump_stack lib/dump_stack.c:77 [inline]
dump_stack+0xa9/0x10e lib/dump_stack.c:113
print_address_description+0x65/0x270 mm/kasan/report.c:187
kasan_report+0x149/0x18d mm/kasan/report.c:317
__list_add_valid+0xd4/0xe0 lib/list_debug.c:26
__list_add include/linux/list.h:60 [inline]
list_add_tail include/linux/list.h:93 [inline]
link_obj+0xb0/0x190 fs/configfs/dir.c:759
link_group+0x1c/0x130 fs/configfs/dir.c:784
configfs_register_group+0x56/0x1e0 fs/configfs/dir.c:1751
configfs_register_default_group+0x72/0xc0 fs/configfs/dir.c:1834
? 0xffffffffc1be0000
iio_sw_trigger_init+0x23/0x1000 [industrialio_sw_trigger]
do_one_initcall+0xbc/0x47d init/main.c:887
do_init_module+0x1b5/0x547 kernel/module.c:3456
load_module+0x6405/0x8c10 kernel/module.c:3804
__do_sys_finit_module+0x162/0x190 kernel/module.c:3898
do_syscall_64+0x9f/0x450 arch/x86/entry/common.c:290
entry_SYSCALL_64_after_hwframe+0x49/0xbe
RIP: 0033:0x462e99
Code: f7 d8 64 89 02 b8 ff ff ff ff c3 66 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 bc ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007f494ecbcc58 EFLAGS: 00000246 ORIG_RAX: 0000000000000139
RAX: ffffffffffffffda RBX: 000000000073bf00 RCX: 0000000000462e99
RDX: 0000000000000000 RSI: 0000000020000180 RDI: 0000000000000003
RBP: 00007f494ecbcc70 R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000246 R12: 00007f494ecbd6bc
R13: 00000000004bcefa R14: 00000000006f6fb0 R15: 0000000000000004
Allocated by task 5987:
set_track mm/kasan/common.c:87 [inline]
__kasan_kmalloc.constprop.3+0xa0/0xd0 mm/kasan/common.c:497
kmalloc include/linux/slab.h:545 [inline]
kzalloc include/linux/slab.h:740 [inline]
configfs_register_default_group+0x4c/0xc0 fs/configfs/dir.c:1829
0xffffffffc1bd0023
do_one_initcall+0xbc/0x47d init/main.c:887
do_init_module+0x1b5/0x547 kernel/module.c:3456
load_module+0x6405/0x8c10 kernel/module.c:3804
__do_sys_finit_module+0x162/0x190 kernel/module.c:3898
do_syscall_64+0x9f/0x450 arch/x86/entry/common.c:290
entry_SYSCALL_64_after_hwframe+0x49/0xbe
Freed by task 5987:
set_track mm/kasan/common.c:87 [inline]
__kasan_slab_free+0x130/0x180 mm/kasan/common.c:459
slab_free_hook mm/slub.c:1429 [inline]
slab_free_freelist_hook mm/slub.c:1456 [inline]
slab_free mm/slub.c:3003 [inline]
kfree+0xe1/0x270 mm/slub.c:3955
configfs_register_default_group+0x9a/0xc0 fs/configfs/dir.c:1836
0xffffffffc1bd0023
do_one_initcall+0xbc/0x47d init/main.c:887
do_init_module+0x1b5/0x547 kernel/module.c:3456
load_module+0x6405/0x8c10 kernel/module.c:3804
__do_sys_finit_module+0x162/0x190 kernel/module.c:3898
do_syscall_64+0x9f/0x450 arch/x86/entry/common.c:290
entry_SYSCALL_64_after_hwframe+0x49/0xbe
The buggy address belongs to the object at ffff8881ef61ae00
which belongs to the cache kmalloc-192 of size 192
The buggy address is located 32 bytes inside of
192-byte region [ffff8881ef61ae00, ffff8881ef61aec0)
The buggy address belongs to the page:
page:ffffea0007bd8680 count:1 mapcount:0 mapping:ffff8881f6c03000 index:0xffff8881ef61a700
flags: 0x2fffc0000000200(slab)
raw: 02fffc0000000200 ffffea0007ca4740 0000000500000005 ffff8881f6c03000
raw: ffff8881ef61a700 000000008010000c 00000001ffffffff 0000000000000000
page dumped because: kasan: bad access detected
Memory state around the buggy address:
ffff8881ef61ad00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
ffff8881ef61ad80: 00 00 00 00 00 00 00 00 fc fc fc fc fc fc fc fc
>ffff8881ef61ae00: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
^
ffff8881ef61ae80: fb fb fb fb fb fb fb fb fc fc fc fc fc fc fc fc
ffff8881ef61af00: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
Fixes: 5cf6a51e6062 ("configfs: allow dynamic group creation")
Reported-by: Hulk Robot <hulkci@huawei.com>
Signed-off-by: YueHaibing <yuehaibing@huawei.com>
Signed-off-by: Christoph Hellwig <hch@lst.de>
diff --git a/fs/configfs/dir.c b/fs/configfs/dir.c
index 39843fa7e11b..920d350df37b 100644
--- a/fs/configfs/dir.c
+++ b/fs/configfs/dir.c
@@ -1755,12 +1755,19 @@ int configfs_register_group(struct config_group *parent_group,
inode_lock_nested(d_inode(parent), I_MUTEX_PARENT);
ret = create_default_group(parent_group, group);
- if (!ret) {
- spin_lock(&configfs_dirent_lock);
- configfs_dir_set_ready(group->cg_item.ci_dentry->d_fsdata);
- spin_unlock(&configfs_dirent_lock);
- }
+ if (ret)
+ goto err_out;
+
+ spin_lock(&configfs_dirent_lock);
+ configfs_dir_set_ready(group->cg_item.ci_dentry->d_fsdata);
+ spin_unlock(&configfs_dirent_lock);
+ inode_unlock(d_inode(parent));
+ return 0;
+err_out:
inode_unlock(d_inode(parent));
+ mutex_lock(&subsys->su_mutex);
+ unlink_group(group);
+ mutex_unlock(&subsys->su_mutex);
return ret;
}
EXPORT_SYMBOL(configfs_register_group);
]
|
| 322/2 |
2026/09/02 21:30 |
llm |
history-explorer |
0m
Model:
Tokens:
input: 21790
output: 46
thoughts: 29
Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience:
**Investigating a Novel Error Path Cleanup**
Okay, this feels like we're navigating a less common error path, which immediately grabs my attention. This isn't your standard operational flow. The error handling in this area needs a close look. So, the first logical step is to dig into the codebase for specific clues. My immediate instinct is to look for "configfs_symlink" within the commit history. That function is often involved when dealing with creating and manipulating symbolic links within configfs. It is a good indicator of things to investigate. It's a key function in handling these kind of operations. I'm expecting that will provide a reasonable starting point to understand the potential failure mode and how the cleanup is meant to work. From there I can explore the related code and figure out the situation.
|
| 323/2 |
2026/09/02 21:30 |
tool |
git-log |
0m
Args: map[Count:20 MessageRegexps:[configfs_symlink] PathPrefix:fs/configfs/ Since:10 years]
Results: map[Output:351e5d869e5a configfs: fix a deadlock in configfs_symlink()
]
|
| 324/2 |
2026/09/02 21:30 |
llm |
history-explorer |
0m
Model:
Tokens:
input: 22089
output: 46
thoughts: 10
Thoughts: Okay, here's my interpretation of that thought process, assuming I'm an expert in this field, and rendered in the first person:
**Navigating the File System Landscape**
Right, so I'm thinking about file system operations, specifically the removal of directories. We've got a lot of options, a lot of potential quirks. And, while I understand the general principles of directory removal and how to approach the various methods available, `configfs_rmdir` immediately jumped to mind as a key point. It's a very specialized function, and its specifics and subtleties need considering given this is an environment where resources are likely being dynamically managed. I'm wondering if its behavior here is going to introduce issues, if there are interactions with the broader system that should be carefully considered, or if there's any particular corner-case specific to this configuration setup that should be flagged. Its specific interaction with other file system components is something I'll need to keep in mind, too.
|
| 325/2 |
2026/09/02 21:30 |
tool |
git-log |
0m
Args: map[Count:20 MessageRegexps:[configfs_rmdir] PathPrefix:fs/configfs/ Since:10 years]
Results: map[Output:8aebfffacfa3 configfs: fix config_item refcnt leak in configfs_rmdir()
cc57c07343bd configfs: fix registered group removal
ba80aa909c99 configfs: Fix race between create_link and configfs_rmdir
]
|
| 326/2 |
2026/09/02 21:30 |
llm |
history-explorer |
0m
Model:
Tokens:
input: 22433
output: 22
thoughts: 30
Thoughts: Okay, here's my take on those thoughts, framed as an expert's internal monologue:
**Investigating a `configfs` Leak**
Alright, let's dive into this `8aebfffacfa3` commit, "configfs: fix config_item refcnt leak in configfs_rmdir()." A refcount leak in `configfs_rmdir()`? That's definitely something to pay attention to. `configfs` is a tricky beast, and these kinds of leaks, especially around directory removal, can be insidious. They can lead to a gradual memory buildup, potentially crashing the system, or more subtle issues that are hard to track down. I need to understand the specifics of how the reference counting was being handled in `configfs_rmdir()` before this fix. What was the scenario that triggered the leak?
I'm thinking: was it related to how the items are added to the directory in the first place? Maybe there was an incorrect assumption about the lifespan of an item or how a reference was dropped in the event of an error during directory removal? I'll need to look at the surrounding code, the data structures used by `configfs`, and the intended usage patterns to get a full picture. The commit message itself should provide some valuable clues, but I'll probably need to dig deeper into the code to really grok it. I also need to understand any side effects the fix has had. Are there potential regressions I need to be aware of? Best to review any associated test cases too! This could be a good reminder to me, to refamiliarize myself with all the intricacies of `configfs`.
|
| 327/2 |
2026/09/02 21:30 |
tool |
git-show |
0m
Args:
Results: map[Output:commit 8aebfffacfa379ba400da573a5bf9e49634e38cb
Author: Xiyu Yang <xiyuyang19@fudan.edu.cn>
Date: Sat Apr 25 20:52:26 2020 +0800
configfs: fix config_item refcnt leak in configfs_rmdir()
configfs_rmdir() invokes configfs_get_config_item(), which returns a
reference of the specified config_item object to "parent_item" with
increased refcnt.
When configfs_rmdir() returns, local variable "parent_item" becomes
invalid, so the refcount should be decreased to keep refcount balanced.
The reference counting issue happens in one exception handling path of
configfs_rmdir(). When down_write_killable() fails, the function forgets
to decrease the refcnt increased by configfs_get_config_item(), causing
a refcnt leak.
Fix this issue by calling config_item_put() when down_write_killable()
fails.
Signed-off-by: Xiyu Yang <xiyuyang19@fudan.edu.cn>
Signed-off-by: Xin Tan <tanxin.ctf@gmail.com>
Signed-off-by: Christoph Hellwig <hch@lst.de>
diff --git a/fs/configfs/dir.c b/fs/configfs/dir.c
index cf7b7e1d5bd7..cb733652ecca 100644
--- a/fs/configfs/dir.c
+++ b/fs/configfs/dir.c
@@ -1519,6 +1519,7 @@ static int configfs_rmdir(struct inode *dir, struct dentry *dentry)
spin_lock(&configfs_dirent_lock);
configfs_detach_rollback(dentry);
spin_unlock(&configfs_dirent_lock);
+ config_item_put(parent_item);
return -EINTR;
}
frag->frag_dead = true;
]
|
| 328/2 |
2026/09/02 21:30 |
llm |
history-explorer |
0m
Model:
Tokens:
input: 23376
output: 25
thoughts: 24
Thoughts: Here's a summary of my thoughts, as though I'm dissecting this commit:
**Analyzing the ConfigFS Group Removal Fix**
Okay, diving into `cc57c07343bd`, looks like we're addressing a bug in `configfs` related to group removal. Immediately, I'm thinking about the potential implications. A broken group removal process could lead to dangling references, resource leaks, or even security vulnerabilities if not handled correctly. We're talking about a core filesystem infrastructure here, so this is definitely something I need to understand thoroughly.
My initial questions are: what was the specific problem? What were the symptoms? And, crucially, how was this handled incorrectly in the first place? I'll be looking for the patch diff to see the exact code changes and pinpoint the root cause. I'm expecting to see modifications around reference counting, locking, or error handling. A sloppy implementation here could introduce race conditions, or prevent proper cleanup when a group is unregistered, so that's where my focus is going to be.
The commit message itself will hopefully provide context, but I know I'll likely need to dig deeper into the surrounding code to grasp the full picture. My focus here is going to be on security implications and stability, if a group is removed prematurely or incorrectly, that's not something I want to take lightly. Ultimately, I'll be assessing if the solution is robust, doesn't introduce any regressions, and effectively addresses the underlying issue without creating any new problems downstream. Time to start digging!
|
| 329/2 |
2026/09/02 21:30 |
tool |
git-show |
0m
Args:
Results: map[Output:commit cc57c07343bd071cdf1915a91a24ab7d40c9b590
Author: Mike Christie <mchristi@redhat.com>
Date: Sun Jul 15 18:16:17 2018 -0500
configfs: fix registered group removal
This patch fixes a bug where configfs_register_group had added
a group in a tree, and userspace has done a rmdir on a dir somewhere
above that group and we hit a kernel crash. The problem is configfs_rmdir
will detach everything under it and unlink groups on the default_groups
list. It will not unlink groups added with configfs_register_group so when
configfs_unregister_group is called to drop its references to the group/items
we crash when we try to access the freed dentrys.
The patch just adds a check for if a rmdir has been done above
us and if so just does the unlink part of unregistration.
Sorry if you are getting this multiple times. I thouhgt I sent
this to some of you and lkml, but I do not see it.
Signed-off-by: Mike Christie <mchristi@redhat.com>
Cc: Christoph Hellwig <hch@lst.de>
Cc: Joel Becker <jlbec@evilplan.org>
Signed-off-by: Christoph Hellwig <hch@lst.de>
diff --git a/fs/configfs/dir.c b/fs/configfs/dir.c
index 577cff24707b..39843fa7e11b 100644
--- a/fs/configfs/dir.c
+++ b/fs/configfs/dir.c
@@ -1777,6 +1777,16 @@ void configfs_unregister_group(struct config_group *group)
struct dentry *dentry = group->cg_item.ci_dentry;
struct dentry *parent = group->cg_item.ci_parent->ci_dentry;
+ mutex_lock(&subsys->su_mutex);
+ if (!group->cg_item.ci_parent->ci_group) {
+ /*
+ * The parent has already been unlinked and detached
+ * due to a rmdir.
+ */
+ goto unlink_group;
+ }
+ mutex_unlock(&subsys->su_mutex);
+
inode_lock_nested(d_inode(parent), I_MUTEX_PARENT);
spin_lock(&configfs_dirent_lock);
configfs_detach_prep(dentry, NULL);
@@ -1791,6 +1801,7 @@ void configfs_unregister_group(struct config_group *group)
dput(dentry);
mutex_lock(&subsys->su_mutex);
+unlink_group:
unlink_group(group);
mutex_unlock(&subsys->su_mutex);
}
]
|
| 330/2 |
2026/09/02 21:30 |
llm |
history-explorer |
0m
Model:
Tokens:
input: 24516
output: 32
thoughts: 90
Thoughts: Okay, here's my take on those thoughts, framed as an expert in the field:
**Digging into ConfigFS Memory Management**
Alright, this bug report is pointing out a potential race condition in `configfs_symlink()` related to freeing memory while a lock is dropped. Specifically, it's highlighting the importance of pinning the **root object**, which is the dynamically allocated parent object (like `ports/1` in this example), rather than just relying on the embedded default group. Makes sense β if we're not careful, that root object could disappear before we're done with it.
Now, my initial thought is, "Where *else* in `configfs` are we doing this sort of thing?" This situation screams for a comprehensive audit. I need to scan the codebase for all instances where we're pinning the root object explicitly, or where we're traversing the dentry tree to determine the root. We need to be consistent in our approach. Any deviation from a robust pinning strategy could introduce similar vulnerabilities. I want to see if this "pin the root" approach is already a standard practice, or if we have other similar mechanisms in place. If it's not consistent, we've got a systemic issue to tackle.
|
| 331/2 |
2026/09/02 21:30 |
tool |
grepper |
0m
Args: map[Expression:CONFIGFS_USET_DEFAULT PathPrefix:fs/configfs/]
Results: map[Output:fs/configfs/configfs_internal.h=30=struct configfs_dirent {
--
fs/configfs/configfs_internal.h-52-#define CONFIGFS_USET_DIR 0x0040
fs/configfs/configfs_internal.h:53:#define CONFIGFS_USET_DEFAULT 0x0080
fs/configfs/configfs_internal.h-54-#define CONFIGFS_USET_DROPPING 0x0100
--
fs/configfs/dir.c=502=static int configfs_detach_prep(struct configfs_dirent *parent_sd, struct dentry **wait)
--
fs/configfs/dir.c-518- continue;
fs/configfs/dir.c:519: if (sd->s_type & CONFIGFS_USET_DEFAULT) {
fs/configfs/dir.c-520- /* Abort if racing with mkdir() */
--
fs/configfs/dir.c=548=static void configfs_detach_rollback(struct configfs_dirent *parent_sd)
--
fs/configfs/dir.c-554- list_for_each_entry(sd, &parent_sd->s_children, s_sibling)
fs/configfs/dir.c:555: if (sd->s_type & CONFIGFS_USET_DEFAULT)
fs/configfs/dir.c-556- configfs_detach_rollback(sd);
--
fs/configfs/dir.c=664=static void detach_groups(struct dentry *dentry)
--
fs/configfs/dir.c-673- next = next_dirent(parent_sd, sd);
fs/configfs/dir.c:674: if (!(sd->s_type & CONFIGFS_USET_DEFAULT))
fs/configfs/dir.c-675- continue;
--
fs/configfs/dir.c=703=static int create_default_group(struct dentry *parent,
--
fs/configfs/dir.c-722- sd = child->d_fsdata;
fs/configfs/dir.c:723: sd->s_type |= CONFIGFS_USET_DEFAULT;
fs/configfs/dir.c-724- } else {
--
fs/configfs/dir.c=977=static void configfs_dump_one(struct configfs_dirent *sd, int level)
--
fs/configfs/dir.c-986- type_print(CONFIGFS_USET_DIR);
fs/configfs/dir.c:987: type_print(CONFIGFS_USET_DEFAULT);
fs/configfs/dir.c-988- type_print(CONFIGFS_USET_DROPPING);
--
fs/configfs/dir.c=1469=static int configfs_rmdir(struct inode *dir, struct dentry *dentry)
--
fs/configfs/dir.c-1479- sd = dentry->d_fsdata;
fs/configfs/dir.c:1480: if (sd->s_type & CONFIGFS_USET_DEFAULT)
fs/configfs/dir.c-1481- return -EPERM;
]
|
| 332/2 |
2026/09/02 21:30 |
llm |
history-explorer |
0m
Model:
Tokens:
input: 25730
output: 28
thoughts: 120
Thoughts: **My Analysis of `CONFIGFS_USET_DEFAULT` and Potential Race Conditions**
Okay, so I'm thinking about this `CONFIGFS_USET_DEFAULT` flag and how it's used within `configfs`. It seems pretty clear that it's designed to prevent the direct removal of default groups, which makes sense β we don't want someone accidentally deleting core functionality. The check in `configfs_rmdir` immediately stands out (`if (sd->s_type & CONFIGFS_USET_DEFAULT) return -EPERM;`). But then it's also involved in recursively detaching these defaults in `detach_groups()`. I'm also seeing it pop up in `configfs_detach_prep()` and `configfs_detach_rollback()` as well.
My immediate next thought is to look deeper. Given my experience with these kinds of file system implementations, I'm already mentally flagging this flag as potentially critical for concurrency and proper rollback operations, particularly when combined with the recursive calls. I need to be sure the semantics are right and we aren't introducing any race conditions by the multiple places the flag is checked and its interactions with other methods such as `inode_unlock`.
Therefore, I'm going to shift gears and drill down on `inode_unlock` calls within `configfs`. My concern is that there *might* be similar situations lurking where we could have race conditions. It's a key operation, so it warrants a thorough review in the context of `CONFIGFS_USET_DEFAULT` and potential concurrent operations. I need to establish if unlock calls are protected. I want to check for potential areas where concurrent access can cause issues.
|
| 333/2 |
2026/09/02 21:30 |
tool |
grepper |
0m
Args: map[Expression:inode_unlock PathPrefix:fs/configfs/]
Results: map[Output:fs/configfs/dir.c=576=static void detach_attrs(struct dentry *dentry)
--
fs/configfs/dir.c-611- drop_nlink(inode);
fs/configfs/dir.c:612: inode_unlock(inode);
fs/configfs/dir.c-613- dput(child);
--
fs/configfs/dir.c=664=static void detach_groups(struct dentry *dentry)
--
fs/configfs/dir.c-685-
fs/configfs/dir.c:686: inode_unlock(d_inode(child));
fs/configfs/dir.c-687-
--
fs/configfs/dir.c=857=static int configfs_attach_item(struct config_item *item,
--
fs/configfs/dir.c-875- dont_mount(dentry);
fs/configfs/dir.c:876: inode_unlock(d_inode(dentry));
fs/configfs/dir.c-877- d_delete(dentry);
--
fs/configfs/dir.c=891=static int configfs_attach_group(struct config_item *item,
--
fs/configfs/dir.c-920- configfs_adjust_dir_dirent_depth_after_populate(sd);
fs/configfs/dir.c:921: inode_unlock(d_inode(dentry));
fs/configfs/dir.c-922- if (ret)
--
fs/configfs/dir.c=1143=int configfs_depend_item(struct configfs_subsystem *subsys,
--
fs/configfs/dir.c-1175-out_unlock_fs:
fs/configfs/dir.c:1176: inode_unlock(d_inode(root));
fs/configfs/dir.c-1177-
--
fs/configfs/dir.c=1225=int configfs_depend_item_unlocked(struct configfs_subsystem *caller_subsys,
--
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=1469=static int configfs_rmdir(struct inode *dir, struct dentry *dentry)
--
fs/configfs/dir.c-1527- inode_lock(d_inode(wait));
fs/configfs/dir.c:1528: inode_unlock(d_inode(wait));
fs/configfs/dir.c-1529- dput(wait);
--
fs/configfs/dir.c=1593=static int configfs_dir_open(struct inode *inode, struct file *file)
--
fs/configfs/dir.c-1608- }
fs/configfs/dir.c:1609: inode_unlock(d_inode(dentry));
fs/configfs/dir.c-1610-
--
fs/configfs/dir.c=1614=static int configfs_dir_close(struct inode *inode, struct file *file)
--
fs/configfs/dir.c-1622- spin_unlock(&configfs_dirent_lock);
fs/configfs/dir.c:1623: inode_unlock(d_inode(dentry));
fs/configfs/dir.c-1624-
--
fs/configfs/dir.c=1751=int configfs_register_group(struct config_group *parent_group,
--
fs/configfs/dir.c-1776- spin_unlock(&configfs_dirent_lock);
fs/configfs/dir.c:1777: inode_unlock(d_inode(parent));
fs/configfs/dir.c-1778- put_fragment(frag);
--
fs/configfs/dir.c-1780-err_out:
fs/configfs/dir.c:1781: inode_unlock(d_inode(parent));
fs/configfs/dir.c-1782- mutex_lock(&subsys->su_mutex);
--
fs/configfs/dir.c=1796=void configfs_unregister_group(struct config_group *group)
--
fs/configfs/dir.c-1817- fsnotify_rmdir(d_inode(parent), dentry);
fs/configfs/dir.c:1818: inode_unlock(d_inode(parent));
fs/configfs/dir.c-1819-
--
fs/configfs/dir.c=1872=int configfs_register_subsystem(struct configfs_subsystem *subsys)
--
fs/configfs/dir.c-1920-
fs/configfs/dir.c:1921: inode_unlock(d_inode(root));
fs/configfs/dir.c-1922-
--
fs/configfs/dir.c=1934=void configfs_unregister_subsystem(struct configfs_subsystem *subsys)
--
fs/configfs/dir.c-1963- dont_mount(dentry);
fs/configfs/dir.c:1964: inode_unlock(d_inode(dentry));
fs/configfs/dir.c-1965-
--
fs/configfs/dir.c-1968-
fs/configfs/dir.c:1969: inode_unlock(d_inode(root));
fs/configfs/dir.c-1970-
--
fs/configfs/file.c=446=int configfs_create_file(struct config_item * item, const struct configfs_attribute * attr)
--
fs/configfs/file.c-455- CONFIGFS_ITEM_ATTR, parent_sd->s_frag);
fs/configfs/file.c:456: inode_unlock(d_inode(dir));
fs/configfs/file.c-457-
--
fs/configfs/file.c=467=int configfs_create_bin_file(struct config_item *item,
--
fs/configfs/file.c-477- CONFIGFS_ITEM_BIN_ATTR, parent_sd->s_frag);
fs/configfs/file.c:478: inode_unlock(dir->d_inode);
fs/configfs/file.c-479-
--
fs/configfs/symlink.c=135=int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
--
fs/configfs/symlink.c-183- */
fs/configfs/symlink.c:184: inode_unlock(dir);
fs/configfs/symlink.c-185- ret = get_target(symname, &target_item, dentry->d_sb);
]
|
| 334/2 |
2026/09/02 21:30 |
llm |
history-explorer |
0m
Model:
Tokens:
input: 28044
output: 33
thoughts: 105
Thoughts: **Investigating the ConfigFS Root Object Pinning Issue**
Okay, so this bug report is pointing out a potential memory safety issue in the ConfigFS code, specifically around `configfs_symlink()`. The core of the problem, as I understand it, is that we need to ensure the **root object** itself (that dynamically allocated parent, like "ports/1") is pinned, not just some embedded default group, to prevent it from being freed prematurely when the lock is dropped. That makes perfect sense; the root is the core of this structure and its lifetime needs to be tied to the symlink.
My immediate thought is, "Where else are we using `configfs_get_config_item` to pin an item?" This kind of pattern is something I'm very familiar with; it's classic race condition territory if we're not careful about managing the lifecycles of these objects. We need a holistic view to see if this is an isolated incident, or a systemic concern that requires broader attention.
The other question that immediately pops to mind is, do we have some already existing, perhaps, a helper function, something like a `configfs_get_root_item` function, that handles this specific need for pinning the root directly? That would be ideal. I'm going to scan the codebase to see if such a utility exists, because re-inventing the wheel is rarely the right answer, even if the wheel is a relatively simple operation like pinning an object. So, I need to look for that.
|
| 335/2 |
2026/09/02 21:30 |
tool |
grepper |
0m
Args: map[Expression:configfs_get_config_item PathPrefix:fs/configfs/]
Results: map[Output:fs/configfs/configfs_internal.h=113=static inline struct configfs_bin_attribute *to_bin_attr(struct dentry *dentry)
--
fs/configfs/configfs_internal.h-119-
fs/configfs/configfs_internal.h:120:static inline struct config_item *configfs_get_config_item(struct dentry *dentry)
fs/configfs/configfs_internal.h-121-{
--
fs/configfs/dir.c=1289=static struct dentry *configfs_mkdir(struct mnt_idmap *idmap, struct inode *dir,
--
fs/configfs/dir.c-1330- /* Get a working ref for the duration of this function */
fs/configfs/dir.c:1331: parent_item = configfs_get_config_item(dentry->d_parent);
fs/configfs/dir.c-1332- type = parent_item->ci_type;
--
fs/configfs/dir.c=1469=static int configfs_rmdir(struct inode *dir, struct dentry *dentry)
--
fs/configfs/dir.c-1483- /* Get a working ref until we have the child */
fs/configfs/dir.c:1484: parent_item = configfs_get_config_item(dentry->d_parent);
fs/configfs/dir.c-1485- subsys = to_config_group(parent_item)->cg_subsys;
--
fs/configfs/dir.c-1544- /* Get a working ref for the duration of this function */
fs/configfs/dir.c:1545: item = configfs_get_config_item(dentry);
fs/configfs/dir.c-1546-
--
fs/configfs/symlink.c=117=static int get_target(const char *symname, struct config_item **target,
--
fs/configfs/symlink.c-127- return -EPERM;
fs/configfs/symlink.c:128: *target = configfs_get_config_item(path.dentry);
fs/configfs/symlink.c-129- if (!*target)
--
fs/configfs/symlink.c=135=int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
--
fs/configfs/symlink.c-151-
fs/configfs/symlink.c:152: parent_item = configfs_get_config_item(dentry->d_parent);
fs/configfs/symlink.c-153- type = parent_item->ci_type;
--
fs/configfs/symlink.c=213=int configfs_unlink(struct inode *dir, struct dentry *dentry)
--
fs/configfs/symlink.c-225-
fs/configfs/symlink.c:226: parent_item = configfs_get_config_item(dentry->d_parent);
fs/configfs/symlink.c-227- type = parent_item->ci_type;
]
|
| 336/2 |
2026/09/02 21:30 |
llm |
history-explorer |
0m
Model:
Tokens:
input: 29321
output: 32
thoughts: 152
Thoughts: **Analyzing a Race Condition in ConfigFS**
Okay, so this bug report is pointing out a nasty race condition in `configfs`. It looks like `configfs_symlink` is dropping the lock at a critical moment β specifically, while it's creating a symbolic link. That's a problem because during that brief window, `configfs_rmdir` could swoop in and delete the parent directory! That's a textbook recipe for disaster.
The question now becomes: where else in the `configfs` code might this happen? Are there other points where we drop a lock and need to ensure the parent directory sticks around? I already know that `configfs_mkdir` doesn't release the lock, so we can cross that off the list. Same goes for `configfs_rmdir`; I know it calls `down_write_killable` on `su_rwsem` but doesn't actually call `inode_unlock`. So, it appears that `configfs_symlink` is the *only* function in this whole mess that's deliberately dropping the lock with `inode_unlock(dir)`.
Now, it's time to take a broader look. Let's see if there are any other examples in `configfs` where we're walking up the directory tree to find some root object. That will give us insight into other potential gotchas. I need to understand if similar fixes have been implemented elsewhere in this codebase. This one feels like a classic.
|
| 337/2 |
2026/09/02 21:30 |
tool |
grepper |
0m
Args: map[Expression:CONFIGFS_USET_DEFAULT PathPrefix:fs/configfs/]
Results: map[Output:fs/configfs/configfs_internal.h=30=struct configfs_dirent {
--
fs/configfs/configfs_internal.h-52-#define CONFIGFS_USET_DIR 0x0040
fs/configfs/configfs_internal.h:53:#define CONFIGFS_USET_DEFAULT 0x0080
fs/configfs/configfs_internal.h-54-#define CONFIGFS_USET_DROPPING 0x0100
--
fs/configfs/dir.c=502=static int configfs_detach_prep(struct configfs_dirent *parent_sd, struct dentry **wait)
--
fs/configfs/dir.c-518- continue;
fs/configfs/dir.c:519: if (sd->s_type & CONFIGFS_USET_DEFAULT) {
fs/configfs/dir.c-520- /* Abort if racing with mkdir() */
--
fs/configfs/dir.c=548=static void configfs_detach_rollback(struct configfs_dirent *parent_sd)
--
fs/configfs/dir.c-554- list_for_each_entry(sd, &parent_sd->s_children, s_sibling)
fs/configfs/dir.c:555: if (sd->s_type & CONFIGFS_USET_DEFAULT)
fs/configfs/dir.c-556- configfs_detach_rollback(sd);
--
fs/configfs/dir.c=664=static void detach_groups(struct dentry *dentry)
--
fs/configfs/dir.c-673- next = next_dirent(parent_sd, sd);
fs/configfs/dir.c:674: if (!(sd->s_type & CONFIGFS_USET_DEFAULT))
fs/configfs/dir.c-675- continue;
--
fs/configfs/dir.c=703=static int create_default_group(struct dentry *parent,
--
fs/configfs/dir.c-722- sd = child->d_fsdata;
fs/configfs/dir.c:723: sd->s_type |= CONFIGFS_USET_DEFAULT;
fs/configfs/dir.c-724- } else {
--
fs/configfs/dir.c=977=static void configfs_dump_one(struct configfs_dirent *sd, int level)
--
fs/configfs/dir.c-986- type_print(CONFIGFS_USET_DIR);
fs/configfs/dir.c:987: type_print(CONFIGFS_USET_DEFAULT);
fs/configfs/dir.c-988- type_print(CONFIGFS_USET_DROPPING);
--
fs/configfs/dir.c=1469=static int configfs_rmdir(struct inode *dir, struct dentry *dentry)
--
fs/configfs/dir.c-1479- sd = dentry->d_fsdata;
fs/configfs/dir.c:1480: if (sd->s_type & CONFIGFS_USET_DEFAULT)
fs/configfs/dir.c-1481- return -EPERM;
]
|
| 338/2 |
2026/09/02 21:30 |
llm |
history-explorer |
0m
Model:
Tokens:
input: 30652
output: 47
thoughts: 26
Thoughts: Okay, here's my summary, framed as my thought process:
**Investigating Potential Race Conditions in `fs/configfs`**
Right, let's get down to it. I'm going to dig into the `fs/configfs` directory. My aim is to see if there are any commits lurking in there that specifically mention both "race" and "symlink." This implies I'm looking for a specific vulnerability class or a particular type of bug. The combination of these keywords is important; a "race" might be common, and symlink vulnerabilities are well-understood. But the *combination* is what has me interested. I'm essentially hunting for instances where the use of symlinks within `configfs` might be creating a race condition. This could lead to a security hole or data corruption. Time to dive into the commit history. Let's see what we can uncover!
|
| 339/2 |
2026/09/02 21:30 |
tool |
git-log |
0m
Args: map[Count:20 MessageRegexps:[race symlink] PathPrefix:fs/configfs/ Since:10 years]
Results: map[Output:ba80aa909c99 configfs: Fix race between create_link and configfs_rmdir
]
|
| 340/2 |
2026/09/02 21:30 |
llm |
history-explorer |
0m
Model:
Tokens:
input: 30953
output: 47
thoughts: 107
Thoughts: **Analyzing a Subtle ConfigFS Race Condition**
Okay, so I've got a new race condition on my hands. It's related to the `configfs` file system, specifically the interplay between creating and removing symbolic links. I remember that commit `ba80aa909c99` addressed a similar race, but that one involved the symlink *target* being removed. This feels similar, but now the issue is with the *parent* directory of the symlink disappearing out from under it.
My first thought is always, let's see if there are any existing clues. I'll need to dig into the `fs/configfs` history. I'm going to search for commits that mention both "race" and "rmdir" in the commit message. I'm hoping I can find a pre-existing fix, some relevant discussion, or at least a similar issue that might shed some light on this new problem. Understanding the history here is key before I start throwing around potential solutions.
|
| 341/2 |
2026/09/02 21:31 |
tool |
git-log |
0m
Args: map[Count:20 MessageRegexps:[race rmdir] PathPrefix:fs/configfs/ Since:10 years]
Results: map[Output:ba80aa909c99 configfs: Fix race between create_link and configfs_rmdir
]
|
| 342/2 |
2026/09/02 21:31 |
llm |
history-explorer |
0m
Model:
Tokens:
input: 31359
output: 46
thoughts: 25
Thoughts: Here's my thought process, summarized as you requested:
**Investigating Potential Use-After-Free Vulnerabilities in ConfigFS**
Okay, time to get my hands dirty. I need to see if there's any recent activity, specifically within `fs/configfs`, that might relate to a "use-after-free" vulnerability. I'll need to use the tools I know to scan for commits mentioning that specific term. It's a key indicator, and finding even one mention is cause for deeper investigation. This could be anything from a fix, a report of a vulnerability, or even a failed attempt to address a related issue. I'll need to parse the results carefully, looking for context. This isn't just about finding the string; it's about understanding the *why* behind it. Let's see what the audit reveals. I have a feeling this could be interesting.
|
| 343/2 |
2026/09/02 21:31 |
tool |
git-log |
0m
Args: map[Count:20 MessageRegexps:[use-after-free] PathPrefix:fs/configfs/ Since:10 years]
Results: map[Output:14fbbc829772 configfs: fix a use-after-free in __configfs_open_file
f6122ed2a4f9 configfs: Fix use-after-free when accessing sd->s_dentry
35399f87e271 configfs: fix possible use-after-free in configfs_register_group
]
|
| 344/2 |
2026/09/02 21:31 |
llm |
history-explorer |
0m
Model:
Tokens:
input: 31720
output: 26
thoughts: 35
Thoughts: Okay, let's dive into this `f6122ed2a4f9` commit related to `configfs`.
**Analyzing a Potential Memory Safety Issue in configfs**
Alright, so the commit is targeting a "use-after-free" vulnerability within the `configfs` filesystem, specifically when it's accessing `sd->s_dentry`. My initial instinct is to assess the potential impact here. "Use-after-free" is a nasty class of bug. It means the code is trying to access a piece of memory after that memory has been freed, which can lead to crashes, security vulnerabilities (like arbitrary code execution), or data corruption.
The mention of `sd->s_dentry` makes me think we're dealing with a struct directory entry. This likely involves some lifecycle management of directories and files within the `configfs` structure. The vulnerability implies a condition where the directory entry (`s_dentry`) is being freed prematurely, and subsequent code is still trying to use the dangling pointer to that memory location. I need to understand the scenario where this could happen and where `configfs` is vulnerable.
I need to quickly pull up the surrounding code and start looking for any potential race conditions, incorrect reference counting, or improper memory management. I will need to look at how `sd` (presumably a superblock data structure) interacts with the directory entry and what operations might lead to a premature free. My experience tells me the fix will likely involve careful management of reference counters, guarding with locks, or introducing memory barriers to ensure the correct ordering of operations. I'll get to work and find the source of the problem.
|
| 345/2 |
2026/09/02 21:31 |
tool |
git-show |
0m
Args:
Results: map[Output:commit f6122ed2a4f9c9c1c073ddf6308d1b2ac10e0781
Author: Sahitya Tummala <quic_stummala@quicinc.com>
Date: Thu Jan 3 16:48:15 2019 +0530
configfs: Fix use-after-free when accessing sd->s_dentry
In the vfs_statx() context, during path lookup, the dentry gets
added to sd->s_dentry via configfs_attach_attr(). In the end,
vfs_statx() kills the dentry by calling path_put(), which invokes
configfs_d_iput(). Ideally, this dentry must be removed from
sd->s_dentry but it doesn't if the sd->s_count >= 3. As a result,
sd->s_dentry is holding reference to a stale dentry pointer whose
memory is already freed up. This results in use-after-free issue,
when this stale sd->s_dentry is accessed later in
configfs_readdir() path.
This issue can be easily reproduced, by running the LTP test case -
sh fs_racer_file_list.sh /config
(https://github.com/linux-test-project/ltp/blob/master/testcases/kernel/fs/racer/fs_racer_file_list.sh)
Fixes: 76ae281f6307 ('configfs: fix race between dentry put and lookup')
Signed-off-by: Sahitya Tummala <stummala@codeaurora.org>
Signed-off-by: Christoph Hellwig <hch@lst.de>
diff --git a/fs/configfs/dir.c b/fs/configfs/dir.c
index 5e7932d668ab..22203a3423a3 100644
--- a/fs/configfs/dir.c
+++ b/fs/configfs/dir.c
@@ -58,15 +58,13 @@ static void configfs_d_iput(struct dentry * dentry,
if (sd) {
/* Coordinate with configfs_readdir */
spin_lock(&configfs_dirent_lock);
- /* Coordinate with configfs_attach_attr where will increase
- * sd->s_count and update sd->s_dentry to new allocated one.
- * Only set sd->dentry to null when this dentry is the only
- * sd owner.
- * If not do so, configfs_d_iput may run just after
- * configfs_attach_attr and set sd->s_dentry to null
- * even it's still in use.
+ /*
+ * Set sd->s_dentry to null only when this dentry is the one
+ * that is going to be killed. Otherwise configfs_d_iput may
+ * run just after configfs_attach_attr and set sd->s_dentry to
+ * NULL even it's still in use.
*/
- if (atomic_read(&sd->s_count) <= 2)
+ if (sd->s_dentry == dentry)
sd->s_dentry = NULL;
spin_unlock(&configfs_dirent_lock);
]
|
| 346/2 |
2026/09/02 21:31 |
llm |
history-explorer |
0m
Model:
Tokens:
input: 32979
output: 25
thoughts: 35
Thoughts: Here's my take on that commit, seeing as I'm an expert in this area:
**Analyzing a Potential Kernel Vulnerability**
Okay, so we're diving into `14fbbc829772 configfs: fix a use-after-free in __configfs_open_file`. Right off the bat, "use-after-free" is a red flag. That's a classic memory corruption issue, and in the kernel, those are always nasty. This suggests a race condition or a lifecycle issue with a `configfs` object, specifically within the `__configfs_open_file` function. I need to figure out what's being freed prematurely and how it's still being accessed later. It sounds like a file handle in configfs, specifically. I'll need to dissect the code in that area closely and see how resources are being managed, which would likely be in refcounting. I want to look for potential missteps with `kfree()`, `kref_put()`, or other resource deallocation functions in relation to this file and its associated structures. Let's see if the commit message provides some high-level details about the issue.
|
| 347/2 |
2026/09/02 21:31 |
tool |
git-show |
0m
Args:
Results: map[Output:commit 14fbbc8297728e880070f7b077b3301a8c698ef9
Author: Daiyue Zhang <zhangdaiyue1@huawei.com>
Date: Mon Mar 1 14:10:53 2021 +0800
configfs: fix a use-after-free in __configfs_open_file
Commit b0841eefd969 ("configfs: provide exclusion between IO and removals")
uses ->frag_dead to mark the fragment state, thus no bothering with extra
refcount on config_item when opening a file. The configfs_get_config_item
was removed in __configfs_open_file, but not with config_item_put. So the
refcount on config_item will lost its balance, causing use-after-free
issues in some occasions like this:
Test:
1. Mount configfs on /config with read-only items:
drwxrwx--- 289 root root 0 2021-04-01 11:55 /config
drwxr-xr-x 2 root root 0 2021-04-01 11:54 /config/a
--w--w--w- 1 root root 4096 2021-04-01 11:53 /config/a/1.txt
......
2. Then run:
for file in /config
do
echo $file
grep -R 'key' $file
done
3. __configfs_open_file will be called in parallel, the first one
got called will do:
if (file->f_mode & FMODE_READ) {
if (!(inode->i_mode & S_IRUGO))
goto out_put_module;
config_item_put(buffer->item);
kref_put()
package_details_release()
kfree()
the other one will run into use-after-free issues like this:
BUG: KASAN: use-after-free in __configfs_open_file+0x1bc/0x3b0
Read of size 8 at addr fffffff155f02480 by task grep/13096
CPU: 0 PID: 13096 Comm: grep VIP: 00 Tainted: G W 4.14.116-kasan #1
TGID: 13096 Comm: grep
Call trace:
dump_stack+0x118/0x160
kasan_report+0x22c/0x294
__asan_load8+0x80/0x88
__configfs_open_file+0x1bc/0x3b0
configfs_open_file+0x28/0x34
do_dentry_open+0x2cc/0x5c0
vfs_open+0x80/0xe0
path_openat+0xd8c/0x2988
do_filp_open+0x1c4/0x2fc
do_sys_open+0x23c/0x404
SyS_openat+0x38/0x48
Allocated by task 2138:
kasan_kmalloc+0xe0/0x1ac
kmem_cache_alloc_trace+0x334/0x394
packages_make_item+0x4c/0x180
configfs_mkdir+0x358/0x740
vfs_mkdir2+0x1bc/0x2e8
SyS_mkdirat+0x154/0x23c
el0_svc_naked+0x34/0x38
Freed by task 13096:
kasan_slab_free+0xb8/0x194
kfree+0x13c/0x910
package_details_release+0x524/0x56c
kref_put+0xc4/0x104
config_item_put+0x24/0x34
__configfs_open_file+0x35c/0x3b0
configfs_open_file+0x28/0x34
do_dentry_open+0x2cc/0x5c0
vfs_open+0x80/0xe0
path_openat+0xd8c/0x2988
do_filp_open+0x1c4/0x2fc
do_sys_open+0x23c/0x404
SyS_openat+0x38/0x48
el0_svc_naked+0x34/0x38
To fix this issue, remove the config_item_put in
__configfs_open_file to balance the refcount of config_item.
Fixes: b0841eefd969 ("configfs: provide exclusion between IO and removals")
Signed-off-by: Daiyue Zhang <zhangdaiyue1@huawei.com>
Signed-off-by: Yi Chen <chenyi77@huawei.com>
Signed-off-by: Ge Qiu <qiuge@huawei.com>
Reviewed-by: Chao Yu <yuchao0@huawei.com>
Acked-by: Al Viro <viro@zeniv.linux.org.uk>
Signed-off-by: Christoph Hellwig <hch@lst.de>
diff --git a/fs/configfs/file.c b/fs/configfs/file.c
index 1f0270229d7b..da8351d1e455 100644
--- a/fs/configfs/file.c
+++ b/fs/configfs/file.c
@@ -378,7 +378,7 @@ static int __configfs_open_file(struct inode *inode, struct file *file, int type
attr = to_attr(dentry);
if (!attr)
- goto out_put_item;
+ goto out_free_buffer;
if (type & CONFIGFS_ITEM_BIN_ATTR) {
buffer->bin_attr = to_bin_attr(dentry);
@@ -391,7 +391,7 @@ static int __configfs_open_file(struct inode *inode, struct file *file, int type
/* Grab the module reference for this attribute if we have one */
error = -ENODEV;
if (!try_module_get(buffer->owner))
- goto out_put_item;
+ goto out_free_buffer;
error = -EACCES;
if (!buffer->item->ci_type)
@@ -435,8 +435,6 @@ static int __configfs_open_file(struct inode *inode, struct file *file, int type
out_put_module:
module_put(buffer->owner);
-out_put_item:
- config_item_put(buffer->item);
out_free_buffer:
up_read(&frag->frag_sem);
kfree(buffer);
]
|
| 348/2 |
2026/09/02 21:31 |
llm |
history-explorer |
0m
Model:
Tokens:
input: 35168
output: 46
thoughts: 25
Thoughts: Here's my thought process, summarized as you requested:
**Investigating Potential Pinning Issues in ConfigFS**
Okay, so I need to quickly determine if there have been any recent commits to `fs/configfs` that might relate to both pinning mechanisms and the "root" of the filesystem. It's a fairly specific query, but important to check. I'm assuming that there's a reason for this check, and the combination of those keywords raises a flag. This isn't a casual search. "Pin" and "root" together likely indicate something around access control, privilege escalation, or how objects are managed in the filesystem tree, perhaps related to security or resource limitations. Let's see if this reveals anything. Quickly, get to the commit history and find out!
|
| 349/2 |
2026/09/02 21:31 |
tool |
git-log |
0m
Args: map[Count:20 MessageRegexps:[pin root] PathPrefix:fs/configfs/ Since:10 years]
Results: map[Output:1a29e25799bc configfs: dentry refcount needs to be pinned only once
b1494e6bc47c configfs, securityfs: kill_litter_super() not needed
]
|
| 350/2 |
2026/09/02 21:31 |
llm |
history-explorer |
0m
Model:
Tokens:
input: 35469
output: 26
thoughts: 31
Thoughts: Okay, here's my interpretation of that thought process, assuming I'm already quite familiar with kernel internals and `configfs`:
**My Immediate Assessment of the `configfs` Dentry Refcount Issue**
Alright, so the commit titled `1a29e25799bc configfs: dentry refcount needs to be pinned only once`β¦ Hmm, that's interesting. Sounds like there's a subtle bug being addressed in `configfs` concerning how it manages dentry reference counts. My initial instinct tells me this is likely related to how the filesystem handles the lifetime of directory entries and their associated in-memory data structures. We're probably talking about a situation where a dentry's reference count isn't being managed atomically or maybe is being incremented multiple times, leading to a potential resource leak or, even worse, premature freeing of the dentry. I'm guessing this could manifest as some kind of obscure race condition or perhaps a double-free scenario if the refcount logic is flawed. I'll need to dig into the code associated with this commit to be sure. My Spidey senses are tingling that it may involve a subtle interaction with how various filesystem operations are being synchronized or how the `configfs` code is handling concurrent accesses to its dentries.
|
| 351/2 |
2026/09/02 21:31 |
tool |
git-show |
0m
Args:
Results: map[Output:commit 1a29e25799bcd7c3b2285f46e2c630b62d969b25
Author: Al Viro <viro@zeniv.linux.org.uk>
Date: Tue May 12 03:19:41 2026 -0400
configfs: dentry refcount needs to be pinned only once
currently we have a weird situation where
* symlinks and roots of subtrees created by mkdir are pinned once
* subdirectories of subtrees created by mkdir are pinned twice
* roots of subtrees created by register_{group,subsystem} are pinned
twice.
It makes things harder to follow for no good reason. The goal is to
encapsulate the unbalanced dget/dput into d_{make,discard}_persisitent()
and, preferably, allow a use of simple_recursive_removal() or analogue
thereof. So let's regularize that and pin things only once.
create_default_group() and configfs_register_subsystem() don't need to
keep their reference around on success - configfs_create_dir() has pinned
the sucker already. So we can drop the reference passed to
configfs_create_dir() (via configfs_attach_group(), etc.) both on success
and on failure. On the removal side we no longer have the double references,
so we need an explicit dget() to compensate.
Reviewed-by: Jan Kara <jack@suse.cz>
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
diff --git a/fs/configfs/dir.c b/fs/configfs/dir.c
index 87a0847d88b9..4836ffb3b70f 100644
--- a/fs/configfs/dir.c
+++ b/fs/configfs/dir.c
@@ -657,7 +657,7 @@ static void detach_groups(struct dentry *dentry)
if (!(sd->s_type & CONFIGFS_USET_DEFAULT))
continue;
- child = sd->s_dentry;
+ child = dget(sd->s_dentry);
spin_unlock(&configfs_dirent_lock);
inode_lock(d_inode(child));
@@ -708,8 +708,8 @@ static int create_default_group(struct config_group *parent_group,
} else {
BUG_ON(d_inode(child));
d_drop(child);
- dput(child);
}
+ dput(child);
}
return ret;
@@ -1781,7 +1781,7 @@ EXPORT_SYMBOL(configfs_register_group);
void configfs_unregister_group(struct config_group *group)
{
struct configfs_subsystem *subsys = group->cg_subsys;
- struct dentry *dentry = group->cg_item.ci_dentry;
+ struct dentry *dentry = dget(group->cg_item.ci_dentry);
struct dentry *parent = group->cg_item.ci_parent->ci_dentry;
struct configfs_dirent *sd = dentry->d_fsdata;
struct configfs_fragment *frag = sd->s_frag;
@@ -1896,12 +1896,12 @@ int configfs_register_subsystem(struct configfs_subsystem *subsys)
if (err) {
BUG_ON(d_inode(dentry));
d_drop(dentry);
- dput(dentry);
} else {
spin_lock(&configfs_dirent_lock);
configfs_dir_set_ready(dentry->d_fsdata);
spin_unlock(&configfs_dirent_lock);
}
+ dput(dentry);
}
inode_unlock(d_inode(root));
@@ -1920,7 +1920,7 @@ int configfs_register_subsystem(struct configfs_subsystem *subsys)
void configfs_unregister_subsystem(struct configfs_subsystem *subsys)
{
struct config_group *group = &subsys->su_group;
- struct dentry *dentry = group->cg_item.ci_dentry;
+ struct dentry *dentry = dget(group->cg_item.ci_dentry);
struct dentry *root = dentry->d_sb->s_root;
struct configfs_dirent *sd = dentry->d_fsdata;
struct configfs_fragment *frag = sd->s_frag;
]
|
| 352/2 |
2026/09/02 21:31 |
llm |
history-explorer |
0m
Model:
Tokens:
input: 37013
output: 27
thoughts: 87
Thoughts: **Investigating the ConfigFS Root Object Pinning**
Okay, so this bug report is claiming that `configfs_symlink()` needs to pin the root object to prevent memory corruption when the lock is dropped. Specifically, it's saying we should pin the parent object of the symlink (like, say, "ports/1"), not just the embedded default group. Makes sense β if the parent can be freed before the symlink is unlinked, we're in trouble.
My initial thought is to make sure this isn't an isolated incident. I need to understand if we are already doing this anywhere else in `fs/configfs`. It's crucial to understand the consistency of this particular implementation. I'm going to search for instances of "root object" within the `fs/configfs` directory. That keyword should help me identify any similar pinning strategies that might already be in place. I need to know if this change is a new pattern, or a fix to an existing one. Hopefully, that search will shed some light on the issue and uncover related code that might need a similar review.
|
| 353/2 |
2026/09/02 21:31 |
tool |
grepper |
0m
Error:
Args: map[Expression:root object PathPrefix:fs/configfs/]
Results:
|
| 354/2 |
2026/09/02 21:31 |
llm |
history-explorer |
0m
Model:
Tokens:
input: 37372
output: 26
thoughts: 15
Thoughts: Here's my attempt at summarizing the thought process, as if I were thinking it:
**Digging into configfs: The "root" Search**
Okay, so I need to explore something within the `fs/configfs` directory. My initial instinct is to use "root" as a search term. It's a common term, right? Given my expertise, I suspect that "root" might point to several potential areas of interest within the context of `configfs`. It could refer to the root directory itself, some form of privileged access, some fundamental aspect of the file system's structure, or perhaps even something completely unexpected. I'm going to start with this keyword, and see what the search results turn up. The results will dictate my next steps, and I'll adapt my search based on what I find.
|
| 355/2 |
2026/09/02 21:31 |
tool |
grepper |
0m
Args: map[Expression:root PathPrefix:fs/configfs/]
Results: map[Output:fs/configfs/configfs_internal.h=64=extern struct kmem_cache *configfs_dir_cachep;
fs/configfs/configfs_internal.h-65-
fs/configfs/configfs_internal.h:66:extern int configfs_is_root(struct config_item *item);
fs/configfs/configfs_internal.h-67-
--
fs/configfs/configfs_internal.h=88=extern const struct inode_operations configfs_dir_inode_operations;
fs/configfs/configfs_internal.h:89:extern const struct inode_operations configfs_root_inode_operations;
fs/configfs/configfs_internal.h-90-extern const struct inode_operations configfs_symlink_inode_operations;
--
fs/configfs/dir.c=35=DEFINE_SPINLOCK(configfs_dirent_lock);
--
fs/configfs/dir.c-39- * subsys->su_mutex is held.
fs/configfs/dir.c:40: * But parent configfs_subsystem is NULL when config_item is root.
fs/configfs/dir.c:41: * Use this mutex when config_item is root.
fs/configfs/dir.c-42- */
--
fs/configfs/dir.c=816=static void link_group(struct config_group *parent_group, struct config_group *group)
--
fs/configfs/dir.c-824- subsys = parent_group->cg_subsys;
fs/configfs/dir.c:825: else if (configfs_is_root(&parent_group->cg_item))
fs/configfs/dir.c-826- subsys = to_configfs_subsystem(group);
--
fs/configfs/dir.c=1122=static inline struct configfs_dirent *
fs/configfs/dir.c:1123:configfs_find_subsys_dentry(struct configfs_dirent *root_sd,
fs/configfs/dir.c-1124- struct config_item *subsys_item)
--
fs/configfs/dir.c-1129- spin_lock(&configfs_dirent_lock);
fs/configfs/dir.c:1130: list_for_each_entry(p, &root_sd->s_children, s_sibling) {
fs/configfs/dir.c-1131- if (p->s_type & CONFIGFS_DIR &&
--
fs/configfs/dir.c=1143=int configfs_depend_item(struct configfs_subsystem *subsys,
--
fs/configfs/dir.c-1148- struct config_item *s_item = &subsys->su_group.cg_item;
fs/configfs/dir.c:1149: struct dentry *root;
fs/configfs/dir.c-1150-
--
fs/configfs/dir.c-1152- * Pin the configfs filesystem. This means we can safely access
fs/configfs/dir.c:1153: * the root of the configfs filesystem.
fs/configfs/dir.c-1154- */
fs/configfs/dir.c:1155: root = configfs_pin_fs();
fs/configfs/dir.c:1156: if (IS_ERR(root))
fs/configfs/dir.c:1157: return PTR_ERR(root);
fs/configfs/dir.c-1158-
fs/configfs/dir.c-1159- /*
fs/configfs/dir.c:1160: * Next, lock the root directory. We're going to check that the
fs/configfs/dir.c-1161- * subsystem is really registered, and so we need to lock out
--
fs/configfs/dir.c-1163- */
fs/configfs/dir.c:1164: inode_lock(d_inode(root));
fs/configfs/dir.c-1165-
fs/configfs/dir.c:1166: subsys_sd = configfs_find_subsys_dentry(root->d_fsdata, s_item);
fs/configfs/dir.c-1167- if (!subsys_sd) {
--
fs/configfs/dir.c-1175-out_unlock_fs:
fs/configfs/dir.c:1176: inode_unlock(d_inode(root));
fs/configfs/dir.c-1177-
--
fs/configfs/dir.c=1214=EXPORT_SYMBOL(configfs_undepend_item);
--
fs/configfs/dir.c-1217- * caller_subsys is a caller's subsystem not target's. This is used to
fs/configfs/dir.c:1218: * determine if we should lock root and check subsys or not. When we are
fs/configfs/dir.c-1219- * in the same subsystem as our target there is no need to do locking as
--
fs/configfs/dir.c-1221- * as we are called from callback of one of his children and VFS holds a lock
fs/configfs/dir.c:1222: * on some inode. Otherwise we have to lock our root to ensure that target's
fs/configfs/dir.c-1223- * subsystem it is not unregistered during this function.
--
fs/configfs/dir.c=1225=int configfs_depend_item_unlocked(struct configfs_subsystem *caller_subsys,
--
fs/configfs/dir.c-1228- struct configfs_subsystem *target_subsys;
fs/configfs/dir.c:1229: struct config_group *root, *parent;
fs/configfs/dir.c-1230- struct configfs_dirent *subsys_sd;
--
fs/configfs/dir.c-1232-
fs/configfs/dir.c:1233: /* Disallow this function for configfs root */
fs/configfs/dir.c:1234: if (configfs_is_root(target))
fs/configfs/dir.c-1235- return -EINVAL;
--
fs/configfs/dir.c-1238- /*
fs/configfs/dir.c:1239: * This may happen when someone is trying to depend root
fs/configfs/dir.c-1240- * directory of some subsystem
fs/configfs/dir.c-1241- */
fs/configfs/dir.c:1242: if (configfs_is_root(&parent->cg_item)) {
fs/configfs/dir.c-1243- target_subsys = to_configfs_subsystem(to_config_group(target));
fs/configfs/dir.c:1244: root = parent;
fs/configfs/dir.c-1245- } else {
fs/configfs/dir.c-1246- target_subsys = parent->cg_subsys;
fs/configfs/dir.c:1247: /* Find a cofnigfs root as we may need it for locking */
fs/configfs/dir.c:1248: for (root = parent; !configfs_is_root(&root->cg_item);
fs/configfs/dir.c:1249: root = root->cg_item.ci_group)
fs/configfs/dir.c-1250- ;
--
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-1268- if (!subsys_sd)
fs/configfs/dir.c:1269: goto out_root_unlock;
fs/configfs/dir.c-1270- } else {
--
fs/configfs/dir.c-1277- if (target_subsys != caller_subsys)
fs/configfs/dir.c:1278:out_root_unlock:
fs/configfs/dir.c-1279- /*
--
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=1579=const struct inode_operations configfs_dir_inode_operations = {
--
fs/configfs/dir.c-1587-
fs/configfs/dir.c:1588:const struct inode_operations configfs_root_inode_operations = {
fs/configfs/dir.c-1589- .lookup = configfs_lookup,
--
fs/configfs/dir.c=1872=int configfs_register_subsystem(struct configfs_subsystem *subsys)
--
fs/configfs/dir.c-1876- struct dentry *dentry;
fs/configfs/dir.c:1877: struct dentry *root;
fs/configfs/dir.c-1878- struct configfs_dirent *sd;
--
fs/configfs/dir.c-1884-
fs/configfs/dir.c:1885: root = configfs_pin_fs();
fs/configfs/dir.c:1886: if (IS_ERR(root)) {
fs/configfs/dir.c-1887- put_fragment(frag);
fs/configfs/dir.c:1888: return PTR_ERR(root);
fs/configfs/dir.c-1889- }
--
fs/configfs/dir.c-1893-
fs/configfs/dir.c:1894: sd = root->d_fsdata;
fs/configfs/dir.c-1895- mutex_lock(&configfs_subsystem_mutex);
--
fs/configfs/dir.c-1898-
fs/configfs/dir.c:1899: inode_lock_nested(d_inode(root), I_MUTEX_PARENT);
fs/configfs/dir.c-1900-
fs/configfs/dir.c-1901- err = -ENOMEM;
fs/configfs/dir.c:1902: dentry = d_alloc_name(root, group->cg_item.ci_name);
fs/configfs/dir.c-1903- if (dentry) {
--
fs/configfs/dir.c-1920-
fs/configfs/dir.c:1921: inode_unlock(d_inode(root));
fs/configfs/dir.c-1922-
--
fs/configfs/dir.c=1934=void configfs_unregister_subsystem(struct configfs_subsystem *subsys)
--
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/dir.c-1939- struct configfs_dirent *sd = dentry->d_fsdata;
--
fs/configfs/dir.c-1941-
fs/configfs/dir.c:1942: if (dentry->d_parent != root) {
fs/configfs/dir.c-1943- pr_err("Tried to unregister non-subsystem!\n");
--
fs/configfs/dir.c-1950-
fs/configfs/dir.c:1951: inode_lock_nested(d_inode(root),
fs/configfs/dir.c-1952- I_MUTEX_PARENT);
--
fs/configfs/dir.c-1966- d_drop(dentry);
fs/configfs/dir.c:1967: fsnotify_rmdir(d_inode(root), dentry);
fs/configfs/dir.c-1968-
fs/configfs/dir.c:1969: inode_unlock(d_inode(root));
fs/configfs/dir.c-1970-
--
fs/configfs/mount.c=37=static const struct super_operations configfs_ops = {
--
fs/configfs/mount.c-42-
fs/configfs/mount.c:43:static struct config_group configfs_root_group = {
fs/configfs/mount.c-44- .cg_item = {
fs/configfs/mount.c:45: .ci_namebuf = "root",
fs/configfs/mount.c:46: .ci_name = configfs_root_group.cg_item.ci_namebuf,
fs/configfs/mount.c-47- },
--
fs/configfs/mount.c-49-
fs/configfs/mount.c:50:int configfs_is_root(struct config_item *item)
fs/configfs/mount.c-51-{
fs/configfs/mount.c:52: return item == &configfs_root_group.cg_item;
fs/configfs/mount.c-53-}
fs/configfs/mount.c-54-
fs/configfs/mount.c:55:static struct configfs_dirent configfs_root = {
fs/configfs/mount.c:56: .s_sibling = LIST_HEAD_INIT(configfs_root.s_sibling),
fs/configfs/mount.c:57: .s_children = LIST_HEAD_INIT(configfs_root.s_children),
fs/configfs/mount.c:58: .s_element = &configfs_root_group.cg_item,
fs/configfs/mount.c-59- .s_type = CONFIGFS_ROOT,
--
fs/configfs/mount.c=63=static int configfs_fill_super(struct super_block *sb, struct fs_context *fc)
--
fs/configfs/mount.c-65- struct inode *inode;
fs/configfs/mount.c:66: struct dentry *root;
fs/configfs/mount.c-67-
--
fs/configfs/mount.c-74- inode = configfs_new_inode(S_IFDIR | S_IRWXU | S_IRUGO | S_IXUGO,
fs/configfs/mount.c:75: &configfs_root, sb);
fs/configfs/mount.c-76- if (inode) {
fs/configfs/mount.c:77: inode->i_op = &configfs_root_inode_operations;
fs/configfs/mount.c-78- inode->i_fop = &configfs_dir_operations;
--
fs/configfs/mount.c-81- } else {
fs/configfs/mount.c:82: pr_debug("could not get root inode\n");
fs/configfs/mount.c-83- return -ENOMEM;
--
fs/configfs/mount.c-85-
fs/configfs/mount.c:86: root = d_make_root(inode);
fs/configfs/mount.c:87: if (!root) {
fs/configfs/mount.c:88: pr_debug("%s: could not get root dentry!\n",__func__);
fs/configfs/mount.c-89- return -ENOMEM;
fs/configfs/mount.c-90- }
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/mount.c:94: sb->s_root = root;
fs/configfs/mount.c-95- set_default_d_op(sb, &configfs_dentry_ops); /* the rest get that */
--
fs/configfs/mount.c=123=struct dentry *configfs_pin_fs(void)
--
fs/configfs/mount.c-126- &configfs_mnt_count);
fs/configfs/mount.c:127: return err ? ERR_PTR(err) : configfs_mount->mnt_root;
fs/configfs/mount.c-128-}
--
fs/configfs/symlink.c=22=static int item_depth(struct config_item * item)
--
fs/configfs/symlink.c-25- int depth = 0;
fs/configfs/symlink.c:26: do { depth++; } while ((p = p->ci_parent) && !configfs_is_root(p));
fs/configfs/symlink.c-27- return depth;
--
fs/configfs/symlink.c=30=static int item_path_length(struct config_item * item)
--
fs/configfs/symlink.c-36- p = p->ci_parent;
fs/configfs/symlink.c:37: } while (p && !configfs_is_root(p));
fs/configfs/symlink.c-38- return length;
--
fs/configfs/symlink.c=41=static void fill_item_path(struct config_item * item, char * buffer, int length)
--
fs/configfs/symlink.c-45- --length;
fs/configfs/symlink.c:46: for (p = item; p && !configfs_is_root(p); p = p->ci_parent) {
fs/configfs/symlink.c-47- int cur = strlen(config_item_name(p));
]
|
| 356/2 |
2026/09/02 21:31 |
llm |
history-explorer |
0m
Model:
Tokens:
input: 42377
output: 38
thoughts: 887
Thoughts: **Analyzing the ConfigFS Symlink Bug and Mitigation Strategies**
Okay, so the bug report highlights a critical race condition in `configfs_symlink` related to how the root object is pinned during a symlink operation. The core issue is that releasing the lock while traversing the dentry tree opens a window where the memory could be freed, leading to a potential use-after-free situation. The suggested fix is clear: we need to find the root dentry that isn't a default group and pin *that* object to prevent premature freeing. The provided code snippet from the bug report nails the specific implementation detail: walk up the dentry tree until you hit a non-default group and then grab the item.
My focus now shifts to contextualizing this fix. I need to understand what idioms and patterns are prevalent in `configfs`, and how similar issues have been addressed in the past. Time to dive into the revision history of `fs/configfs/symlink.c` and `fs/configfs/dir.c`.
First, I see that deadlock fixes introduced an unlock/relock scenario, which introduced the current race.
Second, the history shows that the `configfs` subsystem has dealt with concurrent removals quite a bit. Several fixes stand out:
* The `configfs: fix a race between create_link and configfs_rmdir` fix (ba80aa909c99) addresses a race condition by checking `CONFIGFS_USET_DROPPING` *before* acquiring a reference to the target item.
* The `configfs: fix use-after-free when accessing sd->s_dentry` fix ensures that `sd->s_dentry` is only set to NULL when the specific dentry is being killed.
* The `configfs: fix registered group removal` fix added a check to verify if the parent was already detached due to `rmdir`.
So, the common `configfs` pattern for dealing with concurrent `rmdir` operations appears to be:
1. `configfs_rmdir` sets a flag, like `CONFIGFS_USET_DROPPING` or `frag->frag_dead = true`, indicating that an item is being removed.
2. Other concurrent operations check these flags *before* dereferencing any of the objects that may be affected by the `rmdir`. `create_link` uses this pattern.
3. The report states that `configfs_symlink` needs to be updated to check `sd->s_frag->frag_dead` after re-acquiring the lock. I see that this check is *not* currently present, so the bug report is correct: we need to add that check.
This suggests that the solution should leverage a similar mechanism, ensuring that the pinned root object isn't freed prematurely during the symlink operation. I need to be sure that I am following the pattern. I'll need to double-check that `configfs_symlink` is using the `frag_dead` flag as intended. This seems to be the crucial piece.
|
| 357/2 |
2026/09/02 21:31 |
tool |
read-file |
0m
Args: map[File:fs/configfs/symlink.c FirstLine:130 LineCount:100]
Results: map[Contents: 130: return -ENOENT;
131: return 0;
132: }
133:
134:
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: }
212:
213: int configfs_unlink(struct inode *dir, struct dentry *dentry)
214: {
215: struct configfs_dirent *sd = dentry->d_fsdata, *target_sd;
216: struct config_item *parent_item;
217: const struct config_item_type *type;
218: int ret;
219:
220: ret = -EPERM; /* What lack-of-symlink returns */
221: if (!(sd->s_type & CONFIGFS_ITEM_LINK))
222: goto out;
223:
224: target_sd = sd->s_element;
225:
226: parent_item = configfs_get_config_item(dentry->d_parent);
227: type = parent_item->ci_type;
228:
229: spin_lock(&configfs_dirent_lock);
]
|
| 358/2 |
2026/09/02 21:31 |
llm |
history-explorer |
0m
Model:
Tokens:
input: 45534
output: 48
thoughts: 290
Thoughts: **My Analysis of the ConfigFS Memory Pinning Issue**
Okay, here's what I'm thinking as I dive into this bug report. The core issue, as I understand it, is that `configfs_symlink()` needs to correctly manage memory pinning to prevent a race condition where the memory associated with a dynamically allocated parent object (like "ports/1") gets freed prematurely while the lock is dropped. The report explicitly states that we need to pin the *root object* rather than just the default group.
My immediate instinct is to see if we're already doing something similar elsewhere in the code. I'm wondering if there's an existing pattern or utility function, perhaps something like a hypothetical `configfs_get_root_item`, that handles this "root object" pinning. So, I ran a quick search for `configfs_get_config_item` to see where it's used. I found it's used in several functions: `configfs_mkdir`, `configfs_rmdir`, `configfs_symlink`, `configfs_unlink`, and `get_target`. However, a quick scan of those usages suggests that none of them explicitly traverse the directory tree upwards to identify and pin the root object in the way we require here.
Since I don't see any obvious prior art, I figured I'd investigate for related issues. A natural follow-up is to examine any recent commits that might touch on "pin" and "root object", or related terms, within the ConfigFS code. A search for "root object" turned up nothing, and "root" itself led me to `configfs_is_root`, which identifies the global root group (`configfs_root_group`). That isn't what we want though; the bug report is specifically talking about an object higher up in the hierarchy than the global root that we need to preserve.
So, I'll broaden my search. Now, I'm going to look for any references to "CONFIGFS_USET_DEFAULT" in conjunction with pinning, as this seems to be the key to identifying our "root object". That should give me a better view of relevant prior activity and potential solutions that might already be in place or have been discussed.
|
| 359/2 |
2026/09/02 21:32 |
tool |
git-log |
0m
Args: map[Count:20 MessageRegexps:[CONFIGFS_USET_DEFAULT] PathPrefix:fs/configfs/ Since:10 years]
Results:
|
| 360/2 |
2026/09/02 21:32 |
llm |
history-explorer |
0m
Model:
Tokens:
input: 46357
output: 43
thoughts: 15
Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience:
**Investigating "Default Group" Commit History**
Okay, so I need to figure out the history of how "default group" is handled in this codebase. My immediate thought is to jump into the commit logs and see what's been said about it. It's the most efficient way to trace back any changes, discussions, or potential bugs related to the concept. Searching for that specific phrase is a pretty direct approach; it's specific enough to hopefully filter out irrelevant results, but not so specific that I miss broader context. Let's see what the commits tell me about its evolution and any potential gotchas. I'm expecting to find insights into initial implementations, refactoring efforts, or any discussions around edge cases related to the "default group" configuration. This should provide the necessary context to troubleshoot and/or to refine current group configurations.
|
| 361/2 |
2026/09/02 21:32 |
tool |
git-log |
0m
Args: map[Count:20 MessageRegexps:[default group] PathPrefix:fs/configfs/ Since:10 years]
Results:
|
| 362/2 |
2026/09/02 21:32 |
llm |
history-explorer |
0m
Model:
Tokens:
input: 46621
output: 42
thoughts: 41
Thoughts: Okay, here's my interpretation of that thought process, as if it were my own:
**Investigating Case Sensitivity in Git Log with Regex**
Hmm, hold on a second. I'm using `git log` and these `MessageRegexps`, and something's nagging at me about case sensitivity. The prompt specifically states that the search needs to be case-insensitive, and I need to make sure I'm adhering to the parameters. I wonder if there's a default behavior I'm missing with the tools I'm using, and if that default might be causing me problems. Perhaps, `git-log` or the underlying regular expression engine could be sensitive to case, even if I *think* I'm specifying a case-insensitive search. I should check that. Just in case, let's try a simple "default" string in this context and see what happens. That should be a good sanity check to isolate the issue.
|
| 363/2 |
2026/09/02 21:32 |
tool |
git-log |
0m
Args: map[Count:20 MessageRegexps:[default] PathPrefix:fs/configfs/ Since:10 years]
Results: map[Output:d53ac61b6120 create_default_group(): pass parent's dentry instead of config_group
1a29e25799bc configfs: dentry refcount needs to be pinned only once
bf4afc53b77a Convert 'alloc_obj' family to use the new default GFP_KERNEL argument
05fb0e666495 new helper: set_default_d_op()
4496e1c1354b crash_dump, nvme: select CONFIGFS_FS as built-in
0e6a35b93745 fs/configfs: Add a callback to determine attribute visibility
e2f238f7d5a1 configfs: calculate the depth of parent item
35399f87e271 configfs: fix possible use-after-free in configfs_register_group
cc57c07343bd configfs: fix registered group removal
aa293583f0fe configfs: make ci_type field, some pointers and function arguments const
]
|
| 364/2 |
2026/09/02 21:32 |
llm |
history-explorer |
0m
Model:
Tokens:
input: 47170
output: 26
thoughts: 34
Thoughts: Okay, here's my take on those thoughts, framed as an expert in my field.
**Diving into that `create_default_group()` Function Call**
Alright, so the first thing I'm zeroing in on is that `d53ac61b6120` commit, specifically the change to `create_default_group()`. The core of the matter seems to be passing the parent's dentry instead of the `config_group` itself. Itβs got my attention because this is probably about how we're traversing the filesystem hierarchy and managing context. The devil is often in these details. I need to understand why this change was made. Was it a subtle performance tweak, perhaps to avoid a dereference or copy? Or was it to make a significant change to how these groups are managed in their relationships with the file system? I'm betting this is a targeted fix or optimization. I need to dig into the surrounding code to determine why this was necessary. I suspect it's about addressing a subtle race condition, simplifying a locking scheme, or improving the efficiency of the lookup process. My next step is definitely to go over the commits and see the reasoning behind this design choice.
|
| 365/2 |
2026/09/02 21:32 |
tool |
git-show |
0m
Args:
Results: map[Output:commit d53ac61b6120fa596d758ff6e22b5dcb6db21ce8
Author: Al Viro <viro@zeniv.linux.org.uk>
Date: Tue May 26 19:23:56 2026 -0400
create_default_group(): pass parent's dentry instead of config_group
the only way parent_group is used there...
Reviewed-by: Jan Kara <jack@suse.cz>
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
diff --git a/fs/configfs/dir.c b/fs/configfs/dir.c
index 8fc05fe69992..4d82375b0cfd 100644
--- a/fs/configfs/dir.c
+++ b/fs/configfs/dir.c
@@ -701,14 +701,14 @@ static void detach_groups(struct dentry *dentry)
* We could, perhaps, tweak our parent's ->mkdir for a minute and
* try using vfs_mkdir. Just a thought.
*/
-static int create_default_group(struct config_group *parent_group,
+static int create_default_group(struct dentry *parent,
struct config_group *group,
struct configfs_fragment *frag)
{
int ret;
struct configfs_dirent *sd;
/* We trust the caller holds a reference to parent */
- struct dentry *child, *parent = parent_group->cg_item.ci_dentry;
+ struct dentry *child;
if (!group->cg_item.ci_name)
group->cg_item.ci_name = group->cg_item.ci_namebuf;
@@ -735,10 +735,11 @@ static int create_default_group(struct config_group *parent_group,
static int populate_groups(struct config_group *group,
struct configfs_fragment *frag)
{
+ struct dentry *parent = group->cg_item.ci_dentry;
struct config_group *new_group;
list_for_each_entry(new_group, &group->default_groups, group_entry) {
- int ret = create_default_group(group, new_group, frag);
+ int ret = create_default_group(parent, new_group, frag);
if (ret)
return ret;
}
@@ -1767,7 +1768,7 @@ int configfs_register_group(struct config_group *parent_group,
parent = parent_group->cg_item.ci_dentry;
inode_lock_nested(d_inode(parent), I_MUTEX_PARENT);
- ret = create_default_group(parent_group, group, frag);
+ ret = create_default_group(parent, group, frag);
if (ret)
goto err_out;
]
|
| 366/2 |
2026/09/02 21:32 |
llm |
history-explorer |
0m
Model:
Tokens:
input: 48277
output: 37
thoughts: 614
Thoughts: **My Analysis of the configfs UAF Bug**
Okay, so this bug report has me thinking about how to prevent a use-after-free in `configfs_symlink()`. The core issue, as I understand it, is that we're dropping the lock before ensuring that memory (specifically the "root object" β the dynamically allocated parent like "ports/1") is still valid. The fix proposed by the report makes sense: pin the root object to prevent it from being freed. But before I dive into writing a fix, I need to understand the idioms, locking rules, and general patterns that have worked for similar bugs in the past. Essentially, I need to see what we've done *before* in similar situations.
Let's see, previous similar bugs... I need to review past fixes in `configfs` that deal with race conditions and UAFs to inform my approach. Let me see.
* `ba80aa909c99` - Ah, this one fixed a race between `create_link` and `configfs_rmdir`. The fix was a good one: check `CONFIGFS_USET_DROPPING` on the target's `configfs_dirent` *while* holding `configfs_dirent_lock` before grabbing a reference to the target item. Solid. Seems to imply *careful locking* around critical shared state.
* `351e5d869e5a` - Interesting, this commit actually introduced `inode_unlock(dir)` and `inode_lock(dir)` in `configfs_symlink()` to deal with deadlocks during target resolution. It's the *source* of the current race, though. So, it underscores that locking order is *crucial* in this function. This has added a lot of complexity, and I can now see how our bug could arise.
* `cc57c07343bd` - This fixed a crash during registered group removal. It added a check inside `subsys->su_mutex` to check if the parent was already unlinked and detached via `rmdir`. Another example of checking state within the appropriate lock. Useful.
* `f6122ed2a4f9` - Ah, another UAF fix. Ensuring that `sd->s_dentry` is only set to NULL when the specific dentry is being killed and checking `sd->s_dentry == dentry` under `configfs_dirent_lock`. More fine-grained locking and checking before accessing potentially freed data.
* `14fbbc829772` - This fixed a UAF by removing an unbalanced `config_item_put`. Clean-up and correct reference counting are the key here.
So, the pattern I'm seeing involves these strategies: *careful locking*, *state checks*, and *proper reference counting* to prevent UAFs and race conditions. Okay, now comes the question of precedence for the proposed fix: the bug report is suggesting we walk up the dentry tree to find the non-default "root object" to pin. Is there a precedent in `configfs` for this? Let's check `configfs_depend_item_unlocked` in `fs/configfs/dir.c`. This could very well guide my approach!
|
| 367/2 |
2026/09/02 21:32 |
tool |
read-file |
0m
Args: map[File:fs/configfs/dir.c FirstLine:1240 LineCount:20]
Results: map[Contents:1240: * directory of some subsystem
1241: */
1242: if (configfs_is_root(&parent->cg_item)) {
1243: target_subsys = to_configfs_subsystem(to_config_group(target));
1244: root = parent;
1245: } else {
1246: target_subsys = parent->cg_subsys;
1247: /* Find a cofnigfs root as we may need it for locking */
1248: for (root = parent; !configfs_is_root(&root->cg_item);
1249: root = root->cg_item.ci_group)
1250: ;
1251: }
1252:
1253: if (target_subsys != caller_subsys) {
1254: /*
1255: * We are in other configfs subsystem, so we have to do
1256: * additional locking to prevent other subsystem from being
1257: * unregistered
1258: */
1259: inode_lock(d_inode(root->cg_item.ci_dentry));
]
|
| 368/2 |
2026/09/02 21:32 |
llm |
history-explorer |
0m
Model:
Tokens:
input: 49970
output: 28
thoughts: 249
Thoughts: **My Analysis of the configfs Code**
Okay, so I'm looking at this `configfs_depend_item_unlocked` function, and the first thing that jumps out is this loop:
```c
/* Find a cofnigfs root as we may need it for locking */
for (root = parent; !configfs_is_root(&root->cg_item);
root = root->cg_item.ci_group)
;
```
Right, classic! It's a traversal, walking up the `ci_group` chain until it finds the `configfs` root. Standard practice, really. Finding a stable point in the hierarchy to anchor a lock or pinning operation. I see this all the time. It's a solid, predictable pattern for managing object dependencies and lifecycle. Nothing particularly ground-breaking, but important to understand.
Then I see the `CONFIGFS_USET_DEFAULT` flag being used in `configfs_rmdir` and `detach_groups`, that makes perfect sense. They're using it to distinguish between items that were dynamically created versus the built-in, default groups. Prevents you from deleting the core stuff. Makes sense to be able to have default groups that aren't deleted.
Now, that bug report...it's highlighting the use of `sd->s_frag->frag_dead` *after* re-acquiring a lock. This raises a flag. Is `frag_dead` used *anywhere else* to check for an object's removal? Is this a standard pattern, or is the use of `frag_dead` unique? I need to search the codebase to see if other places are using this member. It's time to dig deeper and see the context.
|
| 369/2 |
2026/09/02 21:32 |
tool |
grepper |
0m
Args: map[Expression:frag_dead PathPrefix:fs/configfs/]
Results: map[Output:fs/configfs/configfs_internal.h=21=struct configfs_fragment {
--
fs/configfs/configfs_internal.h-23- struct rw_semaphore frag_sem;
fs/configfs/configfs_internal.h:24: bool frag_dead;
fs/configfs/configfs_internal.h-25-};
--
fs/configfs/dir.c=158=static struct configfs_fragment *new_fragment(void)
--
fs/configfs/dir.c-165- init_rwsem(&p->frag_sem);
fs/configfs/dir.c:166: p->frag_dead = false;
fs/configfs/dir.c-167- }
--
fs/configfs/dir.c=1469=static int configfs_rmdir(struct inode *dir, struct dentry *dentry)
--
fs/configfs/dir.c-1540- }
fs/configfs/dir.c:1541: frag->frag_dead = true;
fs/configfs/dir.c-1542- up_write(&frag->frag_sem);
--
fs/configfs/dir.c=1796=void configfs_unregister_group(struct config_group *group)
--
fs/configfs/dir.c-1804- down_write(&frag->frag_sem);
fs/configfs/dir.c:1805: frag->frag_dead = true;
fs/configfs/dir.c-1806- up_write(&frag->frag_sem);
--
fs/configfs/dir.c=1934=void configfs_unregister_subsystem(struct configfs_subsystem *subsys)
--
fs/configfs/dir.c-1947- down_write(&frag->frag_sem);
fs/configfs/dir.c:1948: frag->frag_dead = true;
fs/configfs/dir.c-1949- up_write(&frag->frag_sem);
--
fs/configfs/file.c=56=static int fill_read_buffer(struct file *file, struct configfs_buffer *buffer)
--
fs/configfs/file.c-66- down_read(&frag->frag_sem);
fs/configfs/file.c:67: if (!frag->frag_dead)
fs/configfs/file.c-68- count = buffer->attr->show(buffer->item, buffer->page);
--
fs/configfs/file.c=106=static ssize_t configfs_bin_read_iter(struct kiocb *iocb, struct iov_iter *to)
--
fs/configfs/file.c-125- down_read(&frag->frag_sem);
fs/configfs/file.c:126: if (!frag->frag_dead)
fs/configfs/file.c-127- len = buffer->bin_attr->read(buffer->item, NULL, 0);
--
fs/configfs/file.c-150- down_read(&frag->frag_sem);
fs/configfs/file.c:151: if (!frag->frag_dead)
fs/configfs/file.c-152- len = buffer->bin_attr->read(buffer->item,
--
fs/configfs/file.c=200=flush_write_buffer(struct file *file, struct configfs_buffer *buffer, size_t count)
--
fs/configfs/file.c-205- down_read(&frag->frag_sem);
fs/configfs/file.c:206: if (!frag->frag_dead)
fs/configfs/file.c-207- res = buffer->attr->store(buffer->item, buffer->page, count);
--
fs/configfs/file.c=290=static int __configfs_open_file(struct inode *inode, struct file *file, int type)
--
fs/configfs/file.c-304- down_read(&frag->frag_sem);
fs/configfs/file.c:305: if (unlikely(frag->frag_dead))
fs/configfs/file.c-306- goto out_free_buffer;
--
fs/configfs/file.c=400=static int configfs_release_bin_file(struct inode *inode, struct file *file)
--
fs/configfs/file.c-407- down_read(&frag->frag_sem);
fs/configfs/file.c:408: if (!frag->frag_dead) {
fs/configfs/file.c-409- /* result of ->release() is ignored */
]
|
| 370/2 |
2026/09/02 21:32 |
llm |
history-explorer |
0m
Model:
Tokens:
input: 51823
output: 26
thoughts: 108
Thoughts: **My Analysis of `frag_dead` in `configfs/file.c`**
Okay, so I'm looking at `frag_dead` within the `configfs` framework, and it's pretty clear that it's a vital flag within `configfs/file.c`. From what I can see, its primary purpose is to act as a safeguard. It's used extensively to determine if a particular item has already been removed from the system before any of its associated methods are called. We're talking about crucial methods here like `show`, `store`, `read`, and `release` - all of which could lead to nasty issues if they were run on something that no longer exists!
The code indicates that `frag_dead` gets set to `true` under very specific circumstances: inside `configfs_rmdir`, when unregistering a group using `configfs_unregister_group`, and similarly during subsystem unregistration with `configfs_unregister_subsystem`. So, this flag is directly tied to the unregistration or removal process of configfs items.
The commit history suggests that the introduction of `frag_dead` was a response to synchronization issues. The change, `b0841eefd969 ("configfs: provide exclusion between IO and removals")`, is the key. This tells me that the flag's introduction was to provide a safe exclusion mechanism, preventing concurrent I/O operations and removals from causing data corruption or other race conditions. Makes perfect sense, considering the nature of the operations and the potential for these things to happen if not carefully managed. It's a fundamental defensive programming technique.
|
| 371/2 |
2026/09/02 21:32 |
tool |
git-show |
0m
Args:
Results: map[Output:commit b0841eefd9693827afb9888235e26ddd098f9cef
Author: Al Viro <viro@zeniv.linux.org.uk>
Date: Sat Aug 31 09:43:43 2019 +0200
configfs: provide exclusion between IO and removals
Make sure that attribute methods are not called after the item
has been removed from the tree. To do so, we
* at the point of no return in removals, grab ->frag_sem
exclusive and mark the fragment dead.
* call the methods of attributes with ->frag_sem taken
shared and only after having verified that the fragment is still
alive.
The main benefit is for method instances - they are
guaranteed that the objects they are accessing *and* all ancestors
are still there. Another win is that we don't need to bother
with extra refcount on config_item when opening a file -
the item will be alive for as long as it stays in the tree, and
we won't touch it/attributes/any associated data after it's
been removed from the tree.
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
Signed-off-by: Christoph Hellwig <hch@lst.de>
diff --git a/fs/configfs/dir.c b/fs/configfs/dir.c
index 1df65c2440da..79fc25aaa8cd 100644
--- a/fs/configfs/dir.c
+++ b/fs/configfs/dir.c
@@ -1461,6 +1461,7 @@ static int configfs_rmdir(struct inode *dir, struct dentry *dentry)
struct config_item *item;
struct configfs_subsystem *subsys;
struct configfs_dirent *sd;
+ struct configfs_fragment *frag;
struct module *subsys_owner = NULL, *dead_item_owner = NULL;
int ret;
@@ -1518,6 +1519,16 @@ static int configfs_rmdir(struct inode *dir, struct dentry *dentry)
}
} while (ret == -EAGAIN);
+ frag = sd->s_frag;
+ if (down_write_killable(&frag->frag_sem)) {
+ spin_lock(&configfs_dirent_lock);
+ configfs_detach_rollback(dentry);
+ spin_unlock(&configfs_dirent_lock);
+ return -EINTR;
+ }
+ frag->frag_dead = true;
+ up_write(&frag->frag_sem);
+
/* Get a working ref for the duration of this function */
item = configfs_get_config_item(dentry);
@@ -1821,6 +1832,12 @@ void configfs_unregister_group(struct config_group *group)
struct configfs_subsystem *subsys = group->cg_subsys;
struct dentry *dentry = group->cg_item.ci_dentry;
struct dentry *parent = group->cg_item.ci_parent->ci_dentry;
+ struct configfs_dirent *sd = dentry->d_fsdata;
+ struct configfs_fragment *frag = sd->s_frag;
+
+ down_write(&frag->frag_sem);
+ frag->frag_dead = true;
+ up_write(&frag->frag_sem);
inode_lock_nested(d_inode(parent), I_MUTEX_PARENT);
spin_lock(&configfs_dirent_lock);
@@ -1947,12 +1964,18 @@ void configfs_unregister_subsystem(struct configfs_subsystem *subsys)
struct config_group *group = &subsys->su_group;
struct dentry *dentry = group->cg_item.ci_dentry;
struct dentry *root = dentry->d_sb->s_root;
+ struct configfs_dirent *sd = dentry->d_fsdata;
+ struct configfs_fragment *frag = sd->s_frag;
if (dentry->d_parent != root) {
pr_err("Tried to unregister non-subsystem!\n");
return;
}
+ down_write(&frag->frag_sem);
+ frag->frag_dead = true;
+ up_write(&frag->frag_sem);
+
inode_lock_nested(d_inode(root),
I_MUTEX_PARENT);
inode_lock_nested(d_inode(dentry), I_MUTEX_CHILD);
diff --git a/fs/configfs/file.c b/fs/configfs/file.c
index 9d5e888df60b..fb65b706cc0d 100644
--- a/fs/configfs/file.c
+++ b/fs/configfs/file.c
@@ -48,22 +48,32 @@ struct configfs_buffer {
};
};
+static inline struct configfs_fragment *to_frag(struct file *file)
+{
+ struct configfs_dirent *sd = file->f_path.dentry->d_fsdata;
+
+ return sd->s_frag;
+}
-static int fill_read_buffer(struct configfs_buffer * buffer)
+static int fill_read_buffer(struct file *file, struct configfs_buffer *buffer)
{
- ssize_t count;
+ struct configfs_fragment *frag = to_frag(file);
+ ssize_t count = -ENOENT;
if (!buffer->page)
buffer->page = (char *) get_zeroed_page(GFP_KERNEL);
if (!buffer->page)
return -ENOMEM;
- count = buffer->attr->show(buffer->item, buffer->page);
+ down_read(&frag->frag_sem);
+ if (!frag->frag_dead)
+ count = buffer->attr->show(buffer->item, buffer->page);
+ up_read(&frag->frag_sem);
+
if (count < 0)
return count;
if (WARN_ON_ONCE(count > (ssize_t)SIMPLE_ATTR_SIZE))
return -EIO;
-
buffer->needs_read_fill = 0;
buffer->count = count;
return 0;
@@ -96,7 +106,7 @@ configfs_read_file(struct file *file, char __user *buf, size_t count, loff_t *pp
mutex_lock(&buffer->mutex);
if (buffer->needs_read_fill) {
- retval = fill_read_buffer(buffer);
+ retval = fill_read_buffer(file, buffer);
if (retval)
goto out;
}
@@ -133,6 +143,7 @@ static ssize_t
configfs_read_bin_file(struct file *file, char __user *buf,
size_t count, loff_t *ppos)
{
+ struct configfs_fragment *frag = to_frag(file);
struct configfs_buffer *buffer = file->private_data;
ssize_t retval = 0;
ssize_t len = min_t(size_t, count, PAGE_SIZE);
@@ -148,7 +159,12 @@ configfs_read_bin_file(struct file *file, char __user *buf,
if (buffer->needs_read_fill) {
/* perform first read with buf == NULL to get extent */
- len = buffer->bin_attr->read(buffer->item, NULL, 0);
+ down_read(&frag->frag_sem);
+ if (!frag->frag_dead)
+ len = buffer->bin_attr->read(buffer->item, NULL, 0);
+ else
+ len = -ENOENT;
+ up_read(&frag->frag_sem);
if (len <= 0) {
retval = len;
goto out;
@@ -168,8 +184,13 @@ configfs_read_bin_file(struct file *file, char __user *buf,
buffer->bin_buffer_size = len;
/* perform second read to fill buffer */
- len = buffer->bin_attr->read(buffer->item,
- buffer->bin_buffer, len);
+ down_read(&frag->frag_sem);
+ if (!frag->frag_dead)
+ len = buffer->bin_attr->read(buffer->item,
+ buffer->bin_buffer, len);
+ else
+ len = -ENOENT;
+ up_read(&frag->frag_sem);
if (len < 0) {
retval = len;
vfree(buffer->bin_buffer);
@@ -220,9 +241,16 @@ fill_write_buffer(struct configfs_buffer * buffer, const char __user * buf, size
}
static int
-flush_write_buffer(struct configfs_buffer *buffer, size_t count)
+flush_write_buffer(struct file *file, struct configfs_buffer *buffer, size_t count)
{
- return buffer->attr->store(buffer->item, buffer->page, count);
+ struct configfs_fragment *frag = to_frag(file);
+ int res = -ENOENT;
+
+ down_read(&frag->frag_sem);
+ if (!frag->frag_dead)
+ res = buffer->attr->store(buffer->item, buffer->page, count);
+ up_read(&frag->frag_sem);
+ return res;
}
@@ -252,7 +280,7 @@ configfs_write_file(struct file *file, const char __user *buf, size_t count, lof
mutex_lock(&buffer->mutex);
len = fill_write_buffer(buffer, buf, count);
if (len > 0)
- len = flush_write_buffer(buffer, len);
+ len = flush_write_buffer(file, buffer, len);
if (len > 0)
*ppos += len;
mutex_unlock(&buffer->mutex);
@@ -328,6 +356,7 @@ configfs_write_bin_file(struct file *file, const char __user *buf,
static int __configfs_open_file(struct inode *inode, struct file *file, int type)
{
struct dentry *dentry = file->f_path.dentry;
+ struct configfs_fragment *frag = to_frag(file);
struct configfs_attribute *attr;
struct configfs_buffer *buffer;
int error;
@@ -337,8 +366,13 @@ static int __configfs_open_file(struct inode *inode, struct file *file, int type
if (!buffer)
goto out;
+ error = -ENOENT;
+ down_read(&frag->frag_sem);
+ if (unlikely(frag->frag_dead))
+ goto out_free_buffer;
+
error = -EINVAL;
- buffer->item = configfs_get_config_item(dentry->d_parent);
+ buffer->item = to_item(dentry->d_parent);
if (!buffer->item)
goto out_free_buffer;
@@ -396,6 +430,7 @@ static int __configfs_open_file(struct inode *inode, struct file *file, int type
buffer->read_in_progress = false;
buffer->write_in_progress = false;
file->private_data = buffer;
+ up_read(&frag->frag_sem);
return 0;
out_put_module:
@@ -403,6 +438,7 @@ static int __configfs_open_file(struct inode *inode, struct file *file, int type
out_put_item:
config_item_put(buffer->item);
out_free_buffer:
+ up_read(&frag->frag_sem);
kfree(buffer);
out:
return error;
@@ -412,8 +448,6 @@ static int configfs_release(struct inode *inode, struct file *filp)
{
struct configfs_buffer *buffer = filp->private_data;
- if (buffer->item)
- config_item_put(buffer->item);
module_put(buffer->owner);
if (buffer->page)
free_page((unsigned long)buffer->page);
@@ -439,12 +473,17 @@ static int configfs_release_bin_file(struct inode *inode, struct file *file)
buffer->read_in_progress = false;
if (buffer->write_in_progress) {
+ struct configfs_fragment *frag = to_frag(file);
buffer->write_in_progress = false;
- /* result of ->release() is ignored */
- buffer->bin_attr->write(buffer->item, buffer->bin_buffer,
- buffer->bin_buffer_size);
-
+ down_read(&frag->frag_sem);
+ if (!frag->frag_dead) {
+ /* result of ->release() is ignored */
+ buffer->bin_attr->write(buffer->item,
+ buffer->bin_buffer,
+ buffer->bin_buffer_size);
+ }
+ up_read(&frag->frag_sem);
/* vfree on NULL is safe */
vfree(buffer->bin_buffer);
buffer->bin_buffer = NULL;
]
|
| 372/2 |
2026/09/02 21:32 |
llm |
history-explorer |
0m
Model:
Tokens:
input: 55842
output: 33
thoughts: 472
Thoughts: **Analyzing Configfs Race Conditions and Locking**
Okay, so I've been digging into some `configfs` code, and commit `b0841eefd969` is a real gem. It clearly illustrates the method `configfs` uses to handle the race condition between I/O operations and removals. Basically, it employs `frag->frag_dead` to verify whether an item has been deleted. The bug report is spot-on: after re-acquiring the lock, it's absolutely crucial to check `sd->s_frag->frag_dead` to see if the directory was removed during the unlocked period. This directly corresponds with the idiom introduced in `b0841eefd969`.
This leads me to a clear understanding of the idioms and locking rules in `configfs`:
1. **Pinning the Root Object:** When dropping `inode_lock(dir)` within `configfs`βespecially when dealing with potential deadlocks during target resolution in `configfs_symlink`βthe reference count of the embedded default group isn't enough to prevent the dynamically allocated parent object from being freed. We have to traverse up the dentry tree (e.g., checking `!(sd->s_type & CONFIGFS_USET_DEFAULT)`) to find that dynamically allocated root object and *pin* it using `configfs_get_config_item()`. This is essential to prevent the whole structure from getting `kfree()`d when `configfs_rmdir()` releases the subsystem's reference. This is critical.
2. **Checking for Removal (`frag_dead`)**: After re-acquiring `inode_lock(dir)`, *always* check if the item was removed while the lock was dropped. The established idiom in `configfs` βas seen in `b0841eefd969`β is to check `sd->s_frag->frag_dead`. If this is `true`, the operation has to be aborted, probably returning `-ENOENT`.
3. **Target Race Handling**: For symlink targets, `ba80aa909c99` shows that we should check `CONFIGFS_USET_DROPPING` under `configfs_dirent_lock` before acquiring a reference to the target item.
Just to be thorough, I should double-check to see if there are any other commits that might be relevant. I'll search for `configfs_get_root_item` in case someone's already added it. Gotta be proactive!
|
| 373/2 |
2026/09/02 21:33 |
tool |
grepper |
0m
Error:
Args: map[Expression:configfs_get_root_item PathPrefix:fs/configfs/]
Results:
|
| 374/2 |
2026/09/02 21:33 |
llm |
history-explorer |
0m
Model:
Tokens:
input: 56908
output: 893
thoughts: 564
Thoughts: **My Analysis of the `configfs_symlink` Vulnerability**
Okay, so this is a fascinating issue. It's about a race condition that can occur when resolving symbolic links in `configfs`, and understanding it requires a grasp of some subtle `configfs` idioms and how they've evolved. Let me break down my understanding of the problem and the proposed solutions.
Firstly, the core vulnerability, as I see it, stems from the need to drop `inode_lock(dir)` during `configfs_symlink` to resolve the symlink target. This was introduced way back in commit `351e5d869e5a` to avoid deadlocks. The problem is, while the lock is dropped, a concurrent `configfs_rmdir` operation can come along and remove the parent directory, like `nvmet_port`. The tricky part here is the way `configfs` handles default groups. Because these groups are often *embedded* within their parent structures, simply holding a reference to the default group (the `parent_item`) *doesn't* prevent the parent structure itself from being freed when its reference count drops to zero. That's the crux of the race.
Now, let's talk about the key idioms we need to use to mitigate this issue. I've distilled them down into a few distinct strategies:
1. **Pinning the dynamically allocated root object**: To ensure the parent structure *remains* valid, we need to pin the "root" object. The challenge is, in `configfs`, default groups are identified by the `CONFIGFS_USET_DEFAULT` flag in `sd->s_type`. The trick is to walk *up* the `d_parent` chain until you hit a dentry *without* the `CONFIGFS_USET_DEFAULT` flag. That signifies the dynamically allocated root object. We then use `configfs_get_config_item()` to grab a reference and pin it, preventing it from being freed. This is analogous to how `detach_groups()` and `configfs_rmdir()` handle this flag.
2. **Checking `frag_dead` after relocking**: This is a classic `configfs` idiom. After we re-acquire the `inode_lock(dir)`, we need to *verify* that the directory wasn't removed while the lock was dropped. The way to do this, as introduced in `b0841eefd969` ("configfs: provide exclusion between IO and removals"), is to check the `sd->s_frag->frag_dead` flag. If `frag_dead` is true, it means the item has been removed and we need to abort the operation immediately, returning an `-ENOENT` error. This is a common pattern in `configfs` to handle concurrent removals.
3. **Handling target removal races (Contextual understanding)**: For perspective, there's a related race condition that was addressed in `ba80aa909c99 ("configfs: Fix race between create_link and configfs_rmdir")`. That fix dealt with the symbolic link *target* being removed during `create_link`. The fix used `CONFIGFS_USET_DROPPING` under `configfs_dirent_lock` before taking a reference to the target. However, in our current case with `configfs_symlink` and the *parent* directory race, checking `frag_dead` *after* relocking is the appropriate technique. The choice of strategy is crucial here.
In essence, what we have is a combination of good practices and careful synchronization. Pinning the root object to prevent the entire structure from disappearing and confirming the parent dir exists after dropping the lock are critical for robust `configfs` code, especially when you have to relinquish locks. It is important to know that while there are multiple strategies, in this case, the `frag_dead` is critical for ensuring the safety of the `configfs_symlink` operation.
|
| 375/1 |
2026/09/02 21:33 |
action |
kernel-scratch-checkouter |
0m
Results: map[KernelScratchSrc:/app/workdir/cache/tmp/tmp3802645122]
|
| 376/1 |
2026/09/02 21:33 |
loop |
|
37m
|
| 377/2 |
2026/09/02 21:33 |
iteration |
0 |
26m
|
| 378/3 |
2026/09/02 21:33 |
agent |
patch-generator |
2m
Model:
Instruction:
You are an experienced Linux kernel developer tasked with creating a fix for a kernel bug.
You will be given a crash report, an initial explanation of the root cause done by another
kernel expert, and a summary of how similar bugs were fixed in the past.
Use the codeeditor tool to do code edits.
Note: you will not see your changes when looking at the code using codesearch tools.
Use the patch-diff tool to review the modifications you applied.
Your final reply should contain explanation of what you did in the patch and why
(details not present in the initial explanation of the bug).
Your fix must not just prevent the given crash, but also be the best fix for the underlying
root cause from the software engineering point of view. There can be several ways to fix the
same bug. Consider alternatives, and pick the best one. For example, additional checks may be
added at different locations/functions, it's usually better to place them earlier in the
execution to avoid multiple checks at various locations later.
Frequently the same coding mistake is done in several locations in the source code.
Check if your fix should be extended/applied to similar cases around to fix other similar bugs.
But don't go too wide, don't try to fix problems kernel-wide, fix similar issues
in the same file only.
If you are changing post-conditions of a function, consider all callers of the functions,
and if they need to be updated to handle new post-conditions. For example, if you make
a function that previously never returned a NULL, return NULL, consider if callers
need to be updated to handle NULL return value.
Prefer calling several tools at the same time to save round-trips.
Prompt:
The crash that corresponds to the bug is:
==================================================================
BUG: KASAN: slab-use-after-free in nvmet_port_subsys_allow_link+0x41/0x2e0 drivers/nvme/target/configfs.c:1059
Read of size 8 at addr ffff88811c6d44c8 by task syz-executor266/5850
CPU: 0 UID: 0 PID: 5850 Comm: syz-executor266 Not tainted syzkaller #1 PREEMPT(full)
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
Call Trace:
<TASK>
dump_stack_lvl+0xe8/0x150 lib/dump_stack.c:120
print_address_description+0x55/0x1e0 mm/kasan/report.c:378
print_report+0x58/0x70 mm/kasan/report.c:482
kasan_report+0x117/0x150 mm/kasan/report.c:595
nvmet_port_subsys_allow_link+0x41/0x2e0 drivers/nvme/target/configfs.c:1059
configfs_symlink+0x59a/0x1030 fs/configfs/symlink.c:196
vfs_symlink+0x18b/0x330 fs/namei.c:5794
filename_symlinkat+0x1cd/0x410 fs/namei.c:5819
__do_sys_symlinkat fs/namei.c:5839 [inline]
__se_sys_symlinkat+0x4e/0x2b0 fs/namei.c:5834
do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7f648f0cc459
Code: c0 79 93 eb d5 48 8d 7c 1d 00 eb 99 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 d0 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007f648c0911f8 EFLAGS: 00000246 ORIG_RAX: 000000000000010a
RAX: ffffffffffffffda RBX: 00007f648f139028 RCX: 00007f648f0cc459
RDX: 00007f648f139028 RSI: 00000000ffffff9c RDI: 00007f648f139060
RBP: 00007f648f139060 R08: 00007f648c0916c0 R09: 00007f648c0916c0
R10: 00007f648c0916c0 R11: 0000000000000246 R12: ffffffffffffffd0
R13: 0000000000000010 R14: 00007ffe91711350 R15: 00007ffe91711438
</TASK>
Allocated by task 5847:
kasan_save_stack mm/kasan/common.c:57 [inline]
kasan_save_track+0x3e/0x80 mm/kasan/common.c:78
poison_kmalloc_redzone mm/kasan/common.c:398 [inline]
__kasan_kmalloc+0x93/0xb0 mm/kasan/common.c:415
kasan_kmalloc include/linux/kasan.h:263 [inline]
__kmalloc_cache_noprof+0x321/0x600 mm/slub.c:5563
_kmalloc_noprof include/linux/slab.h:991 [inline]
_kzalloc_noprof include/linux/slab.h:1312 [inline]
nvmet_ports_make+0xe6/0xf00 drivers/nvme/target/configfs.c:2051
configfs_mkdir+0x4f6/0x9e0 fs/configfs/dir.c:1360
vfs_mkdir+0x40c/0x620 fs/namei.c:5410
filename_mkdirat+0x285/0x510 fs/namei.c:5443
__do_sys_mkdir fs/namei.c:5470 [inline]
__se_sys_mkdir+0x34/0x150 fs/namei.c:5467
do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
entry_SYSCALL_64_after_hwframe+0x77/0x7f
Freed by task 5847:
kasan_save_stack mm/kasan/common.c:57 [inline]
kasan_save_track+0x3e/0x80 mm/kasan/common.c:78
kasan_save_free_info+0x40/0x50 mm/kasan/generic.c:584
poison_slab_object mm/kasan/common.c:253 [inline]
__kasan_slab_free+0x5c/0x80 mm/kasan/common.c:285
kasan_slab_free include/linux/kasan.h:235 [inline]
slab_free_hook mm/slub.c:2748 [inline]
slab_free mm/slub.c:6499 [inline]
kfree+0x1c5/0x650 mm/slub.c:6792
config_item_cleanup fs/configfs/item.c:128 [inline]
config_item_release+0x13a/0x2d0 fs/configfs/item.c:137
configfs_rmdir+0x885/0x950 fs/configfs/dir.c:1571
vfs_rmdir+0x3e6/0x6a0 fs/namei.c:5515
filename_rmdir+0x292/0x520 fs/namei.c:5572
__do_sys_rmdir fs/namei.c:5595 [inline]
__se_sys_rmdir+0x2e/0x140 fs/namei.c:5592
do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
entry_SYSCALL_64_after_hwframe+0x77/0x7f
The buggy address belongs to the object at ffff88811c6d4000
which belongs to the cache kmalloc-4k of size 4096
The buggy address is located 1224 bytes inside of
freed 4096-byte region [ffff88811c6d4000, ffff88811c6d5000)
The buggy address belongs to the physical page:
page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x11c6d0
head: order:3 mapcount:0 entire_mapcount:0 nr_pages_mapped:0 pincount:0
flags: 0x17ff00000000040(head|node=0|zone=2|lastcpupid=0x7ff)
page_type: f5(slab)
raw: 017ff00000000040 ffff888100042140 dead000000000100 dead000000000122
raw: 0000000000000000 0000000000040004 00000000f5000000 0000000000000000
head: 017ff00000000040 ffff888100042140 dead000000000100 dead000000000122
head: 0000000000000000 0000000000040004 00000000f5000000 0000000000000000
head: 017ff00000000003 fffffffffffffe01 00000000ffffffff 00000000ffffffff
head: ffffffffffffffff 0000000000000000 00000000ffffffff 0000000000000008
page dumped because: kasan: bad access detected
page_owner tracks the page as allocated
page last allocated via order 3, migratetype Unmovable, gfp_mask 0xd20c0(__GFP_IO|__GFP_FS|__GFP_NOWARN|__GFP_NORETRY|__GFP_COMP|__GFP_NOMEMALLOC), pid 5843, tgid 5843 (syz-executor266), ts 63759869729
set_page_owner include/linux/page_owner.h:33 [inline]
post_alloc_hook+0x1f9/0x250 mm/page_alloc.c:1871
prep_new_page mm/page_alloc.c:1879 [inline]
get_page_from_freelist+0x2209/0x2280 mm/page_alloc.c:3943
__alloc_frozen_pages_noprof+0x217/0x5a0 mm/page_alloc.c:5436
alloc_slab_page mm/slub.c:3347 [inline]
allocate_slab+0x7d/0x620 mm/slub.c:3462
new_slab mm/slub.c:3513 [inline]
refill_objects+0x2d5/0x350 mm/slub.c:7410
refill_sheaf mm/slub.c:2885 [inline]
__pcs_replace_empty_main+0x2c8/0x6c0 mm/slub.c:4774
alloc_from_pcs mm/slub.c:4850 [inline]
slab_alloc_node mm/slub.c:4984 [inline]
__kmalloc_cache_noprof+0x39b/0x600 mm/slub.c:5559
_kmalloc_noprof include/linux/slab.h:991 [inline]
do_proc_readlink fs/proc/base.c:1817 [inline]
proc_pid_readlink+0x108/0x230 fs/proc/base.c:1847
vfs_readlink+0x4bb/0x540 fs/namei.c:6407
do_readlinkat+0x218/0x510 fs/stat.c:583
__do_sys_readlinkat fs/stat.c:599 [inline]
__se_sys_readlinkat fs/stat.c:596 [inline]
__x64_sys_readlinkat+0x9a/0xb0 fs/stat.c:596
do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
entry_SYSCALL_64_after_hwframe+0x77/0x7f
page last free pid 5843 tgid 5843 ts 63751651251 stack trace:
reset_page_owner include/linux/page_owner.h:26 [inline]
__free_pages_prepare mm/page_alloc.c:1418 [inline]
__free_frozen_pages+0xc50/0xd30 mm/page_alloc.c:2962
__slab_free+0x274/0x2c0 mm/slub.c:5815
qlink_free mm/kasan/quarantine.c:163 [inline]
qlist_free_all+0x99/0x100 mm/kasan/quarantine.c:179
kasan_quarantine_reduce+0x148/0x160 mm/kasan/quarantine.c:286
__kasan_slab_alloc+0x22/0x80 mm/kasan/common.c:350
kasan_slab_alloc include/linux/kasan.h:253 [inline]
slab_post_alloc_hook mm/slub.c:4683 [inline]
slab_alloc_node mm/slub.c:4996 [inline]
__do_kmalloc_node mm/slub.c:5413 [inline]
__kmalloc_noprof+0x307/0x720 mm/slub.c:5439
_kmalloc_noprof include/linux/slab.h:995 [inline]
tomoyo_add_entry security/tomoyo/common.c:2166 [inline]
tomoyo_supervisor+0xc53/0x1560 security/tomoyo/common.c:2238
tomoyo_audit_env_log security/tomoyo/environ.c:37 [inline]
tomoyo_env_perm+0x151/0x1f0 security/tomoyo/environ.c:64
tomoyo_environ security/tomoyo/domain.c:673 [inline]
tomoyo_find_next_domain+0x1332/0x1a90 security/tomoyo/domain.c:889
tomoyo_bprm_check_security+0x11b/0x180 security/tomoyo/tomoyo.c:102
security_bprm_check+0x85/0x240 security/security.c:820
search_binary_handler fs/exec.c:1749 [inline]
exec_binprm fs/exec.c:1791 [inline]
bprm_execve+0x888/0x1590 fs/exec.c:1847
do_execveat_common+0x4f4/0x670 fs/exec.c:1945
__do_sys_execve fs/exec.c:2018 [inline]
__se_sys_execve fs/exec.c:2012 [inline]
__x64_sys_execve+0x97/0xc0 fs/exec.c:2012
do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
entry_SYSCALL_64_after_hwframe+0x77/0x7f
Memory state around the buggy address:
ffff88811c6d4380: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
ffff88811c6d4400: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
>ffff88811c6d4480: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
^
ffff88811c6d4500: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
ffff88811c6d4580: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
==================================================================
The explanation of the root cause of the bug is:
An elegant and complete analysis of the root cause, along with a robust fix for this `configfs` race condition.
### 1. KASAN Crash Analysis & Offset Math
The crash is a slab-use-after-free read of size 8 at offset **1224** inside a 4096-byte region (`kmalloc-4k`).
The KASAN trace points to `nvmet_port_subsys_allow_link()`, which accesses `parent->ci_parent` where `parent` is a `struct config_item *`.
In `nvmet_ports_make()`, a `struct nvmet_port` is dynamically allocated. Let's look at its memory layout:
```c
struct nvmet_port {
struct list_head entry; // 16 bytes
struct nvmf_disc_rsp_page_entry disc_addr; // 1024 bytes
struct config_group group; // 136 bytes (offset 1040)
struct config_group subsys_group; // 136 bytes (offset 1176)
// ...
};
```
The `parent_item` passed to `allow_link` is `&port->subsys_group.cg_item`.
The offset of `subsys_group` is `16 + 1024 + 136 = 1176` bytes.
Inside `struct config_item`, the `ci_parent` pointer is at offset 48.
When `nvmet_port_subsys_allow_link` reads `parent->ci_parent`, it accesses memory at offset `1176 + 48 = 1224` bytes into the `nvmet_port` allocation. This **perfectly matches** the KASAN report:
> `The buggy address is located 1224 bytes inside of freed 4096-byte region`
### 2. The Root Cause (Race Condition)
The race occurs between Thread 1 creating a symlink in a default group (`ports/1/subsystems/`) and Thread 2 removing the parent root object (`ports/1`).
1. **Thread 1 (`symlinkat`)**:
- Enters `configfs_symlink()` with the VFS holding `inode_lock(dir)` on `ports/1/subsystems`.
- Acquires a reference to `parent_item` (the embedded `subsys_group.cg_item`).
- Drops `inode_unlock(dir)` to resolve the symlink target without deadlocking.
2. **Thread 2 (`rmdir`)**:
- Calls `configfs_rmdir()` on `ports/1`.
- Calls `configfs_detach_group()`, which recursively locks all default groups. Since Thread 1 dropped the lock on `ports/1/subsystems`, Thread 2 acquires it, marks the items as dead, detaches them, and unlocks.
- Calls `unlink_group()` on `ports/1`, dropping the subsystem's reference to the port.
- The `nvmet_port` refcount drops to 0, and the **entire `nvmet_port` struct is freed via `kfree()`**.
3. **Thread 1 Resumes**:
- Re-acquires `inode_lock(dir)`.
- Blindly calls `type->ct_item_ops->allow_link(parent_item, target_item)`.
- `parent_item` points to the `subsys_group` embedded inside the **already freed** `nvmet_port`, causing the UAF read at offset 1224.
- Even if `allow_link` wasn't called, jumping to `out_put` executes `config_item_put(parent_item)`, causing a secondary UAF on the kref.
### 3. Why Simple Fixes Fail
- **Re-acquiring `parent_item`**: You cannot safely drop `parent_item` before the unlock and re-acquire it via `configfs_get_config_item(dentry->d_parent)` after the relock. When `configfs_rmdir` detaches the item, it leaves `sd->s_element` as a dangling pointer. Re-acquiring it will dereference freed memory.
- **Holding `parent_item`**: Holding the reference to `parent_item` across the unlock is insufficient because `parent_item` is a **default group**. Default groups are embedded directly inside their parent structs (like `nvmet_port`). Their individual refcount being $> 0$ does **not** prevent the parent struct from being freed when the parent's refcount drops to 0.
### 4. The Proper Fix
To prevent the memory from being freed while the lock is dropped, `configfs_symlink()` must pin the **root object** (the dynamically allocated parent, e.g., `ports/1`) rather than just the embedded default group.
Because `configfs_symlink()` holds `inode_lock(dir)` initially, any concurrent `configfs_rmdir()` on an ancestor root object will block inside `detach_groups()` trying to acquire `inode_lock(dir)`. This guarantees the dentry tree is stable and we can safely walk up the `d_parent` chain to find the root object (the first dentry without the `CONFIGFS_USET_DEFAULT` flag) and take a reference to it.
After re-acquiring the lock, we must check `sd->s_frag->frag_dead` to see if the directory was removed while unlocked.
**Implementation for `fs/configfs/symlink.c`:**
```c
/* Add this helper function above configfs_symlink */
static struct config_item *configfs_get_root_item(struct dentry *dentry)
{
struct dentry *root_dentry = dentry;
struct configfs_dirent *sd;
while (root_dentry) {
sd = root_dentry->d_fsdata;
if (!(sd->s_type & CONFIGFS_USET_DEFAULT))
break;
root_dentry = root_dentry->d_parent;
}
return configfs_get_config_item(root_dentry);
}
int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
struct dentry *dentry, const char *symname)
{
int ret;
struct config_item *parent_item;
struct config_item *target_item = NULL;
struct config_item *root_item = NULL;
const struct config_item_type *type;
parent_item = configfs_get_config_item(dentry->d_parent);
type = parent_item->ci_type;
ret = -EPERM;
if (!type || !type->ct_item_ops ||
!type->ct_item_ops->allow_link)
goto out_put;
/*
* Pin the root object so that the parent_item (which may be an
* embedded default group) is not freed while we drop the lock.
*/
root_item = configfs_get_root_item(dentry->d_parent);
/*
* Drop the dir inode lock. The target path might be outside
* configfs, and we don't want to deadlock.
*/
inode_unlock(dir);
ret = get_target(symname, &target_item, dentry->d_sb);
inode_lock(dir);
if (ret)
goto out_put_root;
if (dentry->d_inode || d_unhashed(dentry))
ret = -EEXIST;
else
ret = inode_permission(&nop_mnt_idmap, dir,
MAY_WRITE | MAY_EXEC);
if (!ret) {
struct configfs_dirent *sd = dentry->d_parent->d_fsdata;
/* Check if the directory was removed while unlocked */
if (sd->s_frag->frag_dead) {
ret = -ENOENT;
} else {
ret = type->ct_item_ops->allow_link(parent_item, target_item);
}
}
if (!ret) {
mutex_lock(&configfs_symlink_mutex);
ret = create_link(parent_item, target_item, dentry);
mutex_unlock(&configfs_symlink_mutex);
if (ret && type->ct_item_ops->drop_link)
type->ct_item_ops->drop_link(parent_item,
target_item);
}
config_item_put(target_item);
out_put_root:
config_item_put(root_item);
out_put:
config_item_put(parent_item);
return ret;
}
```
Historical context on how similar bugs were fixed in the past:
Based on a review of the `fs/configfs/` history and past fixes for similar race conditions and use-after-free (UAF) bugs, here are the idioms, locking rules, and common patterns that should be followed when fixing this bug:
### 1. The Origin of the Race
The race condition was made possible by commit **`351e5d869e5a ("configfs: fix a deadlock in configfs_symlink()")`**. This commit introduced the `inode_unlock(dir)` and `inode_lock(dir)` around the target resolution in `configfs_symlink()` to prevent deadlocks. However, dropping this lock creates a window where a concurrent `configfs_rmdir()` can remove the parent directory.
### 2. Idiom: Pinning the Dynamically Allocated Root Object
In `configfs`, default groups (like `subsystems`) are embedded directly inside their parent structures (like `nvmet_port`). Holding a reference to an embedded default group does **not** prevent the parent structure from being freed when the parent's own refcount drops to 0.
To safely bridge the unlocked window, you must pin the dynamically allocated parent (the "root object"). The established pattern in `configfs` to distinguish between dynamically allocated items and embedded default groups is to check the **`CONFIGFS_USET_DEFAULT`** flag in `sd->s_type`.
* Functions like `detach_groups()` and `configfs_rmdir()` already use `!(sd->s_type & CONFIGFS_USET_DEFAULT)` to identify the boundaries of default groups.
* Walking up the `d_parent` chain until you hit a dentry without `CONFIGFS_USET_DEFAULT` and pinning it via `configfs_get_config_item()` is the correct and idiomatic way to ensure the entire memory allocation survives the unlocked window.
### 3. Idiom: Checking `frag_dead` for Concurrent Removals
After re-acquiring `inode_lock(dir)`, you must verify that the directory wasn't removed while the lock was dropped. The standard `configfs` idiom for this was introduced in commit **`b0841eefd969 ("configfs: provide exclusion between IO and removals")`**.
* When `configfs_rmdir()` or `configfs_unregister_group()` removes an item, it sets **`sd->s_frag->frag_dead = true`** (often while holding `frag_sem` for write).
* Throughout `fs/configfs/file.c`, operations (like `read`, `write`, `open`, `release`) check `if (!frag->frag_dead)` before proceeding.
* Applying this exact same check (`if (sd->s_frag->frag_dead) return -ENOENT;`) in `configfs_symlink()` after relocking perfectly aligns with the subsystem's established lifecycle rules.
### 4. Prior Art: Symlink Target Races
For historical context, a very similar race involving the symlink *target* (rather than the parent) was fixed in commit **`ba80aa909c99 ("configfs: Fix race between create_link and configfs_rmdir")`**.
* In that bug, the target was being removed concurrently. The fix was to check the `CONFIGFS_USET_DROPPING` flag under the `configfs_dirent_lock` before taking a reference to the target item.
* While that fix handled the *target* side of the symlink equation, your proposed fix correctly handles the *parent* side by combining the root object pinning with the `frag_dead` check.
### Summary
The proposed fix strategy in the bug report is completely sound and aligns with `configfs` design patterns. You should implement the `configfs_get_root_item()` helper to walk the dentry tree checking for `CONFIGFS_USET_DEFAULT`, pin that root object across the `inode_unlock/lock` boundary, and finally check `sd->s_frag->frag_dead` before proceeding with `allow_link()`.
Reply: ### Cause of the Bug
The use-after-free crash occurs due to a race between symlink creation via `configfs_symlink()` and directory removal via `configfs_rmdir()`:
1. In `configfs_symlink()`, `inode_unlock(dir)` is called to drop the directory lock before resolving the symlink target (preventing potential deadlocks during path lookup).
2. If `configfs_symlink()` is operating within an embedded default group (e.g., `ports/1/subsystems`), holding a reference to `parent_item` (`subsys_group`) is not sufficient to prevent the containing parent structure (`struct nvmet_port` representing `ports/1`) from being freed when a concurrent `configfs_rmdir()` removes `ports/1`.
3. While unlocked, `configfs_rmdir()` can detach the default groups, mark the fragment dead (`frag->frag_dead = true`), and unlink/release the root object, freeing the underlying memory with `kfree()`.
4. When `configfs_symlink()` re-acquires `inode_lock(dir)`, it attempts to access `parent_item` and invoke `type->ct_item_ops->allow_link(parent_item, target_item)`, triggering slab use-after-free when dereferencing the freed memory.
### Fix
1. **Root Object Pinning**: Added `configfs_get_root_item()` to walk up the dentry tree past any default groups (`CONFIGFS_USET_DEFAULT`) to locate and take a reference on the dynamically allocated root object (`root_item`) prior to dropping `inode_lock(dir)`. Because `inode_lock(dir)` is held when this reference is taken, concurrent removals are blocked from completing detachment or releasing the root object until after `root_item` is acquired.
2. **Directory Liveness Check**: After re-acquiring `inode_lock(dir)`, check `sd->s_frag->frag_dead` to determine whether the directory was removed during the unlocked window. If so, return `-ENOENT` without calling `allow_link()` or attempting link creation.
3. **Reference Cleanup**: Added proper cleanup in `out_put_root` to drop `root_item` via `config_item_put(root_item)` along all exit paths.
|
| 379/4 |
2026/09/02 21:33 |
llm |
patch-generator |
0m
Model:
Tokens:
input: 11842
output: 36
thoughts: 80
Thoughts: Here's my thought process, summarized as you requested:
**Initial Investigation into a File and its Context**
Okay, so I need to dive into this. The report and background are mentioning specific files and symbols, so I'm going to start with a thorough inspection. Let's see...I have a pretty comprehensive set of tools at my disposal here β `read-file`, `grepper`, `git-log`, `git-show`, `git-blame`, a whole suite of `codesearch-*` functions, `codeexpert`, `codeeditor`, and even `patch-diff`. That's good; I should be able to get a solid understanding of the situation. Given the mention of `fs/configfs/symlink.c`, I think it makes sense to start there, so I'll begin by reading that file. I'll get a base understanding of the code and then go from there.
|
| 380/4 |
2026/09/02 21:33 |
tool |
read-file |
0m
Args: map[File:fs/configfs/symlink.c FirstLine:1 LineCount:100]
Results: map[Contents: 1: // SPDX-License-Identifier: GPL-2.0-or-later
2: /*
3: * symlink.c - operations for configfs symlinks.
4: *
5: * Based on sysfs:
6: * sysfs is Copyright (C) 2001, 2002, 2003 Patrick Mochel
7: *
8: * configfs Copyright (C) 2005 Oracle. All rights reserved.
9: */
10:
11: #include <linux/fs.h>
12: #include <linux/module.h>
13: #include <linux/namei.h>
14: #include <linux/slab.h>
15:
16: #include <linux/configfs.h>
17: #include "configfs_internal.h"
18:
19: /* Protects attachments of new symlinks */
20: DEFINE_MUTEX(configfs_symlink_mutex);
21:
22: static int item_depth(struct config_item * item)
23: {
24: struct config_item * p = item;
25: int depth = 0;
26: do { depth++; } while ((p = p->ci_parent) && !configfs_is_root(p));
27: return depth;
28: }
29:
30: static int item_path_length(struct config_item * item)
31: {
32: struct config_item * p = item;
33: int length = 1;
34: do {
35: length += strlen(config_item_name(p)) + 1;
36: p = p->ci_parent;
37: } while (p && !configfs_is_root(p));
38: return length;
39: }
40:
41: static void fill_item_path(struct config_item * item, char * buffer, int length)
42: {
43: struct config_item * p;
44:
45: --length;
46: for (p = item; p && !configfs_is_root(p); p = p->ci_parent) {
47: int cur = strlen(config_item_name(p));
48:
49: /* back up enough to print this bus id with '/' */
50: length -= cur;
51: memcpy(buffer + length, config_item_name(p), cur);
52: *(buffer + --length) = '/';
53: }
54: }
55:
56: static int configfs_get_target_path(struct config_item *item,
57: struct config_item *target, char *path)
58: {
59: int depth, size;
60: char *s;
61:
62: depth = item_depth(item);
63: size = item_path_length(target) + depth * 3 - 1;
64: if (size > PATH_MAX)
65: return -ENAMETOOLONG;
66:
67: pr_debug("%s: depth = %d, size = %d\n", __func__, depth, size);
68:
69: for (s = path; depth--; s += 3)
70: strcpy(s,"../");
71:
72: fill_item_path(target, path, size);
73: pr_debug("%s: path = '%s'\n", __func__, path);
74: return 0;
75: }
76:
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++;
]
|
| 381/4 |
2026/09/02 21:33 |
llm |
patch-generator |
0m
Model:
Tokens:
input: 13525
output: 38
thoughts: 18
Thoughts: Okay, here we go...
**Diving into `fs/configfs/symlink.c`**
Alright, let's get into the nitty-gritty of how configfs handles symbolic links. I'm already anticipating some interesting design choices here, especially given the flexibility configfs offers in creating and managing virtual file systems. I'm keen to see how they've implemented symlink creation, resolution, and deletion. There are always tricky parts with symlinks, especially regarding security and preventing infinite loops. I'm curious if they've integrated any specific checks or safeguards beyond what the standard VFS provides. I'm looking for the core logic that ties together the VFS operations with the configfs-specific metadata and structures. What's the link between the underlying filesystem and the symlink itself? I want to see how the target path is stored and resolved, and if there are any specific locking mechanisms in place to handle concurrent access. Let's see how well it's all put together!
|
| 382/4 |
2026/09/02 21:33 |
tool |
read-file |
0m
Args: map[File:fs/configfs/symlink.c FirstLine:101 LineCount:100]
Results: map[Contents: 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: }
115:
116:
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: }
133:
134:
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);
]
|
| 383/4 |
2026/09/02 21:33 |
llm |
patch-generator |
0m
Model:
Tokens:
input: 15384
output: 37
thoughts: 21
Thoughts: Here's my take on the remaining sections of `fs/configfs/symlink.c`.
**Diving Deeper into the Symbolism**
Okay, so we've got the basics of the symlink handling in ConfigFS down, but to really understand its implications, I need to dig into the rest of `fs/configfs/symlink.c`. I need to see how the kernel *actually* implements and interacts with these symbolic links within this particular virtual filesystem. Knowing how this is constructed from top to bottom is important to ensure I have a complete understanding. I want to see how these links are created, how the target paths are resolved, how errors are handled, and how the underlying data structures contribute to the overall behavior. This is crucial for anything downstream that involves symlinks, because any issues here will cause problems there.
|
| 384/4 |
2026/09/02 21:33 |
tool |
read-file |
0m
Args: map[File:fs/configfs/symlink.c FirstLine:201 LineCount:50]
Results: map[Contents: 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: }
212:
213: int configfs_unlink(struct inode *dir, struct dentry *dentry)
214: {
215: struct configfs_dirent *sd = dentry->d_fsdata, *target_sd;
216: struct config_item *parent_item;
217: const struct config_item_type *type;
218: int ret;
219:
220: ret = -EPERM; /* What lack-of-symlink returns */
221: if (!(sd->s_type & CONFIGFS_ITEM_LINK))
222: goto out;
223:
224: target_sd = sd->s_element;
225:
226: parent_item = configfs_get_config_item(dentry->d_parent);
227: type = parent_item->ci_type;
228:
229: spin_lock(&configfs_dirent_lock);
230: list_del_init(&sd->s_sibling);
231: spin_unlock(&configfs_dirent_lock);
232: configfs_put(sd);
233: simple_unlink(dir, dentry);
234:
235: /*
236: * drop_link() must be called before
237: * decrementing target's ->s_links, so that the order of
238: * drop_link(this, target) and drop_item(target) is preserved.
239: */
240: if (type && type->ct_item_ops &&
241: type->ct_item_ops->drop_link)
242: type->ct_item_ops->drop_link(parent_item,
243: target_sd->s_element);
244:
245: spin_lock(&configfs_dirent_lock);
246: target_sd->s_links--;
247: spin_unlock(&configfs_dirent_lock);
248: configfs_put(target_sd);
249:
250: config_item_put(parent_item);
]
|
| 385/4 |
2026/09/02 21:33 |
llm |
patch-generator |
0m
Model:
Tokens:
input: 16384
output: 31
thoughts: 107
Thoughts: **Diving into ConfigFS Defaults and Item Management**
Okay, so I need to get a really solid grasp on how default groups are handled within `configfs`. My immediate thought is to start a deep dive into the code. I'm going to focus my search within `fs/configfs/` to understand the flow of things, specifically looking at how default settings interact with various functionalities.
Iβm particularly interested in unraveling the mechanisms surrounding `CONFIGFS_USET_DEFAULT` and the implications of `frag_dead`. It's crucial to see how these bits play together. To start, I'll hit the file system with a `grep` for those terms. Then, Iβll need to delve into `fs/configfs/dir.c` and `fs/configfs/configfs_internal.h`. Those files are likely where the core logic related to directory management, configuration item handling, and perhaps even cleanup is implemented. I expect I'll find the answers I need in those files, along with perhaps some cross-referencing to related structures or functions.
|
| 386/4 |
2026/09/02 21:33 |
tool |
grepper |
0m
Args: map[Expression:CONFIGFS_USET_DEFAULT PathPrefix:fs/configfs]
Results: map[Output:fs/configfs/configfs_internal.h=30=struct configfs_dirent {
--
fs/configfs/configfs_internal.h-52-#define CONFIGFS_USET_DIR 0x0040
fs/configfs/configfs_internal.h:53:#define CONFIGFS_USET_DEFAULT 0x0080
fs/configfs/configfs_internal.h-54-#define CONFIGFS_USET_DROPPING 0x0100
--
fs/configfs/dir.c=502=static int configfs_detach_prep(struct configfs_dirent *parent_sd, struct dentry **wait)
--
fs/configfs/dir.c-518- continue;
fs/configfs/dir.c:519: if (sd->s_type & CONFIGFS_USET_DEFAULT) {
fs/configfs/dir.c-520- /* Abort if racing with mkdir() */
--
fs/configfs/dir.c=548=static void configfs_detach_rollback(struct configfs_dirent *parent_sd)
--
fs/configfs/dir.c-554- list_for_each_entry(sd, &parent_sd->s_children, s_sibling)
fs/configfs/dir.c:555: if (sd->s_type & CONFIGFS_USET_DEFAULT)
fs/configfs/dir.c-556- configfs_detach_rollback(sd);
--
fs/configfs/dir.c=664=static void detach_groups(struct dentry *dentry)
--
fs/configfs/dir.c-673- next = next_dirent(parent_sd, sd);
fs/configfs/dir.c:674: if (!(sd->s_type & CONFIGFS_USET_DEFAULT))
fs/configfs/dir.c-675- continue;
--
fs/configfs/dir.c=703=static int create_default_group(struct dentry *parent,
--
fs/configfs/dir.c-722- sd = child->d_fsdata;
fs/configfs/dir.c:723: sd->s_type |= CONFIGFS_USET_DEFAULT;
fs/configfs/dir.c-724- } else {
--
fs/configfs/dir.c=977=static void configfs_dump_one(struct configfs_dirent *sd, int level)
--
fs/configfs/dir.c-986- type_print(CONFIGFS_USET_DIR);
fs/configfs/dir.c:987: type_print(CONFIGFS_USET_DEFAULT);
fs/configfs/dir.c-988- type_print(CONFIGFS_USET_DROPPING);
--
fs/configfs/dir.c=1469=static int configfs_rmdir(struct inode *dir, struct dentry *dentry)
--
fs/configfs/dir.c-1479- sd = dentry->d_fsdata;
fs/configfs/dir.c:1480: if (sd->s_type & CONFIGFS_USET_DEFAULT)
fs/configfs/dir.c-1481- return -EPERM;
]
|
| 387/4 |
2026/09/02 21:33 |
llm |
patch-generator |
0m
Model:
Tokens:
input: 17581
output: 27
thoughts: 18
Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience, presented in the first person:
**Investigating `frag_dead` within `fs/configfs`**
Right, let's dig into this. I'm thinking, in addition to whatever else we're looking at, it's worth taking a close look for instances of `frag_dead` within the `fs/configfs` directory. Given the nature of that filesystem, and the potential for memory fragmentation issues in general, `frag_dead` could surface some interesting anomalies. This search might reveal some hidden performance bottlenecks, or potential memory management problems that are worth my time. I want to see how `configfs` handles these kinds of internal fragmentation. Let's see what's what.
|
| 388/4 |
2026/09/02 21:33 |
tool |
grepper |
0m
Args: map[Expression:frag_dead PathPrefix:fs/configfs]
Results: map[Output:fs/configfs/configfs_internal.h=21=struct configfs_fragment {
--
fs/configfs/configfs_internal.h-23- struct rw_semaphore frag_sem;
fs/configfs/configfs_internal.h:24: bool frag_dead;
fs/configfs/configfs_internal.h-25-};
--
fs/configfs/dir.c=158=static struct configfs_fragment *new_fragment(void)
--
fs/configfs/dir.c-165- init_rwsem(&p->frag_sem);
fs/configfs/dir.c:166: p->frag_dead = false;
fs/configfs/dir.c-167- }
--
fs/configfs/dir.c=1469=static int configfs_rmdir(struct inode *dir, struct dentry *dentry)
--
fs/configfs/dir.c-1540- }
fs/configfs/dir.c:1541: frag->frag_dead = true;
fs/configfs/dir.c-1542- up_write(&frag->frag_sem);
--
fs/configfs/dir.c=1796=void configfs_unregister_group(struct config_group *group)
--
fs/configfs/dir.c-1804- down_write(&frag->frag_sem);
fs/configfs/dir.c:1805: frag->frag_dead = true;
fs/configfs/dir.c-1806- up_write(&frag->frag_sem);
--
fs/configfs/dir.c=1934=void configfs_unregister_subsystem(struct configfs_subsystem *subsys)
--
fs/configfs/dir.c-1947- down_write(&frag->frag_sem);
fs/configfs/dir.c:1948: frag->frag_dead = true;
fs/configfs/dir.c-1949- up_write(&frag->frag_sem);
--
fs/configfs/file.c=56=static int fill_read_buffer(struct file *file, struct configfs_buffer *buffer)
--
fs/configfs/file.c-66- down_read(&frag->frag_sem);
fs/configfs/file.c:67: if (!frag->frag_dead)
fs/configfs/file.c-68- count = buffer->attr->show(buffer->item, buffer->page);
--
fs/configfs/file.c=106=static ssize_t configfs_bin_read_iter(struct kiocb *iocb, struct iov_iter *to)
--
fs/configfs/file.c-125- down_read(&frag->frag_sem);
fs/configfs/file.c:126: if (!frag->frag_dead)
fs/configfs/file.c-127- len = buffer->bin_attr->read(buffer->item, NULL, 0);
--
fs/configfs/file.c-150- down_read(&frag->frag_sem);
fs/configfs/file.c:151: if (!frag->frag_dead)
fs/configfs/file.c-152- len = buffer->bin_attr->read(buffer->item,
--
fs/configfs/file.c=200=flush_write_buffer(struct file *file, struct configfs_buffer *buffer, size_t count)
--
fs/configfs/file.c-205- down_read(&frag->frag_sem);
fs/configfs/file.c:206: if (!frag->frag_dead)
fs/configfs/file.c-207- res = buffer->attr->store(buffer->item, buffer->page, count);
--
fs/configfs/file.c=290=static int __configfs_open_file(struct inode *inode, struct file *file, int type)
--
fs/configfs/file.c-304- down_read(&frag->frag_sem);
fs/configfs/file.c:305: if (unlikely(frag->frag_dead))
fs/configfs/file.c-306- goto out_free_buffer;
--
fs/configfs/file.c=400=static int configfs_release_bin_file(struct inode *inode, struct file *file)
--
fs/configfs/file.c-407- down_read(&frag->frag_sem);
fs/configfs/file.c:408: if (!frag->frag_dead) {
fs/configfs/file.c-409- /* result of ->release() is ignored */
]
|
| 389/4 |
2026/09/02 21:33 |
llm |
patch-generator |
0m
Model:
Tokens:
input: 18989
output: 37
thoughts: 63
Thoughts: **Diving into the ConfigFS Directory Removal Logic**
Okay, so I'm thinking we need to get a handle on what's happening when a directory gets removed in `configfs`. My gut tells me there could be some interesting interactions, particularly around group detachments. I'm going to start by zeroing in on `fs/configfs/dir.c`, specifically around the functions `configfs_rmdir`, `detach_groups`, and `configfs_detach_prep`. These seem like the key players in orchestrating the cleanup. I'll read through lines 480 to 750 of that file, as that range likely contains the heart of the logic I'm interested in. I expect to find the sequence of events here that explain how the system ensures that dependencies and group associations are correctly handled before the directory goes away. This should give me a solid understanding of how things are *supposed* to work.
|
| 390/4 |
2026/09/02 21:33 |
tool |
read-file |
0m
Args: map[File:fs/configfs/dir.c FirstLine:480 LineCount:100]
Results: map[Contents: 480: inode->i_size = 0;
481: inode->i_fop = &configfs_bin_file_operations;
482: } else {
483: inode->i_size = PAGE_SIZE;
484: inode->i_fop = &configfs_file_operations;
485: }
486: goto done;
487: }
488: }
489: spin_unlock(&configfs_dirent_lock);
490: done:
491: return d_splice_alias(inode, dentry);
492: }
493:
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: }
543:
544: /*
545: * Walk the tree, resetting CONFIGFS_USET_DROPPING wherever it was
546: * set.
547: */
548: static void configfs_detach_rollback(struct configfs_dirent *parent_sd)
549: {
550: struct configfs_dirent *sd;
551:
552: parent_sd->s_type &= ~CONFIGFS_USET_DROPPING;
553:
554: list_for_each_entry(sd, &parent_sd->s_children, s_sibling)
555: if (sd->s_type & CONFIGFS_USET_DEFAULT)
556: configfs_detach_rollback(sd);
557: }
558:
559: /*
560: * Find the next non-cursor. configfs_dirent_lock held by caller.
561: */
562: static struct configfs_dirent *next_dirent(struct configfs_dirent *parent,
563: struct configfs_dirent *last)
564: {
565: struct configfs_dirent *s;
566:
567: s = list_prepare_entry(last, &parent->s_children, s_sibling);
568:
569: list_for_each_entry_continue(s, &parent->s_children, s_sibling) {
570: if (s->s_element)
571: return s;
572: }
573: return NULL;
574: }
575:
576: static void detach_attrs(struct dentry *dentry)
577: {
578: struct configfs_dirent *parent_sd;
579: struct configfs_dirent *sd, *next;
]
|
| 391/4 |
2026/09/02 21:33 |
llm |
patch-generator |
0m
Model:
Tokens:
input: 20724
output: 36
thoughts: 30
Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience:
**Diving Back into configfs/dir.c**
Alright, time to refresh my memory and dig back into `fs/configfs/dir.c`. I need to revisit the code surrounding `detach_groups` and `configfs_rmdir`. I'm focusing specifically on how groups are handled during directory removal within configfs. I'm especially interested in the nuances of the detach operation. I wonder about the interaction between the file system's internal structures and the user's view of group membership in this context. I need to solidify my understanding of the exact sequence of events, and what might be the potential race conditions or edge cases that I need to be aware of. I'm really looking to understand the mechanics of the reference counting, memory management and how errors are handled. I'm hoping to gain a complete mental model of this part of the code. Let's see if this deeper look reveals anything insightful.
|
| 392/4 |
2026/09/02 21:34 |
tool |
read-file |
0m
Args: map[File:fs/configfs/dir.c FirstLine:660 LineCount:80]
Results: map[Contents: 660: struct dentry *dentry,
661: struct configfs_fragment *frag);
662: static void configfs_detach_group(struct dentry *dentry);
663:
664: static void detach_groups(struct dentry *dentry)
665: {
666: struct dentry *child;
667: struct configfs_dirent *parent_sd;
668: struct configfs_dirent *sd, *next;
669:
670: parent_sd = dentry->d_fsdata;
671: spin_lock(&configfs_dirent_lock);
672: for (sd = next_dirent(parent_sd, NULL); sd; sd = next) {
673: next = next_dirent(parent_sd, sd);
674: if (!(sd->s_type & CONFIGFS_USET_DEFAULT))
675: continue;
676:
677: child = dget(sd->s_dentry);
678: spin_unlock(&configfs_dirent_lock);
679:
680: inode_lock(d_inode(child));
681:
682: configfs_detach_group(child);
683: d_inode(child)->i_flags |= S_DEAD;
684: dont_mount(child);
685:
686: inode_unlock(d_inode(child));
687:
688: d_delete(child);
689: dput(child);
690: spin_lock(&configfs_dirent_lock);
691: }
692: spin_unlock(&configfs_dirent_lock);
693: }
694:
695: /*
696: * This fakes mkdir(2) on a default_groups[] entry. It
697: * creates a dentry, attachs it, and then does fixup
698: * on the sd->s_type.
699: *
700: * We could, perhaps, tweak our parent's ->mkdir for a minute and
701: * try using vfs_mkdir. Just a thought.
702: */
703: static int create_default_group(struct dentry *parent,
704: struct config_group *group,
705: struct configfs_fragment *frag)
706: {
707: int ret;
708: struct configfs_dirent *sd;
709: /* We trust the caller holds a reference to parent */
710: struct dentry *child;
711:
712: if (!group->cg_item.ci_name)
713: group->cg_item.ci_name = group->cg_item.ci_namebuf;
714:
715: ret = -ENOMEM;
716: child = d_alloc_name(parent, group->cg_item.ci_name);
717: if (child) {
718: d_add(child, NULL);
719:
720: ret = configfs_attach_group(&group->cg_item, child, frag);
721: if (!ret) {
722: sd = child->d_fsdata;
723: sd->s_type |= CONFIGFS_USET_DEFAULT;
724: } else {
725: BUG_ON(d_inode(child));
726: d_drop(child);
727: }
728: dput(child);
729: }
730:
731: return ret;
732: }
733:
734: static int populate_groups(struct config_group *group,
735: struct configfs_fragment *frag)
736: {
737: struct dentry *parent = group->cg_item.ci_dentry;
738: struct config_group *new_group;
739:
]
|
| 393/4 |
2026/09/02 21:34 |
llm |
patch-generator |
0m
Model:
Tokens:
input: 22169
output: 37
thoughts: 23
Thoughts: Here's my take on those thoughts, tailored for an expert audience:
**Diving into `configfs_rmdir`**
Alright, let's pull up `configfs_rmdir` in `fs/configfs/dir.c`. I'm curious to see how they're handling directory removal in the context of ConfigFS. Specifically, I want to refresh my memory on the interactions with the backing filesystem and the potential for any tricky race conditions, especially given ConfigFS's dynamic nature. Are they doing proper reference counting on the constituent objects? Are they safely handling the lifecycle of the internal data structures and file representations when a directory is removed? I'm hoping to see a clean implementation that aligns with the usual VFS paradigms, avoiding any dangling pointers or unexpected behavior after the directory is gone. Understanding this function's logic is crucial for confirming overall ConfigFS robustness. Let's see how they do it.
|
| 394/4 |
2026/09/02 21:34 |
tool |
read-file |
0m
Args: map[File:fs/configfs/dir.c FirstLine:1465 LineCount:85]
Results: map[Contents:1465: release_dentry_name_snapshot(&n);
1466: return ERR_PTR(ret);
1467: }
1468:
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:
]
|
| 395/4 |
2026/09/02 21:34 |
llm |
patch-generator |
0m
Model:
Tokens:
input: 23690
output: 37
thoughts: 25
Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience:
**Diving Deeper into `configfs_rmdir`**
Alright, so we're looking at `configfs_rmdir` and I'm interested in the aftermath of line 1550. My intuition tells me that's a critical point, likely where some important cleanup or state change occurs. I'm not just going to stop here, I need to see the *entire* call path and any interactions with data structures. Let's trace from that point onwards; I want to understand what resources are being freed, which locks are being released, and ultimately, how the system cleans up the directory and any associated objects. This probably will include calls to `kfree()` or `put_object()` or calls to `list_del()` calls. I suspect some refcounting is involved. I'm expecting some potential race conditions if not done correctly, so I'm paying extra attention to locking strategies and the order of operations. Let's see if this hunch is correct, or if there is something more clever happening.
|
| 396/4 |
2026/09/02 21:34 |
tool |
read-file |
0m
Args: map[File:fs/configfs/dir.c FirstLine:1550 LineCount:50]
Results: map[Contents: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: }
1578:
1579: const struct inode_operations configfs_dir_inode_operations = {
1580: .mkdir = configfs_mkdir,
1581: .rmdir = configfs_rmdir,
1582: .symlink = configfs_symlink,
1583: .unlink = configfs_unlink,
1584: .lookup = configfs_lookup,
1585: .setattr = configfs_setattr,
1586: };
1587:
1588: const struct inode_operations configfs_root_inode_operations = {
1589: .lookup = configfs_lookup,
1590: .setattr = configfs_setattr,
1591: };
1592:
1593: static int configfs_dir_open(struct inode *inode, struct file *file)
1594: {
1595: struct dentry * dentry = file->f_path.dentry;
1596: struct configfs_dirent * parent_sd = dentry->d_fsdata;
1597: int err;
1598:
1599: inode_lock(d_inode(dentry));
]
|
| 397/4 |
2026/09/02 21:34 |
llm |
patch-generator |
0m
Model:
Tokens:
input: 24722
output: 27
thoughts: 67
Thoughts: Here's my take on those thoughts:
**Investigating ConfigFS Group Management during Directory Creation**
Alright, let's dive into this. My initial curiosity lies in how ConfigFS handles default groups when a new directory is created. I want to see exactly what transpires under the hood. Specifically, I need to understand how the system manages group assignments.
My first port of call will be to examine the `configfs_attach_group` function and how it interacts with the process of establishing new directory fragments, particularly within `new_fragment`. I'm especially interested in how `sd->s_frag` is shared or assigned during this stage. That variable is key.
To get a clearer picture of how this is all implemented, I'll perform a focused search for all occurrences of `s_frag` within the `fs/configfs/` directory. That should give me a good overview of its usage within that subsystem and help me connect the dots between directory creation, group attachment, and fragment management. Let's see what the code reveals.
|
| 398/4 |
2026/09/02 21:34 |
tool |
grepper |
0m
Args: map[Expression:s_frag PathPrefix:fs/configfs]
Results: map[Output:fs/configfs/configfs_internal.h-20-
fs/configfs/configfs_internal.h:21:struct configfs_fragment {
fs/configfs/configfs_internal.h-22- atomic_t frag_count;
--
fs/configfs/configfs_internal.h-26-
fs/configfs/configfs_internal.h:27:void put_fragment(struct configfs_fragment *);
fs/configfs/configfs_internal.h:28:struct configfs_fragment *get_fragment(struct configfs_fragment *);
fs/configfs/configfs_internal.h-29-
fs/configfs/configfs_internal.h=30=struct configfs_dirent {
--
fs/configfs/configfs_internal.h-43-#endif
fs/configfs/configfs_internal.h:44: struct configfs_fragment *s_frag;
fs/configfs/configfs_internal.h-45-};
--
fs/configfs/configfs_internal.h=74=extern int configfs_make_dirent(struct configfs_dirent *, struct dentry *,
fs/configfs/configfs_internal.h:75: void *, umode_t, int, struct configfs_fragment *);
fs/configfs/configfs_internal.h-76-extern int configfs_dirent_is_ready(struct configfs_dirent *);
--
fs/configfs/configfs_internal.h=134=static inline void release_configfs_dirent(struct configfs_dirent * sd)
--
fs/configfs/configfs_internal.h-137- kfree(sd->s_iattr);
fs/configfs/configfs_internal.h:138: put_fragment(sd->s_frag);
fs/configfs/configfs_internal.h-139- kmem_cache_free(configfs_dir_cachep, sd);
--
fs/configfs/dir.c=152=configfs_adjust_dir_dirent_depth_after_populate(struct configfs_dirent *sd)
--
fs/configfs/dir.c-157-
fs/configfs/dir.c:158:static struct configfs_fragment *new_fragment(void)
fs/configfs/dir.c-159-{
fs/configfs/dir.c:160: struct configfs_fragment *p;
fs/configfs/dir.c-161-
fs/configfs/dir.c:162: p = kmalloc_obj(struct configfs_fragment);
fs/configfs/dir.c-163- if (p) {
--
fs/configfs/dir.c-170-
fs/configfs/dir.c:171:void put_fragment(struct configfs_fragment *frag)
fs/configfs/dir.c-172-{
--
fs/configfs/dir.c-176-
fs/configfs/dir.c:177:struct configfs_fragment *get_fragment(struct configfs_fragment *frag)
fs/configfs/dir.c-178-{
--
fs/configfs/dir.c=187=static struct configfs_dirent *configfs_new_dirent(struct configfs_dirent *parent_sd,
fs/configfs/dir.c-188- void *element, int type,
fs/configfs/dir.c:189: struct configfs_fragment *frag)
fs/configfs/dir.c-190-{
--
fs/configfs/dir.c-207- }
fs/configfs/dir.c:208: sd->s_frag = get_fragment(frag);
fs/configfs/dir.c-209-
--
fs/configfs/dir.c=253=int configfs_make_dirent(struct configfs_dirent * parent_sd,
fs/configfs/dir.c-254- struct dentry * dentry, void * element,
fs/configfs/dir.c:255: umode_t mode, int type, struct configfs_fragment *frag)
fs/configfs/dir.c-256-{
--
fs/configfs/dir.c=293=static int configfs_create_dir(struct config_item *item, struct dentry *dentry,
fs/configfs/dir.c:294: struct configfs_fragment *frag)
fs/configfs/dir.c-295-{
--
fs/configfs/dir.c=369=int configfs_create_link(struct configfs_dirent *target, struct dentry *parent,
--
fs/configfs/dir.c-378- err = configfs_make_dirent(p, dentry, target, mode, CONFIGFS_ITEM_LINK,
fs/configfs/dir.c:379: p->s_frag);
fs/configfs/dir.c-380- if (err)
--
fs/configfs/dir.c=659=static int configfs_attach_group(struct config_item *item,
fs/configfs/dir.c-660- struct dentry *dentry,
fs/configfs/dir.c:661: struct configfs_fragment *frag);
fs/configfs/dir.c-662-static void configfs_detach_group(struct dentry *dentry);
--
fs/configfs/dir.c=703=static int create_default_group(struct dentry *parent,
fs/configfs/dir.c-704- struct config_group *group,
fs/configfs/dir.c:705: struct configfs_fragment *frag)
fs/configfs/dir.c-706-{
--
fs/configfs/dir.c=734=static int populate_groups(struct config_group *group,
fs/configfs/dir.c:735: struct configfs_fragment *frag)
fs/configfs/dir.c-736-{
--
fs/configfs/dir.c=857=static int configfs_attach_item(struct config_item *item,
fs/configfs/dir.c-858- struct dentry *dentry,
fs/configfs/dir.c:859: struct configfs_fragment *frag)
fs/configfs/dir.c-860-{
--
fs/configfs/dir.c=891=static int configfs_attach_group(struct config_item *item,
fs/configfs/dir.c-892- struct dentry *dentry,
fs/configfs/dir.c:893: struct configfs_fragment *frag)
fs/configfs/dir.c-894-{
--
fs/configfs/dir.c=1289=static struct dentry *configfs_mkdir(struct mnt_idmap *idmap, struct inode *dir,
--
fs/configfs/dir.c-1300- struct module *subsys_owner = NULL, *new_item_owner = NULL;
fs/configfs/dir.c:1301: struct configfs_fragment *frag;
fs/configfs/dir.c-1302- struct name_snapshot n;
--
fs/configfs/dir.c=1469=static int configfs_rmdir(struct inode *dir, struct dentry *dentry)
--
fs/configfs/dir.c-1474- struct configfs_dirent *sd;
fs/configfs/dir.c:1475: struct configfs_fragment *frag;
fs/configfs/dir.c-1476- struct module *subsys_owner = NULL, *dead_item_owner = NULL;
--
fs/configfs/dir.c-1532-
fs/configfs/dir.c:1533: frag = sd->s_frag;
fs/configfs/dir.c-1534- if (down_write_killable(&frag->frag_sem)) {
--
fs/configfs/dir.c=1751=int configfs_register_group(struct config_group *parent_group,
--
fs/configfs/dir.c-1755- struct dentry *parent;
fs/configfs/dir.c:1756: struct configfs_fragment *frag;
fs/configfs/dir.c-1757- int ret;
--
fs/configfs/dir.c=1796=void configfs_unregister_group(struct config_group *group)
--
fs/configfs/dir.c-1801- struct configfs_dirent *sd = dentry->d_fsdata;
fs/configfs/dir.c:1802: struct configfs_fragment *frag = sd->s_frag;
fs/configfs/dir.c-1803-
--
fs/configfs/dir.c=1872=int configfs_register_subsystem(struct configfs_subsystem *subsys)
--
fs/configfs/dir.c-1878- struct configfs_dirent *sd;
fs/configfs/dir.c:1879: struct configfs_fragment *frag;
fs/configfs/dir.c-1880-
--
fs/configfs/dir.c=1934=void configfs_unregister_subsystem(struct configfs_subsystem *subsys)
--
fs/configfs/dir.c-1939- struct configfs_dirent *sd = dentry->d_fsdata;
fs/configfs/dir.c:1940: struct configfs_fragment *frag = sd->s_frag;
fs/configfs/dir.c-1941-
--
fs/configfs/file.c=29=struct configfs_buffer {
--
fs/configfs/file.c-48-
fs/configfs/file.c:49:static inline struct configfs_fragment *to_frag(struct file *file)
fs/configfs/file.c-50-{
--
fs/configfs/file.c-52-
fs/configfs/file.c:53: return sd->s_frag;
fs/configfs/file.c-54-}
--
fs/configfs/file.c=56=static int fill_read_buffer(struct file *file, struct configfs_buffer *buffer)
fs/configfs/file.c-57-{
fs/configfs/file.c:58: struct configfs_fragment *frag = to_frag(file);
fs/configfs/file.c-59- ssize_t count = -ENOENT;
--
fs/configfs/file.c=106=static ssize_t configfs_bin_read_iter(struct kiocb *iocb, struct iov_iter *to)
--
fs/configfs/file.c-108- struct file *file = iocb->ki_filp;
fs/configfs/file.c:109: struct configfs_fragment *frag = to_frag(file);
fs/configfs/file.c-110- struct configfs_buffer *buffer = file->private_data;
--
fs/configfs/file.c=200=flush_write_buffer(struct file *file, struct configfs_buffer *buffer, size_t count)
fs/configfs/file.c-201-{
fs/configfs/file.c:202: struct configfs_fragment *frag = to_frag(file);
fs/configfs/file.c-203- int res = -ENOENT;
--
fs/configfs/file.c=290=static int __configfs_open_file(struct inode *inode, struct file *file, int type)
--
fs/configfs/file.c-292- struct dentry *dentry = file->f_path.dentry;
fs/configfs/file.c:293: struct configfs_fragment *frag = to_frag(file);
fs/configfs/file.c-294- struct configfs_attribute *attr;
--
fs/configfs/file.c=400=static int configfs_release_bin_file(struct inode *inode, struct file *file)
--
fs/configfs/file.c-404- if (buffer->write_in_progress) {
fs/configfs/file.c:405: struct configfs_fragment *frag = to_frag(file);
fs/configfs/file.c-406-
--
fs/configfs/file.c=446=int configfs_create_file(struct config_item * item, const struct configfs_attribute * attr)
--
fs/configfs/file.c-454- error = configfs_make_dirent(parent_sd, NULL, (void *) attr, mode,
fs/configfs/file.c:455: CONFIGFS_ITEM_ATTR, parent_sd->s_frag);
fs/configfs/file.c-456- inode_unlock(d_inode(dir));
--
fs/configfs/file.c=467=int configfs_create_bin_file(struct config_item *item,
--
fs/configfs/file.c-476- error = configfs_make_dirent(parent_sd, NULL, (void *) bin_attr, mode,
fs/configfs/file.c:477: CONFIGFS_ITEM_BIN_ATTR, parent_sd->s_frag);
fs/configfs/file.c-478- inode_unlock(dir->d_inode);
]
|
| 399/4 |
2026/09/02 21:34 |
llm |
patch-generator |
0m
Model:
Tokens:
input: 28465
output: 27
thoughts: 196
Thoughts: **Analyzing ConfigFS Group Management and Cleanup**
Okay, let's break this down. I'm focusing on how ConfigFS handles the creation and destruction of groups, especially default ones, as it relates to the file system's structure. I'm zeroing in on `create_default_group` and `populate_groups` β those are key. It looks like `configfs_mkdir` is the driver here. I see it starts by creating a fragment with `frag = new_fragment()`. Then, the `configfs_attach_group` function calls `populate_groups` which in turn calls `create_default_group`. Aha! So, all the default groups and items that are created as part of this directory creation process, this hierarchy, they *all* share the *same* fragment, `s_frag`. That's the critical linkage.
Now, the interesting bit: when we get to `configfs_rmdir` and remove the root item, it pulls the `s_frag` from the dentry's filesystem data (`d_fsdata`). It sets `frag->frag_dead = true;`. So, that fragment, the *shared* fragment, becomes marked as dead. This means that all of those default subdirectories, which share that same `s_frag`, are effectively orphaned and, theoretically, subject to a cleanup process. Now, I need to look closer at what the implications of `frag->frag_dead` being set actually means in terms of preventing operations on subdirectories.
That linkage β the shared fragment and its subsequent "death" β that's the core. It's a fundamental aspect of how the group hierarchy is managed. I better go look at that commit now, `351e5d869e5a`, with `git-show` to see what changed and why. I'm keen to check if that commit has any bearing on the cleanup implications and/or lifecycle management of this shared fragment.
|
| 400/4 |
2026/09/02 21:34 |
tool |
git-show |
0m
Args:
Results: map[Output:commit 351e5d869e5ac10cb40c78b5f2d7dfc816ad4587
Author: Al Viro <viro@zeniv.linux.org.uk>
Date: Sat Aug 3 11:51:18 2019 -0400
configfs: fix a deadlock in configfs_symlink()
Configfs abuses symlink(2). Unlike the normal filesystems, it
wants the target resolved at symlink(2) time, like link(2) would've
done. The problem is that ->symlink() is called with the parent
directory locked exclusive, so resolving the target inside the
->symlink() is easily deadlocked.
Short of really ugly games in sys_symlink() itself, all we can
do is to unlock the parent before resolving the target and
relock it after. However, that invalidates the checks done
by the caller of ->symlink(), so we have to
* check that dentry is still where it used to be
(it couldn't have been moved, but it could've been unhashed)
* recheck that it's still negative (somebody else
might've successfully created a symlink with the same name
while we were looking the target up)
* recheck the permissions on the parent directory.
Cc: stable@vger.kernel.org
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
Signed-off-by: Christoph Hellwig <hch@lst.de>
diff --git a/fs/configfs/symlink.c b/fs/configfs/symlink.c
index 91eac6c55e07..f3881e4caedd 100644
--- a/fs/configfs/symlink.c
+++ b/fs/configfs/symlink.c
@@ -143,11 +143,42 @@ int configfs_symlink(struct inode *dir, struct dentry *dentry, const char *symna
!type->ct_item_ops->allow_link)
goto out_put;
+ /*
+ * This is really sick. What they wanted was a hybrid of
+ * link(2) and symlink(2) - they wanted the target resolved
+ * at syscall time (as link(2) would've done), be a directory
+ * (which link(2) would've refused to do) *AND* be a deep
+ * fucking magic, making the target busy from rmdir POV.
+ * symlink(2) is nothing of that sort, and the locking it
+ * gets matches the normal symlink(2) semantics. Without
+ * attempts to resolve the target (which might very well
+ * not even exist yet) done prior to locking the parent
+ * directory. This perversion, OTOH, needs to resolve
+ * the target, which would lead to obvious deadlocks if
+ * attempted with any directories locked.
+ *
+ * Unfortunately, that garbage is userland ABI and we should've
+ * said "no" back in 2005. Too late now, so we get to
+ * play very ugly games with locking.
+ *
+ * Try *ANYTHING* of that sort in new code, and you will
+ * really regret it. Just ask yourself - what could a BOFH
+ * do to me and do I want to find it out first-hand?
+ *
+ * AV, a thoroughly annoyed bastard.
+ */
+ inode_unlock(dir);
ret = get_target(symname, &path, &target_item, dentry->d_sb);
+ inode_lock(dir);
if (ret)
goto out_put;
- ret = type->ct_item_ops->allow_link(parent_item, target_item);
+ if (dentry->d_inode || d_unhashed(dentry))
+ ret = -EEXIST;
+ else
+ ret = inode_permission(dir, MAY_WRITE | MAY_EXEC);
+ if (!ret)
+ ret = type->ct_item_ops->allow_link(parent_item, target_item);
if (!ret) {
mutex_lock(&configfs_symlink_mutex);
ret = create_link(parent_item, target_item, dentry);
]
|
| 401/4 |
2026/09/02 21:34 |
llm |
patch-generator |
0m
Model:
Tokens:
input: 30219
output: 25
thoughts: 39
Thoughts: Okay, here's my interpretation of those thoughts, presented as a personal thought process, as requested:
**Digging into the Implementation Details**
Alright, before I give a definitive answer, I'm going to quickly refresh my memory on some related implementations. Specifically, I need to check how the changes in commit `ba80aa909c99` were approached. I recall that touched on a particular area, and I want to make sure I'm not overlooking any subtle nuances in its logic. Then, just to be thorough and ensure I have the full picture, I also want to go back and check how commit `b0841eefd969` dealt with its respective changes. I remember the goal was similar, so the differences in implementation will give the best comparison. I want to see if any of those commits contain relevant solutions to my issue, or if their approach provides valuable context. It's always beneficial to review related code when assessing and defining a solution, right?
|
| 402/4 |
2026/09/02 21:34 |
tool |
git-show |
0m
Args:
Results: map[Output:commit ba80aa909c99802c428682c352b0ee0baac0acd3
Author: Nicholas Bellinger <nab@linux-iscsi.org>
Date: Thu Jun 8 04:51:54 2017 +0000
configfs: Fix race between create_link and configfs_rmdir
This patch closes a long standing race in configfs between
the creation of a new symlink in create_link(), while the
symlink target's config_item is being concurrently removed
via configfs_rmdir().
This can happen because the symlink target's reference
is obtained by config_item_get() in create_link() before
the CONFIGFS_USET_DROPPING bit set by configfs_detach_prep()
during configfs_rmdir() shutdown is actually checked..
This originally manifested itself on ppc64 on v4.8.y under
heavy load using ibmvscsi target ports with Novalink API:
[ 7877.289863] rpadlpar_io: slot U8247.22L.212A91A-V1-C8 added
[ 7879.893760] ------------[ cut here ]------------
[ 7879.893768] WARNING: CPU: 15 PID: 17585 at ./include/linux/kref.h:46 config_item_get+0x7c/0x90 [configfs]
[ 7879.893811] CPU: 15 PID: 17585 Comm: targetcli Tainted: G O 4.8.17-customv2.22 #12
[ 7879.893812] task: c00000018a0d3400 task.stack: c0000001f3b40000
[ 7879.893813] NIP: d000000002c664ec LR: d000000002c60980 CTR: c000000000b70870
[ 7879.893814] REGS: c0000001f3b43810 TRAP: 0700 Tainted: G O (4.8.17-customv2.22)
[ 7879.893815] MSR: 8000000000029033 <SF,EE,ME,IR,DR,RI,LE> CR: 28222242 XER: 00000000
[ 7879.893820] CFAR: d000000002c664bc SOFTE: 1
GPR00: d000000002c60980 c0000001f3b43a90 d000000002c70908 c0000000fbc06820
GPR04: c0000001ef1bd900 0000000000000004 0000000000000001 0000000000000000
GPR08: 0000000000000000 0000000000000001 d000000002c69560 d000000002c66d80
GPR12: c000000000b70870 c00000000e798700 c0000001f3b43ca0 c0000001d4949d40
GPR16: c00000014637e1c0 0000000000000000 0000000000000000 c0000000f2392940
GPR20: c0000001f3b43b98 0000000000000041 0000000000600000 0000000000000000
GPR24: fffffffffffff000 0000000000000000 d000000002c60be0 c0000001f1dac490
GPR28: 0000000000000004 0000000000000000 c0000001ef1bd900 c0000000f2392940
[ 7879.893839] NIP [d000000002c664ec] config_item_get+0x7c/0x90 [configfs]
[ 7879.893841] LR [d000000002c60980] check_perm+0x80/0x2e0 [configfs]
[ 7879.893842] Call Trace:
[ 7879.893844] [c0000001f3b43ac0] [d000000002c60980] check_perm+0x80/0x2e0 [configfs]
[ 7879.893847] [c0000001f3b43b10] [c000000000329770] do_dentry_open+0x2c0/0x460
[ 7879.893849] [c0000001f3b43b70] [c000000000344480] path_openat+0x210/0x1490
[ 7879.893851] [c0000001f3b43c80] [c00000000034708c] do_filp_open+0xfc/0x170
[ 7879.893853] [c0000001f3b43db0] [c00000000032b5bc] do_sys_open+0x1cc/0x390
[ 7879.893856] [c0000001f3b43e30] [c000000000009584] system_call+0x38/0xec
[ 7879.893856] Instruction dump:
[ 7879.893858] 409d0014 38210030 e8010010 7c0803a6 4e800020 3d220000 e94981e0 892a0000
[ 7879.893861] 2f890000 409effe0 39200001 992a0000 <0fe00000> 4bffffd0 60000000 60000000
[ 7879.893866] ---[ end trace 14078f0b3b5ad0aa ]---
To close this race, go ahead and obtain the symlink's target
config_item reference only after the existing CONFIGFS_USET_DROPPING
check succeeds.
This way, if configfs_rmdir() wins create_link() will return -ENONET,
and if create_link() wins configfs_rmdir() will return -EBUSY.
Reported-by: Bryant G. Ly <bryantly@linux.vnet.ibm.com>
Tested-by: Bryant G. Ly <bryantly@linux.vnet.ibm.com>
Signed-off-by: Nicholas Bellinger <nab@linux-iscsi.org>
Signed-off-by: Christoph Hellwig <hch@lst.de>
Cc: stable@vger.kernel.org
diff --git a/fs/configfs/symlink.c b/fs/configfs/symlink.c
index a6ab012a2c6a..c8aabba502f6 100644
--- a/fs/configfs/symlink.c
+++ b/fs/configfs/symlink.c
@@ -83,14 +83,13 @@ static int create_link(struct config_item *parent_item,
ret = -ENOMEM;
sl = kmalloc(sizeof(struct configfs_symlink), GFP_KERNEL);
if (sl) {
- sl->sl_target = config_item_get(item);
spin_lock(&configfs_dirent_lock);
if (target_sd->s_type & CONFIGFS_USET_DROPPING) {
spin_unlock(&configfs_dirent_lock);
- config_item_put(item);
kfree(sl);
return -ENOENT;
}
+ sl->sl_target = config_item_get(item);
list_add(&sl->sl_list, &target_sd->s_links);
spin_unlock(&configfs_dirent_lock);
ret = configfs_create_link(sl, parent_item->ci_dentry,
]
|
| 403/4 |
2026/09/02 21:34 |
llm |
patch-generator |
0m
Model:
Tokens:
input: 33101
output: 26
thoughts: 19
Thoughts: Okay, here's my interpretation of that thought, summarized in the first person, assuming an expert audience:
**Diving Deeper: Exploring `b0841eefd969`**
Alright, so we've covered the broader concepts, the established methodologies. Now, let's get into the specifics, the nitty-gritty. I'm keen to examine this particular identifier, `b0841eefd969`. It's likely a critical piece of the puzzle, a specific data point, a specific artifact, or a particular code segment that warrants closer inspection. My initial hypothesis is that it might be revealing some key underlying operational logic, or perhaps point to a crucial vulnerability. Or perhaps I just need to remind myself of the context around it. It could be some subtle but essential detail. Whatever it is, I can't leave it unexamined. Time to dig in and understand its impact on the bigger picture.
|
| 404/4 |
2026/09/02 21:34 |
tool |
git-show |
0m
Args:
Results: map[Output:commit b0841eefd9693827afb9888235e26ddd098f9cef
Author: Al Viro <viro@zeniv.linux.org.uk>
Date: Sat Aug 31 09:43:43 2019 +0200
configfs: provide exclusion between IO and removals
Make sure that attribute methods are not called after the item
has been removed from the tree. To do so, we
* at the point of no return in removals, grab ->frag_sem
exclusive and mark the fragment dead.
* call the methods of attributes with ->frag_sem taken
shared and only after having verified that the fragment is still
alive.
The main benefit is for method instances - they are
guaranteed that the objects they are accessing *and* all ancestors
are still there. Another win is that we don't need to bother
with extra refcount on config_item when opening a file -
the item will be alive for as long as it stays in the tree, and
we won't touch it/attributes/any associated data after it's
been removed from the tree.
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
Signed-off-by: Christoph Hellwig <hch@lst.de>
diff --git a/fs/configfs/dir.c b/fs/configfs/dir.c
index 1df65c2440da..79fc25aaa8cd 100644
--- a/fs/configfs/dir.c
+++ b/fs/configfs/dir.c
@@ -1461,6 +1461,7 @@ static int configfs_rmdir(struct inode *dir, struct dentry *dentry)
struct config_item *item;
struct configfs_subsystem *subsys;
struct configfs_dirent *sd;
+ struct configfs_fragment *frag;
struct module *subsys_owner = NULL, *dead_item_owner = NULL;
int ret;
@@ -1518,6 +1519,16 @@ static int configfs_rmdir(struct inode *dir, struct dentry *dentry)
}
} while (ret == -EAGAIN);
+ frag = sd->s_frag;
+ if (down_write_killable(&frag->frag_sem)) {
+ spin_lock(&configfs_dirent_lock);
+ configfs_detach_rollback(dentry);
+ spin_unlock(&configfs_dirent_lock);
+ return -EINTR;
+ }
+ frag->frag_dead = true;
+ up_write(&frag->frag_sem);
+
/* Get a working ref for the duration of this function */
item = configfs_get_config_item(dentry);
@@ -1821,6 +1832,12 @@ void configfs_unregister_group(struct config_group *group)
struct configfs_subsystem *subsys = group->cg_subsys;
struct dentry *dentry = group->cg_item.ci_dentry;
struct dentry *parent = group->cg_item.ci_parent->ci_dentry;
+ struct configfs_dirent *sd = dentry->d_fsdata;
+ struct configfs_fragment *frag = sd->s_frag;
+
+ down_write(&frag->frag_sem);
+ frag->frag_dead = true;
+ up_write(&frag->frag_sem);
inode_lock_nested(d_inode(parent), I_MUTEX_PARENT);
spin_lock(&configfs_dirent_lock);
@@ -1947,12 +1964,18 @@ void configfs_unregister_subsystem(struct configfs_subsystem *subsys)
struct config_group *group = &subsys->su_group;
struct dentry *dentry = group->cg_item.ci_dentry;
struct dentry *root = dentry->d_sb->s_root;
+ struct configfs_dirent *sd = dentry->d_fsdata;
+ struct configfs_fragment *frag = sd->s_frag;
if (dentry->d_parent != root) {
pr_err("Tried to unregister non-subsystem!\n");
return;
}
+ down_write(&frag->frag_sem);
+ frag->frag_dead = true;
+ up_write(&frag->frag_sem);
+
inode_lock_nested(d_inode(root),
I_MUTEX_PARENT);
inode_lock_nested(d_inode(dentry), I_MUTEX_CHILD);
diff --git a/fs/configfs/file.c b/fs/configfs/file.c
index 9d5e888df60b..fb65b706cc0d 100644
--- a/fs/configfs/file.c
+++ b/fs/configfs/file.c
@@ -48,22 +48,32 @@ struct configfs_buffer {
};
};
+static inline struct configfs_fragment *to_frag(struct file *file)
+{
+ struct configfs_dirent *sd = file->f_path.dentry->d_fsdata;
+
+ return sd->s_frag;
+}
-static int fill_read_buffer(struct configfs_buffer * buffer)
+static int fill_read_buffer(struct file *file, struct configfs_buffer *buffer)
{
- ssize_t count;
+ struct configfs_fragment *frag = to_frag(file);
+ ssize_t count = -ENOENT;
if (!buffer->page)
buffer->page = (char *) get_zeroed_page(GFP_KERNEL);
if (!buffer->page)
return -ENOMEM;
- count = buffer->attr->show(buffer->item, buffer->page);
+ down_read(&frag->frag_sem);
+ if (!frag->frag_dead)
+ count = buffer->attr->show(buffer->item, buffer->page);
+ up_read(&frag->frag_sem);
+
if (count < 0)
return count;
if (WARN_ON_ONCE(count > (ssize_t)SIMPLE_ATTR_SIZE))
return -EIO;
-
buffer->needs_read_fill = 0;
buffer->count = count;
return 0;
@@ -96,7 +106,7 @@ configfs_read_file(struct file *file, char __user *buf, size_t count, loff_t *pp
mutex_lock(&buffer->mutex);
if (buffer->needs_read_fill) {
- retval = fill_read_buffer(buffer);
+ retval = fill_read_buffer(file, buffer);
if (retval)
goto out;
}
@@ -133,6 +143,7 @@ static ssize_t
configfs_read_bin_file(struct file *file, char __user *buf,
size_t count, loff_t *ppos)
{
+ struct configfs_fragment *frag = to_frag(file);
struct configfs_buffer *buffer = file->private_data;
ssize_t retval = 0;
ssize_t len = min_t(size_t, count, PAGE_SIZE);
@@ -148,7 +159,12 @@ configfs_read_bin_file(struct file *file, char __user *buf,
if (buffer->needs_read_fill) {
/* perform first read with buf == NULL to get extent */
- len = buffer->bin_attr->read(buffer->item, NULL, 0);
+ down_read(&frag->frag_sem);
+ if (!frag->frag_dead)
+ len = buffer->bin_attr->read(buffer->item, NULL, 0);
+ else
+ len = -ENOENT;
+ up_read(&frag->frag_sem);
if (len <= 0) {
retval = len;
goto out;
@@ -168,8 +184,13 @@ configfs_read_bin_file(struct file *file, char __user *buf,
buffer->bin_buffer_size = len;
/* perform second read to fill buffer */
- len = buffer->bin_attr->read(buffer->item,
- buffer->bin_buffer, len);
+ down_read(&frag->frag_sem);
+ if (!frag->frag_dead)
+ len = buffer->bin_attr->read(buffer->item,
+ buffer->bin_buffer, len);
+ else
+ len = -ENOENT;
+ up_read(&frag->frag_sem);
if (len < 0) {
retval = len;
vfree(buffer->bin_buffer);
@@ -220,9 +241,16 @@ fill_write_buffer(struct configfs_buffer * buffer, const char __user * buf, size
}
static int
-flush_write_buffer(struct configfs_buffer *buffer, size_t count)
+flush_write_buffer(struct file *file, struct configfs_buffer *buffer, size_t count)
{
- return buffer->attr->store(buffer->item, buffer->page, count);
+ struct configfs_fragment *frag = to_frag(file);
+ int res = -ENOENT;
+
+ down_read(&frag->frag_sem);
+ if (!frag->frag_dead)
+ res = buffer->attr->store(buffer->item, buffer->page, count);
+ up_read(&frag->frag_sem);
+ return res;
}
@@ -252,7 +280,7 @@ configfs_write_file(struct file *file, const char __user *buf, size_t count, lof
mutex_lock(&buffer->mutex);
len = fill_write_buffer(buffer, buf, count);
if (len > 0)
- len = flush_write_buffer(buffer, len);
+ len = flush_write_buffer(file, buffer, len);
if (len > 0)
*ppos += len;
mutex_unlock(&buffer->mutex);
@@ -328,6 +356,7 @@ configfs_write_bin_file(struct file *file, const char __user *buf,
static int __configfs_open_file(struct inode *inode, struct file *file, int type)
{
struct dentry *dentry = file->f_path.dentry;
+ struct configfs_fragment *frag = to_frag(file);
struct configfs_attribute *attr;
struct configfs_buffer *buffer;
int error;
@@ -337,8 +366,13 @@ static int __configfs_open_file(struct inode *inode, struct file *file, int type
if (!buffer)
goto out;
+ error = -ENOENT;
+ down_read(&frag->frag_sem);
+ if (unlikely(frag->frag_dead))
+ goto out_free_buffer;
+
error = -EINVAL;
- buffer->item = configfs_get_config_item(dentry->d_parent);
+ buffer->item = to_item(dentry->d_parent);
if (!buffer->item)
goto out_free_buffer;
@@ -396,6 +430,7 @@ static int __configfs_open_file(struct inode *inode, struct file *file, int type
buffer->read_in_progress = false;
buffer->write_in_progress = false;
file->private_data = buffer;
+ up_read(&frag->frag_sem);
return 0;
out_put_module:
@@ -403,6 +438,7 @@ static int __configfs_open_file(struct inode *inode, struct file *file, int type
out_put_item:
config_item_put(buffer->item);
out_free_buffer:
+ up_read(&frag->frag_sem);
kfree(buffer);
out:
return error;
@@ -412,8 +448,6 @@ static int configfs_release(struct inode *inode, struct file *filp)
{
struct configfs_buffer *buffer = filp->private_data;
- if (buffer->item)
- config_item_put(buffer->item);
module_put(buffer->owner);
if (buffer->page)
free_page((unsigned long)buffer->page);
@@ -439,12 +473,17 @@ static int configfs_release_bin_file(struct inode *inode, struct file *file)
buffer->read_in_progress = false;
if (buffer->write_in_progress) {
+ struct configfs_fragment *frag = to_frag(file);
buffer->write_in_progress = false;
- /* result of ->release() is ignored */
- buffer->bin_attr->write(buffer->item, buffer->bin_buffer,
- buffer->bin_buffer_size);
-
+ down_read(&frag->frag_sem);
+ if (!frag->frag_dead) {
+ /* result of ->release() is ignored */
+ buffer->bin_attr->write(buffer->item,
+ buffer->bin_buffer,
+ buffer->bin_buffer_size);
+ }
+ up_read(&frag->frag_sem);
/* vfree on NULL is safe */
vfree(buffer->bin_buffer);
buffer->bin_buffer = NULL;
]
|
| 405/4 |
2026/09/02 21:34 |
llm |
patch-generator |
0m
Model:
Tokens:
input: 36888
output: 41
thoughts: 1818
Thoughts: **Deep Dive into ConfigFS Symlink Creation**
Alright, let's dissect this `configfs_symlink` function, meticulously. First, the code focuses on how symlinks are created in ConfigFS, specifically `fs/configfs/symlink.c`. I need to ensure I grasp the entirety of the mechanism.
I see that the core of the function handles permission checks, target resolution (which, as the comment laments, is a bit of a historical kludge), and finally the actual link creation. The initial check filters `dentry` by looking at the parent's `sd` which is `dentry->d_parent->d_fsdata` and then uses `configfs_dirent_is_ready`.
The code takes `dentry` and it's parent `dentry->d_parent`, and then gets the `parent_item` and the `target_item`.
The real pain point, and I fully concur with the tone of the comments, is the forced synchronous resolution of the target, seemingly emulating a hybrid `link(2)` and `symlink(2)` behavior. This is fundamentally at odds with the typical semantics of `symlink(2)` and introduces locking complexities. This synchronous resolution is a serious design smell. Locking is a mess and causes problems.
The code then checks that the `dentry` doesn't already exist and that appropriate permissions are in place. The `allow_link` and `create_link` operations are then invoked, using a mutex to protect the `create_link` operation, which is then undone using `drop_link`.
Next, I'm examining how this relates to finding the root item using `configfs_get_root_item()`. The critical piece here is understanding how "default groups" are handled, and how these nested groups are treated. I see how the `CONFIGFS_USET_DEFAULT` flag is set for default groups using `create_default_group`. My goal here is to follow the logic to trace how the `root_item` is obtained, particularly in cases involving nested default groups.
It's clear that the loop in `configfs_get_root_item` iterates upwards from the link's parent `dentry` until it encounters a directory that *isn't* a default group (or until the root is reached). This looks like it is intended to find the containing "real" configfs item. But I need to be sure.
I've got it, I think: the loop is designed to skip over nested default groups. In the case I'm most interested in, creating a symlink in a nested structure like `/config/ports/1/subsystems/target`, the algorithm correctly finds the immediate parent that *wasn't* created via `create_default_group`.
There's a subtle point that needs to be clarified: what happens if the `dentry` is already the root item (i.e., not a default group)? The loop would immediately terminate, and `configfs_get_root_item` would call `configfs_get_config_item` on the current `dentry`, which is the parent of the symlink. This raises a potential issue; there may be a redundant reference count. Is the `parent_item` already retrieved by another call? My thought is this could result in an unnecessary `config_item_put` down the line. I'll need to double check how reference counting works across this particular code path.
And finally, my investigation of `configfs_get_config_item` needs more research. I want to fully understand the effects of retrieving the root item. This is critical to the refcounting logic.
|
| 406/4 |
2026/09/02 21:34 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/configfs/symlink.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]
|
| 407/4 |
2026/09/02 21:34 |
llm |
patch-generator |
0m
Model:
Tokens:
input: 39761
output: 38
thoughts: 1590
Thoughts: **Analysis of Concurrency in configfs_symlink and configfs_rmdir**
Okay, let's break this down. I'm looking at how `configfs_symlink` and `configfs_rmdir` interact, specifically with the `nvmet_port` and its associated structures. The core issue seems to be ensuring that we don't try to access freed memory due to concurrent operations.
First, I need to understand what `configfs_get_config_item(dentry)` returns. It grabs the `config_item` associated with the `dentry`, which for directories/groups means `sd->s_element` is that `struct config_item *`.
Now, let's trace `configfs_symlink()`. Before it releases `inode_unlock(dir)`, it holds `inode_lock(dir)`, where `dir` is `dentry->d_parent`. The question is, can a concurrent `rmdir` on an ancestor or the target itself make the target invalid *before* `inode_unlock(dir)` is called?
So, I trace `configfs_rmdir`. If someone calls `rmdir("ports/1")`, `configfs_rmdir` sets `CONFIGFS_USET_DROPPING` on `ports/1` and its default groups, then detaches them, and then drops the references to it. Let's really hammer this down:
1. `config_detach_prep` and marking as dropping.
2. `detach_groups()` detaches the default groups (like `ports/1/subsystems`). During detachment, `inode_lock(d_inode(child))` is obtained, then `configfs_detach_group` is called, then the inode is marked as dead and then unlocked. Finally the child is deleted.
3. `unlink_group` unlinks the group.
4. User references are dropped.
5. `config_item_put` drops rmdir's reference, potentially freeing the `nvmet_port` if the refcount hits zero.
Now, here's the crucial point: *while* `configfs_symlink` *still* holds the `inode_lock(dir)`, it's holding `inode_lock(d_inode(ports/1/subsystems))` if it is indeed detaching subsystems (I had to check). Concurrent `configfs_rmdir` *must* block at `inode_lock(d_inode(subsystems))`. It cannot complete its detach, unlink, or free operations before `configfs_symlink` releases `inode_lock(dir)`! Thus the dentry chain is still valid!
Next, inside `configfs_symlink`, before `inode_unlock(dir)`, I see `root_item = configfs_get_root_item(dentry->d_parent);`. Let's clarify what this `root_item` is. For `ports/1/subsystems`, the `root_dentry` starts at that dentry. The loop walks up to `ports/1`, where it *stops* because `ports/1` is not a default group. `root_item` *is* the `struct config_item` for `ports/1`, that dynamically allocated `struct nvmet_port` memory. Importantly, calling `config_item_get()` increases the refcount of `ports/1`. *This* is key: as long as `root_item` exists, `ports/1` and any nested default groups inside the `nvmet_port` CANNOT be freed via `kfree` because the reference is held.
Let's test edge cases. If `ports/1` is nested in other default groups like `default1 / default2 / default3`, the walking up happens until it gets the parent (`parent_struct`). So the parent will be protected. If a symlink is made *directly* to `ports/1`, the item will be `ports/1`. So the same protection is guaranteed.
Now, what if `root_item` is NULL? Could `configfs_get_root_item()` return NULL? I trace back to `configfs_get_config_item(dentry)`. It's protected by a spinlock. It should *not* be possible for `root_dentry` to be unhashed while we are holding `inode_lock(dir)` because rmdir has to lock all children before deleting them. But, even if there's a problem there, it shouldn't matter because of the locks. So as long as there are references to the item, it is protected. Let me double check what `config_item_put(NULL)` does, just in case.
|
| 408/4 |
2026/09/02 21:34 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/configfs/symlink.c Name:config_item_put]
Results: map[SourceCode: 140: /**
141: * config_item_put - decrement refcount for item.
142: * @item: item.
143: *
144: * Decrement the refcount, and if 0, call config_item_cleanup().
145: */
146: void config_item_put(struct config_item *item)
147: {
148: if (item)
149: kref_put(&item->ci_kref, config_item_release);
150: }
SourceFile:fs/configfs/item.c]
|
| 409/4 |
2026/09/02 21:34 |
llm |
patch-generator |
0m
Model:
Tokens:
input: 42598
output: 39
thoughts: 1932
Thoughts: **Analyzing the Concurrency in `configfs_symlink`**
Okay, so I need to understand what happens during a `configfs_symlink` operation, particularly in the face of concurrent `rmdir` calls. The first thing I note is that `config_item_put(NULL)` is safe; that's a good starting point. My concern here is centered around `root_item` and what is happening during the execution of `configfs_symlink()`. Let's step through `configfs_symlink()` carefully:
First, I get the parent item. I need to ensure it's "ready". Then I get the parent item. Next I verify that the item has the appropriate item operations to allow for a link. I get the root object (which could be the `ports/1` directory in this example) via `configfs_get_root_item()`. This is crucial because it pins the root so the parent isn't freed under us while we're dealing with it.
I see the comment about the "sick" hybrid behavior. It's a bit of a hack where the target of the symlink is resolved at syscall time, which is dangerous, and the author is not happy. The core issue is that this implementation needs to resolve the target _before_ the parent directory is locked, which can lead to deadlocks. The author warns against this approach, which is good to know, but doesn's solve my current problem.
The code then unlocks the parent directory inode (`inode_unlock(dir)`) before resolving the `target_item`. This is where the race condition could occur. It calls `get_target()`, which is a key function to examine. After getting the target, the code re-locks the inode (`inode_lock(dir)`). It then checks if the dentry exists or if it's unhashed, performs a permission check and then this is where things get interesting. If the dentry `s_frag->frag_dead`, it returns -ENOENT. If not, it calls the `allow_link` operation and tries to create the link under a mutex. Finally, any dropped link operations are called, and both `target_item` and `root_item` are released, followed by `parent_item`.
Now, let's trace this whole thing with the race. I will assume Thread 1 calls `configfs_symlink()`, and Thread 2 runs `configfs_rmdir()` concurrently.
1. `configfs_symlink` calls `configfs_get_root_item(dentry->d_parent)` which gets a reference to `ports/1` (`root_item`).
2. `configfs_symlink` calls `inode_unlock(dir)` (unlocks `ports/1/subsystems`).
3. _This is the window of opportunity_. Thread 2 calls `configfs_rmdir()` on `ports/1`.
4. Thread 2 sets `CONFIGFS_USET_DROPPING` on `ports/1` and `ports/1/subsystems`.
5. Thread 2 sets `frag->frag_dead = true` (under `frag_sem`). Note: `sd->s_frag` is the same fragment for `ports/1` and `ports/1/subsystems`.
6. Thread 2 calls `configfs_detach_group()` which locks `ports/1/subsystems`, marks it dead, detaches it, and unlocks it.
7. Thread 2 calls `unlink_group()` on `ports/1` and `client_drop_item()` on `ports/1`.
8. Thread 2 calls `config_item_put(ports/1)`. _This is important_. Because Thread 1 holds `root_item` (a reference to `ports/1`), the kref of `ports/1` does NOT reach 0! So `ports/1` (and the embedded `ports/1/subsystems`) is NOT freed!
9. Thread 1 (in `configfs_symlink`) resolves `target_item`, and calls `inode_lock(dir)` to re-lock `ports/1/subsystems`.
10. `get_target` returned 0.
11. Thread 1 checks: `if (dentry->d_inode || d_unhashed(dentry))`. _Wait!_ What is `dentry` here? `dentry` is the new symlink name being created inside `ports/1/subsystems`. Wait, when `detach_groups()` ran on `ports/1`, it did `d_delete(child)` on `ports/1
|
| 410/4 |
2026/09/02 21:34 |
tool |
grepper |
0m
Args: map[Expression:config_item_cleanup|config_item_release|cg_item PathPrefix:fs/configfs]
Results: map[Output:fs/configfs/dir.c=703=static int create_default_group(struct dentry *parent,
--
fs/configfs/dir.c-711-
fs/configfs/dir.c:712: if (!group->cg_item.ci_name)
fs/configfs/dir.c:713: group->cg_item.ci_name = group->cg_item.ci_namebuf;
fs/configfs/dir.c-714-
fs/configfs/dir.c-715- ret = -ENOMEM;
fs/configfs/dir.c:716: child = d_alloc_name(parent, group->cg_item.ci_name);
fs/configfs/dir.c-717- if (child) {
--
fs/configfs/dir.c-719-
fs/configfs/dir.c:720: ret = configfs_attach_group(&group->cg_item, child, frag);
fs/configfs/dir.c-721- if (!ret) {
--
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=748=void configfs_remove_default_groups(struct config_group *group)
--
fs/configfs/dir.c-753- list_del(&g->group_entry);
fs/configfs/dir.c:754: config_item_put(&g->cg_item);
fs/configfs/dir.c-755- }
--
fs/configfs/dir.c=805=static void unlink_group(struct config_group *group)
--
fs/configfs/dir.c-812- group->cg_subsys = NULL;
fs/configfs/dir.c:813: unlink_obj(&group->cg_item);
fs/configfs/dir.c-814-}
--
fs/configfs/dir.c=816=static void link_group(struct config_group *parent_group, struct config_group *group)
--
fs/configfs/dir.c-820-
fs/configfs/dir.c:821: link_obj(&parent_group->cg_item, &group->cg_item);
fs/configfs/dir.c-822-
--
fs/configfs/dir.c-824- subsys = parent_group->cg_subsys;
fs/configfs/dir.c:825: else if (configfs_is_root(&parent_group->cg_item))
fs/configfs/dir.c-826- subsys = to_configfs_subsystem(group);
--
fs/configfs/dir.c=1143=int configfs_depend_item(struct configfs_subsystem *subsys,
--
fs/configfs/dir.c-1147- struct configfs_dirent *subsys_sd;
fs/configfs/dir.c:1148: struct config_item *s_item = &subsys->su_group.cg_item;
fs/configfs/dir.c-1149- struct dentry *root;
--
fs/configfs/dir.c=1225=int configfs_depend_item_unlocked(struct configfs_subsystem *caller_subsys,
--
fs/configfs/dir.c-1241- */
fs/configfs/dir.c:1242: if (configfs_is_root(&parent->cg_item)) {
fs/configfs/dir.c-1243- target_subsys = to_configfs_subsystem(to_config_group(target));
--
fs/configfs/dir.c-1247- /* Find a cofnigfs root as we may need it for locking */
fs/configfs/dir.c:1248: for (root = parent; !configfs_is_root(&root->cg_item);
fs/configfs/dir.c:1249: root = root->cg_item.ci_group)
fs/configfs/dir.c-1250- ;
--
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-1268- if (!subsys_sd)
--
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=1289=static struct dentry *configfs_mkdir(struct mnt_idmap *idmap, struct inode *dir,
--
fs/configfs/dir.c-1347- */
fs/configfs/dir.c:1348: if (!subsys->su_group.cg_item.ci_type) {
fs/configfs/dir.c-1349- ret = -EINVAL;
--
fs/configfs/dir.c-1351- }
fs/configfs/dir.c:1352: subsys_owner = subsys->su_group.cg_item.ci_type->ct_owner;
fs/configfs/dir.c-1353- if (!try_module_get(subsys_owner)) {
--
fs/configfs/dir.c-1364- link_group(to_config_group(parent_item), group);
fs/configfs/dir.c:1365: item = &group->cg_item;
fs/configfs/dir.c-1366- } else
--
fs/configfs/dir.c=1469=static int configfs_rmdir(struct inode *dir, struct dentry *dentry)
--
fs/configfs/dir.c-1493- /* configfs_mkdir() shouldn't have allowed this */
fs/configfs/dir.c:1494: BUG_ON(!subsys->su_group.cg_item.ci_type);
fs/configfs/dir.c:1495: subsys_owner = subsys->su_group.cg_item.ci_type->ct_owner;
fs/configfs/dir.c-1496-
--
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=1872=int configfs_register_subsystem(struct configfs_subsystem *subsys)
--
fs/configfs/dir.c-1890-
fs/configfs/dir.c:1891: if (!group->cg_item.ci_name)
fs/configfs/dir.c:1892: group->cg_item.ci_name = group->cg_item.ci_namebuf;
fs/configfs/dir.c-1893-
--
fs/configfs/dir.c-1901- err = -ENOMEM;
fs/configfs/dir.c:1902: dentry = d_alloc_name(root, group->cg_item.ci_name);
fs/configfs/dir.c-1903- if (dentry) {
--
fs/configfs/dir.c-1907- if (!err)
fs/configfs/dir.c:1908: err = configfs_attach_group(&group->cg_item,
fs/configfs/dir.c-1909- dentry, frag);
--
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/item.c=22=static inline struct config_item *to_item(struct list_head *entry)
--
fs/configfs/item.c-27-/* Evil kernel */
fs/configfs/item.c:28:static void config_item_release(struct kref *kref);
fs/configfs/item.c-29-
--
fs/configfs/item.c=92=void config_group_init_type_name(struct config_group *group, const char *name,
--
fs/configfs/item.c-94-{
fs/configfs/item.c:95: config_item_set_name(&group->cg_item, "%s", name);
fs/configfs/item.c:96: group->cg_item.ci_type = type;
fs/configfs/item.c-97- config_group_init(group);
--
fs/configfs/item.c=115=EXPORT_SYMBOL(config_item_get_unless_zero);
fs/configfs/item.c-116-
fs/configfs/item.c:117:static void config_item_cleanup(struct config_item *item)
fs/configfs/item.c-118-{
--
fs/configfs/item.c-134-
fs/configfs/item.c:135:static void config_item_release(struct kref *kref)
fs/configfs/item.c-136-{
fs/configfs/item.c:137: config_item_cleanup(container_of(kref, struct config_item, ci_kref));
fs/configfs/item.c-138-}
--
fs/configfs/item.c-143- *
fs/configfs/item.c:144: * Decrement the refcount, and if 0, call config_item_cleanup().
fs/configfs/item.c-145- */
fs/configfs/item.c=146=void config_item_put(struct config_item *item)
--
fs/configfs/item.c-148- if (item)
fs/configfs/item.c:149: kref_put(&item->ci_kref, config_item_release);
fs/configfs/item.c-150-}
--
fs/configfs/item.c=157=void config_group_init(struct config_group *group)
fs/configfs/item.c-158-{
fs/configfs/item.c:159: config_item_init(&group->cg_item);
fs/configfs/item.c-160- INIT_LIST_HEAD(&group->cg_children);
--
fs/configfs/mount.c=43=static struct config_group configfs_root_group = {
fs/configfs/mount.c:44: .cg_item = {
fs/configfs/mount.c-45- .ci_namebuf = "root",
fs/configfs/mount.c:46: .ci_name = configfs_root_group.cg_item.ci_namebuf,
fs/configfs/mount.c-47- },
--
fs/configfs/mount.c=50=int configfs_is_root(struct config_item *item)
fs/configfs/mount.c-51-{
fs/configfs/mount.c:52: return item == &configfs_root_group.cg_item;
fs/configfs/mount.c-53-}
--
fs/configfs/mount.c=55=static struct configfs_dirent configfs_root = {
--
fs/configfs/mount.c-57- .s_children = LIST_HEAD_INIT(configfs_root.s_children),
fs/configfs/mount.c:58: .s_element = &configfs_root_group.cg_item,
fs/configfs/mount.c-59- .s_type = CONFIGFS_ROOT,
--
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;
]
|
| 411/4 |
2026/09/02 21:34 |
llm |
patch-generator |
0m
Model:
Tokens:
input: 49453
output: 36
thoughts: 22
Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience:
**Diving into configfs Item Cleanup**
Okay, so I need to understand what's happening with the `config_item_cleanup` function. My gut tells me this is critical for resource management within the `configfs` filesystem, specifically in handling the lifecycle of configuration items. I'm going to jump straight to the source, `fs/configfs/item.c`, that's where I would expect to find the implementation. I'm focusing on cleanup because memory leaks, dangling pointers, and unexpected behavior at shutdown are all things I want to avoid. Time to dig in and see exactly how they're handling object destruction and resource release.
|
| 412/4 |
2026/09/02 21:35 |
tool |
read-file |
0m
Args: map[File:fs/configfs/item.c FirstLine:115 LineCount:40]
Results: map[Contents: 115: EXPORT_SYMBOL(config_item_get_unless_zero);
116:
117: static void config_item_cleanup(struct config_item *item)
118: {
119: const struct config_item_type *t = item->ci_type;
120: struct config_group *s = item->ci_group;
121: struct config_item *parent = item->ci_parent;
122:
123: pr_debug("config_item %s: cleaning up\n", config_item_name(item));
124: if (item->ci_name != item->ci_namebuf)
125: kfree(item->ci_name);
126: item->ci_name = NULL;
127: if (t && t->ct_item_ops && t->ct_item_ops->release)
128: t->ct_item_ops->release(item);
129: if (s)
130: config_group_put(s);
131: if (parent)
132: config_item_put(parent);
133: }
134:
135: static void config_item_release(struct kref *kref)
136: {
137: config_item_cleanup(container_of(kref, struct config_item, ci_kref));
138: }
139:
140: /**
141: * config_item_put - decrement refcount for item.
142: * @item: item.
143: *
144: * Decrement the refcount, and if 0, call config_item_cleanup().
145: */
146: void config_item_put(struct config_item *item)
147: {
148: if (item)
149: kref_put(&item->ci_kref, config_item_release);
150: }
151: EXPORT_SYMBOL(config_item_put);
152:
153: /**
154: * config_group_init - initialize a group for use
]
|
| 413/4 |
2026/09/02 21:35 |
llm |
patch-generator |
0m
Model:
Tokens:
input: 50291
output: 46
thoughts: 234
Thoughts: **Analyzing the nvmet Configfs Cleanup Process**
Okay, let's break this down. I'm looking at `config_item_cleanup` here. The key is understanding how this relates to my nvme target implementation. First, I see the conditional check for `t->ct_item_ops->release`. This is crucial. If a `release` operation is defined for the `config_item`, it gets called. The text is reminding me that the default groups typically *don't* have a `release` op. They're embedded in their parent structs, so they aren't freed individually. Their cleanup is handled by the parent's `release` method. Excellent, this is consistent with how Iβve generally structured things, but itβs always good to have a refresher.
This implies that when I look at `nvmet_port`'s `config_item_release` function, that's where the magic happens for freeing the `nvmet_port` structure itself. It's the point where everything associated with that port gets cleaned up, which makes perfect sense. Now, let's dive into the `drivers/nvme/target/configfs.c` file. I need to refresh my memory on how `nvmet_port` is set up, especially regarding the groups. I'll need to locate `nvmet_ports_make` and `nvmet_port_subsys_allow_link`. These seem like the functions responsible for creating and linking the port within the configfs hierarchy. They'll show me how the parent-child relationships are established, confirming my understanding of the cleanup sequence.
|
| 414/4 |
2026/09/02 21:35 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/nvme/target/configfs.c Name:nvmet_port_subsys_allow_link]
Results: map[SourceCode:1056: static int nvmet_port_subsys_allow_link(struct config_item *parent,
1057: struct config_item *target)
1058: {
1059: struct nvmet_port *port = to_nvmet_port(parent->ci_parent);
1060: struct nvmet_subsys *subsys;
1061: struct nvmet_subsys_link *link, *p;
1062: int ret;
1063:
1064: if (target->ci_type != &nvmet_subsys_type) {
1065: pr_err("can only link subsystems into the subsystems dir.!\n");
1066: return -EINVAL;
1067: }
1068: subsys = to_subsys(target);
1069: link = kmalloc_obj(*link);
1070: if (!link)
1071: return -ENOMEM;
1072: link->subsys = subsys;
1073:
1074: down_write(&nvmet_config_sem);
1075: ret = -EEXIST;
1076: list_for_each_entry(p, &port->subsystems, entry) {
1077: if (p->subsys == subsys)
1078: goto out_free_link;
1079: }
1080:
1081: if (list_empty(&port->subsystems)) {
1082: ret = nvmet_enable_port(port);
1083: if (ret)
1084: goto out_free_link;
1085: }
1086:
1087: list_add_tail(&link->entry, &port->subsystems);
1088: nvmet_port_disc_changed(port, subsys);
1089:
1090: up_write(&nvmet_config_sem);
1091: return 0;
1092:
1093: out_free_link:
1094: up_write(&nvmet_config_sem);
1095: kfree(link);
1096: return ret;
1097: }
SourceFile:drivers/nvme/target/configfs.c]
|
| 415/4 |
2026/09/02 21:35 |
llm |
patch-generator |
0m
Model:
Tokens:
input: 51560
output: 41
thoughts: 128
Thoughts: **Debugging a Dangling Pointer in the NVMe Target Driver**
Okay, so I'm digging into this `nvmet_port_subsys_allow_link` function, trying to figure out what's going on. The issue is, I'm getting a read of freed memory when trying to access `parent->ci_parent`. Looking at the code, it's clear `parent` is actually a `config_group_item` which is embedded inside the `subsys_group` of a `nvmet_port` struct. Specifically, `parent` is `&port->subsys_group.cg_item`, and then `port` is retrieved using `to_nvmet_port(parent->ci_parent)`. The `port` structure, which contains the embedded `subsys_group`... is *dynamically allocated*.
The key here is that when `nvmet_port` was freed, accessing `parent->ci_parent` is reading memory that's no longer valid, right? That's what's causing the crash. It looks like a classic dangling pointer issue. The offset 1224 that's in the crash data is critical: it's likely where the `ci_parent` pointer sits inside of the `parent` struct.
Now, I need to understand how the `nvmet_port` is being freed and, more importantly, *why* the link is being attempted after it's freed. I'm going to shift my focus over to `nvmet_ports_make` in `drivers/nvme/target/configfs.c` to see if I can get some context on the port lifecycle. I'll bet the root cause is hiding in how ports are created and destroyed.
|
| 416/4 |
2026/09/02 21:35 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/nvme/target/configfs.c Name:nvmet_ports_make]
Results: map[SourceCode:2041: static struct config_group *nvmet_ports_make(struct config_group *group,
2042: const char *name)
2043: {
2044: struct nvmet_port *port;
2045: u16 portid;
2046: u32 i;
2047:
2048: if (kstrtou16(name, 0, &portid))
2049: return ERR_PTR(-EINVAL);
2050:
2051: port = kzalloc_flex(*port, ana_state, NVMET_MAX_ANAGRPS + 1);
2052: if (!port)
2053: return ERR_PTR(-ENOMEM);
2054:
2055: if (IS_ENABLED(CONFIG_NVME_TARGET_TCP_TLS) && nvme_keyring_id()) {
2056: port->keyring = key_lookup(nvme_keyring_id());
2057: if (IS_ERR(port->keyring)) {
2058: pr_warn("NVMe keyring not available, disabling TLS\n");
2059: port->keyring = NULL;
2060: }
2061: }
2062:
2063: for (i = 1; i <= NVMET_MAX_ANAGRPS; i++) {
2064: if (i == NVMET_DEFAULT_ANA_GRPID)
2065: port->ana_state[1] = NVME_ANA_OPTIMIZED;
2066: else
2067: port->ana_state[i] = NVME_ANA_INACCESSIBLE;
2068: }
2069:
2070: list_add(&port->global_entry, &nvmet_ports_list);
2071:
2072: INIT_LIST_HEAD(&port->entry);
2073: INIT_LIST_HEAD(&port->subsystems);
2074: INIT_LIST_HEAD(&port->referrals);
2075: port->inline_data_size = -1; /* < 0 == let the transport choose */
2076: port->max_queue_size = -1; /* < 0 == let the transport choose */
2077: port->mdts = -1; /* < 0 == let the transport choose */
2078:
2079: port->disc_addr.trtype = NVMF_TRTYPE_MAX;
2080: port->disc_addr.portid = cpu_to_le16(portid);
2081: port->disc_addr.adrfam = NVMF_ADDR_FAMILY_MAX;
2082: port->disc_addr.treq = NVMF_TREQ_DISABLE_SQFLOW;
2083: config_group_init_type_name(&port->group, name, &nvmet_port_type);
2084:
2085: config_group_init_type_name(&port->subsys_group,
2086: "subsystems", &nvmet_port_subsys_type);
2087: configfs_add_default_group(&port->subsys_group, &port->group);
2088:
2089: config_group_init_type_name(&port->referrals_group,
2090: "referrals", &nvmet_referrals_type);
2091: configfs_add_default_group(&port->referrals_group, &port->group);
2092:
2093: config_group_init_type_name(&port->ana_groups_group,
2094: "ana_groups", &nvmet_ana_groups_type);
2095: configfs_add_default_group(&port->ana_groups_group, &port->group);
2096:
2097: port->ana_default_group.port = port;
2098: port->ana_default_group.grpid = NVMET_DEFAULT_ANA_GRPID;
2099: config_group_init_type_name(&port->ana_default_group.group,
2100: __stringify(NVMET_DEFAULT_ANA_GRPID),
2101: &nvmet_ana_group_type);
2102: configfs_add_default_group(&port->ana_default_group.group,
2103: &port->ana_groups_group);
2104:
2105: return &port->group;
2106: }
SourceFile:drivers/nvme/target/configfs.c]
|
| 417/4 |
2026/09/02 21:35 |
llm |
patch-generator |
0m
Model:
Tokens:
input: 53349
output: 21
thoughts: 5856
Thoughts: **Analysis of ConfigFS Symlink and Concurrency**
Okay, so I've been diving deep into the `configfs` code, specifically focusing on how symlinks are handled and the potential for concurrency issues, especially when coupled with directory removal (`rmdir`). I've gone over `nvmet_ports_make` and its interactions with default groups and how removal of a port can trigger detaching of default groups. The main point is that `configfs_get_root_item` walks up the directory tree to determine the root item which can be pinned. I've also checked `configfs_unlink` and verified that the VFS holds `inode_lock(dir)` throughout, preventing data races in the face of rmdir operations. I examined several other configfs functions like `configfs_mkdir`, `configfs_lookup`, and others. The file I've looked at the most has been `fs/configfs/symlink.c`.
A key observation is that `configfs_symlink` *temporarily* drops `inode_lock(dir)` to resolve the target path, due to the need for `kern_path()`, and then reacquires it. This "unlock-then-lock" pattern creates a critical window for race conditions, and it's the unique aspect of the symlink logic.
I've carefully examined `configfs_get_root_item` and its logic. Iβve validated that the dentry pointer is never null. Then, using `configfs_get_config_item(root_dentry)` I made sure that the reference count of the item is incremented for thread safety, and decremented when the operation is complete.
I've traced the execution paths within `configfs_symlink()` to ensure proper error handling and resource cleanup. I've pinpointed that if `get_target` fails, the `target_item` won't be acquired, so it doesn't need to be put, and other pointers are handled as expected.
I verified the code in `get_target` to be certain that the code is returning the correct error codes.
I've identified a `sd` variable in `configfs_symlink` and made sure that it is not shadowed and that it can be reused for the code to be cleaner. I've made sure that the referenced fragment struct and the dentry struct are valid.
I've scrutinized the crucial check for `sd->s_frag->frag_dead` within `configfs_symlink()`. This check is vital for preventing symlinks to targets that are concurrently being removed. I've analyzed that if `rmdir` calls `frag->frag_dead = true` BEFORE `configfs_symlink` acquires `inode_lock(dir)` and then locks `inode_lock(dir)`, the memory ordering provides an acquire-release barrier, and Thread 1 is guaranteed to see `frag->frag_dead == true`. So even if the parent directory is being removed by a different thread, the symlink will fail with `-ENOENT`. I carefully considered the role of `mutex_lock(&configfs_symlink_mutex)` and concluded that all operations are serialized properly. I've analyzed the locking behavior related to `configfs_rmdir`, especially in the context of the fragment's `frag_dead` flag. I'm satisfied that `frag_sem` is properly used by the fragments.
I've assessed the implications of a concurrent `configfs_unregister_group` and made sure that the logic is correct.
I am certain that a check on `sd->s_frag->frag_dead` is sufficient and does not require `down_read(&sd->s_frag->frag_sem)` to synchronize with a rmdir process.
I've validated that `sd->s_frag->frag_dead` check is thread-safe and the logic for the symlink creation is correct. I am confident that even if the `allow_link` and `create_link` function is called and then the `frag_dead` is set to true, the code will fail with -ENOENT.
I examined `create_link` and it's checks on the target item, and it's reference count. I verified the parent side by checking the memory of the `parent_item`. I've examined that if `allow_link` and `create_link` succeeded, that the memory is guaranteed to be valid.
I am certain that if the `sd->s_frag->frag_dead` is checked before calling `allow_link`, that the correct return value is returned.
I've examined the interactions with the `configfs_dirent_lock` in `create_link`, as it is also used in `rmdir` operation. This logic should be safe and
|
| 418/4 |
2026/09/02 21:35 |
tool |
grepper |
0m
Args: map[Expression:CONFIGFS_USET_DEFAULT]
Results: map[Output:fs/configfs/configfs_internal.h=30=struct configfs_dirent {
--
fs/configfs/configfs_internal.h-52-#define CONFIGFS_USET_DIR 0x0040
fs/configfs/configfs_internal.h:53:#define CONFIGFS_USET_DEFAULT 0x0080
fs/configfs/configfs_internal.h-54-#define CONFIGFS_USET_DROPPING 0x0100
--
fs/configfs/dir.c=502=static int configfs_detach_prep(struct configfs_dirent *parent_sd, struct dentry **wait)
--
fs/configfs/dir.c-518- continue;
fs/configfs/dir.c:519: if (sd->s_type & CONFIGFS_USET_DEFAULT) {
fs/configfs/dir.c-520- /* Abort if racing with mkdir() */
--
fs/configfs/dir.c=548=static void configfs_detach_rollback(struct configfs_dirent *parent_sd)
--
fs/configfs/dir.c-554- list_for_each_entry(sd, &parent_sd->s_children, s_sibling)
fs/configfs/dir.c:555: if (sd->s_type & CONFIGFS_USET_DEFAULT)
fs/configfs/dir.c-556- configfs_detach_rollback(sd);
--
fs/configfs/dir.c=664=static void detach_groups(struct dentry *dentry)
--
fs/configfs/dir.c-673- next = next_dirent(parent_sd, sd);
fs/configfs/dir.c:674: if (!(sd->s_type & CONFIGFS_USET_DEFAULT))
fs/configfs/dir.c-675- continue;
--
fs/configfs/dir.c=703=static int create_default_group(struct dentry *parent,
--
fs/configfs/dir.c-722- sd = child->d_fsdata;
fs/configfs/dir.c:723: sd->s_type |= CONFIGFS_USET_DEFAULT;
fs/configfs/dir.c-724- } else {
--
fs/configfs/dir.c=977=static void configfs_dump_one(struct configfs_dirent *sd, int level)
--
fs/configfs/dir.c-986- type_print(CONFIGFS_USET_DIR);
fs/configfs/dir.c:987: type_print(CONFIGFS_USET_DEFAULT);
fs/configfs/dir.c-988- type_print(CONFIGFS_USET_DROPPING);
--
fs/configfs/dir.c=1469=static int configfs_rmdir(struct inode *dir, struct dentry *dentry)
--
fs/configfs/dir.c-1479- sd = dentry->d_fsdata;
fs/configfs/dir.c:1480: if (sd->s_type & CONFIGFS_USET_DEFAULT)
fs/configfs/dir.c-1481- return -EPERM;
]
|
| 419/4 |
2026/09/02 21:35 |
llm |
patch-generator |
0m
Model:
Tokens:
input: 61082
output: 38
thoughts: 3471
Thoughts: **Analysis of ConfigFS Symlink Creation and Resource Management**
Okay, let's break this down methodically. I've been examining the `configfs_symlink()` function and the surrounding code, especially concerning resource management and potential vulnerabilities in the reference counting. The central concern is ensuring all acquired references are correctly released and that there are no leaks or double frees.
Initially, I'm verifying the correctness of the `configfs_get_root_item` function. The code iterates up the dentry hierarchy, looking for a non-default group. The key here is to confirm `root_dentry` is always valid within the loop, meaning it never goes `NULL` due to some VFS oddity or a misconfiguration within the configfs structure. The analysis shows that since default groups are created in a specific way and have `d_parent` and other types of groups are always created, `root_dentry` will always point to something valid.
Next, I've checked what happens if `dentry->d_parent` is not a default group, meaning that the loop immediately terminates on the first iteration and returns the dentry as-is.
Then, I focused on the `configfs_symlink()` function, meticulously tracing the different execution paths that could occur.
The code flow uses a `parent_item` to be used and `root_item` to be used. I needed to verify that the refcounting is correct, especially around error handling and resource cleanup. All the execution paths have been scrutinized:
* The code correctly handles the case where the parent directory isn't ready, returning an error immediately.
* If the required type operations (allow\_link) are not available, `parent_item` is correctly released, and the function exits.
* If `get_target` fails, the `target_item` is NULL. It jumps to a clean up label to free `root_item` and `parent_item` correctly.
* If the target already exists, the function returns an error, and the already acquired resources are correctly released.
* If `inode_permission` fails or `s_frag->frag_dead` is true, the function returns with a code and releases all resources correctly.
* If `allow_link` fails and create link is not called, all the references are released correctly and a code returned.
* If `allow_link` succeeds but `create_link` fails, the drop link is called to clean up the partially created object, and the references are released.
* Finally, the happy path! When both `allow_link` and `create_link` succeeds, resources are correctly cleaned.
I've gone through each path, verifying that resources (specifically the `config_item` references) are released in all scenarios, even on errors, and that there are no double frees or memory leaks.
My focus then shifts to the `create_link` function, which is where the symlink creation actually happens. I need to understand how the target item's refcount is managed within that function. I looked at the function itself, and traced `create_link`. It incremented a refcount and made sure everything else was correct, including cleaning up.
The final analysis confirmed that temporary reference counts are used and cleaned up. This is essential for the correct lifecycle management of config items during symlink creation. Additionally, it ensures proper cleanup when errors occur in any step of the process. I confirmed that `config_item_put` is called for the same item at the right places in the code. I am confident that the resources are managed correctly now.
Finally, I verified that the resources used in the function are completely independent.
|
| 420/4 |
2026/09/02 21:35 |
tool |
read-file |
0m
Args: map[File:fs/configfs/symlink.c FirstLine:130 LineCount:100]
Results: map[Contents: 130: return -ENOENT;
131: return 0;
132: }
133:
134:
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: }
212:
213: int configfs_unlink(struct inode *dir, struct dentry *dentry)
214: {
215: struct configfs_dirent *sd = dentry->d_fsdata, *target_sd;
216: struct config_item *parent_item;
217: const struct config_item_type *type;
218: int ret;
219:
220: ret = -EPERM; /* What lack-of-symlink returns */
221: if (!(sd->s_type & CONFIGFS_ITEM_LINK))
222: goto out;
223:
224: target_sd = sd->s_element;
225:
226: parent_item = configfs_get_config_item(dentry->d_parent);
227: type = parent_item->ci_type;
228:
229: spin_lock(&configfs_dirent_lock);
]
|
| 421/4 |
2026/09/02 21:35 |
llm |
patch-generator |
0m
Model:
Tokens:
input: 66953
output: 2281
thoughts: 1512
Thoughts: **Analyzing `configfs_symlink()`'s Logic and Potential Issues**
Okay, let's dissect this. I'm taking a close look at `fs/configfs/symlink.c`, specifically the `configfs_symlink()` function. My immediate concern is with the overall structure and flow. First, I'm checking the definition and functionality of `configfs_get_root_item()`. I need to be sure I understand how it traverses the directory hierarchy to find the root. Let's trace how it works. I'm focusing on the logic that determines the root of the item. I see it starts from the provided `dentry` and goes up the hierarchy. The key is in checking `sd->s_type & CONFIGFS_USET_DEFAULT` β this flag distinguishes default groups, which I know are special. The loop continues up the hierarchy as long as `CONFIGFS_USET_DEFAULT` is set, breaking when it encounters a non-default group or reaches the root. So, if `dentry` is something like `ports/1/subsystems`, it'll trace up to `ports/1`. If there are nested default groups like `ports/1/nested_group/subsystems`, it correctly identifies `ports/1` as the root. If the given `dentry` is already `ports/1` (not a default group), the loop will break immediately.
Now, shifting my focus to `configfs_symlink()` itself. I see a critical section related to the target resolution. The code retrieves a `config_item` for the parent. There's a check for `configfs_dirent_is_ready()` β this is important. It likely prevents operations on items that aren't fully initialized. I see `parent_item` is acquired and its type is examined. Then, there's a test for `allow_link` operation.
The tricky part, and what I want to double-check, is how the target is resolved *after* the inode lock is released and then re-acquired. The code's comment, "This is really sick," isn't exactly reassuring. It looks like the target resolution happens between an `inode_unlock` and `inode_lock`. I need to analyze this very carefully. The code then calls `get_target()`, which I'll have to thoroughly review.
The primary point of concern is about `sd` (the `configfs_dirent` of `dentry->d_parent`). The question is, does the `sd` remain valid, even after `inode_unlock`? Because the `parent_item` is acquired and `root_item` is also held, it should be fine. Both `dentry->d_parent` and thus `sd` should stay allocated and valid. So, even though the inode lock is dropped and then reacquired, the link is correctly checking if the parent was removed. The `sd->s_frag->frag_dead` field is key here. This likely uses reference counting to check if `configfs_rmdir()` or `configfs_unregister_group()` has removed the directory.
Okay, I'm now going to apply the edits. This code needs some more attention. I'll make sure there aren't any potential race conditions during the target resolution and directory checks, given this unconventional unlock/lock pattern.
|
| 422/4 |
2026/09/02 21:35 |
tool |
codeeditor |
0m
Args: map[CurrentCode:static int get_target(const char *symname, struct config_item **target,
struct super_block *sb)
{
struct path path __free(path_put) = {};
int ret;
ret = kern_path(symname, LOOKUP_FOLLOW|LOOKUP_DIRECTORY, &path);
if (ret)
return ret;
if (path.dentry->d_sb != sb)
return -EPERM;
*target = configfs_get_config_item(path.dentry);
if (!*target)
return -ENOENT;
return 0;
}
int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
struct dentry *dentry, const char *symname)
{
int ret;
struct configfs_dirent *sd;
struct config_item *parent_item;
struct config_item *target_item = NULL;
const struct config_item_type *type;
sd = dentry->d_parent->d_fsdata;
/*
* Fake invisibility if dir belongs to a group/default groups hierarchy
* being attached
*/
if (!configfs_dirent_is_ready(sd))
return -ENOENT;
parent_item = configfs_get_config_item(dentry->d_parent);
type = parent_item->ci_type;
ret = -EPERM;
if (!type || !type->ct_item_ops ||
!type->ct_item_ops->allow_link)
goto out_put;
/*
* This is really sick. What they wanted was a hybrid of
* link(2) and symlink(2) - they wanted the target resolved
* at syscall time (as link(2) would've done), be a directory
* (which link(2) would've refused to do) *AND* be a deep
* fucking magic, making the target busy from rmdir POV.
* symlink(2) is nothing of that sort, and the locking it
* gets matches the normal symlink(2) semantics. Without
* attempts to resolve the target (which might very well
* not even exist yet) done prior to locking the parent
* directory. This perversion, OTOH, needs to resolve
* the target, which would lead to obvious deadlocks if
* attempted with any directories locked.
*
* Unfortunately, that garbage is userland ABI and we should've
* said "no" back in 2005. Too late now, so we get to
* play very ugly games with locking.
*
* Try *ANYTHING* of that sort in new code, and you will
* really regret it. Just ask yourself - what could a BOFH
* do to me and do I want to find it out first-hand?
*
* AV, a thoroughly annoyed bastard.
*/
inode_unlock(dir);
ret = get_target(symname, &target_item, dentry->d_sb);
inode_lock(dir);
if (ret)
goto out_put;
if (dentry->d_inode || d_unhashed(dentry))
ret = -EEXIST;
else
ret = inode_permission(&nop_mnt_idmap, dir,
MAY_WRITE | MAY_EXEC);
if (!ret)
ret = type->ct_item_ops->allow_link(parent_item, target_item);
if (!ret) {
mutex_lock(&configfs_symlink_mutex);
ret = create_link(parent_item, target_item, dentry);
mutex_unlock(&configfs_symlink_mutex);
if (ret && type->ct_item_ops->drop_link)
type->ct_item_ops->drop_link(parent_item,
target_item);
}
config_item_put(target_item);
out_put:
config_item_put(parent_item);
return ret;
} NewCode:static int get_target(const char *symname, struct config_item **target,
struct super_block *sb)
{
struct path path __free(path_put) = {};
int ret;
ret = kern_path(symname, LOOKUP_FOLLOW|LOOKUP_DIRECTORY, &path);
if (ret)
return ret;
if (path.dentry->d_sb != sb)
return -EPERM;
*target = configfs_get_config_item(path.dentry);
if (!*target)
return -ENOENT;
return 0;
}
static struct config_item *configfs_get_root_item(struct dentry *dentry)
{
struct dentry *root_dentry = dentry;
struct configfs_dirent *sd;
while (root_dentry) {
sd = root_dentry->d_fsdata;
if (!(sd->s_type & CONFIGFS_USET_DEFAULT))
break;
root_dentry = root_dentry->d_parent;
}
return configfs_get_config_item(root_dentry);
}
int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
struct dentry *dentry, const char *symname)
{
int ret;
struct configfs_dirent *sd;
struct config_item *parent_item;
struct config_item *target_item = NULL;
struct config_item *root_item = NULL;
const struct config_item_type *type;
sd = dentry->d_parent->d_fsdata;
/*
* Fake invisibility if dir belongs to a group/default groups hierarchy
* being attached
*/
if (!configfs_dirent_is_ready(sd))
return -ENOENT;
parent_item = configfs_get_config_item(dentry->d_parent);
type = parent_item->ci_type;
ret = -EPERM;
if (!type || !type->ct_item_ops ||
!type->ct_item_ops->allow_link)
goto out_put;
/*
* Pin the root object so that the parent_item (which may be an
* embedded default group) is not freed while we drop the lock.
*/
root_item = configfs_get_root_item(dentry->d_parent);
/*
* This is really sick. What they wanted was a hybrid of
* link(2) and symlink(2) - they wanted the target resolved
* at syscall time (as link(2) would've done), be a directory
* (which link(2) would've refused to do) *AND* be a deep
* fucking magic, making the target busy from rmdir POV.
* symlink(2) is nothing of that sort, and the locking it
* gets matches the normal symlink(2) semantics. Without
* attempts to resolve the target (which might very well
* not even exist yet) done prior to locking the parent
* directory. This perversion, OTOH, needs to resolve
* the target, which would lead to obvious deadlocks if
* attempted with any directories locked.
*
* Unfortunately, that garbage is userland ABI and we should've
* said "no" back in 2005. Too late now, so we get to
* play very ugly games with locking.
*
* Try *ANYTHING* of that sort in new code, and you will
* really regret it. Just ask yourself - what could a BOFH
* do to me and do I want to find it out first-hand?
*
* AV, a thoroughly annoyed bastard.
*/
inode_unlock(dir);
ret = get_target(symname, &target_item, dentry->d_sb);
inode_lock(dir);
if (ret)
goto out_put_root;
if (dentry->d_inode || d_unhashed(dentry))
ret = -EEXIST;
else
ret = inode_permission(&nop_mnt_idmap, dir,
MAY_WRITE | MAY_EXEC);
if (!ret) {
/* Check if the directory was removed while unlocked */
if (sd->s_frag->frag_dead)
ret = -ENOENT;
else
ret = type->ct_item_ops->allow_link(parent_item, target_item);
}
if (!ret) {
mutex_lock(&configfs_symlink_mutex);
ret = create_link(parent_item, target_item, dentry);
mutex_unlock(&configfs_symlink_mutex);
if (ret && type->ct_item_ops->drop_link)
type->ct_item_ops->drop_link(parent_item,
target_item);
}
config_item_put(target_item);
out_put_root:
config_item_put(root_item);
out_put:
config_item_put(parent_item);
return ret;
} SourceFile:fs/configfs/symlink.c]
Results:
|
| 423/4 |
2026/09/02 21:35 |
llm |
patch-generator |
0m
Model:
Tokens:
input: 71474
output: 10
thoughts: 14
Thoughts: Okay, here's my attempt at summarizing the thought process, as if I were an expert:
**Verifying the Code Changes**
Right, before I proceed any further, I need to double-check those modifications. I'll reach for `patch-diff` β itβs the quickest and most reliable way to visually confirm the impact of my latest changes. No need to painstakingly scroll through the full code base, or worse, make assumptions. Iβm an expert, but even I need a sanity check! I'll quickly examine the output of `patch-diff` and ensure everything looks as intended, that the logic aligns with my goals, and that I havenβt introduced any unwanted side effects. Precision is key, after all. Better safe than sorry when dealing with important code. A quick run through of the diff will confirm a job well done.
|
| 424/4 |
2026/09/02 21:35 |
tool |
patch-diff |
0m
Error:
|
| 425/4 |
2026/09/02 21:35 |
llm |
patch-generator |
0m
Model:
Tokens:
input: 71695
output: 24
thoughts: 15
Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience:
**Analyzing the `patch-diff` Call**
Okay, so I'm looking at this `patch-diff` call, specifically the part where it's being given an empty string for the file argument. My initial thought process is to consider why we'd be doing that. Normally, a file argument provides the source against which the patch will be applied. An empty string is... unusual. It immediately flags a few potential scenarios, and each needs a closer look:
* **Potential Redundancy/Bug:** Is this a remnant of some debugging or refactoring process? It's possible an earlier implementation or test case used a placeholder, and it hasn't been properly removed. Perhaps the logic intended to dynamically determine the file, but the code isn't fully implemented or has an error. Or, perhaps, it expects standard in.
* **Implied Standard Input:** Could this be a deliberate signal to `patch-diff` that it should operate on standard input, either because the patch is piped in or because the intended base content is implicitly elsewhere, like perhaps from a previous step in a build process? This would be a more sophisticated design pattern. I need to check the tool's documentation or the source code to confirm the behavior on a blank file argument.
* **Specialized Use Case:** Is `patch-diff` part of a larger workflow or toolchain? Could an empty filename have a very specific, application-dependent meaning? It might be calling another tool with standard input. This seems less likely on face value, but I shouldn't dismiss it yet.
* **Security Implications:** While rare, it's worth considering if this presents any security vulnerabilities, such as a code injection issue if the empty string is handled in an unexpected way. Even if unlikely, best practice is to at least consider the corner cases.
Ultimately, I need to know why the empty file parameter is necessary. The most important next step is to examine the surrounding code to determine intent, and failing that, consult documentation for the `patch-diff` tool.
|
| 426/4 |
2026/09/02 21:36 |
tool |
patch-diff |
0m
Args: map[File:fs/configfs/symlink.c]
Results: map[Output:diff --git a/fs/configfs/symlink.c b/fs/configfs/symlink.c
index 31eb28b27..3e597371e 100644
--- a/fs/configfs/symlink.c
+++ b/fs/configfs/symlink.c
@@ -117,101 +117,128 @@ static int create_link(struct config_item *parent_item,
static int get_target(const char *symname, struct config_item **target,
struct super_block *sb)
{
struct path path __free(path_put) = {};
int ret;
ret = kern_path(symname, LOOKUP_FOLLOW|LOOKUP_DIRECTORY, &path);
if (ret)
return ret;
if (path.dentry->d_sb != sb)
return -EPERM;
*target = configfs_get_config_item(path.dentry);
if (!*target)
return -ENOENT;
return 0;
}
+static struct config_item *configfs_get_root_item(struct dentry *dentry)
+{
+ struct dentry *root_dentry = dentry;
+ struct configfs_dirent *sd;
+
+ while (root_dentry) {
+ sd = root_dentry->d_fsdata;
+ if (!(sd->s_type & CONFIGFS_USET_DEFAULT))
+ break;
+ root_dentry = root_dentry->d_parent;
+ }
+ return configfs_get_config_item(root_dentry);
+}
int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
struct dentry *dentry, const char *symname)
{
int ret;
struct configfs_dirent *sd;
struct config_item *parent_item;
struct config_item *target_item = NULL;
+ struct config_item *root_item = NULL;
const struct config_item_type *type;
sd = dentry->d_parent->d_fsdata;
/*
* Fake invisibility if dir belongs to a group/default groups hierarchy
* being attached
*/
if (!configfs_dirent_is_ready(sd))
return -ENOENT;
parent_item = configfs_get_config_item(dentry->d_parent);
type = parent_item->ci_type;
ret = -EPERM;
if (!type || !type->ct_item_ops ||
!type->ct_item_ops->allow_link)
goto out_put;
+ /*
+ * Pin the root object so that the parent_item (which may be an
+ * embedded default group) is not freed while we drop the lock.
+ */
+ root_item = configfs_get_root_item(dentry->d_parent);
+
/*
* This is really sick. What they wanted was a hybrid of
* link(2) and symlink(2) - they wanted the target resolved
* at syscall time (as link(2) would've done), be a directory
* (which link(2) would've refused to do) *AND* be a deep
* fucking magic, making the target busy from rmdir POV.
* symlink(2) is nothing of that sort, and the locking it
* gets matches the normal symlink(2) semantics. Without
* attempts to resolve the target (which might very well
* not even exist yet) done prior to locking the parent
* directory. This perversion, OTOH, needs to resolve
* the target, which would lead to obvious deadlocks if
* attempted with any directories locked.
*
* Unfortunately, that garbage is userland ABI and we should've
* said "no" back in 2005. Too late now, so we get to
* play very ugly games with locking.
*
* Try *ANYTHING* of that sort in new code, and you will
* really regret it. Just ask yourself - what could a BOFH
* do to me and do I want to find it out first-hand?
*
* AV, a thoroughly annoyed bastard.
*/
inode_unlock(dir);
ret = get_target(symname, &target_item, dentry->d_sb);
inode_lock(dir);
if (ret)
- goto out_put;
+ goto out_put_root;
if (dentry->d_inode || d_unhashed(dentry))
ret = -EEXIST;
else
ret = inode_permission(&nop_mnt_idmap, dir,
MAY_WRITE | MAY_EXEC);
- if (!ret)
- ret = type->ct_item_ops->allow_link(parent_item, target_item);
+ if (!ret) {
+ /* Check if the directory was removed while unlocked */
+ if (sd->s_frag->frag_dead)
+ ret = -ENOENT;
+ else
+ ret = type->ct_item_ops->allow_link(parent_item, target_item);
+ }
if (!ret) {
mutex_lock(&configfs_symlink_mutex);
ret = create_link(parent_item, target_item, dentry);
mutex_unlock(&configfs_symlink_mutex);
if (ret && type->ct_item_ops->drop_link)
type->ct_item_ops->drop_link(parent_item,
target_item);
}
config_item_put(target_item);
+out_put_root:
+ config_item_put(root_item);
out_put:
config_item_put(parent_item);
return ret;
}
int configfs_unlink(struct inode *dir, struct dentry *dentry)
{
struct configfs_dirent *sd = dentry->d_fsdata, *target_sd;
struct config_item *parent_item;
const struct config_item_type *type;
]
|
| 427/4 |
2026/09/02 21:36 |
llm |
patch-generator |
0m
Model:
Tokens:
input: 73805
output: 506
thoughts: 359
Thoughts: **My Assessment of the Proposed Change**
Okay, let's break this down systematically. I'm seeing a carefully considered diff here. First, it looks like `get_target` is left alone, which is good. That likely implies the existing functionality around target retrieval is already sound. The core of the change lies in a new function, `configfs_get_root_item(struct dentry *dentry)`. This function is interesting. It's designed to walk up the `dentry` hierarchy, checking a flag, `CONFIGFS_USET_DEFAULT`, likely to identify a root or default item. It makes sure `configfs_get_config_item` is only called at the appropriate root.
Now, looking at its use within `configfs_symlink()`, it's clear the goal is to safely manage the `root_item` lifecycle in the face of potential errors and races. The flow is: retrieve the root item *before* we potentially encounter issues retrieving the target. It's a key optimization. The logic ensures that `root_item` is always put, even in error paths, using `config_item_put(root_item)`. This is all to prevent UAF, which is exactly the kind of race condition that must be addressed.
The code seems to handle the case of a dead fragment (`sd->s_frag->frag_dead`) correctly, returning an error (`-ENOENT`) and preventing any further operations on it. Furthermore, it incorporates calls to `config_item_put` for `target_item` and `parent_item` in all branches, ensuring no resources are leaked. The `allow_link` call is protected by a check for the fragment death as well, which is an optimization and an essential part of preventing race conditions.
Overall, this is solid. The changes adhere to established kernel and configfs patterns and address the core problem directly. It appears to be a safe, clean, and complete fix. The resource management (use of `config_item_put`) is precisely what I'd expect to see to prevent UAF. I am satisfied with this analysis and ready to give the go-ahead.
|
| 428/3 |
2026/09/02 21:36 |
action |
test-patch |
23m
Results: map[PatchDiff:diff --git a/fs/configfs/symlink.c b/fs/configfs/symlink.c
index 31eb28b27..3e597371e 100644
--- a/fs/configfs/symlink.c
+++ b/fs/configfs/symlink.c
@@ -131,6 +131,19 @@ static int get_target(const char *symname, struct config_item **target,
return 0;
}
+static struct config_item *configfs_get_root_item(struct dentry *dentry)
+{
+ struct dentry *root_dentry = dentry;
+ struct configfs_dirent *sd;
+
+ while (root_dentry) {
+ sd = root_dentry->d_fsdata;
+ if (!(sd->s_type & CONFIGFS_USET_DEFAULT))
+ break;
+ root_dentry = root_dentry->d_parent;
+ }
+ return configfs_get_config_item(root_dentry);
+}
int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
struct dentry *dentry, const char *symname)
@@ -139,6 +152,7 @@ int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
struct configfs_dirent *sd;
struct config_item *parent_item;
struct config_item *target_item = NULL;
+ struct config_item *root_item = NULL;
const struct config_item_type *type;
sd = dentry->d_parent->d_fsdata;
@@ -157,6 +171,12 @@ int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
!type->ct_item_ops->allow_link)
goto out_put;
+ /*
+ * Pin the root object so that the parent_item (which may be an
+ * embedded default group) is not freed while we drop the lock.
+ */
+ root_item = configfs_get_root_item(dentry->d_parent);
+
/*
* This is really sick. What they wanted was a hybrid of
* link(2) and symlink(2) - they wanted the target resolved
@@ -185,15 +205,20 @@ int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
ret = get_target(symname, &target_item, dentry->d_sb);
inode_lock(dir);
if (ret)
- goto out_put;
+ goto out_put_root;
if (dentry->d_inode || d_unhashed(dentry))
ret = -EEXIST;
else
ret = inode_permission(&nop_mnt_idmap, dir,
MAY_WRITE | MAY_EXEC);
- if (!ret)
- ret = type->ct_item_ops->allow_link(parent_item, target_item);
+ if (!ret) {
+ /* Check if the directory was removed while unlocked */
+ if (sd->s_frag->frag_dead)
+ ret = -ENOENT;
+ else
+ ret = type->ct_item_ops->allow_link(parent_item, target_item);
+ }
if (!ret) {
mutex_lock(&configfs_symlink_mutex);
ret = create_link(parent_item, target_item, dentry);
@@ -205,6 +230,8 @@ int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
config_item_put(target_item);
+out_put_root:
+ config_item_put(root_item);
out_put:
config_item_put(parent_item);
return ret;
TestError:==================================================================
BUG: KASAN: slab-use-after-free in instrument_atomic_read_write include/linux/instrumented.h:112 [inline]
BUG: KASAN: slab-use-after-free in atomic_fetch_sub_release include/linux/atomic/atomic-instrumented.h:400 [inline]
BUG: KASAN: slab-use-after-free in __refcount_sub_and_test include/linux/refcount.h:389 [inline]
BUG: KASAN: slab-use-after-free in __refcount_dec_and_test include/linux/refcount.h:432 [inline]
BUG: KASAN: slab-use-after-free in refcount_dec_and_test include/linux/refcount.h:450 [inline]
BUG: KASAN: slab-use-after-free in kref_put include/linux/kref.h:64 [inline]
BUG: KASAN: slab-use-after-free in config_item_put+0x26/0x90 fs/configfs/item.c:149
Write of size 4 at addr ffff8881fa3e04b4 by task syz-executor340/5863
CPU: 1 UID: 0 PID: 5863 Comm: syz-executor340 Not tainted syzkaller #1 PREEMPT(full)
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
Call Trace:
<TASK>
dump_stack_lvl+0xe8/0x150 lib/dump_stack.c:120
print_address_description+0x55/0x1e0 mm/kasan/report.c:378
print_report+0x58/0x70 mm/kasan/report.c:482
kasan_report+0x117/0x150 mm/kasan/report.c:595
check_region_inline mm/kasan/generic.c:-1 [inline]
kasan_check_range+0x264/0x2c0 mm/kasan/generic.c:200
instrument_atomic_read_write include/linux/instrumented.h:112 [inline]
atomic_fetch_sub_release include/linux/atomic/atomic-instrumented.h:400 [inline]
__refcount_sub_and_test include/linux/refcount.h:389 [inline]
__refcount_dec_and_test include/linux/refcount.h:432 [inline]
refcount_dec_and_test include/linux/refcount.h:450 [inline]
kref_put include/linux/kref.h:64 [inline]
config_item_put+0x26/0x90 fs/configfs/item.c:149
configfs_symlink+0x5d1/0x1160 fs/configfs/symlink.c:236
vfs_symlink+0x18b/0x330 fs/namei.c:5794
filename_symlinkat+0x1cd/0x410 fs/namei.c:5819
__do_sys_symlinkat fs/namei.c:5839 [inline]
__se_sys_symlinkat+0x4e/0x2b0 fs/namei.c:5834
do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7f9d17b62459
Code: c0 79 93 eb d5 48 8d 7c 1d 00 eb 99 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 d0 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007f9d15b291f8 EFLAGS: 00000246 ORIG_RAX: 000000000000010a
RAX: ffffffffffffffda RBX: 00007f9d17bcf028 RCX: 00007f9d17b62459
RDX: 00007f9d17bcf028 RSI: 00000000ffffff9c RDI: 00007f9d17bcf060
RBP: 00007f9d17bcf060 R08: 00007f9d15b296c0 R09: 00007f9d15b296c0
R10: 00007f9d15b296c0 R11: 0000000000000246 R12: ffffffffffffffd0
R13: 0000000000000010 R14: 00007fff9f39c490 R15: 00007fff9f39c578
</TASK>
Allocated by task 5860:
kasan_save_stack mm/kasan/common.c:57 [inline]
kasan_save_track+0x3e/0x80 mm/kasan/common.c:78
poison_kmalloc_redzone mm/kasan/common.c:398 [inline]
__kasan_kmalloc+0x93/0xb0 mm/kasan/common.c:415
kasan_kmalloc include/linux/kasan.h:263 [inline]
__kmalloc_cache_noprof+0x321/0x600 mm/slub.c:5563
_kmalloc_noprof include/linux/slab.h:991 [inline]
_kzalloc_noprof include/linux/slab.h:1312 [inline]
nvmet_ports_make+0xe6/0xf00 drivers/nvme/target/configfs.c:2051
configfs_mkdir+0x4f6/0x9e0 fs/configfs/dir.c:1360
vfs_mkdir+0x40c/0x620 fs/namei.c:5410
filename_mkdirat+0x285/0x510 fs/namei.c:5443
__do_sys_mkdir fs/namei.c:5470 [inline]
__se_sys_mkdir+0x34/0x150 fs/namei.c:5467
do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
entry_SYSCALL_64_after_hwframe+0x77/0x7f
Freed by task 5863:
kasan_save_stack mm/kasan/common.c:57 [inline]
kasan_save_track+0x3e/0x80 mm/kasan/common.c:78
kasan_save_free_info+0x40/0x50 mm/kasan/generic.c:584
poison_slab_object mm/kasan/common.c:253 [inline]
__kasan_slab_free+0x5c/0x80 mm/kasan/common.c:285
kasan_slab_free include/linux/kasan.h:235 [inline]
slab_free_hook mm/slub.c:2748 [inline]
slab_free mm/slub.c:6499 [inline]
kfree+0x1c5/0x650 mm/slub.c:6792
config_item_cleanup fs/configfs/item.c:128 [inline]
config_item_release+0x13a/0x2d0 fs/configfs/item.c:137
configfs_symlink+0x5c7/0x1160 fs/configfs/symlink.c:234
vfs_symlink+0x18b/0x330 fs/namei.c:5794
filename_symlinkat+0x1cd/0x410 fs/namei.c:5819
__do_sys_symlinkat fs/namei.c:5839 [inline]
__se_sys_symlinkat+0x4e/0x2b0 fs/namei.c:5834
do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
entry_SYSCALL_64_after_hwframe+0x77/0x7f
The buggy address belongs to the object at ffff8881fa3e0000
which belongs to the cache kmalloc-4k of size 4096
The buggy address is located 1204 bytes inside of
freed 4096-byte region [ffff8881fa3e0000, ffff8881fa3e1000)
The buggy address belongs to the physical page:
page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x1fa3e0
head: order:3 mapcount:0 entire_mapcount:0 nr_pages_mapped:0 pincount:0
flags: 0x57ff00000000040(head|node=1|zone=2|lastcpupid=0x7ff)
page_type: f5(slab)
raw: 057ff00000000040 ffff888100042140 dead000000000122 0000000000000000
raw: 0000000000000000 0000000000040004 00000000f5000000 0000000000000000
head: 057ff00000000040 ffff888100042140 dead000000000122 0000000000000000
head: 0000000000000000 0000000000040004 00000000f5000000 0000000000000000
head: 057ff00000000003 fffffffffffffe01 00000000ffffffff 00000000ffffffff
head: ffffffffffffffff 0000000000000000 00000000ffffffff 0000000000000008
page dumped because: kasan: bad access detected
page_owner tracks the page as allocated
page last allocated via order 3, migratetype Unmovable, gfp_mask 0xd2040(__GFP_IO|__GFP_NOWARN|__GFP_NORETRY|__GFP_COMP|__GFP_NOMEMALLOC), pid 5857, tgid 5857 (sshd-session), ts 84765305688
set_page_owner include/linux/page_owner.h:33 [inline]
post_alloc_hook+0x1f9/0x250 mm/page_alloc.c:1871
prep_new_page mm/page_alloc.c:1879 [inline]
get_page_from_freelist+0x2209/0x2280 mm/page_alloc.c:3943
__alloc_frozen_pages_noprof+0x217/0x5a0 mm/page_alloc.c:5436
alloc_slab_page mm/slub.c:3347 [inline]
allocate_slab+0x7d/0x620 mm/slub.c:3462
new_slab mm/slub.c:3513 [inline]
refill_objects+0x2d5/0x350 mm/slub.c:7410
refill_sheaf mm/slub.c:2885 [inline]
__pcs_replace_empty_main+0x2c8/0x6c0 mm/slub.c:4774
alloc_from_pcs mm/slub.c:4850 [inline]
slab_alloc_node mm/slub.c:4984 [inline]
__do_kmalloc_node mm/slub.c:5413 [inline]
__kmalloc_noprof+0x47b/0x720 mm/slub.c:5439
_kmalloc_noprof include/linux/slab.h:995 [inline]
tomoyo_realpath_from_path+0xef/0x640 security/tomoyo/realpath.c:251
tomoyo_get_realpath security/tomoyo/file.c:151 [inline]
tomoyo_check_open_permission+0x229/0x470 security/tomoyo/file.c:776
security_file_open+0xa9/0x240 security/security.c:2739
do_dentry_open+0x4a0/0x1380 fs/open.c:973
vfs_open+0x3b/0x340 fs/open.c:1101
do_open fs/namei.c:4837 [inline]
path_openat+0x1443/0x1d60 fs/namei.c:5000
do_file_open+0x23e/0x4a0 fs/namei.c:5029
do_sys_openat2+0x115/0x200 fs/open.c:1417
do_sys_open fs/open.c:1423 [inline]
__do_sys_openat fs/open.c:1439 [inline]
__se_sys_openat fs/open.c:1434 [inline]
__x64_sys_openat+0x138/0x170 fs/open.c:1434
page last free pid 5681 tgid 5681 ts 74104377406 stack trace:
reset_page_owner include/linux/page_owner.h:26 [inline]
__free_pages_prepare mm/page_alloc.c:1418 [inline]
__free_frozen_pages+0xc50/0xd30 mm/page_alloc.c:2962
__slab_free+0x274/0x2c0 mm/slub.c:5815
qlink_free mm/kasan/quarantine.c:163 [inline]
qlist_free_all+0x99/0x100 mm/kasan/quarantine.c:179
kasan_quarantine_reduce+0x148/0x160 mm/kasan/quarantine.c:286
__kasan_slab_alloc+0x22/0x80 mm/kasan/common.c:350
kasan_slab_alloc include/linux/kasan.h:253 [inline]
slab_post_alloc_hook mm/slub.c:4683 [inline]
slab_alloc_node mm/slub.c:4996 [inline]
__kmalloc_cache_noprof+0x29f/0x600 mm/slub.c:5559
_kmalloc_noprof include/linux/slab.h:991 [inline]
_kzalloc_noprof include/linux/slab.h:1312 [inline]
nsim_fib6_rt_create drivers/net/netdevsim/fib.c:547 [inline]
nsim_fib6_rt_insert drivers/net/netdevsim/fib.c:752 [inline]
nsim_fib6_event drivers/net/netdevsim/fib.c:856 [inline]
nsim_fib_event+0xce4/0x8860 drivers/net/netdevsim/fib.c:889
nsim_fib_event_work+0x244/0x3b0 drivers/net/netdevsim/fib.c:1493
process_one_work kernel/workqueue.c:3387 [inline]
process_scheduled_works+0xc3d/0x1630 kernel/workqueue.c:3470
worker_thread+0x92d/0xe10 kernel/workqueue.c:3551
kthread+0x38b/0x480 kernel/kthread.c:436
ret_from_fork+0x514/0xb70 arch/x86/kernel/process.c:158
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
Memory state around the buggy address:
ffff8881fa3e0380: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
ffff8881fa3e0400: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
>ffff8881fa3e0480: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
^
ffff8881fa3e0500: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
ffff8881fa3e0580: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
==================================================================
]
|
| 429/3 |
2026/09/02 21:59 |
action |
If |
0m
Args: map[TestError:==================================================================
BUG: KASAN: slab-use-after-free in instrument_atomic_read_write include/linux/instrumented.h:112 [inline]
BUG: KASAN: slab-use-after-free in atomic_fetch_sub_release include/linux/atomic/atomic-instrumented.h:400 [inline]
BUG: KASAN: slab-use-after-free in __refcount_sub_and_test include/linux/refcount.h:389 [inline]
BUG: KASAN: slab-use-after-free in __refcount_dec_and_test include/linux/refcount.h:432 [inline]
BUG: KASAN: slab-use-after-free in refcount_dec_and_test include/linux/refcount.h:450 [inline]
BUG: KASAN: slab-use-after-free in kref_put include/linux/kref.h:64 [inline]
BUG: KASAN: slab-use-after-free in config_item_put+0x26/0x90 fs/configfs/item.c:149
Write of size 4 at addr ffff8881fa3e04b4 by task syz-executor340/5863
CPU: 1 UID: 0 PID: 5863 Comm: syz-executor340 Not tainted syzkaller #1 PREEMPT(full)
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
Call Trace:
<TASK>
dump_stack_lvl+0xe8/0x150 lib/dump_stack.c:120
print_address_description+0x55/0x1e0 mm/kasan/report.c:378
print_report+0x58/0x70 mm/kasan/report.c:482
kasan_report+0x117/0x150 mm/kasan/report.c:595
check_region_inline mm/kasan/generic.c:-1 [inline]
kasan_check_range+0x264/0x2c0 mm/kasan/generic.c:200
instrument_atomic_read_write include/linux/instrumented.h:112 [inline]
atomic_fetch_sub_release include/linux/atomic/atomic-instrumented.h:400 [inline]
__refcount_sub_and_test include/linux/refcount.h:389 [inline]
__refcount_dec_and_test include/linux/refcount.h:432 [inline]
refcount_dec_and_test include/linux/refcount.h:450 [inline]
kref_put include/linux/kref.h:64 [inline]
config_item_put+0x26/0x90 fs/configfs/item.c:149
configfs_symlink+0x5d1/0x1160 fs/configfs/symlink.c:236
vfs_symlink+0x18b/0x330 fs/namei.c:5794
filename_symlinkat+0x1cd/0x410 fs/namei.c:5819
__do_sys_symlinkat fs/namei.c:5839 [inline]
__se_sys_symlinkat+0x4e/0x2b0 fs/namei.c:5834
do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7f9d17b62459
Code: c0 79 93 eb d5 48 8d 7c 1d 00 eb 99 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 d0 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007f9d15b291f8 EFLAGS: 00000246 ORIG_RAX: 000000000000010a
RAX: ffffffffffffffda RBX: 00007f9d17bcf028 RCX: 00007f9d17b62459
RDX: 00007f9d17bcf028 RSI: 00000000ffffff9c RDI: 00007f9d17bcf060
RBP: 00007f9d17bcf060 R08: 00007f9d15b296c0 R09: 00007f9d15b296c0
R10: 00007f9d15b296c0 R11: 0000000000000246 R12: ffffffffffffffd0
R13: 0000000000000010 R14: 00007fff9f39c490 R15: 00007fff9f39c578
</TASK>
Allocated by task 5860:
kasan_save_stack mm/kasan/common.c:57 [inline]
kasan_save_track+0x3e/0x80 mm/kasan/common.c:78
poison_kmalloc_redzone mm/kasan/common.c:398 [inline]
__kasan_kmalloc+0x93/0xb0 mm/kasan/common.c:415
kasan_kmalloc include/linux/kasan.h:263 [inline]
__kmalloc_cache_noprof+0x321/0x600 mm/slub.c:5563
_kmalloc_noprof include/linux/slab.h:991 [inline]
_kzalloc_noprof include/linux/slab.h:1312 [inline]
nvmet_ports_make+0xe6/0xf00 drivers/nvme/target/configfs.c:2051
configfs_mkdir+0x4f6/0x9e0 fs/configfs/dir.c:1360
vfs_mkdir+0x40c/0x620 fs/namei.c:5410
filename_mkdirat+0x285/0x510 fs/namei.c:5443
__do_sys_mkdir fs/namei.c:5470 [inline]
__se_sys_mkdir+0x34/0x150 fs/namei.c:5467
do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
entry_SYSCALL_64_after_hwframe+0x77/0x7f
Freed by task 5863:
kasan_save_stack mm/kasan/common.c:57 [inline]
kasan_save_track+0x3e/0x80 mm/kasan/common.c:78
kasan_save_free_info+0x40/0x50 mm/kasan/generic.c:584
poison_slab_object mm/kasan/common.c:253 [inline]
__kasan_slab_free+0x5c/0x80 mm/kasan/common.c:285
kasan_slab_free include/linux/kasan.h:235 [inline]
slab_free_hook mm/slub.c:2748 [inline]
slab_free mm/slub.c:6499 [inline]
kfree+0x1c5/0x650 mm/slub.c:6792
config_item_cleanup fs/configfs/item.c:128 [inline]
config_item_release+0x13a/0x2d0 fs/configfs/item.c:137
configfs_symlink+0x5c7/0x1160 fs/configfs/symlink.c:234
vfs_symlink+0x18b/0x330 fs/namei.c:5794
filename_symlinkat+0x1cd/0x410 fs/namei.c:5819
__do_sys_symlinkat fs/namei.c:5839 [inline]
__se_sys_symlinkat+0x4e/0x2b0 fs/namei.c:5834
do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
entry_SYSCALL_64_after_hwframe+0x77/0x7f
The buggy address belongs to the object at ffff8881fa3e0000
which belongs to the cache kmalloc-4k of size 4096
The buggy address is located 1204 bytes inside of
freed 4096-byte region [ffff8881fa3e0000, ffff8881fa3e1000)
The buggy address belongs to the physical page:
page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x1fa3e0
head: order:3 mapcount:0 entire_mapcount:0 nr_pages_mapped:0 pincount:0
flags: 0x57ff00000000040(head|node=1|zone=2|lastcpupid=0x7ff)
page_type: f5(slab)
raw: 057ff00000000040 ffff888100042140 dead000000000122 0000000000000000
raw: 0000000000000000 0000000000040004 00000000f5000000 0000000000000000
head: 057ff00000000040 ffff888100042140 dead000000000122 0000000000000000
head: 0000000000000000 0000000000040004 00000000f5000000 0000000000000000
head: 057ff00000000003 fffffffffffffe01 00000000ffffffff 00000000ffffffff
head: ffffffffffffffff 0000000000000000 00000000ffffffff 0000000000000008
page dumped because: kasan: bad access detected
page_owner tracks the page as allocated
page last allocated via order 3, migratetype Unmovable, gfp_mask 0xd2040(__GFP_IO|__GFP_NOWARN|__GFP_NORETRY|__GFP_COMP|__GFP_NOMEMALLOC), pid 5857, tgid 5857 (sshd-session), ts 84765305688
set_page_owner include/linux/page_owner.h:33 [inline]
post_alloc_hook+0x1f9/0x250 mm/page_alloc.c:1871
prep_new_page mm/page_alloc.c:1879 [inline]
get_page_from_freelist+0x2209/0x2280 mm/page_alloc.c:3943
__alloc_frozen_pages_noprof+0x217/0x5a0 mm/page_alloc.c:5436
alloc_slab_page mm/slub.c:3347 [inline]
allocate_slab+0x7d/0x620 mm/slub.c:3462
new_slab mm/slub.c:3513 [inline]
refill_objects+0x2d5/0x350 mm/slub.c:7410
refill_sheaf mm/slub.c:2885 [inline]
__pcs_replace_empty_main+0x2c8/0x6c0 mm/slub.c:4774
alloc_from_pcs mm/slub.c:4850 [inline]
slab_alloc_node mm/slub.c:4984 [inline]
__do_kmalloc_node mm/slub.c:5413 [inline]
__kmalloc_noprof+0x47b/0x720 mm/slub.c:5439
_kmalloc_noprof include/linux/slab.h:995 [inline]
tomoyo_realpath_from_path+0xef/0x640 security/tomoyo/realpath.c:251
tomoyo_get_realpath security/tomoyo/file.c:151 [inline]
tomoyo_check_open_permission+0x229/0x470 security/tomoyo/file.c:776
security_file_open+0xa9/0x240 security/security.c:2739
do_dentry_open+0x4a0/0x1380 fs/open.c:973
vfs_open+0x3b/0x340 fs/open.c:1101
do_open fs/namei.c:4837 [inline]
path_openat+0x1443/0x1d60 fs/namei.c:5000
do_file_open+0x23e/0x4a0 fs/namei.c:5029
do_sys_openat2+0x115/0x200 fs/open.c:1417
do_sys_open fs/open.c:1423 [inline]
__do_sys_openat fs/open.c:1439 [inline]
__se_sys_openat fs/open.c:1434 [inline]
__x64_sys_openat+0x138/0x170 fs/open.c:1434
page last free pid 5681 tgid 5681 ts 74104377406 stack trace:
reset_page_owner include/linux/page_owner.h:26 [inline]
__free_pages_prepare mm/page_alloc.c:1418 [inline]
__free_frozen_pages+0xc50/0xd30 mm/page_alloc.c:2962
__slab_free+0x274/0x2c0 mm/slub.c:5815
qlink_free mm/kasan/quarantine.c:163 [inline]
qlist_free_all+0x99/0x100 mm/kasan/quarantine.c:179
kasan_quarantine_reduce+0x148/0x160 mm/kasan/quarantine.c:286
__kasan_slab_alloc+0x22/0x80 mm/kasan/common.c:350
kasan_slab_alloc include/linux/kasan.h:253 [inline]
slab_post_alloc_hook mm/slub.c:4683 [inline]
slab_alloc_node mm/slub.c:4996 [inline]
__kmalloc_cache_noprof+0x29f/0x600 mm/slub.c:5559
_kmalloc_noprof include/linux/slab.h:991 [inline]
_kzalloc_noprof include/linux/slab.h:1312 [inline]
nsim_fib6_rt_create drivers/net/netdevsim/fib.c:547 [inline]
nsim_fib6_rt_insert drivers/net/netdevsim/fib.c:752 [inline]
nsim_fib6_event drivers/net/netdevsim/fib.c:856 [inline]
nsim_fib_event+0xce4/0x8860 drivers/net/netdevsim/fib.c:889
nsim_fib_event_work+0x244/0x3b0 drivers/net/netdevsim/fib.c:1493
process_one_work kernel/workqueue.c:3387 [inline]
process_scheduled_works+0xc3d/0x1630 kernel/workqueue.c:3470
worker_thread+0x92d/0xe10 kernel/workqueue.c:3551
kthread+0x38b/0x480 kernel/kthread.c:436
ret_from_fork+0x514/0xb70 arch/x86/kernel/process.c:158
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
Memory state around the buggy address:
ffff8881fa3e0380: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
ffff8881fa3e0400: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
>ffff8881fa3e0480: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
^
ffff8881fa3e0500: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
ffff8881fa3e0580: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
==================================================================
]
|
| 430/4 |
2026/09/02 21:59 |
action |
set-test-failure |
0m
Results: map[NeedsIteration:true ReviewApproved:false ReviewComments:<nil> ReviewFeedback:]
|
| 431/2 |
2026/09/02 21:59 |
iteration |
1 |
11m
|
| 432/3 |
2026/09/02 21:59 |
agent |
patch-generator |
3m
Model:
Instruction:
You are an experienced Linux kernel developer tasked with creating a fix for a kernel bug.
You will be given a crash report, an initial explanation of the root cause done by another
kernel expert, and a summary of how similar bugs were fixed in the past.
Use the codeeditor tool to do code edits.
Note: you will not see your changes when looking at the code using codesearch tools.
Use the patch-diff tool to review the modifications you applied.
Your final reply should contain explanation of what you did in the patch and why
(details not present in the initial explanation of the bug).
Your fix must not just prevent the given crash, but also be the best fix for the underlying
root cause from the software engineering point of view. There can be several ways to fix the
same bug. Consider alternatives, and pick the best one. For example, additional checks may be
added at different locations/functions, it's usually better to place them earlier in the
execution to avoid multiple checks at various locations later.
Frequently the same coding mistake is done in several locations in the source code.
Check if your fix should be extended/applied to similar cases around to fix other similar bugs.
But don't go too wide, don't try to fix problems kernel-wide, fix similar issues
in the same file only.
If you are changing post-conditions of a function, consider all callers of the functions,
and if they need to be updated to handle new post-conditions. For example, if you make
a function that previously never returned a NULL, return NULL, consider if callers
need to be updated to handle NULL return value.
Prefer calling several tools at the same time to save round-trips.
Prompt:
The crash that corresponds to the bug is:
==================================================================
BUG: KASAN: slab-use-after-free in nvmet_port_subsys_allow_link+0x41/0x2e0 drivers/nvme/target/configfs.c:1059
Read of size 8 at addr ffff88811c6d44c8 by task syz-executor266/5850
CPU: 0 UID: 0 PID: 5850 Comm: syz-executor266 Not tainted syzkaller #1 PREEMPT(full)
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
Call Trace:
<TASK>
dump_stack_lvl+0xe8/0x150 lib/dump_stack.c:120
print_address_description+0x55/0x1e0 mm/kasan/report.c:378
print_report+0x58/0x70 mm/kasan/report.c:482
kasan_report+0x117/0x150 mm/kasan/report.c:595
nvmet_port_subsys_allow_link+0x41/0x2e0 drivers/nvme/target/configfs.c:1059
configfs_symlink+0x59a/0x1030 fs/configfs/symlink.c:196
vfs_symlink+0x18b/0x330 fs/namei.c:5794
filename_symlinkat+0x1cd/0x410 fs/namei.c:5819
__do_sys_symlinkat fs/namei.c:5839 [inline]
__se_sys_symlinkat+0x4e/0x2b0 fs/namei.c:5834
do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7f648f0cc459
Code: c0 79 93 eb d5 48 8d 7c 1d 00 eb 99 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 d0 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007f648c0911f8 EFLAGS: 00000246 ORIG_RAX: 000000000000010a
RAX: ffffffffffffffda RBX: 00007f648f139028 RCX: 00007f648f0cc459
RDX: 00007f648f139028 RSI: 00000000ffffff9c RDI: 00007f648f139060
RBP: 00007f648f139060 R08: 00007f648c0916c0 R09: 00007f648c0916c0
R10: 00007f648c0916c0 R11: 0000000000000246 R12: ffffffffffffffd0
R13: 0000000000000010 R14: 00007ffe91711350 R15: 00007ffe91711438
</TASK>
Allocated by task 5847:
kasan_save_stack mm/kasan/common.c:57 [inline]
kasan_save_track+0x3e/0x80 mm/kasan/common.c:78
poison_kmalloc_redzone mm/kasan/common.c:398 [inline]
__kasan_kmalloc+0x93/0xb0 mm/kasan/common.c:415
kasan_kmalloc include/linux/kasan.h:263 [inline]
__kmalloc_cache_noprof+0x321/0x600 mm/slub.c:5563
_kmalloc_noprof include/linux/slab.h:991 [inline]
_kzalloc_noprof include/linux/slab.h:1312 [inline]
nvmet_ports_make+0xe6/0xf00 drivers/nvme/target/configfs.c:2051
configfs_mkdir+0x4f6/0x9e0 fs/configfs/dir.c:1360
vfs_mkdir+0x40c/0x620 fs/namei.c:5410
filename_mkdirat+0x285/0x510 fs/namei.c:5443
__do_sys_mkdir fs/namei.c:5470 [inline]
__se_sys_mkdir+0x34/0x150 fs/namei.c:5467
do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
entry_SYSCALL_64_after_hwframe+0x77/0x7f
Freed by task 5847:
kasan_save_stack mm/kasan/common.c:57 [inline]
kasan_save_track+0x3e/0x80 mm/kasan/common.c:78
kasan_save_free_info+0x40/0x50 mm/kasan/generic.c:584
poison_slab_object mm/kasan/common.c:253 [inline]
__kasan_slab_free+0x5c/0x80 mm/kasan/common.c:285
kasan_slab_free include/linux/kasan.h:235 [inline]
slab_free_hook mm/slub.c:2748 [inline]
slab_free mm/slub.c:6499 [inline]
kfree+0x1c5/0x650 mm/slub.c:6792
config_item_cleanup fs/configfs/item.c:128 [inline]
config_item_release+0x13a/0x2d0 fs/configfs/item.c:137
configfs_rmdir+0x885/0x950 fs/configfs/dir.c:1571
vfs_rmdir+0x3e6/0x6a0 fs/namei.c:5515
filename_rmdir+0x292/0x520 fs/namei.c:5572
__do_sys_rmdir fs/namei.c:5595 [inline]
__se_sys_rmdir+0x2e/0x140 fs/namei.c:5592
do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
entry_SYSCALL_64_after_hwframe+0x77/0x7f
The buggy address belongs to the object at ffff88811c6d4000
which belongs to the cache kmalloc-4k of size 4096
The buggy address is located 1224 bytes inside of
freed 4096-byte region [ffff88811c6d4000, ffff88811c6d5000)
The buggy address belongs to the physical page:
page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x11c6d0
head: order:3 mapcount:0 entire_mapcount:0 nr_pages_mapped:0 pincount:0
flags: 0x17ff00000000040(head|node=0|zone=2|lastcpupid=0x7ff)
page_type: f5(slab)
raw: 017ff00000000040 ffff888100042140 dead000000000100 dead000000000122
raw: 0000000000000000 0000000000040004 00000000f5000000 0000000000000000
head: 017ff00000000040 ffff888100042140 dead000000000100 dead000000000122
head: 0000000000000000 0000000000040004 00000000f5000000 0000000000000000
head: 017ff00000000003 fffffffffffffe01 00000000ffffffff 00000000ffffffff
head: ffffffffffffffff 0000000000000000 00000000ffffffff 0000000000000008
page dumped because: kasan: bad access detected
page_owner tracks the page as allocated
page last allocated via order 3, migratetype Unmovable, gfp_mask 0xd20c0(__GFP_IO|__GFP_FS|__GFP_NOWARN|__GFP_NORETRY|__GFP_COMP|__GFP_NOMEMALLOC), pid 5843, tgid 5843 (syz-executor266), ts 63759869729
set_page_owner include/linux/page_owner.h:33 [inline]
post_alloc_hook+0x1f9/0x250 mm/page_alloc.c:1871
prep_new_page mm/page_alloc.c:1879 [inline]
get_page_from_freelist+0x2209/0x2280 mm/page_alloc.c:3943
__alloc_frozen_pages_noprof+0x217/0x5a0 mm/page_alloc.c:5436
alloc_slab_page mm/slub.c:3347 [inline]
allocate_slab+0x7d/0x620 mm/slub.c:3462
new_slab mm/slub.c:3513 [inline]
refill_objects+0x2d5/0x350 mm/slub.c:7410
refill_sheaf mm/slub.c:2885 [inline]
__pcs_replace_empty_main+0x2c8/0x6c0 mm/slub.c:4774
alloc_from_pcs mm/slub.c:4850 [inline]
slab_alloc_node mm/slub.c:4984 [inline]
__kmalloc_cache_noprof+0x39b/0x600 mm/slub.c:5559
_kmalloc_noprof include/linux/slab.h:991 [inline]
do_proc_readlink fs/proc/base.c:1817 [inline]
proc_pid_readlink+0x108/0x230 fs/proc/base.c:1847
vfs_readlink+0x4bb/0x540 fs/namei.c:6407
do_readlinkat+0x218/0x510 fs/stat.c:583
__do_sys_readlinkat fs/stat.c:599 [inline]
__se_sys_readlinkat fs/stat.c:596 [inline]
__x64_sys_readlinkat+0x9a/0xb0 fs/stat.c:596
do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
entry_SYSCALL_64_after_hwframe+0x77/0x7f
page last free pid 5843 tgid 5843 ts 63751651251 stack trace:
reset_page_owner include/linux/page_owner.h:26 [inline]
__free_pages_prepare mm/page_alloc.c:1418 [inline]
__free_frozen_pages+0xc50/0xd30 mm/page_alloc.c:2962
__slab_free+0x274/0x2c0 mm/slub.c:5815
qlink_free mm/kasan/quarantine.c:163 [inline]
qlist_free_all+0x99/0x100 mm/kasan/quarantine.c:179
kasan_quarantine_reduce+0x148/0x160 mm/kasan/quarantine.c:286
__kasan_slab_alloc+0x22/0x80 mm/kasan/common.c:350
kasan_slab_alloc include/linux/kasan.h:253 [inline]
slab_post_alloc_hook mm/slub.c:4683 [inline]
slab_alloc_node mm/slub.c:4996 [inline]
__do_kmalloc_node mm/slub.c:5413 [inline]
__kmalloc_noprof+0x307/0x720 mm/slub.c:5439
_kmalloc_noprof include/linux/slab.h:995 [inline]
tomoyo_add_entry security/tomoyo/common.c:2166 [inline]
tomoyo_supervisor+0xc53/0x1560 security/tomoyo/common.c:2238
tomoyo_audit_env_log security/tomoyo/environ.c:37 [inline]
tomoyo_env_perm+0x151/0x1f0 security/tomoyo/environ.c:64
tomoyo_environ security/tomoyo/domain.c:673 [inline]
tomoyo_find_next_domain+0x1332/0x1a90 security/tomoyo/domain.c:889
tomoyo_bprm_check_security+0x11b/0x180 security/tomoyo/tomoyo.c:102
security_bprm_check+0x85/0x240 security/security.c:820
search_binary_handler fs/exec.c:1749 [inline]
exec_binprm fs/exec.c:1791 [inline]
bprm_execve+0x888/0x1590 fs/exec.c:1847
do_execveat_common+0x4f4/0x670 fs/exec.c:1945
__do_sys_execve fs/exec.c:2018 [inline]
__se_sys_execve fs/exec.c:2012 [inline]
__x64_sys_execve+0x97/0xc0 fs/exec.c:2012
do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
entry_SYSCALL_64_after_hwframe+0x77/0x7f
Memory state around the buggy address:
ffff88811c6d4380: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
ffff88811c6d4400: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
>ffff88811c6d4480: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
^
ffff88811c6d4500: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
ffff88811c6d4580: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
==================================================================
The explanation of the root cause of the bug is:
An elegant and complete analysis of the root cause, along with a robust fix for this `configfs` race condition.
### 1. KASAN Crash Analysis & Offset Math
The crash is a slab-use-after-free read of size 8 at offset **1224** inside a 4096-byte region (`kmalloc-4k`).
The KASAN trace points to `nvmet_port_subsys_allow_link()`, which accesses `parent->ci_parent` where `parent` is a `struct config_item *`.
In `nvmet_ports_make()`, a `struct nvmet_port` is dynamically allocated. Let's look at its memory layout:
```c
struct nvmet_port {
struct list_head entry; // 16 bytes
struct nvmf_disc_rsp_page_entry disc_addr; // 1024 bytes
struct config_group group; // 136 bytes (offset 1040)
struct config_group subsys_group; // 136 bytes (offset 1176)
// ...
};
```
The `parent_item` passed to `allow_link` is `&port->subsys_group.cg_item`.
The offset of `subsys_group` is `16 + 1024 + 136 = 1176` bytes.
Inside `struct config_item`, the `ci_parent` pointer is at offset 48.
When `nvmet_port_subsys_allow_link` reads `parent->ci_parent`, it accesses memory at offset `1176 + 48 = 1224` bytes into the `nvmet_port` allocation. This **perfectly matches** the KASAN report:
> `The buggy address is located 1224 bytes inside of freed 4096-byte region`
### 2. The Root Cause (Race Condition)
The race occurs between Thread 1 creating a symlink in a default group (`ports/1/subsystems/`) and Thread 2 removing the parent root object (`ports/1`).
1. **Thread 1 (`symlinkat`)**:
- Enters `configfs_symlink()` with the VFS holding `inode_lock(dir)` on `ports/1/subsystems`.
- Acquires a reference to `parent_item` (the embedded `subsys_group.cg_item`).
- Drops `inode_unlock(dir)` to resolve the symlink target without deadlocking.
2. **Thread 2 (`rmdir`)**:
- Calls `configfs_rmdir()` on `ports/1`.
- Calls `configfs_detach_group()`, which recursively locks all default groups. Since Thread 1 dropped the lock on `ports/1/subsystems`, Thread 2 acquires it, marks the items as dead, detaches them, and unlocks.
- Calls `unlink_group()` on `ports/1`, dropping the subsystem's reference to the port.
- The `nvmet_port` refcount drops to 0, and the **entire `nvmet_port` struct is freed via `kfree()`**.
3. **Thread 1 Resumes**:
- Re-acquires `inode_lock(dir)`.
- Blindly calls `type->ct_item_ops->allow_link(parent_item, target_item)`.
- `parent_item` points to the `subsys_group` embedded inside the **already freed** `nvmet_port`, causing the UAF read at offset 1224.
- Even if `allow_link` wasn't called, jumping to `out_put` executes `config_item_put(parent_item)`, causing a secondary UAF on the kref.
### 3. Why Simple Fixes Fail
- **Re-acquiring `parent_item`**: You cannot safely drop `parent_item` before the unlock and re-acquire it via `configfs_get_config_item(dentry->d_parent)` after the relock. When `configfs_rmdir` detaches the item, it leaves `sd->s_element` as a dangling pointer. Re-acquiring it will dereference freed memory.
- **Holding `parent_item`**: Holding the reference to `parent_item` across the unlock is insufficient because `parent_item` is a **default group**. Default groups are embedded directly inside their parent structs (like `nvmet_port`). Their individual refcount being $> 0$ does **not** prevent the parent struct from being freed when the parent's refcount drops to 0.
### 4. The Proper Fix
To prevent the memory from being freed while the lock is dropped, `configfs_symlink()` must pin the **root object** (the dynamically allocated parent, e.g., `ports/1`) rather than just the embedded default group.
Because `configfs_symlink()` holds `inode_lock(dir)` initially, any concurrent `configfs_rmdir()` on an ancestor root object will block inside `detach_groups()` trying to acquire `inode_lock(dir)`. This guarantees the dentry tree is stable and we can safely walk up the `d_parent` chain to find the root object (the first dentry without the `CONFIGFS_USET_DEFAULT` flag) and take a reference to it.
After re-acquiring the lock, we must check `sd->s_frag->frag_dead` to see if the directory was removed while unlocked.
**Implementation for `fs/configfs/symlink.c`:**
```c
/* Add this helper function above configfs_symlink */
static struct config_item *configfs_get_root_item(struct dentry *dentry)
{
struct dentry *root_dentry = dentry;
struct configfs_dirent *sd;
while (root_dentry) {
sd = root_dentry->d_fsdata;
if (!(sd->s_type & CONFIGFS_USET_DEFAULT))
break;
root_dentry = root_dentry->d_parent;
}
return configfs_get_config_item(root_dentry);
}
int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
struct dentry *dentry, const char *symname)
{
int ret;
struct config_item *parent_item;
struct config_item *target_item = NULL;
struct config_item *root_item = NULL;
const struct config_item_type *type;
parent_item = configfs_get_config_item(dentry->d_parent);
type = parent_item->ci_type;
ret = -EPERM;
if (!type || !type->ct_item_ops ||
!type->ct_item_ops->allow_link)
goto out_put;
/*
* Pin the root object so that the parent_item (which may be an
* embedded default group) is not freed while we drop the lock.
*/
root_item = configfs_get_root_item(dentry->d_parent);
/*
* Drop the dir inode lock. The target path might be outside
* configfs, and we don't want to deadlock.
*/
inode_unlock(dir);
ret = get_target(symname, &target_item, dentry->d_sb);
inode_lock(dir);
if (ret)
goto out_put_root;
if (dentry->d_inode || d_unhashed(dentry))
ret = -EEXIST;
else
ret = inode_permission(&nop_mnt_idmap, dir,
MAY_WRITE | MAY_EXEC);
if (!ret) {
struct configfs_dirent *sd = dentry->d_parent->d_fsdata;
/* Check if the directory was removed while unlocked */
if (sd->s_frag->frag_dead) {
ret = -ENOENT;
} else {
ret = type->ct_item_ops->allow_link(parent_item, target_item);
}
}
if (!ret) {
mutex_lock(&configfs_symlink_mutex);
ret = create_link(parent_item, target_item, dentry);
mutex_unlock(&configfs_symlink_mutex);
if (ret && type->ct_item_ops->drop_link)
type->ct_item_ops->drop_link(parent_item,
target_item);
}
config_item_put(target_item);
out_put_root:
config_item_put(root_item);
out_put:
config_item_put(parent_item);
return ret;
}
```
Historical context on how similar bugs were fixed in the past:
Based on a review of the `fs/configfs/` history and past fixes for similar race conditions and use-after-free (UAF) bugs, here are the idioms, locking rules, and common patterns that should be followed when fixing this bug:
### 1. The Origin of the Race
The race condition was made possible by commit **`351e5d869e5a ("configfs: fix a deadlock in configfs_symlink()")`**. This commit introduced the `inode_unlock(dir)` and `inode_lock(dir)` around the target resolution in `configfs_symlink()` to prevent deadlocks. However, dropping this lock creates a window where a concurrent `configfs_rmdir()` can remove the parent directory.
### 2. Idiom: Pinning the Dynamically Allocated Root Object
In `configfs`, default groups (like `subsystems`) are embedded directly inside their parent structures (like `nvmet_port`). Holding a reference to an embedded default group does **not** prevent the parent structure from being freed when the parent's own refcount drops to 0.
To safely bridge the unlocked window, you must pin the dynamically allocated parent (the "root object"). The established pattern in `configfs` to distinguish between dynamically allocated items and embedded default groups is to check the **`CONFIGFS_USET_DEFAULT`** flag in `sd->s_type`.
* Functions like `detach_groups()` and `configfs_rmdir()` already use `!(sd->s_type & CONFIGFS_USET_DEFAULT)` to identify the boundaries of default groups.
* Walking up the `d_parent` chain until you hit a dentry without `CONFIGFS_USET_DEFAULT` and pinning it via `configfs_get_config_item()` is the correct and idiomatic way to ensure the entire memory allocation survives the unlocked window.
### 3. Idiom: Checking `frag_dead` for Concurrent Removals
After re-acquiring `inode_lock(dir)`, you must verify that the directory wasn't removed while the lock was dropped. The standard `configfs` idiom for this was introduced in commit **`b0841eefd969 ("configfs: provide exclusion between IO and removals")`**.
* When `configfs_rmdir()` or `configfs_unregister_group()` removes an item, it sets **`sd->s_frag->frag_dead = true`** (often while holding `frag_sem` for write).
* Throughout `fs/configfs/file.c`, operations (like `read`, `write`, `open`, `release`) check `if (!frag->frag_dead)` before proceeding.
* Applying this exact same check (`if (sd->s_frag->frag_dead) return -ENOENT;`) in `configfs_symlink()` after relocking perfectly aligns with the subsystem's established lifecycle rules.
### 4. Prior Art: Symlink Target Races
For historical context, a very similar race involving the symlink *target* (rather than the parent) was fixed in commit **`ba80aa909c99 ("configfs: Fix race between create_link and configfs_rmdir")`**.
* In that bug, the target was being removed concurrently. The fix was to check the `CONFIGFS_USET_DROPPING` flag under the `configfs_dirent_lock` before taking a reference to the target item.
* While that fix handled the *target* side of the symlink equation, your proposed fix correctly handles the *parent* side by combining the root object pinning with the `frag_dead` check.
### Summary
The proposed fix strategy in the bug report is completely sound and aligns with `configfs` design patterns. You should implement the `configfs_get_root_item()` helper to walk the dentry tree checking for `CONFIGFS_USET_DEFAULT`, pin that root object across the `inode_unlock/lock` boundary, and finally check `sd->s_frag->frag_dead` before proceeding with `allow_link()`.
Another developer tried to fix this bug, and come up with the following strategy for fixing:
### Cause of the Bug
The use-after-free crash occurs due to a race between symlink creation via `configfs_symlink()` and directory removal via `configfs_rmdir()`:
1. In `configfs_symlink()`, `inode_unlock(dir)` is called to drop the directory lock before resolving the symlink target (preventing potential deadlocks during path lookup).
2. If `configfs_symlink()` is operating within an embedded default group (e.g., `ports/1/subsystems`), holding a reference to `parent_item` (`subsys_group`) is not sufficient to prevent the containing parent structure (`struct nvmet_port` representing `ports/1`) from being freed when a concurrent `configfs_rmdir()` removes `ports/1`.
3. While unlocked, `configfs_rmdir()` can detach the default groups, mark the fragment dead (`frag->frag_dead = true`), and unlink/release the root object, freeing the underlying memory with `kfree()`.
4. When `configfs_symlink()` re-acquires `inode_lock(dir)`, it attempts to access `parent_item` and invoke `type->ct_item_ops->allow_link(parent_item, target_item)`, triggering slab use-after-free when dereferencing the freed memory.
### Fix
1. **Root Object Pinning**: Added `configfs_get_root_item()` to walk up the dentry tree past any default groups (`CONFIGFS_USET_DEFAULT`) to locate and take a reference on the dynamically allocated root object (`root_item`) prior to dropping `inode_lock(dir)`. Because `inode_lock(dir)` is held when this reference is taken, concurrent removals are blocked from completing detachment or releasing the root object until after `root_item` is acquired.
2. **Directory Liveness Check**: After re-acquiring `inode_lock(dir)`, check `sd->s_frag->frag_dead` to determine whether the directory was removed during the unlocked window. If so, return `-ENOENT` without calling `allow_link()` or attempting link creation.
3. **Reference Cleanup**: Added proper cleanup in `out_put_root` to drop `root_item` via `config_item_put(root_item)` along all exit paths.
and the following patch:
diff --git a/fs/configfs/symlink.c b/fs/configfs/symlink.c
index 31eb28b27..3e597371e 100644
--- a/fs/configfs/symlink.c
+++ b/fs/configfs/symlink.c
@@ -131,6 +131,19 @@ static int get_target(const char *symname, struct config_item **target,
return 0;
}
+static struct config_item *configfs_get_root_item(struct dentry *dentry)
+{
+ struct dentry *root_dentry = dentry;
+ struct configfs_dirent *sd;
+
+ while (root_dentry) {
+ sd = root_dentry->d_fsdata;
+ if (!(sd->s_type & CONFIGFS_USET_DEFAULT))
+ break;
+ root_dentry = root_dentry->d_parent;
+ }
+ return configfs_get_config_item(root_dentry);
+}
int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
struct dentry *dentry, const char *symname)
@@ -139,6 +152,7 @@ int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
struct configfs_dirent *sd;
struct config_item *parent_item;
struct config_item *target_item = NULL;
+ struct config_item *root_item = NULL;
const struct config_item_type *type;
sd = dentry->d_parent->d_fsdata;
@@ -157,6 +171,12 @@ int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
!type->ct_item_ops->allow_link)
goto out_put;
+ /*
+ * Pin the root object so that the parent_item (which may be an
+ * embedded default group) is not freed while we drop the lock.
+ */
+ root_item = configfs_get_root_item(dentry->d_parent);
+
/*
* This is really sick. What they wanted was a hybrid of
* link(2) and symlink(2) - they wanted the target resolved
@@ -185,15 +205,20 @@ int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
ret = get_target(symname, &target_item, dentry->d_sb);
inode_lock(dir);
if (ret)
- goto out_put;
+ goto out_put_root;
if (dentry->d_inode || d_unhashed(dentry))
ret = -EEXIST;
else
ret = inode_permission(&nop_mnt_idmap, dir,
MAY_WRITE | MAY_EXEC);
- if (!ret)
- ret = type->ct_item_ops->allow_link(parent_item, target_item);
+ if (!ret) {
+ /* Check if the directory was removed while unlocked */
+ if (sd->s_frag->frag_dead)
+ ret = -ENOENT;
+ else
+ ret = type->ct_item_ops->allow_link(parent_item, target_item);
+ }
if (!ret) {
mutex_lock(&configfs_symlink_mutex);
ret = create_link(parent_item, target_item, dentry);
@@ -205,6 +230,8 @@ int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
config_item_put(target_item);
+out_put_root:
+ config_item_put(root_item);
out_put:
config_item_put(parent_item);
return ret;
However, the patch testing failed with the following error:
==================================================================
BUG: KASAN: slab-use-after-free in instrument_atomic_read_write include/linux/instrumented.h:112 [inline]
BUG: KASAN: slab-use-after-free in atomic_fetch_sub_release include/linux/atomic/atomic-instrumented.h:400 [inline]
BUG: KASAN: slab-use-after-free in __refcount_sub_and_test include/linux/refcount.h:389 [inline]
BUG: KASAN: slab-use-after-free in __refcount_dec_and_test include/linux/refcount.h:432 [inline]
BUG: KASAN: slab-use-after-free in refcount_dec_and_test include/linux/refcount.h:450 [inline]
BUG: KASAN: slab-use-after-free in kref_put include/linux/kref.h:64 [inline]
BUG: KASAN: slab-use-after-free in config_item_put+0x26/0x90 fs/configfs/item.c:149
Write of size 4 at addr ffff8881fa3e04b4 by task syz-executor340/5863
CPU: 1 UID: 0 PID: 5863 Comm: syz-executor340 Not tainted syzkaller #1 PREEMPT(full)
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
Call Trace:
<TASK>
dump_stack_lvl+0xe8/0x150 lib/dump_stack.c:120
print_address_description+0x55/0x1e0 mm/kasan/report.c:378
print_report+0x58/0x70 mm/kasan/report.c:482
kasan_report+0x117/0x150 mm/kasan/report.c:595
check_region_inline mm/kasan/generic.c:-1 [inline]
kasan_check_range+0x264/0x2c0 mm/kasan/generic.c:200
instrument_atomic_read_write include/linux/instrumented.h:112 [inline]
atomic_fetch_sub_release include/linux/atomic/atomic-instrumented.h:400 [inline]
__refcount_sub_and_test include/linux/refcount.h:389 [inline]
__refcount_dec_and_test include/linux/refcount.h:432 [inline]
refcount_dec_and_test include/linux/refcount.h:450 [inline]
kref_put include/linux/kref.h:64 [inline]
config_item_put+0x26/0x90 fs/configfs/item.c:149
configfs_symlink+0x5d1/0x1160 fs/configfs/symlink.c:236
vfs_symlink+0x18b/0x330 fs/namei.c:5794
filename_symlinkat+0x1cd/0x410 fs/namei.c:5819
__do_sys_symlinkat fs/namei.c:5839 [inline]
__se_sys_symlinkat+0x4e/0x2b0 fs/namei.c:5834
do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7f9d17b62459
Code: c0 79 93 eb d5 48 8d 7c 1d 00 eb 99 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 d0 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007f9d15b291f8 EFLAGS: 00000246 ORIG_RAX: 000000000000010a
RAX: ffffffffffffffda RBX: 00007f9d17bcf028 RCX: 00007f9d17b62459
RDX: 00007f9d17bcf028 RSI: 00000000ffffff9c RDI: 00007f9d17bcf060
RBP: 00007f9d17bcf060 R08: 00007f9d15b296c0 R09: 00007f9d15b296c0
R10: 00007f9d15b296c0 R11: 0000000000000246 R12: ffffffffffffffd0
R13: 0000000000000010 R14: 00007fff9f39c490 R15: 00007fff9f39c578
</TASK>
Allocated by task 5860:
kasan_save_stack mm/kasan/common.c:57 [inline]
kasan_save_track+0x3e/0x80 mm/kasan/common.c:78
poison_kmalloc_redzone mm/kasan/common.c:398 [inline]
__kasan_kmalloc+0x93/0xb0 mm/kasan/common.c:415
kasan_kmalloc include/linux/kasan.h:263 [inline]
__kmalloc_cache_noprof+0x321/0x600 mm/slub.c:5563
_kmalloc_noprof include/linux/slab.h:991 [inline]
_kzalloc_noprof include/linux/slab.h:1312 [inline]
nvmet_ports_make+0xe6/0xf00 drivers/nvme/target/configfs.c:2051
configfs_mkdir+0x4f6/0x9e0 fs/configfs/dir.c:1360
vfs_mkdir+0x40c/0x620 fs/namei.c:5410
filename_mkdirat+0x285/0x510 fs/namei.c:5443
__do_sys_mkdir fs/namei.c:5470 [inline]
__se_sys_mkdir+0x34/0x150 fs/namei.c:5467
do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
entry_SYSCALL_64_after_hwframe+0x77/0x7f
Freed by task 5863:
kasan_save_stack mm/kasan/common.c:57 [inline]
kasan_save_track+0x3e/0x80 mm/kasan/common.c:78
kasan_save_free_info+0x40/0x50 mm/kasan/generic.c:584
poison_slab_object mm/kasan/common.c:253 [inline]
__kasan_slab_free+0x5c/0x80 mm/kasan/common.c:285
kasan_slab_free include/linux/kasan.h:235 [inline]
slab_free_hook mm/slub.c:2748 [inline]
slab_free mm/slub.c:6499 [inline]
kfree+0x1c5/0x650 mm/slub.c:6792
config_item_cleanup fs/configfs/item.c:128 [inline]
config_item_release+0x13a/0x2d0 fs/configfs/item.c:137
configfs_symlink+0x5c7/0x1160 fs/configfs/symlink.c:234
vfs_symlink+0x18b/0x330 fs/namei.c:5794
filename_symlinkat+0x1cd/0x410 fs/namei.c:5819
__do_sys_symlinkat fs/namei.c:5839 [inline]
__se_sys_symlinkat+0x4e/0x2b0 fs/namei.c:5834
do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
entry_SYSCALL_64_after_hwframe+0x77/0x7f
The buggy address belongs to the object at ffff8881fa3e0000
which belongs to the cache kmalloc-4k of size 4096
The buggy address is located 1204 bytes inside of
freed 4096-byte region [ffff8881fa3e0000, ffff8881fa3e1000)
The buggy address belongs to the physical page:
page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x1fa3e0
head: order:3 mapcount:0 entire_mapcount:0 nr_pages_mapped:0 pincount:0
flags: 0x57ff00000000040(head|node=1|zone=2|lastcpupid=0x7ff)
page_type: f5(slab)
raw: 057ff00000000040 ffff888100042140 dead000000000122 0000000000000000
raw: 0000000000000000 0000000000040004 00000000f5000000 0000000000000000
head: 057ff00000000040 ffff888100042140 dead000000000122 0000000000000000
head: 0000000000000000 0000000000040004 00000000f5000000 0000000000000000
head: 057ff00000000003 fffffffffffffe01 00000000ffffffff 00000000ffffffff
head: ffffffffffffffff 0000000000000000 00000000ffffffff 0000000000000008
page dumped because: kasan: bad access detected
page_owner tracks the page as allocated
page last allocated via order 3, migratetype Unmovable, gfp_mask 0xd2040(__GFP_IO|__GFP_NOWARN|__GFP_NORETRY|__GFP_COMP|__GFP_NOMEMALLOC), pid 5857, tgid 5857 (sshd-session), ts 84765305688
set_page_owner include/linux/page_owner.h:33 [inline]
post_alloc_hook+0x1f9/0x250 mm/page_alloc.c:1871
prep_new_page mm/page_alloc.c:1879 [inline]
get_page_from_freelist+0x2209/0x2280 mm/page_alloc.c:3943
__alloc_frozen_pages_noprof+0x217/0x5a0 mm/page_alloc.c:5436
alloc_slab_page mm/slub.c:3347 [inline]
allocate_slab+0x7d/0x620 mm/slub.c:3462
new_slab mm/slub.c:3513 [inline]
refill_objects+0x2d5/0x350 mm/slub.c:7410
refill_sheaf mm/slub.c:2885 [inline]
__pcs_replace_empty_main+0x2c8/0x6c0 mm/slub.c:4774
alloc_from_pcs mm/slub.c:4850 [inline]
slab_alloc_node mm/slub.c:4984 [inline]
__do_kmalloc_node mm/slub.c:5413 [inline]
__kmalloc_noprof+0x47b/0x720 mm/slub.c:5439
_kmalloc_noprof include/linux/slab.h:995 [inline]
tomoyo_realpath_from_path+0xef/0x640 security/tomoyo/realpath.c:251
tomoyo_get_realpath security/tomoyo/file.c:151 [inline]
tomoyo_check_open_permission+0x229/0x470 security/tomoyo/file.c:776
security_file_open+0xa9/0x240 security/security.c:2739
do_dentry_open+0x4a0/0x1380 fs/open.c:973
vfs_open+0x3b/0x340 fs/open.c:1101
do_open fs/namei.c:4837 [inline]
path_openat+0x1443/0x1d60 fs/namei.c:5000
do_file_open+0x23e/0x4a0 fs/namei.c:5029
do_sys_openat2+0x115/0x200 fs/open.c:1417
do_sys_open fs/open.c:1423 [inline]
__do_sys_openat fs/open.c:1439 [inline]
__se_sys_openat fs/open.c:1434 [inline]
__x64_sys_openat+0x138/0x170 fs/open.c:1434
page last free pid 5681 tgid 5681 ts 74104377406 stack trace:
reset_page_owner include/linux/page_owner.h:26 [inline]
__free_pages_prepare mm/page_alloc.c:1418 [inline]
__free_frozen_pages+0xc50/0xd30 mm/page_alloc.c:2962
__slab_free+0x274/0x2c0 mm/slub.c:5815
qlink_free mm/kasan/quarantine.c:163 [inline]
qlist_free_all+0x99/0x100 mm/kasan/quarantine.c:179
kasan_quarantine_reduce+0x148/0x160 mm/kasan/quarantine.c:286
__kasan_slab_alloc+0x22/0x80 mm/kasan/common.c:350
kasan_slab_alloc include/linux/kasan.h:253 [inline]
slab_post_alloc_hook mm/slub.c:4683 [inline]
slab_alloc_node mm/slub.c:4996 [inline]
__kmalloc_cache_noprof+0x29f/0x600 mm/slub.c:5559
_kmalloc_noprof include/linux/slab.h:991 [inline]
_kzalloc_noprof include/linux/slab.h:1312 [inline]
nsim_fib6_rt_create drivers/net/netdevsim/fib.c:547 [inline]
nsim_fib6_rt_insert drivers/net/netdevsim/fib.c:752 [inline]
nsim_fib6_event drivers/net/netdevsim/fib.c:856 [inline]
nsim_fib_event+0xce4/0x8860 drivers/net/netdevsim/fib.c:889
nsim_fib_event_work+0x244/0x3b0 drivers/net/netdevsim/fib.c:1493
process_one_work kernel/workqueue.c:3387 [inline]
process_scheduled_works+0xc3d/0x1630 kernel/workqueue.c:3470
worker_thread+0x92d/0xe10 kernel/workqueue.c:3551
kthread+0x38b/0x480 kernel/kthread.c:436
ret_from_fork+0x514/0xb70 arch/x86/kernel/process.c:158
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
Memory state around the buggy address:
ffff8881fa3e0380: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
ffff8881fa3e0400: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
>ffff8881fa3e0480: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
^
ffff8881fa3e0500: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
ffff8881fa3e0580: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
==================================================================
If the error is fixable, and the fix patch is correct overall,
then create a new fixed patch based on the provided one with the errors fixed.
If the error points to a fundamental issue with the approach in the patch,
then create a new patch from scratch.
Note: in both cases the source tree does not contain the patch yet
(so if you want to create a new fixed patch, you need to recreate it
in its entirety from scratch using the codeeditor tool).
Reply: ### Explanation of the Bug & Fix
#### Root Cause
In `configfs_symlink()`, `inode_unlock(dir)` is called before resolving the symlink target (via `get_target()`) to avoid deadlocks during pathname lookup.
When `configfs_symlink()` is operating within a default group (such as `ports/1/subsystems/` in NVMe-oF target), the `parent_item` is an embedded default group (`subsys_group`) inside the dynamically allocated parent object (`struct nvmet_port`). Because default groups are embedded within their containing parent structure, holding a reference to `parent_item` alone does not prevent the containing parent structure (`nvmet_port`) from being freed when a concurrent `configfs_rmdir()` removes the root object (`ports/1`).
When `configfs_rmdir()` runs concurrently during the unlocked window, it detaches all default groups, unlinks the root group, sets `sd->s_frag->frag_dead = true`, and drops the subsystem's reference to the root group, freeing the entire `nvmet_port` structure with `kfree()`. When `configfs_symlink()` re-acquires `inode_lock(dir)`, accessing `parent_item` triggers a use-after-free read in `allow_link`.
#### Why the Previous Attempt Failed
The previous patch attempted to solve this by acquiring `root_item` via `configfs_get_root_item()` and checking `frag_dead`. However, in the cleanup exit path it put `root_item` **before** `parent_item`:
```c
out_put_root:
config_item_put(root_item);
out_put:
config_item_put(parent_item);
```
When `config_item_put(root_item)` dropped the refcount of `root_item` to 0, `config_item_cleanup(root_item)` invoked the release callback (`nvmet_port_release()`) and freed `nvmet_port`. Immediately afterwards, `config_item_put(parent_item)` attempted to decrement the kref inside the embedded `subsys_group` within the already-freed `nvmet_port`, resulting in a slab-use-after-free write on `kref_put`.
#### The Fix
1. **Pin the Root Object**: Add `configfs_get_root_item()` to walk up the dentry tree past any default groups (`CONFIGFS_USET_DEFAULT`) while holding `inode_lock(dir)` and acquire a reference to the dynamically allocated root object (`root_item`).
2. **Check for Directory Removal**: After re-acquiring `inode_lock(dir)`, check `sd->s_frag->frag_dead`. If the directory was removed during the unlocked window, return `-ENOENT` instead of proceeding with `allow_link()`.
3. **Correct Release Order**: Put `parent_item` **before** `root_item` in `out_put:`. This guarantees that the embedded child item's reference is always released while the memory of the containing root object remains pinned by `root_item`.
|
| 433/4 |
2026/09/02 21:59 |
llm |
patch-generator |
0m
Model:
Tokens:
input: 18340
output: 38
thoughts: 713
Thoughts: **Analyzing the KASAN Slab-Use-After-Free in configfs**
Okay, let's break this down. First, the crash report. It clearly points to a use-after-free vulnerability within `configfs`. The KASAN report immediately flags it: "slab-use-after-free" and the call traces clearly shows this being kicked off with the `configfs_symlink` function.
I'm seeing `atomic_fetch_sub_release` as well, but the key is within `configfs_symlink`. Looking at the lines in question, line 234 and 236... ah, I see! `config_item_put` is being called *twice*. The first call, on `root_item`, is the culprit. Task 5863 is calling `config_item_put(root_item)`.
That's the critical point. That call *decrements* the refcount of `root_item`. It *immediately* frees the memory when that refcount hits zero within `config_item_release`. This is because `root_item` appears to be linked to a `struct nvmet_port` which contains the parent item, `parent_item` (likely the default group, `subsys_group`). Then, *right after* the free, line 236 is hit: `config_item_put(parent_item)`. Boom! It's accessing freed memory, specifically `parent_item->ci_kref` causing the KASAN error.
So the fix is clear: either change the call stack to protect the `parent_item` from being freed when `root_item` is freed, or protect the `root_item` from freeing. I'll need to dig into the `fs/configfs/` code to understand the dependencies and how these items are linked to determine the best solution to guard against this race condition, specifically looking at `configfs_symlink`. I'll start with `read-file` and `codesearch-*` to explore the code.
|
| 434/4 |
2026/09/02 21:59 |
tool |
read-file |
0m
Args: map[File:fs/configfs/symlink.c FirstLine:120 LineCount:100]
Results: map[Contents: 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: }
133:
134:
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: }
212:
213: int configfs_unlink(struct inode *dir, struct dentry *dentry)
214: {
215: struct configfs_dirent *sd = dentry->d_fsdata, *target_sd;
216: struct config_item *parent_item;
217: const struct config_item_type *type;
218: int ret;
219:
]
|
| 435/4 |
2026/09/02 21:59 |
llm |
patch-generator |
0m
Model:
Tokens:
input: 21109
output: 32
thoughts: 408
Thoughts: **Analyzing a Potential Kernel Bug in ConfigFS Symlink Handling**
Okay, let's break this down. I'm looking at `fs/configfs/symlink.c` and trying to understand a potential issue. From what I see in the previous patch, there's a problem with the order of `config_item_put` calls in `out_put_root` and `out_put`. The code currently has:
```c
out_put_root:
config_item_put(root_item);
out_put:
config_item_put(parent_item);
return ret;
```
It seems like if `root_item` is released *before* `parent_item`, there's a race condition. When `root_item`'s refcount hits zero, `config_item_release` is called, which could free the underlying data structure β let's say `nvmet_port` or simply use `kfree(port)`. Then, the subsequent `config_item_put(parent_item)` tries to operate on memory that's already been freed. Boom!
However, if `parent_item` is released *before* `root_item`, the situation seems safer. `parent_item`'s refcount is decremented, and the corresponding cleanup (if there is any) runs without immediately freeing the memory. Then, when `root_item` is put, and its refcount drops to zero, the memory containing the struct can be safely released.
So, is it truly just a matter of the order of those two calls? I have to consider the intricate relationship between `config_item`s, default groups, and how config groups interact overall within the kernel. I'm going to need to do some more digging. Time to check `fs/configfs/` with some `grepper` searches or `read-file` to look into the specifics of these structures, their lifecycles, and their interactions.
|
| 436/4 |
2026/09/02 21:59 |
tool |
grepper |
0m
Args: map[Expression:CONFIGFS_USET_DEFAULT PathPrefix:fs/configfs/]
Results: map[Output:fs/configfs/configfs_internal.h=30=struct configfs_dirent {
--
fs/configfs/configfs_internal.h-52-#define CONFIGFS_USET_DIR 0x0040
fs/configfs/configfs_internal.h:53:#define CONFIGFS_USET_DEFAULT 0x0080
fs/configfs/configfs_internal.h-54-#define CONFIGFS_USET_DROPPING 0x0100
--
fs/configfs/dir.c=502=static int configfs_detach_prep(struct configfs_dirent *parent_sd, struct dentry **wait)
--
fs/configfs/dir.c-518- continue;
fs/configfs/dir.c:519: if (sd->s_type & CONFIGFS_USET_DEFAULT) {
fs/configfs/dir.c-520- /* Abort if racing with mkdir() */
--
fs/configfs/dir.c=548=static void configfs_detach_rollback(struct configfs_dirent *parent_sd)
--
fs/configfs/dir.c-554- list_for_each_entry(sd, &parent_sd->s_children, s_sibling)
fs/configfs/dir.c:555: if (sd->s_type & CONFIGFS_USET_DEFAULT)
fs/configfs/dir.c-556- configfs_detach_rollback(sd);
--
fs/configfs/dir.c=664=static void detach_groups(struct dentry *dentry)
--
fs/configfs/dir.c-673- next = next_dirent(parent_sd, sd);
fs/configfs/dir.c:674: if (!(sd->s_type & CONFIGFS_USET_DEFAULT))
fs/configfs/dir.c-675- continue;
--
fs/configfs/dir.c=703=static int create_default_group(struct dentry *parent,
--
fs/configfs/dir.c-722- sd = child->d_fsdata;
fs/configfs/dir.c:723: sd->s_type |= CONFIGFS_USET_DEFAULT;
fs/configfs/dir.c-724- } else {
--
fs/configfs/dir.c=977=static void configfs_dump_one(struct configfs_dirent *sd, int level)
--
fs/configfs/dir.c-986- type_print(CONFIGFS_USET_DIR);
fs/configfs/dir.c:987: type_print(CONFIGFS_USET_DEFAULT);
fs/configfs/dir.c-988- type_print(CONFIGFS_USET_DROPPING);
--
fs/configfs/dir.c=1469=static int configfs_rmdir(struct inode *dir, struct dentry *dentry)
--
fs/configfs/dir.c-1479- sd = dentry->d_fsdata;
fs/configfs/dir.c:1480: if (sd->s_type & CONFIGFS_USET_DEFAULT)
fs/configfs/dir.c-1481- return -EPERM;
]
|
| 437/4 |
2026/09/02 21:59 |
llm |
patch-generator |
0m
Model:
Tokens:
input: 22807
output: 35
thoughts: 56
Thoughts: Here's my take on the thought process, as if it were my own:
**Diving into ConfigFS Cleanup and Release**
Okay, time to get my hands dirty. I'm going to dig into the `fs/configfs/item.c` and `fs/configfs/dir.c` code. Specifically, I need to refresh my understanding around how `config_item` objects are cleaned up and released, so I need to go spelunking in the code. I'll be focusing on the logic around `config_item_cleanup`, `config_item_release`, and `config_item_put` - and also seeing how `configfs_rmdir` plays into it all, as removal is always a critical point for object lifecycles. I'm especially interested in understanding how the reference counting works and how these functions interact to ensure no memory leaks or dangling pointers occur when configfs items and directories are removed. I'll be looking for any potential race conditions or synchronization issues in these operations, too, because complex filesystem implementations are always prone to these types of issues. This whole area can get pretty intricate, so I'll be keeping a sharp eye out for potential design flaws and areas for improvement.
|
| 438/4 |
2026/09/02 21:59 |
tool |
read-file |
0m
Args: map[File:fs/configfs/item.c FirstLine:1 LineCount:100]
Results: map[Contents: 1: // SPDX-License-Identifier: GPL-2.0-or-later
2: /*
3: * item.c - library routines for handling generic config items
4: *
5: * Based on kobject:
6: * kobject is Copyright (c) 2002-2003 Patrick Mochel
7: *
8: * configfs Copyright (C) 2005 Oracle. All rights reserved.
9: *
10: * Please see the file Documentation/filesystems/configfs.rst for
11: * critical information about using the config_item interface.
12: */
13:
14: #include <linux/string.h>
15: #include <linux/module.h>
16: #include <linux/stat.h>
17: #include <linux/slab.h>
18:
19: #include <linux/configfs.h>
20:
21:
22: static inline struct config_item *to_item(struct list_head *entry)
23: {
24: return container_of(entry, struct config_item, ci_entry);
25: }
26:
27: /* Evil kernel */
28: static void config_item_release(struct kref *kref);
29:
30: /**
31: * config_item_init - initialize item.
32: * @item: item in question.
33: */
34: static void config_item_init(struct config_item *item)
35: {
36: kref_init(&item->ci_kref);
37: INIT_LIST_HEAD(&item->ci_entry);
38: }
39:
40: /**
41: * config_item_set_name - Set the name of an item
42: * @item: item.
43: * @fmt: The vsnprintf()'s format string.
44: *
45: * If strlen(name) >= CONFIGFS_ITEM_NAME_LEN, then use a
46: * dynamically allocated string that @item->ci_name points to.
47: * Otherwise, use the static @item->ci_namebuf array.
48: */
49: int config_item_set_name(struct config_item *item, const char *fmt, ...)
50: {
51: int limit = CONFIGFS_ITEM_NAME_LEN;
52: int need;
53: va_list args;
54: char *name;
55:
56: /*
57: * First, try the static array
58: */
59: va_start(args, fmt);
60: need = vsnprintf(item->ci_namebuf, limit, fmt, args);
61: va_end(args);
62: if (need < limit)
63: name = item->ci_namebuf;
64: else {
65: va_start(args, fmt);
66: name = kvasprintf(GFP_KERNEL, fmt, args);
67: va_end(args);
68: if (!name)
69: return -ENOMEM;
70: }
71:
72: /* Free the old name, if necessary. */
73: if (item->ci_name && item->ci_name != item->ci_namebuf)
74: kfree(item->ci_name);
75:
76: /* Now, set the new name */
77: item->ci_name = name;
78: return 0;
79: }
80: EXPORT_SYMBOL(config_item_set_name);
81:
82: void config_item_init_type_name(struct config_item *item,
83: const char *name,
84: const struct config_item_type *type)
85: {
86: config_item_set_name(item, "%s", name);
87: item->ci_type = type;
88: config_item_init(item);
89: }
90: EXPORT_SYMBOL(config_item_init_type_name);
91:
92: void config_group_init_type_name(struct config_group *group, const char *name,
93: const struct config_item_type *type)
94: {
95: config_item_set_name(&group->cg_item, "%s", name);
96: group->cg_item.ci_type = type;
97: config_group_init(group);
98: }
99: EXPORT_SYMBOL(config_group_init_type_name);
100:
]
|
| 439/4 |
2026/09/02 21:59 |
llm |
patch-generator |
0m
Model:
Tokens:
input: 24513
output: 37
thoughts: 17
Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience:
**Diving Back into `fs/configfs/item.c`**
Alright, time to get back to it. Let's see what else `fs/configfs/item.c` has to offer. I've been focusing on the core object handling, but there's always more to explore in this subsystem. I'm keen to understand the nuances of how items are created, linked, and eventually cleaned up within the configfs framework. I'm especially interested in the edge cases. Where are the potential race conditions? How does the locking strategy hold up under heavy contention? What are the implications of the item lifecycle on memory management and resource allocation? I'm anticipating finding more details on the finer points of attribute handling, as that's often where the real magic happens in a virtual filesystem like this. Looking forward to see what catches my eye this time.
|
| 440/4 |
2026/09/02 22:00 |
tool |
read-file |
0m
Args: map[File:fs/configfs/item.c FirstLine:101 LineCount:100]
Results: map[Contents: 101: struct config_item *config_item_get(struct config_item *item)
102: {
103: if (item)
104: kref_get(&item->ci_kref);
105: return item;
106: }
107: EXPORT_SYMBOL(config_item_get);
108:
109: struct config_item *config_item_get_unless_zero(struct config_item *item)
110: {
111: if (item && kref_get_unless_zero(&item->ci_kref))
112: return item;
113: return NULL;
114: }
115: EXPORT_SYMBOL(config_item_get_unless_zero);
116:
117: static void config_item_cleanup(struct config_item *item)
118: {
119: const struct config_item_type *t = item->ci_type;
120: struct config_group *s = item->ci_group;
121: struct config_item *parent = item->ci_parent;
122:
123: pr_debug("config_item %s: cleaning up\n", config_item_name(item));
124: if (item->ci_name != item->ci_namebuf)
125: kfree(item->ci_name);
126: item->ci_name = NULL;
127: if (t && t->ct_item_ops && t->ct_item_ops->release)
128: t->ct_item_ops->release(item);
129: if (s)
130: config_group_put(s);
131: if (parent)
132: config_item_put(parent);
133: }
134:
135: static void config_item_release(struct kref *kref)
136: {
137: config_item_cleanup(container_of(kref, struct config_item, ci_kref));
138: }
139:
140: /**
141: * config_item_put - decrement refcount for item.
142: * @item: item.
143: *
144: * Decrement the refcount, and if 0, call config_item_cleanup().
145: */
146: void config_item_put(struct config_item *item)
147: {
148: if (item)
149: kref_put(&item->ci_kref, config_item_release);
150: }
151: EXPORT_SYMBOL(config_item_put);
152:
153: /**
154: * config_group_init - initialize a group for use
155: * @group: config_group
156: */
157: void config_group_init(struct config_group *group)
158: {
159: config_item_init(&group->cg_item);
160: INIT_LIST_HEAD(&group->cg_children);
161: INIT_LIST_HEAD(&group->default_groups);
162: }
163: EXPORT_SYMBOL(config_group_init);
164:
165: /**
166: * config_group_find_item - search for item in group.
167: * @group: group we're looking in.
168: * @name: item's name.
169: *
170: * Iterate over @group->cg_list, looking for a matching config_item.
171: * If matching item is found take a reference and return the item.
172: * Caller must have locked group via @group->cg_subsys->su_mtx.
173: */
174: struct config_item *config_group_find_item(struct config_group *group,
175: const char *name)
176: {
177: struct list_head *entry;
178: struct config_item *ret = NULL;
179:
180: list_for_each(entry, &group->cg_children) {
181: struct config_item *item = to_item(entry);
182: if (config_item_name(item) &&
183: !strcmp(config_item_name(item), name)) {
184: ret = config_item_get(item);
185: break;
186: }
187: }
188: return ret;
189: }
190: EXPORT_SYMBOL(config_group_find_item);
]
|
| 441/4 |
2026/09/02 22:00 |
llm |
patch-generator |
0m
Model:
Tokens:
input: 26140
output: 33
thoughts: 55
Thoughts: Here's my analysis of what's going on:
**Deep Dive into `configfs_get_config_item()`**
Okay, so I'm looking at `configfs_get_config_item(dentry)`. My instinct is to understand exactly how this function works. The core functionality must be here, so I'm going to jump straight into the implementation of `configfs_get_config_item()`. I need to figure out how it's designed, its dependencies, what it returns β basically, the whole picture. Let me go ahead and search the code base for the definition of `configfs_get_config_item`. That's where the real answers are going to be. Time to dig in!
|
| 442/4 |
2026/09/02 22:00 |
tool |
grepper |
0m
Args: map[Expression:configfs_get_config_item PathPrefix:fs/configfs/]
Results: map[Output:fs/configfs/configfs_internal.h=113=static inline struct configfs_bin_attribute *to_bin_attr(struct dentry *dentry)
--
fs/configfs/configfs_internal.h-119-
fs/configfs/configfs_internal.h:120:static inline struct config_item *configfs_get_config_item(struct dentry *dentry)
fs/configfs/configfs_internal.h-121-{
--
fs/configfs/dir.c=1289=static struct dentry *configfs_mkdir(struct mnt_idmap *idmap, struct inode *dir,
--
fs/configfs/dir.c-1330- /* Get a working ref for the duration of this function */
fs/configfs/dir.c:1331: parent_item = configfs_get_config_item(dentry->d_parent);
fs/configfs/dir.c-1332- type = parent_item->ci_type;
--
fs/configfs/dir.c=1469=static int configfs_rmdir(struct inode *dir, struct dentry *dentry)
--
fs/configfs/dir.c-1483- /* Get a working ref until we have the child */
fs/configfs/dir.c:1484: parent_item = configfs_get_config_item(dentry->d_parent);
fs/configfs/dir.c-1485- subsys = to_config_group(parent_item)->cg_subsys;
--
fs/configfs/dir.c-1544- /* Get a working ref for the duration of this function */
fs/configfs/dir.c:1545: item = configfs_get_config_item(dentry);
fs/configfs/dir.c-1546-
--
fs/configfs/symlink.c=117=static int get_target(const char *symname, struct config_item **target,
--
fs/configfs/symlink.c-127- return -EPERM;
fs/configfs/symlink.c:128: *target = configfs_get_config_item(path.dentry);
fs/configfs/symlink.c-129- if (!*target)
--
fs/configfs/symlink.c=135=int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
--
fs/configfs/symlink.c-151-
fs/configfs/symlink.c:152: parent_item = configfs_get_config_item(dentry->d_parent);
fs/configfs/symlink.c-153- type = parent_item->ci_type;
--
fs/configfs/symlink.c=213=int configfs_unlink(struct inode *dir, struct dentry *dentry)
--
fs/configfs/symlink.c-225-
fs/configfs/symlink.c:226: parent_item = configfs_get_config_item(dentry->d_parent);
fs/configfs/symlink.c-227- type = parent_item->ci_type;
]
|
| 443/4 |
2026/09/02 22:00 |
llm |
patch-generator |
0m
Model:
Tokens:
input: 27204
output: 39
thoughts: 24
Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience:
**Diving into configfs_internal.h**
Okay, time to get my hands dirty. Let's see... I need to understand something a bit deeper about the internal workings of ConfigFS. Right, I remember that `configfs_internal.h` is the place to be for the nitty-gritty details. Let's start around line 120. I'm expecting to find some crucial data structures or function prototypes that are key to the control and management of configfs and the interactions between the kernel and userspace. Maybe something related to attribute handling, or object creation, or potentially the underlying file system operations. Let's go take a look and get a better grip of the details.
|
| 444/4 |
2026/09/02 22:00 |
tool |
read-file |
0m
Args: map[File:fs/configfs/configfs_internal.h FirstLine:110 LineCount:40]
Results: map[Contents: 110: return ((struct configfs_attribute *) sd->s_element);
111: }
112:
113: static inline struct configfs_bin_attribute *to_bin_attr(struct dentry *dentry)
114: {
115: struct configfs_attribute *attr = to_attr(dentry);
116:
117: return container_of(attr, struct configfs_bin_attribute, cb_attr);
118: }
119:
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: }
133:
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: }
142:
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;
]
|
| 445/4 |
2026/09/02 22:00 |
llm |
patch-generator |
0m
Model:
Tokens:
input: 28035
output: 28
thoughts: 625
Thoughts: **Analyzing a ConfigFS Crash**
Okay, let's break this down. I'm looking at how `configfs_get_config_item(dentry)` interacts with parent and root items. It seems we're primarily interested in understanding the lifecycle of these items, particularly in relation to default groups. The core logic involves getting references to `parent_item` and `root_item` via `config_item_get()` based on the `dentry`'s parent. If the parent isn't a default group, then `parent_item` and `root_item` end up being the same item, each getting a reference count bump. The final `config_item_put()` calls then decrement those counts. Nothing inherently problematic there.
Now, let's get to the crash. Task 5863 crashed, and the key seems to be around a default group (like `subsys_group`) within a regular group (e.g., `nvmet_port` structure). In the crash scenario, another task (5860) was running a `mkdir` operation (`ports/1`), while a symlink was concurrently being created in a subsystem directory under that, specifically, `ports/1/subsystems`. The crucial part is that during this, the default group (`parent_item`, say `&port->subsys_group.cg_item`) and the root group (`root_item`, say `&port->group.cg_item`) both have their reference counts incremented. Then, we get an `inode_unlock(dir)` call.
The real problem arises when another thread tries to remove the directory (`rmdir("ports/1")`). This leads to `unlink_group(port)`, which drops the subsystem's reference to `port->group.cg_item`. At the same time, `rmdir` also calls `detach_groups`, which detaches the `subsys_group`, I need to figure out how that is handled. It seems an initial reference on `subsys_group` was dropped, but I need to understand why! Is the refcounting done correctly for default groups? The key now is understanding how default groups are attached, detached, and refcounted in the context of `configfs`. It's time to dig into `fs/configfs/dir.c` to see how those lifecycle events are managed. I'll focus on how the reference counting plays out in the context of the default groups, and how they relate to normal groups.
|
| 446/4 |
2026/09/02 22:00 |
tool |
grepper |
0m
Args: map[Expression:default_groups PathPrefix:fs/configfs/]
Results: map[Output:fs/configfs/dir.c=502=static int configfs_detach_prep(struct configfs_dirent *parent_sd, struct dentry **wait)
--
fs/configfs/dir.c-528- * Yup, recursive. If there's a problem, blame
fs/configfs/dir.c:529: * deep nesting of default_groups
fs/configfs/dir.c-530- */
--
fs/configfs/dir.c=664=static void detach_groups(struct dentry *dentry)
--
fs/configfs/dir.c-695-/*
fs/configfs/dir.c:696: * This fakes mkdir(2) on a default_groups[] entry. It
fs/configfs/dir.c-697- * creates a dentry, attachs it, and then does fixup
--
fs/configfs/dir.c=734=static int populate_groups(struct config_group *group,
--
fs/configfs/dir.c-739-
fs/configfs/dir.c:740: list_for_each_entry(new_group, &group->default_groups, group_entry) {
fs/configfs/dir.c-741- int ret = create_default_group(parent, new_group, frag);
--
fs/configfs/dir.c-747-
fs/configfs/dir.c:748:void configfs_remove_default_groups(struct config_group *group)
fs/configfs/dir.c-749-{
--
fs/configfs/dir.c-751-
fs/configfs/dir.c:752: list_for_each_entry_safe(g, n, &group->default_groups, group_entry) {
fs/configfs/dir.c-753- list_del(&g->group_entry);
--
fs/configfs/dir.c-756-}
fs/configfs/dir.c:757:EXPORT_SYMBOL(configfs_remove_default_groups);
fs/configfs/dir.c-758-
--
fs/configfs/dir.c=805=static void unlink_group(struct config_group *group)
--
fs/configfs/dir.c-808-
fs/configfs/dir.c:809: list_for_each_entry(new_group, &group->default_groups, group_entry)
fs/configfs/dir.c-810- unlink_group(new_group);
--
fs/configfs/dir.c=816=static void link_group(struct config_group *parent_group, struct config_group *group)
--
fs/configfs/dir.c-830-
fs/configfs/dir.c:831: list_for_each_entry(new_group, &group->default_groups, group_entry)
fs/configfs/dir.c-832- link_group(group, new_group);
--
fs/configfs/item.c=157=void config_group_init(struct config_group *group)
--
fs/configfs/item.c-160- INIT_LIST_HEAD(&group->cg_children);
fs/configfs/item.c:161: INIT_LIST_HEAD(&group->default_groups);
fs/configfs/item.c-162-}
]
|
| 447/4 |
2026/09/02 22:00 |
llm |
patch-generator |
0m
Model:
Tokens:
input: 30084
output: 36
thoughts: 37
Thoughts: Okay, here's my interpretation of what I'm about to dive into, presented as a first-person expert analysis:
**Diving into the ConfigFS Group Management Code**
Alright, I'm focusing in on `fs/configfs/dir.c`. Specifically, I want to get a solid grip on the group management operations within ConfigFS. That means I'm concentrating on the implementation of `link_group`, `unlink_group`, `detach_groups`, and `configfs_rmdir`. I already have a mental model of how ConfigFS works, but I want to scrutinize how these core functions interact to ensure groups are linked, unlinked, and detached cleanly, especially during directory removal. I'll be looking for potential race conditions, resource leaks, and any unusual behavior around reference counting. I expect to be paying close attention to how the data structures (likely `config_item` and `config_group` related) are manipulated during these operations. I need to understand how groups are linked to parent directories, and how those links are broken, both explicitly and implicitly. I am specifically interested in how `configfs_rmdir` behaves when removing a directory that has active groups. I want to see if it cleans up those groups before it deletes the directory entry, or if the cleanup is handled in a more lazy fashion. This is critical for preventing dangling references. I will also make sure to check whether locking is appropriately used.
|
| 448/4 |
2026/09/02 22:00 |
tool |
read-file |
0m
Args: map[File:fs/configfs/dir.c FirstLine:660 LineCount:90]
Results: map[Contents: 660: struct dentry *dentry,
661: struct configfs_fragment *frag);
662: static void configfs_detach_group(struct dentry *dentry);
663:
664: static void detach_groups(struct dentry *dentry)
665: {
666: struct dentry *child;
667: struct configfs_dirent *parent_sd;
668: struct configfs_dirent *sd, *next;
669:
670: parent_sd = dentry->d_fsdata;
671: spin_lock(&configfs_dirent_lock);
672: for (sd = next_dirent(parent_sd, NULL); sd; sd = next) {
673: next = next_dirent(parent_sd, sd);
674: if (!(sd->s_type & CONFIGFS_USET_DEFAULT))
675: continue;
676:
677: child = dget(sd->s_dentry);
678: spin_unlock(&configfs_dirent_lock);
679:
680: inode_lock(d_inode(child));
681:
682: configfs_detach_group(child);
683: d_inode(child)->i_flags |= S_DEAD;
684: dont_mount(child);
685:
686: inode_unlock(d_inode(child));
687:
688: d_delete(child);
689: dput(child);
690: spin_lock(&configfs_dirent_lock);
691: }
692: spin_unlock(&configfs_dirent_lock);
693: }
694:
695: /*
696: * This fakes mkdir(2) on a default_groups[] entry. It
697: * creates a dentry, attachs it, and then does fixup
698: * on the sd->s_type.
699: *
700: * We could, perhaps, tweak our parent's ->mkdir for a minute and
701: * try using vfs_mkdir. Just a thought.
702: */
703: static int create_default_group(struct dentry *parent,
704: struct config_group *group,
705: struct configfs_fragment *frag)
706: {
707: int ret;
708: struct configfs_dirent *sd;
709: /* We trust the caller holds a reference to parent */
710: struct dentry *child;
711:
712: if (!group->cg_item.ci_name)
713: group->cg_item.ci_name = group->cg_item.ci_namebuf;
714:
715: ret = -ENOMEM;
716: child = d_alloc_name(parent, group->cg_item.ci_name);
717: if (child) {
718: d_add(child, NULL);
719:
720: ret = configfs_attach_group(&group->cg_item, child, frag);
721: if (!ret) {
722: sd = child->d_fsdata;
723: sd->s_type |= CONFIGFS_USET_DEFAULT;
724: } else {
725: BUG_ON(d_inode(child));
726: d_drop(child);
727: }
728: dput(child);
729: }
730:
731: return ret;
732: }
733:
734: static int populate_groups(struct config_group *group,
735: struct configfs_fragment *frag)
736: {
737: struct dentry *parent = group->cg_item.ci_dentry;
738: struct config_group *new_group;
739:
740: list_for_each_entry(new_group, &group->default_groups, group_entry) {
741: int ret = create_default_group(parent, new_group, frag);
742: if (ret)
743: return ret;
744: }
745: return 0;
746: }
747:
748: void configfs_remove_default_groups(struct config_group *group)
749: {
]
|
| 449/4 |
2026/09/02 22:00 |
llm |
patch-generator |
0m
Model:
Tokens:
input: 31767
output: 36
thoughts: 24
Thoughts: Here's my summary, as if I were the one thinking it:
**Diving into `fs/configfs/dir.c`**
Alright, time to dig into this. I'm going to jump straight to lines 800-880 of `fs/configfs/dir.c`. This section should be interesting, likely dealing with directory-specific operations within the ConfigFS implementation. Given the line range, I'm expecting to find core directory management code β perhaps related to creating, deleting, or manipulating directories within the ConfigFS structure. I anticipate it will be low-level, probably handling interactions with the underlying VFS layer and dealing with directory entries. I'll be looking for any clues about how ConfigFS handles attribute namespaces within directories, and whether the directory structure handles the security or namespace models. I'll need to keep an eye out for potential locking issues or race conditions related to directory creation and deletion since those are notoriously tricky. Let's get to it and see what we find.
|
| 450/4 |
2026/09/02 22:00 |
tool |
read-file |
0m
Args: map[File:fs/configfs/dir.c FirstLine:800 LineCount:80]
Results: map[Contents: 800: * cg_children
801: */
802: config_item_get(item);
803: }
804:
805: static void unlink_group(struct config_group *group)
806: {
807: struct config_group *new_group;
808:
809: list_for_each_entry(new_group, &group->default_groups, group_entry)
810: unlink_group(new_group);
811:
812: group->cg_subsys = NULL;
813: unlink_obj(&group->cg_item);
814: }
815:
816: static void link_group(struct config_group *parent_group, struct config_group *group)
817: {
818: struct config_group *new_group;
819: struct configfs_subsystem *subsys = NULL; /* gcc is a turd */
820:
821: link_obj(&parent_group->cg_item, &group->cg_item);
822:
823: if (parent_group->cg_subsys)
824: subsys = parent_group->cg_subsys;
825: else if (configfs_is_root(&parent_group->cg_item))
826: subsys = to_configfs_subsystem(group);
827: else
828: BUG();
829: group->cg_subsys = subsys;
830:
831: list_for_each_entry(new_group, &group->default_groups, group_entry)
832: link_group(group, new_group);
833: }
834:
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: }
841:
842: /*
843: * The goal is that configfs_attach_item() (and
844: * configfs_attach_group()) can be called from either the VFS or this
845: * module. That is, they assume that the items have been created,
846: * the dentry allocated, and the dcache is all ready to go.
847: *
848: * If they fail, they must clean up after themselves as if they
849: * had never been called. The caller (VFS or local function) will
850: * handle cleaning up the dcache bits.
851: *
852: * configfs_detach_group() and configfs_detach_item() behave similarly on
853: * the way out. They assume that the proper semaphores are held, they
854: * clean up the configfs items, and they expect their callers will
855: * handle the dcache bits.
856: */
857: static int configfs_attach_item(struct config_item *item,
858: struct dentry *dentry,
859: struct configfs_fragment *frag)
860: {
861: int ret;
862:
863: ret = configfs_create_dir(item, dentry, frag);
864: if (!ret) {
865: ret = populate_attrs(item);
866: if (ret) {
867: /*
868: * We are going to remove an inode and its dentry but
869: * the VFS may already have hit and used them. Thus,
870: * we must lock them as rmdir() would.
871: */
872: inode_lock(d_inode(dentry));
873: configfs_detach_item(dentry);
874: d_inode(dentry)->i_flags |= S_DEAD;
875: dont_mount(dentry);
876: inode_unlock(d_inode(dentry));
877: d_delete(dentry);
878: }
879: }
]
|
| 451/4 |
2026/09/02 22:00 |
llm |
patch-generator |
0m
Model:
Tokens:
input: 33259
output: 36
thoughts: 16
Thoughts: Okay, here's my interpretation of those thoughts, crafted with the understanding that I'm speaking to a fellow expert:
**Reading and Unlinking β Further Exploration Needed**
Alright, I'm thinking we need to delve into the practical implementations of `link_obj` and `unlink_obj`. While conceptually, linking and unlinking objects seem straightforward, the devil is always in the details, especially in a complex system. I'm curious about the specific mechanisms at play β how exactly are these objects being linked? What metadata is being managed during the linking process? And what are the implications for data consistency, particularly during unlinking? Are there any potential race conditions I should be aware of? I'd like to understand what guarantees these operations provide. Are we talking about hard links, soft links, some custom internal linking system, or something else entirely? A thorough examination is in order before I can be truly confident in their behavior and impact. Let's see what the documentation and source code have to reveal.
|
| 452/4 |
2026/09/02 22:00 |
tool |
read-file |
0m
Args: map[File:fs/configfs/dir.c FirstLine:775 LineCount:35]
Results: map[Contents: 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: }
782:
783: static void link_obj(struct config_item *parent_item, struct config_item *item)
784: {
785: /*
786: * Parent seems redundant with group, but it makes certain
787: * traversals much nicer.
788: */
789: item->ci_parent = parent_item;
790:
791: /*
792: * We hold a reference on the parent for the child's ci_parent
793: * link.
794: */
795: item->ci_group = config_group_get(to_config_group(parent_item));
796: list_add_tail(&item->ci_entry, &item->ci_group->cg_children);
797:
798: /*
799: * We hold a reference on the child for ci_entry on the parent's
800: * cg_children
801: */
802: config_item_get(item);
803: }
804:
805: static void unlink_group(struct config_group *group)
806: {
807: struct config_group *new_group;
808:
809: list_for_each_entry(new_group, &group->default_groups, group_entry)
]
|
| 453/4 |
2026/09/02 22:00 |
llm |
patch-generator |
0m
Model:
Tokens:
input: 34010
output: 36
thoughts: 25
Thoughts: Okay, let's dive into this. I'm going to focus on lines 755-780 of `fs/configfs/dir.c`. I need to get a clear picture of what's happening within that specific segment of the code. I'm expecting this to be involved with directory operations in configfs, specifically the creation, deletion, or modification of directories. Given the file and the general structure of the configfs code, I'd anticipate this section deals with core directory handling. I'll be looking for calls to `kernfs_create_dir`, `kernfs_remove_dir`, or similar functions, and how they interact with the configfs-specific data structures. I'll pay close attention to any error handling within this block, as configfs is known for its intricate error management when handling user-provided configuration. I'll also be watching for locking and synchronization primitives, because concurrent access is crucial for a file system. Are there any mutexes protecting specific directory operations? Are there any use of RCU? The specific implementation of how this segment of code interacts with parent and child directory structures in the configfs namespace will be the core of my focus. I'm expecting this segment to be crucial for understanding how the filesystem organizes and manages configuration data.
|
| 454/4 |
2026/09/02 22:00 |
tool |
read-file |
0m
Args: map[File:fs/configfs/dir.c FirstLine:755 LineCount:35]
Results: map[Contents: 755: }
756: }
757: EXPORT_SYMBOL(configfs_remove_default_groups);
758:
759: /*
760: * All of link_obj/unlink_obj/link_group/unlink_group require that
761: * subsys->su_mutex is held.
762: */
763:
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: }
782:
783: static void link_obj(struct config_item *parent_item, struct config_item *item)
784: {
785: /*
786: * Parent seems redundant with group, but it makes certain
787: * traversals much nicer.
788: */
789: item->ci_parent = parent_item;
]
|
| 455/4 |
2026/09/02 22:00 |
llm |
patch-generator |
0m
Model:
Tokens:
input: 34795
output: 38
thoughts: 152
Thoughts: **Deconstructing the `unlink_obj` Function**
Okay, let's break this down. When `unlink_obj(item)` gets called, here's what's happening internally:
First, we grab `item->ci_group` and store it. Then, `item->ci_group` gets set to NULL, and `item->ci_parent` is also set to NULL. Essentially, we're disconnecting this item from its group and any parent association.
Next, `config_item_put(item)` gets called. I know this is crucial; this is where the reference count for `ci_entry` on the `item` is dropped. Itβs the essential step in releasing resources associated with the item.
Finally, `config_group_put(group)` happens. This drops the reference count for `ci_parent` on the `group` that we saved earlier. So, the original group is also seeing its reference count decremented. This whole sequence is all about cleaning up the linkages and releasing resources associated with a configfs item.
Now, I need to see how this plays out within the `nvmet` driver. I'm going to start looking in `drivers/nvme/target/configfs.c` and focus on keywords like `nvmet_port`, `allow_link`, and `subsys_group`. I'm expecting those will provide the context for the code. This is where the rubber meets the road.
|
| 456/4 |
2026/09/02 22:00 |
tool |
grepper |
0m
Args: map[Expression:nvmet_port_subsys_allow_link PathPrefix:drivers/nvme/target/]
Results: map[Output:drivers/nvme/target/configfs.c=1050=static void nvmet_add_passthru_group(struct nvmet_subsys *subsys)
--
drivers/nvme/target/configfs.c-1055-
drivers/nvme/target/configfs.c:1056:static int nvmet_port_subsys_allow_link(struct config_item *parent,
drivers/nvme/target/configfs.c-1057- struct config_item *target)
--
drivers/nvme/target/configfs.c=1125=static struct configfs_item_operations nvmet_port_subsys_item_ops = {
drivers/nvme/target/configfs.c:1126: .allow_link = nvmet_port_subsys_allow_link,
drivers/nvme/target/configfs.c-1127- .drop_link = nvmet_port_subsys_drop_link,
]
|
| 457/4 |
2026/09/02 22:00 |
llm |
patch-generator |
0m
Model:
Tokens:
input: 35549
output: 40
thoughts: 25
Thoughts: Here's my summary, as if I were thinking these thoughts:
**NVMe Target Configuration Deep Dive**
Okay, time to dig into the `configfs.c` file within the NVMe target driver. Specifically, I'm going to focus around line 1056. I need to refresh my memory on how this part of the configuration interacts with the overall system. I'm hoping to understand how something specific is handled - probably related to a recent bug report or optimization idea. My gut feeling is that this section handles the... (I'll have to see once I get there). Alright, let's see what's actually happening in that section of code. I'm expecting to see... (I'll formulate my hypothesis as I go!).
|
| 458/4 |
2026/09/02 22:00 |
tool |
read-file |
0m
Args: map[File:drivers/nvme/target/configfs.c FirstLine:1050 LineCount:80]
Results: map[Contents:1050: static void nvmet_add_passthru_group(struct nvmet_subsys *subsys)
1051: {
1052: }
1053:
1054: #endif /* CONFIG_NVME_TARGET_PASSTHRU */
1055:
1056: static int nvmet_port_subsys_allow_link(struct config_item *parent,
1057: struct config_item *target)
1058: {
1059: struct nvmet_port *port = to_nvmet_port(parent->ci_parent);
1060: struct nvmet_subsys *subsys;
1061: struct nvmet_subsys_link *link, *p;
1062: int ret;
1063:
1064: if (target->ci_type != &nvmet_subsys_type) {
1065: pr_err("can only link subsystems into the subsystems dir.!\n");
1066: return -EINVAL;
1067: }
1068: subsys = to_subsys(target);
1069: link = kmalloc_obj(*link);
1070: if (!link)
1071: return -ENOMEM;
1072: link->subsys = subsys;
1073:
1074: down_write(&nvmet_config_sem);
1075: ret = -EEXIST;
1076: list_for_each_entry(p, &port->subsystems, entry) {
1077: if (p->subsys == subsys)
1078: goto out_free_link;
1079: }
1080:
1081: if (list_empty(&port->subsystems)) {
1082: ret = nvmet_enable_port(port);
1083: if (ret)
1084: goto out_free_link;
1085: }
1086:
1087: list_add_tail(&link->entry, &port->subsystems);
1088: nvmet_port_disc_changed(port, subsys);
1089:
1090: up_write(&nvmet_config_sem);
1091: return 0;
1092:
1093: out_free_link:
1094: up_write(&nvmet_config_sem);
1095: kfree(link);
1096: return ret;
1097: }
1098:
1099: static void nvmet_port_subsys_drop_link(struct config_item *parent,
1100: struct config_item *target)
1101: {
1102: struct nvmet_port *port = to_nvmet_port(parent->ci_parent);
1103: struct nvmet_subsys *subsys = to_subsys(target);
1104: struct nvmet_subsys_link *p;
1105:
1106: down_write(&nvmet_config_sem);
1107: list_for_each_entry(p, &port->subsystems, entry) {
1108: if (p->subsys == subsys)
1109: goto found;
1110: }
1111: up_write(&nvmet_config_sem);
1112: return;
1113:
1114: found:
1115: list_del(&p->entry);
1116: nvmet_port_del_ctrls(port, subsys);
1117: nvmet_port_disc_changed(port, subsys);
1118:
1119: if (list_empty(&port->subsystems))
1120: nvmet_disable_port(port);
1121: up_write(&nvmet_config_sem);
1122: kfree(p);
1123: }
1124:
1125: static struct configfs_item_operations nvmet_port_subsys_item_ops = {
1126: .allow_link = nvmet_port_subsys_allow_link,
1127: .drop_link = nvmet_port_subsys_drop_link,
1128: };
1129:
]
|
| 459/4 |
2026/09/02 22:00 |
llm |
patch-generator |
0m
Model:
Tokens:
input: 36971
output: 33
thoughts: 101
Thoughts: **Deconstructing the nvmet_port_subsys_allow_link Function**
Okay, let's dive into this `nvmet_port_subsys_allow_link` function. The first thing that jumps out is this line: `struct nvmet_port *port = to_nvmet_port(parent->ci_parent);`. Right, so we're trying to extract an `nvmet_port` structure from something, presumably related to a port. The key here is that `parent` is actually `&port->subsys_group.cg_item`. So, the `parent` variable is a pointer to the `cg_item` member *within* `subsys_group` *within* the `nvmet_port` struct.
And then, that `to_nvmet_port` macro... Ah, yes, it's a `container_of` call. We're using that classic Linux kernel trick to get a pointer to the containing structure (`nvmet_port`) given a pointer to a member within it (`group.cg_item`). It's all about traversing from the contained member back to the enclosing struct. Just to be completely sure, I should double check that `to_nvmet_port` implementation again, just to refresh my memory, since it's such a common pattern. I'll take a quick glance.
|
| 460/4 |
2026/09/02 22:00 |
tool |
grepper |
0m
Args: map[Expression:to_nvmet_port PathPrefix:drivers/nvme/target/]
Results: map[Output:drivers/nvme/target/configfs.c=66=static ssize_t nvmet_addr_adrfam_show(struct config_item *item, char *page)
drivers/nvme/target/configfs.c-67-{
drivers/nvme/target/configfs.c:68: u8 adrfam = to_nvmet_port(item)->disc_addr.adrfam;
drivers/nvme/target/configfs.c-69- int i;
--
drivers/nvme/target/configfs.c=80=static ssize_t nvmet_addr_adrfam_store(struct config_item *item,
--
drivers/nvme/target/configfs.c-82-{
drivers/nvme/target/configfs.c:83: struct nvmet_port *port = to_nvmet_port(item);
drivers/nvme/target/configfs.c-84- int i;
--
drivers/nvme/target/configfs.c=104=static ssize_t nvmet_addr_portid_show(struct config_item *item,
--
drivers/nvme/target/configfs.c-106-{
drivers/nvme/target/configfs.c:107: __le16 portid = to_nvmet_port(item)->disc_addr.portid;
drivers/nvme/target/configfs.c-108-
--
drivers/nvme/target/configfs.c=112=static ssize_t nvmet_addr_portid_store(struct config_item *item,
--
drivers/nvme/target/configfs.c-114-{
drivers/nvme/target/configfs.c:115: struct nvmet_port *port = to_nvmet_port(item);
drivers/nvme/target/configfs.c-116- u16 portid = 0;
--
drivers/nvme/target/configfs.c=132=static ssize_t nvmet_addr_traddr_show(struct config_item *item,
--
drivers/nvme/target/configfs.c-134-{
drivers/nvme/target/configfs.c:135: struct nvmet_port *port = to_nvmet_port(item);
drivers/nvme/target/configfs.c-136-
--
drivers/nvme/target/configfs.c=140=static ssize_t nvmet_addr_traddr_store(struct config_item *item,
--
drivers/nvme/target/configfs.c-142-{
drivers/nvme/target/configfs.c:143: struct nvmet_port *port = to_nvmet_port(item);
drivers/nvme/target/configfs.c-144-
--
drivers/nvme/target/configfs.c=171=static ssize_t nvmet_addr_treq_show(struct config_item *item, char *page)
drivers/nvme/target/configfs.c-172-{
drivers/nvme/target/configfs.c:173: u8 treq = nvmet_port_disc_addr_treq_secure_channel(to_nvmet_port(item));
drivers/nvme/target/configfs.c-174- int i;
--
drivers/nvme/target/configfs.c=185=static ssize_t nvmet_addr_treq_store(struct config_item *item,
--
drivers/nvme/target/configfs.c-187-{
drivers/nvme/target/configfs.c:188: struct nvmet_port *port = to_nvmet_port(item);
drivers/nvme/target/configfs.c-189- u8 treq = nvmet_port_disc_addr_treq_mask(port);
--
drivers/nvme/target/configfs.c=225=static ssize_t nvmet_addr_trsvcid_show(struct config_item *item,
--
drivers/nvme/target/configfs.c-227-{
drivers/nvme/target/configfs.c:228: struct nvmet_port *port = to_nvmet_port(item);
drivers/nvme/target/configfs.c-229-
--
drivers/nvme/target/configfs.c=233=static ssize_t nvmet_addr_trsvcid_store(struct config_item *item,
--
drivers/nvme/target/configfs.c-235-{
drivers/nvme/target/configfs.c:236: struct nvmet_port *port = to_nvmet_port(item);
drivers/nvme/target/configfs.c-237-
--
drivers/nvme/target/configfs.c=252=static ssize_t nvmet_param_inline_data_size_show(struct config_item *item,
--
drivers/nvme/target/configfs.c-254-{
drivers/nvme/target/configfs.c:255: struct nvmet_port *port = to_nvmet_port(item);
drivers/nvme/target/configfs.c-256-
--
drivers/nvme/target/configfs.c=260=static ssize_t nvmet_param_inline_data_size_store(struct config_item *item,
--
drivers/nvme/target/configfs.c-262-{
drivers/nvme/target/configfs.c:263: struct nvmet_port *port = to_nvmet_port(item);
drivers/nvme/target/configfs.c-264- int ret;
--
drivers/nvme/target/configfs.c=278=static ssize_t nvmet_param_max_queue_size_show(struct config_item *item,
--
drivers/nvme/target/configfs.c-280-{
drivers/nvme/target/configfs.c:281: struct nvmet_port *port = to_nvmet_port(item);
drivers/nvme/target/configfs.c-282-
--
drivers/nvme/target/configfs.c=286=static ssize_t nvmet_param_max_queue_size_store(struct config_item *item,
--
drivers/nvme/target/configfs.c-288-{
drivers/nvme/target/configfs.c:289: struct nvmet_port *port = to_nvmet_port(item);
drivers/nvme/target/configfs.c-290- int ret;
--
drivers/nvme/target/configfs.c=304=static ssize_t nvmet_param_mdts_show(struct config_item *item, char *page)
drivers/nvme/target/configfs.c-305-{
drivers/nvme/target/configfs.c:306: struct nvmet_port *port = to_nvmet_port(item);
drivers/nvme/target/configfs.c-307-
--
drivers/nvme/target/configfs.c=311=static ssize_t nvmet_param_mdts_store(struct config_item *item,
--
drivers/nvme/target/configfs.c-313-{
drivers/nvme/target/configfs.c:314: struct nvmet_port *port = to_nvmet_port(item);
drivers/nvme/target/configfs.c-315- int ret, mdts;
--
drivers/nvme/target/configfs.c=332=static ssize_t nvmet_param_pi_enable_show(struct config_item *item,
--
drivers/nvme/target/configfs.c-334-{
drivers/nvme/target/configfs.c:335: struct nvmet_port *port = to_nvmet_port(item);
drivers/nvme/target/configfs.c-336-
--
drivers/nvme/target/configfs.c=340=static ssize_t nvmet_param_pi_enable_store(struct config_item *item,
--
drivers/nvme/target/configfs.c-342-{
drivers/nvme/target/configfs.c:343: struct nvmet_port *port = to_nvmet_port(item);
drivers/nvme/target/configfs.c-344- bool val;
--
drivers/nvme/target/configfs.c=359=static ssize_t nvmet_addr_trtype_show(struct config_item *item,
--
drivers/nvme/target/configfs.c-361-{
drivers/nvme/target/configfs.c:362: struct nvmet_port *port = to_nvmet_port(item);
drivers/nvme/target/configfs.c-363- int i;
--
drivers/nvme/target/configfs.c=386=static ssize_t nvmet_addr_trtype_store(struct config_item *item,
--
drivers/nvme/target/configfs.c-388-{
drivers/nvme/target/configfs.c:389: struct nvmet_port *port = to_nvmet_port(item);
drivers/nvme/target/configfs.c-390- int i;
--
drivers/nvme/target/configfs.c=425=static ssize_t nvmet_addr_tsas_show(struct config_item *item,
--
drivers/nvme/target/configfs.c-427-{
drivers/nvme/target/configfs.c:428: struct nvmet_port *port = to_nvmet_port(item);
drivers/nvme/target/configfs.c-429- int i;
--
drivers/nvme/target/configfs.c=467=static ssize_t nvmet_addr_tsas_store(struct config_item *item,
--
drivers/nvme/target/configfs.c-469-{
drivers/nvme/target/configfs.c:470: struct nvmet_port *port = to_nvmet_port(item);
drivers/nvme/target/configfs.c-471- u8 treq = nvmet_port_disc_addr_treq_mask(port);
--
drivers/nvme/target/configfs.c=1056=static int nvmet_port_subsys_allow_link(struct config_item *parent,
--
drivers/nvme/target/configfs.c-1058-{
drivers/nvme/target/configfs.c:1059: struct nvmet_port *port = to_nvmet_port(parent->ci_parent);
drivers/nvme/target/configfs.c-1060- struct nvmet_subsys *subsys;
--
drivers/nvme/target/configfs.c=1099=static void nvmet_port_subsys_drop_link(struct config_item *parent,
--
drivers/nvme/target/configfs.c-1101-{
drivers/nvme/target/configfs.c:1102: struct nvmet_port *port = to_nvmet_port(parent->ci_parent);
drivers/nvme/target/configfs.c-1103- struct nvmet_subsys *subsys = to_subsys(target);
--
drivers/nvme/target/configfs.c=1780=static ssize_t nvmet_referral_enable_show(struct config_item *item,
--
drivers/nvme/target/configfs.c-1782-{
drivers/nvme/target/configfs.c:1783: return snprintf(page, PAGE_SIZE, "%d\n", to_nvmet_port(item)->enabled);
drivers/nvme/target/configfs.c-1784-}
--
drivers/nvme/target/configfs.c=1786=static ssize_t nvmet_referral_enable_store(struct config_item *item,
--
drivers/nvme/target/configfs.c-1788-{
drivers/nvme/target/configfs.c:1789: struct nvmet_port *parent = to_nvmet_port(item->ci_parent->ci_parent);
drivers/nvme/target/configfs.c:1790: struct nvmet_port *port = to_nvmet_port(item);
drivers/nvme/target/configfs.c-1791- bool enable;
--
drivers/nvme/target/configfs.c=1823=static void nvmet_referral_notify(struct config_group *group,
--
drivers/nvme/target/configfs.c-1825-{
drivers/nvme/target/configfs.c:1826: struct nvmet_port *parent = to_nvmet_port(item->ci_parent->ci_parent);
drivers/nvme/target/configfs.c:1827: struct nvmet_port *port = to_nvmet_port(item);
drivers/nvme/target/configfs.c-1828-
--
drivers/nvme/target/configfs.c=1832=static void nvmet_referral_release(struct config_item *item)
drivers/nvme/target/configfs.c-1833-{
drivers/nvme/target/configfs.c:1834: struct nvmet_port *port = to_nvmet_port(item);
drivers/nvme/target/configfs.c-1835-
--
drivers/nvme/target/configfs.c=2003=static void nvmet_port_release(struct config_item *item)
drivers/nvme/target/configfs.c-2004-{
drivers/nvme/target/configfs.c:2005: struct nvmet_port *port = to_nvmet_port(item);
drivers/nvme/target/configfs.c-2006-
--
drivers/nvme/target/nvmet.h=203=struct nvmet_port {
--
drivers/nvme/target/nvmet.h-224-
drivers/nvme/target/nvmet.h:225:static inline struct nvmet_port *to_nvmet_port(struct config_item *item)
drivers/nvme/target/nvmet.h-226-{
]
|
| 461/4 |
2026/09/02 22:00 |
llm |
patch-generator |
0m
Model:
Tokens:
input: 41010
output: 40
thoughts: 80
Thoughts: Here's my analysis of what I'm looking at, from my expert perspective:
**Deconstructing the NVMe-oF Port Handling**
Okay, so I'm diving into this NVMe-oF code. Let's break it down. It looks like the `to_nvmet_port(item)` function is the key here. From what I can tell, it's taking a generic `item` β specifically, a `group.cg_item` β and transforming it into a `struct nvmet_port` structure. Right, that's the expected translation we'd need to perform to start interacting with the port configuration. The goal is to get this into a format we can work with within the NVMe target framework.
And then, we have `nvmet_port_release(item)`. Given the naming convention and the fact that we're dealing with `nvmet_port`, my guess is that this function likely handles the de-allocation or cleanup of the port structure when it's no longer needed. Perhaps releasing resources, unregistering the port, or something similar.
To be absolutely certain, I'm going to jump into the source code itself to verify. I'm going to investigate `drivers/nvme/target/configfs.c` around lines 2000 to 2060. That section should give me the definitive answer on what exactly these functions are doing in the context of the larger ConfigFS integration and NVMe-oF target management. I need to understand the memory model and the release function's relation to that model for port creation and deallocation.
|
| 462/4 |
2026/09/02 22:00 |
tool |
read-file |
0m
Args: map[File:drivers/nvme/target/configfs.c FirstLine:2000 LineCount:60]
Results: map[Contents:2000: /*
2001: * Ports definitions.
2002: */
2003: static void nvmet_port_release(struct config_item *item)
2004: {
2005: struct nvmet_port *port = to_nvmet_port(item);
2006:
2007: /* Let inflight controllers teardown complete */
2008: flush_workqueue(nvmet_wq);
2009: list_del(&port->global_entry);
2010:
2011: key_put(port->keyring);
2012: kfree(port);
2013: }
2014:
2015: static struct configfs_attribute *nvmet_port_attrs[] = {
2016: &nvmet_attr_addr_adrfam,
2017: &nvmet_attr_addr_treq,
2018: &nvmet_attr_addr_traddr,
2019: &nvmet_attr_addr_trsvcid,
2020: &nvmet_attr_addr_trtype,
2021: &nvmet_attr_addr_tsas,
2022: &nvmet_attr_param_inline_data_size,
2023: &nvmet_attr_param_max_queue_size,
2024: &nvmet_attr_param_mdts,
2025: #ifdef CONFIG_BLK_DEV_INTEGRITY
2026: &nvmet_attr_param_pi_enable,
2027: #endif
2028: NULL,
2029: };
2030:
2031: static struct configfs_item_operations nvmet_port_item_ops = {
2032: .release = nvmet_port_release,
2033: };
2034:
2035: static const struct config_item_type nvmet_port_type = {
2036: .ct_attrs = nvmet_port_attrs,
2037: .ct_item_ops = &nvmet_port_item_ops,
2038: .ct_owner = THIS_MODULE,
2039: };
2040:
2041: static struct config_group *nvmet_ports_make(struct config_group *group,
2042: const char *name)
2043: {
2044: struct nvmet_port *port;
2045: u16 portid;
2046: u32 i;
2047:
2048: if (kstrtou16(name, 0, &portid))
2049: return ERR_PTR(-EINVAL);
2050:
2051: port = kzalloc_flex(*port, ana_state, NVMET_MAX_ANAGRPS + 1);
2052: if (!port)
2053: return ERR_PTR(-ENOMEM);
2054:
2055: if (IS_ENABLED(CONFIG_NVME_TARGET_TCP_TLS) && nvme_keyring_id()) {
2056: port->keyring = key_lookup(nvme_keyring_id());
2057: if (IS_ERR(port->keyring)) {
2058: pr_warn("NVMe keyring not available, disabling TLS\n");
2059: port->keyring = NULL;
]
|
| 463/4 |
2026/09/02 22:00 |
llm |
patch-generator |
0m
Model:
Tokens:
input: 42418
output: 38
thoughts: 185
Thoughts: **My Analysis of `nvmet_port` Removal**
Okay, let's break this down. My understanding is that the `nvmet_port` structure has several embedded `config_group` objects: `group` itself (which represents the port's primary configuration like `/config/nvmet/ports/1`), `subsys_group` which probably houses the subsystems beneath the port (e.g., `/config/nvmet/ports/1/subsystems`), and then `referrals_group`, `ana_groups_group`, and `param_group`. Essentially, this structure encapsulates the entire configuration space for a specific NVMe-oF port instance.
Now, the interesting part is what happens when that port, say `/config/nvmet/ports/1`, is removed. The kernel code uses `configfs_rmdir()` which then calls `configfs_detach_group()` on the `port->group`. That makes sense: you detach the top-level configuration group to remove the port.
However, I'm going to pause there because it's always good to refresh my memory and be absolutely sure about `configfs_rmdir()`. Time to go read the source code: `fs/configfs/dir.c`. I need to understand the details of how `configfs_rmdir()` works, specifically how it handles detaching that `config_group` before I continue on with this. I'll make sure it's the `configfs_detach_group(&port->group.cg_item.ci_dentry)` call that I'm focused on when analyzing.
|
| 464/4 |
2026/09/02 22:00 |
tool |
read-file |
0m
Args: map[File:fs/configfs/dir.c FirstLine:1465 LineCount:110]
Results: map[Contents:1465: release_dentry_name_snapshot(&n);
1466: return ERR_PTR(ret);
1467: }
1468:
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);
]
|
| 465/4 |
2026/09/02 22:00 |
llm |
patch-generator |
0m
Model:
Tokens:
input: 44483
output: 37
thoughts: 25
Thoughts: Here's a first-person summary, tailored for an expert audience, based on the prompt:
**Diving into `configfs_rmdir()`**
Okay, let's get into the nitty-gritty of the `configfs_rmdir()` function. I need to understand how configfs handles directory removal β specifically, what's going on behind the scenes in `fs/configfs/dir.c`. This is where the magic happens, or hopefully, where the clean-up does. I'm keen to see the synchronization primitives used, the resource management, and how it handles potential errors. I'm expecting to see calls to common VFS functions, but I'm also interested in the configfs-specific logic for managing attributes, children, and potentially any reference counting specific to its use case. I'm hoping it's well-structured.
|
| 466/4 |
2026/09/02 22:01 |
tool |
read-file |
0m
Args: map[File:fs/configfs/dir.c FirstLine:1565 LineCount:40]
Results: map[Contents: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: }
1578:
1579: const struct inode_operations configfs_dir_inode_operations = {
1580: .mkdir = configfs_mkdir,
1581: .rmdir = configfs_rmdir,
1582: .symlink = configfs_symlink,
1583: .unlink = configfs_unlink,
1584: .lookup = configfs_lookup,
1585: .setattr = configfs_setattr,
1586: };
1587:
1588: const struct inode_operations configfs_root_inode_operations = {
1589: .lookup = configfs_lookup,
1590: .setattr = configfs_setattr,
1591: };
1592:
1593: static int configfs_dir_open(struct inode *inode, struct file *file)
1594: {
1595: struct dentry * dentry = file->f_path.dentry;
1596: struct configfs_dirent * parent_sd = dentry->d_fsdata;
1597: int err;
1598:
1599: inode_lock(d_inode(dentry));
1600: /*
1601: * Fake invisibility if dir belongs to a group/default groups hierarchy
1602: * being attached
1603: */
1604: err = -ENOENT;
]
|
| 467/4 |
2026/09/02 22:01 |
llm |
patch-generator |
0m
Model:
Tokens:
input: 45290
output: 29
thoughts: 3904
Thoughts: **Analysis of a ConfigFS Use-After-Free Vulnerability**
Okay, so I've been digging into this ConfigFS issue, and it looks like we have a pretty nasty use-after-free situation on our hands. The core of the problem lies in how we're handling references to config items during directory unlinking and symlink creation, specifically when default groups are involved.
The basic scenario is this: we're creating a symlink using `configfs_symlink()` to a target that is inside a directory. Simultaneously, a different thread can be rmdir'ing the source directory. This means `config_item_put` is called *before* the reference count is decremented!
Here's how I see it: `configfs_rmdir` detaches an item and then calls `config_item_put` on a `port` object. This calls `nvmet_port_release`, which calls `kfree(port)`. So, `port` is freed. However, during the `configfs_symlink` operation, `config_item_put` gets a reference to both the parent (`parent_item`) and the root directory item (`root_item`) by incrementing their reference counts. The vulnerability emerges because the previous patch put `root_item` *first* and then `parent_item`, meaning the code could attempt to access freed memory.
The call trace reveals the critical sequence. The code calls `config_item_put(root_item)`, which frees `port`. Then, it calls `config_item_put(parent_item)`, which points to the now-freed `port` object, resulting in a use-after-free error.
Now, let's play with the order: If we reverse the `config_item_put` calls, putting `parent_item` *first*, and then `root_item`, things actually work. `config_item_cleanup` will execute since no other reference counts the `port` object, so we don't end up with a memory access error!
We can determine that this is only an issue with default groups because default groups are allocated inside a port, and unlinking a default group (which calls `unlink_obj`), does *not* unlink the port object! In a normal group, the memory is released immediately on rmdir by `configfs_rmdir`, since the port object holds the reference and is then freed. This means if you have a default group as a parent, when rmdir executes, it drops the port's ref count, allowing `nvmet_port_release` to run, which frees the object. Then, in the main thread (symlink's thread), the `config_item_put` attempts to use the freed `port` object.
But, if the `dentry->d_parent` is *not* a default group, then the memory for `port` isn't freed at all. The memory isn't freed by `rmdir` and the `port` object still holds the reference count for `parent_item` and can then be released by the call to `config_item_put` on the main thread. This means there's no memory access error!
It turns out that default groups are structured such that `parent_item` references a part of the port object, and `rmdir` drops the *other* reference (to the full object), so the entire object is freed before `parent_item`'s refcount drops to 0, causing the UAF. Default groups are linked to a port object and have a different release method, which is why a separate `config_item_put` is needed to handle default groups.
So, in a nutshell, the bug is a classic race condition where the simultaneous deletion of a default group can free the container object (`port`), leading to a use-after-free if the `configfs_symlink` routine attempts to access that freed memory. Reversing the order of `config_item_put` operations resolves the bug.
|
| 468/4 |
2026/09/02 22:01 |
tool |
grepper |
0m
Args: map[Expression:CONFIGFS_ROOT PathPrefix:fs/configfs/]
Results: map[Output:fs/configfs/configfs_internal.h=30=struct configfs_dirent {
--
fs/configfs/configfs_internal.h-46-
fs/configfs/configfs_internal.h:47:#define CONFIGFS_ROOT 0x0001
fs/configfs/configfs_internal.h-48-#define CONFIGFS_DIR 0x0002
--
fs/configfs/configfs_internal.h-58-#define CONFIGFS_PINNED \
fs/configfs/configfs_internal.h:59: (CONFIGFS_ROOT | CONFIGFS_DIR | CONFIGFS_ITEM_LINK)
fs/configfs/configfs_internal.h-60-
--
fs/configfs/configfs_internal.h=134=static inline void release_configfs_dirent(struct configfs_dirent * sd)
fs/configfs/configfs_internal.h-135-{
fs/configfs/configfs_internal.h:136: if (!(sd->s_type & CONFIGFS_ROOT)) {
fs/configfs/configfs_internal.h-137- kfree(sd->s_iattr);
--
fs/configfs/dir.c=977=static void configfs_dump_one(struct configfs_dirent *sd, int level)
--
fs/configfs/dir.c-981-#define type_print(_type) if (sd->s_type & _type) pr_info("%*s %s\n", level, " ", #_type)
fs/configfs/dir.c:982: type_print(CONFIGFS_ROOT);
fs/configfs/dir.c-983- type_print(CONFIGFS_DIR);
--
fs/configfs/dir.c=992=static int configfs_dump(struct configfs_dirent *sd, int level)
--
fs/configfs/dir.c-998-
fs/configfs/dir.c:999: if (!(sd->s_type & (CONFIGFS_DIR|CONFIGFS_ROOT)))
fs/configfs/dir.c-1000- return 0;
--
fs/configfs/mount.c=55=static struct configfs_dirent configfs_root = {
--
fs/configfs/mount.c-58- .s_element = &configfs_root_group.cg_item,
fs/configfs/mount.c:59: .s_type = CONFIGFS_ROOT,
fs/configfs/mount.c-60- .s_iattr = NULL,
]
|
| 469/4 |
2026/09/02 22:01 |
llm |
patch-generator |
0m
Model:
Tokens:
input: 50736
output: 36
thoughts: 122
Thoughts: **Analyzing the ConfigFS Root and Symlinks**
Okay, let's break this down. First, I see that `configfs_root`'s `s_type` is `CONFIGFS_ROOT`. That means `CONFIGFS_USET_DEFAULT` isn't set on it. Makes sense. And that also means any default group's ancestors will, by definition, always encounter a non-default element before or at the `configfs_root`. Therefore, `root_dentry` should correctly identify the root item. Seems logical.
Hold on a second... just to be completely sure, let's revisit `configfs_symlink`. Better to be thorough, especially with symlinks. Time to dive deep and refresh my memory. Right, let's meticulously re-read `fs/configfs/symlink.c` again. Gotta make sure I haven't missed anything subtle. It's the details that often trip you up.
|
| 470/4 |
2026/09/02 22:01 |
tool |
read-file |
0m
Args: map[File:fs/configfs/symlink.c FirstLine:1 LineCount:120]
Results: map[Contents: 1: // SPDX-License-Identifier: GPL-2.0-or-later
2: /*
3: * symlink.c - operations for configfs symlinks.
4: *
5: * Based on sysfs:
6: * sysfs is Copyright (C) 2001, 2002, 2003 Patrick Mochel
7: *
8: * configfs Copyright (C) 2005 Oracle. All rights reserved.
9: */
10:
11: #include <linux/fs.h>
12: #include <linux/module.h>
13: #include <linux/namei.h>
14: #include <linux/slab.h>
15:
16: #include <linux/configfs.h>
17: #include "configfs_internal.h"
18:
19: /* Protects attachments of new symlinks */
20: DEFINE_MUTEX(configfs_symlink_mutex);
21:
22: static int item_depth(struct config_item * item)
23: {
24: struct config_item * p = item;
25: int depth = 0;
26: do { depth++; } while ((p = p->ci_parent) && !configfs_is_root(p));
27: return depth;
28: }
29:
30: static int item_path_length(struct config_item * item)
31: {
32: struct config_item * p = item;
33: int length = 1;
34: do {
35: length += strlen(config_item_name(p)) + 1;
36: p = p->ci_parent;
37: } while (p && !configfs_is_root(p));
38: return length;
39: }
40:
41: static void fill_item_path(struct config_item * item, char * buffer, int length)
42: {
43: struct config_item * p;
44:
45: --length;
46: for (p = item; p && !configfs_is_root(p); p = p->ci_parent) {
47: int cur = strlen(config_item_name(p));
48:
49: /* back up enough to print this bus id with '/' */
50: length -= cur;
51: memcpy(buffer + length, config_item_name(p), cur);
52: *(buffer + --length) = '/';
53: }
54: }
55:
56: static int configfs_get_target_path(struct config_item *item,
57: struct config_item *target, char *path)
58: {
59: int depth, size;
60: char *s;
61:
62: depth = item_depth(item);
63: size = item_path_length(target) + depth * 3 - 1;
64: if (size > PATH_MAX)
65: return -ENAMETOOLONG;
66:
67: pr_debug("%s: depth = %d, size = %d\n", __func__, depth, size);
68:
69: for (s = path; depth--; s += 3)
70: strcpy(s,"../");
71:
72: fill_item_path(target, path, size);
73: pr_debug("%s: path = '%s'\n", __func__, path);
74: return 0;
75: }
76:
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++;
]
|
| 471/4 |
2026/09/02 22:01 |
llm |
patch-generator |
0m
Model:
Tokens:
input: 52469
output: 37
thoughts: 50
Thoughts: Here's my analysis of the provided information:
**Investigating `configfs_unlink` in `fs/configfs/symlink.c`**
Alright, given the context, my first action is to quickly scan the `configfs_unlink` function within `fs/configfs/symlink.c`. I need to understand its behavior, specifically focusing on how it handles symbolic links. I'll need to confirm if it correctly handles unlink requests, particularly in relation to the ConfigFS implementation. I'll want to review the section of the code from lines 210 through 270. This range typically holds critical logic, and within that area, I'll be looking for potential race conditions, incorrect error handling, or vulnerabilities associated with unlinking symbolic links. I expect to be looking for confirmation of how the unlink process handles releasing the link's resources.
|
| 472/4 |
2026/09/02 22:01 |
tool |
read-file |
0m
Args: map[File:fs/configfs/symlink.c FirstLine:210 LineCount:60]
Results: map[Contents: 210: return ret;
211: }
212:
213: int configfs_unlink(struct inode *dir, struct dentry *dentry)
214: {
215: struct configfs_dirent *sd = dentry->d_fsdata, *target_sd;
216: struct config_item *parent_item;
217: const struct config_item_type *type;
218: int ret;
219:
220: ret = -EPERM; /* What lack-of-symlink returns */
221: if (!(sd->s_type & CONFIGFS_ITEM_LINK))
222: goto out;
223:
224: target_sd = sd->s_element;
225:
226: parent_item = configfs_get_config_item(dentry->d_parent);
227: type = parent_item->ci_type;
228:
229: spin_lock(&configfs_dirent_lock);
230: list_del_init(&sd->s_sibling);
231: spin_unlock(&configfs_dirent_lock);
232: configfs_put(sd);
233: simple_unlink(dir, dentry);
234:
235: /*
236: * drop_link() must be called before
237: * decrementing target's ->s_links, so that the order of
238: * drop_link(this, target) and drop_item(target) is preserved.
239: */
240: if (type && type->ct_item_ops &&
241: type->ct_item_ops->drop_link)
242: type->ct_item_ops->drop_link(parent_item,
243: target_sd->s_element);
244:
245: spin_lock(&configfs_dirent_lock);
246: target_sd->s_links--;
247: spin_unlock(&configfs_dirent_lock);
248: configfs_put(target_sd);
249:
250: config_item_put(parent_item);
251:
252: ret = 0;
253:
254: out:
255: return ret;
256: }
257:
258: const struct inode_operations configfs_symlink_inode_operations = {
259: .get_link = simple_get_link,
260: .setattr = configfs_setattr,
261: };
262:
]
|
| 473/4 |
2026/09/02 22:01 |
llm |
patch-generator |
0m
Model:
Tokens:
input: 53515
output: 28
thoughts: 3667
Thoughts: **Analysis of `configfs_symlink` and Potential Race Conditions**
Okay, let's break down this `configfs_symlink` function and ensure we haven't introduced a race condition. The primary concern is protecting against a situation where an rmdir operation on an ancestor directory could interfere with the symlink creation process. We're also diving deep into refcounts and object lifetimes, making sure resources are handled carefully.
First, the analysis of `configfs_unlink` revealed it's safe because `inode_lock` is held throughout. We aren't vulnerable to the unlock/relock race there. However, `configfs_symlink` has that explicit `inode_unlock` which requires a very thorough examination.
Let's begin by tracing the crucial control flow and potential failure points of the code:
1. **Early Exit:** We check if the target directory is "ready". If not (`!configfs_dirent_is_ready(sd)`), we return -ENOENT. This prevents issues before anything else happens. Also, checks are made if the item is appropriate for creating a symlink.
2. **`get_target()` Failure:** If `get_target` fails, we jump to `out_put`. In this case, `parent_item` is put and then `root_item` is put. We're done; no problem here.
3. **Directory Removal (Race Condition):** Crucially, the code checks `sd->s_frag->frag_dead`. If true (meaning the directory was removed concurrently), we return -ENOENT, after the target and parent items are put. Correctly, we ensure that both `config_item_put` operations are executed to avoid any memory leaks and invalid accesses.
4. **Successful Symlink:** The happy path is where `allow_link` and `create_link` succeed. In this case, `target_item` is put (the link reference is kept inside) and both the parent item and root item are put in the end. This is the normal case with no rmdir race.
5. **`create_link` Failure:** If `create_link` fails, we call `drop_link` and then put the target, parent, and root items. This is also handled safely.
Now, let's analyze the critical bit: the interaction between `parent_item` and `root_item` and the importance of their order in the put operations. Let's trace it and be sure.
The previous patch had the order reversed, and it was a critical failure! If `root_item` was freed by a concurrent rmdir before `parent_item`, `config_item_put` would be called on freed memory when `config_item_put(parent_item)` executes.
Putting `parent_item` before `root_item` fixes this critical bug: In most cases, the parent's cleanup will not need to access the root (it is only freed on the last `put`). If the parent IS the root, we ensure the shared `config_item` is handled safely.
Therefore, the ordering is vital. The `config_item_put(parent_item)` first, and then the `config_item_put(root_item)` second order ensures that any child (which might be embedded inside the root) is always released before its containing root object can be freed.
To further ensure there are no issues, let's meticulously examine what occurs in `config_item_cleanup(item)`.
* The cleanup function carefully releases resources, it has checks for `ci_group` and `ci_parent`, but it never accesses the root directly.
Now, we can trace two important cases in which a race could have resulted in a free.
1. **When `parent_item` is a default group**: In this case, `configfs_rmdir` occurs. We unlink and remove `parent_item` which calls `unlink_obj`. Then, we call `config_item_cleanup`. Because `parent_item->ci_group` is `NULL` and `parent_item->ci_parent` is `NULL`, the function does nothing and is safe.
2. **When `parent_item` is NOT a default group,** its refcount drops from 2 to 1 (symlink). Nothing happens, and the cleanup is NOT triggered.
In either case, the correct ordering ensures no issues: whether an rmdir happened concurrently or not, the parent is handled first.
Even with nested default groups, like `ports/1/subsys_level1/subsys_level2`, we are safe.
Finally, we need to consider if there are other potential similar issues. So, we'll search the kernel
|
| 474/4 |
2026/09/02 22:02 |
tool |
grepper |
0m
Args: map[Expression:frag_dead PathPrefix:fs/configfs/]
Results: map[Output:fs/configfs/configfs_internal.h=21=struct configfs_fragment {
--
fs/configfs/configfs_internal.h-23- struct rw_semaphore frag_sem;
fs/configfs/configfs_internal.h:24: bool frag_dead;
fs/configfs/configfs_internal.h-25-};
--
fs/configfs/dir.c=158=static struct configfs_fragment *new_fragment(void)
--
fs/configfs/dir.c-165- init_rwsem(&p->frag_sem);
fs/configfs/dir.c:166: p->frag_dead = false;
fs/configfs/dir.c-167- }
--
fs/configfs/dir.c=1469=static int configfs_rmdir(struct inode *dir, struct dentry *dentry)
--
fs/configfs/dir.c-1540- }
fs/configfs/dir.c:1541: frag->frag_dead = true;
fs/configfs/dir.c-1542- up_write(&frag->frag_sem);
--
fs/configfs/dir.c=1796=void configfs_unregister_group(struct config_group *group)
--
fs/configfs/dir.c-1804- down_write(&frag->frag_sem);
fs/configfs/dir.c:1805: frag->frag_dead = true;
fs/configfs/dir.c-1806- up_write(&frag->frag_sem);
--
fs/configfs/dir.c=1934=void configfs_unregister_subsystem(struct configfs_subsystem *subsys)
--
fs/configfs/dir.c-1947- down_write(&frag->frag_sem);
fs/configfs/dir.c:1948: frag->frag_dead = true;
fs/configfs/dir.c-1949- up_write(&frag->frag_sem);
--
fs/configfs/file.c=56=static int fill_read_buffer(struct file *file, struct configfs_buffer *buffer)
--
fs/configfs/file.c-66- down_read(&frag->frag_sem);
fs/configfs/file.c:67: if (!frag->frag_dead)
fs/configfs/file.c-68- count = buffer->attr->show(buffer->item, buffer->page);
--
fs/configfs/file.c=106=static ssize_t configfs_bin_read_iter(struct kiocb *iocb, struct iov_iter *to)
--
fs/configfs/file.c-125- down_read(&frag->frag_sem);
fs/configfs/file.c:126: if (!frag->frag_dead)
fs/configfs/file.c-127- len = buffer->bin_attr->read(buffer->item, NULL, 0);
--
fs/configfs/file.c-150- down_read(&frag->frag_sem);
fs/configfs/file.c:151: if (!frag->frag_dead)
fs/configfs/file.c-152- len = buffer->bin_attr->read(buffer->item,
--
fs/configfs/file.c=200=flush_write_buffer(struct file *file, struct configfs_buffer *buffer, size_t count)
--
fs/configfs/file.c-205- down_read(&frag->frag_sem);
fs/configfs/file.c:206: if (!frag->frag_dead)
fs/configfs/file.c-207- res = buffer->attr->store(buffer->item, buffer->page, count);
--
fs/configfs/file.c=290=static int __configfs_open_file(struct inode *inode, struct file *file, int type)
--
fs/configfs/file.c-304- down_read(&frag->frag_sem);
fs/configfs/file.c:305: if (unlikely(frag->frag_dead))
fs/configfs/file.c-306- goto out_free_buffer;
--
fs/configfs/file.c=400=static int configfs_release_bin_file(struct inode *inode, struct file *file)
--
fs/configfs/file.c-407- down_read(&frag->frag_sem);
fs/configfs/file.c:408: if (!frag->frag_dead) {
fs/configfs/file.c-409- /* result of ->release() is ignored */
]
|
| 475/4 |
2026/09/02 22:02 |
llm |
patch-generator |
0m
Model:
Tokens:
input: 59432
output: 30
thoughts: 70
Thoughts: Here's my thought process, as it stands:
**Digging into `frag` and Group Interactions**
Okay, so I'm trying to wrap my head around how this `frag` mechanism is behaving. The immediate question is, how exactly does this thing get shared, or not, between parent groups and the default group? Time to dive into the code. My instinct is that the key lies within the group population and creation functions. I'll need to check the call sites of `populate_groups`, `create_default_group`, and `configfs_attach_group`. `fs/configfs/dir.c` is the obvious starting point, as that's where a lot of the group management logic in configfs typically resides. That's where I need to focus my attention.
|
| 476/4 |
2026/09/02 22:02 |
tool |
grepper |
0m
Args: map[Expression:create_default_group PathPrefix:fs/configfs/]
Results: map[Output:fs/configfs/dir.c=664=static void detach_groups(struct dentry *dentry)
--
fs/configfs/dir.c-702- */
fs/configfs/dir.c:703:static int create_default_group(struct dentry *parent,
fs/configfs/dir.c-704- struct config_group *group,
--
fs/configfs/dir.c=734=static int populate_groups(struct config_group *group,
--
fs/configfs/dir.c-740- list_for_each_entry(new_group, &group->default_groups, group_entry) {
fs/configfs/dir.c:741: int ret = create_default_group(parent, new_group, frag);
fs/configfs/dir.c-742- if (ret)
--
fs/configfs/dir.c=1751=int configfs_register_group(struct config_group *parent_group,
--
fs/configfs/dir.c-1769- inode_lock_nested(d_inode(parent), I_MUTEX_PARENT);
fs/configfs/dir.c:1770: ret = create_default_group(parent, group, frag);
fs/configfs/dir.c-1771- if (ret)
]
|
| 477/4 |
2026/09/02 22:02 |
llm |
patch-generator |
0m
Model:
Tokens:
input: 60048
output: 25
thoughts: 3131
Thoughts: **Analyzing the ConfigFS Symlink and Default Group Lifecycle**
Okay, let's break this down methodically. I'm focusing on how `configfs_symlink` interacts with default groups and, crucially, the `frag_dead` flag. The initial concern was the shared `struct configfs_fragment *frag` across parent and default groups. In `populate_groups`, `create_default_group` correctly passes the `frag` structure, ensuring all default groups under a parent share the same fragment. This is significant because `configfs_rmdir` on a parent directory marks `frag->frag_dead = true`.
My key realization is that because the `frag` is shared, checking `sd->s_frag->frag_dead` in `configfs_symlink` *immediately* reflects the `frag_dead` state of the parent if `configfs_rmdir` has run. I checked whether a read-down semaphore was needed but, given the `inode_lock(dir)` held during that check, and that `configfs_rmdir()` sets `frag->frag_dead` before the lock is released, the simpler check is sufficient.
My investigation of `configfs_rmdir` showed that the `frag->frag_dead` flag is set *before* any detachment operations, like the `detach_groups()` call. Thus, `inode_lock(dir)` is always acquired after `frag->frag_dead = true`, which means the check in `configfs_symlink` will always see the correct state. Importantly, the `frag` itself remains valid because the associated `configfs_dirent` (`sd`) holds a reference and the `dentry->d_parent` is held by the VFS lookup.
Next, I looked carefully at `configfs_symlink()`. The code first validates that the directory (`sd`) is "ready." The `configfs_get_root_item` function then gets the root item, traversing default group hierarchies, to pin the root object so that the parent is not freed while we drop the lock. After the lock is dropped, the target is resolved, the lock reacquired, and the check for `frag_dead` is performed.
The crux of the issue lies in the fact that, in `configfs_symlink`, if `sd->s_frag->frag_dead` is true, the function returns `-ENOENT` *before* calling `allow_link` and `create_link`. I also verified that the parent item passed to `create_link` will be valid. This implies that the link creation is skipped if the parent directory has been marked as dead.
I concluded that the check for `sd->s_frag->frag_dead` and then subsequent returns is the *guard* against accessing a dead directory. In addition, the git history for `fs/configfs/symlink.c` could be a valuable source of additional context.
|
| 478/4 |
2026/09/02 22:02 |
tool |
git-log |
0m
Args: map[PathPrefix:fs/configfs/symlink.c]
Results: map[Output:1387aedb391a configfs: mark pinned dentries persistent
1b25dea3867a configfs:get_target() - release path as soon as we grab configfs_item reference
4609e1f18e19 fs: port ->permission() to pass mnt_idmap
7a77db95511c fs: port ->symlink() to pass mnt_idmap
fa60ce2cb450 treewide: remove editor modelines and cruft
549c7297717c fs: make helpers idmap mount aware
47291baa8ddf namei: make permission helpers idmapped mount aware
e2f238f7d5a1 configfs: calculate the depth of parent item
e9c03af21cc7 configfs: calculate the symlink target only once
351e5d869e5a configfs: fix a deadlock in configfs_symlink()
]
|
| 479/4 |
2026/09/02 22:02 |
llm |
patch-generator |
0m
Model:
Tokens:
input: 64073
output: 27
thoughts: 25
Thoughts: Here's a summary of my thought process:
**Diving into Commit 351e5d869e5a**
Okay, time to get my hands dirty. Let's start by isolating the changes introduced by that specific commit β `351e5d869e5a`. I'll use `git show` to get a comprehensive view of what was modified. This command is my go-to for really understanding the impact of a particular changeset, right down to the nitty-gritty details of line-by-line diffs. I need a clear picture of what happened, so I can contextualize it within the larger project and understand its potential ripple effects. I'm looking for the "why" behind this commit. What problem was solved? What new features were added? Were there any refactors or optimizations? I need to analyze this change in its entirety.
|
| 480/4 |
2026/09/02 22:02 |
tool |
git-show |
0m
Args:
Results: map[Output:commit 351e5d869e5ac10cb40c78b5f2d7dfc816ad4587
Author: Al Viro <viro@zeniv.linux.org.uk>
Date: Sat Aug 3 11:51:18 2019 -0400
configfs: fix a deadlock in configfs_symlink()
Configfs abuses symlink(2). Unlike the normal filesystems, it
wants the target resolved at symlink(2) time, like link(2) would've
done. The problem is that ->symlink() is called with the parent
directory locked exclusive, so resolving the target inside the
->symlink() is easily deadlocked.
Short of really ugly games in sys_symlink() itself, all we can
do is to unlock the parent before resolving the target and
relock it after. However, that invalidates the checks done
by the caller of ->symlink(), so we have to
* check that dentry is still where it used to be
(it couldn't have been moved, but it could've been unhashed)
* recheck that it's still negative (somebody else
might've successfully created a symlink with the same name
while we were looking the target up)
* recheck the permissions on the parent directory.
Cc: stable@vger.kernel.org
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
Signed-off-by: Christoph Hellwig <hch@lst.de>
diff --git a/fs/configfs/symlink.c b/fs/configfs/symlink.c
index 91eac6c55e07..f3881e4caedd 100644
--- a/fs/configfs/symlink.c
+++ b/fs/configfs/symlink.c
@@ -143,11 +143,42 @@ int configfs_symlink(struct inode *dir, struct dentry *dentry, const char *symna
!type->ct_item_ops->allow_link)
goto out_put;
+ /*
+ * This is really sick. What they wanted was a hybrid of
+ * link(2) and symlink(2) - they wanted the target resolved
+ * at syscall time (as link(2) would've done), be a directory
+ * (which link(2) would've refused to do) *AND* be a deep
+ * fucking magic, making the target busy from rmdir POV.
+ * symlink(2) is nothing of that sort, and the locking it
+ * gets matches the normal symlink(2) semantics. Without
+ * attempts to resolve the target (which might very well
+ * not even exist yet) done prior to locking the parent
+ * directory. This perversion, OTOH, needs to resolve
+ * the target, which would lead to obvious deadlocks if
+ * attempted with any directories locked.
+ *
+ * Unfortunately, that garbage is userland ABI and we should've
+ * said "no" back in 2005. Too late now, so we get to
+ * play very ugly games with locking.
+ *
+ * Try *ANYTHING* of that sort in new code, and you will
+ * really regret it. Just ask yourself - what could a BOFH
+ * do to me and do I want to find it out first-hand?
+ *
+ * AV, a thoroughly annoyed bastard.
+ */
+ inode_unlock(dir);
ret = get_target(symname, &path, &target_item, dentry->d_sb);
+ inode_lock(dir);
if (ret)
goto out_put;
- ret = type->ct_item_ops->allow_link(parent_item, target_item);
+ if (dentry->d_inode || d_unhashed(dentry))
+ ret = -EEXIST;
+ else
+ ret = inode_permission(dir, MAY_WRITE | MAY_EXEC);
+ if (!ret)
+ ret = type->ct_item_ops->allow_link(parent_item, target_item);
if (!ret) {
mutex_lock(&configfs_symlink_mutex);
ret = create_link(parent_item, target_item, dentry);
]
|
| 481/4 |
2026/09/02 22:02 |
llm |
patch-generator |
0m
Model:
Tokens:
input: 65430
output: 37
thoughts: 493
Thoughts: **Analyzing the Vulnerability in ConfigFS Symlink Creation**
Alright, let's dive back into commit `351e5d869e5a`. Al Viro's fix around `get_target()` for symlink creation involved unlocking and relocking the parent directory's inode. He was right; we have to do this to avoid deadlocks. However, dropping the lock introduces a window of vulnerability.
The original fix addressed the potential for the dentry to change during the unlock, but it missed a critical scenario in ConfigFS. If the parent directory (`parent_item`) happens to be an embedded default group, dropping the inode lock allows a concurrent `configfs_rmdir()` on an *ancestor* directory (specifically, the root item). This can lead to the detachment of *all* default groups, and potentially free the entire root structure. This is especially concerning because default groups don't hold the root object alive via refcounts like we might expect.
Therefore, we have to strengthen this. I need to make sure `configfs_symlink()` pins the root object (`root_item = configfs_get_root_item(dentry->d_parent)`) *before* releasing the `inode_lock(dir)`. Then, after reacquiring the lock, I need to verify that the directory wasn't removed while unlocked, by checking `sd->s_frag->frag_dead`.
Furthermore, I have to be extremely careful with cleanup. The `config_item_put()` calls need to be ordered correctly! I *must* call `config_item_put(parent_item)` *before* `config_item_put(root_item)`. This ensures that the child item's reference is dropped while the parent/root structure containing it is still pinned by `root_item`. Then, `config_item_put(root_item)` drops the root item's reference and allows freeing the structure, *only if* the refcount hits zero.
Okay, time to re-examine `fs/configfs/symlink.c`, specifically around lines 130 to 220, to ensure these changes are implemented correctly and completely. I need to verify that we are mitigating this type of race condition.
|
| 482/4 |
2026/09/02 22:02 |
tool |
read-file |
0m
Args: map[File:fs/configfs/symlink.c FirstLine:130 LineCount:90]
Results: map[Contents: 130: return -ENOENT;
131: return 0;
132: }
133:
134:
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: }
212:
213: int configfs_unlink(struct inode *dir, struct dentry *dentry)
214: {
215: struct configfs_dirent *sd = dentry->d_fsdata, *target_sd;
216: struct config_item *parent_item;
217: const struct config_item_type *type;
218: int ret;
219:
]
|
| 483/4 |
2026/09/02 22:02 |
llm |
patch-generator |
0m
Model:
Tokens:
input: 67858
output: 2271
thoughts: 1182
Thoughts: **Analyzing the ConfigFS Symlink Implementation**
Okay, let's dive into this patch. I need to apply this cleanly using `codeeditor`, so I can see exactly what's changing.
First, I see the addition of `configfs_get_root_item()`. Its function is straightforward: it walks up the directory structure from a given `dentry` until it finds a directory entry that isn't part of a default group or a group hierarchy being attached. This suggests a need to identify the "true" root of a configfs subtree, or at least a point above a potentially "invisible" layer.
Now, let's look at `configfs_symlink()`. The comments, ah, the comments! It's clear that this code is dealing with a rather unfortunate design decision from the past. The original intention seems to have been a hybrid of `link(2)` and `symlink(2)` semantics, which is inherently problematic. It attempts to resolve the symlink target *during* the syscall, a recipe for potential deadlocks given file system locking conventions. Essentially, it resolves the target at syscall time, requires the target to be a directory, but makes the target busy from `rmdir` POV. Classic case of a bad userland ABI leaking into the kernel.
The code's structure highlights these challenges:
1. **Visibility Check:** The function first checks if the parent directory is "ready." If it's part of an in-progress group attachment, the link fails immediately.
2. **Permissions & Root Item Pinning:** It retrieves the parent item and checks if the item type allows links. Then, there's a critical step: pinning the root object. This prevents the parent from being freed while handling this awkward locking.
3. **The "Sick" Logic:** This section's where the pain is. It's essentially the code's attempt to reconcile the desired link-like and symlink-like behavior, and it does so with locking gymnastics to avoid deadlocks. The `inode_unlock` and `inode_lock` calls are the telltale sign of this. It resolves the `target_item`, *outside* the parent inode lock, then does a re-check to determine if the operation is possible, and finally, applies the symlink.
4. **Target Resolution & Permission Checks:** Then, the code resolves the symbolic name to a `target_item`. Crucially, it unlocks the parent directory's inode before calling `get_target()`, which I'm sure is where the lock issues arise from. After that the directory is locked, the code then performs some basic existence checks and permission checks against the target and the parent, including a check for a "dead" fragment.
5. **Creation & Cleanup:** If all checks pass, it uses a mutex to protect the creation of the link and potential cleanup via `drop_link`.
This really underscores the inherent complexity of the original design. I'm glad this wasn't my call to make, I would have pushed back very hard! From what I understand of this code, it's clear this is necessary to support this legacy ABI.
|
| 484/4 |
2026/09/02 22:02 |
tool |
codeeditor |
0m
Args: map[CurrentCode:static int get_target(const char *symname, struct config_item **target,
struct super_block *sb)
{
struct path path __free(path_put) = {};
int ret;
ret = kern_path(symname, LOOKUP_FOLLOW|LOOKUP_DIRECTORY, &path);
if (ret)
return ret;
if (path.dentry->d_sb != sb)
return -EPERM;
*target = configfs_get_config_item(path.dentry);
if (!*target)
return -ENOENT;
return 0;
}
int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
struct dentry *dentry, const char *symname)
{
int ret;
struct configfs_dirent *sd;
struct config_item *parent_item;
struct config_item *target_item = NULL;
const struct config_item_type *type;
sd = dentry->d_parent->d_fsdata;
/*
* Fake invisibility if dir belongs to a group/default groups hierarchy
* being attached
*/
if (!configfs_dirent_is_ready(sd))
return -ENOENT;
parent_item = configfs_get_config_item(dentry->d_parent);
type = parent_item->ci_type;
ret = -EPERM;
if (!type || !type->ct_item_ops ||
!type->ct_item_ops->allow_link)
goto out_put;
/*
* This is really sick. What they wanted was a hybrid of
* link(2) and symlink(2) - they wanted the target resolved
* at syscall time (as link(2) would've done), be a directory
* (which link(2) would've refused to do) *AND* be a deep
* fucking magic, making the target busy from rmdir POV.
* symlink(2) is nothing of that sort, and the locking it
* gets matches the normal symlink(2) semantics. Without
* attempts to resolve the target (which might very well
* not even exist yet) done prior to locking the parent
* directory. This perversion, OTOH, needs to resolve
* the target, which would lead to obvious deadlocks if
* attempted with any directories locked.
*
* Unfortunately, that garbage is userland ABI and we should've
* said "no" back in 2005. Too late now, so we get to
* play very ugly games with locking.
*
* Try *ANYTHING* of that sort in new code, and you will
* really regret it. Just ask yourself - what could a BOFH
* do to me and do I want to find it out first-hand?
*
* AV, a thoroughly annoyed bastard.
*/
inode_unlock(dir);
ret = get_target(symname, &target_item, dentry->d_sb);
inode_lock(dir);
if (ret)
goto out_put;
if (dentry->d_inode || d_unhashed(dentry))
ret = -EEXIST;
else
ret = inode_permission(&nop_mnt_idmap, dir,
MAY_WRITE | MAY_EXEC);
if (!ret)
ret = type->ct_item_ops->allow_link(parent_item, target_item);
if (!ret) {
mutex_lock(&configfs_symlink_mutex);
ret = create_link(parent_item, target_item, dentry);
mutex_unlock(&configfs_symlink_mutex);
if (ret && type->ct_item_ops->drop_link)
type->ct_item_ops->drop_link(parent_item,
target_item);
}
config_item_put(target_item);
out_put:
config_item_put(parent_item);
return ret;
} NewCode:static int get_target(const char *symname, struct config_item **target,
struct super_block *sb)
{
struct path path __free(path_put) = {};
int ret;
ret = kern_path(symname, LOOKUP_FOLLOW|LOOKUP_DIRECTORY, &path);
if (ret)
return ret;
if (path.dentry->d_sb != sb)
return -EPERM;
*target = configfs_get_config_item(path.dentry);
if (!*target)
return -ENOENT;
return 0;
}
static struct config_item *configfs_get_root_item(struct dentry *dentry)
{
struct dentry *root_dentry = dentry;
struct configfs_dirent *sd;
while (root_dentry) {
sd = root_dentry->d_fsdata;
if (!sd || !(sd->s_type & CONFIGFS_USET_DEFAULT))
break;
root_dentry = root_dentry->d_parent;
}
return root_dentry ? configfs_get_config_item(root_dentry) : NULL;
}
int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
struct dentry *dentry, const char *symname)
{
int ret;
struct configfs_dirent *sd;
struct config_item *parent_item;
struct config_item *target_item = NULL;
struct config_item *root_item = NULL;
const struct config_item_type *type;
sd = dentry->d_parent->d_fsdata;
/*
* Fake invisibility if dir belongs to a group/default groups hierarchy
* being attached
*/
if (!configfs_dirent_is_ready(sd))
return -ENOENT;
parent_item = configfs_get_config_item(dentry->d_parent);
type = parent_item->ci_type;
ret = -EPERM;
if (!type || !type->ct_item_ops ||
!type->ct_item_ops->allow_link)
goto out_put;
/*
* Pin the root object so that the parent_item (which may be an
* embedded default group) is not freed while we drop the lock.
*/
root_item = configfs_get_root_item(dentry->d_parent);
/*
* This is really sick. What they wanted was a hybrid of
* link(2) and symlink(2) - they wanted the target resolved
* at syscall time (as link(2) would've done), be a directory
* (which link(2) would've refused to do) *AND* be a deep
* fucking magic, making the target busy from rmdir POV.
* symlink(2) is nothing of that sort, and the locking it
* gets matches the normal symlink(2) semantics. Without
* attempts to resolve the target (which might very well
* not even exist yet) done prior to locking the parent
* directory. This perversion, OTOH, needs to resolve
* the target, which would lead to obvious deadlocks if
* attempted with any directories locked.
*
* Unfortunately, that garbage is userland ABI and we should've
* said "no" back in 2005. Too late now, so we get to
* play very ugly games with locking.
*
* Try *ANYTHING* of that sort in new code, and you will
* really regret it. Just ask yourself - what could a BOFH
* do to me and do I want to find it out first-hand?
*
* AV, a thoroughly annoyed bastard.
*/
inode_unlock(dir);
ret = get_target(symname, &target_item, dentry->d_sb);
inode_lock(dir);
if (ret)
goto out_put;
if (dentry->d_inode || d_unhashed(dentry))
ret = -EEXIST;
else
ret = inode_permission(&nop_mnt_idmap, dir,
MAY_WRITE | MAY_EXEC);
if (!ret) {
if (sd->s_frag->frag_dead)
ret = -ENOENT;
else
ret = type->ct_item_ops->allow_link(parent_item, target_item);
}
if (!ret) {
mutex_lock(&configfs_symlink_mutex);
ret = create_link(parent_item, target_item, dentry);
mutex_unlock(&configfs_symlink_mutex);
if (ret && type->ct_item_ops->drop_link)
type->ct_item_ops->drop_link(parent_item,
target_item);
}
config_item_put(target_item);
out_put:
config_item_put(parent_item);
config_item_put(root_item);
return ret;
} SourceFile:fs/configfs/symlink.c]
Results:
|
| 485/4 |
2026/09/02 22:02 |
llm |
patch-generator |
0m
Model:
Tokens:
input: 71986
output: 10
thoughts: 14
Thoughts: Okay, here's my attempt at summarizing that thought process, tailored for an expert audience:
**Checking Our Work: The `patch-diff` Verification Step**
Alright, before we even *think* about pushing these changes upstream, I need to make absolutely sure everythingβs solid. No point in unleashing something buggy on the world, especially when dealing with [*Assume field specific context here, e.g., "production-level code" or "critical infrastructure"*]. Time to take a look at the `patch-diff`. This is my go-to sanity check. It's not just about seeing *what* changed, but confirming the *impact* of those changes. I'm looking for unexpected side effects, regressions, and of course, any glaring oversights in my own logic. Need to be certain that the code behaves as I intend and that no unforeseen issues arise. It's about a disciplined verification step before deploying. Let's see what `patch-diff` has to show.
|
| 486/4 |
2026/09/02 22:02 |
tool |
patch-diff |
0m
Error:
|
| 487/4 |
2026/09/02 22:02 |
llm |
patch-generator |
0m
Model:
Tokens:
input: 72236
output: 24
thoughts: 15
Thoughts: Okay, here's my interpretation of that thought process, assuming I'm deep in the weeds of software development and dealing with a tricky file comparison:
**Analyzing the Call to `patch-diff`**
Alright, so I'm about to call `patch-diff`. This is usually a key utility for me, it's what I rely on to figure out the differences between two files, likely to apply patches or generate them. The real question is: why am I passing an empty string as the `File:` argument? Hmm, that's unusual. It's either a deliberate edge case I'm testing, a placeholder I'll replace later, or potentially something a bit more interesting like wanting to compare to an "empty" file in my mental model. I'll need to figure out what context necessitates this unusual argument. Given my experience, I'm expecting some specific behavior with an empty file name in this particular call. Depending on the library, it could return an error, a default diff, or something entirely context-dependent. Time to dig into the surrounding code and see what I can find!
|
| 488/4 |
2026/09/02 22:02 |
tool |
patch-diff |
0m
Args: map[File:fs/configfs/symlink.c]
Results: map[Output:diff --git a/fs/configfs/symlink.c b/fs/configfs/symlink.c
index 31eb28b27..5fe3722bd 100644
--- a/fs/configfs/symlink.c
+++ b/fs/configfs/symlink.c
@@ -117,103 +117,128 @@ static int create_link(struct config_item *parent_item,
static int get_target(const char *symname, struct config_item **target,
struct super_block *sb)
{
struct path path __free(path_put) = {};
int ret;
ret = kern_path(symname, LOOKUP_FOLLOW|LOOKUP_DIRECTORY, &path);
if (ret)
return ret;
if (path.dentry->d_sb != sb)
return -EPERM;
*target = configfs_get_config_item(path.dentry);
if (!*target)
return -ENOENT;
return 0;
}
+static struct config_item *configfs_get_root_item(struct dentry *dentry)
+{
+ struct dentry *root_dentry = dentry;
+ struct configfs_dirent *sd;
+
+ while (root_dentry) {
+ sd = root_dentry->d_fsdata;
+ if (!sd || !(sd->s_type & CONFIGFS_USET_DEFAULT))
+ break;
+ root_dentry = root_dentry->d_parent;
+ }
+ return root_dentry ? configfs_get_config_item(root_dentry) : NULL;
+}
int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
struct dentry *dentry, const char *symname)
{
int ret;
struct configfs_dirent *sd;
struct config_item *parent_item;
struct config_item *target_item = NULL;
+ struct config_item *root_item = NULL;
const struct config_item_type *type;
sd = dentry->d_parent->d_fsdata;
/*
* Fake invisibility if dir belongs to a group/default groups hierarchy
* being attached
*/
if (!configfs_dirent_is_ready(sd))
return -ENOENT;
parent_item = configfs_get_config_item(dentry->d_parent);
type = parent_item->ci_type;
ret = -EPERM;
if (!type || !type->ct_item_ops ||
!type->ct_item_ops->allow_link)
goto out_put;
+ /*
+ * Pin the root object so that the parent_item (which may be an
+ * embedded default group) is not freed while we drop the lock.
+ */
+ root_item = configfs_get_root_item(dentry->d_parent);
+
/*
* This is really sick. What they wanted was a hybrid of
* link(2) and symlink(2) - they wanted the target resolved
* at syscall time (as link(2) would've done), be a directory
* (which link(2) would've refused to do) *AND* be a deep
* fucking magic, making the target busy from rmdir POV.
* symlink(2) is nothing of that sort, and the locking it
* gets matches the normal symlink(2) semantics. Without
* attempts to resolve the target (which might very well
* not even exist yet) done prior to locking the parent
* directory. This perversion, OTOH, needs to resolve
* the target, which would lead to obvious deadlocks if
* attempted with any directories locked.
*
* Unfortunately, that garbage is userland ABI and we should've
* said "no" back in 2005. Too late now, so we get to
* play very ugly games with locking.
*
* Try *ANYTHING* of that sort in new code, and you will
* really regret it. Just ask yourself - what could a BOFH
* do to me and do I want to find it out first-hand?
*
* AV, a thoroughly annoyed bastard.
*/
inode_unlock(dir);
ret = get_target(symname, &target_item, dentry->d_sb);
inode_lock(dir);
if (ret)
goto out_put;
if (dentry->d_inode || d_unhashed(dentry))
ret = -EEXIST;
else
ret = inode_permission(&nop_mnt_idmap, dir,
MAY_WRITE | MAY_EXEC);
- if (!ret)
- ret = type->ct_item_ops->allow_link(parent_item, target_item);
+ if (!ret) {
+ if (sd->s_frag->frag_dead)
+ ret = -ENOENT;
+ else
+ ret = type->ct_item_ops->allow_link(parent_item, target_item);
+ }
if (!ret) {
mutex_lock(&configfs_symlink_mutex);
ret = create_link(parent_item, target_item, dentry);
mutex_unlock(&configfs_symlink_mutex);
if (ret && type->ct_item_ops->drop_link)
type->ct_item_ops->drop_link(parent_item,
target_item);
}
config_item_put(target_item);
out_put:
config_item_put(parent_item);
+ config_item_put(root_item);
return ret;
}
int configfs_unlink(struct inode *dir, struct dentry *dentry)
{
struct configfs_dirent *sd = dentry->d_fsdata, *target_sd;
struct config_item *parent_item;
const struct config_item_type *type;
int ret;
]
|
| 489/4 |
2026/09/02 22:03 |
llm |
patch-generator |
0m
Model:
Tokens:
input: 74117
output: 688
thoughts: 340
Thoughts: **My Assessment of the Proposed Patch**
Okay, this diff looks clean, very concise. Before I give it the green light, let's just run through the critical parts one more time, really solidify everything in my mind.
First, `configfs_get_root_item`: it's walking up the directory entry's parent (`dentry->d_parent`), gracefully skipping the `CONFIGFS_USET_DEFAULT` entries. Then, it's returning a properly referenced `struct config_item *`, either by successfully using `configfs_get_config_item`, or returning NULL on failure. That's good, no obvious issues there.
Now, in `configfs_symlink()`... we're grabbing `parent_item` with `configfs_get_config_item(dentry->d_parent)` and also `root_item` with `configfs_get_root_item(dentry->d_parent)`. Then it unlocks the directory inode to resolve the target, and relocks the directory inode. The `sd->s_frag->frag_dead` check after the unlock is critical, ensuring the directory wasn't removed while unlocked. If all's good, it calls `allow_link()` and then `create_link()`.
Now, the crucial bit: it cleans up the `target_item` with `config_item_put(target_item)`. And then, in `out_put`, it calls `config_item_put(parent_item)` *first*, then `config_item_put(root_item)` *second*. Aha, that's the key. That ordering is exactly what we need to prevent the use-after-free situation we were seeing before, ensuring that if `root_item` is the allocated container holding `parent_item`, its reference count is kept alive until after `parent_item` is safely dropped. Perfect.
Alright, I'm satisfied. Everything is completely sound. The logic's correct, the sequencing is right, and it addresses the core issue in a robust and understandable way. We're good to go. Time to draft the final explanation.
|
| 490/3 |
2026/09/02 22:03 |
action |
test-patch |
4m
Results: map[PatchDiff:diff --git a/fs/configfs/symlink.c b/fs/configfs/symlink.c
index 31eb28b27..5fe3722bd 100644
--- a/fs/configfs/symlink.c
+++ b/fs/configfs/symlink.c
@@ -131,6 +131,19 @@ static int get_target(const char *symname, struct config_item **target,
return 0;
}
+static struct config_item *configfs_get_root_item(struct dentry *dentry)
+{
+ struct dentry *root_dentry = dentry;
+ struct configfs_dirent *sd;
+
+ while (root_dentry) {
+ sd = root_dentry->d_fsdata;
+ if (!sd || !(sd->s_type & CONFIGFS_USET_DEFAULT))
+ break;
+ root_dentry = root_dentry->d_parent;
+ }
+ return root_dentry ? configfs_get_config_item(root_dentry) : NULL;
+}
int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
struct dentry *dentry, const char *symname)
@@ -139,6 +152,7 @@ int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
struct configfs_dirent *sd;
struct config_item *parent_item;
struct config_item *target_item = NULL;
+ struct config_item *root_item = NULL;
const struct config_item_type *type;
sd = dentry->d_parent->d_fsdata;
@@ -157,6 +171,12 @@ int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
!type->ct_item_ops->allow_link)
goto out_put;
+ /*
+ * Pin the root object so that the parent_item (which may be an
+ * embedded default group) is not freed while we drop the lock.
+ */
+ root_item = configfs_get_root_item(dentry->d_parent);
+
/*
* This is really sick. What they wanted was a hybrid of
* link(2) and symlink(2) - they wanted the target resolved
@@ -192,8 +212,12 @@ int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
else
ret = inode_permission(&nop_mnt_idmap, dir,
MAY_WRITE | MAY_EXEC);
- if (!ret)
- ret = type->ct_item_ops->allow_link(parent_item, target_item);
+ if (!ret) {
+ if (sd->s_frag->frag_dead)
+ ret = -ENOENT;
+ else
+ ret = type->ct_item_ops->allow_link(parent_item, target_item);
+ }
if (!ret) {
mutex_lock(&configfs_symlink_mutex);
ret = create_link(parent_item, target_item, dentry);
@@ -207,6 +231,7 @@ int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
out_put:
config_item_put(parent_item);
+ config_item_put(root_item);
return ret;
}
TestError:]
|
| 491/3 |
2026/09/02 22:07 |
action |
If |
3m
Args:
|
| 492/4 |
2026/09/02 22:07 |
agent |
patch-reviewer |
3m
Model:
Results: map[ReviewApproved:true ReviewComments:[]]
Instruction: You are an experienced Linux kernel maintainer reviewing a proposed patch that compiles and passes reproducer tests.
Evaluate the patch diff against the crash report, root cause, and the architectural design guidelines below.
Scope Constraint (Single-Patch Submissions):
We only generate single, standalone patches (not patch series). If a comprehensive architectural redesign would require a multi-patch series touching many files, accept a localized single-patch fix as long as it correctly fixes the bug, does not introduce new issues, and avoids sentinel band-aids within its local scope.
Evaluation:
- Set ReviewApproved=true (and leave ReviewComments empty) if the patch is acceptable for upstream submission as a single-patch fix.
- Set ReviewApproved=false and list concise, actionable feedback in ReviewComments if the patch contains fixable "Kernel Architectural Design & Maintainer Taste" violations.
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.
---
# Kernel Architectural Design & Maintainer Taste
Evaluate kernel patches and fix proposals beyond binary bug presence. Enforce **maintainer taste**, **lifecycle symmetry**, **typestate soundness**, and **topological graph simplicity**.
---
## 1. Core Philosophy: Band-Aid vs. Architectural Design
Naive patches often introduce **defensive sentinel guards** (e.g., ad-hoc `if (!ptr) return;` checks, status flags, or suppressed warnings) directly at the point of failure. While locally avoiding crashes, they degrade subsystem architecture by leaving uninitialized states reachable.
Maintainer "good taste" solves the root cause by **shifting invariants** (making invalid states unrepresentable) and moving dynamic runtime checks into static typestate guarantees.
```
HACKY BAND-AID FIX (Monolithic Cleanup) ARCHITECTURAL DESIGN (Scoped RAII / Invariant Shift)
βββββββββββββββββββββββββββββββββββββββ ββββββββββββββββββββββββββββββββββββββββββββββββββββ
[ Init / Entry ] [ Init / Entry ]
β β
(Register Global Cleanup) (Allocate Resource A)
β β
[ Init Sub-B ] ββfailβββ [ Init Sub-B ] ββfailβββ
β (success) β β (success) β
βΌ β βΌ βΌ
[ Setup Complete ] β (Arm B Cleanup / RAII) (Unwind A Only:
β β β B never armed)
(Deferred Teardown) β βΌ β
β β [ Setup Complete ] βΌ
βΌ βΌ β [ Error Exit ]
[ release_all ] βββββββββ (LIFO Scope Teardown)
β β
(if (!ctx->b) return;) <-- Defensive guard! βΌ
β [ Clean Destruction ]
[ Unsafe State ] (Zero sentinel checks needed)
```
```c
// ANTI-PATTERN (Defensive Sentinel Guard): Teardown called on partial init -> callee needs guard
void driver_cleanup(struct ctx *ctx) {
if (!ctx->buf) return; // <-- Sentinel band-aid
free_buffer(ctx->buf);
}
// CANONICAL SHIFT (Granular Scoped Action): Registered only upon complete initialization
ctx->buf = alloc_buffer();
if (!ctx->buf) return -ENOMEM;
devm_add_action_or_reset(dev, free_buffer_action, ctx->buf);
```
---
## 2. Maintainer Taste as Graph Topology & Structural Simplicity
Software design quality maps directly to graph-theoretic properties across the Control-Flow Graph (CFG), Data-Flow Graph (DFG), and Object Lifecycle DAG:
### A. Control-Flow Graph (CFG) Simplification
* **Branch Minimization & Path Explosion (McCabe 1976):** Every defensive check added to a compound destructor or callback (`if (!ctx->buffer) return;`) adds a predicate node ($\pi$), increases cyclomatic complexity $v(G) = |E| - |V| + 2$, causes exponential path explosion ($O(2^k)$ paths), and enlarges explicit state spaces ($|S| = \prod |D_i|$). Good taste eliminates the branch by guaranteeing destructors are invoked only on initialized typestates. *(Note: Standard idempotent leaf deallocators like `kfree(NULL)` and public API input sanitizers are exempt).*
* **Single-Entry Single-Exit (SESE) Symmetry (Ferrante 1987, Johnson 1994, Dijkstra 1972):** Resource acquisition and release must form strict **nested dominator trees**. If resource $R_i$ is acquired at node $A$, the set of release actions $\{B_1, \dots, B_m\}$ must form a strict **post-dominating cut** relative to $A$ across all maximal exit paths.
* **Pointer Uniformity (Linus's "Good Taste" Rule):** Eliminate special-case conditional branches by operating on address indirection (e.g., indirect pointers `**curr` in linked list unlinking) to unify edge and interior cases into a branchless invariant.
* **Lexical Scope Invariants & Affine Lifecycles (Wadler 1990, RAII):** Enforce SESE symmetry and "consumed exactly once" affine invariants using compiler-backed scoped cleanup (`<linux/cleanup.h>` `guard()`, `scoped_guard()`, `__free()`).
### B. Ownership & Lifetime DAGs
* **Acyclic Lifecycles & Topological Teardown (Tarjan 1972, Kahn 1962):** Resource ownership must form a strict Directed Acyclic Graph (DAG) $G = (R, E)$. Teardown order must strictly follow reverse topological sort $\text{toposort}(G)^R$. Mixing conflicting lifetime paradigms (e.g., embedding a dynamic refcounted `kref`/socket struct inside a device-managed `devres` buffer or parent container) violates DAG acyclicity, creating synchronous blocking hacks (`wait_for_completion`), circular pins, and Use-After-Free hazards.
* **Three-Phase Concurrent Quiescence:** Multi-threaded and asynchronous teardown (networking, block layer, RCU) must strictly sequence: (1) **Deactivation/Delisting** (make unreachable) -> (2) **Quiescence & Draining** (`synchronize_rcu()`, `cancel_work_sync()`, `napi_disable()`) -> (3) **Physical Reclamation** (`kfree()`, `kmem_cache_destroy()`).
* **Typestate Validity (Strom & Yemini 1986, Aldrich et al. 2009):** A struct with $N$ fields should not use runtime boolean flags (`ctx->is_initialized`) to model incomplete typestates. Sub-resources must transition as a deterministic typestate automaton ($S_{uninit} \xrightarrow{\text{alloc}} S_{init} \xrightarrow{\text{publish}} S_{registered}$), and registration functions must accept only fully initialized typestates.
---
## 3. Deterministic Decision Trigger Matrix
| Code Symptom / Trigger (When you see X) | Anti-Pattern Band-Aid (DO NOT DO Z) | Canonical Invariant Shift (DO Y) |
| :--- | :--- | :--- |
| **Null deref in compound destructor / cleanup callback** | Add `if (!priv->buf) return;` in composite cleanup handler | Register granular cleanup immediately upon allocation via `devm_add_action_or_reset()`, `<linux/cleanup.h>` `__free()`, or discrete reverse LIFO labels |
| **UAF on dynamic object after container unbind** | Allocate with `devm_kzalloc()` and block on `wait_for_completion()` | Allocate with `kzalloc()`, manage lifetime via `kref_get()`/`kref_put()`, call unbind/delist on unbind, free in `kref` release callback |
| **Goto ladder lock leaks on early error exit** | Sprinkle manual `mutex_unlock()` across error returns | Use `guard(mutex)(&lock)` or `scoped_guard(spinlock, &lock)` from `<linux/cleanup.h>` |
| **Callback / IRQ / timer fires before full init** | Add `if (!priv->ready)` check inside IRQ/timer handler | Move `request_irq()`, `timer_setup()`, or `napi_enable()` strictly to the end of setup after all state structures are fully initialized |
| **Multi-step setup failure leaks resources** | Route all errors to a single `err:` label calling a monolithic `cleanup(priv)` with NULL checks | Use `cleanup.h` RAII, granular `devm` actions, or a strict reverse LIFO goto ladder (`err_free_b:` -> `err_free_a:`) |
| **Mixed ownership / asymmetric refcount drops** | Conditionally call `kref_put()` in caller based on error code | Enforce unconditional callee-cleans or caller-cleans ownership convention across all paths |
| **Ad-hoc state flag polling during teardown** | Add `priv->stopping = true` and spin/poll in callbacks | Use atomic typestate transitions and synchronous flush/drain APIs (`cancel_work_sync()`, `drain_workqueue()`) |
---
## 4. The Architectural Review Checklist
Before finalizing any kernel fix or review, audit against the following four criteria:
1. **The Sentinel Test (Caller vs. Callee Responsibility):**
* *Smell:* Adding a defensive guard (`if (!ptr)` or `if (flags & INITIALIZED)`) inside a compound teardown callback or destructor to mask partial initialization.
* *Invariant:* If a destructor executes on uninitialized data, the defect is at the **caller's registration/invocation point**, not the callee.
* *Note:* Standard C allocator no-ops (`kfree(NULL)`) and public API parameter validators (`if (WARN_ON(!ptr)) return -EINVAL;`) are exempt; this rule targets *internal subsystem lifecycle pipelines and teardown paths*.
* *Action:* Move registration to the point of complete initialization (`devm_add_action_or_reset()`, RAII/`__free()`, or discrete caller unwinding).
2. **LIFO Stack Unwinding Symmetry:**
* *Smell:* Releasing resources in arbitrary order or invoking destructors for partially initialized subsystems.
* *Invariant:* Resource release must strictly mirror acquisition order in reverse (LIFO) without monolithic cleanup guards.
* *Action:* Verify that partial setup/initialization failures (e.g., `probe()`, `mount()`, `open()`, `alloc_pool()`) unwind only already-allocated resources in reverse order via strict LIFO labels or `cleanup.h` scope exit.
3. **Ownership Decoupling:**
* *Smell:* Embedding dynamically refcounted objects (`kref`, `struct sock`, `struct inode`, `struct file`) directly inside hardware device-bound allocations (`devres`) or parent containers (`super_block`, `net_device`).
* *Invariant:* Dynamic consumer references must outlive parent unregistration without use-after-free, circular pins, or blocking hacks.
* *Action:* Decouple the refcounted core object from the container binding; container teardown must unbind/delist the object, but memory is freed exclusively by the final `kref_put()`.
4. **Codebase Idiom Alignment:**
* *Smell:* Using legacy custom rollback ladders, ad-hoc state flags, or manual lock unlocking on error paths.
* *Invariant:* Prefer standard, modern kernel abstractions with compiler-enforced safety guarantees.
* *Action (Modern >= 6.6):* Replace manual boilerplate with `cleanup.h` (`guard()`, `scoped_guard()`, `__free()`), `devm_add_action_or_reset()`, `refcount_t`, and standard `kref` helpers.
* *Action (Legacy / LTS / No-RAII Subsystems):* Enforce clean, branchless reverse LIFO goto ladders (`err_free_b:` -> `err_free_a:`).
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:
Bug title:
KASAN: slab-use-after-free Read in nvmet_port_subsys_allow_link
The crash report:
==================================================================
BUG: KASAN: slab-use-after-free in nvmet_port_subsys_allow_link+0x41/0x2e0 drivers/nvme/target/configfs.c:1059
Read of size 8 at addr ffff88811c6d44c8 by task syz-executor266/5850
CPU: 0 UID: 0 PID: 5850 Comm: syz-executor266 Not tainted syzkaller #1 PREEMPT(full)
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
Call Trace:
<TASK>
dump_stack_lvl+0xe8/0x150 lib/dump_stack.c:120
print_address_description+0x55/0x1e0 mm/kasan/report.c:378
print_report+0x58/0x70 mm/kasan/report.c:482
kasan_report+0x117/0x150 mm/kasan/report.c:595
nvmet_port_subsys_allow_link+0x41/0x2e0 drivers/nvme/target/configfs.c:1059
configfs_symlink+0x59a/0x1030 fs/configfs/symlink.c:196
vfs_symlink+0x18b/0x330 fs/namei.c:5794
filename_symlinkat+0x1cd/0x410 fs/namei.c:5819
__do_sys_symlinkat fs/namei.c:5839 [inline]
__se_sys_symlinkat+0x4e/0x2b0 fs/namei.c:5834
do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7f648f0cc459
Code: c0 79 93 eb d5 48 8d 7c 1d 00 eb 99 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 d0 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007f648c0911f8 EFLAGS: 00000246 ORIG_RAX: 000000000000010a
RAX: ffffffffffffffda RBX: 00007f648f139028 RCX: 00007f648f0cc459
RDX: 00007f648f139028 RSI: 00000000ffffff9c RDI: 00007f648f139060
RBP: 00007f648f139060 R08: 00007f648c0916c0 R09: 00007f648c0916c0
R10: 00007f648c0916c0 R11: 0000000000000246 R12: ffffffffffffffd0
R13: 0000000000000010 R14: 00007ffe91711350 R15: 00007ffe91711438
</TASK>
Allocated by task 5847:
kasan_save_stack mm/kasan/common.c:57 [inline]
kasan_save_track+0x3e/0x80 mm/kasan/common.c:78
poison_kmalloc_redzone mm/kasan/common.c:398 [inline]
__kasan_kmalloc+0x93/0xb0 mm/kasan/common.c:415
kasan_kmalloc include/linux/kasan.h:263 [inline]
__kmalloc_cache_noprof+0x321/0x600 mm/slub.c:5563
_kmalloc_noprof include/linux/slab.h:991 [inline]
_kzalloc_noprof include/linux/slab.h:1312 [inline]
nvmet_ports_make+0xe6/0xf00 drivers/nvme/target/configfs.c:2051
configfs_mkdir+0x4f6/0x9e0 fs/configfs/dir.c:1360
vfs_mkdir+0x40c/0x620 fs/namei.c:5410
filename_mkdirat+0x285/0x510 fs/namei.c:5443
__do_sys_mkdir fs/namei.c:5470 [inline]
__se_sys_mkdir+0x34/0x150 fs/namei.c:5467
do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
entry_SYSCALL_64_after_hwframe+0x77/0x7f
Freed by task 5847:
kasan_save_stack mm/kasan/common.c:57 [inline]
kasan_save_track+0x3e/0x80 mm/kasan/common.c:78
kasan_save_free_info+0x40/0x50 mm/kasan/generic.c:584
poison_slab_object mm/kasan/common.c:253 [inline]
__kasan_slab_free+0x5c/0x80 mm/kasan/common.c:285
kasan_slab_free include/linux/kasan.h:235 [inline]
slab_free_hook mm/slub.c:2748 [inline]
slab_free mm/slub.c:6499 [inline]
kfree+0x1c5/0x650 mm/slub.c:6792
config_item_cleanup fs/configfs/item.c:128 [inline]
config_item_release+0x13a/0x2d0 fs/configfs/item.c:137
configfs_rmdir+0x885/0x950 fs/configfs/dir.c:1571
vfs_rmdir+0x3e6/0x6a0 fs/namei.c:5515
filename_rmdir+0x292/0x520 fs/namei.c:5572
__do_sys_rmdir fs/namei.c:5595 [inline]
__se_sys_rmdir+0x2e/0x140 fs/namei.c:5592
do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
entry_SYSCALL_64_after_hwframe+0x77/0x7f
The buggy address belongs to the object at ffff88811c6d4000
which belongs to the cache kmalloc-4k of size 4096
The buggy address is located 1224 bytes inside of
freed 4096-byte region [ffff88811c6d4000, ffff88811c6d5000)
The buggy address belongs to the physical page:
page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x11c6d0
head: order:3 mapcount:0 entire_mapcount:0 nr_pages_mapped:0 pincount:0
flags: 0x17ff00000000040(head|node=0|zone=2|lastcpupid=0x7ff)
page_type: f5(slab)
raw: 017ff00000000040 ffff888100042140 dead000000000100 dead000000000122
raw: 0000000000000000 0000000000040004 00000000f5000000 0000000000000000
head: 017ff00000000040 ffff888100042140 dead000000000100 dead000000000122
head: 0000000000000000 0000000000040004 00000000f5000000 0000000000000000
head: 017ff00000000003 fffffffffffffe01 00000000ffffffff 00000000ffffffff
head: ffffffffffffffff 0000000000000000 00000000ffffffff 0000000000000008
page dumped because: kasan: bad access detected
page_owner tracks the page as allocated
page last allocated via order 3, migratetype Unmovable, gfp_mask 0xd20c0(__GFP_IO|__GFP_FS|__GFP_NOWARN|__GFP_NORETRY|__GFP_COMP|__GFP_NOMEMALLOC), pid 5843, tgid 5843 (syz-executor266), ts 63759869729
set_page_owner include/linux/page_owner.h:33 [inline]
post_alloc_hook+0x1f9/0x250 mm/page_alloc.c:1871
prep_new_page mm/page_alloc.c:1879 [inline]
get_page_from_freelist+0x2209/0x2280 mm/page_alloc.c:3943
__alloc_frozen_pages_noprof+0x217/0x5a0 mm/page_alloc.c:5436
alloc_slab_page mm/slub.c:3347 [inline]
allocate_slab+0x7d/0x620 mm/slub.c:3462
new_slab mm/slub.c:3513 [inline]
refill_objects+0x2d5/0x350 mm/slub.c:7410
refill_sheaf mm/slub.c:2885 [inline]
__pcs_replace_empty_main+0x2c8/0x6c0 mm/slub.c:4774
alloc_from_pcs mm/slub.c:4850 [inline]
slab_alloc_node mm/slub.c:4984 [inline]
__kmalloc_cache_noprof+0x39b/0x600 mm/slub.c:5559
_kmalloc_noprof include/linux/slab.h:991 [inline]
do_proc_readlink fs/proc/base.c:1817 [inline]
proc_pid_readlink+0x108/0x230 fs/proc/base.c:1847
vfs_readlink+0x4bb/0x540 fs/namei.c:6407
do_readlinkat+0x218/0x510 fs/stat.c:583
__do_sys_readlinkat fs/stat.c:599 [inline]
__se_sys_readlinkat fs/stat.c:596 [inline]
__x64_sys_readlinkat+0x9a/0xb0 fs/stat.c:596
do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
entry_SYSCALL_64_after_hwframe+0x77/0x7f
page last free pid 5843 tgid 5843 ts 63751651251 stack trace:
reset_page_owner include/linux/page_owner.h:26 [inline]
__free_pages_prepare mm/page_alloc.c:1418 [inline]
__free_frozen_pages+0xc50/0xd30 mm/page_alloc.c:2962
__slab_free+0x274/0x2c0 mm/slub.c:5815
qlink_free mm/kasan/quarantine.c:163 [inline]
qlist_free_all+0x99/0x100 mm/kasan/quarantine.c:179
kasan_quarantine_reduce+0x148/0x160 mm/kasan/quarantine.c:286
__kasan_slab_alloc+0x22/0x80 mm/kasan/common.c:350
kasan_slab_alloc include/linux/kasan.h:253 [inline]
slab_post_alloc_hook mm/slub.c:4683 [inline]
slab_alloc_node mm/slub.c:4996 [inline]
__do_kmalloc_node mm/slub.c:5413 [inline]
__kmalloc_noprof+0x307/0x720 mm/slub.c:5439
_kmalloc_noprof include/linux/slab.h:995 [inline]
tomoyo_add_entry security/tomoyo/common.c:2166 [inline]
tomoyo_supervisor+0xc53/0x1560 security/tomoyo/common.c:2238
tomoyo_audit_env_log security/tomoyo/environ.c:37 [inline]
tomoyo_env_perm+0x151/0x1f0 security/tomoyo/environ.c:64
tomoyo_environ security/tomoyo/domain.c:673 [inline]
tomoyo_find_next_domain+0x1332/0x1a90 security/tomoyo/domain.c:889
tomoyo_bprm_check_security+0x11b/0x180 security/tomoyo/tomoyo.c:102
security_bprm_check+0x85/0x240 security/security.c:820
search_binary_handler fs/exec.c:1749 [inline]
exec_binprm fs/exec.c:1791 [inline]
bprm_execve+0x888/0x1590 fs/exec.c:1847
do_execveat_common+0x4f4/0x670 fs/exec.c:1945
__do_sys_execve fs/exec.c:2018 [inline]
__se_sys_execve fs/exec.c:2012 [inline]
__x64_sys_execve+0x97/0xc0 fs/exec.c:2012
do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
entry_SYSCALL_64_after_hwframe+0x77/0x7f
Memory state around the buggy address:
ffff88811c6d4380: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
ffff88811c6d4400: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
>ffff88811c6d4480: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
^
ffff88811c6d4500: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
ffff88811c6d4580: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
==================================================================
The root cause explanation:
An elegant and complete analysis of the root cause, along with a robust fix for this `configfs` race condition.
### 1. KASAN Crash Analysis & Offset Math
The crash is a slab-use-after-free read of size 8 at offset **1224** inside a 4096-byte region (`kmalloc-4k`).
The KASAN trace points to `nvmet_port_subsys_allow_link()`, which accesses `parent->ci_parent` where `parent` is a `struct config_item *`.
In `nvmet_ports_make()`, a `struct nvmet_port` is dynamically allocated. Let's look at its memory layout:
```c
struct nvmet_port {
struct list_head entry; // 16 bytes
struct nvmf_disc_rsp_page_entry disc_addr; // 1024 bytes
struct config_group group; // 136 bytes (offset 1040)
struct config_group subsys_group; // 136 bytes (offset 1176)
// ...
};
```
The `parent_item` passed to `allow_link` is `&port->subsys_group.cg_item`.
The offset of `subsys_group` is `16 + 1024 + 136 = 1176` bytes.
Inside `struct config_item`, the `ci_parent` pointer is at offset 48.
When `nvmet_port_subsys_allow_link` reads `parent->ci_parent`, it accesses memory at offset `1176 + 48 = 1224` bytes into the `nvmet_port` allocation. This **perfectly matches** the KASAN report:
> `The buggy address is located 1224 bytes inside of freed 4096-byte region`
### 2. The Root Cause (Race Condition)
The race occurs between Thread 1 creating a symlink in a default group (`ports/1/subsystems/`) and Thread 2 removing the parent root object (`ports/1`).
1. **Thread 1 (`symlinkat`)**:
- Enters `configfs_symlink()` with the VFS holding `inode_lock(dir)` on `ports/1/subsystems`.
- Acquires a reference to `parent_item` (the embedded `subsys_group.cg_item`).
- Drops `inode_unlock(dir)` to resolve the symlink target without deadlocking.
2. **Thread 2 (`rmdir`)**:
- Calls `configfs_rmdir()` on `ports/1`.
- Calls `configfs_detach_group()`, which recursively locks all default groups. Since Thread 1 dropped the lock on `ports/1/subsystems`, Thread 2 acquires it, marks the items as dead, detaches them, and unlocks.
- Calls `unlink_group()` on `ports/1`, dropping the subsystem's reference to the port.
- The `nvmet_port` refcount drops to 0, and the **entire `nvmet_port` struct is freed via `kfree()`**.
3. **Thread 1 Resumes**:
- Re-acquires `inode_lock(dir)`.
- Blindly calls `type->ct_item_ops->allow_link(parent_item, target_item)`.
- `parent_item` points to the `subsys_group` embedded inside the **already freed** `nvmet_port`, causing the UAF read at offset 1224.
- Even if `allow_link` wasn't called, jumping to `out_put` executes `config_item_put(parent_item)`, causing a secondary UAF on the kref.
### 3. Why Simple Fixes Fail
- **Re-acquiring `parent_item`**: You cannot safely drop `parent_item` before the unlock and re-acquire it via `configfs_get_config_item(dentry->d_parent)` after the relock. When `configfs_rmdir` detaches the item, it leaves `sd->s_element` as a dangling pointer. Re-acquiring it will dereference freed memory.
- **Holding `parent_item`**: Holding the reference to `parent_item` across the unlock is insufficient because `parent_item` is a **default group**. Default groups are embedded directly inside their parent structs (like `nvmet_port`). Their individual refcount being $> 0$ does **not** prevent the parent struct from being freed when the parent's refcount drops to 0.
### 4. The Proper Fix
To prevent the memory from being freed while the lock is dropped, `configfs_symlink()` must pin the **root object** (the dynamically allocated parent, e.g., `ports/1`) rather than just the embedded default group.
Because `configfs_symlink()` holds `inode_lock(dir)` initially, any concurrent `configfs_rmdir()` on an ancestor root object will block inside `detach_groups()` trying to acquire `inode_lock(dir)`. This guarantees the dentry tree is stable and we can safely walk up the `d_parent` chain to find the root object (the first dentry without the `CONFIGFS_USET_DEFAULT` flag) and take a reference to it.
After re-acquiring the lock, we must check `sd->s_frag->frag_dead` to see if the directory was removed while unlocked.
**Implementation for `fs/configfs/symlink.c`:**
```c
/* Add this helper function above configfs_symlink */
static struct config_item *configfs_get_root_item(struct dentry *dentry)
{
struct dentry *root_dentry = dentry;
struct configfs_dirent *sd;
while (root_dentry) {
sd = root_dentry->d_fsdata;
if (!(sd->s_type & CONFIGFS_USET_DEFAULT))
break;
root_dentry = root_dentry->d_parent;
}
return configfs_get_config_item(root_dentry);
}
int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
struct dentry *dentry, const char *symname)
{
int ret;
struct config_item *parent_item;
struct config_item *target_item = NULL;
struct config_item *root_item = NULL;
const struct config_item_type *type;
parent_item = configfs_get_config_item(dentry->d_parent);
type = parent_item->ci_type;
ret = -EPERM;
if (!type || !type->ct_item_ops ||
!type->ct_item_ops->allow_link)
goto out_put;
/*
* Pin the root object so that the parent_item (which may be an
* embedded default group) is not freed while we drop the lock.
*/
root_item = configfs_get_root_item(dentry->d_parent);
/*
* Drop the dir inode lock. The target path might be outside
* configfs, and we don't want to deadlock.
*/
inode_unlock(dir);
ret = get_target(symname, &target_item, dentry->d_sb);
inode_lock(dir);
if (ret)
goto out_put_root;
if (dentry->d_inode || d_unhashed(dentry))
ret = -EEXIST;
else
ret = inode_permission(&nop_mnt_idmap, dir,
MAY_WRITE | MAY_EXEC);
if (!ret) {
struct configfs_dirent *sd = dentry->d_parent->d_fsdata;
/* Check if the directory was removed while unlocked */
if (sd->s_frag->frag_dead) {
ret = -ENOENT;
} else {
ret = type->ct_item_ops->allow_link(parent_item, target_item);
}
}
if (!ret) {
mutex_lock(&configfs_symlink_mutex);
ret = create_link(parent_item, target_item, dentry);
mutex_unlock(&configfs_symlink_mutex);
if (ret && type->ct_item_ops->drop_link)
type->ct_item_ops->drop_link(parent_item,
target_item);
}
config_item_put(target_item);
out_put_root:
config_item_put(root_item);
out_put:
config_item_put(parent_item);
return ret;
}
```
The proposed patch diff:
diff --git a/fs/configfs/symlink.c b/fs/configfs/symlink.c
index 31eb28b27..5fe3722bd 100644
--- a/fs/configfs/symlink.c
+++ b/fs/configfs/symlink.c
@@ -131,6 +131,19 @@ static int get_target(const char *symname, struct config_item **target,
return 0;
}
+static struct config_item *configfs_get_root_item(struct dentry *dentry)
+{
+ struct dentry *root_dentry = dentry;
+ struct configfs_dirent *sd;
+
+ while (root_dentry) {
+ sd = root_dentry->d_fsdata;
+ if (!sd || !(sd->s_type & CONFIGFS_USET_DEFAULT))
+ break;
+ root_dentry = root_dentry->d_parent;
+ }
+ return root_dentry ? configfs_get_config_item(root_dentry) : NULL;
+}
int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
struct dentry *dentry, const char *symname)
@@ -139,6 +152,7 @@ int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
struct configfs_dirent *sd;
struct config_item *parent_item;
struct config_item *target_item = NULL;
+ struct config_item *root_item = NULL;
const struct config_item_type *type;
sd = dentry->d_parent->d_fsdata;
@@ -157,6 +171,12 @@ int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
!type->ct_item_ops->allow_link)
goto out_put;
+ /*
+ * Pin the root object so that the parent_item (which may be an
+ * embedded default group) is not freed while we drop the lock.
+ */
+ root_item = configfs_get_root_item(dentry->d_parent);
+
/*
* This is really sick. What they wanted was a hybrid of
* link(2) and symlink(2) - they wanted the target resolved
@@ -192,8 +212,12 @@ int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
else
ret = inode_permission(&nop_mnt_idmap, dir,
MAY_WRITE | MAY_EXEC);
- if (!ret)
- ret = type->ct_item_ops->allow_link(parent_item, target_item);
+ if (!ret) {
+ if (sd->s_frag->frag_dead)
+ ret = -ENOENT;
+ else
+ ret = type->ct_item_ops->allow_link(parent_item, target_item);
+ }
if (!ret) {
mutex_lock(&configfs_symlink_mutex);
ret = create_link(parent_item, target_item, dentry);
@@ -207,6 +231,7 @@ int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
out_put:
config_item_put(parent_item);
+ config_item_put(root_item);
return ret;
}
Review this patch diff against the crash report, root cause explanation, and kernel architectural design guidelines.
|
| 493/5 |
2026/09/02 22:07 |
llm |
patch-reviewer |
0m
Model:
Tokens:
input: 14161
output: 38
thoughts: 61
Thoughts: Here's my take on this patch review:
**Patch Review Deep Dive: `fs/configfs/symlink.c`**
Okay, so we've got a proposed patch for `fs/configfs/symlink.c`. Specifically, it aims to fix a slab-use-after-free *read* vulnerability within the `nvmet_port_subsys_allow_link` function. My immediate instinct is to start digging into the code. This kind of vulnerability is serious, so we need to be thorough. First, let's pull up the affected file, `fs/configfs/symlink.c`, and the relevant part, `nvmet_port_subsys_allow_link`, and start dissecting the surrounding logic. I'll need to use my preferred tools to cross-reference and trace through the code paths; let's get those lined up. I'll be looking for the use-after-free condition β what's being freed, when, and how this read operation is occurring after that point. The "read" aspect of this is interesting, as these are often trickier to exploit than writes, but can still lead to information leaks or other stability issues. We need to ascertain if the read can be controlled by a malicious user. I'll also check the patch against the relevant kernel version and any upstream patches for that area.
|
| 494/5 |
2026/09/02 22:07 |
tool |
read-file |
0m
Args: map[File:fs/configfs/symlink.c FirstLine:120 LineCount:100]
Results: map[Contents: 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: }
133:
134:
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: }
212:
213: int configfs_unlink(struct inode *dir, struct dentry *dentry)
214: {
215: struct configfs_dirent *sd = dentry->d_fsdata, *target_sd;
216: struct config_item *parent_item;
217: const struct config_item_type *type;
218: int ret;
219:
]
|
| 495/5 |
2026/09/02 22:07 |
llm |
patch-reviewer |
0m
Model:
Tokens:
input: 16123
output: 21
thoughts: 73
Thoughts: Here's my take on those initial investigative thoughts:
**Diving into the ConfigFS Underbelly**
Okay, so I'm getting ready to really dig into ConfigFS. My immediate focus is on understanding the core mechanics. I need to get a clear picture of *how* ConfigFS actually functions. I'm thinking about the flow, the relationships...specifically, I want to nail down how the default groups are handled, how items and groups are managed within its structure, and then the critical operation lifecycle. I need to understand `configfs_rmdir`, how it's different to a group `detach`, and others.
To start, my instinct is to track down `CONFIGFS_USET_DEFAULT` and go from there. This seems like a likely entry point for examining the default group behavior. I'll probably be spending some quality time in `fs/configfs/dir.c` and whatever related files become relevant as I work through it. I'm hoping I can piece together a solid mental model of the underlying concepts by exploring the interactions within these key files.
|
| 496/5 |
2026/09/02 22:07 |
tool |
grepper |
0m
Args: map[Expression:CONFIGFS_USET_DEFAULT]
Results: map[Output:fs/configfs/configfs_internal.h=30=struct configfs_dirent {
--
fs/configfs/configfs_internal.h-52-#define CONFIGFS_USET_DIR 0x0040
fs/configfs/configfs_internal.h:53:#define CONFIGFS_USET_DEFAULT 0x0080
fs/configfs/configfs_internal.h-54-#define CONFIGFS_USET_DROPPING 0x0100
--
fs/configfs/dir.c=502=static int configfs_detach_prep(struct configfs_dirent *parent_sd, struct dentry **wait)
--
fs/configfs/dir.c-518- continue;
fs/configfs/dir.c:519: if (sd->s_type & CONFIGFS_USET_DEFAULT) {
fs/configfs/dir.c-520- /* Abort if racing with mkdir() */
--
fs/configfs/dir.c=548=static void configfs_detach_rollback(struct configfs_dirent *parent_sd)
--
fs/configfs/dir.c-554- list_for_each_entry(sd, &parent_sd->s_children, s_sibling)
fs/configfs/dir.c:555: if (sd->s_type & CONFIGFS_USET_DEFAULT)
fs/configfs/dir.c-556- configfs_detach_rollback(sd);
--
fs/configfs/dir.c=664=static void detach_groups(struct dentry *dentry)
--
fs/configfs/dir.c-673- next = next_dirent(parent_sd, sd);
fs/configfs/dir.c:674: if (!(sd->s_type & CONFIGFS_USET_DEFAULT))
fs/configfs/dir.c-675- continue;
--
fs/configfs/dir.c=703=static int create_default_group(struct dentry *parent,
--
fs/configfs/dir.c-722- sd = child->d_fsdata;
fs/configfs/dir.c:723: sd->s_type |= CONFIGFS_USET_DEFAULT;
fs/configfs/dir.c-724- } else {
--
fs/configfs/dir.c=977=static void configfs_dump_one(struct configfs_dirent *sd, int level)
--
fs/configfs/dir.c-986- type_print(CONFIGFS_USET_DIR);
fs/configfs/dir.c:987: type_print(CONFIGFS_USET_DEFAULT);
fs/configfs/dir.c-988- type_print(CONFIGFS_USET_DROPPING);
--
fs/configfs/dir.c=1469=static int configfs_rmdir(struct inode *dir, struct dentry *dentry)
--
fs/configfs/dir.c-1479- sd = dentry->d_fsdata;
fs/configfs/dir.c:1480: if (sd->s_type & CONFIGFS_USET_DEFAULT)
fs/configfs/dir.c-1481- return -EPERM;
]
|
| 497/5 |
2026/09/02 22:07 |
llm |
patch-reviewer |
0m
Model:
Tokens:
input: 17275
output: 37
thoughts: 37
Thoughts: Okay, let's dive into this. Here's how I'd approach this task, as if these were my internal thoughts:
**Analyzing the ConfigFS Directory Removal Process**
Right, so I need to get a handle on how `configfs` handles directory removal, specifically, the cleanup process. I'm focusing on `fs/configfs/dir.c` β that's the likely spot for the core directory operations. I'm zeroing in on `configfs_rmdir`, because that's the system call entry point for removing an empty directory. From there, I'll need to follow the execution path down to understand the details. My guess is it involves some kind of group detachment or teardown, so `detach_groups` and `configfs_detach_prep` immediately jump out as important functions to examine. I'm expecting some kind of recursive cleanup logic here, ensuring that any associated resources with the directory are correctly freed before the directory itself is removed from the file system. I'll be particularly interested in memory management, reference counting (or similar mechanisms) and any synchronization primitives employed to ensure safety during the whole process.
|
| 498/5 |
2026/09/02 22:07 |
tool |
read-file |
0m
Args: map[File:fs/configfs/dir.c FirstLine:490 LineCount:100]
Results: map[Contents: 490: done:
491: return d_splice_alias(inode, dentry);
492: }
493:
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: }
543:
544: /*
545: * Walk the tree, resetting CONFIGFS_USET_DROPPING wherever it was
546: * set.
547: */
548: static void configfs_detach_rollback(struct configfs_dirent *parent_sd)
549: {
550: struct configfs_dirent *sd;
551:
552: parent_sd->s_type &= ~CONFIGFS_USET_DROPPING;
553:
554: list_for_each_entry(sd, &parent_sd->s_children, s_sibling)
555: if (sd->s_type & CONFIGFS_USET_DEFAULT)
556: configfs_detach_rollback(sd);
557: }
558:
559: /*
560: * Find the next non-cursor. configfs_dirent_lock held by caller.
561: */
562: static struct configfs_dirent *next_dirent(struct configfs_dirent *parent,
563: struct configfs_dirent *last)
564: {
565: struct configfs_dirent *s;
566:
567: s = list_prepare_entry(last, &parent->s_children, s_sibling);
568:
569: list_for_each_entry_continue(s, &parent->s_children, s_sibling) {
570: if (s->s_element)
571: return s;
572: }
573: return NULL;
574: }
575:
576: static void detach_attrs(struct dentry *dentry)
577: {
578: struct configfs_dirent *parent_sd;
579: struct configfs_dirent *sd, *next;
580:
581: pr_debug("configfs %pd: dropping attrs for dir\n", dentry);
582:
583: parent_sd = dentry->d_fsdata;
584:
585: spin_lock(&configfs_dirent_lock);
586: for (sd = next_dirent(parent_sd, NULL); sd; sd = next) {
587: struct dentry *child;
588:
589: next = next_dirent(parent_sd, sd);
]
|
| 499/5 |
2026/09/02 22:07 |
llm |
patch-reviewer |
0m
Model:
Tokens:
input: 19041
output: 37
thoughts: 30
Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience:
**Diving Back into configfs/dir.c**
Alright, time to refresh my memory and dig back into `fs/configfs/dir.c`. I need to revisit the code surrounding `detach_groups` and `configfs_rmdir`. I'm focusing specifically on how groups are handled during directory removal within configfs. I'm especially interested in the nuances of the detach operation. I wonder about the interaction between the file system's internal structures and the user's view of group membership in this context. I need to solidify my understanding of the exact sequence of events, and what might be the potential race conditions or edge cases that I need to be aware of. I'm really looking to understand the mechanics of the reference counting, memory management and how errors are handled. I'm hoping to gain a complete mental model of this part of the code. Let's see if this deeper look reveals anything insightful.
|
| 500/5 |
2026/09/02 22:07 |
tool |
read-file |
0m
Args: map[File:fs/configfs/dir.c FirstLine:640 LineCount:100]
Results: map[Contents: 640: error = configfs_create_file(item, attr);
641: if (error)
642: return error;
643: }
644: }
645: if (t->ct_bin_attrs) {
646: for (i = 0; (bin_attr = t->ct_bin_attrs[i]) != NULL; i++) {
647: if (ops && ops->is_bin_visible && !ops->is_bin_visible(item, bin_attr, i))
648: continue;
649:
650: error = configfs_create_bin_file(item, bin_attr);
651: if (error)
652: return error;
653: }
654: }
655:
656: return 0;
657: }
658:
659: static int configfs_attach_group(struct config_item *item,
660: struct dentry *dentry,
661: struct configfs_fragment *frag);
662: static void configfs_detach_group(struct dentry *dentry);
663:
664: static void detach_groups(struct dentry *dentry)
665: {
666: struct dentry *child;
667: struct configfs_dirent *parent_sd;
668: struct configfs_dirent *sd, *next;
669:
670: parent_sd = dentry->d_fsdata;
671: spin_lock(&configfs_dirent_lock);
672: for (sd = next_dirent(parent_sd, NULL); sd; sd = next) {
673: next = next_dirent(parent_sd, sd);
674: if (!(sd->s_type & CONFIGFS_USET_DEFAULT))
675: continue;
676:
677: child = dget(sd->s_dentry);
678: spin_unlock(&configfs_dirent_lock);
679:
680: inode_lock(d_inode(child));
681:
682: configfs_detach_group(child);
683: d_inode(child)->i_flags |= S_DEAD;
684: dont_mount(child);
685:
686: inode_unlock(d_inode(child));
687:
688: d_delete(child);
689: dput(child);
690: spin_lock(&configfs_dirent_lock);
691: }
692: spin_unlock(&configfs_dirent_lock);
693: }
694:
695: /*
696: * This fakes mkdir(2) on a default_groups[] entry. It
697: * creates a dentry, attachs it, and then does fixup
698: * on the sd->s_type.
699: *
700: * We could, perhaps, tweak our parent's ->mkdir for a minute and
701: * try using vfs_mkdir. Just a thought.
702: */
703: static int create_default_group(struct dentry *parent,
704: struct config_group *group,
705: struct configfs_fragment *frag)
706: {
707: int ret;
708: struct configfs_dirent *sd;
709: /* We trust the caller holds a reference to parent */
710: struct dentry *child;
711:
712: if (!group->cg_item.ci_name)
713: group->cg_item.ci_name = group->cg_item.ci_namebuf;
714:
715: ret = -ENOMEM;
716: child = d_alloc_name(parent, group->cg_item.ci_name);
717: if (child) {
718: d_add(child, NULL);
719:
720: ret = configfs_attach_group(&group->cg_item, child, frag);
721: if (!ret) {
722: sd = child->d_fsdata;
723: sd->s_type |= CONFIGFS_USET_DEFAULT;
724: } else {
725: BUG_ON(d_inode(child));
726: d_drop(child);
727: }
728: dput(child);
729: }
730:
731: return ret;
732: }
733:
734: static int populate_groups(struct config_group *group,
735: struct configfs_fragment *frag)
736: {
737: struct dentry *parent = group->cg_item.ci_dentry;
738: struct config_group *new_group;
739:
]
|
| 501/5 |
2026/09/02 22:07 |
llm |
patch-reviewer |
0m
Model:
Tokens:
input: 20766
output: 38
thoughts: 21
Thoughts: Here's a summary of my thought process, as if I were the one thinking it:
**Digging Deeper into ConfigFS Detachment**
Alright, I'm already pretty familiar with the overall ConfigFS structure, but let's not just skim the surface. To really understand the detachment process and how directory removals are handled, I need to get into the details. I know the high-level concepts, but the devil's often in the details, especially with something as nuanced as a virtual filesystem. So, I need to understand exactly what happens under the hood when a ConfigFS group detaches and, relatedly, what happens during a directory removal. I'll read through `configfs_detach_group` and `configfs_rmdir` to trace those execution paths. Need to consider how that ties into how the data structures are managed, how references are cleaned up, and what locks are acquired and released. I want to see if there are any subtle race conditions or memory management concerns I should be aware of. This should shed light on any potential vulnerabilities or optimizations.
|
| 502/5 |
2026/09/02 22:07 |
tool |
read-file |
0m
Args: map[File:fs/configfs/dir.c FirstLine:1465 LineCount:100]
Results: map[Contents:1465: release_dentry_name_snapshot(&n);
1466: return ERR_PTR(ret);
1467: }
1468:
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);
]
|
| 503/5 |
2026/09/02 22:07 |
llm |
patch-reviewer |
0m
Model:
Tokens:
input: 22550
output: 37
thoughts: 23
Thoughts: Okay, here's my interpretation of those thoughts, tailored for an expert audience, presented in the first person:
**Diving Deeper into configfs Cleanup**
Alright, so I'm looking at `configfs_rmdir` and `configfs_detach_group`. My immediate thought is, "Okay, let's get a handle on the _rest_ of the cleanup flow here." I've seen bits and pieces, but I need to understand the complete picture β the full lifecycle of removing a directory and detaching a group within the ConfigFS framework.
I need to see how they interact. Are they chained, mutually exclusive, or do they handle related, but distinct tasks? I need to understand the dependencies and the overall cleanup strategy in play. It's not enough to see snippets; I need the entire context to avoid any potential resource leaks, race conditions, or unexpected side effects.
I'm particularly interested in how these functions handle error conditions and what the failure scenarios look like. Also, how they handle concurrency. Are we using locks? Reference counting? What mechanisms are in place to ensure atomic operations, and how are they implemented? I want to see how these functions contribute to, and potentially rely on, other underlying ConfigFS mechanisms.
|
| 504/5 |
2026/09/02 22:07 |
tool |
read-file |
0m
Args: map[File:fs/configfs/dir.c FirstLine:1565 LineCount:40]
Results: map[Contents: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: }
1578:
1579: const struct inode_operations configfs_dir_inode_operations = {
1580: .mkdir = configfs_mkdir,
1581: .rmdir = configfs_rmdir,
1582: .symlink = configfs_symlink,
1583: .unlink = configfs_unlink,
1584: .lookup = configfs_lookup,
1585: .setattr = configfs_setattr,
1586: };
1587:
1588: const struct inode_operations configfs_root_inode_operations = {
1589: .lookup = configfs_lookup,
1590: .setattr = configfs_setattr,
1591: };
1592:
1593: static int configfs_dir_open(struct inode *inode, struct file *file)
1594: {
1595: struct dentry * dentry = file->f_path.dentry;
1596: struct configfs_dirent * parent_sd = dentry->d_fsdata;
1597: int err;
1598:
1599: inode_lock(d_inode(dentry));
1600: /*
1601: * Fake invisibility if dir belongs to a group/default groups hierarchy
1602: * being attached
1603: */
1604: err = -ENOENT;
]
|
| 505/5 |
2026/09/02 22:07 |
llm |
patch-reviewer |
0m
Model:
Tokens:
input: 23433
output: 30
thoughts: 12
Thoughts: Here's a summary of my thoughts, as though I'm working through this process:
**Diving into `configfs_detach_group`**
Okay, so I'm looking at `configfs_detach_group`. Right off the bat, I need to understand what context this function lives in. Is it called during teardown, device removal, something like that? The naming convention suggests it's related to detaching a group, likely within the ConfigFS filesystem. I need to refresh my memory on the data structures here; what constitutes a "group" in the ConfigFS world? Is it a set of attributes, a collection of devices, or something more abstract?
My intuition says this is a critical function; it has to be responsible for releasing resources, cleaning up any internal state associated with this group, and ensuring that any downstream modules or drivers dependent on the group are notified or cleaned up. I'll need to pay close attention to potential race conditions here. Are there locks involved? If so, what's the locking order, and are there any potential deadlocks? This function probably deals with reference counting as well, I need to identify where they are incremented and decremented.
I'll need to quickly check the surrounding code for any error handling. Is there proper cleanup if something goes wrong during the detach process? Any error codes that should be returned, and what's the expected behavior of the caller if it encounters an error? Also, are there any potential side effects I need to watch out for, like the function removing the group and/or its members.
I'll start tracing the execution path and identify any calls to related functions or system calls. From there, I can understand the exact function this `configfs_detach_group` serves.
|
| 506/5 |
2026/09/02 22:07 |
tool |
grepper |
0m
Args: map[Expression:configfs_detach_group PathPrefix:fs/configfs]
Results: map[Output:fs/configfs/dir.c=68=const struct dentry_operations configfs_dentry_ops = {
--
fs/configfs/dir.c-75- * Helpers to make lockdep happy with our recursive locking of default groups'
fs/configfs/dir.c:76: * inodes (see configfs_attach_group() and configfs_detach_group()).
fs/configfs/dir.c-77- * We put default groups i_mutexes in separate classes according to their depth
--
fs/configfs/dir.c=659=static int configfs_attach_group(struct config_item *item,
--
fs/configfs/dir.c-661- struct configfs_fragment *frag);
fs/configfs/dir.c:662:static void configfs_detach_group(struct dentry *dentry);
fs/configfs/dir.c-663-
fs/configfs/dir.c=664=static void detach_groups(struct dentry *dentry)
--
fs/configfs/dir.c-681-
fs/configfs/dir.c:682: configfs_detach_group(child);
fs/configfs/dir.c-683- d_inode(child)->i_flags |= S_DEAD;
--
fs/configfs/dir.c=836=static void configfs_detach_item(struct dentry *dentry)
--
fs/configfs/dir.c-851- *
fs/configfs/dir.c:852: * configfs_detach_group() and configfs_detach_item() behave similarly on
fs/configfs/dir.c-853- * the way out. They assume that the proper semaphores are held, they
--
fs/configfs/dir.c=857=static int configfs_attach_item(struct config_item *item,
--
fs/configfs/dir.c-884-/* Caller holds the mutex of the group's inode */
fs/configfs/dir.c:885:static void configfs_detach_group(struct dentry *dentry)
fs/configfs/dir.c-886-{
--
fs/configfs/dir.c=891=static int configfs_attach_group(struct config_item *item,
--
fs/configfs/dir.c-915- if (ret) {
fs/configfs/dir.c:916: configfs_detach_group(dentry);
fs/configfs/dir.c-917- d_inode(dentry)->i_flags |= S_DEAD;
--
fs/configfs/dir.c=1469=static int configfs_rmdir(struct inode *dir, struct dentry *dentry)
--
fs/configfs/dir.c-1553- if (sd->s_type & CONFIGFS_USET_DIR) {
fs/configfs/dir.c:1554: configfs_detach_group(dentry);
fs/configfs/dir.c-1555-
--
fs/configfs/dir.c=1796=void configfs_unregister_group(struct config_group *group)
--
fs/configfs/dir.c-1812-
fs/configfs/dir.c:1813: configfs_detach_group(dentry);
fs/configfs/dir.c-1814- d_inode(dentry)->i_flags |= S_DEAD;
--
fs/configfs/dir.c=1934=void configfs_unregister_subsystem(struct configfs_subsystem *subsys)
--
fs/configfs/dir.c-1960- mutex_unlock(&configfs_symlink_mutex);
fs/configfs/dir.c:1961: configfs_detach_group(dentry);
fs/configfs/dir.c-1962- d_inode(dentry)->i_flags |= S_DEAD;
]
|
| 507/5 |
2026/09/02 22:07 |
llm |
patch-reviewer |
0m
Model:
Tokens:
input: 24843
output: 36
thoughts: 29
Thoughts: Here's my summary, as though I'm thinking through this code snippet:
**Diving into configfs_detach_group**
Okay, let's get right to it. I'm focusing on `configfs_detach_group` β specifically, I'm checking it out at line 885 within `fs/configfs/dir.c`. I need to understand how this function is working to detach a group in configfs. I'm already anticipating that it deals with unlinking and cleanup, probably related to removing references and ensuring things are properly freed after a configfs group is taken down. Time to see what's actually happening under the hood.
|
| 508/5 |
2026/09/02 22:07 |
tool |
read-file |
0m
Args: map[File:fs/configfs/dir.c FirstLine:875 LineCount:40]
Results: map[Contents: 875: dont_mount(dentry);
876: inode_unlock(d_inode(dentry));
877: d_delete(dentry);
878: }
879: }
880:
881: return ret;
882: }
883:
884: /* Caller holds the mutex of the group's inode */
885: static void configfs_detach_group(struct dentry *dentry)
886: {
887: detach_groups(dentry);
888: configfs_detach_item(dentry);
889: }
890:
891: static int configfs_attach_group(struct config_item *item,
892: struct dentry *dentry,
893: struct configfs_fragment *frag)
894: {
895: int ret;
896: struct configfs_dirent *sd;
897:
898: ret = configfs_attach_item(item, dentry, frag);
899: if (!ret) {
900: sd = dentry->d_fsdata;
901: sd->s_type |= CONFIGFS_USET_DIR;
902:
903: /*
904: * FYI, we're faking mkdir in populate_groups()
905: * We must lock the group's inode to avoid races with the VFS
906: * which can already hit the inode and try to add/remove entries
907: * under it.
908: *
909: * We must also lock the inode to remove it safely in case of
910: * error, as rmdir() would.
911: */
912: inode_lock_nested(d_inode(dentry), I_MUTEX_CHILD);
913: configfs_adjust_dir_dirent_depth_before_populate(sd);
914: ret = populate_groups(to_config_group(item), frag);
]
|
| 509/5 |
2026/09/02 22:07 |
llm |
patch-reviewer |
0m
Model:
Tokens:
input: 25640
output: 36
thoughts: 12
Thoughts: Okay, let's dive into this. Here's my take on `configfs_detach_item`, from an expert's perspective:
**Diving into `configfs_detach_item`**
Alright, so we're looking at `configfs_detach_item`, which immediately makes me think about resource management within the ConfigFS framework. It's likely the function responsible for the delicate dance of removing a configured item from the filesystem. This isn't just a simple delete, though. We're dealing with a sophisticated system where the removal might have ripple effects β dependencies to be considered, resources to be freed, and perhaps even some kind of cleanup or shutdown sequence that needs to be initiated.
My initial thoughts run along these lines: What are the preconditions for this function to succeed? Does it involve checks to ensure no other users are currently referencing or using the item? What about the potential for race conditions? Are there locks or mutexes involved to protect shared data structures during the detachment process? I'm already mentally running through potential deadlock scenarios and looking for areas where careful locking might be crucial.
I'm also curious about the specific steps involved. Is there a clear ordering of operations? Does it involve unlinking the item from the ConfigFS hierarchy, releasing any associated memory or resources, and then possibly notifying any relevant subsystems that the item is no longer available? A clean implementation will probably involve a well-defined state machine to track the detach process, ensuring that it remains robust, even in the face of errors.
I'll be looking to see if there's any support for graceful degradation and error handling. What happens if the detach fails mid-way? Are there any rollback mechanisms in place? It's crucial that the ConfigFS remains consistent even if a detachment process is interrupted.
Finally, I'll be searching for any potential optimization opportunities. Can we perform certain operations concurrently? Are there any areas where we can reduce lock contention or improve the efficiency of memory management? A well-designed `configfs_detach_item` is critical to overall system performance and stability. I need to understand its behavior fully to guarantee the integrity of my ConfigFS configurations.
|
| 510/5 |
2026/09/02 22:08 |
tool |
read-file |
0m
Args: map[File:fs/configfs/dir.c FirstLine:825 LineCount:40]
Results: map[Contents: 825: else if (configfs_is_root(&parent_group->cg_item))
826: subsys = to_configfs_subsystem(group);
827: else
828: BUG();
829: group->cg_subsys = subsys;
830:
831: list_for_each_entry(new_group, &group->default_groups, group_entry)
832: link_group(group, new_group);
833: }
834:
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: }
841:
842: /*
843: * The goal is that configfs_attach_item() (and
844: * configfs_attach_group()) can be called from either the VFS or this
845: * module. That is, they assume that the items have been created,
846: * the dentry allocated, and the dcache is all ready to go.
847: *
848: * If they fail, they must clean up after themselves as if they
849: * had never been called. The caller (VFS or local function) will
850: * handle cleaning up the dcache bits.
851: *
852: * configfs_detach_group() and configfs_detach_item() behave similarly on
853: * the way out. They assume that the proper semaphores are held, they
854: * clean up the configfs items, and they expect their callers will
855: * handle the dcache bits.
856: */
857: static int configfs_attach_item(struct config_item *item,
858: struct dentry *dentry,
859: struct configfs_fragment *frag)
860: {
861: int ret;
862:
863: ret = configfs_create_dir(item, dentry, frag);
864: if (!ret) {
]
|
| 511/5 |
2026/09/02 22:08 |
llm |
patch-reviewer |
0m
Model:
Tokens:
input: 26781
output: 30
thoughts: 23
Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience and written in the first person:
**Diving into the ConfigFS Directory Removal**
Okay, so I need to understand how directory removal works in ConfigFS. My first step is to zero in on `configfs_remove_dir`. That's the likely entry point. Let's head over to the source code, specifically `fs/configfs/dir.c`. I expect to find the core logic for handling the removal of a directory within the ConfigFS virtual filesystem there. I'm hoping to quickly grasp the interaction with inodes, dentry structures, and the implications for refcounting and potential resource cleanup. I'll be looking for things like how it handles child nodes, what kind of locking is involved to ensure consistency, and, of course, how it prevents the system from removing directories that are currently in use or have pending operations. This should give me a pretty good understanding of the control flow. Now, let's go poke around in the code!
|
| 512/5 |
2026/09/02 22:08 |
tool |
grepper |
0m
Args: map[Expression:configfs_remove_dir PathPrefix:fs/configfs]
Results: map[Output:fs/configfs/dir.c=253=int configfs_make_dirent(struct configfs_dirent * parent_sd,
--
fs/configfs/dir.c-270-
fs/configfs/dir.c:271:static void configfs_remove_dirent(struct dentry *dentry)
fs/configfs/dir.c-272-{
--
fs/configfs/dir.c=293=static int configfs_create_dir(struct config_item *item, struct dentry *dentry,
--
fs/configfs/dir.c-326- configfs_put(dentry->d_fsdata);
fs/configfs/dir.c:327: configfs_remove_dirent(dentry);
fs/configfs/dir.c-328- return PTR_ERR(inode);
--
fs/configfs/dir.c=369=int configfs_create_link(struct configfs_dirent *target, struct dentry *parent,
--
fs/configfs/dir.c-394- configfs_put(dentry->d_fsdata);
fs/configfs/dir.c:395: configfs_remove_dirent(dentry);
fs/configfs/dir.c-396- return PTR_ERR(inode);
--
fs/configfs/dir.c-399-/**
fs/configfs/dir.c:400: * configfs_remove_dir - remove an config_item's directory.
fs/configfs/dir.c-401- * @d: dentry we're removing.
--
fs/configfs/dir.c-409-
fs/configfs/dir.c:410:static void configfs_remove_dir(struct dentry *d)
fs/configfs/dir.c-411-{
--
fs/configfs/dir.c-413-
fs/configfs/dir.c:414: configfs_remove_dirent(d);
fs/configfs/dir.c-415-
--
fs/configfs/dir.c=836=static void configfs_detach_item(struct dentry *dentry)
--
fs/configfs/dir.c-838- detach_attrs(dentry);
fs/configfs/dir.c:839: configfs_remove_dir(dentry);
fs/configfs/dir.c-840-}
]
|
| 513/5 |
2026/09/02 22:08 |
llm |
patch-reviewer |
0m
Model:
Tokens:
input: 27675
output: 36
thoughts: 22
Thoughts: Here's my attempt at summarizing those functions, considering I'm an expert:
**Understanding configfs_remove_dirent and configfs_remove_dir**
Okay, so I'm looking at `configfs_remove_dirent` and `configfs_remove_dir`. These are the workhorses when it comes to *removing* things from the ConfigFS hierarchy. I immediately think of the implications for atomicity and locking β this is crucial in a filesystem like this. I bet `configfs_remove_dirent` handles the individual directory entry (the inode, the dent, the metadata), and `configfs_remove_dir` is the more complex function dealing with an entire directory.
I expect `configfs_remove_dir` would call `configfs_remove_dirent` repeatedly on its child entries before actually removing the directory itself. It would need to iterate through the directory entries, potentially recursively if nested directories are involved. I'd need to examine how they handle reference counting, so a directory isn't deleted while still in use by another part of the system. I also want to see how these functions interact with the ConfigFS's internal data structures and any associated user-defined attributes or custom data.
The order of operations is also key. Does the directory have to be empty before it's removed? Is there a forced removal option? I bet error handling is robust to prevent data corruption or inconsistencies within the ConfigFS namespace, considering this can cause some serious problems. I should see how they are implemented, I bet that they handle things in a way that minimizes the potential for race conditions. This is going to be interesting.
|
| 514/5 |
2026/09/02 22:08 |
tool |
read-file |
0m
Args: map[File:fs/configfs/dir.c FirstLine:400 LineCount:40]
Results: map[Contents: 400: * configfs_remove_dir - remove an config_item's directory.
401: * @d: dentry we're removing.
402: *
403: * The only thing special about this is that we remove any files in
404: * the directory before we remove the directory, and we've inlined
405: * what used to be configfs_rmdir() below, instead of calling separately.
406: *
407: * Caller holds the mutex of the item's inode
408: */
409:
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: }
425:
426: static struct dentry * configfs_lookup(struct inode *dir,
427: struct dentry *dentry,
428: unsigned int flags)
429: {
430: struct configfs_dirent * parent_sd = dentry->d_parent->d_fsdata;
431: struct configfs_dirent * sd;
432: struct inode *inode = NULL;
433:
434: if (dentry->d_name.len > NAME_MAX)
435: return ERR_PTR(-ENAMETOOLONG);
436:
437: /*
438: * Fake invisibility if dir belongs to a group/default groups hierarchy
439: * being attached
]
|
| 515/5 |
2026/09/02 22:08 |
llm |
patch-reviewer |
0m
Model:
Tokens:
input: 28690
output: 36
thoughts: 16
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:
**Investigating `configfs_remove_dirent`**
Alright, so we're looking at some directory entry removal within the ConfigFS context. We've been delving into the core mechanisms, and I think we need to bring `configfs_remove_dirent` into the picture. It's crucial we don't skip over this particular function because it is vital to understand the complete cycle of directory and file deletion. Understanding its logic will undoubtedly give us a better overall picture. It is likely called during the final stages of the removal process. I'm keen to dissect its implementation to ensure we account for all edge cases and interactions, especially in terms of concurrency and resource management within the ConfigFS structure. Let's make sure it's not being overlooked as we develop our deeper understanding.
|
| 516/5 |
2026/09/02 22:08 |
tool |
read-file |
0m
Args: map[File:fs/configfs/dir.c FirstLine:270 LineCount:30]
Results: map[Contents: 270:
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: }
282:
283: /**
284: * configfs_create_dir - create a directory for an config_item.
285: * @item: config_itemwe're creating directory for.
286: * @dentry: config_item's dentry.
287: * @frag: config_item's fragment.
288: *
289: * Note: user-created entries won't be allowed under this new directory
290: * until it is validated by configfs_dir_set_ready()
291: */
292:
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);
]
|
| 517/5 |
2026/09/02 22:08 |
llm |
patch-reviewer |
0m
Model:
Tokens:
input: 29435
output: 32
thoughts: 22
Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience:
**Diving into `configfs_get_config_item` and Config Items/Fragments**
Okay, so I'm thinking about `configfs_get_config_item` again, and it's time to refresh my understanding of how config items and fragments interact within the whole `configfs` framework. I need to nail down the precise mechanisms at play here. Let me see... I need to consider things like how the item's retrieval fits into the larger architecture. I want to make sure I grasp the relationship between item structures, attribute access, and those associated fragments. Are we talking about a simple lookup, or is there more complexity involved in handling different item types and configurations?
I'm thinking about efficiency too. Is the retrieval process optimal or are there potential areas where improvements could be made. Could there be any contention issues if several threads call the same function concurrently? What are the implications of caching item data if present?
I'll start by making sure that my grasp on the details around locking is solid. I need to be absolutely certain how access to the configuration data and the associated fragments are synchronized. I should trace the code path for several common scenarios, with a focus on potential edge cases and error handling. I need to make sure I am comfortable with both read and write access patterns, given these pieces of code.
|
| 518/5 |
2026/09/02 22:08 |
tool |
grepper |
0m
Args: map[Expression:configfs_get_config_item PathPrefix:fs/configfs]
Results: map[Output:fs/configfs/configfs_internal.h=113=static inline struct configfs_bin_attribute *to_bin_attr(struct dentry *dentry)
--
fs/configfs/configfs_internal.h-119-
fs/configfs/configfs_internal.h:120:static inline struct config_item *configfs_get_config_item(struct dentry *dentry)
fs/configfs/configfs_internal.h-121-{
--
fs/configfs/dir.c=1289=static struct dentry *configfs_mkdir(struct mnt_idmap *idmap, struct inode *dir,
--
fs/configfs/dir.c-1330- /* Get a working ref for the duration of this function */
fs/configfs/dir.c:1331: parent_item = configfs_get_config_item(dentry->d_parent);
fs/configfs/dir.c-1332- type = parent_item->ci_type;
--
fs/configfs/dir.c=1469=static int configfs_rmdir(struct inode *dir, struct dentry *dentry)
--
fs/configfs/dir.c-1483- /* Get a working ref until we have the child */
fs/configfs/dir.c:1484: parent_item = configfs_get_config_item(dentry->d_parent);
fs/configfs/dir.c-1485- subsys = to_config_group(parent_item)->cg_subsys;
--
fs/configfs/dir.c-1544- /* Get a working ref for the duration of this function */
fs/configfs/dir.c:1545: item = configfs_get_config_item(dentry);
fs/configfs/dir.c-1546-
--
fs/configfs/symlink.c=117=static int get_target(const char *symname, struct config_item **target,
--
fs/configfs/symlink.c-127- return -EPERM;
fs/configfs/symlink.c:128: *target = configfs_get_config_item(path.dentry);
fs/configfs/symlink.c-129- if (!*target)
--
fs/configfs/symlink.c=135=int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
--
fs/configfs/symlink.c-151-
fs/configfs/symlink.c:152: parent_item = configfs_get_config_item(dentry->d_parent);
fs/configfs/symlink.c-153- type = parent_item->ci_type;
--
fs/configfs/symlink.c=213=int configfs_unlink(struct inode *dir, struct dentry *dentry)
--
fs/configfs/symlink.c-225-
fs/configfs/symlink.c:226: parent_item = configfs_get_config_item(dentry->d_parent);
fs/configfs/symlink.c-227- type = parent_item->ci_type;
]
|
| 519/5 |
2026/09/02 22:08 |
llm |
patch-reviewer |
0m
Model:
Tokens:
input: 30603
output: 39
thoughts: 17
Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience:
**Diving into `configfs_get_config_item`**
Alright, let's take a closer look at `configfs_get_config_item`. My initial hunch is that it's likely a core function within the ConfigFS framework, probably responsible for retrieving a specific configuration item. I'm assuming we're dealing with a structured file system where configuration parameters are represented as files or directories. Knowing ConfigFS, it could be a simple look-up by name, a traversal of the hierarchy, or even a more sophisticated retrieval based on some attribute filter. I'd need to confirm the exact data structures and what the function operates on. It's probably called at various points, and finding the points of invocation would give me some insight on what kind of operations are using this. I'm imagining it's used by the control plane to manipulate the configuration, by user-space tools to query the configuration, or even a combination of the two. This is going to be interesting.
|
| 520/5 |
2026/09/02 22:08 |
tool |
read-file |
0m
Args: map[File:fs/configfs/configfs_internal.h FirstLine:115 LineCount:35]
Results: map[Contents: 115: struct configfs_attribute *attr = to_attr(dentry);
116:
117: return container_of(attr, struct configfs_bin_attribute, cb_attr);
118: }
119:
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: }
133:
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: }
142:
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;
]
|
| 521/5 |
2026/09/02 22:08 |
llm |
patch-reviewer |
0m
Model:
Tokens:
input: 31411
output: 29
thoughts: 52
Thoughts: Okay, let's dive into this. I need to refresh my understanding of how the configuration system handles items, groups, and their lifecycle. I'm thinking specifically about `struct config_item` and `struct config_group`, and how they interact, especially with those default groups. I want to trace how these things are created, accessed, and, crucially, destroyed.
**Deep Dive into Config Item Management**
My primary focus here is the core structure: `struct config_item`. I'll want to trace its lifecycle β from initialization to destruction. I'm going to look closely at functions like `config_item_get` and `config_item_put`. These are the key functions. I want to understand the reference counting and memory management involved. What are the implications of getting and putting these items? Are there any subtleties to be aware of regarding thread safety or potential race conditions, especially when dealing with groups? I suspect the interaction between these calls will illuminate the broader management strategy. I need to understand how the system keeps track of these config items and their associations. Let's see how this all really ties together!
|
| 522/5 |
2026/09/02 22:08 |
tool |
grepper |
0m
Args: map[Expression:config_item_get PathPrefix:fs/configfs]
Results: map[Output:fs/configfs/configfs_internal.h=120=static inline struct config_item *configfs_get_config_item(struct dentry *dentry)
--
fs/configfs/configfs_internal.h-126- struct configfs_dirent * sd = dentry->d_fsdata;
fs/configfs/configfs_internal.h:127: item = config_item_get(sd->s_element);
fs/configfs/configfs_internal.h-128- }
--
fs/configfs/dir.c=783=static void link_obj(struct config_item *parent_item, struct config_item *item)
--
fs/configfs/dir.c-801- */
fs/configfs/dir.c:802: config_item_get(item);
fs/configfs/dir.c-803-}
--
fs/configfs/item.c=99=EXPORT_SYMBOL(config_group_init_type_name);
fs/configfs/item.c-100-
fs/configfs/item.c:101:struct config_item *config_item_get(struct config_item *item)
fs/configfs/item.c-102-{
--
fs/configfs/item.c-106-}
fs/configfs/item.c:107:EXPORT_SYMBOL(config_item_get);
fs/configfs/item.c-108-
fs/configfs/item.c:109:struct config_item *config_item_get_unless_zero(struct config_item *item)
fs/configfs/item.c-110-{
--
fs/configfs/item.c-114-}
fs/configfs/item.c:115:EXPORT_SYMBOL(config_item_get_unless_zero);
fs/configfs/item.c-116-
--
fs/configfs/item.c=174=struct config_item *config_group_find_item(struct config_group *group,
--
fs/configfs/item.c-183- !strcmp(config_item_name(item), name)) {
fs/configfs/item.c:184: ret = config_item_get(item);
fs/configfs/item.c-185- break;
]
|
| 523/5 |
2026/09/02 22:08 |
llm |
patch-reviewer |
0m
Model:
Tokens:
input: 32321
output: 49
thoughts: 50
Thoughts: Okay, here's my take on those thoughts, framed as my own:
**Delving into the NVMe-oF ConfigFS Implementation**
Right, let's dissect the NVMe-oF implementation and understand how it leverages configfs, specifically the nuances of those default groups. I need to get a clear picture of how these subsystems are established and, critically, how they are managed when they're no longer needed. The creation and, more importantly, the teardown.
My first port of call will be the `drivers/nvme/target/` directory. I'm going to fire up grep and zero in on the key functions responsible for subsystem and port group management. I'll search for occurrences of `subsys_group` and `nvmet_ports_make` to illuminate the code paths responsible. Hopefully, this will lead me to the precise mechanisms for group creation, destruction, and any associated resource handling. Let's see what secrets this codebase holds.
|
| 524/5 |
2026/09/02 22:08 |
tool |
grepper |
0m
Args: map[Expression:subsys_group|nvmet_ports_make|nvmet_port_subsys_allow_link PathPrefix:drivers/nvme/target]
Results: map[Output:drivers/nvme/target/configfs.c=1050=static void nvmet_add_passthru_group(struct nvmet_subsys *subsys)
--
drivers/nvme/target/configfs.c-1055-
drivers/nvme/target/configfs.c:1056:static int nvmet_port_subsys_allow_link(struct config_item *parent,
drivers/nvme/target/configfs.c-1057- struct config_item *target)
--
drivers/nvme/target/configfs.c=1125=static struct configfs_item_operations nvmet_port_subsys_item_ops = {
drivers/nvme/target/configfs.c:1126: .allow_link = nvmet_port_subsys_allow_link,
drivers/nvme/target/configfs.c-1127- .drop_link = nvmet_port_subsys_drop_link,
--
drivers/nvme/target/configfs.c=2035=static const struct config_item_type nvmet_port_type = {
--
drivers/nvme/target/configfs.c-2040-
drivers/nvme/target/configfs.c:2041:static struct config_group *nvmet_ports_make(struct config_group *group,
drivers/nvme/target/configfs.c-2042- const char *name)
--
drivers/nvme/target/configfs.c-2084-
drivers/nvme/target/configfs.c:2085: config_group_init_type_name(&port->subsys_group,
drivers/nvme/target/configfs.c-2086- "subsystems", &nvmet_port_subsys_type);
drivers/nvme/target/configfs.c:2087: configfs_add_default_group(&port->subsys_group, &port->group);
drivers/nvme/target/configfs.c-2088-
--
drivers/nvme/target/configfs.c=2108=static struct configfs_group_operations nvmet_ports_group_ops = {
drivers/nvme/target/configfs.c:2109: .make_group = nvmet_ports_make,
drivers/nvme/target/configfs.c-2110-};
--
drivers/nvme/target/nvmet.h=203=struct nvmet_port {
--
drivers/nvme/target/nvmet.h-206- struct config_group group;
drivers/nvme/target/nvmet.h:207: struct config_group subsys_group;
drivers/nvme/target/nvmet.h-208- struct list_head subsystems;
]
|
| 525/5 |
2026/09/02 22:08 |
llm |
patch-reviewer |
0m
Model:
Tokens:
input: 33340
output: 35
thoughts: 41
Thoughts: Here's my summary, as though I'm thinking through this process:
**Diving into the ConfigFS Item Initialization**
Alright, let's get down to brass tacks. I'm going to dig into the `fs/configfs/item.c` file and understand how the initialization magic happens for configfs items, specifically focusing on `config_group_init_type_name` and `configfs_add_default_group`. My goal is to grasp the core mechanics of how these structures are set up, which, in turn, impacts how our configfs entries are presented to userspace. I'm keen to understand any subtle nuances here, and how default groups are handled. Understanding this level of detail is key for any further customization that may be needed.
|
| 526/5 |
2026/09/02 22:08 |
tool |
read-file |
0m
Args: map[File:fs/configfs/item.c FirstLine:1 LineCount:100]
Results: map[Contents: 1: // SPDX-License-Identifier: GPL-2.0-or-later
2: /*
3: * item.c - library routines for handling generic config items
4: *
5: * Based on kobject:
6: * kobject is Copyright (c) 2002-2003 Patrick Mochel
7: *
8: * configfs Copyright (C) 2005 Oracle. All rights reserved.
9: *
10: * Please see the file Documentation/filesystems/configfs.rst for
11: * critical information about using the config_item interface.
12: */
13:
14: #include <linux/string.h>
15: #include <linux/module.h>
16: #include <linux/stat.h>
17: #include <linux/slab.h>
18:
19: #include <linux/configfs.h>
20:
21:
22: static inline struct config_item *to_item(struct list_head *entry)
23: {
24: return container_of(entry, struct config_item, ci_entry);
25: }
26:
27: /* Evil kernel */
28: static void config_item_release(struct kref *kref);
29:
30: /**
31: * config_item_init - initialize item.
32: * @item: item in question.
33: */
34: static void config_item_init(struct config_item *item)
35: {
36: kref_init(&item->ci_kref);
37: INIT_LIST_HEAD(&item->ci_entry);
38: }
39:
40: /**
41: * config_item_set_name - Set the name of an item
42: * @item: item.
43: * @fmt: The vsnprintf()'s format string.
44: *
45: * If strlen(name) >= CONFIGFS_ITEM_NAME_LEN, then use a
46: * dynamically allocated string that @item->ci_name points to.
47: * Otherwise, use the static @item->ci_namebuf array.
48: */
49: int config_item_set_name(struct config_item *item, const char *fmt, ...)
50: {
51: int limit = CONFIGFS_ITEM_NAME_LEN;
52: int need;
53: va_list args;
54: char *name;
55:
56: /*
57: * First, try the static array
58: */
59: va_start(args, fmt);
60: need = vsnprintf(item->ci_namebuf, limit, fmt, args);
61: va_end(args);
62: if (need < limit)
63: name = item->ci_namebuf;
64: else {
65: va_start(args, fmt);
66: name = kvasprintf(GFP_KERNEL, fmt, args);
67: va_end(args);
68: if (!name)
69: return -ENOMEM;
70: }
71:
72: /* Free the old name, if necessary. */
73: if (item->ci_name && item->ci_name != item->ci_namebuf)
74: kfree(item->ci_name);
75:
76: /* Now, set the new name */
77: item->ci_name = name;
78: return 0;
79: }
80: EXPORT_SYMBOL(config_item_set_name);
81:
82: void config_item_init_type_name(struct config_item *item,
83: const char *name,
84: const struct config_item_type *type)
85: {
86: config_item_set_name(item, "%s", name);
87: item->ci_type = type;
88: config_item_init(item);
89: }
90: EXPORT_SYMBOL(config_item_init_type_name);
91:
92: void config_group_init_type_name(struct config_group *group, const char *name,
93: const struct config_item_type *type)
94: {
95: config_item_set_name(&group->cg_item, "%s", name);
96: group->cg_item.ci_type = type;
97: config_group_init(group);
98: }
99: EXPORT_SYMBOL(config_group_init_type_name);
100:
]
|
| 527/5 |
2026/09/02 22:08 |
llm |
patch-reviewer |
0m
Model:
Tokens:
input: 34936
output: 36
thoughts: 24
Thoughts: Here's a summary of my thought process, as if I were analyzing that code:
**Diving into `fs/configfs/item.c` - Lines 101-180**
Okay, time to get my hands dirty. I need to understand what's happening within lines 101 to 180 of `fs/configfs/item.c`. This part of the code almost certainly deals with a core aspect of ConfigFS item management. Given the file and function names Iβm dealing with the item layer makes this section extremely important. My initial hypothesis is that I'll be looking at the creation, destruction, attribute handling, or possibly locking mechanisms related to individual ConfigFS items within the file system.
I'll be paying close attention to any calls to `kobject` functions, since ConfigFS heavily relies on the kernel's object management system. Itβs also crucial to see how ConfigFS-specific data structures are being used; what's defined, how theyβre linked, and what kind of interactions are taking place within them. Are there any error paths I need to be aware of? Are there potentially subtle race conditions?
I'll also keep my eye out for any interactions with other parts of the ConfigFS subsystemβspecifically, how this item-level code fits into the larger picture of directory and file creation, attribute manipulation, and traversal. Context is always key. Letβs see what gems this section holds.
|
| 528/5 |
2026/09/02 22:08 |
tool |
read-file |
0m
Args: map[File:fs/configfs/item.c FirstLine:101 LineCount:80]
Results: map[Contents: 101: struct config_item *config_item_get(struct config_item *item)
102: {
103: if (item)
104: kref_get(&item->ci_kref);
105: return item;
106: }
107: EXPORT_SYMBOL(config_item_get);
108:
109: struct config_item *config_item_get_unless_zero(struct config_item *item)
110: {
111: if (item && kref_get_unless_zero(&item->ci_kref))
112: return item;
113: return NULL;
114: }
115: EXPORT_SYMBOL(config_item_get_unless_zero);
116:
117: static void config_item_cleanup(struct config_item *item)
118: {
119: const struct config_item_type *t = item->ci_type;
120: struct config_group *s = item->ci_group;
121: struct config_item *parent = item->ci_parent;
122:
123: pr_debug("config_item %s: cleaning up\n", config_item_name(item));
124: if (item->ci_name != item->ci_namebuf)
125: kfree(item->ci_name);
126: item->ci_name = NULL;
127: if (t && t->ct_item_ops && t->ct_item_ops->release)
128: t->ct_item_ops->release(item);
129: if (s)
130: config_group_put(s);
131: if (parent)
132: config_item_put(parent);
133: }
134:
135: static void config_item_release(struct kref *kref)
136: {
137: config_item_cleanup(container_of(kref, struct config_item, ci_kref));
138: }
139:
140: /**
141: * config_item_put - decrement refcount for item.
142: * @item: item.
143: *
144: * Decrement the refcount, and if 0, call config_item_cleanup().
145: */
146: void config_item_put(struct config_item *item)
147: {
148: if (item)
149: kref_put(&item->ci_kref, config_item_release);
150: }
151: EXPORT_SYMBOL(config_item_put);
152:
153: /**
154: * config_group_init - initialize a group for use
155: * @group: config_group
156: */
157: void config_group_init(struct config_group *group)
158: {
159: config_item_init(&group->cg_item);
160: INIT_LIST_HEAD(&group->cg_children);
161: INIT_LIST_HEAD(&group->default_groups);
162: }
163: EXPORT_SYMBOL(config_group_init);
164:
165: /**
166: * config_group_find_item - search for item in group.
167: * @group: group we're looking in.
168: * @name: item's name.
169: *
170: * Iterate over @group->cg_list, looking for a matching config_item.
171: * If matching item is found take a reference and return the item.
172: * Caller must have locked group via @group->cg_subsys->su_mtx.
173: */
174: struct config_item *config_group_find_item(struct config_group *group,
175: const char *name)
176: {
177: struct list_head *entry;
178: struct config_item *ret = NULL;
179:
180: list_for_each(entry, &group->cg_children) {
]
|
| 529/5 |
2026/09/02 22:08 |
llm |
patch-reviewer |
0m
Model:
Tokens:
input: 36540
output: 39
thoughts: 30
Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience:
**Examining Group Linking Operations**
Alright, let's dive in. My focus right now is on understanding the nuances of these group linking functions, specifically how `link_group`/`link_obj` and `unlink_group`/`unlink_obj` behave. I need to make sure I understand the low-level mechanics of how the object/group relationships are managed, ideally with an eye to how it will work with my current system. I'm looking at things from the perspective of potential performance impacts, data consistency, and edge-case behaviors. It's time to map out the call flow and internal data structures.
|
| 530/5 |
2026/09/02 22:08 |
tool |
grepper |
0m
Args: map[Expression:link_group|unlink_group|link_obj|unlink_obj PathPrefix:fs/configfs]
Results: map[Output:fs/configfs/dir.c=35=DEFINE_SPINLOCK(configfs_dirent_lock);
--
fs/configfs/dir.c-37-/*
fs/configfs/dir.c:38: * All of link_obj/unlink_obj/link_group/unlink_group require that
fs/configfs/dir.c-39- * subsys->su_mutex is held.
--
fs/configfs/dir.c=757=EXPORT_SYMBOL(configfs_remove_default_groups);
--
fs/configfs/dir.c-759-/*
fs/configfs/dir.c:760: * All of link_obj/unlink_obj/link_group/unlink_group require that
fs/configfs/dir.c-761- * subsys->su_mutex is held.
--
fs/configfs/dir.c-763-
fs/configfs/dir.c:764:static void unlink_obj(struct config_item *item)
fs/configfs/dir.c-765-{
--
fs/configfs/dir.c-782-
fs/configfs/dir.c:783:static void link_obj(struct config_item *parent_item, struct config_item *item)
fs/configfs/dir.c-784-{
--
fs/configfs/dir.c-804-
fs/configfs/dir.c:805:static void unlink_group(struct config_group *group)
fs/configfs/dir.c-806-{
--
fs/configfs/dir.c-809- list_for_each_entry(new_group, &group->default_groups, group_entry)
fs/configfs/dir.c:810: unlink_group(new_group);
fs/configfs/dir.c-811-
fs/configfs/dir.c-812- group->cg_subsys = NULL;
fs/configfs/dir.c:813: unlink_obj(&group->cg_item);
fs/configfs/dir.c-814-}
fs/configfs/dir.c-815-
fs/configfs/dir.c:816:static void link_group(struct config_group *parent_group, struct config_group *group)
fs/configfs/dir.c-817-{
--
fs/configfs/dir.c-820-
fs/configfs/dir.c:821: link_obj(&parent_group->cg_item, &group->cg_item);
fs/configfs/dir.c-822-
--
fs/configfs/dir.c-831- list_for_each_entry(new_group, &group->default_groups, group_entry)
fs/configfs/dir.c:832: link_group(group, new_group);
fs/configfs/dir.c-833-}
--
fs/configfs/dir.c=1289=static struct dentry *configfs_mkdir(struct mnt_idmap *idmap, struct inode *dir,
--
fs/configfs/dir.c-1363- if (!IS_ERR(group)) {
fs/configfs/dir.c:1364: link_group(to_config_group(parent_item), group);
fs/configfs/dir.c-1365- item = &group->cg_item;
--
fs/configfs/dir.c-1372- if (!IS_ERR(item))
fs/configfs/dir.c:1373: link_obj(parent_item, item);
fs/configfs/dir.c-1374- else
--
fs/configfs/dir.c-1380- /*
fs/configfs/dir.c:1381: * If ret != 0, then link_obj() was never called.
fs/configfs/dir.c-1382- * There are no extra references to clean up.
--
fs/configfs/dir.c-1387- /*
fs/configfs/dir.c:1388: * link_obj() has been called (via link_group() for groups).
fs/configfs/dir.c-1389- * From here on out, errors must clean that up.
--
fs/configfs/dir.c-1439- if (group)
fs/configfs/dir.c:1440: unlink_group(group);
fs/configfs/dir.c-1441- else
fs/configfs/dir.c:1442: unlink_obj(item);
fs/configfs/dir.c-1443- client_drop_item(parent_item, item);
--
fs/configfs/dir.c-1456- /*
fs/configfs/dir.c:1457: * link_obj()/link_group() took a reference from child->parent,
fs/configfs/dir.c-1458- * so the parent is safely pinned. We can drop our working
--
fs/configfs/dir.c=1469=static int configfs_rmdir(struct inode *dir, struct dentry *dentry)
--
fs/configfs/dir.c-1557- client_disconnect_notify(parent_item, item);
fs/configfs/dir.c:1558: unlink_group(to_config_group(item));
fs/configfs/dir.c-1559- } else {
--
fs/configfs/dir.c-1563- client_disconnect_notify(parent_item, item);
fs/configfs/dir.c:1564: unlink_obj(item);
fs/configfs/dir.c-1565- }
--
fs/configfs/dir.c=1751=int configfs_register_group(struct config_group *parent_group,
--
fs/configfs/dir.c-1763- mutex_lock(&subsys->su_mutex);
fs/configfs/dir.c:1764: link_group(parent_group, group);
fs/configfs/dir.c-1765- mutex_unlock(&subsys->su_mutex);
--
fs/configfs/dir.c-1782- mutex_lock(&subsys->su_mutex);
fs/configfs/dir.c:1783: unlink_group(group);
fs/configfs/dir.c-1784- mutex_unlock(&subsys->su_mutex);
--
fs/configfs/dir.c=1796=void configfs_unregister_group(struct config_group *group)
--
fs/configfs/dir.c-1822- mutex_lock(&subsys->su_mutex);
fs/configfs/dir.c:1823: unlink_group(group);
fs/configfs/dir.c-1824- mutex_unlock(&subsys->su_mutex);
--
fs/configfs/dir.c=1872=int configfs_register_subsystem(struct configfs_subsystem *subsys)
--
fs/configfs/dir.c-1895- mutex_lock(&configfs_subsystem_mutex);
fs/configfs/dir.c:1896: link_group(to_config_group(sd->s_element), group);
fs/configfs/dir.c-1897- mutex_unlock(&configfs_subsystem_mutex);
--
fs/configfs/dir.c-1924- mutex_lock(&configfs_subsystem_mutex);
fs/configfs/dir.c:1925: unlink_group(group);
fs/configfs/dir.c-1926- mutex_unlock(&configfs_subsystem_mutex);
--
fs/configfs/dir.c=1934=void configfs_unregister_subsystem(struct configfs_subsystem *subsys)
--
fs/configfs/dir.c-1973- mutex_lock(&configfs_subsystem_mutex);
fs/configfs/dir.c:1974: unlink_group(group);
fs/configfs/dir.c-1975- mutex_unlock(&configfs_subsystem_mutex);
]
|
| 531/5 |
2026/09/02 22:08 |
llm |
patch-reviewer |
0m
Model:
Tokens:
input: 38856
output: 36
thoughts: 26
Thoughts: Here's my take on those thoughts, tailored for an expert audience:
**Diving into ConfigFS Directory Operations**
Okay, so I'm thinking I need to refresh my memory on the core configfs directory operations. I'm specifically targeting the `link_obj` and `unlink_obj` functions within `fs/configfs/dir.c`. These are fundamental, as they handle the critical tasks of creating and destroying connections (or "links") between the configfs filesystem's object hierarchy. Understanding these is key to grasping how configfs manages its virtual filesystem and how its objects are organized and ultimately removed. I need to refresh my memory, because those routines are complex, and I'd like to understand what they are doing in detail.
|
| 532/5 |
2026/09/02 22:08 |
tool |
read-file |
0m
Args: map[File:fs/configfs/dir.c FirstLine:760 LineCount:55]
Results: map[Contents: 760: * All of link_obj/unlink_obj/link_group/unlink_group require that
761: * subsys->su_mutex is held.
762: */
763:
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: }
782:
783: static void link_obj(struct config_item *parent_item, struct config_item *item)
784: {
785: /*
786: * Parent seems redundant with group, but it makes certain
787: * traversals much nicer.
788: */
789: item->ci_parent = parent_item;
790:
791: /*
792: * We hold a reference on the parent for the child's ci_parent
793: * link.
794: */
795: item->ci_group = config_group_get(to_config_group(parent_item));
796: list_add_tail(&item->ci_entry, &item->ci_group->cg_children);
797:
798: /*
799: * We hold a reference on the child for ci_entry on the parent's
800: * cg_children
801: */
802: config_item_get(item);
803: }
804:
805: static void unlink_group(struct config_group *group)
806: {
807: struct config_group *new_group;
808:
809: list_for_each_entry(new_group, &group->default_groups, group_entry)
810: unlink_group(new_group);
811:
812: group->cg_subsys = NULL;
813: unlink_obj(&group->cg_item);
814: }
]
|
| 533/5 |
2026/09/02 22:08 |
llm |
patch-reviewer |
0m
Model:
Tokens:
input: 39826
output: 35
thoughts: 1231
Thoughts: **Refcounting Issues in `nvmet_port` and `configfs` Interaction**
Okay, let's break this down. I'm looking at how `nvmet_port` objects are created, linked into the `configfs` hierarchy, and then, crucially, how they're *removed*. The core problem, I suspect, lies in how the reference counting is handled between the `port`'s main group and its default "subsystems" group within `configfs`.
Here's the picture: when `nvmet_ports_make` is called, a `nvmet_port` structure is allocated. Inside it, we see:
1. `port->group` is initialized, and its refcount is set to 1.
2. Then, `port->subsys_group` is also initialized with a refcount of 1.
3. The `subsys_group` is added as a *default group* to `port->group`. This is key because it establishes a specific linkage via `link_group`, which is important for how the hierarchy is traversed during creation and destruction.
The `configfs_mkdir` function links this new `port` into the overall tree. This involves calling `link_group`:
1. This call links the `port->group` to its parent.
2. Crucially, it *also* links `port->subsys_group` as a *child* of `port->group`. This happens via `link_obj`. The way that's implemented, inside `link_obj`, `port->subsys_group.cg_item.ci_group` gets assigned to `port->group`, and the refcount of `port->group` is *incremented to 2*. The refcount of `port->subsys_group` is also incremented to 2.
At the end of `configfs_mkdir`, the system drops its own working ref on `port->group`, so `port->group`'s refcount drops to 1. This means, *at this point*, the refcount on the `port->group` is 1, *held by* `port->subsys_group`, as a result of being a child of the group.
Now, consider `configfs_rmdir`. This is the part that worries me. When we remove a `port` (e.g. `ports/1`):
1. A few preparatory steps happen.
2. `configfs_detach_group` is called for the default groups, including the `subsystems` group.
3. `configfs_detach_item` removes the item.
4. Then, `unlink_group` is called on the `port->group` itself, which calls `unlink_group` on `port->subsys_group`
5. `unlink_obj` gets called on both groups, and *this* is where the refcounting falls apart. `unlink_obj` *decrements* the refcount on both `port->subsys_group` (from 2 to 1) and then on `port->group` (from 1 to 0).
6. *Now* the refcount on `port->group` drops to 0! This triggers `config_item_release`, which calls `nvmet_port_release`. This is the kicker: `nvmet_port_release` *kfrees* the entire `port` structure!
The problem is the *order* of releasing these refs. We *free* the whole `port` structure, including `port->subsys_group`, *even though* `port->subsys_group`'s refcount *was* still 1. It was held by something, but it is gone as we call `nvmet_port_release`!
I need to understand *who* or *what* is keeping the refcount of `port->subsys_group` alive in the first place? And who is responsible for releasing this ref?
I'll have to investigate how and when `config_item_put` gets called for `port->subsys_group`. Is it being handled correctly? This looks like a classic use-after-free scenario. I'm betting the reference count on `port->subsys_group` is lost after the port is freed.
This is a serious bug, potentially leading to crashes and data corruption. The refcounting logic between the parent `port` group and its default `subsystems` group is flawed.
|
| 534/5 |
2026/09/02 22:08 |
tool |
grepper |
0m
Args: map[Expression:nvmet_port_subsys_type PathPrefix:drivers/nvme/target]
Results: map[Output:drivers/nvme/target/configfs.c=1125=static struct configfs_item_operations nvmet_port_subsys_item_ops = {
--
drivers/nvme/target/configfs.c-1129-
drivers/nvme/target/configfs.c:1130:static const struct config_item_type nvmet_port_subsys_type = {
drivers/nvme/target/configfs.c-1131- .ct_item_ops = &nvmet_port_subsys_item_ops,
--
drivers/nvme/target/configfs.c=2041=static struct config_group *nvmet_ports_make(struct config_group *group,
--
drivers/nvme/target/configfs.c-2085- config_group_init_type_name(&port->subsys_group,
drivers/nvme/target/configfs.c:2086: "subsystems", &nvmet_port_subsys_type);
drivers/nvme/target/configfs.c-2087- configfs_add_default_group(&port->subsys_group, &port->group);
]
|
| 535/5 |
2026/09/02 22:08 |
llm |
patch-reviewer |
0m
Model:
Tokens:
input: 42368
output: 22
thoughts: 94
Thoughts: **Analyzing the `nvmet_port_subsys_type` Release Method**
Okay, so I'm looking at this `nvmet_port_subsys_type` and I'm realizing something immediately β there's no explicit `.release` method defined for it. That's a red flag, because normally we need to handle the release of resources. Aha! It's clicking. `port->subsys_group` is actually *embedded* directly within the `struct nvmet_port` itself. Because of this embedding, the memory allocated for `port` gets freed when the parent `port->group` is released. Therefore a direct release method isn't needed here.
Alright, before I sign off on this, let's just do a quick sanity check to make sure this pattern is consistently handled. I want to double-check that other configfs users that utilize default groups operate similarly. I'll take a quick look to verify how configfs handles default group management more broadly across the kernel to be certain this implementation is consistent with standard practices.
|
| 536/5 |
2026/09/02 22:08 |
tool |
grepper |
0m
Args: map[Expression:configfs_add_default_group]
Results: map[Output:Documentation/filesystems/configfs.rst=449=A configfs subsystem specifies default groups by adding them using the
Documentation/filesystems/configfs.rst:450:configfs_add_default_group() function to the parent config_group
Documentation/filesystems/configfs.rst-451-structure. Each added group is populated in the configfs tree at the same
--
drivers/block/null_blk/main.c=667=static void nullb_add_fault_config(struct nullb_device *dev)
--
drivers/block/null_blk/main.c-672-
drivers/block/null_blk/main.c:673: configfs_add_default_group(&dev->timeout_config.group, &dev->group);
drivers/block/null_blk/main.c:674: configfs_add_default_group(&dev->requeue_config.group, &dev->group);
drivers/block/null_blk/main.c:675: configfs_add_default_group(&dev->init_hctx_fault_config.group, &dev->group);
drivers/block/null_blk/main.c-676-}
--
drivers/gpu/drm/vkms/vkms_configfs.c=358=static struct config_group *make_plane_group(struct config_group *group,
--
drivers/gpu/drm/vkms/vkms_configfs.c-388- &plane_possible_crtcs_group_type);
drivers/gpu/drm/vkms/vkms_configfs.c:389: configfs_add_default_group(&plane->possible_crtcs_group,
drivers/gpu/drm/vkms/vkms_configfs.c-390- &plane->group);
--
drivers/gpu/drm/vkms/vkms_configfs.c=474=static struct config_group *make_encoder_group(struct config_group *group,
--
drivers/gpu/drm/vkms/vkms_configfs.c-505- &encoder_possible_crtcs_group_type);
drivers/gpu/drm/vkms/vkms_configfs.c:506: configfs_add_default_group(&encoder->possible_crtcs_group,
drivers/gpu/drm/vkms/vkms_configfs.c-507- &encoder->group);
--
drivers/gpu/drm/vkms/vkms_configfs.c=641=static struct config_group *make_connector_group(struct config_group *group,
--
drivers/gpu/drm/vkms/vkms_configfs.c-672- &connector_possible_encoders_group_type);
drivers/gpu/drm/vkms/vkms_configfs.c:673: configfs_add_default_group(&connector->possible_encoders_group,
drivers/gpu/drm/vkms/vkms_configfs.c-674- &connector->group);
--
drivers/gpu/drm/vkms/vkms_configfs.c=763=static struct config_group *make_device_group(struct config_group *group,
--
drivers/gpu/drm/vkms/vkms_configfs.c-787- &plane_group_type);
drivers/gpu/drm/vkms/vkms_configfs.c:788: configfs_add_default_group(&dev->planes_group, &dev->group);
drivers/gpu/drm/vkms/vkms_configfs.c-789-
--
drivers/gpu/drm/vkms/vkms_configfs.c-791- &crtc_group_type);
drivers/gpu/drm/vkms/vkms_configfs.c:792: configfs_add_default_group(&dev->crtcs_group, &dev->group);
drivers/gpu/drm/vkms/vkms_configfs.c-793-
--
drivers/gpu/drm/vkms/vkms_configfs.c-795- &encoder_group_type);
drivers/gpu/drm/vkms/vkms_configfs.c:796: configfs_add_default_group(&dev->encoders_group, &dev->group);
drivers/gpu/drm/vkms/vkms_configfs.c-797-
--
drivers/gpu/drm/vkms/vkms_configfs.c-799- &connector_group_type);
drivers/gpu/drm/vkms/vkms_configfs.c:800: configfs_add_default_group(&dev->connectors_group, &dev->group);
drivers/gpu/drm/vkms/vkms_configfs.c-801-
--
drivers/gpu/drm/xe/xe_configfs.c=1025=static struct config_group *xe_config_make_device_group(struct config_group *group,
--
drivers/gpu/drm/xe/xe_configfs.c-1091- config_group_init_type_name(&dev->sriov, "sriov", &xe_config_sriov_type);
drivers/gpu/drm/xe/xe_configfs.c:1092: configfs_add_default_group(&dev->sriov, &dev->group);
drivers/gpu/drm/xe/xe_configfs.c-1093- }
--
drivers/hwtracing/coresight/coresight-syscfg-configfs.c=178=static int cscfg_add_preset_groups(struct cscfg_fs_config *cfg_view)
--
drivers/hwtracing/coresight/coresight-syscfg-configfs.c-199- &cscfg_config_preset_type);
drivers/hwtracing/coresight/coresight-syscfg-configfs.c:200: configfs_add_default_group(&cfg_fs_preset->group, &cfg_view->group);
drivers/hwtracing/coresight/coresight-syscfg-configfs.c-201- }
--
drivers/hwtracing/coresight/coresight-syscfg-configfs.c=322=static int cscfg_create_params_group_items(struct cscfg_feature_desc *feat_desc,
--
drivers/hwtracing/coresight/coresight-syscfg-configfs.c-338- &cscfg_param_view_type);
drivers/hwtracing/coresight/coresight-syscfg-configfs.c:339: configfs_add_default_group(¶m_item->group, params_group);
drivers/hwtracing/coresight/coresight-syscfg-configfs.c-340- }
--
drivers/hwtracing/coresight/coresight-syscfg-configfs.c=344=static struct config_group *cscfg_create_feature_group(struct cscfg_feature_desc *feat_desc)
--
drivers/hwtracing/coresight/coresight-syscfg-configfs.c-374- config_group_init_type_name(params_group, "params", params_group_type);
drivers/hwtracing/coresight/coresight-syscfg-configfs.c:375: configfs_add_default_group(params_group, &feat_view->group);
drivers/hwtracing/coresight/coresight-syscfg-configfs.c-376- item_err = cscfg_create_params_group_items(feat_desc, params_group);
--
drivers/hwtracing/coresight/coresight-syscfg-configfs.c=451=int cscfg_configfs_init(struct cscfg_manager *cscfg_mgr)
--
drivers/hwtracing/coresight/coresight-syscfg-configfs.c-471- config_group_init(&cscfg_configs_grp);
drivers/hwtracing/coresight/coresight-syscfg-configfs.c:472: configfs_add_default_group(&cscfg_configs_grp, &subsys->su_group);
drivers/hwtracing/coresight/coresight-syscfg-configfs.c-473-
drivers/hwtracing/coresight/coresight-syscfg-configfs.c-474- config_group_init(&cscfg_features_grp);
drivers/hwtracing/coresight/coresight-syscfg-configfs.c:475: configfs_add_default_group(&cscfg_features_grp, &subsys->su_group);
drivers/hwtracing/coresight/coresight-syscfg-configfs.c-476-
--
drivers/infiniband/core/cma_configfs.c=203=static int make_cma_ports(struct cma_dev_group *cma_dev_group,
--
drivers/infiniband/core/cma_configfs.c-230- &cma_port_group_type);
drivers/infiniband/core/cma_configfs.c:231: configfs_add_default_group(&ports[i].group,
drivers/infiniband/core/cma_configfs.c-232- &cma_dev_group->ports_group);
--
drivers/infiniband/core/cma_configfs.c=280=static struct config_group *make_cma_dev(struct config_group *group,
--
drivers/infiniband/core/cma_configfs.c-308- &cma_device_group_type);
drivers/infiniband/core/cma_configfs.c:309: configfs_add_default_group(&cma_dev_group->ports_group,
drivers/infiniband/core/cma_configfs.c-310- &cma_dev_group->device_group);
--
drivers/net/netconsole.c=1712=static void init_target_config_group(struct netconsole_target *nt,
--
drivers/net/netconsole.c-1717- &userdata_type);
drivers/net/netconsole.c:1718: configfs_add_default_group(&nt->userdata_group, &nt->group);
drivers/net/netconsole.c-1719-}
--
drivers/nvme/target/configfs.c=1040=static void nvmet_add_passthru_group(struct nvmet_subsys *subsys)
--
drivers/nvme/target/configfs.c-1043- "passthru", &nvmet_passthru_type);
drivers/nvme/target/configfs.c:1044: configfs_add_default_group(&subsys->passthru_group,
drivers/nvme/target/configfs.c-1045- &subsys->group);
--
drivers/nvme/target/configfs.c=1736=static struct config_group *nvmet_subsys_make(struct config_group *group,
--
drivers/nvme/target/configfs.c-1758- "namespaces", &nvmet_namespaces_type);
drivers/nvme/target/configfs.c:1759: configfs_add_default_group(&subsys->namespaces_group, &subsys->group);
drivers/nvme/target/configfs.c-1760-
--
drivers/nvme/target/configfs.c-1762- "allowed_hosts", &nvmet_allowed_hosts_type);
drivers/nvme/target/configfs.c:1763: configfs_add_default_group(&subsys->allowed_hosts_group,
drivers/nvme/target/configfs.c-1764- &subsys->group);
--
drivers/nvme/target/configfs.c=2041=static struct config_group *nvmet_ports_make(struct config_group *group,
--
drivers/nvme/target/configfs.c-2086- "subsystems", &nvmet_port_subsys_type);
drivers/nvme/target/configfs.c:2087: configfs_add_default_group(&port->subsys_group, &port->group);
drivers/nvme/target/configfs.c-2088-
--
drivers/nvme/target/configfs.c-2090- "referrals", &nvmet_referrals_type);
drivers/nvme/target/configfs.c:2091: configfs_add_default_group(&port->referrals_group, &port->group);
drivers/nvme/target/configfs.c-2092-
--
drivers/nvme/target/configfs.c-2094- "ana_groups", &nvmet_ana_groups_type);
drivers/nvme/target/configfs.c:2095: configfs_add_default_group(&port->ana_groups_group, &port->group);
drivers/nvme/target/configfs.c-2096-
--
drivers/nvme/target/configfs.c-2101- &nvmet_ana_group_type);
drivers/nvme/target/configfs.c:2102: configfs_add_default_group(&port->ana_default_group.group,
drivers/nvme/target/configfs.c-2103- &port->ana_groups_group);
--
drivers/nvme/target/configfs.c=2365=int __init nvmet_init_configfs(void)
--
drivers/nvme/target/configfs.c-2373- "subsystems", &nvmet_subsystems_type);
drivers/nvme/target/configfs.c:2374: configfs_add_default_group(&nvmet_subsystems_group,
drivers/nvme/target/configfs.c-2375- &nvmet_configfs_subsystem.su_group);
--
drivers/nvme/target/configfs.c-2378- "ports", &nvmet_ports_type);
drivers/nvme/target/configfs.c:2379: configfs_add_default_group(&nvmet_ports_group,
drivers/nvme/target/configfs.c-2380- &nvmet_configfs_subsystem.su_group);
--
drivers/nvme/target/configfs.c-2383- "hosts", &nvmet_hosts_type);
drivers/nvme/target/configfs.c:2384: configfs_add_default_group(&nvmet_hosts_group,
drivers/nvme/target/configfs.c-2385- &nvmet_configfs_subsystem.su_group);
--
drivers/pci/endpoint/pci-ep-cfs.c=97=static struct config_group
--
drivers/pci/endpoint/pci-ep-cfs.c-104- &pci_secondary_epc_type);
drivers/pci/endpoint/pci-ep-cfs.c:105: configfs_add_default_group(secondary_epc_group, &epf_group->group);
drivers/pci/endpoint/pci-ep-cfs.c-106-
--
drivers/pci/endpoint/pci-ep-cfs.c=161=static struct config_group
--
drivers/pci/endpoint/pci-ep-cfs.c-167- &pci_primary_epc_type);
drivers/pci/endpoint/pci-ep-cfs.c:168: configfs_add_default_group(primary_epc_group, &epf_group->group);
drivers/pci/endpoint/pci-ep-cfs.c-169-
--
drivers/pci/endpoint/pci-ep-cfs.c=558=static void pci_ep_cfs_add_type_group(struct pci_epf_group *epf_group)
--
drivers/pci/endpoint/pci-ep-cfs.c-572-
drivers/pci/endpoint/pci-ep-cfs.c:573: configfs_add_default_group(group, &epf_group->group);
drivers/pci/endpoint/pci-ep-cfs.c-574-}
--
drivers/target/iscsi/iscsi_target_configfs.c=725=static int lio_target_init_nodeacl(struct se_node_acl *se_nacl,
--
drivers/target/iscsi/iscsi_target_configfs.c-731- "iscsi_sess_stats", &iscsi_stat_sess_cit);
drivers/target/iscsi/iscsi_target_configfs.c:732: configfs_add_default_group(&acl->node_stat_grps.iscsi_sess_stats_group,
drivers/target/iscsi/iscsi_target_configfs.c-733- &se_nacl->acl_fabric_stat_group);
--
drivers/target/iscsi/iscsi_target_configfs.c=1202=static void lio_target_add_wwn_groups(struct se_wwn *wwn)
--
drivers/target/iscsi/iscsi_target_configfs.c-1207- "iscsi_instance", &iscsi_stat_instance_cit);
drivers/target/iscsi/iscsi_target_configfs.c:1208: configfs_add_default_group(&tiqn->tiqn_stat_grps.iscsi_instance_group,
drivers/target/iscsi/iscsi_target_configfs.c-1209- &tiqn->tiqn_wwn.fabric_stat_group);
--
drivers/target/iscsi/iscsi_target_configfs.c-1212- "iscsi_sess_err", &iscsi_stat_sess_err_cit);
drivers/target/iscsi/iscsi_target_configfs.c:1213: configfs_add_default_group(&tiqn->tiqn_stat_grps.iscsi_sess_err_group,
drivers/target/iscsi/iscsi_target_configfs.c-1214- &tiqn->tiqn_wwn.fabric_stat_group);
--
drivers/target/iscsi/iscsi_target_configfs.c-1217- "iscsi_tgt_attr", &iscsi_stat_tgt_attr_cit);
drivers/target/iscsi/iscsi_target_configfs.c:1218: configfs_add_default_group(&tiqn->tiqn_stat_grps.iscsi_tgt_attr_group,
drivers/target/iscsi/iscsi_target_configfs.c-1219- &tiqn->tiqn_wwn.fabric_stat_group);
--
drivers/target/iscsi/iscsi_target_configfs.c-1222- "iscsi_login_stats", &iscsi_stat_login_cit);
drivers/target/iscsi/iscsi_target_configfs.c:1223: configfs_add_default_group(&tiqn->tiqn_stat_grps.iscsi_login_stats_group,
drivers/target/iscsi/iscsi_target_configfs.c-1224- &tiqn->tiqn_wwn.fabric_stat_group);
--
drivers/target/iscsi/iscsi_target_configfs.c-1227- "iscsi_logout_stats", &iscsi_stat_logout_cit);
drivers/target/iscsi/iscsi_target_configfs.c:1228: configfs_add_default_group(&tiqn->tiqn_stat_grps.iscsi_logout_stats_group,
drivers/target/iscsi/iscsi_target_configfs.c-1229- &tiqn->tiqn_wwn.fabric_stat_group);
--
drivers/target/target_core_configfs.c=182=static struct config_group *target_core_register_fabric(
--
drivers/target/target_core_configfs.c-256- &tf->tf_discovery_cit);
drivers/target/target_core_configfs.c:257: configfs_add_default_group(&tf->tf_disc_group, &tf->tf_group);
drivers/target/target_core_configfs.c-258-
--
drivers/target/target_core_configfs.c=3422=static struct config_group *target_core_make_subdev(
--
drivers/target/target_core_configfs.c-3444- &tb->tb_dev_action_cit);
drivers/target/target_core_configfs.c:3445: configfs_add_default_group(&dev->dev_action_group, &dev->dev_group);
drivers/target/target_core_configfs.c-3446-
--
drivers/target/target_core_configfs.c-3448- &tb->tb_dev_attrib_cit);
drivers/target/target_core_configfs.c:3449: configfs_add_default_group(&dev->dev_attrib.da_group, &dev->dev_group);
drivers/target/target_core_configfs.c-3450-
--
drivers/target/target_core_configfs.c-3452- &tb->tb_dev_pr_cit);
drivers/target/target_core_configfs.c:3453: configfs_add_default_group(&dev->dev_pr_group, &dev->dev_group);
drivers/target/target_core_configfs.c-3454-
--
drivers/target/target_core_configfs.c-3456- &tb->tb_dev_wwn_cit);
drivers/target/target_core_configfs.c:3457: configfs_add_default_group(&dev->t10_wwn.t10_wwn_group,
drivers/target/target_core_configfs.c-3458- &dev->dev_group);
--
drivers/target/target_core_configfs.c-3461- "alua", &tb->tb_dev_alua_tg_pt_gps_cit);
drivers/target/target_core_configfs.c:3462: configfs_add_default_group(&dev->t10_alua.alua_tg_pt_gps_group,
drivers/target/target_core_configfs.c-3463- &dev->dev_group);
--
drivers/target/target_core_configfs.c-3466- "statistics", &tb->tb_dev_stat_cit);
drivers/target/target_core_configfs.c:3467: configfs_add_default_group(&dev->dev_stat_grps.stat_group,
drivers/target/target_core_configfs.c-3468- &dev->dev_group);
--
drivers/target/target_core_configfs.c-3479- "default_tg_pt_gp", &target_core_alua_tg_pt_gp_cit);
drivers/target/target_core_configfs.c:3480: configfs_add_default_group(&tg_pt_gp->tg_pt_gp_group,
drivers/target/target_core_configfs.c-3481- &dev->t10_alua.alua_tg_pt_gps_group);
--
drivers/target/target_core_configfs.c=3744=static int __init target_core_init_configfs(void)
--
drivers/target/target_core_configfs.c-3765- &target_core_cit);
drivers/target/target_core_configfs.c:3766: configfs_add_default_group(&target_core_hbagroup, &subsys->su_group);
drivers/target/target_core_configfs.c-3767-
--
drivers/target/target_core_configfs.c-3771- config_group_init_type_name(&alua_group, "alua", &target_core_alua_cit);
drivers/target/target_core_configfs.c:3772: configfs_add_default_group(&alua_group, &target_core_hbagroup);
drivers/target/target_core_configfs.c-3773-
--
drivers/target/target_core_configfs.c-3779- &target_core_alua_lu_gps_cit);
drivers/target/target_core_configfs.c:3780: configfs_add_default_group(&alua_lu_gps_group, &alua_group);
drivers/target/target_core_configfs.c-3781-
--
drivers/target/target_core_configfs.c-3792- &target_core_alua_lu_gp_cit);
drivers/target/target_core_configfs.c:3793: configfs_add_default_group(&lu_gp->lu_gp_group, &alua_lu_gps_group);
drivers/target/target_core_configfs.c-3794-
--
drivers/target/target_core_fabric_configfs.c=265=static struct config_group *target_fabric_make_mappedlun(
--
drivers/target/target_core_fabric_configfs.c-312- "statistics", &tf->tf_tpg_mappedlun_stat_cit);
drivers/target/target_core_fabric_configfs.c:313: configfs_add_default_group(&lacl->ml_stat_grps.stat_group,
drivers/target/target_core_fabric_configfs.c-314- &lacl->se_lun_group);
--
drivers/target/target_core_fabric_configfs.c=373=static struct config_group *target_fabric_make_nodeacl(
--
drivers/target/target_core_fabric_configfs.c-390- &tf->tf_tpg_nacl_attrib_cit);
drivers/target/target_core_fabric_configfs.c:391: configfs_add_default_group(&se_nacl->acl_attrib_group,
drivers/target/target_core_fabric_configfs.c-392- &se_nacl->acl_group);
--
drivers/target/target_core_fabric_configfs.c-395- &tf->tf_tpg_nacl_auth_cit);
drivers/target/target_core_fabric_configfs.c:396: configfs_add_default_group(&se_nacl->acl_auth_group,
drivers/target/target_core_fabric_configfs.c-397- &se_nacl->acl_group);
--
drivers/target/target_core_fabric_configfs.c-400- &tf->tf_tpg_nacl_param_cit);
drivers/target/target_core_fabric_configfs.c:401: configfs_add_default_group(&se_nacl->acl_param_group,
drivers/target/target_core_fabric_configfs.c-402- &se_nacl->acl_group);
--
drivers/target/target_core_fabric_configfs.c-405- "fabric_statistics", &tf->tf_tpg_nacl_stat_cit);
drivers/target/target_core_fabric_configfs.c:406: configfs_add_default_group(&se_nacl->acl_fabric_stat_group,
drivers/target/target_core_fabric_configfs.c-407- &se_nacl->acl_group);
--
drivers/target/target_core_fabric_configfs.c=748=static struct config_group *target_fabric_make_lun(
--
drivers/target/target_core_fabric_configfs.c-776- "statistics", &tf->tf_tpg_port_stat_cit);
drivers/target/target_core_fabric_configfs.c:777: configfs_add_default_group(&lun->port_stat_grps.stat_group,
drivers/target/target_core_fabric_configfs.c-778- &lun->lun_group);
--
drivers/target/target_core_fabric_configfs.c=934=static struct config_group *target_fabric_make_tpg(
--
drivers/target/target_core_fabric_configfs.c-955- &tf->tf_tpg_lun_cit);
drivers/target/target_core_fabric_configfs.c:956: configfs_add_default_group(&se_tpg->tpg_lun_group,
drivers/target/target_core_fabric_configfs.c-957- &se_tpg->tpg_group);
--
drivers/target/target_core_fabric_configfs.c-960- &tf->tf_tpg_np_cit);
drivers/target/target_core_fabric_configfs.c:961: configfs_add_default_group(&se_tpg->tpg_np_group,
drivers/target/target_core_fabric_configfs.c-962- &se_tpg->tpg_group);
--
drivers/target/target_core_fabric_configfs.c-965- &tf->tf_tpg_nacl_cit);
drivers/target/target_core_fabric_configfs.c:966: configfs_add_default_group(&se_tpg->tpg_acl_group,
drivers/target/target_core_fabric_configfs.c-967- &se_tpg->tpg_group);
--
drivers/target/target_core_fabric_configfs.c-970- &tf->tf_tpg_attrib_cit);
drivers/target/target_core_fabric_configfs.c:971: configfs_add_default_group(&se_tpg->tpg_attrib_group,
drivers/target/target_core_fabric_configfs.c-972- &se_tpg->tpg_group);
--
drivers/target/target_core_fabric_configfs.c-975- &tf->tf_tpg_auth_cit);
drivers/target/target_core_fabric_configfs.c:976: configfs_add_default_group(&se_tpg->tpg_auth_group,
drivers/target/target_core_fabric_configfs.c-977- &se_tpg->tpg_group);
--
drivers/target/target_core_fabric_configfs.c-980- &tf->tf_tpg_param_cit);
drivers/target/target_core_fabric_configfs.c:981: configfs_add_default_group(&se_tpg->tpg_param_group,
drivers/target/target_core_fabric_configfs.c-982- &se_tpg->tpg_group);
--
drivers/target/target_core_fabric_configfs.c=1133=static struct config_group *target_fabric_make_wwn(
--
drivers/target/target_core_fabric_configfs.c-1156- &tf->tf_wwn_fabric_stats_cit);
drivers/target/target_core_fabric_configfs.c:1157: configfs_add_default_group(&wwn->fabric_stat_group, &wwn->wwn_group);
drivers/target/target_core_fabric_configfs.c-1158-
--
drivers/target/target_core_fabric_configfs.c-1160- &tf->tf_wwn_param_cit);
drivers/target/target_core_fabric_configfs.c:1161: configfs_add_default_group(&wwn->param_group, &wwn->wwn_group);
drivers/target/target_core_fabric_configfs.c-1162-
--
drivers/target/target_core_stat.c=396=void target_stat_setup_dev_default_groups(struct se_device *dev)
--
drivers/target/target_core_stat.c-399- "scsi_dev", &target_stat_scsi_dev_cit);
drivers/target/target_core_stat.c:400: configfs_add_default_group(&dev->dev_stat_grps.scsi_dev_group,
drivers/target/target_core_stat.c-401- &dev->dev_stat_grps.stat_group);
--
drivers/target/target_core_stat.c-404- "scsi_tgt_dev", &target_stat_scsi_tgt_dev_cit);
drivers/target/target_core_stat.c:405: configfs_add_default_group(&dev->dev_stat_grps.scsi_tgt_dev_group,
drivers/target/target_core_stat.c-406- &dev->dev_stat_grps.stat_group);
--
drivers/target/target_core_stat.c-409- "scsi_lu", &target_stat_scsi_lu_cit);
drivers/target/target_core_stat.c:410: configfs_add_default_group(&dev->dev_stat_grps.scsi_lu_group,
drivers/target/target_core_stat.c-411- &dev->dev_stat_grps.stat_group);
--
drivers/target/target_core_stat.c=803=void target_stat_setup_port_default_groups(struct se_lun *lun)
--
drivers/target/target_core_stat.c-806- "scsi_port", &target_stat_scsi_port_cit);
drivers/target/target_core_stat.c:807: configfs_add_default_group(&lun->port_stat_grps.scsi_port_group,
drivers/target/target_core_stat.c-808- &lun->port_stat_grps.stat_group);
--
drivers/target/target_core_stat.c-811- "scsi_tgt_port", &target_stat_scsi_tgt_port_cit);
drivers/target/target_core_stat.c:812: configfs_add_default_group(&lun->port_stat_grps.scsi_tgt_port_group,
drivers/target/target_core_stat.c-813- &lun->port_stat_grps.stat_group);
--
drivers/target/target_core_stat.c-816- "scsi_transport", &target_stat_scsi_transport_cit);
drivers/target/target_core_stat.c:817: configfs_add_default_group(&lun->port_stat_grps.scsi_transport_group,
drivers/target/target_core_stat.c-818- &lun->port_stat_grps.stat_group);
--
drivers/target/target_core_stat.c=1301=void target_stat_setup_mappedlun_default_groups(struct se_lun_acl *lacl)
--
drivers/target/target_core_stat.c-1304- "scsi_auth_intr", &target_stat_scsi_auth_intr_cit);
drivers/target/target_core_stat.c:1305: configfs_add_default_group(&lacl->ml_stat_grps.scsi_auth_intr_group,
drivers/target/target_core_stat.c-1306- &lacl->ml_stat_grps.stat_group);
--
drivers/target/target_core_stat.c-1309- "scsi_att_intr_port", &target_stat_scsi_att_intr_port_cit);
drivers/target/target_core_stat.c:1310: configfs_add_default_group(&lacl->ml_stat_grps.scsi_att_intr_port_group,
drivers/target/target_core_stat.c-1311- &lacl->ml_stat_grps.stat_group);
--
drivers/usb/gadget/configfs.c=696=static struct config_group *config_desc_make(
--
drivers/usb/gadget/configfs.c-748- &gadget_config_name_strings_type);
drivers/usb/gadget/configfs.c:749: configfs_add_default_group(&cfg->strings_group, &cfg->group);
drivers/usb/gadget/configfs.c-750-
--
drivers/usb/gadget/configfs.c=1525=struct config_group *usb_os_desc_prepare_interf_dir(
--
drivers/usb/gadget/configfs.c-1549- config_group_init_type_name(os_desc_group, "os_desc", os_desc_type);
drivers/usb/gadget/configfs.c:1550: configfs_add_default_group(os_desc_group, parent);
drivers/usb/gadget/configfs.c-1551-
--
drivers/usb/gadget/configfs.c-1563- names[n_interf]);
drivers/usb/gadget/configfs.c:1564: configfs_add_default_group(&d->group, os_desc_group);
drivers/usb/gadget/configfs.c-1565- }
--
drivers/usb/gadget/configfs.c=1988=static struct config_group *gadgets_make(
--
drivers/usb/gadget/configfs.c-2001- &functions_type);
drivers/usb/gadget/configfs.c:2002: configfs_add_default_group(&gi->functions_group, &gi->group);
drivers/usb/gadget/configfs.c-2003-
--
drivers/usb/gadget/configfs.c-2005- &config_desc_type);
drivers/usb/gadget/configfs.c:2006: configfs_add_default_group(&gi->configs_group, &gi->group);
drivers/usb/gadget/configfs.c-2007-
--
drivers/usb/gadget/configfs.c-2009- &gadget_language_strings_type);
drivers/usb/gadget/configfs.c:2010: configfs_add_default_group(&gi->strings_group, &gi->group);
drivers/usb/gadget/configfs.c-2011-
--
drivers/usb/gadget/configfs.c-2013- &os_desc_type);
drivers/usb/gadget/configfs.c:2014: configfs_add_default_group(&gi->os_desc_group, &gi->group);
drivers/usb/gadget/configfs.c-2015-
--
drivers/usb/gadget/configfs.c-2017- &webusb_type);
drivers/usb/gadget/configfs.c:2018: configfs_add_default_group(&gi->webusb_group, &gi->group);
drivers/usb/gadget/configfs.c-2019-
--
drivers/usb/gadget/function/f_mass_storage.c=3493=static struct usb_function_instance *fsg_alloc_inst(void)
--
drivers/usb/gadget/function/f_mass_storage.c-3527- config_group_init_type_name(&opts->lun0.group, "lun.0", &fsg_lun_type);
drivers/usb/gadget/function/f_mass_storage.c:3528: configfs_add_default_group(&opts->lun0.group, &opts->func_inst.group);
drivers/usb/gadget/function/f_mass_storage.c-3529-
--
drivers/usb/gadget/function/f_midi2.c=2649=static struct usb_function_instance *f_midi2_alloc_inst(void)
--
drivers/usb/gadget/function/f_midi2.c-2689- &f_midi2_ep_type);
drivers/usb/gadget/function/f_midi2.c:2690: configfs_add_default_group(&ep_opts->group, &opts->func_inst.group);
drivers/usb/gadget/function/f_midi2.c-2691-
--
drivers/usb/gadget/function/f_midi2.c-2693- &f_midi2_block_type);
drivers/usb/gadget/function/f_midi2.c:2694: configfs_add_default_group(&block_opts->group, &ep_opts->group);
drivers/usb/gadget/function/f_midi2.c-2695-
--
drivers/usb/gadget/function/uvc_configfs.c=156=static int uvcg_config_create_group(struct config_group *parent,
--
drivers/usb/gadget/function/uvc_configfs.c-165- config_group_init_type_name(group, type->name, &type->type);
drivers/usb/gadget/function/uvc_configfs.c:166: configfs_add_default_group(group, parent);
drivers/usb/gadget/function/uvc_configfs.c-167-
--
drivers/usb/gadget/function/uvc_configfs.c=1432=static int uvcg_control_class_create_children(struct config_group *parent)
--
drivers/usb/gadget/function/uvc_configfs.c-1447- &uvcg_control_class_type);
drivers/usb/gadget/function/uvc_configfs.c:1448: configfs_add_default_group(&group->group, parent);
drivers/usb/gadget/function/uvc_configfs.c-1449- }
--
drivers/usb/gadget/function/uvc_configfs.c=3096=static int uvcg_color_matching_create_children(struct config_group *parent)
--
drivers/usb/gadget/function/uvc_configfs.c-3112- &uvcg_color_matching_type);
drivers/usb/gadget/function/uvc_configfs.c:3113: configfs_add_default_group(&color_match->group, parent);
drivers/usb/gadget/function/uvc_configfs.c-3114-
--
drivers/usb/gadget/function/uvc_configfs.c=3548=static int uvcg_streaming_class_create_children(struct config_group *parent)
--
drivers/usb/gadget/function/uvc_configfs.c-3563- &uvcg_streaming_class_type);
drivers/usb/gadget/function/uvc_configfs.c:3564: configfs_add_default_group(&group->group, parent);
drivers/usb/gadget/function/uvc_configfs.c-3565- }
--
fs/dlm/config.c=422=static struct config_group *make_cluster(struct config_group *g,
--
fs/dlm/config.c-442-
fs/dlm/config.c:443: configfs_add_default_group(&sps->ss_group, &cl->group);
fs/dlm/config.c:444: configfs_add_default_group(&cms->cs_group, &cl->group);
fs/dlm/config.c-445-
--
fs/dlm/config.c=478=static struct config_group *make_space(struct config_group *g, const char *name)
--
fs/dlm/config.c-491- config_group_init_type_name(&nds->ns_group, "nodes", &nodes_type);
fs/dlm/config.c:492: configfs_add_default_group(&nds->ns_group, &sp->group);
fs/dlm/config.c-493-
--
fs/ocfs2/cluster/nodemanager.c=691=static struct config_group *o2nm_cluster_group_make_group(struct config_group *group,
--
fs/ocfs2/cluster/nodemanager.c-710- &o2nm_cluster_type);
fs/ocfs2/cluster/nodemanager.c:711: configfs_add_default_group(&ns->ns_group, &cluster->cl_group);
fs/ocfs2/cluster/nodemanager.c-712-
--
fs/ocfs2/cluster/nodemanager.c-714- &o2nm_node_group_type);
fs/ocfs2/cluster/nodemanager.c:715: configfs_add_default_group(o2hb_group, &cluster->cl_group);
fs/ocfs2/cluster/nodemanager.c-716-
--
include/linux/configfs.h=105=extern struct config_item *config_group_find_item(struct config_group *,
--
include/linux/configfs.h-108-
include/linux/configfs.h:109:static inline void configfs_add_default_group(struct config_group *new_group,
include/linux/configfs.h-110- struct config_group *group)
]
|
| 537/5 |
2026/09/02 22:08 |
llm |
patch-reviewer |
0m
Model:
Tokens:
input: 54362
output: 20
thoughts: 48
Thoughts: Okay, let's see. I need to get to the bottom of this race condition. My first instinct is to dig into the existing documentation, so I'm going to pull up `Documentation/filesystems/configfs.rst`. I'm hoping that'll give me a good overview of the filesystem and, more importantly, maybe highlight any known race conditions or previous fixes that are relevant. Beyond the documentation, I'll need to use git to really get a handle on the code's history. Specifically, I'll need to check the git history of configfs or other filesystems for fixes for similar race conditions.
From there, I'll hone my search with `git log`, focusing on patches that deal with symlink issues within configfs. Symlinks are often a source of contention in these types of situations, so I'll prioritize any history that involves them. This strategy should help me to determine if there are any existing solutions that I can leverage.
|
| 538/5 |
2026/09/02 22:08 |
tool |
git-log |
0m
Args: map[PathPrefix:fs/configfs]
Results: map[Output:d53ac61b6120 create_default_group(): pass parent's dentry instead of config_group
ad235a65dfff configfs_attach_group(): drop the unused parent_item argument
d4152c7562a8 configs_attach_item(): drop unused parent_item argument
967c898d9def configfs_create(): lift parent timestamp updates into callers
43b45755e80e kill configfs_drop_dentry()
1387aedb391a configfs: mark pinned dentries persistent
1a29e25799bc configfs: dentry refcount needs to be pinned only once
abe91a2aa9c4 switch configfs_detach_{group,item}() to passing dentry
05a4d3d1b676 configfs_remove_dir(), detach_attrs(): switch to passing dentry
34a62943762c populate_attrs(): move cleanup to the sole caller
]
|
| 539/5 |
2026/09/02 22:08 |
llm |
patch-reviewer |
0m
Model:
Tokens:
input: 54889
output: 25
thoughts: 18
Thoughts: Here's my take on the `fs/configfs/symlink.c` commits, distilled down to what I'm thinking:
**Examining ConfigFS Symlink Implementation**
Alright, let's dive into the guts of `fs/configfs/symlink.c`. I need to get a clear picture of how they're handling symbolic links within the configfs framework. I'm especially interested in understanding the core logic, error handling, and any potential areas for optimization or, frankly, any oddities in the implementation. I'm anticipating the standard challenges associated with symlink management β things like ensuring correct target resolution, preventing loops, and maintaining security context. I'll be looking for how they've addressed those concerns specifically within the configfs paradigm, which has its own unique set of constraints and design goals, as compared to a general purpose filesystem. I'll also be searching for any nuances that jump out related to how they're handling things like user-space interaction and permission enforcement, given the configfs model. I want to build a rock-solid mental model of this part of the codebase. Let's start digging.
|
| 540/5 |
2026/09/02 22:09 |
tool |
git-log |
0m
Args: map[PathPrefix:fs/configfs/symlink.c]
Results: map[Output:1387aedb391a configfs: mark pinned dentries persistent
1b25dea3867a configfs:get_target() - release path as soon as we grab configfs_item reference
4609e1f18e19 fs: port ->permission() to pass mnt_idmap
7a77db95511c fs: port ->symlink() to pass mnt_idmap
fa60ce2cb450 treewide: remove editor modelines and cruft
549c7297717c fs: make helpers idmap mount aware
47291baa8ddf namei: make permission helpers idmapped mount aware
e2f238f7d5a1 configfs: calculate the depth of parent item
e9c03af21cc7 configfs: calculate the symlink target only once
351e5d869e5a configfs: fix a deadlock in configfs_symlink()
]
|
| 541/5 |
2026/09/02 22:09 |
llm |
patch-reviewer |
0m
Model:
Tokens:
input: 55419
output: 27
thoughts: 26
Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience, presented in the first person:
**Analyzing Git Commits with `git show`**
Alright, so the first thing on my mind is diving into the history. Specifically, I want to take a look at commit `351e5d869e5a`, and I'm probably going to explore a few more. My immediate inclination is to leverage `git show`. It's my go-to for quickly examining the contents of a specific commit: the diff, the author, the committer, the message, the whole shebang. I'm thinking I'll start with that specific commit hash to get a focused view, understand what changes were introduced there, and the context of those changes.
I'll probably use `git show` with some options. Maybe `-p` to see the patch (the diff), and `-w` to see the whitespace changes, especially if I suspect some refactoring or formatting changes might be at play. Depending on the situation, I might also use `-U` to include some context lines around the changes. Sometimes, I'll pipe the output to `less` to examine the commit more easily.
I'm probably looking for a specific clue, an anomaly, a pattern, or perhaps something that ties in with a specific bug report or issue I've been working on. Perhaps I'm trying to identify a regression, understand the rationale behind a particular design decision, or simply brush up on an old piece of code. Whatever the reason, `git show` will be my primary tool in this particular investigation.
|
| 542/5 |
2026/09/02 22:09 |
tool |
git-show |
0m
Args:
Results: map[Output:commit 351e5d869e5ac10cb40c78b5f2d7dfc816ad4587
Author: Al Viro <viro@zeniv.linux.org.uk>
Date: Sat Aug 3 11:51:18 2019 -0400
configfs: fix a deadlock in configfs_symlink()
Configfs abuses symlink(2). Unlike the normal filesystems, it
wants the target resolved at symlink(2) time, like link(2) would've
done. The problem is that ->symlink() is called with the parent
directory locked exclusive, so resolving the target inside the
->symlink() is easily deadlocked.
Short of really ugly games in sys_symlink() itself, all we can
do is to unlock the parent before resolving the target and
relock it after. However, that invalidates the checks done
by the caller of ->symlink(), so we have to
* check that dentry is still where it used to be
(it couldn't have been moved, but it could've been unhashed)
* recheck that it's still negative (somebody else
might've successfully created a symlink with the same name
while we were looking the target up)
* recheck the permissions on the parent directory.
Cc: stable@vger.kernel.org
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
Signed-off-by: Christoph Hellwig <hch@lst.de>
diff --git a/fs/configfs/symlink.c b/fs/configfs/symlink.c
index 91eac6c55e07..f3881e4caedd 100644
--- a/fs/configfs/symlink.c
+++ b/fs/configfs/symlink.c
@@ -143,11 +143,42 @@ int configfs_symlink(struct inode *dir, struct dentry *dentry, const char *symna
!type->ct_item_ops->allow_link)
goto out_put;
+ /*
+ * This is really sick. What they wanted was a hybrid of
+ * link(2) and symlink(2) - they wanted the target resolved
+ * at syscall time (as link(2) would've done), be a directory
+ * (which link(2) would've refused to do) *AND* be a deep
+ * fucking magic, making the target busy from rmdir POV.
+ * symlink(2) is nothing of that sort, and the locking it
+ * gets matches the normal symlink(2) semantics. Without
+ * attempts to resolve the target (which might very well
+ * not even exist yet) done prior to locking the parent
+ * directory. This perversion, OTOH, needs to resolve
+ * the target, which would lead to obvious deadlocks if
+ * attempted with any directories locked.
+ *
+ * Unfortunately, that garbage is userland ABI and we should've
+ * said "no" back in 2005. Too late now, so we get to
+ * play very ugly games with locking.
+ *
+ * Try *ANYTHING* of that sort in new code, and you will
+ * really regret it. Just ask yourself - what could a BOFH
+ * do to me and do I want to find it out first-hand?
+ *
+ * AV, a thoroughly annoyed bastard.
+ */
+ inode_unlock(dir);
ret = get_target(symname, &path, &target_item, dentry->d_sb);
+ inode_lock(dir);
if (ret)
goto out_put;
- ret = type->ct_item_ops->allow_link(parent_item, target_item);
+ if (dentry->d_inode || d_unhashed(dentry))
+ ret = -EEXIST;
+ else
+ ret = inode_permission(dir, MAY_WRITE | MAY_EXEC);
+ if (!ret)
+ ret = type->ct_item_ops->allow_link(parent_item, target_item);
if (!ret) {
mutex_lock(&configfs_symlink_mutex);
ret = create_link(parent_item, target_item, dentry);
]
|
| 543/5 |
2026/09/02 22:09 |
llm |
patch-reviewer |
0m
Model:
Tokens:
input: 56928
output: 28
thoughts: 3167
Thoughts: **Deep Dive into the ConfigFS Symlink Vulnerability**
Okay, here's what's running through my head as I dissect this patch. It starts with a gut feeling, triggered by that Al Viro commit: a lock-unlock mismatch, and potential dangling pointers in a filesystem? My senses are on high alert. This looks like a classic race condition, and my immediate goal is to confirm my intuition and understand the patch's attempt to fix it, and whether the fix is robust.
First, I'm reliving the bug scenario. We have `configfs_symlink` unlocking the parent directory prematurely, leading to a possible "use-after-free" if a concurrent `rmdir` operation on the parent happens. Specifically, if the parent is a default group (like those in `ports/1/subsystems`), the `rmdir` can free the underlying data while the symlink operation still holds a pointer to it.
The crux, as I see it, is how the patch tries to protect the `parent_item` (which contains the potentially freed memory) from being accessed after the `inode_unlock` in the critical section. So, this patch attempts to safeguard this memory by tracking the 'root' of the item (i.e. 'ports/1'). This tracking, coupled with some new checks, *should* prevent the UAF if `ports/1` is removed.
Now, let's pick this apart. My focus right now is on this function: `configfs_get_root_item`. I need to ensure it's safe. Specifically, I'm thinking:
* **Safety of `configfs_get_root_item`:** I'm tracing the `d_parent` traversal, keeping in mind that `inode_lock` is held. It's a key detail! The code walks up the directory tree until it finds a non-default group. This is where I start thinking about the race condition. If the parent can be concurrently removed, `d_parent` could become invalid, right? Wait! Default groups CANNOT be removed directly. Only when the parent (e.g. `ports/1`) is removed, which also removes the default groups. And crucially: `configfs_symlink` holds a lock on the parent, so concurrent removal is impossible during the dentry walk.
* **Default Group Scenario:** Since the parent is locked, the walk will stop at the root (ports/1). `configfs_get_config_item` increments a reference to this root (via the spin lock and the `config_item_get`), which ensures its memory won't be freed while it's in use. This looks promising.
* **Non-Default Group Scenario:** What if the symlink is *not* in a default group? In that case, the root becomes the parent. The code simply grabs an additional reference to the parent. This seems safe as well.
* **Root or Subsystem Root:** I need to make sure the root and subsystem scenarios are handled correctly. I'll need to check the definition of `CONFIGFS_ROOT` and `CONFIGFS_USET_DEFAULT` to confirm those cases.
My analysis so far is that the initial part of the patch makes the "UAF" scenario impossible. Now I have to examine the other details of the patch. The key is in preventing any access to freed memory after the lock release.
|
| 544/5 |
2026/09/02 22:09 |
tool |
grepper |
0m
Args: map[Expression:CONFIGFS_ROOT PathPrefix:fs/configfs]
Results: map[Output:fs/configfs/configfs_internal.h=30=struct configfs_dirent {
--
fs/configfs/configfs_internal.h-46-
fs/configfs/configfs_internal.h:47:#define CONFIGFS_ROOT 0x0001
fs/configfs/configfs_internal.h-48-#define CONFIGFS_DIR 0x0002
--
fs/configfs/configfs_internal.h-58-#define CONFIGFS_PINNED \
fs/configfs/configfs_internal.h:59: (CONFIGFS_ROOT | CONFIGFS_DIR | CONFIGFS_ITEM_LINK)
fs/configfs/configfs_internal.h-60-
--
fs/configfs/configfs_internal.h=134=static inline void release_configfs_dirent(struct configfs_dirent * sd)
fs/configfs/configfs_internal.h-135-{
fs/configfs/configfs_internal.h:136: if (!(sd->s_type & CONFIGFS_ROOT)) {
fs/configfs/configfs_internal.h-137- kfree(sd->s_iattr);
--
fs/configfs/dir.c=977=static void configfs_dump_one(struct configfs_dirent *sd, int level)
--
fs/configfs/dir.c-981-#define type_print(_type) if (sd->s_type & _type) pr_info("%*s %s\n", level, " ", #_type)
fs/configfs/dir.c:982: type_print(CONFIGFS_ROOT);
fs/configfs/dir.c-983- type_print(CONFIGFS_DIR);
--
fs/configfs/dir.c=992=static int configfs_dump(struct configfs_dirent *sd, int level)
--
fs/configfs/dir.c-998-
fs/configfs/dir.c:999: if (!(sd->s_type & (CONFIGFS_DIR|CONFIGFS_ROOT)))
fs/configfs/dir.c-1000- return 0;
--
fs/configfs/mount.c=55=static struct configfs_dirent configfs_root = {
--
fs/configfs/mount.c-58- .s_element = &configfs_root_group.cg_item,
fs/configfs/mount.c:59: .s_type = CONFIGFS_ROOT,
fs/configfs/mount.c-60- .s_iattr = NULL,
]
|
| 545/5 |
2026/09/02 22:09 |
llm |
patch-reviewer |
0m
Model:
Tokens:
input: 61503
output: 30
thoughts: 83
Thoughts: **Analyzing the ConfigFS Root**
Okay, so I'm looking at this `configfs_root` structure and trying to understand its setup. The `s_type` field is set to `CONFIGFS_ROOT`, which is expected, but the crucial point is that it *doesn't* have `CONFIGFS_USET_DEFAULT` set. Interesting. Then, `s_element` is pointing to `&configfs_root_group.cg_item`. Alright, that makes sense β it's linking to the root group.
But this brings me to a bigger question: what about the subsystem groups? How are they registered and handled within this framework? I need to refresh my memory on the subsystem registration process. Let's dig into `configfs_register_subsystem` and `configfs_register_group`. Those are the functions I need to review to fully grasp how these pieces fit together. I need to understand how the root interacts with the subsystems.
|
| 546/5 |
2026/09/02 22:09 |
tool |
grepper |
0m
Args: map[Expression:CONFIGFS_USET_ PathPrefix:fs/configfs]
Results: map[Output:fs/configfs/configfs_internal.h=30=struct configfs_dirent {
--
fs/configfs/configfs_internal.h-51-#define CONFIGFS_ITEM_LINK 0x0020
fs/configfs/configfs_internal.h:52:#define CONFIGFS_USET_DIR 0x0040
fs/configfs/configfs_internal.h:53:#define CONFIGFS_USET_DEFAULT 0x0080
fs/configfs/configfs_internal.h:54:#define CONFIGFS_USET_DROPPING 0x0100
fs/configfs/configfs_internal.h:55:#define CONFIGFS_USET_IN_MKDIR 0x0200
fs/configfs/configfs_internal.h:56:#define CONFIGFS_USET_CREATING 0x0400
fs/configfs/configfs_internal.h-57-#define CONFIGFS_NOT_PINNED (CONFIGFS_ITEM_ATTR | CONFIGFS_ITEM_BIN_ATTR)
--
fs/configfs/dir.c-30- *
fs/configfs/dir.c:31: * Protects setting of CONFIGFS_USET_DROPPING: checking the flag
fs/configfs/dir.c-32- * unlocked is not reliable unless in detach_groups() called from
--
fs/configfs/dir.c=187=static struct configfs_dirent *configfs_new_dirent(struct configfs_dirent *parent_sd,
--
fs/configfs/dir.c-202- spin_lock(&configfs_dirent_lock);
fs/configfs/dir.c:203: if (parent_sd->s_type & CONFIGFS_USET_DROPPING) {
fs/configfs/dir.c-204- spin_unlock(&configfs_dirent_lock);
--
fs/configfs/dir.c=293=static int configfs_create_dir(struct config_item *item, struct dentry *dentry,
--
fs/configfs/dir.c-304- error = configfs_make_dirent(p->d_fsdata, dentry, item, mode,
fs/configfs/dir.c:305: CONFIGFS_DIR | CONFIGFS_USET_CREATING,
fs/configfs/dir.c-306- frag);
--
fs/configfs/dir.c=338=static void configfs_dir_set_ready(struct configfs_dirent *sd)
--
fs/configfs/dir.c-341-
fs/configfs/dir.c:342: sd->s_type &= ~CONFIGFS_USET_CREATING;
fs/configfs/dir.c-343- list_for_each_entry(child_sd, &sd->s_children, s_sibling)
fs/configfs/dir.c:344: if (child_sd->s_type & CONFIGFS_USET_CREATING)
fs/configfs/dir.c-345- configfs_dir_set_ready(child_sd);
--
fs/configfs/dir.c=358=int configfs_dirent_is_ready(struct configfs_dirent *sd)
--
fs/configfs/dir.c-362- spin_lock(&configfs_dirent_lock);
fs/configfs/dir.c:363: ret = !(sd->s_type & CONFIGFS_USET_CREATING);
fs/configfs/dir.c-364- spin_unlock(&configfs_dirent_lock);
--
fs/configfs/dir.c=426=static struct dentry * configfs_lookup(struct inode *dir,
--
fs/configfs/dir.c-496- * attributes and are removed by rmdir(). We recurse, setting
fs/configfs/dir.c:497: * CONFIGFS_USET_DROPPING on all children that are candidates for
fs/configfs/dir.c-498- * default detach.
--
fs/configfs/dir.c=502=static int configfs_detach_prep(struct configfs_dirent *parent_sd, struct dentry **wait)
--
fs/configfs/dir.c-507- /* Mark that we're trying to drop the group */
fs/configfs/dir.c:508: parent_sd->s_type |= CONFIGFS_USET_DROPPING;
fs/configfs/dir.c-509-
--
fs/configfs/dir.c-518- continue;
fs/configfs/dir.c:519: if (sd->s_type & CONFIGFS_USET_DEFAULT) {
fs/configfs/dir.c-520- /* Abort if racing with mkdir() */
fs/configfs/dir.c:521: if (sd->s_type & CONFIGFS_USET_IN_MKDIR) {
fs/configfs/dir.c-522- if (wait)
--
fs/configfs/dir.c-544-/*
fs/configfs/dir.c:545: * Walk the tree, resetting CONFIGFS_USET_DROPPING wherever it was
fs/configfs/dir.c-546- * set.
--
fs/configfs/dir.c=548=static void configfs_detach_rollback(struct configfs_dirent *parent_sd)
--
fs/configfs/dir.c-551-
fs/configfs/dir.c:552: parent_sd->s_type &= ~CONFIGFS_USET_DROPPING;
fs/configfs/dir.c-553-
fs/configfs/dir.c-554- list_for_each_entry(sd, &parent_sd->s_children, s_sibling)
fs/configfs/dir.c:555: if (sd->s_type & CONFIGFS_USET_DEFAULT)
fs/configfs/dir.c-556- configfs_detach_rollback(sd);
--
fs/configfs/dir.c=664=static void detach_groups(struct dentry *dentry)
--
fs/configfs/dir.c-673- next = next_dirent(parent_sd, sd);
fs/configfs/dir.c:674: if (!(sd->s_type & CONFIGFS_USET_DEFAULT))
fs/configfs/dir.c-675- continue;
--
fs/configfs/dir.c=703=static int create_default_group(struct dentry *parent,
--
fs/configfs/dir.c-722- sd = child->d_fsdata;
fs/configfs/dir.c:723: sd->s_type |= CONFIGFS_USET_DEFAULT;
fs/configfs/dir.c-724- } else {
--
fs/configfs/dir.c=891=static int configfs_attach_group(struct config_item *item,
--
fs/configfs/dir.c-900- sd = dentry->d_fsdata;
fs/configfs/dir.c:901: sd->s_type |= CONFIGFS_USET_DIR;
fs/configfs/dir.c-902-
--
fs/configfs/dir.c=977=static void configfs_dump_one(struct configfs_dirent *sd, int level)
--
fs/configfs/dir.c-985- type_print(CONFIGFS_ITEM_LINK);
fs/configfs/dir.c:986: type_print(CONFIGFS_USET_DIR);
fs/configfs/dir.c:987: type_print(CONFIGFS_USET_DEFAULT);
fs/configfs/dir.c:988: type_print(CONFIGFS_USET_DROPPING);
fs/configfs/dir.c-989-#undef type_print
--
fs/configfs/dir.c=992=static int configfs_dump(struct configfs_dirent *sd, int level)
--
fs/configfs/dir.c-1058- * Note: subdirectories in the middle of attachment start with s_type =
fs/configfs/dir.c:1059: * CONFIGFS_DIR|CONFIGFS_USET_CREATING set by create_dir(). When
fs/configfs/dir.c:1060: * CONFIGFS_USET_CREATING is set, we ignore the item. The actual set of
fs/configfs/dir.c-1061- * s_type is in configfs_new_dirent(), which has configfs_dirent_lock.
--
fs/configfs/dir.c=1071=static int configfs_depend_prep(struct configfs_dirent *sd,
--
fs/configfs/dir.c-1081- if ((child_sd->s_type & CONFIGFS_DIR) &&
fs/configfs/dir.c:1082: !(child_sd->s_type & CONFIGFS_USET_DROPPING) &&
fs/configfs/dir.c:1083: !(child_sd->s_type & CONFIGFS_USET_CREATING)) {
fs/configfs/dir.c-1084- ret = configfs_depend_prep(child_sd, target);
--
fs/configfs/dir.c=1289=static struct dentry *configfs_mkdir(struct mnt_idmap *idmap, struct inode *dir,
--
fs/configfs/dir.c-1318-
fs/configfs/dir.c:1319: if (!(sd->s_type & CONFIGFS_USET_DIR)) {
fs/configfs/dir.c-1320- ret = -EPERM;
--
fs/configfs/dir.c-1412- * Make racing rmdir() fail if it did not tag parent with
fs/configfs/dir.c:1413: * CONFIGFS_USET_DROPPING
fs/configfs/dir.c:1414: * Note: if CONFIGFS_USET_DROPPING is already set, attach_group() will
fs/configfs/dir.c-1415- * fail and let rmdir() terminate correctly
--
fs/configfs/dir.c-1418- /* This will make configfs_detach_prep() fail */
fs/configfs/dir.c:1419: sd->s_type |= CONFIGFS_USET_IN_MKDIR;
fs/configfs/dir.c-1420- spin_unlock(&configfs_dirent_lock);
--
fs/configfs/dir.c-1427- spin_lock(&configfs_dirent_lock);
fs/configfs/dir.c:1428: sd->s_type &= ~CONFIGFS_USET_IN_MKDIR;
fs/configfs/dir.c-1429- if (!ret)
--
fs/configfs/dir.c=1469=static int configfs_rmdir(struct inode *dir, struct dentry *dentry)
--
fs/configfs/dir.c-1479- sd = dentry->d_fsdata;
fs/configfs/dir.c:1480: if (sd->s_type & CONFIGFS_USET_DEFAULT)
fs/configfs/dir.c-1481- return -EPERM;
--
fs/configfs/dir.c-1552-
fs/configfs/dir.c:1553: if (sd->s_type & CONFIGFS_USET_DIR) {
fs/configfs/dir.c-1554- configfs_detach_group(dentry);
--
fs/configfs/symlink.c=77=static int create_link(struct config_item *parent_item,
--
fs/configfs/symlink.c-93- spin_lock(&configfs_dirent_lock);
fs/configfs/symlink.c:94: if (target_sd->s_type & CONFIGFS_USET_DROPPING) {
fs/configfs/symlink.c-95- spin_unlock(&configfs_dirent_lock);
]
|
| 547/5 |
2026/09/02 22:09 |
llm |
patch-reviewer |
0m
Model:
Tokens:
input: 64918
output: 27
thoughts: 2174
Thoughts: **Analyzing a Potential Race Condition in configfs**
Okay, so I'm diving into a potential race condition between `configfs_rmdir` and `configfs_symlink`, specifically focusing on how they interact with reference counting and the `frag` structure. The scenario I'm looking at involves a symbolic link operation in a subsystem directory while a directory removal is in progress.
First, let's establish the context: `configfs_symlink` is running for a link within `ports/1/subsystems/symlink_name`. I need to trace the critical path. `configfs_symlink` first gets the `sd`, and the parent and root items of the target group. Both `parent_item` (the `port->subsys_group.cg_item`) and `root_item` (the `port->group.cg_item` after walking up to `ports/1`) have their reference counts incremented. Then, crucially, it unlocks the `ports/1/subsystems` inode.
Next, we have `configfs_rmdir` running concurrently on `ports/1`. `configfs_rmdir` calls `configfs_detach_prep`, which sets `CONFIGFS_USET_DROPPING` on `ports/1` and its default groups, gets the `frag` and locks its semaphore, and sets `frag->frag_dead = true`. `configfs_detach_group` detaches the `subsystems` group, marks the inode as `S_DEAD`, and unlocks the inode. Then `configfs_detach_item` removes dirents and so on. `unlink_group` then proceeds with removing the groups.
The tricky part comes in `unlink_group`. It calls `unlink_obj`, which calls `config_item_put` on `port->subsys_group` and `port->group`. It appears `port->subsys_group` ref count becomes 2 after the first call and then the refcount of `port->group` becomes 1. However, the `parent_item` and `root_item` still hold references! `client_drop_item` is called and `config_item_put` is called on the item, but `port` is still alive!.
I need to meticulously track the reference counts of `port->group` and `port->subsys_group` throughout their lifecycles to be certain. Let's start from the beginning. `nvmet_ports_make` initializes them to 1. `configfs_mkdir` does the attaching, where reference counts are incremented and decremented as the groups are linked and attached. This shows `port->group` is 2, 1 initial and 1 from its parent. `port->subsys_group` is 2, one initial and one from its parent.
Now, let's focus on the `configfs_symlink` operation without `rmdir`. It increases refcounts of `port->subsys_group` and `port->group` and then decrements them after completion.
Now, with the concurrent `rmdir`, let's check it. `configfs_symlink` gets `parent_item` and `root_item`, incrementing the refcounts of `port->subsys_group` and `port->group`, and unlocks the inode. `configfs_rmdir` then steps in, and increases the refcount of the `port->group`. `unlink_group` calls `config_item_put`, and finally decrements the refcounts on the items, but crucially, *doesn't* free `port->group` because it's still held by `root_item`. Then `client_drop_item` and final `config_item_put` calls, which reduces the reference of `port->group` from 2 to 1 and it is not freed.
The key question: When `configfs_symlink` resumes, it re-locks the inode. The `sd->s_frag->frag_dead` flag is checked. Did `frag->frag_dead` become true? Because, in `configfs_rmdir`, the `frag` is set by `configfs_detach_prep`, which is called before the `configfs_detach_group`. The `frag` struct is the same one? I need to check how the `frag` is propagated in `create_default_group` and `populate_groups`.
I need to search for `s_frag` in the code to figure out if the `frag` is shared between `ports/1` and `subsystems` to determine if `frag_dead` is set in this race.
|
| 548/5 |
2026/09/02 22:09 |
tool |
grepper |
0m
Args: map[Expression:s_frag PathPrefix:fs/configfs]
Results: map[Output:fs/configfs/configfs_internal.h-20-
fs/configfs/configfs_internal.h:21:struct configfs_fragment {
fs/configfs/configfs_internal.h-22- atomic_t frag_count;
--
fs/configfs/configfs_internal.h-26-
fs/configfs/configfs_internal.h:27:void put_fragment(struct configfs_fragment *);
fs/configfs/configfs_internal.h:28:struct configfs_fragment *get_fragment(struct configfs_fragment *);
fs/configfs/configfs_internal.h-29-
fs/configfs/configfs_internal.h=30=struct configfs_dirent {
--
fs/configfs/configfs_internal.h-43-#endif
fs/configfs/configfs_internal.h:44: struct configfs_fragment *s_frag;
fs/configfs/configfs_internal.h-45-};
--
fs/configfs/configfs_internal.h=74=extern int configfs_make_dirent(struct configfs_dirent *, struct dentry *,
fs/configfs/configfs_internal.h:75: void *, umode_t, int, struct configfs_fragment *);
fs/configfs/configfs_internal.h-76-extern int configfs_dirent_is_ready(struct configfs_dirent *);
--
fs/configfs/configfs_internal.h=134=static inline void release_configfs_dirent(struct configfs_dirent * sd)
--
fs/configfs/configfs_internal.h-137- kfree(sd->s_iattr);
fs/configfs/configfs_internal.h:138: put_fragment(sd->s_frag);
fs/configfs/configfs_internal.h-139- kmem_cache_free(configfs_dir_cachep, sd);
--
fs/configfs/dir.c=152=configfs_adjust_dir_dirent_depth_after_populate(struct configfs_dirent *sd)
--
fs/configfs/dir.c-157-
fs/configfs/dir.c:158:static struct configfs_fragment *new_fragment(void)
fs/configfs/dir.c-159-{
fs/configfs/dir.c:160: struct configfs_fragment *p;
fs/configfs/dir.c-161-
fs/configfs/dir.c:162: p = kmalloc_obj(struct configfs_fragment);
fs/configfs/dir.c-163- if (p) {
--
fs/configfs/dir.c-170-
fs/configfs/dir.c:171:void put_fragment(struct configfs_fragment *frag)
fs/configfs/dir.c-172-{
--
fs/configfs/dir.c-176-
fs/configfs/dir.c:177:struct configfs_fragment *get_fragment(struct configfs_fragment *frag)
fs/configfs/dir.c-178-{
--
fs/configfs/dir.c=187=static struct configfs_dirent *configfs_new_dirent(struct configfs_dirent *parent_sd,
fs/configfs/dir.c-188- void *element, int type,
fs/configfs/dir.c:189: struct configfs_fragment *frag)
fs/configfs/dir.c-190-{
--
fs/configfs/dir.c-207- }
fs/configfs/dir.c:208: sd->s_frag = get_fragment(frag);
fs/configfs/dir.c-209-
--
fs/configfs/dir.c=253=int configfs_make_dirent(struct configfs_dirent * parent_sd,
fs/configfs/dir.c-254- struct dentry * dentry, void * element,
fs/configfs/dir.c:255: umode_t mode, int type, struct configfs_fragment *frag)
fs/configfs/dir.c-256-{
--
fs/configfs/dir.c=293=static int configfs_create_dir(struct config_item *item, struct dentry *dentry,
fs/configfs/dir.c:294: struct configfs_fragment *frag)
fs/configfs/dir.c-295-{
--
fs/configfs/dir.c=369=int configfs_create_link(struct configfs_dirent *target, struct dentry *parent,
--
fs/configfs/dir.c-378- err = configfs_make_dirent(p, dentry, target, mode, CONFIGFS_ITEM_LINK,
fs/configfs/dir.c:379: p->s_frag);
fs/configfs/dir.c-380- if (err)
--
fs/configfs/dir.c=659=static int configfs_attach_group(struct config_item *item,
fs/configfs/dir.c-660- struct dentry *dentry,
fs/configfs/dir.c:661: struct configfs_fragment *frag);
fs/configfs/dir.c-662-static void configfs_detach_group(struct dentry *dentry);
--
fs/configfs/dir.c=703=static int create_default_group(struct dentry *parent,
fs/configfs/dir.c-704- struct config_group *group,
fs/configfs/dir.c:705: struct configfs_fragment *frag)
fs/configfs/dir.c-706-{
--
fs/configfs/dir.c=734=static int populate_groups(struct config_group *group,
fs/configfs/dir.c:735: struct configfs_fragment *frag)
fs/configfs/dir.c-736-{
--
fs/configfs/dir.c=857=static int configfs_attach_item(struct config_item *item,
fs/configfs/dir.c-858- struct dentry *dentry,
fs/configfs/dir.c:859: struct configfs_fragment *frag)
fs/configfs/dir.c-860-{
--
fs/configfs/dir.c=891=static int configfs_attach_group(struct config_item *item,
fs/configfs/dir.c-892- struct dentry *dentry,
fs/configfs/dir.c:893: struct configfs_fragment *frag)
fs/configfs/dir.c-894-{
--
fs/configfs/dir.c=1289=static struct dentry *configfs_mkdir(struct mnt_idmap *idmap, struct inode *dir,
--
fs/configfs/dir.c-1300- struct module *subsys_owner = NULL, *new_item_owner = NULL;
fs/configfs/dir.c:1301: struct configfs_fragment *frag;
fs/configfs/dir.c-1302- struct name_snapshot n;
--
fs/configfs/dir.c=1469=static int configfs_rmdir(struct inode *dir, struct dentry *dentry)
--
fs/configfs/dir.c-1474- struct configfs_dirent *sd;
fs/configfs/dir.c:1475: struct configfs_fragment *frag;
fs/configfs/dir.c-1476- struct module *subsys_owner = NULL, *dead_item_owner = NULL;
--
fs/configfs/dir.c-1532-
fs/configfs/dir.c:1533: frag = sd->s_frag;
fs/configfs/dir.c-1534- if (down_write_killable(&frag->frag_sem)) {
--
fs/configfs/dir.c=1751=int configfs_register_group(struct config_group *parent_group,
--
fs/configfs/dir.c-1755- struct dentry *parent;
fs/configfs/dir.c:1756: struct configfs_fragment *frag;
fs/configfs/dir.c-1757- int ret;
--
fs/configfs/dir.c=1796=void configfs_unregister_group(struct config_group *group)
--
fs/configfs/dir.c-1801- struct configfs_dirent *sd = dentry->d_fsdata;
fs/configfs/dir.c:1802: struct configfs_fragment *frag = sd->s_frag;
fs/configfs/dir.c-1803-
--
fs/configfs/dir.c=1872=int configfs_register_subsystem(struct configfs_subsystem *subsys)
--
fs/configfs/dir.c-1878- struct configfs_dirent *sd;
fs/configfs/dir.c:1879: struct configfs_fragment *frag;
fs/configfs/dir.c-1880-
--
fs/configfs/dir.c=1934=void configfs_unregister_subsystem(struct configfs_subsystem *subsys)
--
fs/configfs/dir.c-1939- struct configfs_dirent *sd = dentry->d_fsdata;
fs/configfs/dir.c:1940: struct configfs_fragment *frag = sd->s_frag;
fs/configfs/dir.c-1941-
--
fs/configfs/file.c=29=struct configfs_buffer {
--
fs/configfs/file.c-48-
fs/configfs/file.c:49:static inline struct configfs_fragment *to_frag(struct file *file)
fs/configfs/file.c-50-{
--
fs/configfs/file.c-52-
fs/configfs/file.c:53: return sd->s_frag;
fs/configfs/file.c-54-}
--
fs/configfs/file.c=56=static int fill_read_buffer(struct file *file, struct configfs_buffer *buffer)
fs/configfs/file.c-57-{
fs/configfs/file.c:58: struct configfs_fragment *frag = to_frag(file);
fs/configfs/file.c-59- ssize_t count = -ENOENT;
--
fs/configfs/file.c=106=static ssize_t configfs_bin_read_iter(struct kiocb *iocb, struct iov_iter *to)
--
fs/configfs/file.c-108- struct file *file = iocb->ki_filp;
fs/configfs/file.c:109: struct configfs_fragment *frag = to_frag(file);
fs/configfs/file.c-110- struct configfs_buffer *buffer = file->private_data;
--
fs/configfs/file.c=200=flush_write_buffer(struct file *file, struct configfs_buffer *buffer, size_t count)
fs/configfs/file.c-201-{
fs/configfs/file.c:202: struct configfs_fragment *frag = to_frag(file);
fs/configfs/file.c-203- int res = -ENOENT;
--
fs/configfs/file.c=290=static int __configfs_open_file(struct inode *inode, struct file *file, int type)
--
fs/configfs/file.c-292- struct dentry *dentry = file->f_path.dentry;
fs/configfs/file.c:293: struct configfs_fragment *frag = to_frag(file);
fs/configfs/file.c-294- struct configfs_attribute *attr;
--
fs/configfs/file.c=400=static int configfs_release_bin_file(struct inode *inode, struct file *file)
--
fs/configfs/file.c-404- if (buffer->write_in_progress) {
fs/configfs/file.c:405: struct configfs_fragment *frag = to_frag(file);
fs/configfs/file.c-406-
--
fs/configfs/file.c=446=int configfs_create_file(struct config_item * item, const struct configfs_attribute * attr)
--
fs/configfs/file.c-454- error = configfs_make_dirent(parent_sd, NULL, (void *) attr, mode,
fs/configfs/file.c:455: CONFIGFS_ITEM_ATTR, parent_sd->s_frag);
fs/configfs/file.c-456- inode_unlock(d_inode(dir));
--
fs/configfs/file.c=467=int configfs_create_bin_file(struct config_item *item,
--
fs/configfs/file.c-476- error = configfs_make_dirent(parent_sd, NULL, (void *) bin_attr, mode,
fs/configfs/file.c:477: CONFIGFS_ITEM_BIN_ATTR, parent_sd->s_frag);
fs/configfs/file.c-478- inode_unlock(dir->d_inode);
]
|
| 549/5 |
2026/09/02 22:09 |
llm |
patch-reviewer |
0m
Model:
Tokens:
input: 71551
output: 28
thoughts: 667
Thoughts: **Analyzing a Potential Race Condition in ConfigFS**
Okay, let's break this down. I see a potential issue here in ConfigFS, specifically around directory removals and symlink creation. Let's trace this through:
Initially, I'm looking at `configfs_mkdir`, and I see a `frag` gets allocated: `frag = new_fragment();`. Then, it's attached to a group: `configfs_attach_group(item, dentry, frag)`. Inside, the `populate_groups` function seems to pass this same `frag` to `create_default_group`. So, *all* default groups created under the same directory are sharing the exact same `frag`! This `frag`, importantly, is refcounted through `sd->s_frag`. As long as the `configfs_dirent` (`sd`) exists, the memory pointed to by `sd->s_frag` should be valid.
Now, if `configfs_rmdir` runs on a directory like `ports/1`, that shared `frag` has its `frag_dead` flag set to true: `frag->frag_dead = true;`. So, this means any "child" default groups under that same directory will have a `dentry->d_parent->d_fsdata->s_frag->frag_dead` that is now also true.
But hold on... is this actually a problem? I need to verify if `frag_dead` is even checked. I see a check:
```c
if (!ret) {
if (sd->s_frag->frag_dead)
ret = -ENOENT;
else
ret = type->ct_item_ops->allow_link(parent_item, target_item);
}
```
Okay, so the `frag_dead` flag is checked *before* calling `allow_link`! I feel like I'm on to something here.
Now, looking at `fs/configfs/symlink.c` and how the `sd` is initialized in `configfs_symlink`: the interesting line is `sd = dentry->d_parent->d_fsdata;`.
So, here's the critical question: is `sd` (the `configfs_dirent`) valid across an `inode_unlock(dir)` and `inode_lock(dir)` sequence? In other words, can the `dentry->d_parent->d_fsdata` *change* in between the unlock and relock? No, it shouldn't. `dentry->d_parent` is the dentry of `dir`, and the `dentry->d_fsdata` is the `configfs_dirent` of that directory.
Can the parent's `configfs_dirent` be freed while we hold a reference to the `dentry` or `dir` or `root_item`?
So, how does the `configfs_dirent` actually get freed? In `configfs_rmdir`, `configfs_remove_dir(dentry)` calls `configfs_remove_dirent(d)`, which in turn calls `configfs_put(sd)`.
Now, I'm going to have to check the `configfs_put` and `release_configfs_dirent` functions to fully understand how this `configfs_dirent` is freed. I need to know when that happens to make sure that shared `frag` doesn't lead to a use-after-free scenario. This is starting to look like a possible race condition.
|
| 550/5 |
2026/09/02 22:09 |
tool |
grepper |
0m
Args: map[Expression:configfs_put PathPrefix:fs/configfs]
Results: map[Output:fs/configfs/configfs_internal.h=143=static inline struct configfs_dirent * configfs_get(struct configfs_dirent * sd)
--
fs/configfs/configfs_internal.h-151-
fs/configfs/configfs_internal.h:152:static inline void configfs_put(struct configfs_dirent * sd)
fs/configfs/configfs_internal.h-153-{
--
fs/configfs/dir.c=45=static void configfs_d_iput(struct dentry * dentry,
--
fs/configfs/dir.c-62- spin_unlock(&configfs_dirent_lock);
fs/configfs/dir.c:63: configfs_put(sd);
fs/configfs/dir.c-64- }
--
fs/configfs/dir.c=271=static void configfs_remove_dirent(struct dentry *dentry)
--
fs/configfs/dir.c-279- spin_unlock(&configfs_dirent_lock);
fs/configfs/dir.c:280: configfs_put(sd);
fs/configfs/dir.c-281-}
--
fs/configfs/dir.c=293=static int configfs_create_dir(struct config_item *item, struct dentry *dentry,
--
fs/configfs/dir.c-325-out_remove:
fs/configfs/dir.c:326: configfs_put(dentry->d_fsdata);
fs/configfs/dir.c-327- configfs_remove_dirent(dentry);
--
fs/configfs/dir.c=369=int configfs_create_link(struct configfs_dirent *target, struct dentry *parent,
--
fs/configfs/dir.c-393-out_remove:
fs/configfs/dir.c:394: configfs_put(dentry->d_fsdata);
fs/configfs/dir.c-395- configfs_remove_dirent(dentry);
--
fs/configfs/dir.c=426=static struct dentry * configfs_lookup(struct inode *dir,
--
fs/configfs/dir.c-475- spin_unlock(&configfs_dirent_lock);
fs/configfs/dir.c:476: configfs_put(sd);
fs/configfs/dir.c-477- return ERR_CAST(inode);
--
fs/configfs/dir.c=576=static void detach_attrs(struct dentry *dentry)
--
fs/configfs/dir.c-614- }
fs/configfs/dir.c:615: configfs_put(sd);
fs/configfs/dir.c-616- spin_lock(&configfs_dirent_lock);
--
fs/configfs/symlink.c=77=static int create_link(struct config_item *parent_item,
--
fs/configfs/symlink.c-95- spin_unlock(&configfs_dirent_lock);
fs/configfs/symlink.c:96: configfs_put(target_sd);
fs/configfs/symlink.c-97- kfree(body);
--
fs/configfs/symlink.c-109- spin_unlock(&configfs_dirent_lock);
fs/configfs/symlink.c:110: configfs_put(target_sd);
fs/configfs/symlink.c-111- kfree(body);
--
fs/configfs/symlink.c=213=int configfs_unlink(struct inode *dir, struct dentry *dentry)
--
fs/configfs/symlink.c-231- spin_unlock(&configfs_dirent_lock);
fs/configfs/symlink.c:232: configfs_put(sd);
fs/configfs/symlink.c-233- simple_unlink(dir, dentry);
--
fs/configfs/symlink.c-247- spin_unlock(&configfs_dirent_lock);
fs/configfs/symlink.c:248: configfs_put(target_sd);
fs/configfs/symlink.c-249-
]
|
| 551/5 |
2026/09/02 22:09 |
llm |
patch-reviewer |
0m
Model:
Tokens:
input: 74122
output: 35
thoughts: 57
Thoughts: Here's my take:
**Investigating the `configfs_d_iput` Function**
Okay, so I need to understand this `configfs_d_iput` function. It seems to be the crucial piece where the reference on `sd` gets dropped when the dentry is finally iputted or destroyed. This is a common pattern in file system implementations, so let's dig into the details. Time to crack open the `fs/configfs/dir.c` file and see what's actually happening within `configfs_d_iput`. I'm expecting to see the refcount management and cleanup logic for the superblock data structure (`sd`) in this function. I'll be looking for potential race conditions or resource leaks. Let's see if this is all working as I would expect, or if there might be anything to look out for.
|
| 552/5 |
2026/09/02 22:09 |
tool |
read-file |
0m
Args: map[File:fs/configfs/dir.c FirstLine:40 LineCount:35]
Results: map[Contents: 40: * But parent configfs_subsystem is NULL when config_item is root.
41: * Use this mutex when config_item is root.
42: */
43: static DEFINE_MUTEX(configfs_subsystem_mutex);
44:
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: }
67:
68: const struct dentry_operations configfs_dentry_ops = {
69: .d_iput = configfs_d_iput,
70: };
71:
72: #ifdef CONFIG_LOCKDEP
73:
74: /*
]
|
| 553/5 |
2026/09/02 22:09 |
llm |
patch-reviewer |
0m
Model:
Tokens:
input: 74883
output: 27
thoughts: 2916
Thoughts: **Analysis of `configfs_symlink` and Related Functions**
Alright, let's break down this `configfs_symlink` function and the surrounding logic. My primary concern is ensuring the memory safety and validity of the objects involved, especially during potential error conditions or hierarchical structures.
First, the initial assessment regarding the positive refcount of `dentry->d_parent` and the validity of `sd = dentry->d_parent->d_fsdata` seems sound. Because of how symlinks are created, the parent directory *must* remain valid while the symlink operation is in progress. The parent directory is pinned by the child dentry. Therefore the `fsdata` associated with the parent is valid throughout the process of the `configfs_symlink` function.
Now, let's look at error handling. The patch contains several crucial checks. If the target lookup succeeds, but `sd->s_frag->frag_dead` is true, then `-ENOENT` is returned. Crucially, the code correctly handles this by skipping the critical section and immediately calling `config_item_put` on the relevant items. It's the order of these `config_item_put` calls and how the `nvmet_port` is pinned (via `root_item`) that is important, ensuring there are no use-after-free issues. `root_item` pins the `nvmet_port`, which in turn pins `parent_item`.
A failed target lookup (e.g., `get_target` returning an error) also triggers a jump to `out_put`. In this scenario, `target_item` is either NULL or never set. The code correctly handles this case by only putting the `parent_item` and the `root_item`, and these are both pinned when calling `configfs_symlink`, so they are valid in memory.
If the `type->ct_item_ops->allow_link` check fails, the control flow also jumps to `out_put`. The `root_item` is pinned prior to the check, which is critical. A safe `config_item_put(NULL)` occurs here if the `root_item` is NULL.
Let's dig into the possibility of a `NULL` return from `configfs_get_root_item`. The code shows that even if `configfs_get_root_item` returns `NULL`, the `config_item_put(NULL)` call is safe (a no-op). However, let's make sure this is not reachable. This code also pins default groups, like default namespaces in `nvmet`, so it's a critical safety point.
Now, nested default groups: This is where it gets a little more complex. I need to make sure `configfs_get_root_item` correctly traverses the hierarchy. It appears that it correctly walks up the directory structure, handling multiple nested default groups, as it should pin the root of the hierarchy (e.g. `ports/1`) that contains all the child default groups. When it hits a non-default group, it stops and gets the config item for the root group.
If a symlink is created in a default group, like `/config/nvmet/ports/1/subsystems`, the pin is correctly set on `ports/1` - which contains the embedded `subsystems` default group.
Finally, what about the root dentry? It correctly handles the case where `dentry->d_parent` is the root of the configfs mount. The traversal always breaks at or before the root. The loop will never walk past the root, so it is safe.
I've also verified the `CONFIGFS_USET_DEFAULT` bit definition and how it's set and cleared. Only default groups created by the specific function have this bit set. Regular groups, created through the standard `configfs_mkdir`, don't have it.
I need to confirm that `configfs_get_root_item` is used only in `configfs_symlink` to minimize the scope of any potential problems. This will require a broader search. I need to verify all places where `inode_lock(dir)` is dropped to make sure the same pinning logic exists in the other call paths.
|
| 554/5 |
2026/09/02 22:10 |
tool |
grepper |
0m
Args: map[Expression:inode_unlock PathPrefix:fs/configfs]
Results: map[Output:fs/configfs/dir.c=576=static void detach_attrs(struct dentry *dentry)
--
fs/configfs/dir.c-611- drop_nlink(inode);
fs/configfs/dir.c:612: inode_unlock(inode);
fs/configfs/dir.c-613- dput(child);
--
fs/configfs/dir.c=664=static void detach_groups(struct dentry *dentry)
--
fs/configfs/dir.c-685-
fs/configfs/dir.c:686: inode_unlock(d_inode(child));
fs/configfs/dir.c-687-
--
fs/configfs/dir.c=857=static int configfs_attach_item(struct config_item *item,
--
fs/configfs/dir.c-875- dont_mount(dentry);
fs/configfs/dir.c:876: inode_unlock(d_inode(dentry));
fs/configfs/dir.c-877- d_delete(dentry);
--
fs/configfs/dir.c=891=static int configfs_attach_group(struct config_item *item,
--
fs/configfs/dir.c-920- configfs_adjust_dir_dirent_depth_after_populate(sd);
fs/configfs/dir.c:921: inode_unlock(d_inode(dentry));
fs/configfs/dir.c-922- if (ret)
--
fs/configfs/dir.c=1143=int configfs_depend_item(struct configfs_subsystem *subsys,
--
fs/configfs/dir.c-1175-out_unlock_fs:
fs/configfs/dir.c:1176: inode_unlock(d_inode(root));
fs/configfs/dir.c-1177-
--
fs/configfs/dir.c=1225=int configfs_depend_item_unlocked(struct configfs_subsystem *caller_subsys,
--
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=1469=static int configfs_rmdir(struct inode *dir, struct dentry *dentry)
--
fs/configfs/dir.c-1527- inode_lock(d_inode(wait));
fs/configfs/dir.c:1528: inode_unlock(d_inode(wait));
fs/configfs/dir.c-1529- dput(wait);
--
fs/configfs/dir.c=1593=static int configfs_dir_open(struct inode *inode, struct file *file)
--
fs/configfs/dir.c-1608- }
fs/configfs/dir.c:1609: inode_unlock(d_inode(dentry));
fs/configfs/dir.c-1610-
--
fs/configfs/dir.c=1614=static int configfs_dir_close(struct inode *inode, struct file *file)
--
fs/configfs/dir.c-1622- spin_unlock(&configfs_dirent_lock);
fs/configfs/dir.c:1623: inode_unlock(d_inode(dentry));
fs/configfs/dir.c-1624-
--
fs/configfs/dir.c=1751=int configfs_register_group(struct config_group *parent_group,
--
fs/configfs/dir.c-1776- spin_unlock(&configfs_dirent_lock);
fs/configfs/dir.c:1777: inode_unlock(d_inode(parent));
fs/configfs/dir.c-1778- put_fragment(frag);
--
fs/configfs/dir.c-1780-err_out:
fs/configfs/dir.c:1781: inode_unlock(d_inode(parent));
fs/configfs/dir.c-1782- mutex_lock(&subsys->su_mutex);
--
fs/configfs/dir.c=1796=void configfs_unregister_group(struct config_group *group)
--
fs/configfs/dir.c-1817- fsnotify_rmdir(d_inode(parent), dentry);
fs/configfs/dir.c:1818: inode_unlock(d_inode(parent));
fs/configfs/dir.c-1819-
--
fs/configfs/dir.c=1872=int configfs_register_subsystem(struct configfs_subsystem *subsys)
--
fs/configfs/dir.c-1920-
fs/configfs/dir.c:1921: inode_unlock(d_inode(root));
fs/configfs/dir.c-1922-
--
fs/configfs/dir.c=1934=void configfs_unregister_subsystem(struct configfs_subsystem *subsys)
--
fs/configfs/dir.c-1963- dont_mount(dentry);
fs/configfs/dir.c:1964: inode_unlock(d_inode(dentry));
fs/configfs/dir.c-1965-
--
fs/configfs/dir.c-1968-
fs/configfs/dir.c:1969: inode_unlock(d_inode(root));
fs/configfs/dir.c-1970-
--
fs/configfs/file.c=446=int configfs_create_file(struct config_item * item, const struct configfs_attribute * attr)
--
fs/configfs/file.c-455- CONFIGFS_ITEM_ATTR, parent_sd->s_frag);
fs/configfs/file.c:456: inode_unlock(d_inode(dir));
fs/configfs/file.c-457-
--
fs/configfs/file.c=467=int configfs_create_bin_file(struct config_item *item,
--
fs/configfs/file.c-477- CONFIGFS_ITEM_BIN_ATTR, parent_sd->s_frag);
fs/configfs/file.c:478: inode_unlock(dir->d_inode);
fs/configfs/file.c-479-
--
fs/configfs/symlink.c=135=int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
--
fs/configfs/symlink.c-183- */
fs/configfs/symlink.c:184: inode_unlock(dir);
fs/configfs/symlink.c-185- ret = get_target(symname, &target_item, dentry->d_sb);
]
|
| 555/5 |
2026/09/02 22:10 |
llm |
patch-reviewer |
0m
Model:
Tokens:
input: 80546
output: 37
thoughts: 74
Thoughts: Here's my take on those thoughts:
**Analyzing `configfs` Inode Operations**
Okay, so `configfs_symlink` really is the odd one out, isn't it? It's the ONLY inode operation where the VFS *actually* hands me an inode with `inode_lock(dir)` already held. And the reason they can do that is that it *explicitly* drops `inode_unlock(dir)` to resolve the target path without a deadlock situation. That's a pretty clever workaround. I've seen some interesting tricks in other filesystems, but this is a particularly elegant solution to a tricky problem.
Now, I need to see how `configfs_unlink` does things when it is *not* given an already held inode lock. I'm especially interested in how it handles the locking normally, since `configfs_symlink` has this weird exception. It'll be key to understanding the general design principles and any potential gotchas with lock ordering and concurrency in the `configfs` module. Time to dig into the code!
|
| 556/5 |
2026/09/02 22:10 |
tool |
read-file |
0m
Args: map[File:fs/configfs/symlink.c FirstLine:213 LineCount:50]
Results: map[Contents: 213: int configfs_unlink(struct inode *dir, struct dentry *dentry)
214: {
215: struct configfs_dirent *sd = dentry->d_fsdata, *target_sd;
216: struct config_item *parent_item;
217: const struct config_item_type *type;
218: int ret;
219:
220: ret = -EPERM; /* What lack-of-symlink returns */
221: if (!(sd->s_type & CONFIGFS_ITEM_LINK))
222: goto out;
223:
224: target_sd = sd->s_element;
225:
226: parent_item = configfs_get_config_item(dentry->d_parent);
227: type = parent_item->ci_type;
228:
229: spin_lock(&configfs_dirent_lock);
230: list_del_init(&sd->s_sibling);
231: spin_unlock(&configfs_dirent_lock);
232: configfs_put(sd);
233: simple_unlink(dir, dentry);
234:
235: /*
236: * drop_link() must be called before
237: * decrementing target's ->s_links, so that the order of
238: * drop_link(this, target) and drop_item(target) is preserved.
239: */
240: if (type && type->ct_item_ops &&
241: type->ct_item_ops->drop_link)
242: type->ct_item_ops->drop_link(parent_item,
243: target_sd->s_element);
244:
245: spin_lock(&configfs_dirent_lock);
246: target_sd->s_links--;
247: spin_unlock(&configfs_dirent_lock);
248: configfs_put(target_sd);
249:
250: config_item_put(parent_item);
251:
252: ret = 0;
253:
254: out:
255: return ret;
256: }
257:
258: const struct inode_operations configfs_symlink_inode_operations = {
259: .get_link = simple_get_link,
260: .setattr = configfs_setattr,
261: };
262:
]
|
| 557/5 |
2026/09/02 22:10 |
llm |
patch-reviewer |
0m
Model:
Tokens:
input: 81635
output: 36
thoughts: 46
Thoughts: **Diving Back into `configfs_symlink`**
Okay, time to circle back to `configfs_symlink`. I need to refresh my memory, particularly on the inner workings of `create_link`. There's something I'm missing there, or perhaps I'm overlooking a subtle interaction. Let's start by re-reading the relevant source code, specifically the first 115 lines of `fs/configfs/symlink.c`. I need a solid grounding on the core functionality before I start trying to debug. I'll need to refresh my mind of the specifics of the structure, error handling, and file system interactions within those initial lines. Let's see if this gives me a clearer picture.
|
| 558/5 |
2026/09/02 22:10 |
tool |
read-file |
0m
Args: map[File:fs/configfs/symlink.c FirstLine:1 LineCount:115]
Results: map[Contents: 1: // SPDX-License-Identifier: GPL-2.0-or-later
2: /*
3: * symlink.c - operations for configfs symlinks.
4: *
5: * Based on sysfs:
6: * sysfs is Copyright (C) 2001, 2002, 2003 Patrick Mochel
7: *
8: * configfs Copyright (C) 2005 Oracle. All rights reserved.
9: */
10:
11: #include <linux/fs.h>
12: #include <linux/module.h>
13: #include <linux/namei.h>
14: #include <linux/slab.h>
15:
16: #include <linux/configfs.h>
17: #include "configfs_internal.h"
18:
19: /* Protects attachments of new symlinks */
20: DEFINE_MUTEX(configfs_symlink_mutex);
21:
22: static int item_depth(struct config_item * item)
23: {
24: struct config_item * p = item;
25: int depth = 0;
26: do { depth++; } while ((p = p->ci_parent) && !configfs_is_root(p));
27: return depth;
28: }
29:
30: static int item_path_length(struct config_item * item)
31: {
32: struct config_item * p = item;
33: int length = 1;
34: do {
35: length += strlen(config_item_name(p)) + 1;
36: p = p->ci_parent;
37: } while (p && !configfs_is_root(p));
38: return length;
39: }
40:
41: static void fill_item_path(struct config_item * item, char * buffer, int length)
42: {
43: struct config_item * p;
44:
45: --length;
46: for (p = item; p && !configfs_is_root(p); p = p->ci_parent) {
47: int cur = strlen(config_item_name(p));
48:
49: /* back up enough to print this bus id with '/' */
50: length -= cur;
51: memcpy(buffer + length, config_item_name(p), cur);
52: *(buffer + --length) = '/';
53: }
54: }
55:
56: static int configfs_get_target_path(struct config_item *item,
57: struct config_item *target, char *path)
58: {
59: int depth, size;
60: char *s;
61:
62: depth = item_depth(item);
63: size = item_path_length(target) + depth * 3 - 1;
64: if (size > PATH_MAX)
65: return -ENAMETOOLONG;
66:
67: pr_debug("%s: depth = %d, size = %d\n", __func__, depth, size);
68:
69: for (s = path; depth--; s += 3)
70: strcpy(s,"../");
71:
72: fill_item_path(target, path, size);
73: pr_debug("%s: path = '%s'\n", __func__, path);
74: return 0;
75: }
76:
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++;
]
|
| 559/5 |
2026/09/02 22:10 |
llm |
patch-reviewer |
0m
Model:
Tokens:
input: 83244
output: 40
thoughts: 298
Thoughts: **My Concerns Regarding Potential Use-After-Free in ConfigFS Link Creation**
Okay, let's break this down. I'm looking at the `create_link` function (lines 77-115) and immediately I'm spotting a potential vulnerability here. Specifically, the function calls `configfs_get_target_path`, and that's where things get interesting. The code sequence looks like this:
`configfs_get_target_path(parent_item, item, body)` gets called, and *then* the `configfs_create_link` function uses the result. Seems straightforward enough. But *hang on* a second. I remember `configfs_get_target_path` is the one that *traverses* `parent_item->ci_parent` and `item->ci_parent`. My first alarm bells are ringing. If `parent_item` somehow gets freed *before* the call to `configfs_create_link` completes β maybe due to a concurrent operation or a race condition β then that initial traversal, and indeed the subsequent access inside of `nvmet_port_subsys_allow_link` will lead to a **use-after-free** condition. That's a textbook kernel crash waiting to happen.
The subsequent `nvmet_port_subsys_allow_link` call within `configfs_create_link` in the driver code adds to my concerns because the code accesses `parent->ci_parent`. I need to review that code as well to find out how `parent_item` is validated. I'll need to look at `drivers/nvme/target/configfs.c` to see if there is an issue with `nvmet_port_subsys_allow_link` as well.
|
| 560/5 |
2026/09/02 22:10 |
tool |
read-file |
0m
Args: map[File:drivers/nvme/target/configfs.c FirstLine:1055 LineCount:40]
Results: map[Contents:1055:
1056: static int nvmet_port_subsys_allow_link(struct config_item *parent,
1057: struct config_item *target)
1058: {
1059: struct nvmet_port *port = to_nvmet_port(parent->ci_parent);
1060: struct nvmet_subsys *subsys;
1061: struct nvmet_subsys_link *link, *p;
1062: int ret;
1063:
1064: if (target->ci_type != &nvmet_subsys_type) {
1065: pr_err("can only link subsystems into the subsystems dir.!\n");
1066: return -EINVAL;
1067: }
1068: subsys = to_subsys(target);
1069: link = kmalloc_obj(*link);
1070: if (!link)
1071: return -ENOMEM;
1072: link->subsys = subsys;
1073:
1074: down_write(&nvmet_config_sem);
1075: ret = -EEXIST;
1076: list_for_each_entry(p, &port->subsystems, entry) {
1077: if (p->subsys == subsys)
1078: goto out_free_link;
1079: }
1080:
1081: if (list_empty(&port->subsystems)) {
1082: ret = nvmet_enable_port(port);
1083: if (ret)
1084: goto out_free_link;
1085: }
1086:
1087: list_add_tail(&link->entry, &port->subsystems);
1088: nvmet_port_disc_changed(port, subsys);
1089:
1090: up_write(&nvmet_config_sem);
1091: return 0;
1092:
1093: out_free_link:
1094: up_write(&nvmet_config_sem);
]
|
| 561/5 |
2026/09/02 22:10 |
llm |
patch-reviewer |
0m
Model:
Tokens:
input: 84561
output: 37
thoughts: 1074
Thoughts: **Analyzing a Potential Use-After-Free Vulnerability in NVMe-oF Configuration**
Okay, let's break this down. My focus is on understanding a potential Use-After-Free (UAF) condition within the `nvmet_port_subsys_allow_link` function, specifically related to the interaction with `configfs` and directory removal.
First, I see the core issue revolves around how the `port` structure, which contains crucial NVMe-oF port information, is handled in relation to its parent directory object. The code uses `to_nvmet_port(parent->ci_parent)` to get the port structure, where `parent` points to the `cg_item` within a `subsys_group` related to a `nvmet_port`.
The critical observation here is that `configfs_rmdir` calls `unlink_obj` during the directory removal process. Within `unlink_obj`, the `ci_parent` field of the `config_item` (which contains our `parent` pointer) is set to NULL. This happens *after* `inode_unlock(dir)` is potentially dropped.
Now, here's where the vulnerability emerges. If `unlink_obj` executes *while* `inode_unlock(dir)` is dropped, we can have a race condition:
1. **UAF Scenario:** If the `root_item` isn't pinned, `port` might be freed before `nvmet_port_subsys_allow_link` is called. That would lead to dereferencing freed memory via `to_nvmet_port(parent->ci_parent)` and ultimately, a crash.
2. **NULL Pointer Dereference:** Even if `port` isn't freed, `parent->ci_parent` becomes NULL. Consequently, `to_nvmet_port(NULL)` resolves to a pointer that is close to NULL, causing a NULL pointer dereference when accessing `port->subsystems` or anything else within the `port` structure during `allow_link`.
This is where the check on `sd->s_frag->frag_dead` *before* `allow_link` is crucial. The key is how `configfs_rmdir` sets `frag->frag_dead` *before* it calls `unlink_obj`. This is a protective measure. It happens due to this sequence:
1. `configfs_rmdir` acquires `down_write(&frag->frag_sem)` and sets `frag->frag_dead = true;`.
2. Then, it calls `configfs_detach_group(dentry)`. In `detach_groups`, an `inode_lock` is taken, but the `configfs_symlink` *could* drop the `inode_unlock(dir)` and finish detaching.
3. The important part is that `frag->frag_dead` is set BEFORE `detach_groups` can acquire `inode_lock(dir)` again, guaranteeing `sd->s_frag->frag_dead` to be `true` when `configfs_symlink` acquires `inode_lock(dir)`.
So, the check acts as a fence. If `frag->frag_dead` is true, it means the rmdir operation has already taken place or is in progress, protecting us from the race condition.
Finally, I want to clarify my thinking about other directory removal scenarios. I recognize `rmdir` might be called on something *other* than the immediate parent of the `subsystems` directory. However, in `configfs`, one cannot remove a directory if there are user-created subdirectories (which, in this case, would be the `subsystems` directory). However, if `ports/1` is being removed, `ports/1`'s frag covers it and all default groups below it. Now, `configfs_unregister_group` or `configfs_unregister_subsystem` are also potential vectors, which I will check lines 1796-1825 and 1934-1975 of `fs/configfs/dir.c` to understand these cases.
|
| 562/5 |
2026/09/02 22:10 |
tool |
read-file |
0m
Args: map[File:fs/configfs/dir.c FirstLine:1796 LineCount:80]
Results: map[Contents:1796: void configfs_unregister_group(struct config_group *group)
1797: {
1798: struct configfs_subsystem *subsys = group->cg_subsys;
1799: struct dentry *dentry = dget(group->cg_item.ci_dentry);
1800: struct dentry *parent = group->cg_item.ci_parent->ci_dentry;
1801: struct configfs_dirent *sd = dentry->d_fsdata;
1802: struct configfs_fragment *frag = sd->s_frag;
1803:
1804: down_write(&frag->frag_sem);
1805: frag->frag_dead = true;
1806: up_write(&frag->frag_sem);
1807:
1808: inode_lock_nested(d_inode(parent), I_MUTEX_PARENT);
1809: spin_lock(&configfs_dirent_lock);
1810: configfs_detach_prep(sd, NULL);
1811: spin_unlock(&configfs_dirent_lock);
1812:
1813: configfs_detach_group(dentry);
1814: d_inode(dentry)->i_flags |= S_DEAD;
1815: dont_mount(dentry);
1816: d_drop(dentry);
1817: fsnotify_rmdir(d_inode(parent), dentry);
1818: inode_unlock(d_inode(parent));
1819:
1820: dput(dentry);
1821:
1822: mutex_lock(&subsys->su_mutex);
1823: unlink_group(group);
1824: mutex_unlock(&subsys->su_mutex);
1825: }
1826: EXPORT_SYMBOL(configfs_unregister_group);
1827:
1828: /**
1829: * configfs_register_default_group() - allocates and registers a child group
1830: * @parent_group: parent group
1831: * @name: child group name
1832: * @item_type: child item type description
1833: *
1834: * boilerplate to allocate and register a child group with its parent. We need
1835: * kzalloc'ed memory because child's default_group is initially empty.
1836: *
1837: * Return: allocated config group or ERR_PTR() on error
1838: */
1839: struct config_group *
1840: configfs_register_default_group(struct config_group *parent_group,
1841: const char *name,
1842: const struct config_item_type *item_type)
1843: {
1844: int ret;
1845: struct config_group *group;
1846:
1847: group = kzalloc_obj(*group);
1848: if (!group)
1849: return ERR_PTR(-ENOMEM);
1850: config_group_init_type_name(group, name, item_type);
1851:
1852: ret = configfs_register_group(parent_group, group);
1853: if (ret) {
1854: kfree(group);
1855: return ERR_PTR(ret);
1856: }
1857: return group;
1858: }
1859: EXPORT_SYMBOL(configfs_register_default_group);
1860:
1861: /**
1862: * configfs_unregister_default_group() - unregisters and frees a child group
1863: * @group: the group to act on
1864: */
1865: void configfs_unregister_default_group(struct config_group *group)
1866: {
1867: configfs_unregister_group(group);
1868: kfree(group);
1869: }
1870: EXPORT_SYMBOL(configfs_unregister_default_group);
1871:
1872: int configfs_register_subsystem(struct configfs_subsystem *subsys)
1873: {
1874: int err;
1875: struct config_group *group = &subsys->su_group;
]
|
| 563/5 |
2026/09/02 22:10 |
llm |
patch-reviewer |
0m
Model:
Tokens:
input: 87782
output: 27
thoughts: 308
Thoughts: **Analyzing Thread Safety in ConfigFS Fragment Deletion**
Okay, so I'm looking closely at how `configfs_unregister_group` and `configfs_unregister_subsystem` handle the `frag_dead` flag. Both of them set `frag->frag_dead = true;` before they detach groups and unlink things, which makes sense from a cleanup perspective, but let's make sure it's thread-safe.
My main concern is whether reading `sd->s_frag->frag_dead` is safe in all situations. Itβs a simple boolean (`bool frag_dead`), so thatβs a small win, but I need to be *sure*.
Let's dissect `configfs_rmdir`. I see it uses a write semaphore: `down_write(&frag->frag_sem);` before setting `frag->frag_dead = true;` and then `up_write(&frag->frag_sem);`. `detach_groups` then goes on to use `inode_lock(dir)` and `inode_unlock(d_inode(child))`. That `inode_unlock` *should* have full memory barrier semantics, acting as a lock release, right? Then, `configfs_symlink` acquires `inode_lock(dir)` and that's a lock acquire. So, the unlock by `detach_groups` happens before the lock by `configfs_symlink`. That establishes a happens-before relationship. Therefore, the write to `sd->s_frag->frag_dead` should be visible to `configfs_symlink`! That's good.
But, I'm not entirely satisfied. I need to get a bigger picture, let's also check other file operations to see how `frag_dead` is checked in the rest of `fs/configfs`. Okay, let's search for `frag_dead` across the entire `fs/configfs` directory and see what pops up, just to verify my assumptions.
|
| 564/5 |
2026/09/02 22:10 |
tool |
grepper |
0m
Args: map[Expression:frag_dead PathPrefix:fs/configfs]
Results: map[Output:fs/configfs/configfs_internal.h=21=struct configfs_fragment {
--
fs/configfs/configfs_internal.h-23- struct rw_semaphore frag_sem;
fs/configfs/configfs_internal.h:24: bool frag_dead;
fs/configfs/configfs_internal.h-25-};
--
fs/configfs/dir.c=158=static struct configfs_fragment *new_fragment(void)
--
fs/configfs/dir.c-165- init_rwsem(&p->frag_sem);
fs/configfs/dir.c:166: p->frag_dead = false;
fs/configfs/dir.c-167- }
--
fs/configfs/dir.c=1469=static int configfs_rmdir(struct inode *dir, struct dentry *dentry)
--
fs/configfs/dir.c-1540- }
fs/configfs/dir.c:1541: frag->frag_dead = true;
fs/configfs/dir.c-1542- up_write(&frag->frag_sem);
--
fs/configfs/dir.c=1796=void configfs_unregister_group(struct config_group *group)
--
fs/configfs/dir.c-1804- down_write(&frag->frag_sem);
fs/configfs/dir.c:1805: frag->frag_dead = true;
fs/configfs/dir.c-1806- up_write(&frag->frag_sem);
--
fs/configfs/dir.c=1934=void configfs_unregister_subsystem(struct configfs_subsystem *subsys)
--
fs/configfs/dir.c-1947- down_write(&frag->frag_sem);
fs/configfs/dir.c:1948: frag->frag_dead = true;
fs/configfs/dir.c-1949- up_write(&frag->frag_sem);
--
fs/configfs/file.c=56=static int fill_read_buffer(struct file *file, struct configfs_buffer *buffer)
--
fs/configfs/file.c-66- down_read(&frag->frag_sem);
fs/configfs/file.c:67: if (!frag->frag_dead)
fs/configfs/file.c-68- count = buffer->attr->show(buffer->item, buffer->page);
--
fs/configfs/file.c=106=static ssize_t configfs_bin_read_iter(struct kiocb *iocb, struct iov_iter *to)
--
fs/configfs/file.c-125- down_read(&frag->frag_sem);
fs/configfs/file.c:126: if (!frag->frag_dead)
fs/configfs/file.c-127- len = buffer->bin_attr->read(buffer->item, NULL, 0);
--
fs/configfs/file.c-150- down_read(&frag->frag_sem);
fs/configfs/file.c:151: if (!frag->frag_dead)
fs/configfs/file.c-152- len = buffer->bin_attr->read(buffer->item,
--
fs/configfs/file.c=200=flush_write_buffer(struct file *file, struct configfs_buffer *buffer, size_t count)
--
fs/configfs/file.c-205- down_read(&frag->frag_sem);
fs/configfs/file.c:206: if (!frag->frag_dead)
fs/configfs/file.c-207- res = buffer->attr->store(buffer->item, buffer->page, count);
--
fs/configfs/file.c=290=static int __configfs_open_file(struct inode *inode, struct file *file, int type)
--
fs/configfs/file.c-304- down_read(&frag->frag_sem);
fs/configfs/file.c:305: if (unlikely(frag->frag_dead))
fs/configfs/file.c-306- goto out_free_buffer;
--
fs/configfs/file.c=400=static int configfs_release_bin_file(struct inode *inode, struct file *file)
--
fs/configfs/file.c-407- down_read(&frag->frag_sem);
fs/configfs/file.c:408: if (!frag->frag_dead) {
fs/configfs/file.c-409- /* result of ->release() is ignored */
]
|
| 565/5 |
2026/09/02 22:10 |
llm |
patch-reviewer |
0m
Model:
Tokens:
input: 89738
output: 19
thoughts: 5206
Thoughts: **Analysis of ConfigFS Symlink Race Condition and Proposed Fix**
Alright, let's break this down. My initial thought process went something like this: I see this code in `fs/configfs/file.c` consistently using `frag->frag_dead` within a semaphore, which is *the* mechanism ConfigFS uses to signal that a directory or group is being torn down. So, the first question is, does `configfs_symlink` *need* to use this mechanism too, or is the inode lock on the directory sufficient?
Digging into `configfs_rmdir`, I see that when a directory is removed, `frag->frag_dead` is set to `true` while holding a write semaphore. This is followed by a detach operation that acquires the inode lock. The key here is the *order* of operations: `frag->frag_dead` is set *before* acquiring the inode lock.
So, if `configfs_symlink` acquires the inode lock *after* the detach operation, it's safe. `frag->frag_dead` will be set by that point. The question is, what if `rmdir` is *concurrently* running? The good news is that if `rmdir` *is* running, it'll be blocked on the inode lock, and if `frag->frag_dead` is set, `symlink` sees that and aborts with `-ENOENT`, which is safe. If `rmdir` *hasn't* set `frag_dead` yet, `configfs_symlink` proceeds, but is protected by a mutex, `configfs_symlink_mutex`, which is also used by `rmdir`. So either the symlink is created before the detach or the detach prevents creation of the symlink.
To check this, I examined different interleavings: the symlink creates the link, or the rmdir runs first, or rmdir runs, but is blocked, or `rmdir` has not yet set the flag. All permutations are safe: if the symlink creates the link, then `configfs_detach_prep` in `rmdir` will fail with -EBUSY; if the rmdir runs first, then `configfs_symlink` will see the `frag_dead` flag is true and abort, and other cases work similarly due to the semaphore and mutex. All cases are deterministic and safe.
Now, checking the provided patch: I look at `configfs_get_root_item` to get the root, and confirm that there are no ways that the return value can be null, and that the count for the ref is maintained. If it can be null, the code properly handles it. I analyze where `root_item` is acquired and released. I'm checking to see if there are any subtle bugs or coding style issues. The key here is the addition of the `root_item` and its corresponding `config_item_put` calls, which are critical to keeping the memory pointed to by `parent_item` alive during the window where the inode lock is dropped. I check to see if the root item and parent item can point to the same item, in which case the refcounts have to be properly handled.
I also see an additional check using `sd->s_frag->frag_dead` before calling `allow_link`, and that's precisely what's needed to prevent proceeding with the link creation if the directory is being torn down. I am confirming that `sd->s_frag` will never be null, and checking that it will not be freed while the function is running.
I evaluate the patch in light of the four criteria for safe code: sentinel tests, LIFO stack unwinding, resource ownership and idiom alignment.
I conclude that the fix is correct: it prevents the race condition by correctly pinning the root object (which contains the memory for the default group that could be freed), and by checking the `frag_dead` flag after re-acquiring the lock. I've re-examined the code and found no issues or inconsistencies. This patch avoids the need for ad-hoc fixes in the calling code.
ReviewApproved=true
ReviewComments=
```json
{
"ReviewApproved": true,
"ReviewComments": ""
}
```
|
| 566/5 |
2026/09/02 22:10 |
tool |
set-results |
0m
Args: map[ReviewApproved:true ReviewComments:[]]
Results: map[ReviewApproved:true ReviewComments:[]]
|
| 567/4 |
2026/09/02 22:10 |
action |
evaluate-patch-review |
0m
Results: map[NeedsIteration:false ReviewFeedback:]
|
| 568/1 |
2026/09/02 22:10 |
action |
apply-patch |
0m
Results:
|
| 569/1 |
2026/09/02 22:10 |
action |
init-style-items |
0m
Results:
|
| 570/1 |
2026/09/02 22:10 |
action |
run-checkpatch |
0m
Results: map[CheckpatchOutput:total: 0 errors, 0 warnings, 59 lines checked
Your patch has no obvious style problems and is ready for submission.
]
|
| 571/1 |
2026/09/02 22:10 |
loop |
|
0m
|
| 572/2 |
2026/09/02 22:10 |
iteration |
0 |
0m
|
| 573/3 |
2026/09/02 22:10 |
agent |
checkpatch-arbiter |
0m
Model:
Results:
Instruction:
You are an expert Linux kernel maintainer acting as a code style arbiter.
Your task is to review scripts/checkpatch.pl output and reviewer style requests for a proposed patch.
Determine which reported issues are genuine, actionable formatting/style defects (e.g. indentation, whitespace,
naming, syntax style) and should be fixed. List them in ActionableIssues.
Ignore issues that are false positives, intentional constructs (e.g. BUG_ON/XA_BUG_ON in test files, macros,
subsystem conventions), or unfixable without breaking code logic or tests. If no issues should be changed,
leave ActionableIssues empty.
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 patch diff is:
diff --git a/fs/configfs/symlink.c b/fs/configfs/symlink.c
index 31eb28b27..5fe3722bd 100644
--- a/fs/configfs/symlink.c
+++ b/fs/configfs/symlink.c
@@ -131,6 +131,19 @@ static int get_target(const char *symname, struct config_item **target,
return 0;
}
+static struct config_item *configfs_get_root_item(struct dentry *dentry)
+{
+ struct dentry *root_dentry = dentry;
+ struct configfs_dirent *sd;
+
+ while (root_dentry) {
+ sd = root_dentry->d_fsdata;
+ if (!sd || !(sd->s_type & CONFIGFS_USET_DEFAULT))
+ break;
+ root_dentry = root_dentry->d_parent;
+ }
+ return root_dentry ? configfs_get_config_item(root_dentry) : NULL;
+}
int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
struct dentry *dentry, const char *symname)
@@ -139,6 +152,7 @@ int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
struct configfs_dirent *sd;
struct config_item *parent_item;
struct config_item *target_item = NULL;
+ struct config_item *root_item = NULL;
const struct config_item_type *type;
sd = dentry->d_parent->d_fsdata;
@@ -157,6 +171,12 @@ int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
!type->ct_item_ops->allow_link)
goto out_put;
+ /*
+ * Pin the root object so that the parent_item (which may be an
+ * embedded default group) is not freed while we drop the lock.
+ */
+ root_item = configfs_get_root_item(dentry->d_parent);
+
/*
* This is really sick. What they wanted was a hybrid of
* link(2) and symlink(2) - they wanted the target resolved
@@ -192,8 +212,12 @@ int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
else
ret = inode_permission(&nop_mnt_idmap, dir,
MAY_WRITE | MAY_EXEC);
- if (!ret)
- ret = type->ct_item_ops->allow_link(parent_item, target_item);
+ if (!ret) {
+ if (sd->s_frag->frag_dead)
+ ret = -ENOENT;
+ else
+ ret = type->ct_item_ops->allow_link(parent_item, target_item);
+ }
if (!ret) {
mutex_lock(&configfs_symlink_mutex);
ret = create_link(parent_item, target_item, dentry);
@@ -207,6 +231,7 @@ int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
out_put:
config_item_put(parent_item);
+ config_item_put(root_item);
return ret;
}
The checkpatch.pl output is:
total: 0 errors, 0 warnings, 59 lines checked
Your patch has no obvious style problems and is ready for submission.
Carefully evaluate each issue and populate ActionableIssues.
|
| 574/4 |
2026/09/02 22:10 |
llm |
checkpatch-arbiter |
0m
Model:
Tokens:
input: 1278
output: 15
thoughts: 592
Thoughts: **Reviewing the Patch for Style Adherence**
Okay, let's get down to it. I need to make sure this patch is clean and adheres to the kernel's coding style. First, I'll take a look at the `checkpatch.pl` output β ah, perfect! Zero errors, zero warnings. That's a good start. The output confirms that the patch is ready for submission, and it suggests no style problems.
Now, let's dive into the diff itself, line by line, just to be thorough. I'm focusing on the formatting, readability, and consistency of the code.
Specifically, I'm examining the changes in `fs/configfs/symlink.c`. I've noticed the addition of `configfs_get_root_item` function. I'm taking a mental note of the spacing around that function; it looks like the empty line after the previous function, `get_target`, is now the empty line after the new function. Everything checks out; the code's spacing adheres to the standard style.
I'm paying close attention to `configfs_symlink` as well. Checking the surrounding lines shows a clear and standard pattern of: a variable definition initialized to `NULL`, assignment of the return value of a function, an error check block, and finally, a resource release (`config_item_put`). Nothing sticks out as problematic here.
Given the `checkpatch` report and my manual review, I'm confident that there are no style issues that need attention. Everything looks clean and consistent with the established kernel coding conventions. Therefore, the ActionableIssues list is empty.
|
| 575/4 |
2026/09/02 22:10 |
tool |
set-results |
0m
Args:
Results:
|
| 576/3 |
2026/09/02 22:10 |
action |
evaluate-checkpatch-arbiter |
0m
Results: map[NeedRefinement:false]
|
| 577/3 |
2026/09/02 22:10 |
action |
If |
0m
Args: map[NeedRefinement:false]
|
| 578/1 |
2026/09/02 22:10 |
agent |
fixes-finder |
11m
Model:
Results: map[FixesHash:351e5d869e5ac10cb40c78b5f2d7dfc816ad4587]
Instruction:
You are an experienced Linux kernel developer tasked with identifying the commit
that introduced the bug being fixed. Identifying the correct buggy commit is crucial
for proper kernel maintenance (backporting to stable trees, etc.).
Your investigation strategy:
1. Examine the patch that fixes the bug. Use git tools (like git-log or git-blame)
to trace the history of the lines or functions modified by the patch.
2. Analyze the stack trace in the crash report. Identify the key files and functions
involved in the crash and investigate their history to see when the problematic
logic was introduced.
3. Compare the bug explanation with the commit history to find the point where
the described logic error first appeared.
A bug is typically introduced when a piece of code is first written, or when
a refactoring changed its logic in a way that introduced the bug.
Trace the history of relevant symbols or find when specific code patterns were introduced/removed.
You must provide exactly one bug-introducing commit hash.
If you are unable to confidently determine the bug-introducing commit after investigation,
return an empty string rather than guessing.
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 crash is:
==================================================================
BUG: KASAN: slab-use-after-free in nvmet_port_subsys_allow_link+0x41/0x2e0 drivers/nvme/target/configfs.c:1059
Read of size 8 at addr ffff88811c6d44c8 by task syz-executor266/5850
CPU: 0 UID: 0 PID: 5850 Comm: syz-executor266 Not tainted syzkaller #1 PREEMPT(full)
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
Call Trace:
<TASK>
dump_stack_lvl+0xe8/0x150 lib/dump_stack.c:120
print_address_description+0x55/0x1e0 mm/kasan/report.c:378
print_report+0x58/0x70 mm/kasan/report.c:482
kasan_report+0x117/0x150 mm/kasan/report.c:595
nvmet_port_subsys_allow_link+0x41/0x2e0 drivers/nvme/target/configfs.c:1059
configfs_symlink+0x59a/0x1030 fs/configfs/symlink.c:196
vfs_symlink+0x18b/0x330 fs/namei.c:5794
filename_symlinkat+0x1cd/0x410 fs/namei.c:5819
__do_sys_symlinkat fs/namei.c:5839 [inline]
__se_sys_symlinkat+0x4e/0x2b0 fs/namei.c:5834
do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7f648f0cc459
Code: c0 79 93 eb d5 48 8d 7c 1d 00 eb 99 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 d0 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007f648c0911f8 EFLAGS: 00000246 ORIG_RAX: 000000000000010a
RAX: ffffffffffffffda RBX: 00007f648f139028 RCX: 00007f648f0cc459
RDX: 00007f648f139028 RSI: 00000000ffffff9c RDI: 00007f648f139060
RBP: 00007f648f139060 R08: 00007f648c0916c0 R09: 00007f648c0916c0
R10: 00007f648c0916c0 R11: 0000000000000246 R12: ffffffffffffffd0
R13: 0000000000000010 R14: 00007ffe91711350 R15: 00007ffe91711438
</TASK>
Allocated by task 5847:
kasan_save_stack mm/kasan/common.c:57 [inline]
kasan_save_track+0x3e/0x80 mm/kasan/common.c:78
poison_kmalloc_redzone mm/kasan/common.c:398 [inline]
__kasan_kmalloc+0x93/0xb0 mm/kasan/common.c:415
kasan_kmalloc include/linux/kasan.h:263 [inline]
__kmalloc_cache_noprof+0x321/0x600 mm/slub.c:5563
_kmalloc_noprof include/linux/slab.h:991 [inline]
_kzalloc_noprof include/linux/slab.h:1312 [inline]
nvmet_ports_make+0xe6/0xf00 drivers/nvme/target/configfs.c:2051
configfs_mkdir+0x4f6/0x9e0 fs/configfs/dir.c:1360
vfs_mkdir+0x40c/0x620 fs/namei.c:5410
filename_mkdirat+0x285/0x510 fs/namei.c:5443
__do_sys_mkdir fs/namei.c:5470 [inline]
__se_sys_mkdir+0x34/0x150 fs/namei.c:5467
do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
entry_SYSCALL_64_after_hwframe+0x77/0x7f
Freed by task 5847:
kasan_save_stack mm/kasan/common.c:57 [inline]
kasan_save_track+0x3e/0x80 mm/kasan/common.c:78
kasan_save_free_info+0x40/0x50 mm/kasan/generic.c:584
poison_slab_object mm/kasan/common.c:253 [inline]
__kasan_slab_free+0x5c/0x80 mm/kasan/common.c:285
kasan_slab_free include/linux/kasan.h:235 [inline]
slab_free_hook mm/slub.c:2748 [inline]
slab_free mm/slub.c:6499 [inline]
kfree+0x1c5/0x650 mm/slub.c:6792
config_item_cleanup fs/configfs/item.c:128 [inline]
config_item_release+0x13a/0x2d0 fs/configfs/item.c:137
configfs_rmdir+0x885/0x950 fs/configfs/dir.c:1571
vfs_rmdir+0x3e6/0x6a0 fs/namei.c:5515
filename_rmdir+0x292/0x520 fs/namei.c:5572
__do_sys_rmdir fs/namei.c:5595 [inline]
__se_sys_rmdir+0x2e/0x140 fs/namei.c:5592
do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
entry_SYSCALL_64_after_hwframe+0x77/0x7f
The buggy address belongs to the object at ffff88811c6d4000
which belongs to the cache kmalloc-4k of size 4096
The buggy address is located 1224 bytes inside of
freed 4096-byte region [ffff88811c6d4000, ffff88811c6d5000)
The buggy address belongs to the physical page:
page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x11c6d0
head: order:3 mapcount:0 entire_mapcount:0 nr_pages_mapped:0 pincount:0
flags: 0x17ff00000000040(head|node=0|zone=2|lastcpupid=0x7ff)
page_type: f5(slab)
raw: 017ff00000000040 ffff888100042140 dead000000000100 dead000000000122
raw: 0000000000000000 0000000000040004 00000000f5000000 0000000000000000
head: 017ff00000000040 ffff888100042140 dead000000000100 dead000000000122
head: 0000000000000000 0000000000040004 00000000f5000000 0000000000000000
head: 017ff00000000003 fffffffffffffe01 00000000ffffffff 00000000ffffffff
head: ffffffffffffffff 0000000000000000 00000000ffffffff 0000000000000008
page dumped because: kasan: bad access detected
page_owner tracks the page as allocated
page last allocated via order 3, migratetype Unmovable, gfp_mask 0xd20c0(__GFP_IO|__GFP_FS|__GFP_NOWARN|__GFP_NORETRY|__GFP_COMP|__GFP_NOMEMALLOC), pid 5843, tgid 5843 (syz-executor266), ts 63759869729
set_page_owner include/linux/page_owner.h:33 [inline]
post_alloc_hook+0x1f9/0x250 mm/page_alloc.c:1871
prep_new_page mm/page_alloc.c:1879 [inline]
get_page_from_freelist+0x2209/0x2280 mm/page_alloc.c:3943
__alloc_frozen_pages_noprof+0x217/0x5a0 mm/page_alloc.c:5436
alloc_slab_page mm/slub.c:3347 [inline]
allocate_slab+0x7d/0x620 mm/slub.c:3462
new_slab mm/slub.c:3513 [inline]
refill_objects+0x2d5/0x350 mm/slub.c:7410
refill_sheaf mm/slub.c:2885 [inline]
__pcs_replace_empty_main+0x2c8/0x6c0 mm/slub.c:4774
alloc_from_pcs mm/slub.c:4850 [inline]
slab_alloc_node mm/slub.c:4984 [inline]
__kmalloc_cache_noprof+0x39b/0x600 mm/slub.c:5559
_kmalloc_noprof include/linux/slab.h:991 [inline]
do_proc_readlink fs/proc/base.c:1817 [inline]
proc_pid_readlink+0x108/0x230 fs/proc/base.c:1847
vfs_readlink+0x4bb/0x540 fs/namei.c:6407
do_readlinkat+0x218/0x510 fs/stat.c:583
__do_sys_readlinkat fs/stat.c:599 [inline]
__se_sys_readlinkat fs/stat.c:596 [inline]
__x64_sys_readlinkat+0x9a/0xb0 fs/stat.c:596
do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
entry_SYSCALL_64_after_hwframe+0x77/0x7f
page last free pid 5843 tgid 5843 ts 63751651251 stack trace:
reset_page_owner include/linux/page_owner.h:26 [inline]
__free_pages_prepare mm/page_alloc.c:1418 [inline]
__free_frozen_pages+0xc50/0xd30 mm/page_alloc.c:2962
__slab_free+0x274/0x2c0 mm/slub.c:5815
qlink_free mm/kasan/quarantine.c:163 [inline]
qlist_free_all+0x99/0x100 mm/kasan/quarantine.c:179
kasan_quarantine_reduce+0x148/0x160 mm/kasan/quarantine.c:286
__kasan_slab_alloc+0x22/0x80 mm/kasan/common.c:350
kasan_slab_alloc include/linux/kasan.h:253 [inline]
slab_post_alloc_hook mm/slub.c:4683 [inline]
slab_alloc_node mm/slub.c:4996 [inline]
__do_kmalloc_node mm/slub.c:5413 [inline]
__kmalloc_noprof+0x307/0x720 mm/slub.c:5439
_kmalloc_noprof include/linux/slab.h:995 [inline]
tomoyo_add_entry security/tomoyo/common.c:2166 [inline]
tomoyo_supervisor+0xc53/0x1560 security/tomoyo/common.c:2238
tomoyo_audit_env_log security/tomoyo/environ.c:37 [inline]
tomoyo_env_perm+0x151/0x1f0 security/tomoyo/environ.c:64
tomoyo_environ security/tomoyo/domain.c:673 [inline]
tomoyo_find_next_domain+0x1332/0x1a90 security/tomoyo/domain.c:889
tomoyo_bprm_check_security+0x11b/0x180 security/tomoyo/tomoyo.c:102
security_bprm_check+0x85/0x240 security/security.c:820
search_binary_handler fs/exec.c:1749 [inline]
exec_binprm fs/exec.c:1791 [inline]
bprm_execve+0x888/0x1590 fs/exec.c:1847
do_execveat_common+0x4f4/0x670 fs/exec.c:1945
__do_sys_execve fs/exec.c:2018 [inline]
__se_sys_execve fs/exec.c:2012 [inline]
__x64_sys_execve+0x97/0xc0 fs/exec.c:2012
do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
entry_SYSCALL_64_after_hwframe+0x77/0x7f
Memory state around the buggy address:
ffff88811c6d4380: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
ffff88811c6d4400: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
>ffff88811c6d4480: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
^
ffff88811c6d4500: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
ffff88811c6d4580: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
==================================================================
The explanation of the root cause is:
An elegant and complete analysis of the root cause, along with a robust fix for this `configfs` race condition.
### 1. KASAN Crash Analysis & Offset Math
The crash is a slab-use-after-free read of size 8 at offset **1224** inside a 4096-byte region (`kmalloc-4k`).
The KASAN trace points to `nvmet_port_subsys_allow_link()`, which accesses `parent->ci_parent` where `parent` is a `struct config_item *`.
In `nvmet_ports_make()`, a `struct nvmet_port` is dynamically allocated. Let's look at its memory layout:
```c
struct nvmet_port {
struct list_head entry; // 16 bytes
struct nvmf_disc_rsp_page_entry disc_addr; // 1024 bytes
struct config_group group; // 136 bytes (offset 1040)
struct config_group subsys_group; // 136 bytes (offset 1176)
// ...
};
```
The `parent_item` passed to `allow_link` is `&port->subsys_group.cg_item`.
The offset of `subsys_group` is `16 + 1024 + 136 = 1176` bytes.
Inside `struct config_item`, the `ci_parent` pointer is at offset 48.
When `nvmet_port_subsys_allow_link` reads `parent->ci_parent`, it accesses memory at offset `1176 + 48 = 1224` bytes into the `nvmet_port` allocation. This **perfectly matches** the KASAN report:
> `The buggy address is located 1224 bytes inside of freed 4096-byte region`
### 2. The Root Cause (Race Condition)
The race occurs between Thread 1 creating a symlink in a default group (`ports/1/subsystems/`) and Thread 2 removing the parent root object (`ports/1`).
1. **Thread 1 (`symlinkat`)**:
- Enters `configfs_symlink()` with the VFS holding `inode_lock(dir)` on `ports/1/subsystems`.
- Acquires a reference to `parent_item` (the embedded `subsys_group.cg_item`).
- Drops `inode_unlock(dir)` to resolve the symlink target without deadlocking.
2. **Thread 2 (`rmdir`)**:
- Calls `configfs_rmdir()` on `ports/1`.
- Calls `configfs_detach_group()`, which recursively locks all default groups. Since Thread 1 dropped the lock on `ports/1/subsystems`, Thread 2 acquires it, marks the items as dead, detaches them, and unlocks.
- Calls `unlink_group()` on `ports/1`, dropping the subsystem's reference to the port.
- The `nvmet_port` refcount drops to 0, and the **entire `nvmet_port` struct is freed via `kfree()`**.
3. **Thread 1 Resumes**:
- Re-acquires `inode_lock(dir)`.
- Blindly calls `type->ct_item_ops->allow_link(parent_item, target_item)`.
- `parent_item` points to the `subsys_group` embedded inside the **already freed** `nvmet_port`, causing the UAF read at offset 1224.
- Even if `allow_link` wasn't called, jumping to `out_put` executes `config_item_put(parent_item)`, causing a secondary UAF on the kref.
### 3. Why Simple Fixes Fail
- **Re-acquiring `parent_item`**: You cannot safely drop `parent_item` before the unlock and re-acquire it via `configfs_get_config_item(dentry->d_parent)` after the relock. When `configfs_rmdir` detaches the item, it leaves `sd->s_element` as a dangling pointer. Re-acquiring it will dereference freed memory.
- **Holding `parent_item`**: Holding the reference to `parent_item` across the unlock is insufficient because `parent_item` is a **default group**. Default groups are embedded directly inside their parent structs (like `nvmet_port`). Their individual refcount being $> 0$ does **not** prevent the parent struct from being freed when the parent's refcount drops to 0.
### 4. The Proper Fix
To prevent the memory from being freed while the lock is dropped, `configfs_symlink()` must pin the **root object** (the dynamically allocated parent, e.g., `ports/1`) rather than just the embedded default group.
Because `configfs_symlink()` holds `inode_lock(dir)` initially, any concurrent `configfs_rmdir()` on an ancestor root object will block inside `detach_groups()` trying to acquire `inode_lock(dir)`. This guarantees the dentry tree is stable and we can safely walk up the `d_parent` chain to find the root object (the first dentry without the `CONFIGFS_USET_DEFAULT` flag) and take a reference to it.
After re-acquiring the lock, we must check `sd->s_frag->frag_dead` to see if the directory was removed while unlocked.
**Implementation for `fs/configfs/symlink.c`:**
```c
/* Add this helper function above configfs_symlink */
static struct config_item *configfs_get_root_item(struct dentry *dentry)
{
struct dentry *root_dentry = dentry;
struct configfs_dirent *sd;
while (root_dentry) {
sd = root_dentry->d_fsdata;
if (!(sd->s_type & CONFIGFS_USET_DEFAULT))
break;
root_dentry = root_dentry->d_parent;
}
return configfs_get_config_item(root_dentry);
}
int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
struct dentry *dentry, const char *symname)
{
int ret;
struct config_item *parent_item;
struct config_item *target_item = NULL;
struct config_item *root_item = NULL;
const struct config_item_type *type;
parent_item = configfs_get_config_item(dentry->d_parent);
type = parent_item->ci_type;
ret = -EPERM;
if (!type || !type->ct_item_ops ||
!type->ct_item_ops->allow_link)
goto out_put;
/*
* Pin the root object so that the parent_item (which may be an
* embedded default group) is not freed while we drop the lock.
*/
root_item = configfs_get_root_item(dentry->d_parent);
/*
* Drop the dir inode lock. The target path might be outside
* configfs, and we don't want to deadlock.
*/
inode_unlock(dir);
ret = get_target(symname, &target_item, dentry->d_sb);
inode_lock(dir);
if (ret)
goto out_put_root;
if (dentry->d_inode || d_unhashed(dentry))
ret = -EEXIST;
else
ret = inode_permission(&nop_mnt_idmap, dir,
MAY_WRITE | MAY_EXEC);
if (!ret) {
struct configfs_dirent *sd = dentry->d_parent->d_fsdata;
/* Check if the directory was removed while unlocked */
if (sd->s_frag->frag_dead) {
ret = -ENOENT;
} else {
ret = type->ct_item_ops->allow_link(parent_item, target_item);
}
}
if (!ret) {
mutex_lock(&configfs_symlink_mutex);
ret = create_link(parent_item, target_item, dentry);
mutex_unlock(&configfs_symlink_mutex);
if (ret && type->ct_item_ops->drop_link)
type->ct_item_ops->drop_link(parent_item,
target_item);
}
config_item_put(target_item);
out_put_root:
config_item_put(root_item);
out_put:
config_item_put(parent_item);
return ret;
}
```
The patch that fixes the bug is:
diff --git a/fs/configfs/symlink.c b/fs/configfs/symlink.c
index 31eb28b27..5fe3722bd 100644
--- a/fs/configfs/symlink.c
+++ b/fs/configfs/symlink.c
@@ -131,6 +131,19 @@ static int get_target(const char *symname, struct config_item **target,
return 0;
}
+static struct config_item *configfs_get_root_item(struct dentry *dentry)
+{
+ struct dentry *root_dentry = dentry;
+ struct configfs_dirent *sd;
+
+ while (root_dentry) {
+ sd = root_dentry->d_fsdata;
+ if (!sd || !(sd->s_type & CONFIGFS_USET_DEFAULT))
+ break;
+ root_dentry = root_dentry->d_parent;
+ }
+ return root_dentry ? configfs_get_config_item(root_dentry) : NULL;
+}
int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
struct dentry *dentry, const char *symname)
@@ -139,6 +152,7 @@ int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
struct configfs_dirent *sd;
struct config_item *parent_item;
struct config_item *target_item = NULL;
+ struct config_item *root_item = NULL;
const struct config_item_type *type;
sd = dentry->d_parent->d_fsdata;
@@ -157,6 +171,12 @@ int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
!type->ct_item_ops->allow_link)
goto out_put;
+ /*
+ * Pin the root object so that the parent_item (which may be an
+ * embedded default group) is not freed while we drop the lock.
+ */
+ root_item = configfs_get_root_item(dentry->d_parent);
+
/*
* This is really sick. What they wanted was a hybrid of
* link(2) and symlink(2) - they wanted the target resolved
@@ -192,8 +212,12 @@ int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
else
ret = inode_permission(&nop_mnt_idmap, dir,
MAY_WRITE | MAY_EXEC);
- if (!ret)
- ret = type->ct_item_ops->allow_link(parent_item, target_item);
+ if (!ret) {
+ if (sd->s_frag->frag_dead)
+ ret = -ENOENT;
+ else
+ ret = type->ct_item_ops->allow_link(parent_item, target_item);
+ }
if (!ret) {
mutex_lock(&configfs_symlink_mutex);
ret = create_link(parent_item, target_item, dentry);
@@ -207,6 +231,7 @@ int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
out_put:
config_item_put(parent_item);
+ config_item_put(root_item);
return ret;
}
Search for the commit(s) that introduced this bug.
|
| 579/2 |
2026/09/02 22:10 |
llm |
fixes-finder |
0m
Model:
Tokens:
input: 11524
output: 56
thoughts: 80
Thoughts: **Tracing the Bug's Origins**
Okay, so I've been given a crash report, a breakdown of the problem, and the fix β excellent. My next move is clear: pinpoint the exact commit that introduced this bug. Gotta go back to the source and understand when this issue was born.
Given the location of the fix, it's highly probable the issue resides in `fs/configfs/symlink.c`, specifically within the `configfs_symlink` function. I'll dive into the history of that function to find the culprit.
I'll reach for either `git log` or `git blame` to carefully reconstruct the evolution of that part of the code. This will help me narrow down which changes could have introduced the vulnerability, and I'll carefully consider the surrounding context of the change as I consider the commit messages.
|
| 580/2 |
2026/09/02 22:11 |
tool |
git-log |
0m
Error:
Args: map[Count:20 PathPrefix:fs/configfs/symlink.c SourcePath:fs/configfs/symlink.c SymbolName:configfs_symlink]
Results:
|
| 581/2 |
2026/09/02 22:11 |
llm |
fixes-finder |
0m
Model:
Tokens:
input: 11861
output: 41
thoughts: 14
Thoughts: Here's my summary, as though I'm thinking through this process:
**Thinking Through Git Log Usage for Symbol Navigation**
Okay, so I need to find all instances of a specific symbol and its source path within a project's history. Given my expertise, the obvious route to take here is `git log`. But I need to be precise. Standard `git log` on its own will be too broad, especially in a large repository. I need to be able to filter by both the symbol's name and its file location. That leads me to think, I need to use `git log` in conjunction with options for searching specific patterns. I'm imagining how to combine it with a search pattern, perhaps using a combination of flags to zero in on just the changes I'm interested in. The trick will be crafting the exact command that gets the output format I need in the right way. This is going to require me to quickly formulate a query and see the output so I can assess whether it is suitable to my needs.
|
| 582/2 |
2026/09/02 22:11 |
tool |
git-log |
0m
Args: map[Count:20 SourcePath:fs/configfs/symlink.c SymbolName:configfs_symlink]
Results: map[Output:9a73d78cda75 [PATCH] configfs: Fix failing symlink() making rmdir() fail
]
|
| 583/2 |
2026/09/02 22:11 |
llm |
fixes-finder |
0m
Model:
Tokens:
input: 12173
output: 30
thoughts: 40
Thoughts: Okay, let's dive into this. Here's how I'd approach this task, as if these were my internal thoughts:
**Analyzing `fs/configfs/symlink.c` Commit History**
Alright, I need to get a clear picture of the evolution of the `fs/configfs/symlink.c` file. I'm going to leverage `git log` β the workhorse for digging through the commit history. The core of this is pinpointing the commits that *actually* touched `fs/configfs/symlink.c`. To make sure I zero in on only those relevant changes, I'll use `PathPrefix="fs/configfs/symlink.c"` as a filter. This gives me a much more focused view, avoiding distractions from unrelated parts of the codebase. I expect this will give me the most efficient way to understand the specific changes that have been applied to this part of the filesystem. With the output of `git log` I should be able to look at the relevant commits and understand the evolution of the symlink logic within ConfigFS.
|
| 584/2 |
2026/09/02 22:11 |
tool |
git-log |
0m
Args: map[Count:50 PathPrefix:fs/configfs/symlink.c]
Results: map[Output:1387aedb391a configfs: mark pinned dentries persistent
1b25dea3867a configfs:get_target() - release path as soon as we grab configfs_item reference
4609e1f18e19 fs: port ->permission() to pass mnt_idmap
7a77db95511c fs: port ->symlink() to pass mnt_idmap
fa60ce2cb450 treewide: remove editor modelines and cruft
549c7297717c fs: make helpers idmap mount aware
47291baa8ddf namei: make permission helpers idmapped mount aware
e2f238f7d5a1 configfs: calculate the depth of parent item
e9c03af21cc7 configfs: calculate the symlink target only once
351e5d869e5a configfs: fix a deadlock in configfs_symlink()
328970de0e39 treewide: Replace GPLv2 boilerplate/reference with SPDX - rule 145
1823342a1f2b configfs: replace strncpy with memcpy
aa293583f0fe configfs: make ci_type field, some pointers and function arguments const
ba80aa909c99 configfs: Fix race between create_link and configfs_rmdir
dfeef68862ed vfs: remove ".readlink = generic_readlink" assignments
fceef393a538 switch ->get_link() to delayed_call, kill ->put_link()
cd3417c8fc95 kill free_page_put_link()
6b2553918d8b replace ->follow_link() with new method that could stay in RCU mode
ecc087ff1435 new helper: free_page_put_link()
5f2c4179e129 switch ->put_link() from dentry to inode
6e77137b363b don't pass nameidata to ->follow_link()
680baacbca69 new ->follow_link() and ->put_link() calling conventions
b7c177fcd202 configfs: kill configfs_sb
81d44ed159e3 configfs: don't bother with checks for mkdir/rmdir/unlink/symlink in root
5a0e3ad6af86 include cleanup: Update gfp.h and slab.h includes to prepare for breaking implicit slab.h inclusion from percpu.h
9b6e31021122 Fix configfs leak
3c48f23adada configfs: Fix Trivial Warning in fs/configfs/symlink.c
421748ecde8e [PATCH] assorted path_lookup() -> kern_path() conversions
2a109f2a4155 [PATCH] configfs: Prevent userspace from creating new entries under attaching directories
9a73d78cda75 [PATCH] configfs: Fix failing symlink() making rmdir() fail
4768e9b18dc6 [PATCH] configfs: Fix symlink() to a removing item
e75206517504 configfs: call drop_link() to cleanup after create_link() failure
5301a77da2da configfs: Protect configfs_dirent s_links list mutations
6f6107640625 configfs: Introduce configfs_dirent_lock
8e24eea72806 fs: replace remaining __FUNCTION__ occurrences
1d957f9bf87d Introduce path_put()
4ac9137858e0 Embed a struct path into struct nameidata instead of nd->{dentry,mnt}
754661f143e7 [PATCH] mark struct inode_operations const 1
e7515d065d09 configfs: Clear up a few extra spaces where there should be TABs.
1a1974fd4533 [PATCH] BUG_ON() Conversion in fs/configfs/
3d0f89bb1694 configfs: Add permission and ownership to configfs objects.
7063fbf22611 [PATCH] configfs: User-driven configuration filesystem
]
|
| 585/2 |
2026/09/02 22:11 |
llm |
fixes-finder |
0m
Model:
Tokens:
input: 13534
output: 27
thoughts: 85
Thoughts: Here's my take on those initial thoughts:
**Analyzing the Commit History**
Okay, time to dive in. I need to get a handle on what's been happening in this particular codebase, so my initial instinct is to start by examining those commit hashes β `351e5d869e5a`, `ba80aa909c99`, `7063fbf22611`, `1387aedb391a`, and so on. I'll need to dig into the details to understand the evolution of the project.
Since I have to start somewhere, I'll begin by inspecting `351e5d869e5a`. I think the best way to do that initially is with `git show`. It should give me a good, focused view of the changes associated with that specific commit. This will allow me to see the commit message, the author, and most importantly, the actual code diffs. From there, I can build an understanding of the impact of that particular change. Then, I can move on to the next.
|
| 586/2 |
2026/09/02 22:11 |
tool |
git-show |
0m
Args:
Results: map[Output:commit 351e5d869e5ac10cb40c78b5f2d7dfc816ad4587
Author: Al Viro <viro@zeniv.linux.org.uk>
Date: Sat Aug 3 11:51:18 2019 -0400
configfs: fix a deadlock in configfs_symlink()
Configfs abuses symlink(2). Unlike the normal filesystems, it
wants the target resolved at symlink(2) time, like link(2) would've
done. The problem is that ->symlink() is called with the parent
directory locked exclusive, so resolving the target inside the
->symlink() is easily deadlocked.
Short of really ugly games in sys_symlink() itself, all we can
do is to unlock the parent before resolving the target and
relock it after. However, that invalidates the checks done
by the caller of ->symlink(), so we have to
* check that dentry is still where it used to be
(it couldn't have been moved, but it could've been unhashed)
* recheck that it's still negative (somebody else
might've successfully created a symlink with the same name
while we were looking the target up)
* recheck the permissions on the parent directory.
Cc: stable@vger.kernel.org
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
Signed-off-by: Christoph Hellwig <hch@lst.de>
diff --git a/fs/configfs/symlink.c b/fs/configfs/symlink.c
index 91eac6c55e07..f3881e4caedd 100644
--- a/fs/configfs/symlink.c
+++ b/fs/configfs/symlink.c
@@ -143,11 +143,42 @@ int configfs_symlink(struct inode *dir, struct dentry *dentry, const char *symna
!type->ct_item_ops->allow_link)
goto out_put;
+ /*
+ * This is really sick. What they wanted was a hybrid of
+ * link(2) and symlink(2) - they wanted the target resolved
+ * at syscall time (as link(2) would've done), be a directory
+ * (which link(2) would've refused to do) *AND* be a deep
+ * fucking magic, making the target busy from rmdir POV.
+ * symlink(2) is nothing of that sort, and the locking it
+ * gets matches the normal symlink(2) semantics. Without
+ * attempts to resolve the target (which might very well
+ * not even exist yet) done prior to locking the parent
+ * directory. This perversion, OTOH, needs to resolve
+ * the target, which would lead to obvious deadlocks if
+ * attempted with any directories locked.
+ *
+ * Unfortunately, that garbage is userland ABI and we should've
+ * said "no" back in 2005. Too late now, so we get to
+ * play very ugly games with locking.
+ *
+ * Try *ANYTHING* of that sort in new code, and you will
+ * really regret it. Just ask yourself - what could a BOFH
+ * do to me and do I want to find it out first-hand?
+ *
+ * AV, a thoroughly annoyed bastard.
+ */
+ inode_unlock(dir);
ret = get_target(symname, &path, &target_item, dentry->d_sb);
+ inode_lock(dir);
if (ret)
goto out_put;
- ret = type->ct_item_ops->allow_link(parent_item, target_item);
+ if (dentry->d_inode || d_unhashed(dentry))
+ ret = -EEXIST;
+ else
+ ret = inode_permission(dir, MAY_WRITE | MAY_EXEC);
+ if (!ret)
+ ret = type->ct_item_ops->allow_link(parent_item, target_item);
if (!ret) {
mutex_lock(&configfs_symlink_mutex);
ret = create_link(parent_item, target_item, dentry);
]
|
| 587/2 |
2026/09/02 22:11 |
llm |
fixes-finder |
0m
Model:
Tokens:
input: 14999
output: 25
thoughts: 634
Thoughts: **Analyzing a ConfigFS Deadlock Fix**
Okay, let's break this down. I'm looking at commit `351e5d869e5a`, which addresses a deadlock in `configfs_symlink()`. Before this commit, the code *didn't* unlock the `dir` inode (using `inode_unlock(dir)`) during the symlink creation process. I can see that in the original code, the unlock was absent, but the commit added `inode_unlock(dir)`. The crucial point is that this unlocking happens *before* resolving the target path with `get_target()`. Then after the target is gotten, the commit re-locks the `dir`.
The fix introduces a vulnerability, because by unlocking `dir` while resolving the target, it allowed `configfs_rmdir()` to potentially run concurrently. This is the root of the problem. If `configfs_rmdir()` runs concurrently, it can detach default groups and free the parent structure (`parent_item`) *while* another thread (thread 1) is still holding a reference to that `parent_item`βspecifically, the `subsys_group` embedded within the root object.
The explanation clarifies a race condition where:
1. **Thread 1 (symlinkat):** Enters `configfs_symlink()` holding `inode_lock(dir)` on, for instance, `ports/1/subsystems`. It then acquires a reference to `parent_item`. Crucially, it *drops* the lock (`inode_unlock(dir)`) to resolve the symlink target without deadlock.
2. **Thread 2 (rmdir):** Calls `configfs_rmdir()` on, say, `ports/1`. `configfs_rmdir()` then calls `configfs_detach_group()`, which recursively locks all default groups. Because Thread 1 had dropped the lock, Thread 2 acquires the lock, detaches the items, and unlocks them. Then, in this case, `unlink_group()` is called which is the start of the whole nvmet_port struct being freed via `kfree()`.
3. **Thread 1 resumes:** Re-acquires `inode_lock(dir)`. The problem is that it then calls `allow_link()` on a `parent_item` that *now points to freed memory*. This is the Use-After-Free vulnerability.
Alright, let's be thorough, I should look at related commits like `ba80aa909c99` and `1387aedb391a` to ensure this is the definitive fix, and to see if there are any other subtle implications.
|
| 588/2 |
2026/09/02 22:11 |
tool |
git-show |
0m
Args:
Results: map[Output:commit ba80aa909c99802c428682c352b0ee0baac0acd3
Author: Nicholas Bellinger <nab@linux-iscsi.org>
Date: Thu Jun 8 04:51:54 2017 +0000
configfs: Fix race between create_link and configfs_rmdir
This patch closes a long standing race in configfs between
the creation of a new symlink in create_link(), while the
symlink target's config_item is being concurrently removed
via configfs_rmdir().
This can happen because the symlink target's reference
is obtained by config_item_get() in create_link() before
the CONFIGFS_USET_DROPPING bit set by configfs_detach_prep()
during configfs_rmdir() shutdown is actually checked..
This originally manifested itself on ppc64 on v4.8.y under
heavy load using ibmvscsi target ports with Novalink API:
[ 7877.289863] rpadlpar_io: slot U8247.22L.212A91A-V1-C8 added
[ 7879.893760] ------------[ cut here ]------------
[ 7879.893768] WARNING: CPU: 15 PID: 17585 at ./include/linux/kref.h:46 config_item_get+0x7c/0x90 [configfs]
[ 7879.893811] CPU: 15 PID: 17585 Comm: targetcli Tainted: G O 4.8.17-customv2.22 #12
[ 7879.893812] task: c00000018a0d3400 task.stack: c0000001f3b40000
[ 7879.893813] NIP: d000000002c664ec LR: d000000002c60980 CTR: c000000000b70870
[ 7879.893814] REGS: c0000001f3b43810 TRAP: 0700 Tainted: G O (4.8.17-customv2.22)
[ 7879.893815] MSR: 8000000000029033 <SF,EE,ME,IR,DR,RI,LE> CR: 28222242 XER: 00000000
[ 7879.893820] CFAR: d000000002c664bc SOFTE: 1
GPR00: d000000002c60980 c0000001f3b43a90 d000000002c70908 c0000000fbc06820
GPR04: c0000001ef1bd900 0000000000000004 0000000000000001 0000000000000000
GPR08: 0000000000000000 0000000000000001 d000000002c69560 d000000002c66d80
GPR12: c000000000b70870 c00000000e798700 c0000001f3b43ca0 c0000001d4949d40
GPR16: c00000014637e1c0 0000000000000000 0000000000000000 c0000000f2392940
GPR20: c0000001f3b43b98 0000000000000041 0000000000600000 0000000000000000
GPR24: fffffffffffff000 0000000000000000 d000000002c60be0 c0000001f1dac490
GPR28: 0000000000000004 0000000000000000 c0000001ef1bd900 c0000000f2392940
[ 7879.893839] NIP [d000000002c664ec] config_item_get+0x7c/0x90 [configfs]
[ 7879.893841] LR [d000000002c60980] check_perm+0x80/0x2e0 [configfs]
[ 7879.893842] Call Trace:
[ 7879.893844] [c0000001f3b43ac0] [d000000002c60980] check_perm+0x80/0x2e0 [configfs]
[ 7879.893847] [c0000001f3b43b10] [c000000000329770] do_dentry_open+0x2c0/0x460
[ 7879.893849] [c0000001f3b43b70] [c000000000344480] path_openat+0x210/0x1490
[ 7879.893851] [c0000001f3b43c80] [c00000000034708c] do_filp_open+0xfc/0x170
[ 7879.893853] [c0000001f3b43db0] [c00000000032b5bc] do_sys_open+0x1cc/0x390
[ 7879.893856] [c0000001f3b43e30] [c000000000009584] system_call+0x38/0xec
[ 7879.893856] Instruction dump:
[ 7879.893858] 409d0014 38210030 e8010010 7c0803a6 4e800020 3d220000 e94981e0 892a0000
[ 7879.893861] 2f890000 409effe0 39200001 992a0000 <0fe00000> 4bffffd0 60000000 60000000
[ 7879.893866] ---[ end trace 14078f0b3b5ad0aa ]---
To close this race, go ahead and obtain the symlink's target
config_item reference only after the existing CONFIGFS_USET_DROPPING
check succeeds.
This way, if configfs_rmdir() wins create_link() will return -ENONET,
and if create_link() wins configfs_rmdir() will return -EBUSY.
Reported-by: Bryant G. Ly <bryantly@linux.vnet.ibm.com>
Tested-by: Bryant G. Ly <bryantly@linux.vnet.ibm.com>
Signed-off-by: Nicholas Bellinger <nab@linux-iscsi.org>
Signed-off-by: Christoph Hellwig <hch@lst.de>
Cc: stable@vger.kernel.org
diff --git a/fs/configfs/symlink.c b/fs/configfs/symlink.c
index a6ab012a2c6a..c8aabba502f6 100644
--- a/fs/configfs/symlink.c
+++ b/fs/configfs/symlink.c
@@ -83,14 +83,13 @@ static int create_link(struct config_item *parent_item,
ret = -ENOMEM;
sl = kmalloc(sizeof(struct configfs_symlink), GFP_KERNEL);
if (sl) {
- sl->sl_target = config_item_get(item);
spin_lock(&configfs_dirent_lock);
if (target_sd->s_type & CONFIGFS_USET_DROPPING) {
spin_unlock(&configfs_dirent_lock);
- config_item_put(item);
kfree(sl);
return -ENOENT;
}
+ sl->sl_target = config_item_get(item);
list_add(&sl->sl_list, &target_sd->s_links);
spin_unlock(&configfs_dirent_lock);
ret = configfs_create_link(sl, parent_item->ci_dentry,
]
|
| 589/2 |
2026/09/02 22:11 |
llm |
fixes-finder |
0m
Model:
Tokens:
input: 18843
output: 26
thoughts: 35
Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience:
**Analyzing Recent Code Revisions**
Alright, time to get my hands dirty. I need to dive into some recent commits. Let's start with `1387aedb391a` and `1b25dea3867a` β those look like a good place to begin. I'll need to review the diffs and assess the impact. I'll also want to expand the scope beyond those two; I should probably pull up the last few days of activity and do a quick skim through those commits, too. My gut tells me something significant, possibly related to that recent performance regression, or perhaps even something related to the security patch we just implemented, is lurking within those updates. I'll need to keep an eye out for any unexpected changes or deviations from the established coding standards. Then I will need to determine if I need to dig deeper into the code to determine the root cause, or if the commit messages make it obvious.
|
| 590/2 |
2026/09/02 22:11 |
tool |
git-show |
0m
Args:
Results: map[Output:commit 1387aedb391ac6e790fa941f29570d6f72113dd9
Author: Al Viro <viro@zeniv.linux.org.uk>
Date: Tue May 12 12:18:21 2026 -0400
configfs: mark pinned dentries persistent
on the removal side we can (finally) get rid of __simple_unlink()
and __simple_rmdir() kludges now that dentries in question are
properly marked persistent - simple_unlink() and simple_rmdir()
will do the right thing for those.
Reviewed-by: Jan Kara <jack@suse.cz>
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
diff --git a/fs/configfs/dir.c b/fs/configfs/dir.c
index 4836ffb3b70f..479d37b8d806 100644
--- a/fs/configfs/dir.c
+++ b/fs/configfs/dir.c
@@ -315,9 +315,7 @@ static int configfs_create_dir(struct config_item *item, struct dentry *dentry,
inode->i_fop = &configfs_dir_operations;
/* directory inodes start off with i_nlink == 2 (for "." entry) */
inc_nlink(inode);
- d_instantiate(dentry, inode);
- /* already hashed */
- dget(dentry); /* pin directory dentries in core */
+ d_make_persistent(dentry, inode);
inc_nlink(d_inode(p));
item->ci_dentry = dentry;
return 0;
@@ -385,8 +383,7 @@ int configfs_create_link(struct configfs_dirent *target, struct dentry *parent,
inode->i_link = body;
inode->i_op = &configfs_symlink_inode_operations;
- d_instantiate(dentry, inode);
- dget(dentry); /* pin link dentries in core */
+ d_make_persistent(dentry, inode);
return 0;
out_remove:
@@ -413,12 +410,8 @@ static void configfs_remove_dir(struct dentry *d)
configfs_remove_dirent(d);
if (d_really_is_positive(d)) {
- if (likely(simple_empty(d))) {
- __simple_rmdir(d_inode(parent),d);
- dput(d);
- } else {
+ if (unlikely(simple_rmdir(d_inode(parent), d)))
pr_warn("remove_dir (%pd): attributes remain", d);
- }
}
pr_debug(" o %pd removing done (%d)\n", d, d_count(d));
diff --git a/fs/configfs/symlink.c b/fs/configfs/symlink.c
index f3f79c67add5..31eb28b27309 100644
--- a/fs/configfs/symlink.c
+++ b/fs/configfs/symlink.c
@@ -229,9 +229,8 @@ int configfs_unlink(struct inode *dir, struct dentry *dentry)
spin_lock(&configfs_dirent_lock);
list_del_init(&sd->s_sibling);
spin_unlock(&configfs_dirent_lock);
- configfs_drop_dentry(sd, dentry->d_parent);
- dput(dentry);
configfs_put(sd);
+ simple_unlink(dir, dentry);
/*
* drop_link() must be called before
]
|
| 591/2 |
2026/09/02 22:11 |
llm |
fixes-finder |
0m
Model:
Tokens:
input: 20138
output: 25
thoughts: 16
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:
**Checking a Hash: Initial Assessment**
Alright, let's take a look at this hash: `1b25dea3867a`. My first instinct is to immediately try and categorize it. The length immediately screams a few possibilities: it could be an MD5 hash (though increasingly less common now), a truncated SHA-1 (also fading), a part of a larger object identifier, or perhaps even something proprietary. I'd need the context, of course, to be certain, but the brevity suggests something relatively compact.
My mind immediately starts running through potential scenarios. Is this related to a specific project? A known vulnerability? A log file analysis? Based on the lack of context, my next thought is to cross-reference it with known databases. I'd typically query VirusTotal, haveibeenpwned, and any internal threat intelligence feeds I have access to. A quick lookup might give me some immediate leads, revealing if it's been flagged as malicious, associated with a breach, or linked to any public vulnerabilities.
If those initial checks prove fruitless, I'd probably move on to a local analysis. I'd want to check this within the current set of data I'm using, and run an quick MD5 hash on it to confirm my assumptions about the MD5. I also might want to consider running it through a local hash database to compare it to anything I have saved from previous examinations.
My next line of enquiry depends heavily on the initial findings, but my starting point is always context and then quickly checking known databases for known artifacts. That gets me the biggest bang for my buck, time-wise. Then the real fun starts β I'd need to dive deeper based on what the initial search reveals (or doesn't reveal).
|
| 592/2 |
2026/09/02 22:11 |
tool |
git-show |
0m
Args:
Results: map[Output:commit 1b25dea3867abc9bad6f0337d395c6f0ce4e4f6f
Author: Al Viro <viro@zeniv.linux.org.uk>
Date: Thu Sep 4 21:47:50 2025 -0400
configfs:get_target() - release path as soon as we grab configfs_item reference
... and get rid of path argument - it turns into a local variable in get_target()
Reviewed-by: Jan Kara <jack@suse.cz>
Reviewed-by: Christian Brauner <brauner@kernel.org>
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
diff --git a/fs/configfs/symlink.c b/fs/configfs/symlink.c
index 69133ec1fac2..f3f79c67add5 100644
--- a/fs/configfs/symlink.c
+++ b/fs/configfs/symlink.c
@@ -114,26 +114,21 @@ static int create_link(struct config_item *parent_item,
}
-static int get_target(const char *symname, struct path *path,
- struct config_item **target, struct super_block *sb)
+static int get_target(const char *symname, struct config_item **target,
+ struct super_block *sb)
{
+ struct path path __free(path_put) = {};
int ret;
- ret = kern_path(symname, LOOKUP_FOLLOW|LOOKUP_DIRECTORY, path);
- if (!ret) {
- if (path->dentry->d_sb == sb) {
- *target = configfs_get_config_item(path->dentry);
- if (!*target) {
- ret = -ENOENT;
- path_put(path);
- }
- } else {
- ret = -EPERM;
- path_put(path);
- }
- }
-
- return ret;
+ ret = kern_path(symname, LOOKUP_FOLLOW|LOOKUP_DIRECTORY, &path);
+ if (ret)
+ return ret;
+ if (path.dentry->d_sb != sb)
+ return -EPERM;
+ *target = configfs_get_config_item(path.dentry);
+ if (!*target)
+ return -ENOENT;
+ return 0;
}
@@ -141,7 +136,6 @@ int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
struct dentry *dentry, const char *symname)
{
int ret;
- struct path path;
struct configfs_dirent *sd;
struct config_item *parent_item;
struct config_item *target_item = NULL;
@@ -188,7 +182,7 @@ int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
* AV, a thoroughly annoyed bastard.
*/
inode_unlock(dir);
- ret = get_target(symname, &path, &target_item, dentry->d_sb);
+ ret = get_target(symname, &target_item, dentry->d_sb);
inode_lock(dir);
if (ret)
goto out_put;
@@ -210,7 +204,6 @@ int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
}
config_item_put(target_item);
- path_put(&path);
out_put:
config_item_put(parent_item);
]
|
| 593/2 |
2026/09/02 22:11 |
llm |
fixes-finder |
0m
Model:
Tokens:
input: 21572
output: 20
thoughts: 972
Thoughts: **Analyzing a Kernel Bug: A Deep Dive into a ConfigFS Deadlock Fix and its Implications**
Okay, let's break this down. We're looking at commit `351e5d869e5a`, a seemingly innocuous fix for a deadlock in `configfs_symlink()`. My expert eye immediately hones in on the core of the problem: why did Al Viro introduce those `inode_unlock(dir)` and `inode_lock(dir)` calls? It seems like he was trying to address a critical locking issue. Before this commit, `configfs_symlink()` held the inode lock on the parent directory (`dir`) for the *entire* duration of the function, a practice inherited from the VFS call to the directory's `symlink` operation.
The original problem, as I see it, was this: Concurrent calls to `configfs_rmdir()` on an ancestor directory (like `ports/1` in the example) would trigger `configfs_detach_group()`. This function, crucially, tries to acquire the lock on a child directory (e.g., `ports/1/subsystems`). Because `configfs_symlink()` held the lock on `dir`, `configfs_detach_group()` would block, leading to a deadlock. The ancestor directory could never be detached/freed while `configfs_symlink()` was running.
Viro's solution was to unlock the parent directory (`dir`) before resolving the symlink target with `get_target()`, and then re-lock it afterward. This clearly breaks the continuous lock hold, so he includes checks to make sure the target has not changed, the dentry is still negative, and the permissions are still valid, which shows he understands the implications of breaking the lock. This is a very standard approach to avoiding deadlocks in the kernel.
But here's where it gets interesting, and concerning. When you introduce `inode_unlock(dir)`, a race condition is introduced. This is where I start to see the potential for a Use-After-Free (UAF) bug.
Here's my line of thinking:
1. **Missing Protection**: The `parent_item` is acquired *before* unlocking. If the parent is an embedded default group within a larger struct (like an `nvmet_port` containing `subsys_group`), unlocking the parent makes it vulnerable.
2. **Concurrency Window**: With the `dir` lock dropped, a concurrent `configfs_rmdir` on the ancestor can now acquire the `dir` lock in `configfs_detach_group()`. It can detach the default group and *unlink the parent*, potentially freeing the entire `nvmet_port` struct. Default groups do not pin the container struct.
3. **Incomplete Checks**: After relocking the `dir`, `configfs_symlink()` checks the dentry's hash status and permissions, but *it doesn't verify if the directory had been removed*. Furthermore, it did *not* pin the root object to prevent the container from being freed while unlocked. This is a crucial oversight!
This means that this commit *directly* introduced a race condition β a potential UAF vulnerability because the directory can be freed out from under the function's feet, leaving `configfs_symlink` to potentially access freed memory. The danger of this is serious.
Now, I need to verify my hypothesis. I'm going to scan the git log to see when the `s_frag` or `frag_dead` fragments were introduced into the code. This will tell me if the code *knew* about a mechanism for checking for a directory being removed, and if it could have used that mechanism to provide protection. I need to understand when these concepts and code elements entered the scene to ascertain if the solution was available and omitted, or if there was no such thing available at that time.
|
| 594/2 |
2026/09/02 22:11 |
tool |
git-log |
10m
Error:
Args: map[CodeRegexp:frag_dead]
Results:
|
| 595/2 |
2026/09/02 22:21 |
llm |
fixes-finder |
0m
Model:
Tokens:
input: 23434
output: 30
thoughts: 12
Thoughts: Here's my take on those thoughts, tailored for an expert audience:
**Adding PathPrefix for configfs Integration**
Okay, so I'm looking at how we're integrating `configfs` into the system. The current setup, while functional, could definitely benefit from a more explicit path definition. The primary thought is to introduce `PathPrefix="fs/configfs"`. This is about establishing a clear, well-defined location within the filesystem hierarchy for our `configfs` mounts. This provides better organizational structure and also offers a central and predictable entry point for accessing the configuration data managed by `configfs`. It improves clarity in our system by explicitly saying, that configfs belongs at the "fs/configfs" location. This should help with debugging and future development efforts.
|
| 596/2 |
2026/09/02 22:21 |
tool |
git-log |
0m
Args: map[CodeRegexp:frag_dead PathPrefix:fs/configfs]
Results: map[Output:b0841eefd969 configfs: provide exclusion between IO and removals
47320fbe11a6 configfs: new object reprsenting tree fragments
]
|
| 597/2 |
2026/09/02 22:21 |
llm |
fixes-finder |
0m
Model:
Tokens:
input: 23695
output: 26
thoughts: 35
Thoughts: Okay, here's my take on those thoughts, framed as an expert's internal monologue:
**Investigating Code Introductions**
Right, let's get down to brass tacks. I need to figure out the timeline for the introduction of those two commit hashes β `b0841eefd969` and `47320fbe11a6`. Understanding *when* these were integrated is crucial for whatever I'm troubleshooting or analyzing. Knowing the order, or even just approximate dates, narrows the scope considerably. It's the first logical step.
|
| 598/2 |
2026/09/02 22:21 |
tool |
git-show |
0m
Args:
Results: map[Output:commit b0841eefd9693827afb9888235e26ddd098f9cef
Author: Al Viro <viro@zeniv.linux.org.uk>
Date: Sat Aug 31 09:43:43 2019 +0200
configfs: provide exclusion between IO and removals
Make sure that attribute methods are not called after the item
has been removed from the tree. To do so, we
* at the point of no return in removals, grab ->frag_sem
exclusive and mark the fragment dead.
* call the methods of attributes with ->frag_sem taken
shared and only after having verified that the fragment is still
alive.
The main benefit is for method instances - they are
guaranteed that the objects they are accessing *and* all ancestors
are still there. Another win is that we don't need to bother
with extra refcount on config_item when opening a file -
the item will be alive for as long as it stays in the tree, and
we won't touch it/attributes/any associated data after it's
been removed from the tree.
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
Signed-off-by: Christoph Hellwig <hch@lst.de>
diff --git a/fs/configfs/dir.c b/fs/configfs/dir.c
index 1df65c2440da..79fc25aaa8cd 100644
--- a/fs/configfs/dir.c
+++ b/fs/configfs/dir.c
@@ -1461,6 +1461,7 @@ static int configfs_rmdir(struct inode *dir, struct dentry *dentry)
struct config_item *item;
struct configfs_subsystem *subsys;
struct configfs_dirent *sd;
+ struct configfs_fragment *frag;
struct module *subsys_owner = NULL, *dead_item_owner = NULL;
int ret;
@@ -1518,6 +1519,16 @@ static int configfs_rmdir(struct inode *dir, struct dentry *dentry)
}
} while (ret == -EAGAIN);
+ frag = sd->s_frag;
+ if (down_write_killable(&frag->frag_sem)) {
+ spin_lock(&configfs_dirent_lock);
+ configfs_detach_rollback(dentry);
+ spin_unlock(&configfs_dirent_lock);
+ return -EINTR;
+ }
+ frag->frag_dead = true;
+ up_write(&frag->frag_sem);
+
/* Get a working ref for the duration of this function */
item = configfs_get_config_item(dentry);
@@ -1821,6 +1832,12 @@ void configfs_unregister_group(struct config_group *group)
struct configfs_subsystem *subsys = group->cg_subsys;
struct dentry *dentry = group->cg_item.ci_dentry;
struct dentry *parent = group->cg_item.ci_parent->ci_dentry;
+ struct configfs_dirent *sd = dentry->d_fsdata;
+ struct configfs_fragment *frag = sd->s_frag;
+
+ down_write(&frag->frag_sem);
+ frag->frag_dead = true;
+ up_write(&frag->frag_sem);
inode_lock_nested(d_inode(parent), I_MUTEX_PARENT);
spin_lock(&configfs_dirent_lock);
@@ -1947,12 +1964,18 @@ void configfs_unregister_subsystem(struct configfs_subsystem *subsys)
struct config_group *group = &subsys->su_group;
struct dentry *dentry = group->cg_item.ci_dentry;
struct dentry *root = dentry->d_sb->s_root;
+ struct configfs_dirent *sd = dentry->d_fsdata;
+ struct configfs_fragment *frag = sd->s_frag;
if (dentry->d_parent != root) {
pr_err("Tried to unregister non-subsystem!\n");
return;
}
+ down_write(&frag->frag_sem);
+ frag->frag_dead = true;
+ up_write(&frag->frag_sem);
+
inode_lock_nested(d_inode(root),
I_MUTEX_PARENT);
inode_lock_nested(d_inode(dentry), I_MUTEX_CHILD);
diff --git a/fs/configfs/file.c b/fs/configfs/file.c
index 9d5e888df60b..fb65b706cc0d 100644
--- a/fs/configfs/file.c
+++ b/fs/configfs/file.c
@@ -48,22 +48,32 @@ struct configfs_buffer {
};
};
+static inline struct configfs_fragment *to_frag(struct file *file)
+{
+ struct configfs_dirent *sd = file->f_path.dentry->d_fsdata;
+
+ return sd->s_frag;
+}
-static int fill_read_buffer(struct configfs_buffer * buffer)
+static int fill_read_buffer(struct file *file, struct configfs_buffer *buffer)
{
- ssize_t count;
+ struct configfs_fragment *frag = to_frag(file);
+ ssize_t count = -ENOENT;
if (!buffer->page)
buffer->page = (char *) get_zeroed_page(GFP_KERNEL);
if (!buffer->page)
return -ENOMEM;
- count = buffer->attr->show(buffer->item, buffer->page);
+ down_read(&frag->frag_sem);
+ if (!frag->frag_dead)
+ count = buffer->attr->show(buffer->item, buffer->page);
+ up_read(&frag->frag_sem);
+
if (count < 0)
return count;
if (WARN_ON_ONCE(count > (ssize_t)SIMPLE_ATTR_SIZE))
return -EIO;
-
buffer->needs_read_fill = 0;
buffer->count = count;
return 0;
@@ -96,7 +106,7 @@ configfs_read_file(struct file *file, char __user *buf, size_t count, loff_t *pp
mutex_lock(&buffer->mutex);
if (buffer->needs_read_fill) {
- retval = fill_read_buffer(buffer);
+ retval = fill_read_buffer(file, buffer);
if (retval)
goto out;
}
@@ -133,6 +143,7 @@ static ssize_t
configfs_read_bin_file(struct file *file, char __user *buf,
size_t count, loff_t *ppos)
{
+ struct configfs_fragment *frag = to_frag(file);
struct configfs_buffer *buffer = file->private_data;
ssize_t retval = 0;
ssize_t len = min_t(size_t, count, PAGE_SIZE);
@@ -148,7 +159,12 @@ configfs_read_bin_file(struct file *file, char __user *buf,
if (buffer->needs_read_fill) {
/* perform first read with buf == NULL to get extent */
- len = buffer->bin_attr->read(buffer->item, NULL, 0);
+ down_read(&frag->frag_sem);
+ if (!frag->frag_dead)
+ len = buffer->bin_attr->read(buffer->item, NULL, 0);
+ else
+ len = -ENOENT;
+ up_read(&frag->frag_sem);
if (len <= 0) {
retval = len;
goto out;
@@ -168,8 +184,13 @@ configfs_read_bin_file(struct file *file, char __user *buf,
buffer->bin_buffer_size = len;
/* perform second read to fill buffer */
- len = buffer->bin_attr->read(buffer->item,
- buffer->bin_buffer, len);
+ down_read(&frag->frag_sem);
+ if (!frag->frag_dead)
+ len = buffer->bin_attr->read(buffer->item,
+ buffer->bin_buffer, len);
+ else
+ len = -ENOENT;
+ up_read(&frag->frag_sem);
if (len < 0) {
retval = len;
vfree(buffer->bin_buffer);
@@ -220,9 +241,16 @@ fill_write_buffer(struct configfs_buffer * buffer, const char __user * buf, size
}
static int
-flush_write_buffer(struct configfs_buffer *buffer, size_t count)
+flush_write_buffer(struct file *file, struct configfs_buffer *buffer, size_t count)
{
- return buffer->attr->store(buffer->item, buffer->page, count);
+ struct configfs_fragment *frag = to_frag(file);
+ int res = -ENOENT;
+
+ down_read(&frag->frag_sem);
+ if (!frag->frag_dead)
+ res = buffer->attr->store(buffer->item, buffer->page, count);
+ up_read(&frag->frag_sem);
+ return res;
}
@@ -252,7 +280,7 @@ configfs_write_file(struct file *file, const char __user *buf, size_t count, lof
mutex_lock(&buffer->mutex);
len = fill_write_buffer(buffer, buf, count);
if (len > 0)
- len = flush_write_buffer(buffer, len);
+ len = flush_write_buffer(file, buffer, len);
if (len > 0)
*ppos += len;
mutex_unlock(&buffer->mutex);
@@ -328,6 +356,7 @@ configfs_write_bin_file(struct file *file, const char __user *buf,
static int __configfs_open_file(struct inode *inode, struct file *file, int type)
{
struct dentry *dentry = file->f_path.dentry;
+ struct configfs_fragment *frag = to_frag(file);
struct configfs_attribute *attr;
struct configfs_buffer *buffer;
int error;
@@ -337,8 +366,13 @@ static int __configfs_open_file(struct inode *inode, struct file *file, int type
if (!buffer)
goto out;
+ error = -ENOENT;
+ down_read(&frag->frag_sem);
+ if (unlikely(frag->frag_dead))
+ goto out_free_buffer;
+
error = -EINVAL;
- buffer->item = configfs_get_config_item(dentry->d_parent);
+ buffer->item = to_item(dentry->d_parent);
if (!buffer->item)
goto out_free_buffer;
@@ -396,6 +430,7 @@ static int __configfs_open_file(struct inode *inode, struct file *file, int type
buffer->read_in_progress = false;
buffer->write_in_progress = false;
file->private_data = buffer;
+ up_read(&frag->frag_sem);
return 0;
out_put_module:
@@ -403,6 +438,7 @@ static int __configfs_open_file(struct inode *inode, struct file *file, int type
out_put_item:
config_item_put(buffer->item);
out_free_buffer:
+ up_read(&frag->frag_sem);
kfree(buffer);
out:
return error;
@@ -412,8 +448,6 @@ static int configfs_release(struct inode *inode, struct file *filp)
{
struct configfs_buffer *buffer = filp->private_data;
- if (buffer->item)
- config_item_put(buffer->item);
module_put(buffer->owner);
if (buffer->page)
free_page((unsigned long)buffer->page);
@@ -439,12 +473,17 @@ static int configfs_release_bin_file(struct inode *inode, struct file *file)
buffer->read_in_progress = false;
if (buffer->write_in_progress) {
+ struct configfs_fragment *frag = to_frag(file);
buffer->write_in_progress = false;
- /* result of ->release() is ignored */
- buffer->bin_attr->write(buffer->item, buffer->bin_buffer,
- buffer->bin_buffer_size);
-
+ down_read(&frag->frag_sem);
+ if (!frag->frag_dead) {
+ /* result of ->release() is ignored */
+ buffer->bin_attr->write(buffer->item,
+ buffer->bin_buffer,
+ buffer->bin_buffer_size);
+ }
+ up_read(&frag->frag_sem);
/* vfree on NULL is safe */
vfree(buffer->bin_buffer);
buffer->bin_buffer = NULL;
]
|
| 599/2 |
2026/09/02 22:21 |
llm |
fixes-finder |
0m
Model:
Tokens:
input: 27421
output: 32
thoughts: 84
Thoughts: **Tracing the Root Cause**
Okay, let's get down to brass tacks here. We have a specific commit, `351e5d869e5a`, and we need to understand its impact. My first thought is to meticulously trace both that individual commit and the larger series it belongs to. The goal is to gain a broader perspective; was the bug introduced in that exact commit, or was it a later manifestation of something that was subtly introduced earlier in the series?
Next, I'll zero in on the commit series, seeing if there are any other commits surrounding the potential issue. I need to understand the evolution of the code in that area.
Finally, I'll dive deep into the `git log` surrounding `351e5d869e5a`. I want to see the context β the changes that were made, the discussions that took place, and any potential clues that will lead me to the root cause of this bug. This is where the real detective work begins.
|
| 600/2 |
2026/09/02 22:21 |
tool |
git-log |
0m
Args: map[Count:10 PathPrefix:fs/configfs Since:6 years]
Results: map[Output:d53ac61b6120 create_default_group(): pass parent's dentry instead of config_group
ad235a65dfff configfs_attach_group(): drop the unused parent_item argument
d4152c7562a8 configs_attach_item(): drop unused parent_item argument
967c898d9def configfs_create(): lift parent timestamp updates into callers
43b45755e80e kill configfs_drop_dentry()
1387aedb391a configfs: mark pinned dentries persistent
1a29e25799bc configfs: dentry refcount needs to be pinned only once
abe91a2aa9c4 switch configfs_detach_{group,item}() to passing dentry
05a4d3d1b676 configfs_remove_dir(), detach_attrs(): switch to passing dentry
34a62943762c populate_attrs(): move cleanup to the sole caller
]
|
| 601/2 |
2026/09/02 22:21 |
llm |
fixes-finder |
0m
Model:
Tokens:
input: 28007
output: 24
thoughts: 25
Thoughts: Okay, here's my interpretation of that thought process, assuming I'm deep in my code:
**Diving into the Git History**
Alright, time to investigate what was happening around the commit `351e5d869e5a`. I need to get a clear picture of the context surrounding that particular point in the project's history. Specifically, I'm thinking, "Let's use `git log` to get a curated view." I want to see which other commits were nearby chronologically, what changes they introduced, and hopefully, I'll identify the upstream and downstream impacts of what went on then. By looking at surrounding commits, I should be able to gain a clearer perspective on the overall situation. This will help me understand why `351e5d869e5a` happened and also guide any troubleshooting or feature work related to the affected area. Gotta get the git-fu strong here!
|
| 602/2 |
2026/09/02 22:21 |
tool |
git-log |
0m
Args: map[Count:20 MessageRegexps:[configfs]]
Results: map[Output:41a9c2b48e25 MAINTAINERS: configfs: split configfs entry in C and Rust parts
95491fb05105 null_blk: fix UBSAN shift-out-of-bounds when zone_size is 0 or overflows
4e1f23f9c33c null_blk: serialize configfs attribute updates with device setup
2cd9e14abece null_blk: serialize configfs attribute shows with the lock
7e7fff518082 null_blk: serialize configfs attribute stores with the lock
e3ef4b1b76d6 null_blk: convert file-scope mutex users to guard(mutex)
1cdfe2fa62b4 null_blk: reject per-device queue resize for shared tag set
2a6357a9b935 null_blk: free zones array on device power-off
c9d293d6bb05 null_blk: register configfs subsystem after creating default devices
017dac767090 null_blk: use DEFINE_MUTEX for the file-scope mutex
9dbf74f4022f usb: gadget: f_tcm: fix deadlock in usbg_make_tpg()
569dd7e5dcff usb: gadget: f_fs: Prevent deadlock during ep0 read loop
0f6bffb5008f usb: gadget: midi2: remove default configfs groups on teardown
f1a8846e0638 nvmet: fix max_qid race between configfs and controller allocation
3d2452c2fb2f selftests: netconsole: add a userdata torture test
a9560343d4e9 netconsole: publish the userdata payload with RCU
003c01b2f0ba rust: configfs: use `LocalModule` for `THIS_MODULE`
751709592d26 nvmet: propagate percpu_ref_init() failure in nvmet_ns_enable()
467e5862ccb0 coresight: etm4x: fix leaked trace id
011291b70ba4 ocfs2: o2hb: quiesce negotiate handlers and timeout work
]
|
| 603/2 |
2026/09/02 22:21 |
llm |
fixes-finder |
0m
Model:
Tokens:
input: 28797
output: 38
thoughts: 97
Thoughts: **Examining Recent Changes in configfs_symlink**
Okay, let's dive back into the `git log` to zero in on `fs/configfs/symlink.c`. I need to revisit that file. My focus is on the `configfs_symlink` function, specifically looking at changes that predated the commit `351e5d869e5a`. To get a good look at the state of `fs/configfs/symlink.c` just *before* that commit, I should use `git show` with the parent commit, referencing the file directly. So, I'll execute `git show 351e5d869e5a^:fs/configfs/symlink.c`. This should give me the view I'm after, essentially the diff of the code just before that commit occurred.
|
| 604/2 |
2026/09/02 22:21 |
tool |
git-show |
0m
Args: map[Commit:351e5d869e5a^:fs/configfs/symlink.c]
Results: map[Output:// SPDX-License-Identifier: GPL-2.0-or-later
/* -*- mode: c; c-basic-offset: 8; -*-
* vim: noexpandtab sw=8 ts=8 sts=0:
*
* symlink.c - operations for configfs symlinks.
*
* Based on sysfs:
* sysfs is Copyright (C) 2001, 2002, 2003 Patrick Mochel
*
* configfs Copyright (C) 2005 Oracle. All rights reserved.
*/
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/namei.h>
#include <linux/slab.h>
#include <linux/configfs.h>
#include "configfs_internal.h"
/* Protects attachments of new symlinks */
DEFINE_MUTEX(configfs_symlink_mutex);
static int item_depth(struct config_item * item)
{
struct config_item * p = item;
int depth = 0;
do { depth++; } while ((p = p->ci_parent) && !configfs_is_root(p));
return depth;
}
static int item_path_length(struct config_item * item)
{
struct config_item * p = item;
int length = 1;
do {
length += strlen(config_item_name(p)) + 1;
p = p->ci_parent;
} while (p && !configfs_is_root(p));
return length;
}
static void fill_item_path(struct config_item * item, char * buffer, int length)
{
struct config_item * p;
--length;
for (p = item; p && !configfs_is_root(p); p = p->ci_parent) {
int cur = strlen(config_item_name(p));
/* back up enough to print this bus id with '/' */
length -= cur;
memcpy(buffer + length, config_item_name(p), cur);
*(buffer + --length) = '/';
}
}
static int create_link(struct config_item *parent_item,
struct config_item *item,
struct dentry *dentry)
{
struct configfs_dirent *target_sd = item->ci_dentry->d_fsdata;
struct configfs_symlink *sl;
int ret;
ret = -ENOENT;
if (!configfs_dirent_is_ready(target_sd))
goto out;
ret = -ENOMEM;
sl = kmalloc(sizeof(struct configfs_symlink), GFP_KERNEL);
if (sl) {
spin_lock(&configfs_dirent_lock);
if (target_sd->s_type & CONFIGFS_USET_DROPPING) {
spin_unlock(&configfs_dirent_lock);
kfree(sl);
return -ENOENT;
}
sl->sl_target = config_item_get(item);
list_add(&sl->sl_list, &target_sd->s_links);
spin_unlock(&configfs_dirent_lock);
ret = configfs_create_link(sl, parent_item->ci_dentry,
dentry);
if (ret) {
spin_lock(&configfs_dirent_lock);
list_del_init(&sl->sl_list);
spin_unlock(&configfs_dirent_lock);
config_item_put(item);
kfree(sl);
}
}
out:
return ret;
}
static int get_target(const char *symname, struct path *path,
struct config_item **target, struct super_block *sb)
{
int ret;
ret = kern_path(symname, LOOKUP_FOLLOW|LOOKUP_DIRECTORY, path);
if (!ret) {
if (path->dentry->d_sb == sb) {
*target = configfs_get_config_item(path->dentry);
if (!*target) {
ret = -ENOENT;
path_put(path);
}
} else {
ret = -EPERM;
path_put(path);
}
}
return ret;
}
int configfs_symlink(struct inode *dir, struct dentry *dentry, const char *symname)
{
int ret;
struct path path;
struct configfs_dirent *sd;
struct config_item *parent_item;
struct config_item *target_item = NULL;
const struct config_item_type *type;
sd = dentry->d_parent->d_fsdata;
/*
* Fake invisibility if dir belongs to a group/default groups hierarchy
* being attached
*/
ret = -ENOENT;
if (!configfs_dirent_is_ready(sd))
goto out;
parent_item = configfs_get_config_item(dentry->d_parent);
type = parent_item->ci_type;
ret = -EPERM;
if (!type || !type->ct_item_ops ||
!type->ct_item_ops->allow_link)
goto out_put;
ret = get_target(symname, &path, &target_item, dentry->d_sb);
if (ret)
goto out_put;
ret = type->ct_item_ops->allow_link(parent_item, target_item);
if (!ret) {
mutex_lock(&configfs_symlink_mutex);
ret = create_link(parent_item, target_item, dentry);
mutex_unlock(&configfs_symlink_mutex);
if (ret && type->ct_item_ops->drop_link)
type->ct_item_ops->drop_link(parent_item,
target_item);
}
config_item_put(target_item);
path_put(&path);
out_put:
config_item_put(parent_item);
out:
return ret;
}
int configfs_unlink(struct inode *dir, struct dentry *dentry)
{
struct configfs_dirent *sd = dentry->d_fsdata;
struct configfs_symlink *sl;
struct config_item *parent_item;
const struct config_item_type *type;
int ret;
ret = -EPERM; /* What lack-of-symlink returns */
if (!(sd->s_type & CONFIGFS_ITEM_LINK))
goto out;
sl = sd->s_element;
parent_item = configfs_get_config_item(dentry->d_parent);
type = parent_item->ci_type;
spin_lock(&configfs_dirent_lock);
list_del_init(&sd->s_sibling);
spin_unlock(&configfs_dirent_lock);
configfs_drop_dentry(sd, dentry->d_parent);
dput(dentry);
configfs_put(sd);
/*
* drop_link() must be called before
* list_del_init(&sl->sl_list), so that the order of
* drop_link(this, target) and drop_item(target) is preserved.
*/
if (type && type->ct_item_ops &&
type->ct_item_ops->drop_link)
type->ct_item_ops->drop_link(parent_item,
sl->sl_target);
spin_lock(&configfs_dirent_lock);
list_del_init(&sl->sl_list);
spin_unlock(&configfs_dirent_lock);
/* Put reference from create_link() */
config_item_put(sl->sl_target);
kfree(sl);
config_item_put(parent_item);
ret = 0;
out:
return ret;
}
static int configfs_get_target_path(struct config_item * item, struct config_item * target,
char *path)
{
char * s;
int depth, size;
depth = item_depth(item);
size = item_path_length(target) + depth * 3 - 1;
if (size > PATH_MAX)
return -ENAMETOOLONG;
pr_debug("%s: depth = %d, size = %d\n", __func__, depth, size);
for (s = path; depth--; s += 3)
strcpy(s,"../");
fill_item_path(target, path, size);
pr_debug("%s: path = '%s'\n", __func__, path);
return 0;
}
static int configfs_getlink(struct dentry *dentry, char * path)
{
struct config_item *item, *target_item;
int error = 0;
item = configfs_get_config_item(dentry->d_parent);
if (!item)
return -EINVAL;
target_item = configfs_get_config_item(dentry);
if (!target_item) {
config_item_put(item);
return -EINVAL;
}
down_read(&configfs_rename_sem);
error = configfs_get_target_path(item, target_item, path);
up_read(&configfs_rename_sem);
config_item_put(item);
config_item_put(target_item);
return error;
}
static const char *configfs_get_link(struct dentry *dentry,
struct inode *inode,
struct delayed_call *done)
{
char *body;
int error;
if (!dentry)
return ERR_PTR(-ECHILD);
body = kzalloc(PAGE_SIZE, GFP_KERNEL);
if (!body)
return ERR_PTR(-ENOMEM);
error = configfs_getlink(dentry, body);
if (!error) {
set_delayed_call(done, kfree_link, body);
return body;
}
kfree(body);
return ERR_PTR(error);
}
const struct inode_operations configfs_symlink_inode_operations = {
.get_link = configfs_get_link,
.setattr = configfs_setattr,
};
]
|
| 605/2 |
2026/09/02 22:21 |
llm |
fixes-finder |
0m
Model:
Tokens:
input: 31702
output: 53
thoughts: 1410
Thoughts: **Analyzing a ConfigFS Race Condition and its Fix**
Okay, so here's the breakdown of this configfs issue. Before commit `351e5d869e5a`, which we now know is `351e5d869e5ac10cb40c78b5f2d7dfc816ad4587`, the `configfs_symlink()` function had a critical flaw. It held `inode_lock(dir)` β let's say on something like `ports/1/subsystems` β throughout its entire execution. Then `configfs_symlink()` was called. Inside, the code acquired a reference to `parent_item`, which points to the relevant config item. Then, crucially, the code needed to resolve the symlink target.
Now, the problem was that while that process of resolving the symlink was happening, another thread could call `configfs_rmdir()` on the parent directory, `ports/1`. This, in turn, called `configfs_detach_group()`, which recursively locks *all* default groups, including the one we're holding the lock on, `ports/1/subsystems`. So, the lock on `ports/1/subsystems` was exclusive. As it was, `rmdir` would block within `configfs_detach_group()`. The root object in `ports/1` couldn't be removed, and the `nvmet_port` wouldn't be freed while `configfs_symlink` was running, so there was no harm, but we could make this more robust.
The fix, introduced in commit `351e5d869e5a`, was to *unlock* the `dir` (i.e., `ports/1/subsystems`) right before resolving the symlink target: `inode_unlock(dir)`. It then re-acquired the lock after getting the target. Great, this prevented the deadlock! However, by unlocking, it opened a race condition. Now, `configfs_rmdir()` could proceed. It would run `configfs_detach_group()`, unlink `ports/1`, and free the `nvmet_port` structure, which *embedded* our `subsys_group`.
The critical point is this: after the unlock, `configfs_symlink` would *re-lock* the directory, but the `parent_item` variable still pointed to the *old* memory location within the now-freed `nvmet_port`. Thus, when `configfs_symlink` continued, it would dereference this now-dangling pointer, leading to a classic use-after-free vulnerability, which is evident in both `allow_link()` and the `config_item_put()`. The root cause is the `inode_unlock`. The commit description is spot on: it "drops `inode_unlock(dir)` to resolve the symlink target without deadlocking."
So, let's confirm the commit: It was the one with short hash `351e5d869e5a`, full hash `351e5d869e5ac10cb40c78b5f2d7dfc816ad4587`. I'm going to flag that one, since that was the culprit. It makes sense, as the fix involves a check `sd->s_frag->frag_dead` after re-acquiring the lock and pinning of the root item.
|
| 606/2 |
2026/09/02 22:21 |
tool |
set-results |
0m
Args: map[FixesHash:351e5d869e5ac10cb40c78b5f2d7dfc816ad4587]
Results: map[FixesHash:351e5d869e5ac10cb40c78b5f2d7dfc816ad4587]
|
| 607/1 |
2026/09/02 22:21 |
action |
format-fixes |
0m
Results: map[Fixes:map[AuthorEmail:viro@zeniv.linux.org.uk AuthorName:Al Viro Hash:351e5d869e5ac10cb40c78b5f2d7dfc816ad4587 Title:configfs: fix a deadlock in configfs_symlink()]]
|
| 608/1 |
2026/09/02 22:21 |
action |
get-maintainers |
0m
Results: map[Recipients:[map[Email:a.hindborg@kernel.org Name:Andreas Hindborg To:false] map[Email:leitao@debian.org Name:Breno Leitao To:true] map[Email:linux-kernel@vger.kernel.org Name: To:true] map[Email:viro@zeniv.linux.org.uk Name:Al Viro To:true]]]
|
| 609/1 |
2026/09/02 22:22 |
action |
get-recent-commits |
0m
Results: map[RecentCommits:configfs: mark pinned dentries persistent
configfs:get_target() - release path as soon as we grab configfs_item reference
fs: port ->permission() to pass mnt_idmap
fs: port ->symlink() to pass mnt_idmap
treewide: remove editor modelines and cruft
fs: make helpers idmap mount aware
namei: make permission helpers idmapped mount aware
configfs: calculate the depth of parent item
configfs: calculate the symlink target only once
configfs: fix a deadlock in configfs_symlink()
treewide: Replace GPLv2 boilerplate/reference with SPDX - rule 145
configfs: replace strncpy with memcpy
configfs: make ci_type field, some pointers and function arguments const
configfs: Fix race between create_link and configfs_rmdir
vfs: remove ".readlink = generic_readlink" assignments
switch ->get_link() to delayed_call, kill ->put_link()
kill free_page_put_link()
replace ->follow_link() with new method that could stay in RCU mode
new helper: free_page_put_link()
switch ->put_link() from dentry to inode
]
|
| 610/1 |
2026/09/02 22:22 |
agent |
description-generator |
0m
Model:
Instruction:
You are an experienced Linux kernel developer tasked with writing a commit description for
a kernel bug fixing commit. The description should start with a one-line summary,
and then include description of the bug being fixed, and how it's fixed by the provided patch.
The one-line summary should describe the change being made, rather than mention the tool that
detected the bug.
The description must not contain lines starting with '#' because they will dropped by git as comments.
The description must not contain lines starting with '--' or '---' (including inline code diffs)
because they may confuse git/patch utilities.
Your final reply should contain only the text of the commit description.
The one-line summary must be not longer than 72 characters.
IMPORTANT: Do not wrap lines manually (e.g., at 80 characters); we will reformat the text
automatically, so keep paragraphs as single lines without newlines.
Generally try to phrase the description without mentioning syzkaller
(avoid phrases like "the bug was triggered by syzkaller" or "the bug was triggered by fuzzer", etc).
How the bug was triggered is generally an irrelevant detail.
Any bug triggered by a fuzzer can also be triggered by a malicious user, or a buggy program.
If the crash is reported by a sanitizer (e.g., KASAN, KMSAN, lockdep), include the relevant
parts of the sanitizer output to illustrate the problem. Exclude less relevant sections,
as the stack trace can be very long. Describe the execution path that leads to the manifestation
of the kernel bug.
Prefer calling several tools at the same time to save round-trips.
Prompt:
The crash that corresponds to the bug is:
==================================================================
BUG: KASAN: slab-use-after-free in nvmet_port_subsys_allow_link+0x41/0x2e0 drivers/nvme/target/configfs.c:1059
Read of size 8 at addr ffff88811c6d44c8 by task syz-executor266/5850
CPU: 0 UID: 0 PID: 5850 Comm: syz-executor266 Not tainted syzkaller #1 PREEMPT(full)
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
Call Trace:
<TASK>
dump_stack_lvl+0xe8/0x150 lib/dump_stack.c:120
print_address_description+0x55/0x1e0 mm/kasan/report.c:378
print_report+0x58/0x70 mm/kasan/report.c:482
kasan_report+0x117/0x150 mm/kasan/report.c:595
nvmet_port_subsys_allow_link+0x41/0x2e0 drivers/nvme/target/configfs.c:1059
configfs_symlink+0x59a/0x1030 fs/configfs/symlink.c:196
vfs_symlink+0x18b/0x330 fs/namei.c:5794
filename_symlinkat+0x1cd/0x410 fs/namei.c:5819
__do_sys_symlinkat fs/namei.c:5839 [inline]
__se_sys_symlinkat+0x4e/0x2b0 fs/namei.c:5834
do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7f648f0cc459
Code: c0 79 93 eb d5 48 8d 7c 1d 00 eb 99 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 d0 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007f648c0911f8 EFLAGS: 00000246 ORIG_RAX: 000000000000010a
RAX: ffffffffffffffda RBX: 00007f648f139028 RCX: 00007f648f0cc459
RDX: 00007f648f139028 RSI: 00000000ffffff9c RDI: 00007f648f139060
RBP: 00007f648f139060 R08: 00007f648c0916c0 R09: 00007f648c0916c0
R10: 00007f648c0916c0 R11: 0000000000000246 R12: ffffffffffffffd0
R13: 0000000000000010 R14: 00007ffe91711350 R15: 00007ffe91711438
</TASK>
Allocated by task 5847:
kasan_save_stack mm/kasan/common.c:57 [inline]
kasan_save_track+0x3e/0x80 mm/kasan/common.c:78
poison_kmalloc_redzone mm/kasan/common.c:398 [inline]
__kasan_kmalloc+0x93/0xb0 mm/kasan/common.c:415
kasan_kmalloc include/linux/kasan.h:263 [inline]
__kmalloc_cache_noprof+0x321/0x600 mm/slub.c:5563
_kmalloc_noprof include/linux/slab.h:991 [inline]
_kzalloc_noprof include/linux/slab.h:1312 [inline]
nvmet_ports_make+0xe6/0xf00 drivers/nvme/target/configfs.c:2051
configfs_mkdir+0x4f6/0x9e0 fs/configfs/dir.c:1360
vfs_mkdir+0x40c/0x620 fs/namei.c:5410
filename_mkdirat+0x285/0x510 fs/namei.c:5443
__do_sys_mkdir fs/namei.c:5470 [inline]
__se_sys_mkdir+0x34/0x150 fs/namei.c:5467
do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
entry_SYSCALL_64_after_hwframe+0x77/0x7f
Freed by task 5847:
kasan_save_stack mm/kasan/common.c:57 [inline]
kasan_save_track+0x3e/0x80 mm/kasan/common.c:78
kasan_save_free_info+0x40/0x50 mm/kasan/generic.c:584
poison_slab_object mm/kasan/common.c:253 [inline]
__kasan_slab_free+0x5c/0x80 mm/kasan/common.c:285
kasan_slab_free include/linux/kasan.h:235 [inline]
slab_free_hook mm/slub.c:2748 [inline]
slab_free mm/slub.c:6499 [inline]
kfree+0x1c5/0x650 mm/slub.c:6792
config_item_cleanup fs/configfs/item.c:128 [inline]
config_item_release+0x13a/0x2d0 fs/configfs/item.c:137
configfs_rmdir+0x885/0x950 fs/configfs/dir.c:1571
vfs_rmdir+0x3e6/0x6a0 fs/namei.c:5515
filename_rmdir+0x292/0x520 fs/namei.c:5572
__do_sys_rmdir fs/namei.c:5595 [inline]
__se_sys_rmdir+0x2e/0x140 fs/namei.c:5592
do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
entry_SYSCALL_64_after_hwframe+0x77/0x7f
The buggy address belongs to the object at ffff88811c6d4000
which belongs to the cache kmalloc-4k of size 4096
The buggy address is located 1224 bytes inside of
freed 4096-byte region [ffff88811c6d4000, ffff88811c6d5000)
The buggy address belongs to the physical page:
page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x11c6d0
head: order:3 mapcount:0 entire_mapcount:0 nr_pages_mapped:0 pincount:0
flags: 0x17ff00000000040(head|node=0|zone=2|lastcpupid=0x7ff)
page_type: f5(slab)
raw: 017ff00000000040 ffff888100042140 dead000000000100 dead000000000122
raw: 0000000000000000 0000000000040004 00000000f5000000 0000000000000000
head: 017ff00000000040 ffff888100042140 dead000000000100 dead000000000122
head: 0000000000000000 0000000000040004 00000000f5000000 0000000000000000
head: 017ff00000000003 fffffffffffffe01 00000000ffffffff 00000000ffffffff
head: ffffffffffffffff 0000000000000000 00000000ffffffff 0000000000000008
page dumped because: kasan: bad access detected
page_owner tracks the page as allocated
page last allocated via order 3, migratetype Unmovable, gfp_mask 0xd20c0(__GFP_IO|__GFP_FS|__GFP_NOWARN|__GFP_NORETRY|__GFP_COMP|__GFP_NOMEMALLOC), pid 5843, tgid 5843 (syz-executor266), ts 63759869729
set_page_owner include/linux/page_owner.h:33 [inline]
post_alloc_hook+0x1f9/0x250 mm/page_alloc.c:1871
prep_new_page mm/page_alloc.c:1879 [inline]
get_page_from_freelist+0x2209/0x2280 mm/page_alloc.c:3943
__alloc_frozen_pages_noprof+0x217/0x5a0 mm/page_alloc.c:5436
alloc_slab_page mm/slub.c:3347 [inline]
allocate_slab+0x7d/0x620 mm/slub.c:3462
new_slab mm/slub.c:3513 [inline]
refill_objects+0x2d5/0x350 mm/slub.c:7410
refill_sheaf mm/slub.c:2885 [inline]
__pcs_replace_empty_main+0x2c8/0x6c0 mm/slub.c:4774
alloc_from_pcs mm/slub.c:4850 [inline]
slab_alloc_node mm/slub.c:4984 [inline]
__kmalloc_cache_noprof+0x39b/0x600 mm/slub.c:5559
_kmalloc_noprof include/linux/slab.h:991 [inline]
do_proc_readlink fs/proc/base.c:1817 [inline]
proc_pid_readlink+0x108/0x230 fs/proc/base.c:1847
vfs_readlink+0x4bb/0x540 fs/namei.c:6407
do_readlinkat+0x218/0x510 fs/stat.c:583
__do_sys_readlinkat fs/stat.c:599 [inline]
__se_sys_readlinkat fs/stat.c:596 [inline]
__x64_sys_readlinkat+0x9a/0xb0 fs/stat.c:596
do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
entry_SYSCALL_64_after_hwframe+0x77/0x7f
page last free pid 5843 tgid 5843 ts 63751651251 stack trace:
reset_page_owner include/linux/page_owner.h:26 [inline]
__free_pages_prepare mm/page_alloc.c:1418 [inline]
__free_frozen_pages+0xc50/0xd30 mm/page_alloc.c:2962
__slab_free+0x274/0x2c0 mm/slub.c:5815
qlink_free mm/kasan/quarantine.c:163 [inline]
qlist_free_all+0x99/0x100 mm/kasan/quarantine.c:179
kasan_quarantine_reduce+0x148/0x160 mm/kasan/quarantine.c:286
__kasan_slab_alloc+0x22/0x80 mm/kasan/common.c:350
kasan_slab_alloc include/linux/kasan.h:253 [inline]
slab_post_alloc_hook mm/slub.c:4683 [inline]
slab_alloc_node mm/slub.c:4996 [inline]
__do_kmalloc_node mm/slub.c:5413 [inline]
__kmalloc_noprof+0x307/0x720 mm/slub.c:5439
_kmalloc_noprof include/linux/slab.h:995 [inline]
tomoyo_add_entry security/tomoyo/common.c:2166 [inline]
tomoyo_supervisor+0xc53/0x1560 security/tomoyo/common.c:2238
tomoyo_audit_env_log security/tomoyo/environ.c:37 [inline]
tomoyo_env_perm+0x151/0x1f0 security/tomoyo/environ.c:64
tomoyo_environ security/tomoyo/domain.c:673 [inline]
tomoyo_find_next_domain+0x1332/0x1a90 security/tomoyo/domain.c:889
tomoyo_bprm_check_security+0x11b/0x180 security/tomoyo/tomoyo.c:102
security_bprm_check+0x85/0x240 security/security.c:820
search_binary_handler fs/exec.c:1749 [inline]
exec_binprm fs/exec.c:1791 [inline]
bprm_execve+0x888/0x1590 fs/exec.c:1847
do_execveat_common+0x4f4/0x670 fs/exec.c:1945
__do_sys_execve fs/exec.c:2018 [inline]
__se_sys_execve fs/exec.c:2012 [inline]
__x64_sys_execve+0x97/0xc0 fs/exec.c:2012
do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
entry_SYSCALL_64_after_hwframe+0x77/0x7f
Memory state around the buggy address:
ffff88811c6d4380: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
ffff88811c6d4400: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
>ffff88811c6d4480: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
^
ffff88811c6d4500: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
ffff88811c6d4580: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
==================================================================
Other crashes triggered:
Oops: general protection fault, probably for non-canonical address 0xdffffc0000000022: 0000 [#1] SMP KASAN NOPTI
KASAN: null-ptr-deref in range [0x0000000000000110-0x0000000000000117]
CPU: 0 UID: 0 PID: 5854 Comm: syz-executor380 Not tainted syzkaller #1 PREEMPT(full)
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
RIP: 0010:nvmet_port_subsys_allow_link+0x102/0x2e0 drivers/nvme/target/configfs.c:1076
Code: 89 6c 24 10 49 89 1c 24 48 c7 c7 40 13 94 8f e8 a4 9b 5b 05 48 89 6c 24 08 48 81 c5 10 01 00 00 49 89 ec 4c 89 e0 48 c1 e8 03 <42> 80 3c 38 00 74 08 4c 89 e7 e8 7f 4b c9 fb 4d 8b 24 24 49 39 ec
RSP: 0018:ffffc90003537c30 EFLAGS: 00010206
RAX: 0000000000000022 RBX: ffff888195763800 RCX: ffffffff9aa3f501
RDX: 0000000000000001 RSI: 0000000000000008 RDI: 00000000ffffffff
RBP: 0000000000000110 R08: ffffffff8f94134f R09: 1ffffffff1f28269
R10: dffffc0000000000 R11: fffffbfff1f2826a R12: 0000000000000110
R13: ffff888110ceb140 R14: ffff888195763990 R15: dffffc0000000000
FS: 00007f7f0c2bc6c0(0000) GS:ffff8881a4c74000(0000) knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 000055a03a15e300 CR3: 00000001fe873000 CR4: 0000000000352ef0
Call Trace:
<TASK>
configfs_symlink+0x59a/0x1030 fs/configfs/symlink.c:196
vfs_symlink+0x18b/0x330 fs/namei.c:5794
filename_symlinkat+0x1cd/0x410 fs/namei.c:5819
__do_sys_symlinkat fs/namei.c:5839 [inline]
__se_sys_symlinkat+0x4e/0x2b0 fs/namei.c:5834
do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7f7f0f2f7459
Code: c0 79 93 eb d5 48 8d 7c 1d 00 eb 99 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 d0 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007f7f0c2bc1f8 EFLAGS: 00000246 ORIG_RAX: 000000000000010a
RAX: ffffffffffffffda RBX: 00007f7f0f364028 RCX: 00007f7f0f2f7459
RDX: 00007f7f0f364028 RSI: 00000000ffffff9c RDI: 00007f7f0f364060
RBP: 00007f7f0f364060 R08: 00007f7f0c2bc6c0 R09: 00007f7f0c2bc6c0
R10: 00007f7f0c2bc6c0 R11: 0000000000000246 R12: ffffffffffffffd0
R13: 0000000000000010 R14: 00007ffe369fbe30 R15: 00007ffe369fbf18
</TASK>
Modules linked in:
---[ end trace 0000000000000000 ]---
RIP: 0010:nvmet_port_subsys_allow_link+0x102/0x2e0 drivers/nvme/target/configfs.c:1076
Code: 89 6c 24 10 49 89 1c 24 48 c7 c7 40 13 94 8f e8 a4 9b 5b 05 48 89 6c 24 08 48 81 c5 10 01 00 00 49 89 ec 4c 89 e0 48 c1 e8 03 <42> 80 3c 38 00 74 08 4c 89 e7 e8 7f 4b c9 fb 4d 8b 24 24 49 39 ec
RSP: 0018:ffffc90003537c30 EFLAGS: 00010206
RAX: 0000000000000022 RBX: ffff888195763800 RCX: ffffffff9aa3f501
RDX: 0000000000000001 RSI: 0000000000000008 RDI: 00000000ffffffff
RBP: 0000000000000110 R08: ffffffff8f94134f R09: 1ffffffff1f28269
R10: dffffc0000000000 R11: fffffbfff1f2826a R12: 0000000000000110
R13: ffff888110ceb140 R14: ffff888195763990 R15: dffffc0000000000
FS: 00007f7f0c2bc6c0(0000) GS:ffff8881a4c74000(0000) knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 000055a03a15e300 CR3: 00000001fe873000 CR4: 0000000000352ef0
----------------
Code disassembly (best guess):
0: 89 6c 24 10 mov %ebp,0x10(%rsp)
4: 49 89 1c 24 mov %rbx,(%r12)
8: 48 c7 c7 40 13 94 8f mov $0xffffffff8f941340,%rdi
f: e8 a4 9b 5b 05 call 0x55b9bb8
14: 48 89 6c 24 08 mov %rbp,0x8(%rsp)
19: 48 81 c5 10 01 00 00 add $0x110,%rbp
20: 49 89 ec mov %rbp,%r12
23: 4c 89 e0 mov %r12,%rax
26: 48 c1 e8 03 shr $0x3,%rax
* 2a: 42 80 3c 38 00 cmpb $0x0,(%rax,%r15,1) <-- trapping instruction
2f: 74 08 je 0x39
31: 4c 89 e7 mov %r12,%rdi
34: e8 7f 4b c9 fb call 0xfbc94bb8
39: 4d 8b 24 24 mov (%r12),%r12
3d: 49 39 ec cmp %rbp,%r12
The explanation of the root cause of the bug is:
An elegant and complete analysis of the root cause, along with a robust fix for this `configfs` race condition.
### 1. KASAN Crash Analysis & Offset Math
The crash is a slab-use-after-free read of size 8 at offset **1224** inside a 4096-byte region (`kmalloc-4k`).
The KASAN trace points to `nvmet_port_subsys_allow_link()`, which accesses `parent->ci_parent` where `parent` is a `struct config_item *`.
In `nvmet_ports_make()`, a `struct nvmet_port` is dynamically allocated. Let's look at its memory layout:
```c
struct nvmet_port {
struct list_head entry; // 16 bytes
struct nvmf_disc_rsp_page_entry disc_addr; // 1024 bytes
struct config_group group; // 136 bytes (offset 1040)
struct config_group subsys_group; // 136 bytes (offset 1176)
// ...
};
```
The `parent_item` passed to `allow_link` is `&port->subsys_group.cg_item`.
The offset of `subsys_group` is `16 + 1024 + 136 = 1176` bytes.
Inside `struct config_item`, the `ci_parent` pointer is at offset 48.
When `nvmet_port_subsys_allow_link` reads `parent->ci_parent`, it accesses memory at offset `1176 + 48 = 1224` bytes into the `nvmet_port` allocation. This **perfectly matches** the KASAN report:
> `The buggy address is located 1224 bytes inside of freed 4096-byte region`
### 2. The Root Cause (Race Condition)
The race occurs between Thread 1 creating a symlink in a default group (`ports/1/subsystems/`) and Thread 2 removing the parent root object (`ports/1`).
1. **Thread 1 (`symlinkat`)**:
- Enters `configfs_symlink()` with the VFS holding `inode_lock(dir)` on `ports/1/subsystems`.
- Acquires a reference to `parent_item` (the embedded `subsys_group.cg_item`).
- Drops `inode_unlock(dir)` to resolve the symlink target without deadlocking.
2. **Thread 2 (`rmdir`)**:
- Calls `configfs_rmdir()` on `ports/1`.
- Calls `configfs_detach_group()`, which recursively locks all default groups. Since Thread 1 dropped the lock on `ports/1/subsystems`, Thread 2 acquires it, marks the items as dead, detaches them, and unlocks.
- Calls `unlink_group()` on `ports/1`, dropping the subsystem's reference to the port.
- The `nvmet_port` refcount drops to 0, and the **entire `nvmet_port` struct is freed via `kfree()`**.
3. **Thread 1 Resumes**:
- Re-acquires `inode_lock(dir)`.
- Blindly calls `type->ct_item_ops->allow_link(parent_item, target_item)`.
- `parent_item` points to the `subsys_group` embedded inside the **already freed** `nvmet_port`, causing the UAF read at offset 1224.
- Even if `allow_link` wasn't called, jumping to `out_put` executes `config_item_put(parent_item)`, causing a secondary UAF on the kref.
### 3. Why Simple Fixes Fail
- **Re-acquiring `parent_item`**: You cannot safely drop `parent_item` before the unlock and re-acquire it via `configfs_get_config_item(dentry->d_parent)` after the relock. When `configfs_rmdir` detaches the item, it leaves `sd->s_element` as a dangling pointer. Re-acquiring it will dereference freed memory.
- **Holding `parent_item`**: Holding the reference to `parent_item` across the unlock is insufficient because `parent_item` is a **default group**. Default groups are embedded directly inside their parent structs (like `nvmet_port`). Their individual refcount being $> 0$ does **not** prevent the parent struct from being freed when the parent's refcount drops to 0.
### 4. The Proper Fix
To prevent the memory from being freed while the lock is dropped, `configfs_symlink()` must pin the **root object** (the dynamically allocated parent, e.g., `ports/1`) rather than just the embedded default group.
Because `configfs_symlink()` holds `inode_lock(dir)` initially, any concurrent `configfs_rmdir()` on an ancestor root object will block inside `detach_groups()` trying to acquire `inode_lock(dir)`. This guarantees the dentry tree is stable and we can safely walk up the `d_parent` chain to find the root object (the first dentry without the `CONFIGFS_USET_DEFAULT` flag) and take a reference to it.
After re-acquiring the lock, we must check `sd->s_frag->frag_dead` to see if the directory was removed while unlocked.
**Implementation for `fs/configfs/symlink.c`:**
```c
/* Add this helper function above configfs_symlink */
static struct config_item *configfs_get_root_item(struct dentry *dentry)
{
struct dentry *root_dentry = dentry;
struct configfs_dirent *sd;
while (root_dentry) {
sd = root_dentry->d_fsdata;
if (!(sd->s_type & CONFIGFS_USET_DEFAULT))
break;
root_dentry = root_dentry->d_parent;
}
return configfs_get_config_item(root_dentry);
}
int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
struct dentry *dentry, const char *symname)
{
int ret;
struct config_item *parent_item;
struct config_item *target_item = NULL;
struct config_item *root_item = NULL;
const struct config_item_type *type;
parent_item = configfs_get_config_item(dentry->d_parent);
type = parent_item->ci_type;
ret = -EPERM;
if (!type || !type->ct_item_ops ||
!type->ct_item_ops->allow_link)
goto out_put;
/*
* Pin the root object so that the parent_item (which may be an
* embedded default group) is not freed while we drop the lock.
*/
root_item = configfs_get_root_item(dentry->d_parent);
/*
* Drop the dir inode lock. The target path might be outside
* configfs, and we don't want to deadlock.
*/
inode_unlock(dir);
ret = get_target(symname, &target_item, dentry->d_sb);
inode_lock(dir);
if (ret)
goto out_put_root;
if (dentry->d_inode || d_unhashed(dentry))
ret = -EEXIST;
else
ret = inode_permission(&nop_mnt_idmap, dir,
MAY_WRITE | MAY_EXEC);
if (!ret) {
struct configfs_dirent *sd = dentry->d_parent->d_fsdata;
/* Check if the directory was removed while unlocked */
if (sd->s_frag->frag_dead) {
ret = -ENOENT;
} else {
ret = type->ct_item_ops->allow_link(parent_item, target_item);
}
}
if (!ret) {
mutex_lock(&configfs_symlink_mutex);
ret = create_link(parent_item, target_item, dentry);
mutex_unlock(&configfs_symlink_mutex);
if (ret && type->ct_item_ops->drop_link)
type->ct_item_ops->drop_link(parent_item,
target_item);
}
config_item_put(target_item);
out_put_root:
config_item_put(root_item);
out_put:
config_item_put(parent_item);
return ret;
}
```
The diff of the bug fix is:
diff --git a/fs/configfs/symlink.c b/fs/configfs/symlink.c
index 31eb28b27..5fe3722bd 100644
--- a/fs/configfs/symlink.c
+++ b/fs/configfs/symlink.c
@@ -131,6 +131,19 @@ static int get_target(const char *symname, struct config_item **target,
return 0;
}
+static struct config_item *configfs_get_root_item(struct dentry *dentry)
+{
+ struct dentry *root_dentry = dentry;
+ struct configfs_dirent *sd;
+
+ while (root_dentry) {
+ sd = root_dentry->d_fsdata;
+ if (!sd || !(sd->s_type & CONFIGFS_USET_DEFAULT))
+ break;
+ root_dentry = root_dentry->d_parent;
+ }
+ return root_dentry ? configfs_get_config_item(root_dentry) : NULL;
+}
int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
struct dentry *dentry, const char *symname)
@@ -139,6 +152,7 @@ int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
struct configfs_dirent *sd;
struct config_item *parent_item;
struct config_item *target_item = NULL;
+ struct config_item *root_item = NULL;
const struct config_item_type *type;
sd = dentry->d_parent->d_fsdata;
@@ -157,6 +171,12 @@ int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
!type->ct_item_ops->allow_link)
goto out_put;
+ /*
+ * Pin the root object so that the parent_item (which may be an
+ * embedded default group) is not freed while we drop the lock.
+ */
+ root_item = configfs_get_root_item(dentry->d_parent);
+
/*
* This is really sick. What they wanted was a hybrid of
* link(2) and symlink(2) - they wanted the target resolved
@@ -192,8 +212,12 @@ int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
else
ret = inode_permission(&nop_mnt_idmap, dir,
MAY_WRITE | MAY_EXEC);
- if (!ret)
- ret = type->ct_item_ops->allow_link(parent_item, target_item);
+ if (!ret) {
+ if (sd->s_frag->frag_dead)
+ ret = -ENOENT;
+ else
+ ret = type->ct_item_ops->allow_link(parent_item, target_item);
+ }
if (!ret) {
mutex_lock(&configfs_symlink_mutex);
ret = create_link(parent_item, target_item, dentry);
@@ -207,6 +231,7 @@ int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
out_put:
config_item_put(parent_item);
+ config_item_put(root_item);
return ret;
}
Additional description of the patch:
### Explanation of the Bug & Fix
#### Root Cause
In `configfs_symlink()`, `inode_unlock(dir)` is called before resolving the symlink target (via `get_target()`) to avoid deadlocks during pathname lookup.
When `configfs_symlink()` is operating within a default group (such as `ports/1/subsystems/` in NVMe-oF target), the `parent_item` is an embedded default group (`subsys_group`) inside the dynamically allocated parent object (`struct nvmet_port`). Because default groups are embedded within their containing parent structure, holding a reference to `parent_item` alone does not prevent the containing parent structure (`nvmet_port`) from being freed when a concurrent `configfs_rmdir()` removes the root object (`ports/1`).
When `configfs_rmdir()` runs concurrently during the unlocked window, it detaches all default groups, unlinks the root group, sets `sd->s_frag->frag_dead = true`, and drops the subsystem's reference to the root group, freeing the entire `nvmet_port` structure with `kfree()`. When `configfs_symlink()` re-acquires `inode_lock(dir)`, accessing `parent_item` triggers a use-after-free read in `allow_link`.
#### Why the Previous Attempt Failed
The previous patch attempted to solve this by acquiring `root_item` via `configfs_get_root_item()` and checking `frag_dead`. However, in the cleanup exit path it put `root_item` **before** `parent_item`:
```c
out_put_root:
config_item_put(root_item);
out_put:
config_item_put(parent_item);
```
When `config_item_put(root_item)` dropped the refcount of `root_item` to 0, `config_item_cleanup(root_item)` invoked the release callback (`nvmet_port_release()`) and freed `nvmet_port`. Immediately afterwards, `config_item_put(parent_item)` attempted to decrement the kref inside the embedded `subsys_group` within the already-freed `nvmet_port`, resulting in a slab-use-after-free write on `kref_put`.
#### The Fix
1. **Pin the Root Object**: Add `configfs_get_root_item()` to walk up the dentry tree past any default groups (`CONFIGFS_USET_DEFAULT`) while holding `inode_lock(dir)` and acquire a reference to the dynamically allocated root object (`root_item`).
2. **Check for Directory Removal**: After re-acquiring `inode_lock(dir)`, check `sd->s_frag->frag_dead`. If the directory was removed during the unlocked window, return `-ENOENT` instead of proceeding with `allow_link()`.
3. **Correct Release Order**: Put `parent_item` **before** `root_item` in `out_put:`. This guarantees that the embedded child item's reference is always released while the memory of the containing root object remains pinned by `root_item`.
Here are summaries of recent commits that touched the same files.
Format the summary line consistently with these, look how prefixes
are specified, letter capitalization, style, etc.
configfs: mark pinned dentries persistent
configfs:get_target() - release path as soon as we grab configfs_item reference
fs: port ->permission() to pass mnt_idmap
fs: port ->symlink() to pass mnt_idmap
treewide: remove editor modelines and cruft
fs: make helpers idmap mount aware
namei: make permission helpers idmapped mount aware
configfs: calculate the depth of parent item
configfs: calculate the symlink target only once
configfs: fix a deadlock in configfs_symlink()
treewide: Replace GPLv2 boilerplate/reference with SPDX - rule 145
configfs: replace strncpy with memcpy
configfs: make ci_type field, some pointers and function arguments const
configfs: Fix race between create_link and configfs_rmdir
vfs: remove ".readlink = generic_readlink" assignments
switch ->get_link() to delayed_call, kill ->put_link()
kill free_page_put_link()
replace ->follow_link() with new method that could stay in RCU mode
new helper: free_page_put_link()
switch ->put_link() from dentry to inode
Reply: configfs: fix use-after-free in configfs_symlink()
In configfs_symlink(), inode_unlock(dir) is called before resolving the
symlink target via get_target() to avoid deadlocks during pathname lookup.
When configfs_symlink() operates within a default group (such as
ports/1/subsystems/ in NVMe-oF target), parent_item is an embedded default
group inside the dynamically allocated parent object (struct nvmet_port).
Because default groups are embedded within their containing structure,
holding a reference to parent_item alone does not prevent the containing
parent structure from being freed when a concurrent configfs_rmdir()
removes the root directory.
When configfs_rmdir() runs concurrently while inode_lock(dir) is dropped in
configfs_symlink(), it detaches all default groups, unlinks the root group,
sets frag_dead to true, and drops the subsystem's reference to the root
group. Once the root object's refcount drops to zero, the entire containing
structure is freed via kfree(). When configfs_symlink() re-acquires
inode_lock(dir), accessing parent_item in allow_link() accesses freed
memory, causing a slab-use-after-free:
BUG: KASAN: slab-use-after-free in nvmet_port_subsys_allow_link+0x41/0x2e0
drivers/nvme/target/configfs.c:1059
Read of size 8 at addr ffff88811c6d44c8
Call Trace:
<TASK>
kasan_report+0x117/0x150 mm/kasan/report.c:595
nvmet_port_subsys_allow_link+0x41/0x2e0
drivers/nvme/target/configfs.c:1059
configfs_symlink+0x59a/0x1030 fs/configfs/symlink.c:196
vfs_symlink+0x18b/0x330 fs/namei.c:5794
filename_symlinkat+0x1cd/0x410 fs/namei.c:5819
do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
entry_SYSCALL_64_after_hwframe+0x77/0x7f
</TASK>
Allocated by task 5847:
kasan_save_stack mm/kasan/common.c:57 [inline]
kasan_save_track+0x3e/0x80 mm/kasan/common.c:78
__kasan_kmalloc+0x93/0xb0 mm/kasan/common.c:415
__kmalloc_cache_noprof+0x321/0x600 mm/slub.c:5563
nvmet_ports_make+0xe6/0xf00 drivers/nvme/target/configfs.c:2051
configfs_mkdir+0x4f6/0x9e0 fs/configfs/dir.c:1360
vfs_mkdir+0x40c/0x620 fs/namei.c:5410
filename_mkdirat+0x285/0x510 fs/namei.c:5443
do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
entry_SYSCALL_64_after_hwframe+0x77/0x7f
Freed by task 5847:
kasan_save_stack mm/kasan/common.c:57 [inline]
kasan_save_track+0x3e/0x80 mm/kasan/common.c:78
__kasan_slab_free+0x5c/0x80 mm/kasan/common.c:285
kfree+0x1c5/0x650 mm/slub.c:6792
config_item_release+0x13a/0x2d0 fs/configfs/item.c:137
configfs_rmdir+0x885/0x950 fs/configfs/dir.c:1571
vfs_rmdir+0x3e6/0x6a0 fs/namei.c:5515
filename_rmdir+0x292/0x520 fs/namei.c:5572
do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
entry_SYSCALL_64_after_hwframe+0x77/0x7f
Fix this by:
1. Adding configfs_get_root_item() to walk up the dentry tree past any
default groups (CONFIGFS_USET_DEFAULT) while holding inode_lock(dir) to
acquire a reference to the root object (root_item), preventing the
containing structure from being freed while inode_lock(dir) is dropped.
2. Checking sd->s_frag->frag_dead after re-acquiring inode_lock(dir) to
detect if the directory was removed during the unlocked window, returning
-ENOENT instead of calling allow_link().
3. Releasing parent_item before root_item in the out_put cleanup path so
that the embedded item reference is dropped while the memory of the
containing root object remains pinned.
|
| 611/2 |
2026/09/02 22:22 |
llm |
description-generator |
0m
Model:
Tokens:
input: 15311
output: 30
thoughts: 205
Thoughts: **Kernel Commit Description for configfs/symlink.c Bug Fix**
Okay, I need to craft a kernel commit message for a bug fix in `fs/configfs/symlink.c`. The task is clearly defined, and I understand the requirements. First, the summary line needs to be concise and follow the existing style, probably starting with "configfs:". I'll need to clearly articulate the nature of the bug, the solution implemented by the patch, and include relevant details from the KASAN report, specifically the parts that highlight the problem, but I need to be careful to trim the stack trace, keeping it focused. I also need to outline the execution path that leads to the bug, which is important for understanding the root cause. Crucially, no manual line wrapping, no lines starting with `#`, `--`, or `---`, and no references to fuzzers or syzkaller. I need to focus solely on the content of the commit message itself. I should probably review related commits in the `fs/configfs` directory to ensure consistency with the established style. Let's see if I need to dig into the source code and use git to see how it should be done.
|
| 612/2 |
2026/09/02 22:22 |
tool |
git-log |
0m
Args: map[Count:10 PathPrefix:fs/configfs/symlink.c]
Results: map[Output:1387aedb391a configfs: mark pinned dentries persistent
1b25dea3867a configfs:get_target() - release path as soon as we grab configfs_item reference
4609e1f18e19 fs: port ->permission() to pass mnt_idmap
7a77db95511c fs: port ->symlink() to pass mnt_idmap
fa60ce2cb450 treewide: remove editor modelines and cruft
549c7297717c fs: make helpers idmap mount aware
47291baa8ddf namei: make permission helpers idmapped mount aware
e2f238f7d5a1 configfs: calculate the depth of parent item
e9c03af21cc7 configfs: calculate the symlink target only once
351e5d869e5a configfs: fix a deadlock in configfs_symlink()
]
|
| 613/2 |
2026/09/02 22:22 |
llm |
description-generator |
0m
Model:
Tokens:
input: 16041
output: 26
thoughts: 30
Thoughts: Okay, here's my take on those thoughts, framed as an expert's inner monologue:
**Investigating ConfigFS Style for Consistent Coding**
Alright, before I dive too deep into this `fs/configfs` module, I need to get a good sense of the existing style within it. Consistency is key, and I don't want to introduce any stylistic variations that will upset the flow. So, my first move is going to be to look for some existing examples within that directory. I'll likely start with either `read-file` or `git-log` looking specifically for previous commits in `fs/configfs`. That should quickly give me a feel for the established conventions in use here. Itβll show me things like indentation, naming schemes, comment usage, and generally the approach the original authors took. Once I have a baseline of the standard style, I can ensure my changes align with it. Itβs a foundational step, saving headaches down the line.
|
| 614/2 |
2026/09/02 22:22 |
tool |
git-log |
0m
Args: map[Count:10 PathPrefix:fs/configfs/]
Results: map[Output:d53ac61b6120 create_default_group(): pass parent's dentry instead of config_group
ad235a65dfff configfs_attach_group(): drop the unused parent_item argument
d4152c7562a8 configs_attach_item(): drop unused parent_item argument
967c898d9def configfs_create(): lift parent timestamp updates into callers
43b45755e80e kill configfs_drop_dentry()
1387aedb391a configfs: mark pinned dentries persistent
1a29e25799bc configfs: dentry refcount needs to be pinned only once
abe91a2aa9c4 switch configfs_detach_{group,item}() to passing dentry
05a4d3d1b676 configfs_remove_dir(), detach_attrs(): switch to passing dentry
34a62943762c populate_attrs(): move cleanup to the sole caller
]
|
| 615/2 |
2026/09/02 22:22 |
llm |
description-generator |
0m
Model:
Tokens:
input: 16558
output: 1322
thoughts: 5838
Thoughts: **configfs: fix use-after-free in configfs_symlink()**
In `configfs_symlink()`, `inode_unlock(dir)` is called before resolving the symlink target to prevent deadlocks during pathname lookup. When operating within a default group (e.g., `ports/1/subsystems/` in NVMe-oF), `parent_item` is an embedded default group within a dynamically allocated root structure (e.g., `struct nvmet_port`). Holding a reference to `parent_item` alone doesn't prevent the root from being freed when a concurrent `configfs_rmdir()` removes the root directory.
When `configfs_rmdir()` runs concurrently, it detaches default groups, unlinks the root group, sets `frag_dead = true`, and drops the root group's reference. The root object's memory is freed. When `configfs_symlink()` re-acquires `inode_lock(dir)`, accessing `parent_item` (e.g., in `allow_link()` or on cleanup) dereferences freed memory, leading to a use-after-free.
```
BUG: KASAN: slab-use-after-free in nvmet_port_subsys_allow_link+0x41/0x2e0 drivers/nvme/target/configfs.c:1059
Read of size 8 at addr ffff88811c6d44c8
Call Trace:
<TASK>
kasan_report+0x117/0x150 mm/kasan/report.c:595
nvmet_port_subsys_allow_link+0x41/0x2e0 drivers/nvme/target/configfs.c:1059
configfs_symlink+0x59a/0x1030 fs/configfs/symlink.c:196
vfs_symlink+0x18b/0x330 fs/namei.c:5794
filename_symlinkat+0x1cd/0x410 fs/namei.c:5819
do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
entry_SYSCALL_64_after_hwframe+0x77/0x7f
</TASK>
Allocated by task 5847:
kasan_save_stack mm/kasan/common.c:57 [inline]
kasan_save_track+0x3e/0x80 mm/kasan/common.c:78
__kasan_kmalloc+0x93/0xb0 mm/kasan/common.c:415
__kmalloc_cache_noprof+0x321/0x600 mm/slub.c:5563
nvmet_ports_make+0xe6/0xf00 drivers/nvme/target/configfs.c:2051
configfs_mkdir+0x4f6/0x9e0 fs/configfs/dir.c:1360
vfs_mkdir+0x40c/0x620 fs/namei.c:5410
filename_mkdirat+0x285/0x510 fs/namei.c:5443
do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
entry_SYSCALL_64_after_hwframe+0x77/0x7f
Freed by task 5847:
kasan_save_stack mm/kasan/common.c:57 [inline]
kasan_save_track+0x3e/0x80 mm/kasan/common.c:78
__kasan_slab_free+0x5c/0x80 mm/kasan/common.c:285
kfree+0x1c5/0x650 mm/slub.c:6792
config_item_release+0x13a/0x2d0 fs/configfs/item.c:137
configfs_rmdir+0x885/0x950 fs/configfs/dir.c:15
|