| 0/0 |
2026/05/26 15:49 |
flow |
assessment-security |
2m
Results: map[DenialOfService:false Explanation:Based on the analysis of the provided KCSAN bug report and the kernel source
code, here is the assessment of the security impact:
**Bug Mechanism & Impact:**
The report highlights a data race on the `inode->i_mtime_nsec` field (4 bytes).
- **Writer:** `simple_rename_timestamp()` updates the parent directory's
modification time (`mtime`) during a rename operation by calling
`inode_set_mtime_to_ts()`, which performs a plain write to
`inode->i_mtime_nsec`.
- **Reader:** Concurrently, a `stat()` family system call (in this case
`newfstatat`) reads the directory's attributes. The newly introduced multigrain
timestamp infrastructure calls `fill_mg_cmtime()`, which fetches the `mtime` via
`inode_get_mtime()`, performing a plain read of `inode->i_mtime_nsec`.
This is a classic and known benign data race in the Linux VFS layer. Timestamps
are frequently read locklessly (e.g., during RCU path walks or `stat` calls)
while being updated by other operations.
The worst-case consequence of this race is that a user-space process might
receive a slightly inconsistent or torn read of the `tv_nsec` (nanoseconds)
field of the modification time. It does not corrupt kernel memory, does not
affect kernel control flow, and does not trigger any crashes, hangs, or resource
leaks.
**Triggerability:**
- **Unprivileged / User Namespaces:** Yes. Any unprivileged user can trigger
this by simply spamming `rename()` and `stat()` concurrently on a directory they
have access to. It does not require any special capabilities or namespaces,
though it is equally reachable from within a user namespace.
- **Exploitable / Denial of Service:** No. The race is completely benign and has
no security or stability implications.
- **Remote / Network / Peripheral / Filesystem / VM:** No. This is a generic
local VFS behavior and is not specific to any malicious external inputs, network
packets, or virtualization contexts.
**Conclusion:**
This is a benign data race with no security impact (Not Exploitable, No Denial
of Service). Exploitable:false FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:true UserNamespace:true VMGuestTrigger:false VMHostTrigger:false]
|
| 1/1 |
2026/05/26 15:49 |
action |
syz-repro-to-c-repro |
0m
Results:
|
| 2/1 |
2026/05/26 15:49 |
action |
kernel-checkouter |
0m
Results: map[KernelSrc:/app/workdir/cache/src/f39a207a13f89c53fd2e8bbde7e26ff46eff3645]
|
| 3/1 |
2026/05/26 15:49 |
action |
kernel-builder |
0m
Results: map[KernelObj:/app/workdir/cache/build/efe7d785d063c8b0f48208a861578a079302fdb4]
|
| 4/1 |
2026/05/26 15:49 |
action |
codesearch-prepare |
0m
Results: map[Index:codesearch-index]
|
| 5/1 |
2026/05/26 15:49 |
agent |
expert |
1m
Model:
Results: map[DenialOfService:false Exploitable:false FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:true UserNamespace:true 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 greeping
".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 completly 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 tranfers, that's 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 exploitabability 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 Confidetial 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:
==================================================================
BUG: KCSAN: data-race in fill_mg_cmtime / simple_rename_timestamp
write to 0xffff8881020f9bcc of 4 bytes by task 7666 on cpu 0:
inode_set_mtime_to_ts include/linux/fs.h:1660 [inline]
simple_rename_timestamp+0x53/0xd0 fs/libfs.c:840
shmem_rename2+0x2df/0x310 mm/shmem.c:4074
vfs_rename+0x97f/0xad0 fs/namei.c:6054
filename_renameat2+0x3b6/0x6e0 fs/namei.c:6172
__do_sys_rename fs/namei.c:6216 [inline]
__se_sys_rename+0x4a/0x1c0 fs/namei.c:6212
__x64_sys_rename+0x31/0x40 fs/namei.c:6212
x64_sys_call+0x2b3/0x3020 arch/x86/include/generated/asm/syscalls_64.h:83
do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
do_syscall_64+0x12c/0x3b0 arch/x86/entry/syscall_64.c:94
entry_SYSCALL_64_after_hwframe+0x77/0x7f
read to 0xffff8881020f9bcc of 4 bytes by task 7663 on cpu 1:
inode_get_mtime_nsec include/linux/fs.h:1646 [inline]
inode_get_mtime include/linux/fs.h:1652 [inline]
fill_mg_cmtime+0x5b/0x240 fs/stat.c:55
generic_fillattr+0x24a/0x340 fs/stat.c:99
shmem_getattr+0x181/0x200 mm/shmem.c:1302
vfs_getattr_nosec+0x146/0x1e0 fs/stat.c:213
vfs_getattr+0x48/0x60 fs/stat.c:262
vfs_statx_path+0x28/0x140 fs/stat.c:299
vfs_statx+0xc4/0x170 fs/stat.c:356
vfs_fstatat+0x118/0x170 fs/stat.c:373
__do_sys_newfstatat fs/stat.c:538 [inline]
__se_sys_newfstatat+0x55/0x3e0 fs/stat.c:532
__x64_sys_newfstatat+0x55/0x70 fs/stat.c:532
x64_sys_call+0x2c64/0x3020 arch/x86/include/generated/asm/syscalls_64.h:263
do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
do_syscall_64+0x12c/0x3b0 arch/x86/entry/syscall_64.c:94
entry_SYSCALL_64_after_hwframe+0x77/0x7f
value changed: 0x1f85316d -> 0x1f99a327
Reported by Kernel Concurrency Sanitizer on:
CPU: 1 UID: 0 PID: 7663 Comm: udevd Tainted: G W syzkaller #0 PREEMPT(full)
Tainted: [W]=WARN
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 04/18/2026
==================================================================
Reply: Based on the analysis of the provided KCSAN bug report and the kernel source code, here is the assessment of the security impact:
**Bug Mechanism & Impact:**
The report highlights a data race on the `inode->i_mtime_nsec` field (4 bytes).
- **Writer:** `simple_rename_timestamp()` updates the parent directory's modification time (`mtime`) during a rename operation by calling `inode_set_mtime_to_ts()`, which performs a plain write to `inode->i_mtime_nsec`.
- **Reader:** Concurrently, a `stat()` family system call (in this case `newfstatat`) reads the directory's attributes. The newly introduced multigrain timestamp infrastructure calls `fill_mg_cmtime()`, which fetches the `mtime` via `inode_get_mtime()`, performing a plain read of `inode->i_mtime_nsec`.
This is a classic and known benign data race in the Linux VFS layer. Timestamps are frequently read locklessly (e.g., during RCU path walks or `stat` calls) while being updated by other operations.
The worst-case consequence of this race is that a user-space process might receive a slightly inconsistent or torn read of the `tv_nsec` (nanoseconds) field of the modification time. It does not corrupt kernel memory, does not affect kernel control flow, and does not trigger any crashes, hangs, or resource leaks.
**Triggerability:**
- **Unprivileged / User Namespaces:** Yes. Any unprivileged user can trigger this by simply spamming `rename()` and `stat()` concurrently on a directory they have access to. It does not require any special capabilities or namespaces, though it is equally reachable from within a user namespace.
- **Exploitable / Denial of Service:** No. The race is completely benign and has no security or stability implications.
- **Remote / Network / Peripheral / Filesystem / VM:** No. This is a generic local VFS behavior and is not specific to any malicious external inputs, network packets, or virtualization contexts.
**Conclusion:**
This is a benign data race with no security impact (Not Exploitable, No Denial of Service).
|
| 6/2 |
2026/05/26 15:49 |
llm |
expert |
0m
Model:
Tokens:
input: 6454
output: 150
thoughts: 147
Thoughts: **Analyzing a KCSAN Data-Race Report**
Okay, so I've got a KCSAN data-race report here. Looks like there's a race condition. The report indicates a conflict between `fill_mg_cmtime`, which is reading, and `simple_rename_timestamp`, which is writing. The write operation seems to be happening within `inode_set_mtime_to_ts`, which is defined in `include/linux/fs.h`, specifically at line 1660. That function is then called by `simple_rename_timestamp` which is located in `fs/libfs.c` at line 840. On the read side, `inode_get_mtime_nsec` from `include/linux/fs.h` (line 1646) is involved, and it's being called by `fill_mg_cmtime` in `fs/stat.c` at line 55.
Time to dive into the code and see exactly how these functions interact and where this race could be occurring. I need to understand the memory access patterns and locking involved to determine if this is a false positive or an actual issue that needs fixing.
|
| 7/2 |
2026/05/26 15:49 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/stat.c Name:fill_mg_cmtime]
Results: map[SourceCode: 31: /**
32: * fill_mg_cmtime - Fill in the mtime and ctime and flag ctime as QUERIED
33: * @stat: where to store the resulting values
34: * @request_mask: STATX_* values requested
35: * @inode: inode from which to grab the c/mtime
36: *
37: * Given @inode, grab the ctime and mtime out if it and store the result
38: * in @stat. When fetching the value, flag it as QUERIED (if not already)
39: * so the next write will record a distinct timestamp.
40: *
41: * NB: The QUERIED flag is tracked in the ctime, but we set it there even
42: * if only the mtime was requested, as that ensures that the next mtime
43: * change will be distinct.
44: */
45: void fill_mg_cmtime(struct kstat *stat, u32 request_mask, struct inode *inode)
46: {
47: atomic_t *pcn = (atomic_t *)&inode->i_ctime_nsec;
48:
49: /* If neither time was requested, then don't report them */
50: if (!(request_mask & (STATX_CTIME|STATX_MTIME))) {
51: stat->result_mask &= ~(STATX_CTIME|STATX_MTIME);
52: return;
53: }
54:
55: stat->mtime = inode_get_mtime(inode);
56: stat->ctime.tv_sec = inode->i_ctime_sec;
57: stat->ctime.tv_nsec = (u32)atomic_read(pcn);
58: if (!(stat->ctime.tv_nsec & I_CTIME_QUERIED))
59: stat->ctime.tv_nsec = ((u32)atomic_fetch_or(I_CTIME_QUERIED, pcn));
60: stat->ctime.tv_nsec &= ~I_CTIME_QUERIED;
61: trace_fill_mg_cmtime(inode, &stat->ctime, &stat->mtime);
62: }
SourceFile:fs/stat.c]
|
| 8/2 |
2026/05/26 15:49 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/libfs.c Name:simple_rename_timestamp]
Results: map[SourceCode: 824: /**
825: * simple_rename_timestamp - update the various inode timestamps for rename
826: * @old_dir: old parent directory
827: * @old_dentry: dentry that is being renamed
828: * @new_dir: new parent directory
829: * @new_dentry: target for rename
830: *
831: * POSIX mandates that the old and new parent directories have their ctime and
832: * mtime updated, and that inodes of @old_dentry and @new_dentry (if any), have
833: * their ctime updated.
834: */
835: void simple_rename_timestamp(struct inode *old_dir, struct dentry *old_dentry,
836: struct inode *new_dir, struct dentry *new_dentry)
837: {
838: struct inode *newino = d_inode(new_dentry);
839:
840: inode_set_mtime_to_ts(old_dir, inode_set_ctime_current(old_dir));
841: if (new_dir != old_dir)
842: inode_set_mtime_to_ts(new_dir,
843: inode_set_ctime_current(new_dir));
844: inode_set_ctime_current(d_inode(old_dentry));
845: if (newino)
846: inode_set_ctime_current(newino);
847: }
SourceFile:fs/libfs.c]
|
| 9/2 |
2026/05/26 15:49 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:include/linux/fs.h Name:inode_set_mtime_to_ts]
Results: map[SourceCode:1656: static inline struct timespec64 inode_set_mtime_to_ts(struct inode *inode,
1657: struct timespec64 ts)
1658: {
1659: inode->i_mtime_sec = ts.tv_sec;
1660: inode->i_mtime_nsec = ts.tv_nsec;
1661: return ts;
1662: }
SourceFile:include/linux/fs.h]
|
| 10/2 |
2026/05/26 15:49 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:include/linux/fs.h Name:inode_get_mtime_nsec]
Results: map[SourceCode:1644: static inline long inode_get_mtime_nsec(const struct inode *inode)
1645: {
1646: return inode->i_mtime_nsec;
1647: }
SourceFile:include/linux/fs.h]
|
| 11/2 |
2026/05/26 15:49 |
llm |
expert |
0m
Model:
Tokens:
input: 8314
output: 37
thoughts: 117
Thoughts: Okay, let's break this down.
**Potential Data Race Detected**
My immediate concern is around a potential data race involving `inode->i_mtime_nsec`. I'm seeing that `inode_set_mtime_to_ts` modifies this field directly, without the benefit of any mutexes, spinlocks, or even atomic operations, from what I can tell in this snippet. It's a straight-up assignment. The alarm bells are already going off.
On the other side of this, `inode_get_mtime_nsec` is reading that same `inode->i_mtime_nsec` value, again, without any form of synchronization. That's a classic setup for a race condition, and it's something I need to investigate further.
My next step is to examine the `inode_get_mtime` and `inode_set_mtime_to_ts` functions in detail. I need to understand the complete calling context, and whether any other synchronization mechanisms are at play that aren't immediately obvious from this piece of code. I need to confirm the lack of locking, or identify any other synchronization mechanisms if they are there, and how they relate to the read and write operations on `inode->i_mtime_nsec`.
|
| 12/2 |
2026/05/26 15:49 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:include/linux/fs.h Name:inode_get_mtime]
Results: map[SourceCode:1649: static inline struct timespec64 inode_get_mtime(const struct inode *inode)
1650: {
1651: struct timespec64 ts = { .tv_sec = inode_get_mtime_sec(inode),
1652: .tv_nsec = inode_get_mtime_nsec(inode) };
1653: return ts;
1654: }
SourceFile:include/linux/fs.h]
|
| 13/2 |
2026/05/26 15:49 |
llm |
expert |
0m
Model:
Tokens:
input: 8877
output: 24
thoughts: 616
Thoughts: **Benign Data Race in Filesystem Timestamp Updates**
Okay, so I'm looking at this KCSAN report, and it's a textbook case: a data race on a timestamp. A process is using `stat` or `newfstatat` to get info about a file or directory while another process is renaming it. The rename operation is updating the `mtime` of the parent directory, and the `stat` call is reading that `mtime`. The problem is that the `mtime` is made of a `tv_sec` and `tv_nsec` fields. Without proper synchronization, the `stat` call can potentially read torn values – inconsistent `sec` and `nsec` components.
Now, I'm already aware this is a known quirk in the way VFS timestamps are handled in Linux. We don't generally have strict locking on these updates because it would be a huge performance bottleneck. Even if there are locks (like at a higher level), readers like the `stat` function might not take them (e.g., when walking the path with RCU or using lockless stat calls). I just confirmed `vfs_getattr` doesn't take the inode lock, it just reads the data, and `simple_rename_timestamp` is called with the directory's inode lock held (usually `i_rwsem`), but `stat` doesn't take this lock.
So, this data race is expected and, ultimately, benign. The worst that happens is a slightly inconsistent timestamp gets returned to the user-space application. It could be `tv_sec` from the old timestamp and `tv_nsec` from the new timestamp, or a torn read on 32-bit architectures. However, on 64-bit architectures, like the ones I'm most familiar with, the `tv_nsec` is a 32-bit integer, while `tv_sec` is a 64-bit integer. The write and read of `tv_nsec` happen concurrently. In this particular case, `tv_nsec` changed from `0x1f85316d` to `0x1f99a327`.
The key here is that it's *just* a timestamp race. There's no memory corruption, no possibility of crashes, and absolutely no exploitable conditions. This isn't a denial-of-service vector either. Any user can trigger this, as `stat` and `rename` are basic operations if they have access to a directory. User namespaces are also unaffected. It's not network-based, remote, or triggered by a malicious file image.
Just to be thorough, I quickly checked if there's any recent chatter about this. This confirms that this KCSAN report is exactly what I'd expect – a standard report for a benign data race. Let's make sure `inode_get_mtime_nsec` or `inode_set_mtime_to_ts` doesn't have `READ_ONCE` or `WRITE_ONCE`.
So, bottom line: nothing to see here. Move along.
|
| 14/2 |
2026/05/26 15:50 |
tool |
grepper |
0m
Args: map[Expression:inode_set_mtime_to_ts]
Results: map[Output:
Full output is too long, showing 500 out of 1474 lines.
Use more precise expression if possible.
[drivers/misc/ibmvmc.c=1039=static ssize_t ibmvmc_write(struct file *file, const char *buffer,
--
drivers/misc/ibmvmc.c-1126- inode = file_inode(file);
drivers/misc/ibmvmc.c:1127: inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode));
drivers/misc/ibmvmc.c-1128- mark_inode_dirty(inode);
--
drivers/usb/core/devio.c=2599=static long usbdev_do_ioctl(struct file *file, unsigned int cmd,
--
drivers/usb/core/devio.c-2646- if (ret >= 0)
drivers/usb/core/devio.c:2647: inode_set_mtime_to_ts(inode,
drivers/usb/core/devio.c-2648- inode_set_ctime_current(inode));
--
drivers/usb/core/devio.c-2654- if (ret >= 0)
drivers/usb/core/devio.c:2655: inode_set_mtime_to_ts(inode,
drivers/usb/core/devio.c-2656- inode_set_ctime_current(inode));
--
drivers/usb/core/devio.c-2662- if (ret >= 0)
drivers/usb/core/devio.c:2663: inode_set_mtime_to_ts(inode,
drivers/usb/core/devio.c-2664- inode_set_ctime_current(inode));
--
drivers/usb/core/devio.c-2675- if (ret >= 0)
drivers/usb/core/devio.c:2676: inode_set_mtime_to_ts(inode,
drivers/usb/core/devio.c-2677- inode_set_ctime_current(inode));
--
drivers/usb/core/devio.c-2703- if (ret >= 0)
drivers/usb/core/devio.c:2704: inode_set_mtime_to_ts(inode,
drivers/usb/core/devio.c-2705- inode_set_ctime_current(inode));
--
drivers/usb/core/devio.c-2712- if (ret >= 0)
drivers/usb/core/devio.c:2713: inode_set_mtime_to_ts(inode,
drivers/usb/core/devio.c-2714- inode_set_ctime_current(inode));
--
drivers/usb/core/devio.c-2720- if (ret >= 0)
drivers/usb/core/devio.c:2721: inode_set_mtime_to_ts(inode,
drivers/usb/core/devio.c-2722- inode_set_ctime_current(inode));
--
drivers/usb/core/devio.c-2733- if (ret >= 0)
drivers/usb/core/devio.c:2734: inode_set_mtime_to_ts(inode,
drivers/usb/core/devio.c-2735- inode_set_ctime_current(inode));
--
drivers/usb/gadget/function/f_fs.c=1854=ffs_sb_make_inode(struct super_block *sb, void *data,
--
drivers/usb/gadget/function/f_fs.c-1870- inode_set_atime_to_ts(inode, ts);
drivers/usb/gadget/function/f_fs.c:1871: inode_set_mtime_to_ts(inode, ts);
drivers/usb/gadget/function/f_fs.c-1872- inode->i_private = data;
--
fs/adfs/inode.c=245=adfs_iget(struct super_block *sb, struct object_info *obj)
--
fs/adfs/inode.c-276- inode_set_atime_to_ts(inode, ts);
fs/adfs/inode.c:277: inode_set_mtime_to_ts(inode, ts);
fs/adfs/inode.c-278- inode_set_ctime_to_ts(inode, ts);
--
fs/adfs/inode.c=302=adfs_setattr(struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr)
--
fs/adfs/inode.c-328- adfs_adfs2unix_time(&attr->ia_mtime, inode);
fs/adfs/inode.c:329: inode_set_mtime_to_ts(inode, attr->ia_mtime);
fs/adfs/inode.c-330- }
--
fs/affs/amigaffs.c=26=affs_insert_hash(struct inode *dir, struct buffer_head *bh)
--
fs/affs/amigaffs.c-62-
fs/affs/amigaffs.c:63: inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir));
fs/affs/amigaffs.c-64- inode_inc_iversion(dir);
--
fs/affs/amigaffs.c=75=affs_remove_hash(struct inode *dir, struct buffer_head *rem_bh)
--
fs/affs/amigaffs.c-116-
fs/affs/amigaffs.c:117: inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir));
fs/affs/amigaffs.c-118- inode_inc_iversion(dir);
--
fs/afs/inode.c=63=static int afs_inode_init_from_status(struct afs_operation *op,
--
fs/afs/inode.c-88- inode_set_ctime_to_ts(inode, t);
fs/afs/inode.c:89: inode_set_mtime_to_ts(inode, t);
fs/afs/inode.c-90- inode_set_atime_to_ts(inode, t);
--
fs/afs/inode.c=162=static void afs_apply_status(struct afs_operation *op,
--
fs/afs/inode.c-207- t = status->mtime_client;
fs/afs/inode.c:208: inode_set_mtime_to_ts(inode, t);
fs/afs/inode.c-209- if (vp->update_ctime)
--
fs/attr.c=294=static void setattr_copy_mgtime(struct inode *inode, const struct iattr *attr)
--
fs/attr.c-311- if (ia_valid & ATTR_MTIME_SET)
fs/attr.c:312: inode_set_mtime_to_ts(inode, attr->ia_mtime);
fs/attr.c-313- else if (ia_valid & ATTR_MTIME)
fs/attr.c:314: inode_set_mtime_to_ts(inode, now);
fs/attr.c-315-}
--
fs/attr.c=342=void setattr_copy(struct mnt_idmap *idmap, struct inode *inode,
--
fs/attr.c-362- if (ia_valid & ATTR_MTIME)
fs/attr.c:363: inode_set_mtime_to_ts(inode, attr->ia_mtime);
fs/attr.c-364-
--
fs/autofs/root.c=575=static int autofs_dir_symlink(struct mnt_idmap *idmap,
--
fs/autofs/root.c-607-
fs/autofs/root.c:608: inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir));
fs/autofs/root.c-609-
--
fs/autofs/root.c=628=static int autofs_dir_unlink(struct inode *dir, struct dentry *dentry)
--
fs/autofs/root.c-639-
fs/autofs/root.c:640: inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir));
fs/autofs/root.c-641-
--
fs/autofs/root.c=727=static struct dentry *autofs_dir_mkdir(struct mnt_idmap *idmap,
--
fs/autofs/root.c-754- inc_nlink(dir);
fs/autofs/root.c:755: inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir));
fs/autofs/root.c-756-
--
fs/bfs/dir.c=176=static int bfs_unlink(struct inode *dir, struct dentry *dentry)
--
fs/bfs/dir.c-196- mmb_mark_buffer_dirty(bh, &BFS_I(dir)->i_metadata_bhs);
fs/bfs/dir.c:197: inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir));
fs/bfs/dir.c-198- mark_inode_dirty(dir);
--
fs/bfs/dir.c=209=static int bfs_rename(struct mnt_idmap *idmap, struct inode *old_dir,
--
fs/bfs/dir.c-249- old_de->ino = 0;
fs/bfs/dir.c:250: inode_set_mtime_to_ts(old_dir, inode_set_ctime_current(old_dir));
fs/bfs/dir.c-251- mark_inode_dirty(old_dir);
--
fs/bfs/dir.c=274=static int bfs_add_entry(struct inode *dir, const struct qstr *child, int ino)
--
fs/bfs/dir.c-298- }
fs/bfs/dir.c:299: inode_set_mtime_to_ts(dir,
fs/bfs/dir.c-300- inode_set_ctime_current(dir));
--
fs/btrfs/file.c=1050=int btrfs_write_check(struct kiocb *iocb, size_t count)
--
fs/btrfs/file.c-1079- if (!IS_NOCMTIME(inode)) {
fs/btrfs/file.c:1080: inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode));
fs/btrfs/file.c-1081- inode_inc_iversion(inode);
--
fs/btrfs/file.c=2401=int btrfs_replace_file_extents(struct btrfs_inode *inode,
--
fs/btrfs/file.c-2545- if (!extent_info || extent_info->update_times)
fs/btrfs/file.c:2546: inode_set_mtime_to_ts(&inode->vfs_inode,
fs/btrfs/file.c-2547- inode_set_ctime_current(&inode->vfs_inode));
--
fs/btrfs/file.c=2656=static int btrfs_punch_hole(struct file *file, loff_t offset, loff_t len)
--
fs/btrfs/file.c-2783- inode_inc_iversion(inode);
fs/btrfs/file.c:2784: inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode));
fs/btrfs/file.c-2785- ret = btrfs_update_inode(trans, BTRFS_I(inode));
--
fs/btrfs/file.c-2803- inode_inc_iversion(inode);
fs/btrfs/file.c:2804: inode_set_mtime_to_ts(inode, now);
fs/btrfs/file.c-2805- trans = btrfs_start_transaction(root, 1);
--
fs/btrfs/inode.c=4381=static void update_time_after_link_or_unlink(struct btrfs_inode *dir)
--
fs/btrfs/inode.c-4394- now = inode_set_ctime_current(&dir->vfs_inode);
fs/btrfs/inode.c:4395: inode_set_mtime_to_ts(&dir->vfs_inode, now);
fs/btrfs/inode.c-4396-}
--
fs/btrfs/inode.c=4574=static int btrfs_unlink_subvol(struct btrfs_trans_handle *trans,
--
fs/btrfs/inode.c-4666- inode_inc_iversion(&dir->vfs_inode);
fs/btrfs/inode.c:4667: inode_set_mtime_to_ts(&dir->vfs_inode, inode_set_ctime_current(&dir->vfs_inode));
fs/btrfs/inode.c-4668- ret = btrfs_update_inode_fallback(trans, dir);
--
fs/btrfs/inode.c=5385=static int btrfs_setsize(struct inode *inode, struct iattr *attr)
--
fs/btrfs/inode.c-5402- if (!(mask & (ATTR_CTIME | ATTR_MTIME))) {
fs/btrfs/inode.c:5403: inode_set_mtime_to_ts(inode,
fs/btrfs/inode.c-5404- inode_set_ctime_current(inode));
--
fs/btrfs/inode.c=6008=static struct btrfs_inode *new_simple_dir(struct inode *dir,
--
fs/btrfs/inode.c-6036- ts = inode_set_ctime_current(vfs_inode);
fs/btrfs/inode.c:6037: inode_set_mtime_to_ts(vfs_inode, ts);
fs/btrfs/inode.c-6038- inode_set_atime_to_ts(vfs_inode, inode_get_atime(dir));
--
fs/btrfs/reflink.c=22=static int clone_finish_inode_update(struct btrfs_trans_handle *trans,
--
fs/btrfs/reflink.c-32- if (!no_time_update) {
fs/btrfs/reflink.c:33: inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode));
fs/btrfs/reflink.c-34- }
--
fs/btrfs/transaction.c=1666=static noinline int create_pending_snapshot(struct btrfs_trans_handle *trans,
--
fs/btrfs/transaction.c-1895- fname.disk_name.len * 2);
fs/btrfs/transaction.c:1896: inode_set_mtime_to_ts(&parent_inode->vfs_inode,
fs/btrfs/transaction.c-1897- inode_set_ctime_current(&parent_inode->vfs_inode));
--
fs/ceph/inode.c=211=struct inode *ceph_get_snapdir(struct inode *parent)
--
fs/ceph/inode.c-239- inode->i_gid = parent->i_gid;
fs/ceph/inode.c:240: inode_set_mtime_to_ts(inode, inode_get_mtime(parent));
fs/ceph/inode.c-241- inode_set_ctime_to_ts(inode, inode_get_ctime(parent));
--
fs/ceph/inode.c=913=void ceph_fill_file_time(struct inode *inode, int issued,
--
fs/ceph/inode.c-939-
fs/ceph/inode.c:940: inode_set_mtime_to_ts(inode, *mtime);
fs/ceph/inode.c-941- inode_set_atime_to_ts(inode, *atime);
--
fs/ceph/inode.c-946- doutc(cl, "mtime %ptSp -> %ptSp inc\n", &imtime, mtime);
fs/ceph/inode.c:947: inode_set_mtime_to_ts(inode, *mtime);
fs/ceph/inode.c-948- }
--
fs/ceph/inode.c-961- inode_set_ctime_to_ts(inode, *ctime);
fs/ceph/inode.c:962: inode_set_mtime_to_ts(inode, *mtime);
fs/ceph/inode.c-963- inode_set_atime_to_ts(inode, *atime);
--
fs/ceph/inode.c=2569=int __ceph_setattr(struct mnt_idmap *idmap, struct inode *inode,
--
fs/ceph/inode.c-2814- ci->i_time_warp_seq++;
fs/ceph/inode.c:2815: inode_set_mtime_to_ts(inode, attr->ia_mtime);
fs/ceph/inode.c-2816- dirtied |= CEPH_CAP_FILE_EXCL;
--
fs/ceph/inode.c-2818- timespec64_compare(&mtime, &attr->ia_mtime) < 0) {
fs/ceph/inode.c:2819: inode_set_mtime_to_ts(inode, attr->ia_mtime);
fs/ceph/inode.c-2820- dirtied |= CEPH_CAP_FILE_WR;
--
fs/coda/coda_linux.c=105=void coda_vattr_to_iattr(struct inode *inode, struct coda_vattr *attr)
--
fs/coda/coda_linux.c-128- if (attr->va_mtime.tv_sec != -1)
fs/coda/coda_linux.c:129: inode_set_mtime_to_ts(inode,
fs/coda/coda_linux.c-130- coda_to_timespec64(attr->va_mtime));
--
fs/coda/dir.c=104=static inline void coda_dir_update_mtime(struct inode *dir)
--
fs/coda/dir.c-113- * right most of the time. Note: we only do this for directories. */
fs/coda/dir.c:114: inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir));
fs/coda/dir.c-115-#endif
--
fs/coda/file.c=66=coda_file_write_iter(struct kiocb *iocb, struct iov_iter *to)
--
fs/coda/file.c-85- coda_inode->i_blocks = (coda_inode->i_size + 511) >> 9;
fs/coda/file.c:86: inode_set_mtime_to_ts(coda_inode, inode_set_ctime_current(coda_inode));
fs/coda/file.c-87- inode_unlock(coda_inode);
--
fs/configfs/inode.c=94=static inline void set_inode_attr(struct inode * inode, struct iattr * iattr)
--
fs/configfs/inode.c-99- inode_set_atime_to_ts(inode, iattr->ia_atime);
fs/configfs/inode.c:100: inode_set_mtime_to_ts(inode, iattr->ia_mtime);
fs/configfs/inode.c-101- inode_set_ctime_to_ts(inode, iattr->ia_ctime);
--
fs/configfs/inode.c=156=struct inode *configfs_create(struct dentry *dentry, umode_t mode)
--
fs/configfs/inode.c-173- p_inode = d_inode(dentry->d_parent);
fs/configfs/inode.c:174: inode_set_mtime_to_ts(p_inode, inode_set_ctime_current(p_inode));
fs/configfs/inode.c-175- configfs_set_inode_lock_class(sd, inode);
--
fs/cramfs/inode.c=90=static struct inode *get_cramfs_inode(struct super_block *sb,
--
fs/cramfs/inode.c-144- /* Struct copy intentional */
fs/cramfs/inode.c:145: inode_set_mtime_to_ts(inode,
fs/cramfs/inode.c-146- inode_set_atime_to_ts(inode, inode_set_ctime_to_ts(inode, zerotime)));
--
fs/efivarfs/file.c=15=static ssize_t efivarfs_file_write(struct file *file,
--
fs/efivarfs/file.c-65- i_size_write(inode, datasize + sizeof(attributes));
fs/efivarfs/file.c:66: inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode));
fs/efivarfs/file.c-67- }
--
fs/exfat/file.c=21=static int exfat_cont_expand(struct inode *inode, loff_t size)
--
fs/exfat/file.c-77-out:
fs/exfat/file.c:78: inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode));
fs/exfat/file.c-79- /* Expanded range not zeroed, do not update valid_size */
--
fs/exfat/file.c=326=int exfat_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
--
fs/exfat/file.c-379- if (attr->ia_valid & ATTR_SIZE)
fs/exfat/file.c:380: inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode));
fs/exfat/file.c-381-
--
fs/exfat/inode.c=415=static void exfat_write_failed(struct address_space *mapping, loff_t to)
--
fs/exfat/inode.c-420- truncate_pagecache(inode, i_size_read(inode));
fs/exfat/inode.c:421: inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode));
fs/exfat/inode.c-422- exfat_truncate(inode);
--
fs/exfat/inode.c=444=static int exfat_write_end(const struct kiocb *iocb,
--
fs/exfat/inode.c-462- if (!(err < 0) && !(ei->attr & EXFAT_ATTR_ARCHIVE)) {
fs/exfat/inode.c:463: inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode));
fs/exfat/inode.c-464- ei->attr |= EXFAT_ATTR_ARCHIVE;
--
fs/exfat/inode.c=598=static int exfat_fill_inode(struct inode *inode, struct exfat_dir_entry *info)
--
fs/exfat/inode.c-643- inode->i_blocks = round_up(i_size_read(inode), sbi->cluster_size) >> 9;
fs/exfat/inode.c:644: inode_set_mtime_to_ts(inode, info->mtime);
fs/exfat/inode.c-645- inode_set_ctime_to_ts(inode, info->mtime);
--
fs/exfat/namei.c=539=static int exfat_create(struct mnt_idmap *idmap, struct inode *dir,
--
fs/exfat/namei.c-558- inode_inc_iversion(dir);
fs/exfat/namei.c:559: inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir));
fs/exfat/namei.c-560- if (IS_DIRSYNC(dir) && size != i_size_read(dir))
--
fs/exfat/namei.c=841=static struct dentry *exfat_mkdir(struct mnt_idmap *idmap, struct inode *dir,
--
fs/exfat/namei.c-860- inode_inc_iversion(dir);
fs/exfat/namei.c:861: inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir));
fs/exfat/namei.c-862- if (IS_DIRSYNC(dir) && size != i_size_read(dir))
--
fs/ext2/dir.c=451=int ext2_set_link(struct inode *dir, struct ext2_dir_entry_2 *de,
--
fs/ext2/dir.c-467- if (update_times)
fs/ext2/dir.c:468: inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir));
fs/ext2/dir.c-469- EXT2_I(dir)->i_flags &= ~EXT2_BTREE_FL;
--
fs/ext2/dir.c=477=int ext2_add_link (struct dentry *dentry, struct inode *inode)
--
fs/ext2/dir.c-554- ext2_commit_chunk(folio, pos, rec_len);
fs/ext2/dir.c:555: inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir));
fs/ext2/dir.c-556- EXT2_I(dir)->i_flags &= ~EXT2_BTREE_FL;
--
fs/ext2/dir.c=572=int ext2_delete_entry(struct ext2_dir_entry_2 *dir, struct folio *folio)
--
fs/ext2/dir.c-608- ext2_commit_chunk(folio, pos, to - from);
fs/ext2/dir.c:609: inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode));
fs/ext2/dir.c-610- EXT2_I(inode)->i_flags &= ~EXT2_BTREE_FL;
--
fs/ext2/inode.c=1279=static int ext2_setsize(struct inode *inode, loff_t newsize)
--
fs/ext2/inode.c-1306-
fs/ext2/inode.c:1307: inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode));
fs/ext2/inode.c-1308- if (inode_needs_sync(inode)) {
--
fs/ext2/super.c=1563=static ssize_t ext2_quota_write(struct super_block *sb, int type,
--
fs/ext2/super.c-1608- inode_inc_iversion(inode);
fs/ext2/super.c:1609: inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode));
fs/ext2/super.c-1610- mark_inode_dirty(inode);
--
fs/ext4/ext4.h=951=do { \
fs/ext4/ext4.h:952: inode_set_mtime_to_ts(inode, \
fs/ext4/ext4.h-953- EXT4_INODE_GET_XTIME_VAL(i_mtime, inode, raw_inode)); \
--
fs/ext4/inline.c=969=static int ext4_add_dirent_to_inline(handle_t *handle,
--
fs/ext4/inline.c-1003- */
fs/ext4/inline.c:1004: inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir));
fs/ext4/inline.c-1005- ext4_update_dx_flag(dir);
--
fs/ext4/inline.c=1844=int ext4_inline_data_truncate(struct inode *inode, int *has_inline)
--
fs/ext4/inline.c-1946- if (err == 0) {
fs/ext4/inline.c:1947: inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode));
fs/ext4/inline.c-1948- err = ext4_mark_inode_dirty(handle, inode);
--
fs/ext4/inode.c=4594=int ext4_truncate(struct inode *inode)
--
fs/ext4/inode.c-4688-
fs/ext4/inode.c:4689: inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode));
fs/ext4/inode.c-4690- err2 = ext4_mark_inode_dirty(handle, inode);
--
fs/ext4/inode.c=5882=int ext4_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
--
fs/ext4/inode.c-6021- if (!shrink) {
fs/ext4/inode.c:6022: inode_set_mtime_to_ts(inode,
fs/ext4/inode.c-6023- inode_set_ctime_current(inode));
--
fs/ext4/ioctl.c=313=static void swap_inode_data(struct inode *inode1, struct inode *inode2)
--
fs/ext4/ioctl.c-332- ts2 = inode_get_mtime(inode2);
fs/ext4/ioctl.c:333: inode_set_mtime_to_ts(inode1, ts2);
fs/ext4/ioctl.c:334: inode_set_mtime_to_ts(inode2, ts1);
fs/ext4/ioctl.c-335-
--
fs/ext4/namei.c=2108=static int add_dirent_to_buf(handle_t *handle, struct ext4_filename *fname,
--
fs/ext4/namei.c-2147- */
fs/ext4/namei.c:2148: inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir));
fs/ext4/namei.c-2149- ext4_update_dx_flag(dir);
--
fs/ext4/namei.c=3136=static int ext4_rmdir(struct inode *dir, struct dentry *dentry)
--
fs/ext4/namei.c-3199- ext4_orphan_add(handle, inode);
fs/ext4/namei.c:3200: inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir));
fs/ext4/namei.c-3201- inode_set_ctime_current(inode);
--
fs/ext4/namei.c=3226=int __ext4_unlink(struct inode *dir, const struct qstr *d_name,
--
fs/ext4/namei.c-3272- goto out_handle;
fs/ext4/namei.c:3273: inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir));
fs/ext4/namei.c-3274- ext4_update_dx_flag(dir);
--
fs/ext4/namei.c=3641=static int ext4_setent(handle_t *handle, struct ext4_renament *ent,
--
fs/ext4/namei.c-3654- inode_inc_iversion(ent->dir);
fs/ext4/namei.c:3655: inode_set_mtime_to_ts(ent->dir, inode_set_ctime_current(ent->dir));
fs/ext4/namei.c-3656- retval = ext4_mark_inode_dirty(handle, ent->dir);
--
fs/ext4/namei.c=3797=static int ext4_rename(struct mnt_idmap *idmap, struct inode *old_dir,
--
fs/ext4/namei.c-3969- }
fs/ext4/namei.c:3970: inode_set_mtime_to_ts(old.dir, inode_set_ctime_current(old.dir));
fs/ext4/namei.c-3971- ext4_update_dx_flag(old.dir);
--
fs/ext4/super.c=7279=static int ext4_quota_off(struct super_block *sb, int type)
--
fs/ext4/super.c-7316- inode_set_flags(inode, 0, S_NOATIME | S_IMMUTABLE);
fs/ext4/super.c:7317: inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode));
fs/ext4/super.c-7318- err = ext4_mark_inode_dirty(handle, inode);
--
fs/f2fs/dir.c=445=void f2fs_set_link(struct inode *dir, struct f2fs_dir_entry *de,
--
fs/f2fs/dir.c-455-
fs/f2fs/dir.c:456: inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir));
fs/f2fs/dir.c-457- f2fs_mark_inode_dirty_sync(dir, false);
--
fs/f2fs/dir.c=603=void f2fs_update_parent_metadata(struct inode *dir, struct inode *inode,
--
fs/f2fs/dir.c-610- }
fs/f2fs/dir.c:611: inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir));
fs/f2fs/dir.c-612- f2fs_mark_inode_dirty_sync(dir, false);
--
fs/f2fs/dir.c=880=void f2fs_delete_entry(struct f2fs_dir_entry *dentry, struct folio *folio,
--
fs/f2fs/dir.c-922-
fs/f2fs/dir.c:923: inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir));
fs/f2fs/dir.c-924- f2fs_mark_inode_dirty_sync(dir, false);
--
fs/f2fs/file.c=905=int f2fs_truncate(struct inode *inode)
--
fs/f2fs/file.c-943-
fs/f2fs/file.c:944: inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode));
fs/f2fs/file.c-945- f2fs_mark_inode_dirty_sync(inode, false);
--
fs/f2fs/file.c=1048=static void __setattr_copy(struct mnt_idmap *idmap,
--
fs/f2fs/file.c-1057- if (ia_valid & ATTR_MTIME)
fs/f2fs/file.c:1058: inode_set_mtime_to_ts(inode, attr->ia_mtime);
fs/f2fs/file.c-1059- if (ia_valid & ATTR_CTIME)
--
fs/f2fs/file.c=1073=int f2fs_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
--
fs/f2fs/file.c-1185- spin_lock(&fi->i_size_lock);
fs/f2fs/file.c:1186: inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode));
fs/f2fs/file.c-1187- fi->last_disk_size = i_size_read(inode);
--
fs/f2fs/file.c=1995=static long f2fs_fallocate(struct file *file, int mode,
--
fs/f2fs/file.c-2059- if (!ret) {
fs/f2fs/file.c:2060: inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode));
fs/f2fs/file.c-2061- f2fs_mark_inode_dirty_sync(inode, false);
--
fs/f2fs/file.c=3108=static int f2fs_move_file_range(struct file *file_in, loff_t pos_in,
--
fs/f2fs/file.c-3229-
fs/f2fs/file.c:3230: inode_set_mtime_to_ts(src, inode_set_ctime_current(src));
fs/f2fs/file.c-3231- f2fs_mark_inode_dirty_sync(src, false);
fs/f2fs/file.c-3232- if (src != dst) {
fs/f2fs/file.c:3233: inode_set_mtime_to_ts(dst, inode_set_ctime_current(dst));
fs/f2fs/file.c-3234- f2fs_mark_inode_dirty_sync(dst, false);
--
fs/f2fs/inline.c=702=void f2fs_delete_inline_entry(struct f2fs_dir_entry *dentry,
--
fs/f2fs/inline.c-723-
fs/f2fs/inline.c:724: inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir));
fs/f2fs/inline.c-725- f2fs_mark_inode_dirty_sync(dir, false);
--
fs/f2fs/super.c=3189=static ssize_t f2fs_quota_write(struct super_block *sb, int type,
--
fs/f2fs/super.c-3229- return err;
fs/f2fs/super.c:3230: inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode));
fs/f2fs/super.c-3231- f2fs_mark_inode_dirty_sync(inode, false);
--
fs/fat/inode.c=513=int fat_fill_inode(struct inode *inode, struct msdos_dir_entry *de)
--
fs/fat/inode.c-566- fat_time_fat2unix(sbi, &mtime, de->time, de->date, 0);
fs/fat/inode.c:567: inode_set_mtime_to_ts(inode, mtime);
fs/fat/inode.c-568- inode_set_ctime_to_ts(inode, mtime);
--
fs/fat/inode.c=1321=static int fat_read_root(struct inode *inode)
--
fs/fat/inode.c-1348- fat_save_attrs(inode, ATTR_DIR);
fs/fat/inode.c:1349: inode_set_mtime_to_ts(inode,
fs/fat/inode.c-1350- inode_set_atime_to_ts(inode, inode_set_ctime(inode, 0, 0)));
--
fs/fat/misc.c=311=void fat_truncate_time(struct inode *inode, struct timespec64 *now,
--
fs/fat/misc.c-330-
fs/fat/misc.c:331: inode_set_mtime_to_ts(inode, mtime);
fs/fat/misc.c-332- inode_set_ctime_to_ts(inode, mtime);
--
fs/fuse/dir.c=2136=int fuse_do_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
--
fs/fuse/dir.c-2258- if (attr->ia_valid & ATTR_MTIME)
fs/fuse/dir.c:2259: inode_set_mtime_to_ts(inode, attr->ia_mtime);
fs/fuse/dir.c-2260- if (attr->ia_valid & ATTR_CTIME)
--
fs/gfs2/bmap.c=1372=static int trunc_start(struct inode *inode, u64 newsize)
--
fs/gfs2/bmap.c-1409- i_size_write(inode, newsize);
fs/gfs2/bmap.c:1410: inode_set_mtime_to_ts(&ip->i_inode, inode_set_ctime_current(&ip->i_inode));
fs/gfs2/bmap.c-1411- gfs2_dinode_out(ip, dibh->b_data);
--
fs/gfs2/bmap.c=1471=static int sweep_bh_for_rgrps(struct gfs2_inode *ip, struct gfs2_holder *rd_gh,
--
]
]
|
| 15/2 |
2026/05/26 15:50 |
llm |
expert |
0m
Model:
Tokens:
input: 21175
output: 37
thoughts: 313
Thoughts: **Analyzing a KCSAN Report on `i_mtime_nsec`**
Okay, so I'm looking at this KCSAN report. The code snippet is straightforward enough; it's just two simple inline functions, `inode_set_mtime_to_ts` which sets the modification time of an inode, and `inode_get_mtime_nsec` which retrieves the nanosecond component of that time. Nothing fancy here, just direct assignments and returns.
The report flags a data race on `i_mtime_nsec`, a 4-byte field. Now, that raises a flag, but immediately, my experience kicks in. There are no `READ_ONCE` or `WRITE_ONCE` calls around this field, so technically, the compiler *could* do something wonky, but realistically, this is just a simple 32-bit read and write. It's almost certainly benign.
The VFS layer is riddled with these types of data races on timestamps. They're common, accepted as a necessary evil in a highly concurrent system. Often, we simply ignore them, maybe slap a `data_race()` call in there if they generate excessive noise, or, more often than not, just leave them be. They rarely, if ever, present any exploitable vulnerability.
Hold on a second... `fill_mg_cmtime`... that rings a bell. It's related to the multigrain timestamp feature. I need to refresh my memory on that. Let me quickly dig into the commit history for `fill_mg_cmtime` and see what's going on there. I'm guessing that's where the context for this data race probably lies.
|
| 16/2 |
2026/05/26 15:50 |
tool |
git-log |
0m
Args: map[Count:5 SourcePath:fs/stat.c SymbolName:fill_mg_cmtime]
Results: map[Output:c86e3c47187a fs: tracepoints around multigrain timestamp events
4e40eff0b573 fs: add infrastructure for multigrain timestamps
]
|
| 17/2 |
2026/05/26 15:51 |
llm |
expert |
0m
Model:
Tokens:
input: 21939
output: 25
thoughts: 16
Thoughts: Okay, here's my interpretation of that thought process, tailored for an expert audience, and written in the first person:
**Decoding the Identifier**
Alright, that hash... `4e40eff0b573`. Immediately, my brain kicks into gear, assessing what kind of context it exists within. Is it a Git commit SHA? A database key? A unique identifier for a file or a specific version? The exact meaning hinges on that context, which is currently missing.
My initial scan tells me it's likely a hexadecimal representation. The length and the characters suggest it could be a common hashing algorithm result, potentially SHA-1 or MD5, though SHA-1's decline in security means I'd immediately suspect something more robust if it involves recent data. Knowing the generation context of the code is key; I can tell what my focus will need to be.
The first step is to quickly mentally reconstruct the possible origin. Is this something I generated? Is this coming from an external tool, or is it related to code I know. If I have the environment, I can immediately start looking for the object hash within. From there I can determine if a lookup is possible.
I also have to immediately ask myself if this is a known quantity. Is this a code reference I am familiar with? Have I seen it before in a code context? Does this connect to a debugging log or data stream I know?
Essentially, my thought process is a rapid-fire attempt to correlate this identifier with any existing knowledge or system I'm familiar with, to narrow down the possible meaning and origin of this hash. Context is everything.
|
| 18/2 |
2026/05/26 15:51 |
tool |
git-show |
0m
Args:
Results: map[Output:commit 4e40eff0b5737c0de39e1ae5812509efbc0b986e
Author: Jeff Layton <jlayton@kernel.org>
Date: Wed Oct 2 17:27:18 2024 -0400
fs: add infrastructure for multigrain timestamps
The VFS has always used coarse-grained timestamps when updating the
ctime and mtime after a change. This has the benefit of allowing
filesystems to optimize away a lot metadata updates, down to around 1
per jiffy, even when a file is under heavy writes.
Unfortunately, this has always been an issue when we're exporting via
NFSv3, which relies on timestamps to validate caches. A lot of changes
can happen in a jiffy, so timestamps aren't sufficient to help the
client decide when to invalidate the cache. Even with NFSv4, a lot of
exported filesystems don't properly support a change attribute and are
subject to the same problems with timestamp granularity. Other
applications have similar issues with timestamps (e.g backup
applications).
If fine-grained timestamps were always used, that would improve the
situation, but that becomes rather expensive, as the underlying
filesystem would have to log a lot more metadata updates.
What is needed is a way to only use fine-grained timestamps when they
are being actively queried. Use the (unused) top bit in
inode->i_ctime_nsec as a flag that indicates whether the current
timestamps have been queried via stat() or the like. When it's set,
allow the update to use a fine-grained timestamp iff it's necessary to
make the ctime show a different value.
If it has been queried, then first see whether the current coarse time
is later than the existing ctime. If it is, accept that value. If it
isn't, then get a fine-grained timestamp and attempt to stamp the inode
ctime with that value. If that races with another concurrent stamp, then
abandon the update and take the new value without retrying.
Filesystems can opt into this by setting the FS_MGTIME fstype flag.
Others should be unaffected (other than being subject to the same floor
value as multigrain filesystems).
Tested-by: Randy Dunlap <rdunlap@infradead.org> # documentation bits
Reviewed-by: Jan Kara <jack@suse.cz>
Signed-off-by: Jeff Layton <jlayton@kernel.org>
Link: https://lore.kernel.org/r/20241002-mgtime-v10-3-d1c4717f5284@kernel.org
Signed-off-by: Christian Brauner <brauner@kernel.org>
diff --git a/fs/inode.c b/fs/inode.c
index 10c4619faeef..53f56f6e1ff2 100644
--- a/fs/inode.c
+++ b/fs/inode.c
@@ -2172,19 +2172,58 @@ int file_remove_privs(struct file *file)
}
EXPORT_SYMBOL(file_remove_privs);
+/**
+ * current_time - Return FS time (possibly fine-grained)
+ * @inode: inode.
+ *
+ * Return the current time truncated to the time granularity supported by
+ * the fs, as suitable for a ctime/mtime change. If the ctime is flagged
+ * as having been QUERIED, get a fine-grained timestamp, but don't update
+ * the floor.
+ *
+ * For a multigrain inode, this is effectively an estimate of the timestamp
+ * that a file would receive. An actual update must go through
+ * inode_set_ctime_current().
+ */
+struct timespec64 current_time(struct inode *inode)
+{
+ struct timespec64 now;
+ u32 cns;
+
+ ktime_get_coarse_real_ts64_mg(&now);
+
+ if (!is_mgtime(inode))
+ goto out;
+
+ /* If nothing has queried it, then coarse time is fine */
+ cns = smp_load_acquire(&inode->i_ctime_nsec);
+ if (cns & I_CTIME_QUERIED) {
+ /*
+ * If there is no apparent change, then get a fine-grained
+ * timestamp.
+ */
+ if (now.tv_nsec == (cns & ~I_CTIME_QUERIED))
+ ktime_get_real_ts64(&now);
+ }
+out:
+ return timestamp_truncate(now, inode);
+}
+EXPORT_SYMBOL(current_time);
+
static int inode_needs_update_time(struct inode *inode)
{
+ struct timespec64 now, ts;
int sync_it = 0;
- struct timespec64 now = current_time(inode);
- struct timespec64 ts;
/* First try to exhaust all avenues to not sync */
if (IS_NOCMTIME(inode))
return 0;
+ now = current_time(inode);
+
ts = inode_get_mtime(inode);
if (!timespec64_equal(&ts, &now))
- sync_it = S_MTIME;
+ sync_it |= S_MTIME;
ts = inode_get_ctime(inode);
if (!timespec64_equal(&ts, &now))
@@ -2562,6 +2601,15 @@ void inode_nohighmem(struct inode *inode)
}
EXPORT_SYMBOL(inode_nohighmem);
+struct timespec64 inode_set_ctime_to_ts(struct inode *inode, struct timespec64 ts)
+{
+ set_normalized_timespec64(&ts, ts.tv_sec, ts.tv_nsec);
+ inode->i_ctime_sec = ts.tv_sec;
+ inode->i_ctime_nsec = ts.tv_nsec;
+ return ts;
+}
+EXPORT_SYMBOL(inode_set_ctime_to_ts);
+
/**
* timestamp_truncate - Truncate timespec to a granularity
* @t: Timespec
@@ -2594,36 +2642,77 @@ struct timespec64 timestamp_truncate(struct timespec64 t, struct inode *inode)
EXPORT_SYMBOL(timestamp_truncate);
/**
- * current_time - Return FS time
- * @inode: inode.
+ * inode_set_ctime_current - set the ctime to current_time
+ * @inode: inode
*
- * Return the current time truncated to the time granularity supported by
- * the fs.
+ * Set the inode's ctime to the current value for the inode. Returns the
+ * current value that was assigned. If this is not a multigrain inode, then we
+ * set it to the later of the coarse time and floor value.
*
- * Note that inode and inode->sb cannot be NULL.
- * Otherwise, the function warns and returns time without truncation.
+ * If it is multigrain, then we first see if the coarse-grained timestamp is
+ * distinct from what is already there. If so, then use that. Otherwise, get a
+ * fine-grained timestamp.
+ *
+ * After that, try to swap the new value into i_ctime_nsec. Accept the
+ * resulting ctime, regardless of the outcome of the swap. If it has
+ * already been replaced, then that timestamp is later than the earlier
+ * unacceptable one, and is thus acceptable.
*/
-struct timespec64 current_time(struct inode *inode)
+struct timespec64 inode_set_ctime_current(struct inode *inode)
{
struct timespec64 now;
+ u32 cns, cur;
- ktime_get_coarse_real_ts64(&now);
- return timestamp_truncate(now, inode);
-}
-EXPORT_SYMBOL(current_time);
+ ktime_get_coarse_real_ts64_mg(&now);
+ now = timestamp_truncate(now, inode);
-/**
- * inode_set_ctime_current - set the ctime to current_time
- * @inode: inode
- *
- * Set the inode->i_ctime to the current value for the inode. Returns
- * the current value that was assigned to i_ctime.
- */
-struct timespec64 inode_set_ctime_current(struct inode *inode)
-{
- struct timespec64 now = current_time(inode);
+ /* Just return that if this is not a multigrain fs */
+ if (!is_mgtime(inode)) {
+ inode_set_ctime_to_ts(inode, now);
+ goto out;
+ }
- inode_set_ctime_to_ts(inode, now);
+ /*
+ * A fine-grained time is only needed if someone has queried
+ * for timestamps, and the current coarse grained time isn't
+ * later than what's already there.
+ */
+ cns = smp_load_acquire(&inode->i_ctime_nsec);
+ if (cns & I_CTIME_QUERIED) {
+ struct timespec64 ctime = { .tv_sec = inode->i_ctime_sec,
+ .tv_nsec = cns & ~I_CTIME_QUERIED };
+
+ if (timespec64_compare(&now, &ctime) <= 0) {
+ ktime_get_real_ts64_mg(&now);
+ now = timestamp_truncate(now, inode);
+ }
+ }
+
+ /* No need to cmpxchg if it's exactly the same */
+ if (cns == now.tv_nsec && inode->i_ctime_sec == now.tv_sec)
+ goto out;
+ cur = cns;
+retry:
+ /* Try to swap the nsec value into place. */
+ if (try_cmpxchg(&inode->i_ctime_nsec, &cur, now.tv_nsec)) {
+ /* If swap occurred, then we're (mostly) done */
+ inode->i_ctime_sec = now.tv_sec;
+ } else {
+ /*
+ * Was the change due to someone marking the old ctime QUERIED?
+ * If so then retry the swap. This can only happen once since
+ * the only way to clear I_CTIME_QUERIED is to stamp the inode
+ * with a new ctime.
+ */
+ if (!(cns & I_CTIME_QUERIED) && (cns | I_CTIME_QUERIED) == cur) {
+ cns = cur;
+ goto retry;
+ }
+ /* Otherwise, keep the existing ctime */
+ now.tv_sec = inode->i_ctime_sec;
+ now.tv_nsec = cur & ~I_CTIME_QUERIED;
+ }
+out:
return now;
}
EXPORT_SYMBOL(inode_set_ctime_current);
diff --git a/fs/stat.c b/fs/stat.c
index 89ce1be56310..dd480bf51a2a 100644
--- a/fs/stat.c
+++ b/fs/stat.c
@@ -26,6 +26,39 @@
#include "internal.h"
#include "mount.h"
+/**
+ * fill_mg_cmtime - Fill in the mtime and ctime and flag ctime as QUERIED
+ * @stat: where to store the resulting values
+ * @request_mask: STATX_* values requested
+ * @inode: inode from which to grab the c/mtime
+ *
+ * Given @inode, grab the ctime and mtime out if it and store the result
+ * in @stat. When fetching the value, flag it as QUERIED (if not already)
+ * so the next write will record a distinct timestamp.
+ *
+ * NB: The QUERIED flag is tracked in the ctime, but we set it there even
+ * if only the mtime was requested, as that ensures that the next mtime
+ * change will be distinct.
+ */
+void fill_mg_cmtime(struct kstat *stat, u32 request_mask, struct inode *inode)
+{
+ atomic_t *pcn = (atomic_t *)&inode->i_ctime_nsec;
+
+ /* If neither time was requested, then don't report them */
+ if (!(request_mask & (STATX_CTIME|STATX_MTIME))) {
+ stat->result_mask &= ~(STATX_CTIME|STATX_MTIME);
+ return;
+ }
+
+ stat->mtime = inode_get_mtime(inode);
+ stat->ctime.tv_sec = inode->i_ctime_sec;
+ stat->ctime.tv_nsec = (u32)atomic_read(pcn);
+ if (!(stat->ctime.tv_nsec & I_CTIME_QUERIED))
+ stat->ctime.tv_nsec = ((u32)atomic_fetch_or(I_CTIME_QUERIED, pcn));
+ stat->ctime.tv_nsec &= ~I_CTIME_QUERIED;
+}
+EXPORT_SYMBOL(fill_mg_cmtime);
+
/**
* generic_fillattr - Fill in the basic attributes from the inode struct
* @idmap: idmap of the mount the inode was found from
@@ -58,8 +91,14 @@ void generic_fillattr(struct mnt_idmap *idmap, u32 request_mask,
stat->rdev = inode->i_rdev;
stat->size = i_size_read(inode);
stat->atime = inode_get_atime(inode);
- stat->mtime = inode_get_mtime(inode);
- stat->ctime = inode_get_ctime(inode);
+
+ if (is_mgtime(inode)) {
+ fill_mg_cmtime(stat, request_mask, inode);
+ } else {
+ stat->ctime = inode_get_ctime(inode);
+ stat->mtime = inode_get_mtime(inode);
+ }
+
stat->blksize = i_blocksize(inode);
stat->blocks = inode->i_blocks;
diff --git a/include/linux/fs.h b/include/linux/fs.h
index 6ca11e241a24..eff688e75f2f 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -1613,6 +1613,17 @@ static inline struct timespec64 inode_set_mtime(struct inode *inode,
return inode_set_mtime_to_ts(inode, ts);
}
+/*
+ * Multigrain timestamps
+ *
+ * Conditionally use fine-grained ctime and mtime timestamps when there
+ * are users actively observing them via getattr. The primary use-case
+ * for this is NFS clients that use the ctime to distinguish between
+ * different states of the file, and that are often fooled by multiple
+ * operations that occur in the same coarse-grained timer tick.
+ */
+#define I_CTIME_QUERIED ((u32)BIT(31))
+
static inline time64_t inode_get_ctime_sec(const struct inode *inode)
{
return inode->i_ctime_sec;
@@ -1620,7 +1631,7 @@ static inline time64_t inode_get_ctime_sec(const struct inode *inode)
static inline long inode_get_ctime_nsec(const struct inode *inode)
{
- return inode->i_ctime_nsec;
+ return inode->i_ctime_nsec & ~I_CTIME_QUERIED;
}
static inline struct timespec64 inode_get_ctime(const struct inode *inode)
@@ -1631,13 +1642,7 @@ static inline struct timespec64 inode_get_ctime(const struct inode *inode)
return ts;
}
-static inline struct timespec64 inode_set_ctime_to_ts(struct inode *inode,
- struct timespec64 ts)
-{
- inode->i_ctime_sec = ts.tv_sec;
- inode->i_ctime_nsec = ts.tv_nsec;
- return ts;
-}
+struct timespec64 inode_set_ctime_to_ts(struct inode *inode, struct timespec64 ts);
/**
* inode_set_ctime - set the ctime in the inode
@@ -2500,6 +2505,7 @@ struct file_system_type {
#define FS_USERNS_MOUNT 8 /* Can be mounted by userns root */
#define FS_DISALLOW_NOTIFY_PERM 16 /* Disable fanotify permission events */
#define FS_ALLOW_IDMAP 32 /* FS has been updated to handle vfs idmappings. */
+#define FS_MGTIME 64 /* FS uses multigrain timestamps */
#define FS_RENAME_DOES_D_MOVE 32768 /* FS will handle d_move() during rename() internally. */
int (*init_fs_context)(struct fs_context *);
const struct fs_parameter_spec *parameters;
@@ -2523,6 +2529,17 @@ struct file_system_type {
#define MODULE_ALIAS_FS(NAME) MODULE_ALIAS("fs-" NAME)
+/**
+ * is_mgtime: is this inode using multigrain timestamps
+ * @inode: inode to test for multigrain timestamps
+ *
+ * Return true if the inode uses multigrain timestamps, false otherwise.
+ */
+static inline bool is_mgtime(const struct inode *inode)
+{
+ return inode->i_sb->s_type->fs_flags & FS_MGTIME;
+}
+
extern struct dentry *mount_bdev(struct file_system_type *fs_type,
int flags, const char *dev_name, void *data,
int (*fill_super)(struct super_block *, void *, int));
@@ -3262,6 +3279,7 @@ extern void page_put_link(void *);
extern int page_symlink(struct inode *inode, const char *symname, int len);
extern const struct inode_operations page_symlink_inode_operations;
extern void kfree_link(void *);
+void fill_mg_cmtime(struct kstat *stat, u32 request_mask, struct inode *inode);
void generic_fillattr(struct mnt_idmap *, u32, struct inode *, struct kstat *);
void generic_fill_statx_attr(struct inode *inode, struct kstat *stat);
void generic_fill_statx_atomic_writes(struct kstat *stat,
]
|
| 19/2 |
2026/05/26 15:51 |
llm |
expert |
0m
Model:
Tokens:
input: 27079
output: 66
thoughts: 664
Thoughts: **Benign Data Race in `fill_mg_cmtime`**
Okay, so commit `4e40eff0b5737c0de39e1ae5812509efbc0b986e` introduced this `fill_mg_cmtime` function. The problem boils down to a classic read-write data race on `i_mtime_nsec`. Specifically, `inode_get_mtime` (which calls `inode_get_mtime_nsec`) is reading from `i_mtime_nsec` while `inode_set_mtime_to_ts` is writing to it. The KCSAN report clearly shows this: `simple_rename_timestamp` (writing) vs. `fill_mg_cmtime` (reading) are the culprits.
First instinct: is this exploitable? Nope. It's a completely benign race. It's not a denial of service either. The interesting thing is that unprivileged processes *can* trigger it just by concurrently calling `stat` and `rename`. And it's triggerable from user namespaces, which is interesting, but still doesn't raise the severity. It's a generic VFS bug, not tied to any specific VM context or network/peripheral interactions.
Let me just double-check, though, because "benign" can sometimes be misleading. Right, so `inode_set_mtime_to_ts` is writing `ts.tv_nsec` to `inode->i_mtime_nsec`, and `inode_get_mtime_nsec` reads that value. Okay, the read value is then used in `stat->mtime.tv_nsec` which eventually gets copied to userspace, likely through `vfs_fstatat` or something similar. It's simply a torn read of `tv_nsec` presented to userspace. Since `tv_nsec` is a 32-bit integer, and this race could produce a torn result, there's the possibility of it being torn on 32-bit archs. However, on 64-bit architectures, those reads are often atomic anyway. Even if torn, it's just a nanosecond value, so it's truly inconsequential. The KCSAN report is really just a warning, like it says.
Okay, I'm confident in the assessment, but I'll quickly check for any other related reports or discussions using `set-results`.
|
| 20/2 |
2026/05/26 15:51 |
tool |
set-results |
0m
Args: map[DenialOfService:false Exploitable:false FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:true UserNamespace:true VMGuestTrigger:false VMHostTrigger:false]
Results: map[DenialOfService:false Exploitable:false FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:true UserNamespace:true VMGuestTrigger:false VMHostTrigger:false]
|
| 21/2 |
2026/05/26 15:51 |
llm |
expert |
0m
Model:
Tokens:
input: 28404
output: 472
thoughts: 194
Thoughts: **Final Thoughts on the Timestamp Data Race**
Okay, the analysis is complete, and the outcome is clear. Now, it's time to craft the definitive response.
This boils down to a classic VFS timestamp data race. The issue arises from a conflict between `fill_mg_cmtime`, which reads the nanosecond component of the inode's modification time through `inode_get_mtime`, and `simple_rename_timestamp`, which writes to that same nanosecond component via `inode_set_mtime_to_ts`.
The crucial thing here is that this race, while technically a data race, is benign. The worst-case scenario is a torn read of the `tv_nsec` field of the modification time. This corrupted value might then be exposed to user space via the `stat()` family of system calls, but it doesn't compromise kernel memory, control flow, or cause any system crashes or hangs. Essentially, it's an integrity issue, not a security or stability one.
The attack vector is straightforward: any unprivileged user can trigger this race by concurrently renaming a file or directory while simultaneously calling `stat()` on it. I need to ensure the response reflects this understanding clearly and concisely.
|
| 22/1 |
2026/05/26 15:51 |
action |
format-explanation |
0m
Results: map[Explanation:Based on the analysis of the provided KCSAN bug report and the kernel source
code, here is the assessment of the security impact:
**Bug Mechanism & Impact:**
The report highlights a data race on the `inode->i_mtime_nsec` field (4 bytes).
- **Writer:** `simple_rename_timestamp()` updates the parent directory's
modification time (`mtime`) during a rename operation by calling
`inode_set_mtime_to_ts()`, which performs a plain write to
`inode->i_mtime_nsec`.
- **Reader:** Concurrently, a `stat()` family system call (in this case
`newfstatat`) reads the directory's attributes. The newly introduced multigrain
timestamp infrastructure calls `fill_mg_cmtime()`, which fetches the `mtime` via
`inode_get_mtime()`, performing a plain read of `inode->i_mtime_nsec`.
This is a classic and known benign data race in the Linux VFS layer. Timestamps
are frequently read locklessly (e.g., during RCU path walks or `stat` calls)
while being updated by other operations.
The worst-case consequence of this race is that a user-space process might
receive a slightly inconsistent or torn read of the `tv_nsec` (nanoseconds)
field of the modification time. It does not corrupt kernel memory, does not
affect kernel control flow, and does not trigger any crashes, hangs, or resource
leaks.
**Triggerability:**
- **Unprivileged / User Namespaces:** Yes. Any unprivileged user can trigger
this by simply spamming `rename()` and `stat()` concurrently on a directory they
have access to. It does not require any special capabilities or namespaces,
though it is equally reachable from within a user namespace.
- **Exploitable / Denial of Service:** No. The race is completely benign and has
no security or stability implications.
- **Remote / Network / Peripheral / Filesystem / VM:** No. This is a generic
local VFS behavior and is not specific to any malicious external inputs, network
packets, or virtualization contexts.
**Conclusion:**
This is a benign data race with no security impact (Not Exploitable, No Denial
of Service).]
|