| 0/0 |
2026/06/09 15:36 |
flow |
assessment-security |
46m
Results: map[DenialOfService:true Explanation:The bug report indicates a `WARNING` in `__up_write` triggered during
`inode_unlock` in the `mkdirat` syscall path. The warning specifically complains
that the `rwsem` being unlocked is not owned by the current thread
(`rwsem_owner(sem) != current`), and its count is `0x0` (meaning it's already
unlocked or was never locked).
### Analysis of the Bug
The call trace shows the following sequence:
```
inode_unlock include/linux/fs.h:1039 [inline]
end_dirop fs/namei.c:2951 [inline]
end_creating include/linux/namei.h:123 [inline]
end_creating_path fs/namei.c:4990 [inline]
filename_mkdirat+0x305/0x510 fs/namei.c:5299
```
In `filename_mkdirat`, the parent directory's inode is locked during
`filename_create` (which calls `start_dirop`). Then, `vfs_mkdir` is called to
perform the actual directory creation.
Recent changes in the VFS layer modified `vfs_mkdir` to unlock the parent inode
internally if it encounters an error, and return an `ERR_PTR`. If `vfs_mkdir`
returns an error, `filename_mkdirat` passes that `ERR_PTR` to
`end_creating_path`, which correctly does nothing (avoiding a double-unlock).
However, if a specific filesystem's `->mkdir` implementation behaves
unexpectedly—for example, by returning a spliced dentry (`d_splice_alias`) whose
parent does not match the originally locked directory, or by erroneously
unlocking the parent inode itself before returning success—`end_creating_path`
will attempt to unlock an inode that is either already unlocked or was never
locked by the current thread. This triggers the `DEBUG_RWSEMS_WARN_ON` warning.
### Security Impact
* **Exploitable:** No. This is a lock state mismatch warning. While it indicates
a violation of locking rules (unlocking an unlocked semaphore or unlocking the
wrong inode's semaphore), it does not directly lead to memory corruption (like
use-after-free or out-of-bounds access) or privilege escalation.
* **Denial Of Service:** Yes. Unlocking a semaphore that is not locked, or
leaving the originally locked semaphore locked forever, will corrupt the lock
state. This can easily lead to deadlocks when other processes attempt to access
the same directory, effectively causing a local denial-of-service (hanging
tasks).
* **Accessible From Unprivileged Processes:** Yes. The bug is triggered via the
standard `mkdir` / `mkdirat` system calls, which are available to any
unprivileged user process.
### Conclusion
This is a local Denial-of-Service (DoS) vulnerability reachable by unprivileged
users due to a locking imbalance in the VFS directory creation path (likely
interacting with a specific filesystem's `mkdir` implementation). It is not
exploitable for privilege escalation or arbitrary code execution. Exploitable:false FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:true UserNamespace:false VMGuestTrigger:false VMHostTrigger:false]
|
| 1/1 |
2026/06/09 15:36 |
action |
syz-repro-to-c-repro |
0m
Results:
|
| 2/1 |
2026/06/09 15:36 |
action |
kernel-checkouter |
1m
Results: map[KernelSrc:/app/workdir/cache/src/171fe422c2eec0fa1a6ac037552081a9579009f3]
|
| 3/1 |
2026/06/09 15:38 |
action |
kernel-builder |
23m
Results: map[KernelObj:/app/workdir/cache/build/12f875cc4e8ec2ec76e0677cc114e33749bccd4e]
|
| 4/1 |
2026/06/09 16:01 |
action |
codesearch-prepare |
9m
Results: map[Index:codesearch-index]
|
| 5/1 |
2026/06/09 16:11 |
agent |
expert |
12m
Model:
Results: map[DenialOfService:true Exploitable:false FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:true UserNamespace:false VMGuestTrigger:false VMHostTrigger:false]
Instruction: You are an experienced Linux kernel security engineer. Your task is to analyze given kernel bug report
and determine its security impact based on the following dimensions.
Use the provided tools to examine the source code, check for capability checks (e.g., capable(), ns_capable()),
and understand the nature of the bug. Analyze the given kernel build and configuration.
You can check the kernel config by grepping ".config" file; you can check kernel cmdline by grepping
".config" file for "CONFIG_CMDLINE=". Assume sysctl parameters have default values.
But analyze for the corresponding production build w/o debugging tools enabled (like KASAN, KMSAN, UBSAN).
Try different strategies when analyzing the bug:
- think of ways in which the vulnerable code is unreachable
- or the other way around: try to come up with different ideas of how an unprivileged user can reach the bug
If still unsure err on the side of the bug being non-exploitable/not-accessible.
In the final reply, provide a reasoning for your assessment.
Analysis dimensions:
* Exploitable:
Determine if the bug can result in memory corruption or elevated privileges.
Memory safety issues are almost always exploitable (KASAN or UBSAN reports for use-after-free, out-of-bounds;
refcounting issues, corrupted lists, etc). When kernel is crashing on a completely wild pointer access
(e.g. user-space address, or non-canonical address, but not on NULL or address corresponding to KASAN shadow
for NULL address), including both data accesses and control transfers, that also usually implies possibility
of exploitation. Such reports usually say "unable to handle kernel paging request".
Uses of uninitialized values detected by KMSAN may be exploitable b/c attacker frequently can affect uninit
values with spraying techniques. However, for these exploitability depends on how exactly the uninit value
is used in the code, and what it affects.
Think of what happens after the bug is triggered. Some bugs cause kernel panic and halt execution,
they are harder to exploit. For example, BUG reports halts the kernel. However, WARNING reports don't halt
execution in production builds. Debug bug detection tools (like KASAN, KMSAN, KCSAN, UBSAN) are also not enabled
in production builds, so attacker can freely exploit these bugs w/o being detected by these tools.
If you see an integer overflow, think how the overflowed value used later (if it's used as allocation size,
or an array index). If you see an out-of-bounds read, think if it's followed by an out-of-bounds write as well.
Some KCSAN data-races may be exploitable by skilled attackers as well. Think what data structures got corrupted
as the result of data races and how. However, note that kernel has lots of "benign" data races that don't lead
to any runtime misbehavior at all.
* Denial Of Service:
Determine if the bug can result in denial-of-service. Most bugs can, since they cause system crash,
hangs, deadlocks, or resource leaks. This is mostly applicable to WARNING bugs that won't cause system crash
in production. For these think what will be consequences of the violation of the kernel assumptions flagged
by the WARNING. In some cases the unexpected condition is also properly handled by the normal control flow
(e.g. with "if (WARN_ON(...))"), these won't cause denial-of-service. If the condition is not handled,
then it may or may not cause denial-of-service.
* Accessible From Unprivileged Processes:
Determine if the bug can be reached from a typical (non-root) user process that does NOT have any special capabilities
(like CAP_SYS_ADMIN, CAP_NET_ADMIN, CAP_NET_RAW, CAP_PERFMON) or access to device nodes restricted to root.
Assume that unprivileged_bpf_disabled=1, that is eBPF loading is not accessible. However, cBPF (classical BPF)
is still accessible to non-root processes.
Assume that user namespaces are not accessible, that is, the process cannot get the mentioned capabilities even
within a new user namespace (checked by ns_capable() function in the kernel sources).
* Accessible From User Namespaces:
Determine if the bug can be reached within a user-namespace where the process has all capabilities
(including CAP_SYS_ADMIN, CAP_NET_ADMIN, CAP_NET_RAW, CAP_PERFMON). Such capabilities are checked with ns_capable()
function in the kernel sources.
* VM Guest Trigger:
Determine if the bug can be triggered from the context of a typical KVM guest (e.g., set up by a QEMU VMM).
Consider accesses to standard Linux host paravirtualized features (virtio-blk, virtio-net, etc.),
and handling of VM exits in the KVM code.
* VM Host Trigger in The Confidential Computing Context:
Determine if the bug can be triggered in a confidential computing guest kernel from the context of a KVM host.
Consider access to standard Linux guest paravirtualized features (virtio-blk, virtio-net, etc.).
* Ethernet Network Trigger:
Determine if the bug can be triggered by processing ingress network Ethernet traffic, either directly (network stack)
or via drivers exposed to network data.
* Other Remote Trigger:
Determine if the bug can be triggered by processing remote traffic other than Ethernet (Wifi, Bluetooth, NFC, etc).
* Peripheral Trigger:
Determine if the bug can be triggered via an untrusted peripheral device that can be physically plugged
into a system, such as a USB device or a niche hardware driver handling external hardware inputs.
This is particularly important for mobile and desktop environments where users can plug in unknown devices.
* Malicious Filesystem Trigger:
Determine if the bug can be triggered by the kernel mounting and parsing a malicious filesystem image.
This is highly critical for Desktop and Mobile environments where external media or downloaded images
might be auto-mounted.
Don't make assumptions about the kernel source code (it may be different from what you assume it is).
Extensively use the provided code access tools (codesearch-*, git-*, grepper, etc)
to examine the actual source code, and confirm any assumptions.
Prefer calling several tools at the same time to save round-trips.
Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.
Prompt:
The kernel bug report is:
------------[ cut here ]------------
DEBUG_RWSEMS_WARN_ON((rwsem_owner(sem) != current) && !rwsem_test_oflags(sem, RWSEM_NONSPINNABLE)): count = 0x0, magic = 0xffff888046de1118, owner = 0x0, curr 0xffff888000932540, list not empty
WARNING: kernel/locking/rwsem.c:1411 at __up_write kernel/locking/rwsem.c:1410 [inline], CPU#0: syz.0.0/5329
WARNING: kernel/locking/rwsem.c:1411 at up_write+0x2d4/0x410 kernel/locking/rwsem.c:1682, CPU#0: syz.0.0/5329
Modules linked in:
CPU: 0 UID: 0 PID: 5329 Comm: syz.0.0 Not tainted syzkaller #0 PREEMPT(full)
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
RIP: 0010:__up_write kernel/locking/rwsem.c:1410 [inline]
RIP: 0010:up_write+0x384/0x410 kernel/locking/rwsem.c:1682
Code: cc 8b 49 c7 c2 40 e6 cc 8b 4c 0f 44 d0 48 8b 7c 24 08 48 c7 c6 e0 e8 cc 8b 48 8b 14 24 4c 89 f1 4d 89 e0 4c 8b 4c 24 10 41 52 <67> 48 0f b9 3a 48 83 c4 08 e8 4e c2 0e 03 e9 6b fd ff ff 48 c7 c1
RSP: 0018:ffffc9000e66fd80 EFLAGS: 00010246
RAX: ffffffff8bcce660 RBX: ffff888046de1118 RCX: ffff888046de1118
RDX: 0000000000000000 RSI: ffffffff8bcce8e0 RDI: ffffffff9033a6b0
RBP: ffff888046de1170 R08: 0000000000000000 R09: ffff888000932540
R10: ffffffff8bcce660 R11: ffffed1008dbc225 R12: 0000000000000000
R13: dffffc0000000000 R14: ffff888046de1118 R15: 1ffff11008dbc224
FS: 00007f9ebdd156c0(0000) GS:ffff88808c891000(0000) knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 00007fdfc4261000 CR3: 0000000012e29000 CR4: 0000000000352ef0
Call Trace:
<TASK>
inode_unlock include/linux/fs.h:1039 [inline]
end_dirop fs/namei.c:2951 [inline]
end_creating include/linux/namei.h:123 [inline]
end_creating_path fs/namei.c:4990 [inline]
filename_mkdirat+0x305/0x510 fs/namei.c:5299
__do_sys_mkdirat fs/namei.c:5315 [inline]
__se_sys_mkdirat+0x35/0x150 fs/namei.c:5312
do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
do_syscall_64+0x174/0x580 arch/x86/entry/syscall_64.c:94
entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7f9ebcd9ce59
Code: ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 44 00 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 e8 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007f9ebdd14fe8 EFLAGS: 00000246 ORIG_RAX: 0000000000000102
RAX: ffffffffffffffda RBX: 00007f9ebd016090 RCX: 00007f9ebcd9ce59
RDX: 00000000000001c0 RSI: 0000200000000080 RDI: ffffffffffffff9c
RBP: 00007f9ebce32d6f R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000
R13: 00007f9ebd016128 R14: 00007f9ebd016090 R15: 00007fffe154be58
</TASK>
----------------
Code disassembly (best guess), 2 bytes skipped:
0: 49 c7 c2 40 e6 cc 8b mov $0xffffffff8bcce640,%r10
7: 4c 0f 44 d0 cmove %rax,%r10
b: 48 8b 7c 24 08 mov 0x8(%rsp),%rdi
10: 48 c7 c6 e0 e8 cc 8b mov $0xffffffff8bcce8e0,%rsi
17: 48 8b 14 24 mov (%rsp),%rdx
1b: 4c 89 f1 mov %r14,%rcx
1e: 4d 89 e0 mov %r12,%r8
21: 4c 8b 4c 24 10 mov 0x10(%rsp),%r9
26: 41 52 push %r10
* 28: 67 48 0f b9 3a ud1 (%edx),%rdi <-- trapping instruction
2d: 48 83 c4 08 add $0x8,%rsp
31: e8 4e c2 0e 03 call 0x30ec284
36: e9 6b fd ff ff jmp 0xfffffda6
3b: 48 rex.W
3c: c7 .byte 0xc7
3d: c1 .byte 0xc1
Reply: The bug report indicates a `WARNING` in `__up_write` triggered during `inode_unlock` in the `mkdirat` syscall path. The warning specifically complains that the `rwsem` being unlocked is not owned by the current thread (`rwsem_owner(sem) != current`), and its count is `0x0` (meaning it's already unlocked or was never locked).
### Analysis of the Bug
The call trace shows the following sequence:
```
inode_unlock include/linux/fs.h:1039 [inline]
end_dirop fs/namei.c:2951 [inline]
end_creating include/linux/namei.h:123 [inline]
end_creating_path fs/namei.c:4990 [inline]
filename_mkdirat+0x305/0x510 fs/namei.c:5299
```
In `filename_mkdirat`, the parent directory's inode is locked during `filename_create` (which calls `start_dirop`). Then, `vfs_mkdir` is called to perform the actual directory creation.
Recent changes in the VFS layer modified `vfs_mkdir` to unlock the parent inode internally if it encounters an error, and return an `ERR_PTR`. If `vfs_mkdir` returns an error, `filename_mkdirat` passes that `ERR_PTR` to `end_creating_path`, which correctly does nothing (avoiding a double-unlock).
However, if a specific filesystem's `->mkdir` implementation behaves unexpectedly—for example, by returning a spliced dentry (`d_splice_alias`) whose parent does not match the originally locked directory, or by erroneously unlocking the parent inode itself before returning success—`end_creating_path` will attempt to unlock an inode that is either already unlocked or was never locked by the current thread. This triggers the `DEBUG_RWSEMS_WARN_ON` warning.
### Security Impact
* **Exploitable:** No. This is a lock state mismatch warning. While it indicates a violation of locking rules (unlocking an unlocked semaphore or unlocking the wrong inode's semaphore), it does not directly lead to memory corruption (like use-after-free or out-of-bounds access) or privilege escalation.
* **Denial Of Service:** Yes. Unlocking a semaphore that is not locked, or leaving the originally locked semaphore locked forever, will corrupt the lock state. This can easily lead to deadlocks when other processes attempt to access the same directory, effectively causing a local denial-of-service (hanging tasks).
* **Accessible From Unprivileged Processes:** Yes. The bug is triggered via the standard `mkdir` / `mkdirat` system calls, which are available to any unprivileged user process.
### Conclusion
This is a local Denial-of-Service (DoS) vulnerability reachable by unprivileged users due to a locking imbalance in the VFS directory creation path (likely interacting with a specific filesystem's `mkdir` implementation). It is not exploitable for privilege escalation or arbitrary code execution.
|
| 6/2 |
2026/06/09 16:11 |
llm |
expert |
0m
Model:
Tokens:
input: 7689
output: 18
thoughts: 437
Thoughts: **Analyzing a Syzkaller Bug Report: RWSEM Contention in File System Operations**
Okay, here's what's going on. I've got a syzkaller bug report right in front of me. It's flagging a potential issue with a read-write semaphore (`rwsem`) in the kernel. Specifically, I see this warning coming from `__up_write` in `kernel/locking/rwsem.c`. The core of the problem seems to be that the current process doesn't own the semaphore (`rwsem_owner(sem) != current`) when a write operation is being completed. The warning mentions a count of 0, which is interesting, and the call trace is key.
The call trace provides valuable context. It shows the path of execution leading to the `__up_write` call. It looks like the issue arises during a file system operation related to creating a directory. The functions involved are `inode_unlock`, `end_dirop`, `end_creating`, `end_creating_path`, `filename_mkdirat`, `__do_sys_mkdirat`, and `__se_sys_mkdirat`. The specific line number in `filename_mkdirat` is `fs/namei.c:5299`. I should also look at the `end_creating_path` function to understand the flow here. I'll need to examine the interactions between these functions to pinpoint where the semaphore ownership is being mishandled. My next step is to carefully review the code around `fs/namei.c:5299` and the surrounding `rwsem` usage to understand the locking logic and potential race conditions that could lead to this warning.
|
| 7/2 |
2026/06/09 16:11 |
tool |
grepper |
0m
Args: map[Expression:filename_mkdirat]
Results: map[Output:fs/init.c=184=int __init init_mkdir(const char *pathname, umode_t mode)
--
fs/init.c-186- CLASS(filename_kernel, name)(pathname);
fs/init.c:187: return filename_mkdirat(AT_FDCWD, name, mode);
fs/init.c-188-}
--
fs/internal.h=59=int filename_renameat2(int olddfd, struct filename *oldname, int newdfd,
fs/internal.h-60- struct filename *newname, unsigned int flags);
fs/internal.h:61:int filename_mkdirat(int dfd, struct filename *name, umode_t mode);
fs/internal.h-62-int filename_mknodat(int dfd, struct filename *name, umode_t mode, unsigned int dev);
--
fs/namei.c=5276=EXPORT_SYMBOL(vfs_mkdir);
fs/namei.c-5277-
fs/namei.c:5278:int filename_mkdirat(int dfd, struct filename *name, umode_t mode)
fs/namei.c-5279-{
--
fs/namei.c=5312=SYSCALL_DEFINE3(mkdirat, int, dfd, const char __user *, pathname, umode_t, mode)
--
fs/namei.c-5314- CLASS(filename, name)(pathname);
fs/namei.c:5315: return filename_mkdirat(dfd, name, mode);
fs/namei.c-5316-}
--
fs/namei.c=5318=SYSCALL_DEFINE2(mkdir, const char __user *, pathname, umode_t, mode)
--
fs/namei.c-5320- CLASS(filename, name)(pathname);
fs/namei.c:5321: return filename_mkdirat(AT_FDCWD, name, mode);
fs/namei.c-5322-}
--
io_uring/fs.c=183=int io_mkdirat(struct io_kiocb *req, unsigned int issue_flags)
--
io_uring/fs.c-190-
io_uring/fs.c:191: ret = filename_mkdirat(mkd->dfd, name, mkd->mode);
io_uring/fs.c-192-
]
|
| 8/2 |
2026/06/09 16:11 |
llm |
expert |
0m
Model:
Tokens:
input: 9124
output: 34
thoughts: 19
Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience:
**Diving into `filename_mkdirat`**
Alright, time to get my hands dirty. I'm going to jump straight into `fs/namei.c` and specifically examine the `filename_mkdirat` function. This is where the core logic for `mkdirat` presumably resides, and I need to understand how it handles the interaction between the filename, the directory file descriptor, and the creation of the new directory. I need to refresh my understanding of how this interacts with the VFS layer, the i-node allocation, and of course, those tricky security checks. I'm particularly interested in how it manages potential race conditions during the creation process, and whether it properly handles errors, especially those that might be related to permissions or the filesystem's capacity. I'll need to scrutinize how it deals with symbolic links in the path, as well.
|
| 9/2 |
2026/06/09 16:11 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/namei.c Name:filename_mkdirat]
Results: map[SourceCode:5278: int filename_mkdirat(int dfd, struct filename *name, umode_t mode)
5279: {
5280: struct dentry *dentry;
5281: struct path path;
5282: int error;
5283: unsigned int lookup_flags = LOOKUP_DIRECTORY;
5284: struct delegated_inode delegated_inode = { };
5285:
5286: retry:
5287: dentry = filename_create(dfd, name, &path, lookup_flags);
5288: if (IS_ERR(dentry))
5289: return PTR_ERR(dentry);
5290:
5291: error = security_path_mkdir(&path, dentry,
5292: mode_strip_umask(path.dentry->d_inode, mode));
5293: if (!error) {
5294: dentry = vfs_mkdir(mnt_idmap(path.mnt), path.dentry->d_inode,
5295: dentry, mode, &delegated_inode);
5296: if (IS_ERR(dentry))
5297: error = PTR_ERR(dentry);
5298: }
5299: end_creating_path(&path, dentry);
5300: if (is_delegated(&delegated_inode)) {
5301: error = break_deleg_wait(&delegated_inode);
5302: if (!error)
5303: goto retry;
5304: }
5305: if (retry_estale(error, lookup_flags)) {
5306: lookup_flags |= LOOKUP_REVAL;
5307: goto retry;
5308: }
5309: return error;
5310: }
SourceFile:fs/namei.c]
|
| 10/2 |
2026/06/09 16:11 |
llm |
expert |
0m
Model:
Tokens:
input: 9901
output: 35
thoughts: 11
Thoughts: Okay, here's my attempt at summarizing that thought process, keeping in mind I'm speaking to an expert:
**Checking `end_creating_path`**
Alright, let's just quickly zero in on `end_creating_path`. It's a crucial point in the flow, isn't it? I need to make sure everything's tidied up and ready for whatever comes next, and `end_creating_path` is the likely location to do it. Is it functioning as expected? Are all the necessary variables initialized, paths finalized, and resources released? I'm picturing the sequence in my head, mentally stepping through the code. Let's make sure there aren't any subtle, easily-overlooked issues, like memory leaks or incomplete finalizations, here. I'm double-checking my assumptions about that function's scope and side effects, just to be sure. It would be a nightmare to debug a path generation problem later on, if I skip this. Need to be certain that the path construction process is truly complete at this point.
|
| 11/2 |
2026/06/09 16:11 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/namei.c Name:end_creating_path]
Results: map[SourceCode:4977: /**
4978: * end_creating_path - finish a code section started by start_creating_path()
4979: * @path: the path instantiated by start_creating_path()
4980: * @dentry: the dentry returned by start_creating_path()
4981: *
4982: * end_creating_path() will unlock and locks taken by start_creating_path()
4983: * and drop an references that were taken. It should only be called
4984: * if start_creating_path() returned a non-error.
4985: * If vfs_mkdir() was called and it returned an error, that error *should*
4986: * be passed to end_creating_path() together with the path.
4987: */
4988: void end_creating_path(const struct path *path, struct dentry *dentry)
4989: {
4990: end_creating(dentry);
4991: mnt_drop_write(path->mnt);
4992: path_put(path);
4993: }
SourceFile:fs/namei.c]
|
| 12/2 |
2026/06/09 16:11 |
llm |
expert |
0m
Model:
Tokens:
input: 10501
output: 33
thoughts: 9
Thoughts: Okay, here's my interpretation of that thought process, tailored for an expert audience, and written in the first person:
**Analyzing the `end_creating` Flag**
Right, let's see. My current thought process is focused on a crucial point: I need to ascertain the state of the `end_creating` flag. The nuance here is critical, as depending on what value is returned it means something quite significant. Based on the state of the flag I need to determine the status of some important processes. This likely pertains to the conclusion of a construction, data gathering, or object instantiation sequence. The value of this flag dictates the next steps and influences downstream operations significantly. So, my primary focus at this stage is to verify its value, interpret its meaning within the current context, and then proceed with the logical control flow dictated by its state. It's a fundamental checkpoint, a gatekeeper ensuring everything's in order before further operations.
|
| 13/2 |
2026/06/09 16:11 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/namei.c Name:end_creating]
Results: map[SourceCode: 106: /* end_creating - finish action started with start_creating
107: * @child: dentry returned by start_creating() or vfs_mkdir()
108: *
109: * Unlock and release the child. This can be called after
110: * start_creating() whether that function succeeded or not,
111: * but it is not needed on failure.
112: *
113: * If vfs_mkdir() was called then the value returned from that function
114: * should be given for @child rather than the original dentry, as vfs_mkdir()
115: * may have provided a new dentry.
116: *
117: *
118: * If vfs_mkdir() was not called, then @child will be a valid dentry and
119: * @parent will be ignored.
120: */
121: static inline void end_creating(struct dentry *child)
122: {
123: end_dirop(child);
124: }
SourceFile:include/linux/namei.h]
|
| 14/2 |
2026/06/09 16:11 |
llm |
expert |
0m
Model:
Tokens:
input: 11066
output: 34
thoughts: 10
Thoughts: Okay, here's my attempt at summarizing the thought process, as if it were my own, assuming I'm an expert in the field:
**Checking `end_dirop`**
Right, let's take a look at `end_dirop`. My initial thought is to quickly assess what this function is designed to do in relation to the current process. I'm assuming it's part of some larger system, and I need to understand its role within that context. Perhaps it's a cleanup routine after directory operations, or maybe it deals with handling the end of a specific type of operation related to the target data.
I'd want to immediately recall the surrounding code: What calls `end_dirop`? What arguments does it take? And crucially, what is its expected behaviour with various inputs? Is there a documentation reference? What potential race conditions can occur?
The name itself gives some clues - "end_dirop" suggests it could be related to the end of a directory operation. Is this a critical section protection mechanism? An error handling function? Is it a way of notifying other components? What's the protocol? Is it a global state variable?
I'm thinking, based on context, that the most important thing is to evaluate the implications of its execution. Is it synchronous or asynchronous? Does it block the current process? What resources does it release or allocate? What are the potential failure modes? And how are those failures handled? I need to quickly identify any potential bottlenecks or areas prone to errors.
My mental checklist starts with memory management, resource locking, error handling, and concurrency concerns. I'll need to know whether any cleanup needs to occur. It's often where the problems arise. I'll check its return value, if any, and see how that is handled.
From my perspective, a quick examination of `end_dirop` is vital, it can tell me how to best move forward.
|
| 15/2 |
2026/06/09 16:11 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/namei.c Name:end_dirop]
Results: map[SourceCode:2941: /**
2942: * end_dirop - signal completion of a dirop
2943: * @de: the dentry which was returned by start_dirop or similar.
2944: *
2945: * If the de is an error, nothing happens. Otherwise any lock taken to
2946: * protect the dentry is dropped and the dentry itself is release (dput()).
2947: */
2948: void end_dirop(struct dentry *de)
2949: {
2950: if (!IS_ERR(de)) {
2951: inode_unlock(de->d_parent->d_inode);
2952: dput(de);
2953: }
2954: }
SourceFile:fs/namei.c]
|
| 16/2 |
2026/06/09 16:11 |
llm |
expert |
0m
Model:
Tokens:
input: 11754
output: 33
thoughts: 437
Thoughts: **Debugging a Potential Double-Unlock in File System Operations**
Okay, so I'm tracing this `end_dirop` function, and it's calling `inode_unlock` on the parent's inode – makes sense, standard practice. But now, `inode_unlock` itself calls `up_write` on the inode's read-write semaphore (`i_rwsem`). And *that's* where the problem is. I'm seeing a warning: the classic `DEBUG_RWSEMS_WARN_ON`.
The core issue is that the semaphore's owner isn't the current process (`current`), but the owner pointer is `0x0`. That *always* means the semaphore isn't locked by anyone, which is the same as the `count = 0x0`, implying it's already unlocked. So `inode_unlock` is being called when the inode is either unlocked already, or not locked by the process calling `end_dirop`. That's a textbook double-unlock scenario or a race condition!
Now, let's back up and examine where this issue could be introduced. I'm focusing on the `filename_mkdirat` code path. It's a key entry point, so any oddity introduced there will impact this scenario. The code flow goes: `filename_create` which I believe will call `start_creating_path` or equivalent, so I'll need to double-check that. Then there's security checks and finally `vfs_mkdir`, and then `end_creating_path`. It looks like `end_creating_path` is where we would expect the call to `inode_unlock` to occur. We'll need to figure out what's going on in the `filename_create` code path to find out when this unlock should occur.
|
| 17/2 |
2026/06/09 16:12 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/namei.c Name:filename_create]
Results: map[SourceCode:4920: static struct dentry *filename_create(int dfd, struct filename *name,
4921: struct path *path, unsigned int lookup_flags)
4922: {
4923: struct dentry *dentry = ERR_PTR(-EEXIST);
4924: struct qstr last;
4925: bool want_dir = lookup_flags & LOOKUP_DIRECTORY;
4926: unsigned int reval_flag = lookup_flags & LOOKUP_REVAL;
4927: unsigned int create_flags = LOOKUP_CREATE | LOOKUP_EXCL;
4928: int type;
4929: int error;
4930:
4931: error = filename_parentat(dfd, name, reval_flag, path, &last, &type);
4932: if (error)
4933: return ERR_PTR(error);
4934:
4935: /*
4936: * Yucky last component or no last component at all?
4937: * (foo/., foo/.., /////)
4938: */
4939: if (unlikely(type != LAST_NORM))
4940: goto out;
4941:
4942: /* don't fail immediately if it's r/o, at least try to report other errors */
4943: error = mnt_want_write(path->mnt);
4944: /*
4945: * Do the final lookup. Suppress 'create' if there is a trailing
4946: * '/', and a directory wasn't requested.
4947: */
4948: if (last.name[last.len] && !want_dir)
4949: create_flags &= ~LOOKUP_CREATE;
4950: dentry = start_dirop(path->dentry, &last, reval_flag | create_flags);
4951: if (IS_ERR(dentry))
4952: goto out_drop_write;
4953:
4954: if (unlikely(error))
4955: goto fail;
4956:
4957: return dentry;
4958: fail:
4959: end_dirop(dentry);
4960: dentry = ERR_PTR(error);
4961: out_drop_write:
4962: if (!error)
4963: mnt_drop_write(path->mnt);
4964: out:
4965: path_put(path);
4966: return dentry;
4967: }
SourceFile:fs/namei.c]
|
| 18/2 |
2026/06/09 16:12 |
llm |
expert |
0m
Model:
Tokens:
input: 13353
output: 34
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:
**Navigating the File System: A Quick Look**
Alright, so `start_dirop`...that's my entry point for a directory operation, right? And what I get back is a dentry. Makes sense, that's the thing representing the directory entry itself. But what's really crucial here, and what I automatically consider, is that this call *also* grabs a lock on the parent inode. I have to make sure that as I delve into this, I have a clear grip of what's going on and that this entry isn't changing while I am doing something. Okay, so now I have a locked inode, and my starting dentry. It's a straightforward, yet critical, step in the whole process. I have the building blocks now.
|
| 19/2 |
2026/06/09 16:12 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/namei.c Name:start_dirop]
Results: map[SourceCode:2922: /**
2923: * start_dirop - begin a create or remove dirop, performing locking and lookup
2924: * @parent: the dentry of the parent in which the operation will occur
2925: * @name: a qstr holding the name within that parent
2926: * @lookup_flags: intent and other lookup flags.
2927: *
2928: * The lookup is performed and necessary locks are taken so that, on success,
2929: * the returned dentry can be operated on safely.
2930: * The qstr must already have the hash value calculated.
2931: *
2932: * Returns: a locked dentry, or an error.
2933: *
2934: */
2935: struct dentry *start_dirop(struct dentry *parent, struct qstr *name,
2936: unsigned int lookup_flags)
2937: {
2938: return __start_dirop(parent, name, lookup_flags, TASK_NORMAL);
2939: }
SourceFile:fs/namei.c]
|
| 20/2 |
2026/06/09 16:12 |
llm |
expert |
0m
Model:
Tokens:
input: 13918
output: 35
thoughts: 11
Thoughts: Okay, here's my attempt at summarizing that thought process, keeping in mind I'm speaking to an expert:
**Internal Code Check - `__start_dirop`**
Right, let's just make sure we're on the same page. The immediate priority is to examine `__start_dirop`. I want to quickly refresh my understanding of how this particular function is structured, what its inputs and outputs are, and, most importantly, where it sits within the broader architecture. Is this the entry point for directory operations? Does it interact with caching layers? How does it handle potential race conditions or locking? I'm expecting this to either be the *very* first step, or perhaps a wrapper for an initial setup that then calls the core functionality. I'm hoping it’s well-documented, because it's the kind of thing that has to be *absolutely* bulletproof.
|
| 21/2 |
2026/06/09 16:12 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/namei.c Name:__start_dirop]
Results: map[SourceCode:2901: static struct dentry *__start_dirop(struct dentry *parent, struct qstr *name,
2902: unsigned int lookup_flags,
2903: unsigned int state)
2904: {
2905: struct dentry *dentry;
2906: struct inode *dir = d_inode(parent);
2907:
2908: if (state == TASK_KILLABLE) {
2909: int ret = down_write_killable_nested(&dir->i_rwsem,
2910: I_MUTEX_PARENT);
2911: if (ret)
2912: return ERR_PTR(ret);
2913: } else {
2914: inode_lock_nested(dir, I_MUTEX_PARENT);
2915: }
2916: dentry = lookup_one_qstr_excl(name, parent, lookup_flags);
2917: if (IS_ERR(dentry))
2918: inode_unlock(dir);
2919: return dentry;
2920: }
SourceFile:fs/namei.c]
|
| 22/2 |
2026/06/09 16:12 |
llm |
expert |
0m
Model:
Tokens:
input: 14484
output: 34
thoughts: 60
Thoughts: Okay, let's break this down.
**Analyzing the Directory Creation Process**
Right, so I'm focusing on the directory creation sequence here. The initial point is that `__start_dirop` is locking the `dir->i_rwsem` semaphore. Makes sense, that's the standard locking mechanism for directory operations, ensuring exclusive access. Now, the next logical step is `vfs_mkdir`, which is actually called inside `filename_mkdirat`.
Hold on... this triggers a question. It hits me that `vfs_mkdir` might actually release the lock on the parent inode at some point. It's a key point to confirm because the locking order is critical in any filesystem. I need to make sure the locking order is correct, so let's check `vfs_mkdir` and confirm that behavior. Time to delve into the source code and confirm my suspicion.
|
| 23/2 |
2026/06/09 16:12 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/namei.c Name:vfs_mkdir]
Results: map[SourceCode:5210: /**
5211: * vfs_mkdir - create directory returning correct dentry if possible
5212: * @idmap: idmap of the mount the inode was found from
5213: * @dir: inode of the parent directory
5214: * @dentry: dentry of the child directory
5215: * @mode: mode of the child directory
5216: * @delegated_inode: returns parent inode, if the inode is delegated.
5217: *
5218: * Create a directory.
5219: *
5220: * If the inode has been found through an idmapped mount the idmap of
5221: * the vfsmount must be passed through @idmap. This function will then take
5222: * care to map the inode according to @idmap before checking permissions.
5223: * On non-idmapped mounts or if permission checking is to be performed on the
5224: * raw inode simply pass @nop_mnt_idmap.
5225: *
5226: * In the event that the filesystem does not use the *@dentry but leaves it
5227: * negative or unhashes it and possibly splices a different one returning it,
5228: * the original dentry is dput() and the alternate is returned.
5229: *
5230: * In case of an error the dentry is dput() and an ERR_PTR() is returned.
5231: */
5232: struct dentry *vfs_mkdir(struct mnt_idmap *idmap, struct inode *dir,
5233: struct dentry *dentry, umode_t mode,
5234: struct delegated_inode *delegated_inode)
5235: {
5236: int error;
5237: unsigned max_links = dir->i_sb->s_max_links;
5238: struct dentry *de;
5239:
5240: error = may_create_dentry(idmap, dir, dentry);
5241: if (error)
5242: goto err;
5243:
5244: error = -EPERM;
5245: if (!dir->i_op->mkdir)
5246: goto err;
5247:
5248: mode = vfs_prepare_mode(idmap, dir, mode, S_IRWXUGO | S_ISVTX, 0);
5249: error = security_inode_mkdir(dir, dentry, mode);
5250: if (error)
5251: goto err;
5252:
5253: error = -EMLINK;
5254: if (max_links && dir->i_nlink >= max_links)
5255: goto err;
5256:
5257: error = try_break_deleg(dir, delegated_inode);
5258: if (error)
5259: goto err;
5260:
5261: de = dir->i_op->mkdir(idmap, dir, dentry, mode);
5262: error = PTR_ERR(de);
5263: if (IS_ERR(de))
5264: goto err;
5265: if (de) {
5266: dput(dentry);
5267: dentry = de;
5268: }
5269: fsnotify_mkdir(dir, dentry);
5270: return dentry;
5271:
5272: err:
5273: end_creating(dentry);
5274: return ERR_PTR(error);
5275: }
SourceFile:fs/namei.c]
|
| 24/2 |
2026/06/09 16:12 |
llm |
expert |
0m
Model:
Tokens:
input: 15804
output: 35
thoughts: 197
Thoughts: **Double `end_creating` Call in `filename_mkdirat`?**
Okay, so I'm looking at the flow of directory creation, specifically how `filename_mkdirat` handles errors. I see that `vfs_mkdir` calls `end_creating(dentry)` if it fails, which is correct. But then I'm noticing something potentially problematic with how `filename_mkdirat` handles the return from `vfs_mkdir`. It seems like if `vfs_mkdir` fails, `end_creating(dentry)` *has already been called*. However, the code proceeds to call `end_creating_path(&path, dentry)` after this, and I remember `end_creating_path` *also* calls `end_creating(dentry)`! That's a double call! I need to re-examine the implementation of `end_creating_path` to confirm, but this immediately raises a red flag for a potential double free or similar issue if `end_creating` isn't designed to be idempotent. I need to dig deeper.
|
| 25/2 |
2026/06/09 16:12 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/namei.c Name:end_creating_path]
Results: map[SourceCode:4977: /**
4978: * end_creating_path - finish a code section started by start_creating_path()
4979: * @path: the path instantiated by start_creating_path()
4980: * @dentry: the dentry returned by start_creating_path()
4981: *
4982: * end_creating_path() will unlock and locks taken by start_creating_path()
4983: * and drop an references that were taken. It should only be called
4984: * if start_creating_path() returned a non-error.
4985: * If vfs_mkdir() was called and it returned an error, that error *should*
4986: * be passed to end_creating_path() together with the path.
4987: */
4988: void end_creating_path(const struct path *path, struct dentry *dentry)
4989: {
4990: end_creating(dentry);
4991: mnt_drop_write(path->mnt);
4992: path_put(path);
4993: }
SourceFile:fs/namei.c]
|
| 26/2 |
2026/06/09 16:12 |
llm |
expert |
0m
Model:
Tokens:
input: 16593
output: 33
thoughts: 49
Thoughts: Okay, let me think this through.
**Analyzing Potential Error Handling in File System Path Creation**
Hold on a second... the `end_creating_path` function, as I recall, expects a `dentry` argument. But if `vfs_mkdir` – the function we were just looking at – returns an error, that `dentry` pointer is going to be set to `ERR_PTR(error)`. That's a classic gotcha! We can't pass an error pointer directly to a function expecting a valid `dentry`. It would lead to... well, let's just say it would cause problems.
Right, so we need to be very careful with how we handle that return code. We can't just blithely pass it along. We *must* check the return value from the `vfs_mkdir` call before we feed anything into `end_creating_path`. It feels like there's an potential bug, especially if there's any code that assumes a valid `dentry` will always be passed to that final step.
Okay, let's go check the definition of `end_creating`. That's where the error handling for the dentry is processed. I'm going to have to make sure there's robust code there to handle the case where it gets a bad pointer.
|
| 27/2 |
2026/06/09 16:12 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/namei.c Name:end_creating]
Results: map[SourceCode: 106: /* end_creating - finish action started with start_creating
107: * @child: dentry returned by start_creating() or vfs_mkdir()
108: *
109: * Unlock and release the child. This can be called after
110: * start_creating() whether that function succeeded or not,
111: * but it is not needed on failure.
112: *
113: * If vfs_mkdir() was called then the value returned from that function
114: * should be given for @child rather than the original dentry, as vfs_mkdir()
115: * may have provided a new dentry.
116: *
117: *
118: * If vfs_mkdir() was not called, then @child will be a valid dentry and
119: * @parent will be ignored.
120: */
121: static inline void end_creating(struct dentry *child)
122: {
123: end_dirop(child);
124: }
SourceFile:include/linux/namei.h]
|
| 28/2 |
2026/06/09 16:12 |
llm |
expert |
0m
Model:
Tokens:
input: 17285
output: 34
thoughts: 7
Thoughts: Okay, here's my attempt at summarizing the thought process, tailored for an expert audience, and written in the first person:
**Navigating the `end_dirop` Process**
Alright, let's break this down. When I see `end_dirop`, I'm immediately thinking about its implications for resource management, specifically within whatever context this code is operating. The initial trigger for `end_dirop` likely indicates that a particular directory operation is complete. That could be anything from a simple directory listing, file creation, deletion or a complex hierarchical traversal.
My mind jumps to checking for proper cleanup. What exactly needs to be finalized when a directory operation has finished? Are we talking about releasing file descriptors, memory allocated for directory entries, or perhaps signaling a completion event to a waiting process? I need to know precisely which of the possible actions is being triggered. I would be looking for the details to confirm.
I'm also considering the potential error paths. Does `end_dirop` guarantee successful completion, or is there a possibility of partial success or failure? If so, what mechanisms are in place to handle those scenarios? Error handling around `end_dirop` is probably critical to ensuring data integrity. Specifically, I'd want to be sure that the resources are properly handled and any state changes are consistent with the operation.
Then I am going to have to consider potential race conditions. If multiple threads or processes might interact with the same directory structure, I need to know how `end_dirop` interacts with any synchronization primitives used to protect concurrent access. Is there an atomic operation? Mutex? Semaphores? Is the system designed to be thread-safe or does it rely on external locking mechanisms?
My immediate goal would be to look for the function which calls `end_dirop`, and analyze the context. I'm looking for clues about the overall flow of the program and how the resources are managed, to be sure I understand the operation. The specifics of the operating system and the filesystem in use are essential. The implementation will vary based on this context.
Finally, I'm thinking about performance. Does `end_dirop` represent a significant overhead? Are there potential optimizations that could be applied, such as batching operations or employing more efficient data structures? I'd need to consider if its impact is likely to be a performance bottleneck under heavy load.
And `end_dirop`.
|
| 29/2 |
2026/06/09 16:12 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/namei.c Name:end_dirop]
Results: map[SourceCode:2941: /**
2942: * end_dirop - signal completion of a dirop
2943: * @de: the dentry which was returned by start_dirop or similar.
2944: *
2945: * If the de is an error, nothing happens. Otherwise any lock taken to
2946: * protect the dentry is dropped and the dentry itself is release (dput()).
2947: */
2948: void end_dirop(struct dentry *de)
2949: {
2950: if (!IS_ERR(de)) {
2951: inode_unlock(de->d_parent->d_inode);
2952: dput(de);
2953: }
2954: }
SourceFile:fs/namei.c]
|
| 30/2 |
2026/06/09 16:12 |
llm |
expert |
0m
Model:
Tokens:
input: 18068
output: 33
thoughts: 1087
Thoughts: **Debugging a Potential Double-Unlock in File Creation**
Okay, so I'm tracing the error handling in this `mkdir` process, and it seems like I might have a double unlock issue. Here's what I'm thinking:
First, I realized that `end_dirop` is a no-op if the dentry is an error pointer (`IS_ERR(de)`). Good. That means if `vfs_mkdir` returns an error, it calls `end_creating` to clean up the original dentry *before* returning the error, and that error gets propagated. Then, `end_creating_path` is correctly called, but since it's an error it knows not to call the no-op `end_dirop`, so that's handled.
I went back and read `vfs_mkdir`, and confirmed my assumptions: if it encounters an error, it correctly calls `end_creating` and returns an error pointer. Then I looked again at how `filename_mkdirat` is called.
Now, let's play the game.
1. if `security_path_mkdir` errors, `vfs_mkdir` isn't called, which is correct and `end_creating_path` then is called, and cleans up the original dentry.
2. if `vfs_mkdir` does error, it correctly calls `end_creating` *inside* `vfs_mkdir`, so that cleanup *does* happen. `filename_mkdirat` sees the error and then again calls `end_creating_path` with an error. The error does not call `end_dirop` which is also correct.
3. If `vfs_mkdir` succeeds and returns a valid dentry, `filename_mkdirat` has `end_creating_path` with the new or original dentry. It calls `end_creating` which then calls `end_dirop`, which then *correctly* unlocks the parent and dputs the new dentry. This seems correct as well.
So, I'm not seeing any obvious double-unlocks in the error paths or successful path of the `mkdir` code. This is perplexing, I was certain I was on the right path.
I need to expand the search, let's try `filename_create`. Perhaps the error resides there.
|
| 31/2 |
2026/06/09 16:12 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/namei.c Name:filename_create]
Results: map[SourceCode:4920: static struct dentry *filename_create(int dfd, struct filename *name,
4921: struct path *path, unsigned int lookup_flags)
4922: {
4923: struct dentry *dentry = ERR_PTR(-EEXIST);
4924: struct qstr last;
4925: bool want_dir = lookup_flags & LOOKUP_DIRECTORY;
4926: unsigned int reval_flag = lookup_flags & LOOKUP_REVAL;
4927: unsigned int create_flags = LOOKUP_CREATE | LOOKUP_EXCL;
4928: int type;
4929: int error;
4930:
4931: error = filename_parentat(dfd, name, reval_flag, path, &last, &type);
4932: if (error)
4933: return ERR_PTR(error);
4934:
4935: /*
4936: * Yucky last component or no last component at all?
4937: * (foo/., foo/.., /////)
4938: */
4939: if (unlikely(type != LAST_NORM))
4940: goto out;
4941:
4942: /* don't fail immediately if it's r/o, at least try to report other errors */
4943: error = mnt_want_write(path->mnt);
4944: /*
4945: * Do the final lookup. Suppress 'create' if there is a trailing
4946: * '/', and a directory wasn't requested.
4947: */
4948: if (last.name[last.len] && !want_dir)
4949: create_flags &= ~LOOKUP_CREATE;
4950: dentry = start_dirop(path->dentry, &last, reval_flag | create_flags);
4951: if (IS_ERR(dentry))
4952: goto out_drop_write;
4953:
4954: if (unlikely(error))
4955: goto fail;
4956:
4957: return dentry;
4958: fail:
4959: end_dirop(dentry);
4960: dentry = ERR_PTR(error);
4961: out_drop_write:
4962: if (!error)
4963: mnt_drop_write(path->mnt);
4964: out:
4965: path_put(path);
4966: return dentry;
4967: }
SourceFile:fs/namei.c]
|
| 32/2 |
2026/06/09 16:12 |
llm |
expert |
0m
Model:
Tokens:
input: 20428
output: 37
thoughts: 1218
Thoughts: **Analyzing the Mkdir Path in the Filesystem**
Okay, let's step through this. I see that `filename_create` calls `start_dirop`, which is expected. If `start_dirop` gives us a valid dentry, but `mnt_want_write` fails with a non-zero `error` (like a read-only filesystem), we go to `fail`. The `fail` routine then calls `end_dirop(dentry)`. `error` stems from `mnt_want_write` which makes perfect sense. If we can't write, we can't create. So, assuming `start_dirop` succeeded, we've locked the parent inode. Then, `fail` calls `end_dirop(dentry)`, which unlocks the parent and puts the dentry. The code then converts the error into a pointer `dentry = ERR_PTR(error)`, and proceeds to `out_drop_write`. Since `error` is non-zero, it skips `mnt_drop_write`. Finally, it proceeds to `out`, calls `path_put(path)`, and returns `ERR_PTR(error)`. It all seems to be working correctly.
Now, let's refocus on `vfs_mkdir`. It calls the filesystem's `mkdir` operation: `dir->i_op->mkdir(idmap, dir, dentry, mode)`. Crucially, if that call *succeeds* but somehow *unlocks* the parent inode prematurely, there's a problem. But `mkdir` *shouldn't* unlock the parent. Also, if `mkdir` returns an error pointer `ERR_PTR(...)`, `vfs_mkdir` jumps to `err`, which calls `end_creating(dentry)`. This, in turn, calls `end_dirop(dentry)`. Inside `end_dirop` is a call to `inode_unlock(de->d_parent->d_inode)`. Now, if that parent inode is *not* what we think it is (namely `dir`), there's a logic error. Of course, `dentry->d_parent->d_inode` *should* be `dir`.
Now, what about `try_break_deleg`? If that returns an error like `-EWOULDBLOCK`, we jump to `err`, again calling `end_creating(dentry)`, unlocking the parent. Then `vfs_mkdir` returns `ERR_PTR(error)`. Now, in `filename_mkdirat`, we see `IS_ERR(dentry)`, set `error = PTR_ERR(dentry)` and call `end_creating_path(&path, dentry)`. Since `dentry` is an error pointer, this doesn't unlock. We also have that delegation break wait after the call, which seems correct. If `try_break_deleg` sets `delegated_inode` and returns `-EWOULDBLOCK`, `vfs_mkdir` has already called `end_creating(dentry)`, unlocking. Then `filename_mkdirat` calls `break_deleg_wait(&delegated_inode)` and we retry. Good.
Now, the *successful* case where `dir->i_op->mkdir` returns a *new* valid dentry `de`. `vfs_mkdir` updates the dentry `dentry = de` and returns. `filename_mkdirat` calls `end_creating_path(&path, dentry)`, which calls `end_creating`, and then `end_dirop`. Again, `end_dirop` calls `inode_unlock(dentry->d_parent->d_inode)`. This is where I have to be careful. If `dir->i_op->mkdir` returns *a new* dentry, does that dentry's parent have to be correctly set? Yes, I believe `d_splice_alias` or similar routines handle that, right? But again, what if `de->d_parent->d_inode` isn't `dir`? If the new dentry forms a root of a disconnected tree, the mkdir operation should not be constructing such dentry.
I should probably review recent commits in `fs/namei.c`, `fs/internal.h`, or `include/linux/namei.h` to see if there have been any changes or fixes related to these concerns.
|
| 33/2 |
2026/06/09 16:13 |
tool |
git-log |
0m
Args: map[CodeRegexp:end_creating Count:10 PathPrefix:fs/namei.c]
Results: map[Output:fe497f0759e0 VFS: change vfs_mkdir() to unlock on failure.
f046fbb4d81d ecryptfs: use new start_creating/start_removing APIs
7ab96df840e6 VFS/nfsd/cachefiles/ovl: add start_creating() and end_creating()
4037d966f034 VFS: introduce start_dirop() and end_dirop()
3d18f80ce181 VFS: rename kern_path_locked() and related functions.
]
|
| 34/2 |
2026/06/09 16:13 |
llm |
expert |
0m
Model:
Tokens:
input: 22787
output: 26
thoughts: 37
Thoughts: Okay, here's my take on those thoughts, framed as an expert's internal monologue:
**Investigating a VFS Optimization**
Right, so "VFS: change vfs_mkdir() to unlock on failure" – that sounds like a potentially significant performance tweak for the virtual filesystem layer. Specifically, the commit `fe497f0759e0`. This suggests they're addressing a locking issue within `vfs_mkdir()`, likely related to the handling of error cases. My immediate assumption is that, in the past, a failure might have left a lock held unnecessarily, which is a classic bottleneck. It's not uncommon to see such optimizations in mature filesystems. The details are, of course, where it gets interesting. I'll need to dig into the code associated with that commit to see what they were addressing. I'm guessing a brief study of that commit's diff will be more than enough to get the gist, but I'll make notes about any interesting edge cases they may have addressed, or performance metrics they may have used. I'll be looking for changes in lock acquisition and release sequences and error paths. This could be a nice example of how to make subtle improvements to the system by minimizing lock contention. Gotta verify this wasn't a regression, too, of course! Alright, let's see what they actually did...
|
| 35/2 |
2026/06/09 16:13 |
tool |
git-show |
0m
Args:
Results: map[Output:commit fe497f0759e0efb949f9480911d00b6045c21f50
Author: NeilBrown <neil@brown.name>
Date: Thu Nov 13 11:18:37 2025 +1100
VFS: change vfs_mkdir() to unlock on failure.
vfs_mkdir() already drops the reference to the dentry on failure but it
leaves the parent locked.
This complicates end_creating() which needs to unlock the parent even
though the dentry is no longer available.
If we change vfs_mkdir() to unlock on failure as well as releasing the
dentry, we can remove the "parent" arg from end_creating() and simplify
the rules for calling it.
Note that cachefiles_get_directory() can choose to substitute an error
instead of actually calling vfs_mkdir(), for fault injection. In that
case it needs to call end_creating(), just as vfs_mkdir() now does on
error.
ovl_create_real() will now unlock on error. So the conditional
end_creating() after the call is removed, and end_creating() is called
internally on error.
Reviewed-by: Amir Goldstein <amir73il@gmail.com>
Reviewed-by: Jeff Layton <jlayton@kernel.org>
Tested-by: syzbot@syzkaller.appspotmail.com
Signed-off-by: NeilBrown <neil@brown.name>
Link: https://patch.msgid.link/20251113002050.676694-15-neilb@ownmail.net
Signed-off-by: Christian Brauner <brauner@kernel.org>
diff --git a/Documentation/filesystems/porting.rst b/Documentation/filesystems/porting.rst
index 7233b04668fc..76ff738a00f3 100644
--- a/Documentation/filesystems/porting.rst
+++ b/Documentation/filesystems/porting.rst
@@ -1309,3 +1309,16 @@ a different length, use
vfs_parse_fs_qstr(fc, key, &QSTR_LEN(value, len))
instead.
+
+---
+
+**mandatory**
+
+vfs_mkdir() now returns a dentry - the one returned by ->mkdir(). If
+that dentry is different from the dentry passed in, including if it is
+an IS_ERR() dentry pointer, the original dentry is dput().
+
+When vfs_mkdir() returns an error, and so both dputs() the original
+dentry and doesn't provide a replacement, it also unlocks the parent.
+Consequently the return value from vfs_mkdir() can be passed to
+end_creating() and the parent will be unlocked precisely when necessary.
diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c
index 4fbfdd8faf6a..90ef777eae25 100644
--- a/fs/btrfs/ioctl.c
+++ b/fs/btrfs/ioctl.c
@@ -935,7 +935,7 @@ static noinline int btrfs_mksubvol(struct dentry *parent,
out_up_read:
up_read(&fs_info->subvol_sem);
out_dput:
- end_creating(dentry, parent);
+ end_creating(dentry);
return ret;
}
diff --git a/fs/cachefiles/namei.c b/fs/cachefiles/namei.c
index 0104ac00485d..59327618ac42 100644
--- a/fs/cachefiles/namei.c
+++ b/fs/cachefiles/namei.c
@@ -128,10 +128,12 @@ struct dentry *cachefiles_get_directory(struct cachefiles_cache *cache,
if (ret < 0)
goto mkdir_error;
ret = cachefiles_inject_write_error();
- if (ret == 0)
+ if (ret == 0) {
subdir = vfs_mkdir(&nop_mnt_idmap, d_inode(dir), subdir, 0700);
- else
+ } else {
+ end_creating(subdir);
subdir = ERR_PTR(ret);
+ }
if (IS_ERR(subdir)) {
trace_cachefiles_vfs_error(NULL, d_inode(dir), ret,
cachefiles_trace_mkdir_error);
@@ -140,7 +142,7 @@ struct dentry *cachefiles_get_directory(struct cachefiles_cache *cache,
trace_cachefiles_mkdir(dir, subdir);
if (unlikely(d_unhashed(subdir) || d_is_negative(subdir))) {
- end_creating(subdir, dir);
+ end_creating(subdir);
goto retry;
}
ASSERT(d_backing_inode(subdir));
@@ -154,7 +156,7 @@ struct dentry *cachefiles_get_directory(struct cachefiles_cache *cache,
/* Tell rmdir() it's not allowed to delete the subdir */
inode_lock(d_inode(subdir));
dget(subdir);
- end_creating(subdir, dir);
+ end_creating(subdir);
if (!__cachefiles_mark_inode_in_use(NULL, d_inode(subdir))) {
pr_notice("cachefiles: Inode already in use: %pd (B=%lx)\n",
@@ -196,7 +198,7 @@ struct dentry *cachefiles_get_directory(struct cachefiles_cache *cache,
return ERR_PTR(-EBUSY);
mkdir_error:
- end_creating(subdir, dir);
+ end_creating(subdir);
pr_err("mkdir %s failed with error %d\n", dirname, ret);
return ERR_PTR(ret);
@@ -699,7 +701,7 @@ bool cachefiles_commit_tmpfile(struct cachefiles_cache *cache,
if (ret < 0)
goto out_end;
- end_creating(dentry, fan);
+ end_creating(dentry);
ret = cachefiles_inject_read_error();
if (ret == 0)
@@ -733,7 +735,7 @@ bool cachefiles_commit_tmpfile(struct cachefiles_cache *cache,
}
out_end:
- end_creating(dentry, fan);
+ end_creating(dentry);
out:
_leave(" = %u", success);
return success;
diff --git a/fs/ecryptfs/inode.c b/fs/ecryptfs/inode.c
index 6a5bca89e752..2ad1db2cd2ec 100644
--- a/fs/ecryptfs/inode.c
+++ b/fs/ecryptfs/inode.c
@@ -211,7 +211,7 @@ ecryptfs_do_create(struct inode *directory_inode,
fsstack_copy_attr_times(directory_inode, lower_dir);
fsstack_copy_inode_size(directory_inode, lower_dir);
out_lock:
- end_creating(lower_dentry, NULL);
+ end_creating(lower_dentry);
return inode;
}
@@ -456,7 +456,7 @@ static int ecryptfs_link(struct dentry *old_dentry, struct inode *dir,
ecryptfs_inode_to_lower(d_inode(old_dentry))->i_nlink);
i_size_write(d_inode(new_dentry), file_size_save);
out_lock:
- end_creating(lower_new_dentry, NULL);
+ end_creating(lower_new_dentry);
return rc;
}
@@ -500,7 +500,7 @@ static int ecryptfs_symlink(struct mnt_idmap *idmap,
fsstack_copy_attr_times(dir, lower_dir);
fsstack_copy_inode_size(dir, lower_dir);
out_lock:
- end_creating(lower_dentry, NULL);
+ end_creating(lower_dentry);
if (d_really_is_negative(dentry))
d_drop(dentry);
return rc;
@@ -534,7 +534,7 @@ static struct dentry *ecryptfs_mkdir(struct mnt_idmap *idmap, struct inode *dir,
fsstack_copy_inode_size(dir, lower_dir);
set_nlink(dir, lower_dir->i_nlink);
out:
- end_creating(lower_dentry, lower_dir_dentry);
+ end_creating(lower_dentry);
if (d_really_is_negative(dentry))
d_drop(dentry);
return ERR_PTR(rc);
diff --git a/fs/namei.c b/fs/namei.c
index 8b7807cd1343..d284ebae41bf 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -4832,7 +4832,7 @@ EXPORT_SYMBOL(start_creating_path);
*/
void end_creating_path(const struct path *path, struct dentry *dentry)
{
- end_creating(dentry, path->dentry);
+ end_creating(dentry);
mnt_drop_write(path->mnt);
path_put(path);
}
@@ -5034,7 +5034,7 @@ struct dentry *vfs_mkdir(struct mnt_idmap *idmap, struct inode *dir,
return dentry;
err:
- dput(dentry);
+ end_creating(dentry);
return ERR_PTR(error);
}
EXPORT_SYMBOL(vfs_mkdir);
diff --git a/fs/nfsd/nfs3proc.c b/fs/nfsd/nfs3proc.c
index e2aac0def2cb..6b39e4aff959 100644
--- a/fs/nfsd/nfs3proc.c
+++ b/fs/nfsd/nfs3proc.c
@@ -364,7 +364,7 @@ nfsd3_create_file(struct svc_rqst *rqstp, struct svc_fh *fhp,
status = nfsd_create_setattr(rqstp, fhp, resfhp, &attrs);
out:
- end_creating(child, parent);
+ end_creating(child);
out_write:
fh_drop_write(fhp);
return status;
diff --git a/fs/nfsd/nfs4proc.c b/fs/nfsd/nfs4proc.c
index b2c95e8e7c68..524cb07a477c 100644
--- a/fs/nfsd/nfs4proc.c
+++ b/fs/nfsd/nfs4proc.c
@@ -376,7 +376,7 @@ nfsd4_create_file(struct svc_rqst *rqstp, struct svc_fh *fhp,
if (attrs.na_aclerr)
open->op_bmval[0] &= ~FATTR4_WORD0_ACL;
out:
- end_creating(child, parent);
+ end_creating(child);
nfsd_attrs_free(&attrs);
out_write:
fh_drop_write(fhp);
diff --git a/fs/nfsd/nfs4recover.c b/fs/nfsd/nfs4recover.c
index 3eefaa2202e3..18c08395b273 100644
--- a/fs/nfsd/nfs4recover.c
+++ b/fs/nfsd/nfs4recover.c
@@ -215,7 +215,7 @@ nfsd4_create_clid_dir(struct nfs4_client *clp)
if (IS_ERR(dentry))
status = PTR_ERR(dentry);
out_end:
- end_creating(dentry, dir);
+ end_creating(dentry);
out:
if (status == 0) {
if (nn->in_grace)
diff --git a/fs/nfsd/nfsproc.c b/fs/nfsd/nfsproc.c
index ee1b16e921fd..28f03a6a3cc3 100644
--- a/fs/nfsd/nfsproc.c
+++ b/fs/nfsd/nfsproc.c
@@ -421,7 +421,7 @@ nfsd_proc_create(struct svc_rqst *rqstp)
}
out_unlock:
- end_creating(dchild, dirfhp->fh_dentry);
+ end_creating(dchild);
out_write:
fh_drop_write(dirfhp);
done:
diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c
index 62109885d4db..6e9a57863904 100644
--- a/fs/nfsd/vfs.c
+++ b/fs/nfsd/vfs.c
@@ -1589,7 +1589,7 @@ nfsd_create_locked(struct svc_rqst *rqstp, struct svc_fh *fhp,
out:
if (!err)
fh_fill_post_attrs(fhp);
- end_creating(dchild, dentry);
+ end_creating(dchild);
return err;
out_nfserr:
@@ -1646,7 +1646,7 @@ nfsd_create(struct svc_rqst *rqstp, struct svc_fh *fhp,
return err;
out_unlock:
- end_creating(dchild, dentry);
+ end_creating(dchild);
return err;
}
@@ -1747,7 +1747,7 @@ nfsd_symlink(struct svc_rqst *rqstp, struct svc_fh *fhp,
nfsd_create_setattr(rqstp, fhp, resfhp, attrs);
fh_fill_post_attrs(fhp);
out_unlock:
- end_creating(dnew, dentry);
+ end_creating(dnew);
if (!err)
err = nfserrno(commit_metadata(fhp));
if (!err)
@@ -1824,7 +1824,7 @@ nfsd_link(struct svc_rqst *rqstp, struct svc_fh *ffhp,
host_err = vfs_link(dold, &nop_mnt_idmap, dirp, dnew, NULL);
fh_fill_post_attrs(ffhp);
out_unlock:
- end_creating(dnew, ddir);
+ end_creating(dnew);
if (!host_err) {
host_err = commit_metadata(ffhp);
if (!host_err)
diff --git a/fs/overlayfs/copy_up.c b/fs/overlayfs/copy_up.c
index 27014ada11c7..36949856ddea 100644
--- a/fs/overlayfs/copy_up.c
+++ b/fs/overlayfs/copy_up.c
@@ -624,7 +624,7 @@ static int ovl_link_up(struct ovl_copy_up_ctx *c)
ovl_dentry_set_upper_alias(c->dentry);
ovl_dentry_update_reval(c->dentry, upper);
}
- end_creating(upper, upperdir);
+ end_creating(upper);
}
if (err)
goto out;
@@ -891,7 +891,7 @@ static int ovl_copy_up_tmpfile(struct ovl_copy_up_ctx *c)
err = PTR_ERR(upper);
if (!IS_ERR(upper)) {
err = ovl_do_link(ofs, temp, udir, upper);
- end_creating(upper, c->destdir);
+ end_creating(upper);
}
if (err)
diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c
index b7f443932d93..e097ef4e79d2 100644
--- a/fs/overlayfs/dir.c
+++ b/fs/overlayfs/dir.c
@@ -91,7 +91,7 @@ static struct dentry *ovl_whiteout(struct ovl_fs *ofs)
err = ovl_do_whiteout(ofs, wdir, whiteout);
if (!err)
ofs->whiteout = dget(whiteout);
- end_creating(whiteout, workdir);
+ end_creating(whiteout);
if (err)
return ERR_PTR(err);
}
@@ -103,7 +103,7 @@ static struct dentry *ovl_whiteout(struct ovl_fs *ofs)
err = ovl_do_link(ofs, ofs->whiteout, wdir, link);
if (!err)
whiteout = dget(link);
- end_creating(link, workdir);
+ end_creating(link);
if (!err)
return whiteout;;
@@ -187,7 +187,7 @@ struct dentry *ovl_create_real(struct ovl_fs *ofs, struct dentry *parent,
if (!err && ofs->casefold != ovl_dentry_casefolded(newdentry)) {
pr_warn_ratelimited("wrong inherited casefold (%pd2)\n",
newdentry);
- dput(newdentry);
+ end_creating(newdentry);
err = -EINVAL;
}
break;
@@ -237,8 +237,7 @@ struct dentry *ovl_create_real(struct ovl_fs *ofs, struct dentry *parent,
}
out:
if (err) {
- if (!IS_ERR(newdentry))
- dput(newdentry);
+ end_creating(newdentry);
return ERR_PTR(err);
}
return newdentry;
@@ -254,7 +253,7 @@ struct dentry *ovl_create_temp(struct ovl_fs *ofs, struct dentry *workdir,
ret = ovl_create_real(ofs, workdir, ret, attr);
if (!IS_ERR(ret))
dget(ret);
- end_creating(ret, workdir);
+ end_creating(ret);
return ret;
}
@@ -362,12 +361,11 @@ static int ovl_create_upper(struct dentry *dentry, struct inode *inode,
if (IS_ERR(newdentry))
return PTR_ERR(newdentry);
newdentry = ovl_create_real(ofs, upperdir, newdentry, attr);
- if (IS_ERR(newdentry)) {
- end_creating(newdentry, upperdir);
+ if (IS_ERR(newdentry))
return PTR_ERR(newdentry);
- }
+
dget(newdentry);
- end_creating(newdentry, upperdir);
+ end_creating(newdentry);
if (ovl_type_merge(dentry->d_parent) && d_is_dir(newdentry) &&
!ovl_allow_offline_changes(ofs)) {
diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c
index a721ef2b90e8..3acda985c8a3 100644
--- a/fs/overlayfs/super.c
+++ b/fs/overlayfs/super.c
@@ -320,7 +320,7 @@ static struct dentry *ovl_workdir_create(struct ovl_fs *ofs,
if (work->d_inode) {
dget(work);
- end_creating(work, ofs->workbasedir);
+ end_creating(work);
if (persist)
return work;
err = -EEXIST;
@@ -338,7 +338,7 @@ static struct dentry *ovl_workdir_create(struct ovl_fs *ofs,
work = ovl_do_mkdir(ofs, dir, work, attr.ia_mode);
if (!IS_ERR(work))
dget(work);
- end_creating(work, ofs->workbasedir);
+ end_creating(work);
err = PTR_ERR(work);
if (IS_ERR(work))
goto out_err;
@@ -632,7 +632,7 @@ static struct dentry *ovl_lookup_or_create(struct ovl_fs *ofs,
OVL_CATTR(mode));
if (!IS_ERR(child))
dget(child);
- end_creating(child, parent);
+ end_creating(child);
}
dput(parent);
diff --git a/fs/xfs/scrub/orphanage.c b/fs/xfs/scrub/orphanage.c
index e732605924a1..b77c2b6b6d44 100644
--- a/fs/xfs/scrub/orphanage.c
+++ b/fs/xfs/scrub/orphanage.c
@@ -199,7 +199,7 @@ xrep_orphanage_create(
sc->orphanage_ilock_flags = 0;
out_dput_orphanage:
- end_creating(orphanage_dentry, root_dentry);
+ end_creating(orphanage_dentry);
out_dput_root:
dput(root_dentry);
out:
diff --git a/include/linux/namei.h b/include/linux/namei.h
index 208aed1d6728..0ef73d739a31 100644
--- a/include/linux/namei.h
+++ b/include/linux/namei.h
@@ -105,34 +105,24 @@ struct dentry *start_creating_dentry(struct dentry *parent,
struct dentry *start_removing_dentry(struct dentry *parent,
struct dentry *child);
-/**
- * end_creating - finish action started with start_creating
- * @child: dentry returned by start_creating() or vfs_mkdir()
- * @parent: dentry given to start_creating(),
- *
- * Unlock and release the child.
+/* end_creating - finish action started with start_creating
+ * @child: dentry returned by start_creating() or vfs_mkdir()
*
- * Unlike end_dirop() this can only be called if start_creating() succeeded.
- * It handles @child being and error as vfs_mkdir() might have converted the
- * dentry to an error - in that case the parent still needs to be unlocked.
+ * Unlock and release the child. This can be called after
+ * start_creating() whether that function succeeded or not,
+ * but it is not needed on failure.
*
* If vfs_mkdir() was called then the value returned from that function
* should be given for @child rather than the original dentry, as vfs_mkdir()
- * may have provided a new dentry. Even if vfs_mkdir() returns an error
- * it must be given to end_creating().
+ * may have provided a new dentry.
+ *
*
* If vfs_mkdir() was not called, then @child will be a valid dentry and
* @parent will be ignored.
*/
-static inline void end_creating(struct dentry *child, struct dentry *parent)
+static inline void end_creating(struct dentry *child)
{
- if (IS_ERR(child))
- /* The parent is still locked despite the error from
- * vfs_mkdir() - must unlock it.
- */
- inode_unlock(parent->d_inode);
- else
- end_dirop(child);
+ end_dirop(child);
}
/**
diff --git a/ipc/mqueue.c b/ipc/mqueue.c
index 6d7610310003..83d9466710d6 100644
--- a/ipc/mqueue.c
+++ b/ipc/mqueue.c
@@ -932,7 +932,7 @@ static int do_mq_open(const char __user *u_name, int oflag, umode_t mode,
put_unused_fd(fd);
fd = error;
}
- end_creating(path.dentry, root);
+ end_creating(path.dentry);
if (!ro)
mnt_drop_write(mnt);
out_putname:
]
|
| 36/2 |
2026/06/09 16:13 |
llm |
expert |
0m
Model:
Tokens:
input: 29802
output: 19
thoughts: 2898
Thoughts: **Analysis of potential double-unlock in `vfs_mkdir` and related functions**
Okay, let's break this down. I've been going through the commit, and it appears the goal is to refactor the error handling in the `mkdir` path to avoid potentially double unlocking the parent inode. My initial analysis focused on where `end_creating` and `end_dirop` are called and the scenarios they address. Specifically, I've looked at how errors from `vfs_mkdir`, `security_path_mkdir` and `filename_create` impact the flow.
Here's my current thinking:
1. **Error path in `vfs_mkdir`:** If `vfs_mkdir` itself encounters an error (including errors from deeper calls), it calls `end_creating(dentry)`, which correctly calls `inode_unlock` on the parent inode. This seems correct so far. The initial design was to address that case.
2. **Error from `vfs_mkdir` in `filename_mkdirat`:** `filename_mkdirat` then checks if an error occurred. If so, it updates dentry to the error value. This looks correct. It then calls `end_creating_path` with an error value. `end_creating_path` then calls `end_creating` which correctly short-circuits to nothing.
3. **Error from `security_path_mkdir`:** If `security_path_mkdir` fails, `vfs_mkdir` is *not* called. The original dentry from `filename_create` is then passed to `end_creating_path`. So, the parent inode is unlocked. That seems correct.
4. **Success path, either `vfs_mkdir` returns a valid dentry or no dentry:** If `vfs_mkdir` is successful and returns a new dentry, `end_creating_path` is called with that valid dentry. If it does not return a new dentry, `end_creating_path` is called with the original dentry. This will also call `inode_unlock` for the parent inode, which should be correct.
5. **Focus on potential double unlocks:** I went to look at possible double unlocks. The first place this could happen is inside `vfs_mkdir`. I realized the core problem could be that some file system's `i_op->mkdir` could *itself* unlock the parent inode on error. If that happened, we'd have a double unlock.
6. **Edge Case `mkdir` Returns NULL:** The return of `NULL` from `i_op->mkdir` is another edge case that requires consideration. If that happens, no other dentry would be used in error, so the original dentry would be passed to `end_creating_path`.
7. **Delegation Break:** If `try_break_deleg` returns an error, there is the potential to call `end_creating` and subsequently `inode_unlock`.
8. **Analysis of `vfs_mkdir` Logic**: The critical question is whether a specific filesystem's `mkdir` implementation might unlock the parent *within* its error handling. That's the real potential for a bug. No, `mkdir` is not supposed to unlock the parent inode.
So, to summarize my position: the provided change makes sense. But the risk of a double unlock really hinges on *how* filesystems implement the `mkdir` inode operation. I need to review some filesystem implementations to confirm that they don't incorrectly unlock the parent within the `mkdir` operation itself. I'll need to check the NFS, CIFS, and FUSE filesystems to be sure.
|
| 37/2 |
2026/06/09 16:13 |
tool |
grepper |
0m
Error:
Args: map[Expression:inode_unlock.*mkdir]
Results:
|
| 38/2 |
2026/06/09 16:13 |
llm |
expert |
0m
Model:
Tokens:
input: 33528
output: 18
thoughts: 21
Thoughts: Okay, let's dive into this. Here's how I'd approach that prompt, thinking as an expert:
**Investigating `vfs_mkdir` Callers**
Alright, I need to understand who's calling `vfs_mkdir`. My immediate instinct is to check `fs/namei.c`. It's a central file related to path name lookup and directory operations. Given that `mkdir` is fundamentally a directory operation, it's highly likely that the relevant calls will be orchestrated or mediated within the namei subsystem. Time to dig in and see what functions are making use of that. Let's see what we can find.
|
| 39/2 |
2026/06/09 16:13 |
tool |
grepper |
0m
Args: map[Expression:vfs_mkdir]
Results: map[Output:Documentation/filesystems/caching/cachefiles.rst=391=when it acts on behalf of another process, in that process's context.
Documentation/filesystems/caching/cachefiles.rst-392-
Documentation/filesystems/caching/cachefiles.rst:393:The reason it does this is that it calls vfs_mkdir() and suchlike rather than
Documentation/filesystems/caching/cachefiles.rst-394-bypassing security and calling inode ops directly. Therefore the VFS and LSM
--
Documentation/filesystems/idmappings.rst=343=VFS has already completed path lookup right before it calls into the filesystem
Documentation/filesystems/idmappings.rst:344:itself. So we're concerned with what happens when e.g. ``vfs_mkdir()`` is
Documentation/filesystems/idmappings.rst-345-called. We will also assume that the directory we're creating filesystem
--
Documentation/filesystems/porting.rst=1313=instead.
--
Documentation/filesystems/porting.rst-1318-
Documentation/filesystems/porting.rst:1319:vfs_mkdir() now returns a dentry - the one returned by ->mkdir(). If
Documentation/filesystems/porting.rst-1320-that dentry is different from the dentry passed in, including if it is
Documentation/filesystems/porting.rst=1321=an IS_ERR() dentry pointer, the original dentry is dput().
Documentation/filesystems/porting.rst-1322-
Documentation/filesystems/porting.rst:1323:When vfs_mkdir() returns an error, and so both dputs() the original
Documentation/filesystems/porting.rst-1324-dentry and doesn't provide a replacement, it also unlocks the parent.
Documentation/filesystems/porting.rst:1325:Consequently the return value from vfs_mkdir() can be passed to
Documentation/filesystems/porting.rst-1326-end_creating() and the parent will be unlocked precisely when necessary.
--
Documentation/security/credentials.rst=553=Under some circumstances it is desirable to override the credentials used by
Documentation/security/credentials.rst:554:the VFS, and that can be done by calling into such as ``vfs_mkdir()`` with a
Documentation/security/credentials.rst-555-different set of credentials. This is done in the following places:
--
Documentation/translations/zh_CN/security/credentials.rst=254=const指针上操作,因此不需要进行类型转换,但需要临时放弃const限定,以便能够修改
--
Documentation/translations/zh_CN/security/credentials.rst-474-在某些情况下,需要覆盖VFS使用的凭据,可以通过使用不同的凭据集调用
Documentation/translations/zh_CN/security/credentials.rst:475:如 ``vfs_mkdir()`` 来实现。以下是一些进行此操作的位置:
Documentation/translations/zh_CN/security/credentials.rst-476-
--
drivers/base/devtmpfs.c=173=static int dev_mkdir(const char *name, umode_t mode)
--
drivers/base/devtmpfs.c-181-
drivers/base/devtmpfs.c:182: dentry = vfs_mkdir(&nop_mnt_idmap, d_inode(path.dentry), dentry, mode, NULL);
drivers/base/devtmpfs.c-183- if (!IS_ERR(dentry))
--
fs/9p/vfs_inode.c=645=v9fs_vfs_create(struct mnt_idmap *idmap, struct inode *dir,
--
fs/9p/vfs_inode.c-663-/**
fs/9p/vfs_inode.c:664: * v9fs_vfs_mkdir - VFS mkdir hook to create a directory
fs/9p/vfs_inode.c-665- * @idmap: idmap of the mount
--
fs/9p/vfs_inode.c-671-
fs/9p/vfs_inode.c:672:static struct dentry *v9fs_vfs_mkdir(struct mnt_idmap *idmap, struct inode *dir,
fs/9p/vfs_inode.c-673- struct dentry *dentry, umode_t mode)
--
fs/9p/vfs_inode.c=1360=static const struct inode_operations v9fs_dir_inode_operations_dotu = {
--
fs/9p/vfs_inode.c-1366- .unlink = v9fs_vfs_unlink,
fs/9p/vfs_inode.c:1367: .mkdir = v9fs_vfs_mkdir,
fs/9p/vfs_inode.c-1368- .rmdir = v9fs_vfs_rmdir,
--
fs/9p/vfs_inode.c=1375=static const struct inode_operations v9fs_dir_inode_operations = {
--
fs/9p/vfs_inode.c-1379- .unlink = v9fs_vfs_unlink,
fs/9p/vfs_inode.c:1380: .mkdir = v9fs_vfs_mkdir,
fs/9p/vfs_inode.c-1381- .rmdir = v9fs_vfs_rmdir,
--
fs/9p/vfs_inode_dotl.c=227=v9fs_vfs_atomic_open_dotl(struct inode *dir, struct dentry *dentry,
--
fs/9p/vfs_inode_dotl.c-339-/**
fs/9p/vfs_inode_dotl.c:340: * v9fs_vfs_mkdir_dotl - VFS mkdir hook to create a directory
fs/9p/vfs_inode_dotl.c-341- * @idmap: The idmap of the mount
--
fs/9p/vfs_inode_dotl.c-347-
fs/9p/vfs_inode_dotl.c:348:static struct dentry *v9fs_vfs_mkdir_dotl(struct mnt_idmap *idmap,
fs/9p/vfs_inode_dotl.c-349- struct inode *dir, struct dentry *dentry,
--
fs/9p/vfs_inode_dotl.c=919=const struct inode_operations v9fs_dir_inode_operations_dotl = {
--
fs/9p/vfs_inode_dotl.c-925- .unlink = v9fs_vfs_unlink,
fs/9p/vfs_inode_dotl.c:926: .mkdir = v9fs_vfs_mkdir_dotl,
fs/9p/vfs_inode_dotl.c-927- .rmdir = v9fs_vfs_rmdir,
--
fs/btrfs/ioctl.c=704=static int create_snapshot(struct btrfs_root *root, struct inode *dir,
--
fs/btrfs/ioctl.c-825- * Create a new subvolume below @parent. This is largely modeled after
fs/btrfs/ioctl.c:826: * sys_mkdirat and vfs_mkdir, but we only do a single component lookup
fs/btrfs/ioctl.c-827- * inside this filesystem so it's quite a bit simpler.
--
fs/cachefiles/namei.c=84=struct dentry *cachefiles_get_directory(struct cachefiles_cache *cache,
--
fs/cachefiles/namei.c-131- if (ret == 0) {
fs/cachefiles/namei.c:132: subdir = vfs_mkdir(&nop_mnt_idmap, d_inode(dir), subdir, 0700, NULL);
fs/cachefiles/namei.c-133- if (IS_ERR(subdir))
--
fs/configfs/dir.c=650=static void detach_groups(struct config_group *group)
--
fs/configfs/dir.c-691- * We could, perhaps, tweak our parent's ->mkdir for a minute and
fs/configfs/dir.c:692: * try using vfs_mkdir. Just a thought.
fs/configfs/dir.c-693- */
--
fs/ecryptfs/inode.c=508=static struct dentry *ecryptfs_mkdir(struct mnt_idmap *idmap, struct inode *dir,
--
fs/ecryptfs/inode.c-520- lower_dir = lower_dir_dentry->d_inode;
fs/ecryptfs/inode.c:521: lower_dentry = vfs_mkdir(&nop_mnt_idmap, lower_dir,
fs/ecryptfs/inode.c-522- lower_dentry, mode, NULL);
--
fs/namei.c=4975=EXPORT_SYMBOL(start_creating_path);
--
fs/namei.c-4984- * if start_creating_path() returned a non-error.
fs/namei.c:4985: * If vfs_mkdir() was called and it returned an error, that error *should*
fs/namei.c-4986- * be passed to end_creating_path() together with the path.
--
fs/namei.c=5204=SYSCALL_DEFINE3(mknod, const char __user *, filename, umode_t, mode, unsigned, dev)
--
fs/namei.c-5210-/**
fs/namei.c:5211: * vfs_mkdir - create directory returning correct dentry if possible
fs/namei.c-5212- * @idmap: idmap of the mount the inode was found from
--
fs/namei.c-5231- */
fs/namei.c:5232:struct dentry *vfs_mkdir(struct mnt_idmap *idmap, struct inode *dir,
fs/namei.c-5233- struct dentry *dentry, umode_t mode,
--
fs/namei.c-5275-}
fs/namei.c:5276:EXPORT_SYMBOL(vfs_mkdir);
fs/namei.c-5277-
fs/namei.c=5278=int filename_mkdirat(int dfd, struct filename *name, umode_t mode)
--
fs/namei.c-5293- if (!error) {
fs/namei.c:5294: dentry = vfs_mkdir(mnt_idmap(path.mnt), path.dentry->d_inode,
fs/namei.c-5295- dentry, mode, &delegated_inode);
--
fs/nfsd/nfs4recover.c=123=nfsd4_create_clid_dir(struct nfs4_client *clp)
--
fs/nfsd/nfs4recover.c-162- goto out_end;
fs/nfsd/nfs4recover.c:163: dentry = vfs_mkdir(&nop_mnt_idmap, d_inode(dir), dentry, 0700, NULL);
fs/nfsd/nfs4recover.c-164- if (IS_ERR(dentry))
--
fs/nfsd/vfs.c=1780=nfsd_create_locked(struct svc_rqst *rqstp, struct svc_fh *fhp,
--
fs/nfsd/vfs.c-1813- case S_IFDIR:
fs/nfsd/vfs.c:1814: dchild = vfs_mkdir(&nop_mnt_idmap, dirp, dchild, iap->ia_mode, NULL);
fs/nfsd/vfs.c-1815- if (IS_ERR(dchild)) {
--
fs/overlayfs/overlayfs.h=250=static inline struct dentry *ovl_do_mkdir(struct ovl_fs *ofs,
--
fs/overlayfs/overlayfs.h-256-
fs/overlayfs/overlayfs.h:257: ret = vfs_mkdir(ovl_upper_mnt_idmap(ofs), dir, dentry, mode, NULL);
fs/overlayfs/overlayfs.h-258- pr_debug("mkdir(%pd2, 0%o) = %i\n", dentry, mode, PTR_ERR_OR_ZERO(ret));
--
fs/smb/server/smb2pdu.c=2611=static int smb2_creat(struct ksmbd_work *work,
--
fs/smb/server/smb2pdu.c-2627- mode = share_config_directory_mode(share, posix_mode);
fs/smb/server/smb2pdu.c:2628: rc = ksmbd_vfs_mkdir(work, name, mode);
fs/smb/server/smb2pdu.c-2629- if (rc)
--
fs/smb/server/vfs.c=151=int ksmbd_vfs_create(struct ksmbd_work *work, const char *name, umode_t mode)
--
fs/smb/server/vfs.c-180-/**
fs/smb/server/vfs.c:181: * ksmbd_vfs_mkdir() - vfs helper for smb create directory
fs/smb/server/vfs.c-182- * @work: work
--
fs/smb/server/vfs.c-187- */
fs/smb/server/vfs.c:188:int ksmbd_vfs_mkdir(struct ksmbd_work *work, const char *name, umode_t mode)
fs/smb/server/vfs.c-189-{
--
fs/smb/server/vfs.c-208- d = dentry;
fs/smb/server/vfs.c:209: dentry = vfs_mkdir(idmap, d_inode(path.dentry), dentry, mode, NULL);
fs/smb/server/vfs.c-210- if (IS_ERR(dentry))
--
fs/smb/server/vfs.h=78=int ksmbd_vfs_create(struct ksmbd_work *work, const char *name, umode_t mode);
fs/smb/server/vfs.h:79:int ksmbd_vfs_mkdir(struct ksmbd_work *work, const char *name, umode_t mode);
fs/smb/server/vfs.h-80-int ksmbd_vfs_read(struct ksmbd_work *work, struct ksmbd_file *fp, size_t count,
--
fs/xfs/scrub/orphanage.c=122=xrep_orphanage_create(
--
fs/xfs/scrub/orphanage.c-167- if (d_really_is_negative(orphanage_dentry)) {
fs/xfs/scrub/orphanage.c:168: orphanage_dentry = vfs_mkdir(&nop_mnt_idmap, root_inode,
fs/xfs/scrub/orphanage.c-169- orphanage_dentry, 0750, NULL);
--
include/linux/fs.h=1769=int vfs_create(struct mnt_idmap *, struct dentry *, umode_t,
include/linux/fs.h-1770- struct delegated_inode *);
include/linux/fs.h:1771:struct dentry *vfs_mkdir(struct mnt_idmap *, struct inode *,
include/linux/fs.h-1772- struct dentry *, umode_t, struct delegated_inode *);
--
include/linux/namei.h=103=struct dentry *start_removing_dentry(struct dentry *parent,
--
include/linux/namei.h-106-/* end_creating - finish action started with start_creating
include/linux/namei.h:107: * @child: dentry returned by start_creating() or vfs_mkdir()
include/linux/namei.h-108- *
--
include/linux/namei.h-112- *
include/linux/namei.h:113: * If vfs_mkdir() was called then the value returned from that function
include/linux/namei.h:114: * should be given for @child rather than the original dentry, as vfs_mkdir()
include/linux/namei.h-115- * may have provided a new dentry.
--
include/linux/namei.h-117- *
include/linux/namei.h:118: * If vfs_mkdir() was not called, then @child will be a valid dentry and
include/linux/namei.h-119- * @parent will be ignored.
--
include/linux/namei.h=121=static inline void end_creating(struct dentry *child)
--
include/linux/namei.h-126-/* end_creating_keep - finish action started with start_creating() and return result
include/linux/namei.h:127: * @child: dentry returned by start_creating() or vfs_mkdir()
include/linux/namei.h-128- *
--
include/linux/namei.h-132- *
include/linux/namei.h:133: * If vfs_mkdir() was called then the value returned from that function
include/linux/namei.h:134: * should be given for @child rather than the original dentry, as vfs_mkdir()
include/linux/namei.h-135- * may have provided a new dentry.
]
|
| 40/2 |
2026/06/09 16:13 |
llm |
expert |
0m
Model:
Tokens:
input: 38247
output: 22
thoughts: 3519
Thoughts: **Analyzing the VFS mkdir Implementation and Potential Race Conditions**
Okay, so I'm diving into this `vfs_mkdir` code, and my primary concern revolves around the dentry handling and potential race conditions, given the locking strategy. The basic flow seems clear: `vfs_mkdir` either returns the original dentry (after a possible error) or a newly created dentry. I'm focusing on the interaction between `vfs_mkdir`, `filename_mkdirat`, and those crucial `end_creating_path` calls. The core issue, it seems, is ensuring proper inode unlocking. I'm worried about scenarios where the parent inode gets unlocked prematurely or incorrectly.
Specifically, I'm thinking: if `dir->i_op->mkdir` returns a valid dentry but it is the SAME dentry. It looks correct as the original `dentry` is returned. If it is a DIFFERENT valid dentry, is it correct that the new dentry's parent is the original? What if the returned dentry is a root dentry? Will `inode_unlock` then unlock the wrong inode.
Okay, let's step through the logic. I can see the various error paths, but I'm trying to see if there are problems where a parent inode could be unlocked.
My concern is if we call `end_creating` after returning an error, and the parent inode is incorrectly unlocked. If the parent inode is unlocked. If `dir->i_op->mkdir` returns `ERR_PTR(error)` AND it *also* unlocks the parent inode itself - which it is not supposed to.
Let's trace: What happens if `try_break_deleg` returns an error? It calls `end_creating` which unlocks the parent. That's fine because `vfs_mkdir` returns an error. But `end_creating_path` should do nothing since `end_dirop` is skipped.
But let's look at `filename_mkdirat` again, what if there's a problem with delegation? What if the filesystem does not support delegation? It calls `end_creating_path` and drops a write lock, that's normal.
And what about that `security_path_mkdir` call? If it fails, `vfs_mkdir` *isn't* called, but `end_creating_path` *is*, operating on the *original* dentry. Okay, so that should be correct too. The parent is unlocked. That seems logical.
I'm starting to get worried: What if `vfs_mkdir` returns an error, but the caller doesn't properly update `dentry`? No, the code explicitly updates it.
Okay, back to the core. A different valid dentry `de` is returned, what happens when it is not the original dentry? It is passed to `end_creating_path`. The code looks correct, but it is calling `inode_unlock` on `de->d_parent->d_inode`. If `de->d_parent` is the original dentry's parent, that seems fine, right?
But *what if* `de` is a *new* dentry with a different parent? Is the parent set correctly? Yes, it *should* be, via `d_splice_alias`. But can we *guarantee* it? And if we can't guarantee it, what is the impact?
The case where the new dentry `de` is a *root dentry* is bad because `de->d_parent == de`. So `inode_unlock(de->d_inode)` would be called and this could cause an issue and a race condition. This could unlock the parent when it shouldn't be unlocked. And the parent inode, which is `dir`, remains locked.
Hmm, this `vfs_mkdir` function itself, if `dir->i_op->mkdir` returns `de`, `vfs_mkdir` returns `de`. The caller calls `end_creating` and that calls `end_dirop` and then `inode_unlock`. My concern is still about that `de->d_parent->d_inode == dir` being *guaranteed*. It *should* be, if it's a new dentry. But is it? Is it possible for `mkdir` to return a disconnected dentry? The code makes it sound like we are returning a newly created dentry.
I am re-reading the bug report. The warning is in `up_write`, so `inode_unlock` is being called with the *correct* inode. So we are good. Let's look at `filename_mkdirat` again. The critical part is the valid dentry, and it must have the correct parent. When `security_path_mkdir` returns an
|
| 41/2 |
2026/06/09 16:14 |
tool |
grepper |
0m
Args: map[Expression:\.mkdir\s*=\s*]
Results: map[Output:fs/9p/vfs_inode.c=1360=static const struct inode_operations v9fs_dir_inode_operations_dotu = {
--
fs/9p/vfs_inode.c-1366- .unlink = v9fs_vfs_unlink,
fs/9p/vfs_inode.c:1367: .mkdir = v9fs_vfs_mkdir,
fs/9p/vfs_inode.c-1368- .rmdir = v9fs_vfs_rmdir,
--
fs/9p/vfs_inode.c=1375=static const struct inode_operations v9fs_dir_inode_operations = {
--
fs/9p/vfs_inode.c-1379- .unlink = v9fs_vfs_unlink,
fs/9p/vfs_inode.c:1380: .mkdir = v9fs_vfs_mkdir,
fs/9p/vfs_inode.c-1381- .rmdir = v9fs_vfs_rmdir,
--
fs/9p/vfs_inode_dotl.c=919=const struct inode_operations v9fs_dir_inode_operations_dotl = {
--
fs/9p/vfs_inode_dotl.c-925- .unlink = v9fs_vfs_unlink,
fs/9p/vfs_inode_dotl.c:926: .mkdir = v9fs_vfs_mkdir_dotl,
fs/9p/vfs_inode_dotl.c-927- .rmdir = v9fs_vfs_rmdir,
--
fs/affs/dir.c=65=const struct inode_operations affs_dir_inode_operations = {
--
fs/affs/dir.c-70- .symlink = affs_symlink,
fs/affs/dir.c:71: .mkdir = affs_mkdir,
fs/affs/dir.c-72- .rmdir = affs_rmdir,
--
fs/afs/dir.c=58=const struct inode_operations afs_dir_inode_operations = {
--
fs/afs/dir.c-63- .symlink = afs_symlink,
fs/afs/dir.c:64: .mkdir = afs_mkdir,
fs/afs/dir.c-65- .rmdir = afs_rmdir,
--
fs/autofs/root.c=53=const struct inode_operations autofs_dir_inode_operations = {
--
fs/autofs/root.c-57- .symlink = autofs_dir_symlink,
fs/autofs/root.c:58: .mkdir = autofs_dir_mkdir,
fs/autofs/root.c-59- .rmdir = autofs_dir_rmdir,
--
fs/bad_inode.c=163=static const struct inode_operations bad_inode_ops =
--
fs/bad_inode.c-169- .symlink = bad_inode_symlink,
fs/bad_inode.c:170: .mkdir = bad_inode_mkdir,
fs/bad_inode.c-171- .rmdir = bad_inode_rmdir,
--
fs/btrfs/inode.c=10711=static const struct inode_operations btrfs_dir_inode_operations = {
--
fs/btrfs/inode.c-10716- .link = btrfs_link,
fs/btrfs/inode.c:10717: .mkdir = btrfs_mkdir,
fs/btrfs/inode.c-10718- .rmdir = btrfs_rmdir,
--
fs/ceph/dir.c=2241=const struct inode_operations ceph_dir_iops = {
--
fs/ceph/dir.c-2250- .symlink = ceph_symlink,
fs/ceph/dir.c:2251: .mkdir = ceph_mkdir,
fs/ceph/dir.c-2252- .link = ceph_link,
--
fs/ceph/dir.c=2260=const struct inode_operations ceph_snapdir_iops = {
--
fs/ceph/dir.c-2263- .getattr = ceph_getattr,
fs/ceph/dir.c:2264: .mkdir = ceph_mkdir,
fs/ceph/dir.c-2265- .rmdir = ceph_unlink,
--
fs/coda/dir.c=550=const struct inode_operations coda_dir_inode_operations = {
--
fs/coda/dir.c-555- .symlink = coda_symlink,
fs/coda/dir.c:556: .mkdir = coda_mkdir,
fs/coda/dir.c-557- .rmdir = coda_rmdir,
--
fs/configfs/dir.c=1582=const struct inode_operations configfs_dir_inode_operations = {
fs/configfs/dir.c:1583: .mkdir = configfs_mkdir,
fs/configfs/dir.c-1584- .rmdir = configfs_rmdir,
--
fs/ecryptfs/inode.c=1125=const struct inode_operations ecryptfs_dir_iops = {
--
fs/ecryptfs/inode.c-1130- .symlink = ecryptfs_symlink,
fs/ecryptfs/inode.c:1131: .mkdir = ecryptfs_mkdir,
fs/ecryptfs/inode.c-1132- .rmdir = ecryptfs_rmdir,
--
fs/exfat/namei.c=1305=const struct inode_operations exfat_dir_inode_operations = {
--
fs/exfat/namei.c-1308- .unlink = exfat_unlink,
fs/exfat/namei.c:1309: .mkdir = exfat_mkdir,
fs/exfat/namei.c-1310- .rmdir = exfat_rmdir,
--
fs/ext2/namei.c=408=const struct inode_operations ext2_dir_inode_operations = {
--
fs/ext2/namei.c-413- .symlink = ext2_symlink,
fs/ext2/namei.c:414: .mkdir = ext2_mkdir,
fs/ext2/namei.c-415- .rmdir = ext2_rmdir,
--
fs/ext4/namei.c=4222=const struct inode_operations ext4_dir_inode_operations = {
--
fs/ext4/namei.c-4227- .symlink = ext4_symlink,
fs/ext4/namei.c:4228: .mkdir = ext4_mkdir,
fs/ext4/namei.c-4229- .rmdir = ext4_rmdir,
--
fs/f2fs/namei.c=1368=const struct inode_operations f2fs_dir_inode_operations = {
--
fs/f2fs/namei.c-1373- .symlink = f2fs_symlink,
fs/f2fs/namei.c:1374: .mkdir = f2fs_mkdir,
fs/f2fs/namei.c-1375- .rmdir = f2fs_rmdir,
--
fs/fat/namei_msdos.c=638=static const struct inode_operations msdos_dir_inode_operations = {
--
fs/fat/namei_msdos.c-641- .unlink = msdos_unlink,
fs/fat/namei_msdos.c:642: .mkdir = msdos_mkdir,
fs/fat/namei_msdos.c-643- .rmdir = msdos_rmdir,
--
fs/fat/namei_vfat.c=1179=static const struct inode_operations vfat_dir_inode_operations = {
--
fs/fat/namei_vfat.c-1182- .unlink = vfat_unlink,
fs/fat/namei_vfat.c:1183: .mkdir = vfat_mkdir,
fs/fat/namei_vfat.c-1184- .rmdir = vfat_rmdir,
--
fs/fuse/dir.c=2404=static const struct inode_operations fuse_dir_inode_operations = {
fs/fuse/dir.c-2405- .lookup = fuse_lookup,
fs/fuse/dir.c:2406: .mkdir = fuse_mkdir,
fs/fuse/dir.c-2407- .symlink = fuse_symlink,
--
fs/gfs2/inode.c=2305=static const struct inode_operations gfs2_dir_iops = {
--
fs/gfs2/inode.c-2310- .symlink = gfs2_symlink,
fs/gfs2/inode.c:2311: .mkdir = gfs2_mkdir,
fs/gfs2/inode.c-2312- .rmdir = gfs2_unlink,
--
fs/hfs/dir.c=323=const struct inode_operations hfs_dir_inode_operations = {
--
fs/hfs/dir.c-326- .unlink = hfs_remove,
fs/hfs/dir.c:327: .mkdir = hfs_mkdir,
fs/hfs/dir.c-328- .rmdir = hfs_remove,
--
fs/hfsplus/dir.c=626=const struct inode_operations hfsplus_dir_inode_operations = {
--
fs/hfsplus/dir.c-630- .unlink = hfsplus_unlink,
fs/hfsplus/dir.c:631: .mkdir = hfsplus_mkdir,
fs/hfsplus/dir.c-632- .rmdir = hfsplus_rmdir,
--
fs/hostfs/hostfs_kern.c=881=static const struct inode_operations hostfs_dir_iops = {
--
fs/hostfs/hostfs_kern.c-886- .symlink = hostfs_symlink,
fs/hostfs/hostfs_kern.c:887: .mkdir = hostfs_mkdir,
fs/hostfs/hostfs_kern.c-888- .rmdir = hostfs_rmdir,
--
fs/hpfs/namei.c=610=const struct inode_operations hpfs_dir_iops =
--
fs/hpfs/namei.c-615- .symlink = hpfs_symlink,
fs/hpfs/namei.c:616: .mkdir = hpfs_mkdir,
fs/hpfs/namei.c-617- .rmdir = hpfs_rmdir,
--
fs/hugetlbfs/inode.c=1218=static const struct inode_operations hugetlbfs_dir_inode_operations = {
--
fs/hugetlbfs/inode.c-1223- .symlink = hugetlbfs_symlink,
fs/hugetlbfs/inode.c:1224: .mkdir = hugetlbfs_mkdir,
fs/hugetlbfs/inode.c-1225- .rmdir = simple_rmdir,
--
fs/jffs2/dir.c=56=const struct inode_operations jffs2_dir_inode_operations =
--
fs/jffs2/dir.c-62- .symlink = jffs2_symlink,
fs/jffs2/dir.c:63: .mkdir = jffs2_mkdir,
fs/jffs2/dir.c-64- .rmdir = jffs2_rmdir,
--
fs/jfs/namei.c=1523=const struct inode_operations jfs_dir_inode_operations = {
--
fs/jfs/namei.c-1528- .symlink = jfs_symlink,
fs/jfs/namei.c:1529: .mkdir = jfs_mkdir,
fs/jfs/namei.c-1530- .rmdir = jfs_rmdir,
--
fs/kernfs/dir.c=1361=const struct inode_operations kernfs_dir_iops = {
--
fs/kernfs/dir.c-1367-
fs/kernfs/dir.c:1368: .mkdir = kernfs_iop_mkdir,
fs/kernfs/dir.c-1369- .rmdir = kernfs_iop_rmdir,
--
fs/minix/namei.c=279=const struct inode_operations minix_dir_inode_operations = {
--
fs/minix/namei.c-284- .symlink = minix_symlink,
fs/minix/namei.c:285: .mkdir = minix_mkdir,
fs/minix/namei.c-286- .rmdir = minix_rmdir,
--
fs/nfs/nfs3proc.c=1042=static const struct inode_operations nfs3_dir_inode_operations = {
--
fs/nfs/nfs3proc.c-1048- .symlink = nfs_symlink,
fs/nfs/nfs3proc.c:1049: .mkdir = nfs_mkdir,
fs/nfs/nfs3proc.c-1050- .rmdir = nfs_rmdir,
--
fs/nfs/nfs3proc.c=1074=const struct nfs_rpc_ops nfs_v3_clientops = {
--
fs/nfs/nfs3proc.c-1099- .symlink = nfs3_proc_symlink,
fs/nfs/nfs3proc.c:1100: .mkdir = nfs3_proc_mkdir,
fs/nfs/nfs3proc.c-1101- .rmdir = nfs3_proc_rmdir,
--
fs/nfs/nfs4proc.c=10605=static const struct inode_operations nfs4_dir_inode_operations = {
--
fs/nfs/nfs4proc.c-10611- .symlink = nfs_symlink,
fs/nfs/nfs4proc.c:10612: .mkdir = nfs_mkdir,
fs/nfs/nfs4proc.c-10613- .rmdir = nfs_rmdir,
--
fs/nfs/nfs4proc.c=10652=const struct nfs_rpc_ops nfs_v4_clientops = {
--
fs/nfs/nfs4proc.c-10676- .symlink = nfs4_proc_symlink,
fs/nfs/nfs4proc.c:10677: .mkdir = nfs4_proc_mkdir,
fs/nfs/nfs4proc.c-10678- .rmdir = nfs4_proc_rmdir,
--
fs/nfs/proc.c=707=static const struct inode_operations nfs_dir_inode_operations = {
--
fs/nfs/proc.c-713- .symlink = nfs_symlink,
fs/nfs/proc.c:714: .mkdir = nfs_mkdir,
fs/nfs/proc.c-715- .rmdir = nfs_rmdir,
--
fs/nfs/proc.c=729=const struct nfs_rpc_ops nfs_v2_clientops = {
--
fs/nfs/proc.c-752- .symlink = nfs_proc_symlink,
fs/nfs/proc.c:753: .mkdir = nfs_proc_mkdir,
fs/nfs/proc.c-754- .rmdir = nfs_proc_rmdir,
--
fs/nilfs2/namei.c=562=const struct inode_operations nilfs_dir_inode_operations = {
--
fs/nilfs2/namei.c-567- .symlink = nilfs_symlink,
fs/nilfs2/namei.c:568: .mkdir = nilfs_mkdir,
fs/nilfs2/namei.c-569- .rmdir = nilfs_rmdir,
--
fs/ntfs/namei.c=1573=const struct inode_operations ntfs_dir_inode_ops = {
--
fs/ntfs/namei.c-1576- .unlink = ntfs_unlink,
fs/ntfs/namei.c:1577: .mkdir = ntfs_mkdir,
fs/ntfs/namei.c-1578- .rmdir = ntfs_rmdir,
--
fs/ntfs3/namei.c=505=const struct inode_operations ntfs_dir_inode_operations = {
--
fs/ntfs3/namei.c-510- .symlink = ntfs_symlink,
fs/ntfs3/namei.c:511: .mkdir = ntfs_mkdir,
fs/ntfs3/namei.c-512- .rmdir = ntfs_rmdir,
--
fs/ocfs2/dlmfs/dlmfs.c=537=static const struct inode_operations dlmfs_root_inode_operations = {
fs/ocfs2/dlmfs/dlmfs.c-538- .lookup = simple_lookup,
fs/ocfs2/dlmfs/dlmfs.c:539: .mkdir = dlmfs_mkdir,
fs/ocfs2/dlmfs/dlmfs.c-540- .rmdir = simple_rmdir,
--
fs/ocfs2/namei.c=2926=const struct inode_operations ocfs2_dir_iops = {
--
fs/ocfs2/namei.c-2932- .symlink = ocfs2_symlink,
fs/ocfs2/namei.c:2933: .mkdir = ocfs2_mkdir,
fs/ocfs2/namei.c-2934- .mknod = ocfs2_mknod,
--
fs/omfs/dir.c=448=const struct inode_operations omfs_dir_inops = {
fs/omfs/dir.c-449- .lookup = omfs_lookup,
fs/omfs/dir.c:450: .mkdir = omfs_mkdir,
fs/omfs/dir.c-451- .rename = omfs_rename,
--
fs/orangefs/namei.c=417=const struct inode_operations orangefs_dir_inode_operations = {
--
fs/orangefs/namei.c-423- .symlink = orangefs_symlink,
fs/orangefs/namei.c:424: .mkdir = orangefs_mkdir,
fs/orangefs/namei.c-425- .rmdir = orangefs_unlink,
--
fs/overlayfs/dir.c=1475=const struct inode_operations ovl_dir_inode_operations = {
fs/overlayfs/dir.c-1476- .lookup = ovl_lookup,
fs/overlayfs/dir.c:1477: .mkdir = ovl_mkdir,
fs/overlayfs/dir.c-1478- .symlink = ovl_symlink,
--
fs/ramfs/inode.c=189=static const struct inode_operations ramfs_dir_inode_operations = {
--
fs/ramfs/inode.c-194- .symlink = ramfs_symlink,
fs/ramfs/inode.c:195: .mkdir = ramfs_mkdir,
fs/ramfs/inode.c-196- .rmdir = simple_rmdir,
--
fs/resctrl/rdtgroup.c=4249=static struct kernfs_syscall_ops rdtgroup_kf_syscall_ops = {
fs/resctrl/rdtgroup.c:4250: .mkdir = rdtgroup_mkdir,
fs/resctrl/rdtgroup.c-4251- .rmdir = rdtgroup_rmdir,
--
fs/smb/client/cifsfs.c=1168=const struct inode_operations cifs_dir_inode_ops = {
--
fs/smb/client/cifsfs.c-1175- .link = cifs_hardlink,
fs/smb/client/cifsfs.c:1176: .mkdir = cifs_mkdir,
fs/smb/client/cifsfs.c-1177- .rmdir = cifs_rmdir,
--
fs/smb/client/smb1ops.c=1386=struct smb_version_operations smb1_operations = {
--
fs/smb/client/smb1ops.c-1429- .echo = CIFSSMBEcho,
fs/smb/client/smb1ops.c:1430: .mkdir = CIFSSMBMkDir,
fs/smb/client/smb1ops.c-1431- .mkdir_setinfo = cifs_mkdir_setinfo,
--
fs/smb/client/smb2ops.c=5402=struct smb_version_operations smb20_operations = {
--
fs/smb/client/smb2ops.c-5444- .set_compression = smb2_set_compression,
fs/smb/client/smb2ops.c:5445: .mkdir = smb2_mkdir,
fs/smb/client/smb2ops.c-5446- .mkdir_setinfo = smb2_mkdir_setinfo,
--
fs/smb/client/smb2ops.c=5504=struct smb_version_operations smb21_operations = {
--
fs/smb/client/smb2ops.c-5547- .set_compression = smb2_set_compression,
fs/smb/client/smb2ops.c:5548: .mkdir = smb2_mkdir,
fs/smb/client/smb2ops.c-5549- .mkdir_setinfo = smb2_mkdir_setinfo,
--
fs/smb/client/smb2ops.c=5608=struct smb_version_operations smb30_operations = {
--
fs/smb/client/smb2ops.c-5654- .set_compression = smb2_set_compression,
fs/smb/client/smb2ops.c:5655: .mkdir = smb2_mkdir,
fs/smb/client/smb2ops.c-5656- .mkdir_setinfo = smb2_mkdir_setinfo,
--
fs/smb/client/smb2ops.c=5724=struct smb_version_operations smb311_operations = {
--
fs/smb/client/smb2ops.c-5769- .set_compression = smb2_set_compression,
fs/smb/client/smb2ops.c:5770: .mkdir = smb2_mkdir,
fs/smb/client/smb2ops.c-5771- .mkdir_setinfo = smb2_mkdir_setinfo,
--
fs/tracefs/inode.c=226=static const struct inode_operations tracefs_instance_dir_inode_operations = {
fs/tracefs/inode.c-227- .lookup = simple_lookup,
fs/tracefs/inode.c:228: .mkdir = tracefs_syscall_mkdir,
fs/tracefs/inode.c-229- .rmdir = tracefs_syscall_rmdir,
--
fs/tracefs/inode.c=724=__init struct dentry *tracefs_create_instance_dir(const char *name,
--
fs/tracefs/inode.c-738-
fs/tracefs/inode.c:739: tracefs_ops.mkdir = mkdir;
fs/tracefs/inode.c-740- tracefs_ops.rmdir = rmdir;
--
fs/ubifs/dir.c=1742=const struct inode_operations ubifs_dir_inode_operations = {
--
fs/ubifs/dir.c-1747- .unlink = ubifs_unlink,
fs/ubifs/dir.c:1748: .mkdir = ubifs_mkdir,
fs/ubifs/dir.c-1749- .rmdir = ubifs_rmdir,
--
fs/udf/namei.c=1017=const struct inode_operations udf_dir_inode_operations = {
--
fs/udf/namei.c-1022- .symlink = udf_symlink,
fs/udf/namei.c:1023: .mkdir = udf_mkdir,
fs/udf/namei.c-1024- .rmdir = udf_rmdir,
--
fs/ufs/namei.c=320=const struct inode_operations ufs_dir_inode_operations = {
--
fs/ufs/namei.c-325- .symlink = ufs_symlink,
fs/ufs/namei.c:326: .mkdir = ufs_mkdir,
fs/ufs/namei.c-327- .rmdir = ufs_rmdir,
--
fs/vboxsf/dir.c=469=const struct inode_operations vboxsf_dir_iops = {
--
fs/vboxsf/dir.c-471- .create = vboxsf_dir_mkfile,
fs/vboxsf/dir.c:472: .mkdir = vboxsf_dir_mkdir,
fs/vboxsf/dir.c-473- .atomic_open = vboxsf_dir_atomic_open,
--
fs/xfs/xfs_iops.c=1277=static const struct inode_operations xfs_dir_inode_operations = {
--
fs/xfs/xfs_iops.c-1282- .symlink = xfs_vn_symlink,
fs/xfs/xfs_iops.c:1283: .mkdir = xfs_vn_mkdir,
fs/xfs/xfs_iops.c-1284- /*
--
fs/xfs/xfs_iops.c=1305=static const struct inode_operations xfs_dir_ci_inode_operations = {
--
fs/xfs/xfs_iops.c-1310- .symlink = xfs_vn_symlink,
fs/xfs/xfs_iops.c:1311: .mkdir = xfs_vn_mkdir,
fs/xfs/xfs_iops.c-1312- /*
--
kernel/bpf/inode.c=404=static const struct inode_operations bpf_dir_iops = {
kernel/bpf/inode.c-405- .lookup = bpf_lookup,
kernel/bpf/inode.c:406: .mkdir = bpf_mkdir,
kernel/bpf/inode.c-407- .symlink = bpf_symlink,
--
kernel/cgroup/cgroup-v1.c=1148=struct kernfs_syscall_ops cgroup1_kf_syscall_ops = {
--
kernel/cgroup/cgroup-v1.c-1150- .show_options = cgroup1_show_options,
kernel/cgroup/cgroup-v1.c:1151: .mkdir = cgroup_mkdir,
kernel/cgroup/cgroup-v1.c-1152- .rmdir = cgroup_rmdir,
--
kernel/cgroup/cgroup.c=6271=static struct kernfs_syscall_ops cgroup_kf_syscall_ops = {
kernel/cgroup/cgroup.c-6272- .show_options = cgroup_show_options,
kernel/cgroup/cgroup.c:6273: .mkdir = cgroup_mkdir,
kernel/cgroup/cgroup.c-6274- .rmdir = cgroup_rmdir,
--
mm/shmem.c=5244=static const struct inode_operations shmem_dir_inode_operations = {
--
mm/shmem.c-5251- .symlink = shmem_symlink,
mm/shmem.c:5252: .mkdir = shmem_mkdir,
mm/shmem.c-5253- .rmdir = shmem_rmdir,
--
security/apparmor/apparmorfs.c=1971=static const struct inode_operations ns_dir_inode_operations = {
security/apparmor/apparmorfs.c-1972- .lookup = simple_lookup,
security/apparmor/apparmorfs.c:1973: .mkdir = ns_mkdir_op,
security/apparmor/apparmorfs.c-1974- .rmdir = ns_rmdir_op,
]
|
| 42/2 |
2026/06/09 16:14 |
llm |
expert |
0m
Model:
Tokens:
input: 50478
output: 34
thoughts: 27
Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience:
**Dentry Return from vfs_mkdir: Implications & Next Steps**
Okay, so `vfs_mkdir` has been modified to return a dentry now. That's a significant change, likely aimed at streamlining some internal operations or potentially enabling optimizations in the VFS layer. I need to understand the implications of this. Let me pivot to the `shmem_mkdir` implementation. I need to verify how it interacts with this core VFS function, to ensure proper interaction with this change. Specifically I need to ensure that the memory file system still behaves correctly, handling the dentry appropriately. I'm wondering if any of the assumptions it makes about the return value have become invalid or require adaptation. Any subtle deviations from the expected `vfs_mkdir` behavior could introduce file system corruption, and so it needs careful attention.
|
| 43/2 |
2026/06/09 16:14 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:mm/shmem.c Name:shmem_mkdir]
Results: map[SourceCode:3918: static struct dentry *shmem_mkdir(struct mnt_idmap *idmap, struct inode *dir,
3919: struct dentry *dentry, umode_t mode)
3920: {
3921: int error;
3922:
3923: error = shmem_mknod(idmap, dir, dentry, mode | S_IFDIR, 0);
3924: if (error)
3925: return ERR_PTR(error);
3926: inc_nlink(dir);
3927: return NULL;
3928: }
SourceFile:mm/shmem.c]
|
| 44/2 |
2026/06/09 16:14 |
llm |
expert |
0m
Model:
Tokens:
input: 50927
output: 38
thoughts: 17
Thoughts: Okay, here's my interpretation of those thoughts, crafted with the understanding that I'm talking to a fellow expert:
**Investigating a Null Return**
Hmm, so the function is returning `NULL`. That's not ideal. It means something went wrong, but we don't know what yet. My immediate instinct is to dig into the `btrfs_mkdir` function itself. That's the logical starting point. I need to understand its control flow and error handling to pinpoint the cause of this `NULL` return. The problem is definitely within that function. I am confident that digging into its implementation will illuminate the route to resolution. Let's see what's going on in there.
|
| 45/2 |
2026/06/09 16:14 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/btrfs/inode.c Name:btrfs_mkdir]
Results: map[SourceCode:7115: static struct dentry *btrfs_mkdir(struct mnt_idmap *idmap, struct inode *dir,
7116: struct dentry *dentry, umode_t mode)
7117: {
7118: struct inode *inode;
7119:
7120: inode = new_inode(dir->i_sb);
7121: if (!inode)
7122: return ERR_PTR(-ENOMEM);
7123: inode_init_owner(idmap, inode, dir, S_IFDIR | mode);
7124: inode->i_op = &btrfs_dir_inode_operations;
7125: inode->i_fop = &btrfs_dir_file_operations;
7126: return ERR_PTR(btrfs_create_common(dir, dentry, inode));
7127: }
SourceFile:fs/btrfs/inode.c]
|
| 46/2 |
2026/06/09 16:14 |
llm |
expert |
0m
Model:
Tokens:
input: 51400
output: 38
thoughts: 97
Thoughts: **Debugging a Btrfs Filesystem Call**
Okay, let me trace this `btrfs_mkdir` call... I see it immediately returns `ERR_PTR(btrfs_create_common(dir, dentry, inode))`. Hmm, that suggests an error propagation, which is typical. But if `btrfs_create_common` returns a zero, which would indicate success... wouldn't that result in `ERR_PTR(0)` being returned? And... wait a second... yes, `ERR_PTR(0)` *is* `NULL` in the kernel. So, a successful `btrfs_create_common` call would actually translate to a `NULL` return from `btrfs_mkdir` according to this implementation.
Now, let's compare this with `kernfs_iop_mkdir`... I need to see how they handle success codes. This could explain a subtle bug or a potential discrepancy in behavior.
|
| 47/2 |
2026/06/09 16:14 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/kernfs/dir.c Name:kernfs_iop_mkdir]
Results: map[SourceCode:1292: static struct dentry *kernfs_iop_mkdir(struct mnt_idmap *idmap,
1293: struct inode *dir, struct dentry *dentry,
1294: umode_t mode)
1295: {
1296: struct kernfs_node *parent = dir->i_private;
1297: struct kernfs_syscall_ops *scops = kernfs_root(parent)->syscall_ops;
1298: int ret;
1299:
1300: if (!scops || !scops->mkdir)
1301: return ERR_PTR(-EPERM);
1302:
1303: if (!kernfs_get_active(parent))
1304: return ERR_PTR(-ENODEV);
1305:
1306: ret = scops->mkdir(parent, dentry->d_name.name, mode);
1307:
1308: kernfs_put_active(parent);
1309: return ERR_PTR(ret);
1310: }
SourceFile:fs/kernfs/dir.c]
|
| 48/2 |
2026/06/09 16:14 |
llm |
expert |
0m
Model:
Tokens:
input: 52061
output: 36
thoughts: 2687
Thoughts: **Analysis of Inode Locking and Unlocking in `mkdir` Implementation**
Okay, so I've been tracing the execution flow of `mkdir` calls, trying to ensure that inode locks are handled correctly. It seems straightforward at first: if `vfs_mkdir` returns an error, it should unlock the parent inode. However, the details are getting complex, and I need to be careful.
Initially, I looked at the error paths directly. If `vfs_mkdir` encounters an error, it calls `end_creating` which then unlocks the parent. That seemed safe. But then I started to consider potential race conditions and corner cases.
The `security_path_mkdir` function's return also triggers the correct unlock sequence if it fails. That’s good. It ensures consistency.
I got concerned about the case where `vfs_mkdir` returns an error after the security checks, but `filename_mkdirat` doesn't pick it up immediately. But, that is handled.
The `try_break_deleg` function introduced another point of complexity. But the subsequent `retry` logic in `filename_mkdirat` addresses this.
The real headache comes when `vfs_mkdir` returns an error, but the `end_creating_path` and retry logic are involved. I traced the `mnt_drop_write` and `path_put` calls in `end_creating_path` correctly and the subsequent retry.
Then I focused on `vfs_mkdir`'s specific error handling. My concerns escalated. The crucial point is that if the filesystem's `mkdir` operation returns a valid dentry, it calls `dput` on the original dentry, which means it unlocked the parent inode, and the returned dentry becomes the new, valid one. BUT, the real question is: Can the filesystem return a dentry with a different parent inode than the original directory? If so, the parent inode is NOT locked, but the `end_dirop` logic is still trying to unlock the original dir, not the actual parent of the created entry.
I found my issue with the `dentry` and it’s parent. If the filesystem's `mkdir` operation returns a new dentry, but with a different parent inode than the original dir, then the `inode_unlock` call would unlock the returned entry's inode and leave the dir’s inode locked. This causes a bug and warning. This would lead to a locking inconsistency which in turn would create problems. I confirmed the cause by checking the warning message and that it matches the theory.
|
| 49/2 |
2026/06/09 16:15 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/nfs/dir.c Name:nfs_mkdir]
Results: map[SourceCode:2462: /*
2463: * See comments for nfs_proc_create regarding failed operations.
2464: */
2465: struct dentry *nfs_mkdir(struct mnt_idmap *idmap, struct inode *dir,
2466: struct dentry *dentry, umode_t mode)
2467: {
2468: struct iattr attr;
2469: struct dentry *ret;
2470:
2471: dfprintk(VFS, "NFS: mkdir(%s/%llu), %pd\n",
2472: dir->i_sb->s_id, dir->i_ino, dentry);
2473:
2474: attr.ia_valid = ATTR_MODE;
2475: attr.ia_mode = mode | S_IFDIR;
2476:
2477: trace_nfs_mkdir_enter(dir, dentry);
2478: ret = NFS_PROTO(dir)->mkdir(dir, dentry, &attr);
2479: trace_nfs_mkdir_exit(dir, dentry, PTR_ERR_OR_ZERO(ret));
2480: return ret;
2481: }
SourceFile:fs/nfs/dir.c]
|
| 50/2 |
2026/06/09 16:15 |
llm |
expert |
0m
Model:
Tokens:
input: 55689
output: 42
thoughts: 31
Thoughts: Here's my thought process, as if I were considering this myself:
**Delving into the NFS Directory Creation Path**
Okay, so I'm focusing on how directory creation works in NFS, specifically with `mkdir`. I'm immediately thinking, "Aha! `NFS_PROTO(dir)->mkdir` returns a dentry, that's crucial." That `dentry` is the key to understanding the file system entry's location. Now, my mind is already jumping to the next logical step, how this call is handled at the NFSv3 protocol level. I need to understand how the v3 protocol handles the request and creates the file. So, I have to examine how `nfs3_proc_mkdir` fits into this picture. I'm going to dig into the details of that function. I expect it'll give me the essential info I need about the interaction between the RPC call and the local file system.
|
| 51/2 |
2026/06/09 16:15 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/nfs/nfs3proc.c Name:nfs3_proc_mkdir]
Results: map[SourceCode: 588: static struct dentry *
589: nfs3_proc_mkdir(struct inode *dir, struct dentry *dentry, struct iattr *sattr)
590: {
591: struct posix_acl *default_acl, *acl;
592: struct nfs3_createdata *data;
593: struct dentry *ret = ERR_PTR(-ENOMEM);
594: int status;
595:
596: dprintk("NFS call mkdir %pd\n", dentry);
597:
598: data = nfs3_alloc_createdata();
599: if (data == NULL)
600: goto out;
601:
602: ret = ERR_PTR(posix_acl_create(dir, &sattr->ia_mode,
603: &default_acl, &acl));
604: if (IS_ERR(ret))
605: goto out;
606:
607: data->msg.rpc_proc = &nfs3_procedures[NFS3PROC_MKDIR];
608: data->arg.mkdir.fh = NFS_FH(dir);
609: data->arg.mkdir.name = dentry->d_name.name;
610: data->arg.mkdir.len = dentry->d_name.len;
611: data->arg.mkdir.sattr = sattr;
612:
613: ret = nfs3_do_create(dir, dentry, data);
614:
615: if (IS_ERR(ret))
616: goto out_release_acls;
617:
618: if (ret)
619: dentry = ret;
620:
621: status = nfs3_proc_setacls(d_inode(dentry), acl, default_acl);
622: if (status) {
623: dput(ret);
624: ret = ERR_PTR(status);
625: }
626:
627: out_release_acls:
628: posix_acl_release(acl);
629: posix_acl_release(default_acl);
630: out:
631: nfs3_free_createdata(data);
632: dprintk("NFS reply mkdir: %d\n", PTR_ERR_OR_ZERO(ret));
633: return ret;
634: }
SourceFile:fs/nfs/nfs3proc.c]
|
| 52/2 |
2026/06/09 16:15 |
llm |
expert |
0m
Model:
Tokens:
input: 56711
output: 42
thoughts: 30
Thoughts: Here's my thought process, as if I were considering this code:
**Dentry Return from `nfs3_do_create`**
Okay, so I'm looking at this and I need to understand this new codebase. The first thing that jumps out at me is that `nfs3_do_create` *returns a dentry*. That's a key observation. Generally, dentry management in the VFS layer is pretty intricate, and seeing a function explicitly returning one, especially a creation function, immediately raises a few flags. I need to understand how this ties into the overall VFS architecture and the NFS client's handling of dentries and inodes. I wonder if this dentry is newly created or retrieved from cache. Is this the standard behaviour of NFS3 creation calls, or is something custom happening in this particular client implementation?
Right. First thing's first: I need to dive into the source code for `nfs3_do_create`. Understanding the implementation of that function is the *critical* next step. I need to figure out what it's doing with that dentry and, by extension, how it's interacting with the VFS and the server. I should check how this dentry is being managed, used, referenced, and released. Understanding the flow from the create system call to this function and back will be vital.
|
| 53/2 |
2026/06/09 16:15 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/nfs/nfs3proc.c Name:nfs3_do_create]
Results: map[SourceCode: 316: static struct dentry *
317: nfs3_do_create(struct inode *dir, struct dentry *dentry, struct nfs3_createdata *data)
318: {
319: int status;
320:
321: status = rpc_call_sync(NFS_CLIENT(dir), &data->msg, 0);
322: nfs_post_op_update_inode(dir, data->res.dir_attr);
323: if (status != 0)
324: return ERR_PTR(status);
325:
326: return nfs_add_or_obtain(dentry, data->res.fh, data->res.fattr);
327: }
SourceFile:fs/nfs/nfs3proc.c]
|
| 54/2 |
2026/06/09 16:15 |
llm |
expert |
0m
Model:
Tokens:
input: 57312
output: 40
thoughts: 30
Thoughts: Here's my analysis of the provided snippet, framed as my own thought process:
**Diving into the `nfs_add_or_obtain` Function**
Okay, so we're dealing with `nfs_add_or_obtain`. The key takeaway here is that it returns a `dentry`. That's the core result, the thing we're getting back. Let's dig into that function. My initial questions are: what is this dentry representing? What context and processes are involved in obtaining or adding it? I need to understand its relationship to the underlying NFS mechanisms and what this function is specifically tasked to do. Clearly, this `nfs_add_or_obtain` must be a fundamental building block of the NFS client side implementation. Let's see how it operates.
|
| 55/2 |
2026/06/09 16:15 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/nfs/dir.c Name:nfs_add_or_obtain]
Results: map[SourceCode:2337: struct dentry *
2338: nfs_add_or_obtain(struct dentry *dentry, struct nfs_fh *fhandle,
2339: struct nfs_fattr *fattr)
2340: {
2341: struct dentry *parent = dget_parent(dentry);
2342: struct inode *dir = d_inode(parent);
2343: struct inode *inode;
2344: struct dentry *d;
2345: int error;
2346:
2347: d_drop(dentry);
2348:
2349: if (fhandle->size == 0) {
2350: error = NFS_PROTO(dir)->lookup(dir, dentry, &dentry->d_name,
2351: fhandle, fattr);
2352: if (error)
2353: goto out_error;
2354: }
2355: nfs_set_verifier(dentry, nfs_save_change_attribute(dir));
2356: if (!(fattr->valid & NFS_ATTR_FATTR)) {
2357: struct nfs_server *server = NFS_SB(dentry->d_sb);
2358: error = server->nfs_client->rpc_ops->getattr(server, fhandle,
2359: fattr, NULL);
2360: if (error < 0)
2361: goto out_error;
2362: }
2363: inode = nfs_fhget(dentry->d_sb, fhandle, fattr);
2364: d = d_splice_alias(inode, dentry);
2365: out:
2366: dput(parent);
2367: return d;
2368: out_error:
2369: d = ERR_PTR(error);
2370: goto out;
2371: }
SourceFile:fs/nfs/dir.c]
|
| 56/2 |
2026/06/09 16:15 |
llm |
expert |
0m
Model:
Tokens:
input: 58118
output: 38
thoughts: 834
Thoughts: **Analyzing the mkdir Path and Potential Race Conditions**
Okay, so I'm tracing the flow of execution for a directory creation, specifically how the dentry gets unlocked. It seems pretty straightforward, at least initially. `d_splice_alias` gives us a dentry, and that dentry is eventually returned up the stack through `nfs_add_or_obtain`, `nfs3_do_create`, `nfs3_proc_mkdir`, `nfs_mkdir`, `vfs_mkdir`, and finally `filename_mkdirat`, which then gets passed to `end_creating_path`. `end_creating_path` calls `end_creating(dentry)`, which calls `end_dirop(dentry)`, which *should* correctly unlock the parent inode with `inode_unlock(dentry->d_parent->d_inode)`. Since `d_splice_alias` is creating an alias, the new dentry's `d_parent` *should* be the intended directory, and thus the unlocking is correct. I'm reasonably comfortable with that.
But, I need to be *sure*. What if something goes wrong *before* `end_creating(dentry)` is called? The core vulnerability here would be to unlock the parent inode *before* it's actually finished being modified or checked. If there is some early return then that causes this unlocked inode to have its lock released, then potentially other problems could occur.
Okay, let's look at `vfs_mkdir` itself. Hmm, so `may_create_dentry`, permission checks, and link limit checks. Then the all-important call to the filesystem's `mkdir` operation (`dir->i_op->mkdir`). Then some delegations and security checks. Ah, there's `err:`, which *does* call `end_creating(dentry)`. *But*... what if the *filesystem's* `mkdir` function returns an error *itself*, but also *already* called `end_creating(dentry)` internally? That would create a double-unlock situation and potentially a race. It is also possible that a filesystem that calls `end_creating(dentry)` *without* an error return.
I need to confirm if this *can* happen. Is there *any* filesystem that would handle its `mkdir` that way? I should check the implementations of various filesystems' `mkdir` functions. Let's start with btrfs, it is notorious for being complex.
|
| 57/2 |
2026/06/09 16:15 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/btrfs/inode.c Name:btrfs_mkdir]
Results: map[SourceCode:7115: static struct dentry *btrfs_mkdir(struct mnt_idmap *idmap, struct inode *dir,
7116: struct dentry *dentry, umode_t mode)
7117: {
7118: struct inode *inode;
7119:
7120: inode = new_inode(dir->i_sb);
7121: if (!inode)
7122: return ERR_PTR(-ENOMEM);
7123: inode_init_owner(idmap, inode, dir, S_IFDIR | mode);
7124: inode->i_op = &btrfs_dir_inode_operations;
7125: inode->i_fop = &btrfs_dir_file_operations;
7126: return ERR_PTR(btrfs_create_common(dir, dentry, inode));
7127: }
SourceFile:fs/btrfs/inode.c]
|
| 58/2 |
2026/06/09 16:15 |
llm |
expert |
0m
Model:
Tokens:
input: 59795
output: 38
thoughts: 14
Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience:
**Analyzing the `ecryptfs_mkdir` Function**
Hmm, just... *no*. That's not quite right. Need to dig deeper. Okay, let's zero in on `ecryptfs_mkdir`. I have to revisit that. Time to check `ecryptfs_mkdir` again. I think that's the key to unlocking this issue. It has something important to tell me.
|
| 59/2 |
2026/06/09 16:15 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/ecryptfs/inode.c Name:ecryptfs_mkdir]
Results: map[SourceCode: 508: static struct dentry *ecryptfs_mkdir(struct mnt_idmap *idmap, struct inode *dir,
509: struct dentry *dentry, umode_t mode)
510: {
511: int rc;
512: struct dentry *lower_dentry;
513: struct dentry *lower_dir_dentry;
514: struct inode *lower_dir;
515:
516: lower_dentry = ecryptfs_start_creating_dentry(dentry);
517: if (IS_ERR(lower_dentry))
518: return lower_dentry;
519: lower_dir_dentry = dget(lower_dentry->d_parent);
520: lower_dir = lower_dir_dentry->d_inode;
521: lower_dentry = vfs_mkdir(&nop_mnt_idmap, lower_dir,
522: lower_dentry, mode, NULL);
523: rc = PTR_ERR(lower_dentry);
524: if (IS_ERR(lower_dentry))
525: goto out;
526: rc = 0;
527: if (d_unhashed(lower_dentry))
528: goto out;
529: rc = ecryptfs_interpose(lower_dentry, dentry, dir->i_sb);
530: if (rc)
531: goto out;
532: fsstack_copy_attr_times(dir, lower_dir);
533: fsstack_copy_inode_size(dir, lower_dir);
534: set_nlink(dir, lower_dir->i_nlink);
535: out:
536: dput(lower_dir_dentry);
537: end_creating(lower_dentry);
538: if (d_really_is_negative(dentry))
539: d_drop(dentry);
540: return ERR_PTR(rc);
541: }
SourceFile:fs/ecryptfs/inode.c]
|
| 60/2 |
2026/06/09 16:15 |
llm |
expert |
0m
Model:
Tokens:
input: 60562
output: 36
thoughts: 68
Thoughts: Here's my summary, as the expert:
**Understanding the `mkdir` Path in Encrypted and Overlay Filesystems**
Okay, so I'm digging into the filesystem code, specifically the `mkdir` operation and how it plays out with encryption, and overlay filesystems. My first observation is that with `ecryptfs_mkdir`, the crucial call to `end_creating` is made on the *lower* dentry (`lower_dentry`). That's as it should be, considering the layered nature of this setup. The *upper* dentry, or the dentry representing the encrypted file, doesn't directly trigger `end_creating`. That makes perfect sense; the lower layer handles the actual physical creation, so that makes sense.
Now, that brings me to the overlay filesystem. I need to make sure I am thinking about how `ovl_mkdir` is handled and whether it is consistent with how this is being handled in `ecryptfs_mkdir`. I have a feeling there might be a subtle difference there, so that's where my focus needs to be.
|
| 61/2 |
2026/06/09 16:15 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/overlayfs/dir.c Name:ovl_mkdir]
Results: map[SourceCode: 740: static struct dentry *ovl_mkdir(struct mnt_idmap *idmap, struct inode *dir,
741: struct dentry *dentry, umode_t mode)
742: {
743: return ERR_PTR(ovl_create_object(dentry, (mode & 07777) | S_IFDIR, 0, NULL));
744: }
SourceFile:fs/overlayfs/dir.c]
|
| 62/2 |
2026/06/09 16:15 |
llm |
expert |
0m
Model:
Tokens:
input: 61036
output: 39
thoughts: 16
Thoughts: Okay, here's my summary, tailored for an expert audience, as if it's a direct stream of thought:
**Initial Assessment: A Flat "No"**
Initially, I had a knee-jerk, almost reflexive negative reaction. Something about the problem just didn't sit right. It triggered an immediate "No" in my mind. Perhaps I'm missing something, or there's a fundamental misunderstanding, and I needed to reject the premise entirely.
**Re-evaluation: A Possibility Emerges**
But hold on... that initial rejection felt too quick, too uninformed. Now, I should be looking at `cachefiles_get_directory`. I need to revisit that function. What did it specifically do, and why did it bring itself to mind? The interaction with the cache layer deserves a second look. There could be a subtle interplay or relevant side effect that I initially dismissed. Let me see how it fits in to the problem at hand.
|
| 63/2 |
2026/06/09 16:15 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/cachefiles/namei.c Name:cachefiles_get_directory]
Results: map[SourceCode: 81: /*
82: * get a subdirectory
83: */
84: struct dentry *cachefiles_get_directory(struct cachefiles_cache *cache,
85: struct dentry *dir,
86: const char *dirname,
87: bool *_is_new)
88: {
89: struct dentry *subdir;
90: struct path path;
91: int ret;
92:
93: _enter(",,%s", dirname);
94:
95: /* search the current directory for the element name */
96:
97: retry:
98: ret = cachefiles_inject_read_error();
99: if (ret == 0)
100: subdir = start_creating(&nop_mnt_idmap, dir, &QSTR(dirname));
101: else
102: subdir = ERR_PTR(ret);
103: trace_cachefiles_lookup(NULL, dir, subdir);
104: if (IS_ERR(subdir)) {
105: trace_cachefiles_vfs_error(NULL, d_backing_inode(dir),
106: PTR_ERR(subdir),
107: cachefiles_trace_lookup_error);
108: if (PTR_ERR(subdir) == -ENOMEM)
109: goto nomem_d_alloc;
110: goto lookup_error;
111: }
112:
113: _debug("subdir -> %pd %s",
114: subdir, d_backing_inode(subdir) ? "positive" : "negative");
115:
116: /* we need to create the subdir if it doesn't exist yet */
117: if (d_is_negative(subdir)) {
118: ret = cachefiles_has_space(cache, 1, 0,
119: cachefiles_has_space_for_create);
120: if (ret < 0)
121: goto mkdir_error;
122:
123: _debug("attempt mkdir");
124:
125: path.mnt = cache->mnt;
126: path.dentry = dir;
127: ret = security_path_mkdir(&path, subdir, 0700);
128: if (ret < 0)
129: goto mkdir_error;
130: ret = cachefiles_inject_write_error();
131: if (ret == 0) {
132: subdir = vfs_mkdir(&nop_mnt_idmap, d_inode(dir), subdir, 0700, NULL);
133: if (IS_ERR(subdir))
134: ret = PTR_ERR(subdir);
135: } else {
136: end_creating(subdir);
137: subdir = ERR_PTR(ret);
138: }
139: if (IS_ERR(subdir)) {
140: trace_cachefiles_vfs_error(NULL, d_inode(dir), ret,
141: cachefiles_trace_mkdir_error);
142: goto mkdir_error;
143: }
144: trace_cachefiles_mkdir(dir, subdir);
145:
146: if (unlikely(d_unhashed(subdir) || d_is_negative(subdir))) {
147: end_creating(subdir);
148: goto retry;
149: }
150: ASSERT(d_backing_inode(subdir));
151:
152: _debug("mkdir -> %pd{ino=%llu}",
153: subdir, d_backing_inode(subdir)->i_ino);
154: if (_is_new)
155: *_is_new = true;
156: }
157:
158: /* Tell rmdir() it's not allowed to delete the subdir */
159: inode_lock(d_inode(subdir));
160: end_creating_keep(subdir);
161:
162: if (!__cachefiles_mark_inode_in_use(NULL, d_inode(subdir))) {
163: pr_notice("cachefiles: Inode already in use: %pd (B=%llx)\n",
164: subdir, d_inode(subdir)->i_ino);
165: goto mark_error;
166: }
167:
168: inode_unlock(d_inode(subdir));
169:
170: /* we need to make sure the subdir is a directory */
171: ASSERT(d_backing_inode(subdir));
172:
173: if (!d_can_lookup(subdir)) {
174: pr_err("%s is not a directory\n", dirname);
175: ret = -EIO;
176: goto check_error;
177: }
178:
179: ret = -EPERM;
180: if (!(d_backing_inode(subdir)->i_opflags & IOP_XATTR) ||
181: !d_backing_inode(subdir)->i_op->lookup ||
182: !d_backing_inode(subdir)->i_op->mkdir ||
183: !d_backing_inode(subdir)->i_op->rename ||
184: !d_backing_inode(subdir)->i_op->rmdir ||
185: !d_backing_inode(subdir)->i_op->unlink)
186: goto check_error;
187:
188: _leave(" = [%llu]", d_backing_inode(subdir)->i_ino);
189: return subdir;
190:
191: check_error:
192: cachefiles_put_directory(subdir);
193: _leave(" = %d [check]", ret);
194: return ERR_PTR(ret);
195:
196: mark_error:
197: inode_unlock(d_inode(subdir));
198: dput(subdir);
199: return ERR_PTR(-EBUSY);
200:
201: mkdir_error:
202: end_creating(subdir);
203: pr_err("mkdir %s failed with error %d\n", dirname, ret);
204: return ERR_PTR(ret);
205:
206: lookup_error:
207: ret = PTR_ERR(subdir);
208: pr_err("Lookup %s failed with error %d\n", dirname, ret);
209: return ERR_PTR(ret);
210:
211: nomem_d_alloc:
212: inode_unlock(d_inode(dir));
213: _leave(" = -ENOMEM");
214: return ERR_PTR(-ENOMEM);
215: }
SourceFile:fs/cachefiles/namei.c]
|
| 64/2 |
2026/06/09 16:15 |
llm |
expert |
0m
Model:
Tokens:
input: 63307
output: 36
thoughts: 2583
Thoughts: **Analysis of `vfs_mkdir` and Associated Locking Behavior**
Okay, let's break this down systematically. My initial concern was about how directory inode locks are managed during `mkdir` operations. First, the standard case: If `vfs_mkdir` encounters an error before the filesystem's `mkdir` operation is called, or if that call itself returns an error, the parent inode is correctly unlocked via `end_creating(dentry)` and the dentry is dropped, which is expected. That's straightforward.
Now, the trickier scenarios. What happens if the filesystem returns a *new* dentry, not the original, after a successful `mkdir`? My first thought was that this could lead to unlocking the *wrong* inode. However, it seems that if the dentry is changed during an `mkdir` operation such as `d_splice_alias`, the new dentry's parent will still *be* the original `dir`. So, `inode_unlock` *should* unlock the correct inode, which is what we want.
I needed to check if there were cases where `d_splice_alias` does *not* move the dentry, but if it doesn't, it returns an error, so the normal error paths are taken. Likewise, in the case of `btrfs_mkdir`, it seems that if the mkdir function returns NULL (ERR_PTR(0)), that just means that the operation failed, and the regular dentry gets returned, which results in the correct unlocking via `end_creating`.
Next, I looked at cases where `vfs_mkdir` returns an error, and the error gets passed to `filename_mkdirat`. In this case, `vfs_mkdir` has already unlocked the inode. In this case, the `end_creating` does *not* unlock the inode. Instead, `filename_mkdirat` might retry the operation, which is also correct.
Finally, let's consider a scenario where `dir->i_op->mkdir` returns a new dentry, but somehow the new dentry is not correctly parented. The key is to verify what `dput` does, and what calls `dput`.
The `mkdir` function returns a new dentry, and it calls `dput(dentry)` on the original dentry. If the `mkdir` function returns an error, then the `vfs_mkdir` function will call the `end_creating(dentry)` which will `dput` the dentry. The dentry's parent *should* be the original, locked, parent directory. So, unlocking the parent inode at that point should also be correct.
Basically, I've covered the cases where the parent inode is unlocked at the right time.
|
| 65/2 |
2026/06/09 16:16 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/overlayfs/dir.c Name:ovl_mkdir]
Results: map[SourceCode: 740: static struct dentry *ovl_mkdir(struct mnt_idmap *idmap, struct inode *dir,
741: struct dentry *dentry, umode_t mode)
742: {
743: return ERR_PTR(ovl_create_object(dentry, (mode & 07777) | S_IFDIR, 0, NULL));
744: }
SourceFile:fs/overlayfs/dir.c]
|
| 66/2 |
2026/06/09 16:16 |
llm |
expert |
0m
Model:
Tokens:
input: 66653
output: 36
thoughts: 3100
Thoughts: **Analyzing a Potential Race Condition in `vfs_mkdir`**
Alright, let's break this down systematically. We're looking at a scenario where `vfs_mkdir` might be causing an `inode_unlock` on an already unlocked inode, leading to the reported RWSEM warning. My initial thought is that the return of `ERR_PTR` in certain paths could be the root cause. It looks like the issue stems from an incorrect usage of `dentry`, specifically, the double `dput` or unlocking the wrong inode.
My mind started going through the control flow. I confirmed that `fsnotify_mkdir` doesn't unlock the parent. Then, I considered the case where a filesystem's `mkdir` implementation returns a *different* dentry than the one initially provided. This would mean `inode_unlock` would be called on a dentry's parent that's *not* the original directory `dir`. However, mkdir doesn't move existing directories. Then the scenario where `mkdir` could return an existing dentry, returning `-EEXIST` (the appropriate behavior).
I dug into the provided call stack and found it pointed directly to `inode_unlock`. This confirms my suspicion that we're dealing with an inode being unlocked at the wrong time or in the wrong context. The warning itself suggests the semaphore isn't locked by the expected thread, but I needed to understand *why*. So, I'm checking the specific logic of `vfs_mkdir` to find out.
I meticulously examined the `vfs_mkdir` code path. Returning an error triggers `end_creating` which unlocks the parent. That seemed correct. However, what if a valid dentry is returned, but with a different parent? That's when I zoomed in on `nfs_add_or_obtain` and specifically, the possibility of `d_splice_alias`. This function *does* move a dentry, and this moves the dentry to have the right parent. So, the parent will *always* be the correct parent.
I then honed in on the specific case where the filesystem's `mkdir` *doesn't* return an error, but *does* return a dentry (that may be the SAME dentry). If it's the SAME, `dput(dentry)` is called, then the same dentry is assigned to the return `dentry`. Here, the dentry is *DOUBLE* `dput`ed, a memory corruption! This is a likely culprit for the race. Specifically, `vfs_mkdir`'s implementation seems to have an edge case where it returns a valid dentry, but calls `dput` *twice* on it if the filesystem's mkdir implementation is the same dentry that was passed in.
|
| 67/2 |
2026/06/09 16:16 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/nfs/dir.c Name:nfs_mkdir]
Results: map[SourceCode:2462: /*
2463: * See comments for nfs_proc_create regarding failed operations.
2464: */
2465: struct dentry *nfs_mkdir(struct mnt_idmap *idmap, struct inode *dir,
2466: struct dentry *dentry, umode_t mode)
2467: {
2468: struct iattr attr;
2469: struct dentry *ret;
2470:
2471: dfprintk(VFS, "NFS: mkdir(%s/%llu), %pd\n",
2472: dir->i_sb->s_id, dir->i_ino, dentry);
2473:
2474: attr.ia_valid = ATTR_MODE;
2475: attr.ia_mode = mode | S_IFDIR;
2476:
2477: trace_nfs_mkdir_enter(dir, dentry);
2478: ret = NFS_PROTO(dir)->mkdir(dir, dentry, &attr);
2479: trace_nfs_mkdir_exit(dir, dentry, PTR_ERR_OR_ZERO(ret));
2480: return ret;
2481: }
SourceFile:fs/nfs/dir.c]
|
| 68/2 |
2026/06/09 16:16 |
llm |
expert |
6m
Model:
Tokens:
input: 70738
output: 66
thoughts: 62910
|
| 69/2 |
2026/06/09 16:23 |
tool |
set-results |
0m
Args: map[DenialOfService:true Exploitable:false FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:true UserNamespace:false VMGuestTrigger:false VMHostTrigger:false]
Results: map[DenialOfService:true Exploitable:false FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:true UserNamespace:false VMGuestTrigger:false VMHostTrigger:false]
|
| 70/2 |
2026/06/09 16:23 |
llm |
expert |
0m
Model:
Tokens:
input: 133778
output: 668
thoughts: 226
Thoughts: **My Analysis of the rwsem Unlock Warning**
Okay, so I've got this bug report, and it looks like a classic rwsem unlock issue – specifically, a thread is trying to release a read-write semaphore that it *didn't* acquire in the first place. That's a red flag, and it's flagged as a warning, which is good, it doesn't immediately crash the system.
Tracing the call stack, I see it originates in `filename_mkdirat`, which then calls `end_creating_path`, which leads to `end_creating`, then `end_dirop`, and finally to `inode_unlock`. So, somewhere along this chain, something went wrong with the locking/unlocking logic.
My initial hypothesis? The `vfs_mkdir` function might be the culprit. It could be returning a dentry where the parent inode is *different* from the inode that was originally locked, or, even worse, the filesystem's `mkdir` implementation might have unlocked the parent already! That's a textbook violation of locking semantics. It's a bug in either the VFS layer, or the implementation of `mkdir` in some filesystems.
While it's just a warning, this kind of lock state mismatch is concerning. It could introduce subtle deadlocks or, worse, potentially lead to a denial-of-service condition – and that's something we need to take seriously. The warning tells me the problem can be triggered by unprivileged users, specifically through the `mkdirat` system call.
Fortunately, it doesn't seem directly exploitable for things like memory corruption or privilege escalation. It's just a lock state issue. But, a deadlock could be triggered, and that's still a pretty big deal.
Based on this analysis, I'm going to classify this as DenialOfService=true and Unprivileged=true. I'll need to dig deeper into the code to determine the exact path to trigger this and figure out the best way to resolve it.
|
| 71/1 |
2026/06/09 16:23 |
action |
format-explanation |
0m
Results: map[Explanation:The bug report indicates a `WARNING` in `__up_write` triggered during
`inode_unlock` in the `mkdirat` syscall path. The warning specifically complains
that the `rwsem` being unlocked is not owned by the current thread
(`rwsem_owner(sem) != current`), and its count is `0x0` (meaning it's already
unlocked or was never locked).
### Analysis of the Bug
The call trace shows the following sequence:
```
inode_unlock include/linux/fs.h:1039 [inline]
end_dirop fs/namei.c:2951 [inline]
end_creating include/linux/namei.h:123 [inline]
end_creating_path fs/namei.c:4990 [inline]
filename_mkdirat+0x305/0x510 fs/namei.c:5299
```
In `filename_mkdirat`, the parent directory's inode is locked during
`filename_create` (which calls `start_dirop`). Then, `vfs_mkdir` is called to
perform the actual directory creation.
Recent changes in the VFS layer modified `vfs_mkdir` to unlock the parent inode
internally if it encounters an error, and return an `ERR_PTR`. If `vfs_mkdir`
returns an error, `filename_mkdirat` passes that `ERR_PTR` to
`end_creating_path`, which correctly does nothing (avoiding a double-unlock).
However, if a specific filesystem's `->mkdir` implementation behaves
unexpectedly—for example, by returning a spliced dentry (`d_splice_alias`) whose
parent does not match the originally locked directory, or by erroneously
unlocking the parent inode itself before returning success—`end_creating_path`
will attempt to unlock an inode that is either already unlocked or was never
locked by the current thread. This triggers the `DEBUG_RWSEMS_WARN_ON` warning.
### Security Impact
* **Exploitable:** No. This is a lock state mismatch warning. While it indicates
a violation of locking rules (unlocking an unlocked semaphore or unlocking the
wrong inode's semaphore), it does not directly lead to memory corruption (like
use-after-free or out-of-bounds access) or privilege escalation.
* **Denial Of Service:** Yes. Unlocking a semaphore that is not locked, or
leaving the originally locked semaphore locked forever, will corrupt the lock
state. This can easily lead to deadlocks when other processes attempt to access
the same directory, effectively causing a local denial-of-service (hanging
tasks).
* **Accessible From Unprivileged Processes:** Yes. The bug is triggered via the
standard `mkdir` / `mkdirat` system calls, which are available to any
unprivileged user process.
### Conclusion
This is a local Denial-of-Service (DoS) vulnerability reachable by unprivileged
users due to a locking imbalance in the VFS directory creation path (likely
interacting with a specific filesystem's `mkdir` implementation). It is not
exploitable for privilege escalation or arbitrary code execution.]
|