| 0/0 |
2026/05/31 13:16 |
flow |
assessment-security |
50m
Results: map[DenialOfService:false Explanation:**Bug Analysis & Security Impact**
The reported bug is a KCSAN (Kernel Concurrency Sanitizer) data-race between a
timer interrupt updating the system time and a user task reading the time to
update a file's timestamp.
1. **The Write Path**: Triggered by the timer interrupt (`tick_nohz_handler` ->
`update_wall_time` -> `timekeeping_update_from_shadow`). The write operation is
a `memcpy` that updates the global `timekeeper` structure. Crucially, this write
is enclosed within a sequence lock (`write_seqcount_begin` and
`write_seqcount_end`).
2. **The Read Path**: Triggered by a standard file operation (`sendfile64` ->
`file_update_time` -> `ktime_get_coarse_real_ts64_mg`). The read operation
fetches the coarse time (`tk->coarse_nsec` and `tk->xtime_sec`). This read is
enclosed within a sequence lock reader loop (`read_seqcount_begin` and
`read_seqcount_retry`).
**Reasoning:**
This is a textbook example of a benign data race within a seqlock loop. KCSAN
flags this because the reader performs plain C memory accesses (without
`READ_ONCE()` or `data_race()` annotations) while the writer concurrently
modifies the data via `memcpy`.
However, the seqlock mechanism guarantees safety:
* If the read is torn or fetches inconsistent data due to the concurrent write,
the sequence counter will be detected as changed by `read_seqcount_retry()`.
* The loop will then discard the garbage values and retry the read.
* The variables being read are simple integers (`u64` and `u32`). They are not
used as pointers, array indices, or in any arithmetic that could cause a CPU
exception (like division by zero) *before* the sequence counter is validated.
**Conclusion:**
Because the inconsistent data is safely discarded and cannot cause a fault prior
to the retry check, this data race has **no security impact**. It is not
exploitable for privilege escalation or memory corruption, nor can it cause a
Denial of Service (DoS) in production kernels.
The code paths are reachable by any unprivileged user (e.g., by simply reading
time or writing to files), meaning it is accessible from unprivileged processes
and within user namespaces, but it poses no risk. Exploitable:false FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:true UserNamespace:true VMGuestTrigger:false VMHostTrigger:false]
|
| 1/1 |
2026/05/31 13:16 |
action |
syz-repro-to-c-repro |
0m
Results:
|
| 2/1 |
2026/05/31 13:16 |
action |
kernel-checkouter |
1m
Results: map[KernelSrc:/app/workdir/cache/src/e19ffe1746501f57f2de25d64663ee42c654bce7]
|
| 3/1 |
2026/05/31 13:17 |
action |
kernel-builder |
21m
Results: map[KernelObj:/app/workdir/cache/build/4a2c7deb8a438f0153f4907b92589114e67ed632]
|
| 4/1 |
2026/05/31 13:39 |
action |
codesearch-prepare |
15m
Results: map[Index:codesearch-index]
|
| 5/1 |
2026/05/31 13:54 |
agent |
expert |
12m
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 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:
==================================================================
BUG: KCSAN: data-race in ktime_get_coarse_real_ts64_mg / timekeeping_update_from_shadow
write to 0xffffffff893aa488 of 304 bytes by interrupt on cpu 0:
timekeeping_update_from_shadow+0x40d/0x440 kernel/time/timekeeping.c:829
__timekeeping_advance+0xa5d/0xc10 kernel/time/timekeeping.c:2532
timekeeping_advance kernel/time/timekeeping.c:2540 [inline]
update_wall_time+0x21/0x50 kernel/time/timekeeping.c:2550
tick_do_update_jiffies64+0x169/0x1c0 kernel/time/tick-sched.c:149
tick_sched_do_timer kernel/time/tick-sched.c:253 [inline]
tick_nohz_handler+0x8d/0x3d0 kernel/time/tick-sched.c:312
__run_hrtimer kernel/time/hrtimer.c:1930 [inline]
__hrtimer_run_queues+0x276/0x4f0 kernel/time/hrtimer.c:1994
hrtimer_interrupt+0x261/0x850 kernel/time/hrtimer.c:2113
local_apic_timer_interrupt arch/x86/kernel/apic/apic.c:1050 [inline]
__sysvec_apic_timer_interrupt+0x5f/0x1c0 arch/x86/kernel/apic/apic.c:1067
instr_sysvec_apic_timer_interrupt arch/x86/kernel/apic/apic.c:1061 [inline]
sysvec_apic_timer_interrupt+0x6f/0x80 arch/x86/kernel/apic/apic.c:1061
asm_sysvec_apic_timer_interrupt+0x1a/0x20 arch/x86/include/asm/idtentry.h:697
kcsan_setup_watchpoint+0x404/0x410 kernel/kcsan/core.c:705
check_access kernel/kcsan/core.c:753 [inline]
__tsan_memset+0x1d9/0x1f0 kernel/kcsan/core.c:1335
cred_has_capability+0x40/0x2a0 security/selinux/hooks.c:1644
selinux_capable+0x31/0x40 security/selinux/hooks.c:2189
security_capable+0x7b/0x90 security/security.c:660
ns_capable_common kernel/capability.c:342 [inline]
ns_capable+0x4c/0xb0 kernel/capability.c:363
__netlink_ns_capable net/netlink/af_netlink.c:852 [inline]
netlink_ns_capable net/netlink/af_netlink.c:869 [inline]
netlink_net_capable+0xaa/0xc0 net/netlink/af_netlink.c:900
rtnetlink_rcv_msg+0x14c/0x720 net/core/rtnetlink.c:6924
netlink_rcv_skb+0x123/0x220 net/netlink/af_netlink.c:2550
rtnetlink_rcv+0x1c/0x30 net/core/rtnetlink.c:7024
netlink_unicast_kernel net/netlink/af_netlink.c:1318 [inline]
netlink_unicast+0x5a8/0x680 net/netlink/af_netlink.c:1344
netlink_sendmsg+0x5c8/0x6f0 net/netlink/af_netlink.c:1894
sock_sendmsg_nosec net/socket.c:787 [inline]
__sock_sendmsg net/socket.c:802 [inline]
__sys_sendto+0x42e/0x490 net/socket.c:2265
__do_sys_sendto net/socket.c:2272 [inline]
__se_sys_sendto net/socket.c:2268 [inline]
__x64_sys_sendto+0x76/0x90 net/socket.c:2268
x64_sys_call+0x2d35/0x3020 arch/x86/include/generated/asm/syscalls_64.h:45
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 0xffffffff893aa4f8 of 4 bytes by task 12748 on cpu 1:
tk_xtime_coarse kernel/time/timekeeping.c:212 [inline]
ktime_get_coarse_real_ts64_mg+0x89/0x1a0 kernel/time/timekeeping.c:2606
current_time+0x41/0x240 fs/inode.c:2376
need_cmtime_update fs/inode.c:2398 [inline]
file_update_time_flags+0x83/0x2e0 fs/inode.c:2419
file_update_time+0x17/0x20 fs/inode.c:2449
shmem_file_write_iter+0x9c/0xf0 mm/shmem.c:3475
iter_file_splice_write+0x6c4/0xa80 fs/splice.c:736
do_splice_from fs/splice.c:936 [inline]
direct_splice_actor+0x156/0x2a0 fs/splice.c:1159
splice_direct_to_actor+0x311/0x670 fs/splice.c:1103
do_splice_direct_actor fs/splice.c:1202 [inline]
do_splice_direct+0x119/0x1a0 fs/splice.c:1228
do_sendfile+0x382/0x650 fs/read_write.c:1372
__do_sys_sendfile64 fs/read_write.c:1433 [inline]
__se_sys_sendfile64 fs/read_write.c:1419 [inline]
__x64_sys_sendfile64+0x105/0x150 fs/read_write.c:1419
x64_sys_call+0x2dc4/0x3020 arch/x86/include/generated/asm/syscalls_64.h:41
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: 0x2f5594d9 -> 0x2fee2b59
Reported by Kernel Concurrency Sanitizer on:
CPU: 1 UID: 0 PID: 12748 Comm: syz.7.2273 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: **Bug Analysis & Security Impact**
The reported bug is a KCSAN (Kernel Concurrency Sanitizer) data-race between a timer interrupt updating the system time and a user task reading the time to update a file's timestamp.
1. **The Write Path**: Triggered by the timer interrupt (`tick_nohz_handler` -> `update_wall_time` -> `timekeeping_update_from_shadow`). The write operation is a `memcpy` that updates the global `timekeeper` structure. Crucially, this write is enclosed within a sequence lock (`write_seqcount_begin` and `write_seqcount_end`).
2. **The Read Path**: Triggered by a standard file operation (`sendfile64` -> `file_update_time` -> `ktime_get_coarse_real_ts64_mg`). The read operation fetches the coarse time (`tk->coarse_nsec` and `tk->xtime_sec`). This read is enclosed within a sequence lock reader loop (`read_seqcount_begin` and `read_seqcount_retry`).
**Reasoning:**
This is a textbook example of a benign data race within a seqlock loop. KCSAN flags this because the reader performs plain C memory accesses (without `READ_ONCE()` or `data_race()` annotations) while the writer concurrently modifies the data via `memcpy`.
However, the seqlock mechanism guarantees safety:
* If the read is torn or fetches inconsistent data due to the concurrent write, the sequence counter will be detected as changed by `read_seqcount_retry()`.
* The loop will then discard the garbage values and retry the read.
* The variables being read are simple integers (`u64` and `u32`). They are not used as pointers, array indices, or in any arithmetic that could cause a CPU exception (like division by zero) *before* the sequence counter is validated.
**Conclusion:**
Because the inconsistent data is safely discarded and cannot cause a fault prior to the retry check, this data race has **no security impact**. It is not exploitable for privilege escalation or memory corruption, nor can it cause a Denial of Service (DoS) in production kernels.
The code paths are reachable by any unprivileged user (e.g., by simply reading time or writing to files), meaning it is accessible from unprivileged processes and within user namespaces, but it poses no risk.
|
| 6/2 |
2026/05/31 13:54 |
llm |
expert |
0m
Model:
Tokens:
input: 7498
output: 47
thoughts: 372
Thoughts: **Analyzing a KCSAN Data-Race in Timekeeping**
Okay, so I've got a KCSAN report here, flagging a data-race. The crux of the issue appears to be around `ktime_get_coarse_real_ts64_mg` and `timekeeping_update_from_shadow`. This is interesting; I know these functions and the timekeeping subsystem pretty well. The write side of this race is happening in `timekeeping_update_from_shadow` on CPU 0. I see the call stack traces back to `update_wall_time`, so I know exactly what's going on; the shadow variable is being updated, which makes sense. The key is what's on the read side.
The read is happening on CPU 1, within `ktime_get_coarse_real_ts64_mg`. It's reading the timestamp used to derive 'coarse' time. The stack trace here shows it's called from `current_time` and subsequently through `file_update_time_flags`. So we have the inode subsystem using this `ktime` value for updating timestamps. It seems a data-race might occur when another CPU is writing to the same structure or variable that CPU1 is trying to read.
My next step is to examine `kernel/time/timekeeping.c` and specifically lines around 2606 and 829. I'll focus on how `ktime_get_coarse_real_ts64_mg` accesses and `timekeeping_update_from_shadow` updates the timekeeping data structures. I need to understand if there is a synchronization issue. If this is a real race, it's going to manifest as a subtle, but potentially significant, timing issue in the file system or other consumers of time. This needs to be resolved quickly.
|
| 7/2 |
2026/05/31 13:54 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:kernel/time/timekeeping.c Name:ktime_get_coarse_real_ts64_mg]
Results: map[SourceCode:2588: /**
2589: * ktime_get_coarse_real_ts64_mg - return latter of coarse grained time or floor
2590: * @ts: timespec64 to be filled
2591: *
2592: * Fetch the global mg_floor value, convert it to realtime and compare it
2593: * to the current coarse-grained time. Fill @ts with whichever is
2594: * latest. Note that this is a filesystem-specific interface and should be
2595: * avoided outside of that context.
2596: */
2597: void ktime_get_coarse_real_ts64_mg(struct timespec64 *ts)
2598: {
2599: struct timekeeper *tk = &tk_core.timekeeper;
2600: u64 floor = atomic64_read(&mg_floor);
2601: ktime_t f_real, offset, coarse;
2602: unsigned int seq;
2603:
2604: do {
2605: seq = read_seqcount_begin(&tk_core.seq);
2606: *ts = tk_xtime_coarse(tk);
2607: offset = tk_core.timekeeper.offs_real;
2608: } while (read_seqcount_retry(&tk_core.seq, seq));
2609:
2610: coarse = timespec64_to_ktime(*ts);
2611: f_real = ktime_add(floor, offset);
2612: if (ktime_after(f_real, coarse))
2613: *ts = ktime_to_timespec64(f_real);
2614: }
SourceFile:kernel/time/timekeeping.c]
|
| 8/2 |
2026/05/31 13:54 |
llm |
expert |
0m
Model:
Tokens:
input: 8818
output: 38
thoughts: 47
Thoughts: Okay, let's see. My mind's buzzing a bit here...
**Deconstructing a Concurrency Bottleneck**
Hold on a second... I'm noticing something that piques my interest. `tk_xtime_coarse(tk)`... that's being called inside this `read_seqcount_begin` / `read_seqcount_retry` loop. That's unusual; I need to understand that. I need to dig into this specific function call now. Right, let's trace through `tk_xtime_coarse` and see what's happening in there. What are its dependencies? What kind of overhead are we talking about? That's definitely a point to explore.
|
| 9/2 |
2026/05/31 13:54 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:kernel/time/timekeeping.c Name:tk_xtime_coarse]
Results: map[SourceCode: 207: static inline struct timespec64 tk_xtime_coarse(const struct timekeeper *tk)
208: {
209: struct timespec64 ts;
210:
211: ts.tv_sec = tk->xtime_sec;
212: ts.tv_nsec = tk->coarse_nsec;
213: return ts;
214: }
SourceFile:kernel/time/timekeeping.c]
|
| 10/2 |
2026/05/31 13:54 |
llm |
expert |
0m
Model:
Tokens:
input: 9198
output: 40
thoughts: 129
Thoughts: Here's my analysis of this code snippet, distilled into a first-person perspective:
**Debugging Timekeeping Issues**
Okay, so I'm looking at a timing issue, specifically at line 212 within the `timekeeping.c` file. The report flags `ts.tv_nsec = tk->coarse_nsec;` as the read location. The key data I see is the system trying to read 4 bytes to 0xffffffff893aa4f8. The report indicates this read is happening by task 12748 on CPU 1. This ties directly to the kernel function `tk_xtime_coarse`, which is inlined at the problematic line.
Now, I need to understand what's happening *before* this read. The report gives me the write, specifically at `timekeeping_update_from_shadow+0x40d/0x440 kernel/time/timekeeping.c:829`. That's the *update* function. It would appear a shadow variable is being updated, which is then read in that line 212 call.
My initial thought is to verify if there are any race conditions between the write and the read. It's plausible a synchronization issue exists here that is causing inconsistent time readings. I should now focus on the `timekeeping_update_from_shadow` function and trace how `tk->coarse_nsec` is modified and whether the update is properly synchronized before being read. Perhaps the shadow variable isn't fully updated, or there's a missed lock. This is where I'll dive in next.
|
| 11/2 |
2026/05/31 13:54 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:kernel/time/timekeeping.c Name:timekeeping_update_from_shadow]
Results: map[SourceCode: 783: static void timekeeping_update_from_shadow(struct tk_data *tkd, unsigned int action)
784: {
785: struct timekeeper *tk = &tkd->shadow_timekeeper;
786:
787: lockdep_assert_held(&tkd->lock);
788:
789: /*
790: * Block out readers before running the updates below because that
791: * updates VDSO and other time related infrastructure. Not blocking
792: * the readers might let a reader see time going backwards when
793: * reading from the VDSO after the VDSO update and then reading in
794: * the kernel from the timekeeper before that got updated.
795: */
796: write_seqcount_begin(&tkd->seq);
797:
798: if (action & TK_CLEAR_NTP) {
799: tk->ntp_error = 0;
800: ntp_clear(tk->id);
801: }
802:
803: tk_update_leap_state(tk);
804: tk_update_ktime_data(tk);
805: tk->tkr_mono.base_real = tk->tkr_mono.base + tk->offs_real;
806:
807: if (tk->id == TIMEKEEPER_CORE) {
808: tk_update_ns_to_cyc(tk, &tkd->timekeeper);
809: update_vsyscall(tk);
810: update_pvclock_gtod(tk, action & TK_CLOCK_WAS_SET);
811:
812: update_fast_timekeeper(&tk->tkr_mono, &tk_fast_mono);
813: update_fast_timekeeper(&tk->tkr_raw, &tk_fast_raw);
814: } else if (tk_is_aux(tk)) {
815: vdso_time_update_aux(tk);
816: }
817:
818: if (action & TK_CLOCK_WAS_SET)
819: tk->clock_was_set_seq++;
820:
821: /*
822: * Update the real timekeeper.
823: *
824: * We could avoid this memcpy() by switching pointers, but that has
825: * the downside that the reader side does not longer benefit from
826: * the cacheline optimized data layout of the timekeeper and requires
827: * another indirection.
828: */
829: memcpy(&tkd->timekeeper, tk, sizeof(*tk));
830: write_seqcount_end(&tkd->seq);
831: }
SourceFile:kernel/time/timekeeping.c]
|
| 12/2 |
2026/05/31 13:54 |
llm |
expert |
0m
Model:
Tokens:
input: 10526
output: 34
thoughts: 1050
Thoughts: **Analyzing a Potential Seqlock Data Race**
Okay, so I'm looking at this code and immediately see a classic seqlock pattern. We've got `memcpy` writing to `tkd->timekeeper` and a reader fetching data, specifically `ts.tv_nsec = tk->coarse_nsec`. KCSAN is flagging this, which makes sense; this is a textbook data race, a plain read and a plain write. Now, `memcpy` isn't *atomic*, and that's the heart of the issue.
Right, the point is, in a seqlock, the readers *should* be using `READ_ONCE` or something similar, at least according to KCSAN to avoid compiler optimizations if it is reading data that can be concurrently modified. The absence here causes a KCSAN complaint.
But, is it a *real* bug? In a seqlock, data races in the read loop are usually benign, unless the compiler does something truly outlandish. But here, the access is just a simple assignment, so the value will be discarded by retry.
Let's dig deeper and see if there's any way this could lead to exploitation or a denial-of-service. The reader grabs `tk->coarse_nsec` and `tk->xtime_sec`. If there's a tear in the read, `ts` will be garbage. Then `timespec64_to_ktime` is called, which is just a bunch of arithmetic. Then it checks if the result is after the time. That math is *outside* the retry loop, right? But, the critical values of `*ts` and `offset` are checked by the `read_seqcount_retry` call, so the values are consistent.
Ah, but could the garbage value cause problems *before* the retry? No, the code just stores it in `*ts`. Then `read_seqcount_retry` checks the sequence number and restarts. The only way this could be bad is if the read itself caused a fault, like a bad pointer dereference. But, we're just reading integers from a static location! Or if the compiler optimized in a way that moved the read outside the loop, but memory barriers in the `read_seqcount_begin`/`retry` functions will stop that from happening.
So, yeah, it looks like a completely benign data race. KCSAN is just being its usual, hyper-vigilant self. This type of thing is all over the kernel, and the standard fix is usually to sprinkle in `data_race()` annotations or use `READ_ONCE()`.
Just to be absolutely sure, let's verify that this *cannot* cause a crash. `tk_xtime_coarse` reads `tk->xtime_sec` and `tk->coarse_nsec`. These are `time64_t` and `u32` (or similar sized types) . The write is a `memcpy` of a larger size.
Given that it's a benign data race, this isn't exploitable, and it doesn't cause a Denial of Service. It looks safe. Let's make sure the types are what I think they are, and the loop structure is what I think it is.
|
| 13/2 |
2026/05/31 13:55 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:kernel/time/timekeeping.c Name:timekeeper]
Results: map[SourceCode: 61: /**
62: * struct timekeeper - Structure holding internal timekeeping values.
63: * @tkr_mono: The readout base structure for CLOCK_MONOTONIC
64: * @xtime_sec: Current CLOCK_REALTIME time in seconds
65: * @ktime_sec: Current CLOCK_MONOTONIC time in seconds
66: * @wall_to_monotonic: CLOCK_REALTIME to CLOCK_MONOTONIC offset
67: * @offs_real: Offset clock monotonic -> clock realtime
68: * @offs_boot: Offset clock monotonic -> clock boottime
69: * @offs_tai: Offset clock monotonic -> clock tai
70: * @offs_aux: Offset clock monotonic -> clock AUX
71: * @coarse_nsec: The nanoseconds part for coarse time getters
72: * @id: The timekeeper ID
73: * @tkr_raw: The readout base structure for CLOCK_MONOTONIC_RAW
74: * @raw_sec: CLOCK_MONOTONIC_RAW time in seconds
75: * @cs_id: The ID of the current clocksource
76: * @cs_ns_to_cyc_mult: Multiplicator for nanoseconds to cycles conversion
77: * @cs_ns_to_cyc_shift: Shift value for nanoseconds to cycles conversion
78: * @cs_ns_to_cyc_maxns: Maximum nanoseconds to cyles conversion range
79: * @clock_was_set_seq: The sequence number of clock was set events
80: * @cs_was_changed_seq: The sequence number of clocksource change events
81: * @clock_valid: Indicator for valid clock
82: * @monotonic_to_boot: CLOCK_MONOTONIC to CLOCK_BOOTTIME offset
83: * @monotonic_to_aux: CLOCK_MONOTONIC to CLOCK_AUX offset
84: * @cycle_interval: Number of clock cycles in one NTP interval
85: * @xtime_interval: Number of clock shifted nano seconds in one NTP
86: * interval.
87: * @xtime_remainder: Shifted nano seconds left over when rounding
88: * @cycle_interval
89: * @raw_interval: Shifted raw nano seconds accumulated per NTP interval.
90: * @next_leap_ktime: CLOCK_MONOTONIC time value of a pending leap-second
91: * @ntp_tick: The ntp_tick_length() value currently being
92: * used. This cached copy ensures we consistently
93: * apply the tick length for an entire tick, as
94: * ntp_tick_length may change mid-tick, and we don't
95: * want to apply that new value to the tick in
96: * progress.
97: * @ntp_error: Difference between accumulated time and NTP time in ntp
98: * shifted nano seconds.
99: * @ntp_error_shift: Shift conversion between clock shifted nano seconds and
100: * ntp shifted nano seconds.
101: * @ntp_err_mult: Multiplication factor for scaled math conversion
102: * @skip_second_overflow: Flag used to avoid updating NTP twice with same second
103: * @tai_offset: The current UTC to TAI offset in seconds
104: *
105: * Note: For timespec(64) based interfaces wall_to_monotonic is what
106: * we need to add to xtime (or xtime corrected for sub jiffy times)
107: * to get to monotonic time. Monotonic is pegged at zero at system
108: * boot time, so wall_to_monotonic will be negative, however, we will
109: * ALWAYS keep the tv_nsec part positive so we can use the usual
110: * normalization.
111: *
112: * wall_to_monotonic is moved after resume from suspend for the
113: * monotonic time not to jump. We need to add total_sleep_time to
114: * wall_to_monotonic to get the real boot based time offset.
115: *
116: * wall_to_monotonic is no longer the boot time, getboottime must be
117: * used instead.
118: *
119: * @monotonic_to_boottime is a timespec64 representation of @offs_boot to
120: * accelerate the VDSO update for CLOCK_BOOTTIME.
121: *
122: * @offs_aux is used by the auxiliary timekeepers which do not utilize any
123: * of the regular timekeeper offset fields.
124: *
125: * @monotonic_to_aux is a timespec64 representation of @offs_aux to
126: * accelerate the VDSO update for CLOCK_AUX.
127: *
128: * The cacheline ordering of the structure is optimized for in kernel usage of
129: * the ktime_get() and ktime_get_ts64() family of time accessors. Struct
130: * timekeeper is prepended in the core timekeeping code with a sequence count,
131: * which results in the following cacheline layout:
132: *
133: * 0: seqcount, tkr_mono
134: * 1: xtime_sec ... id
135: * 2: tkr_raw, raw_sec
136: * 3,4: Internal variables
137: *
138: * Cacheline 0,1 contain the data which is used for accessing
139: * CLOCK_MONOTONIC/REALTIME/BOOTTIME/TAI, while cacheline 2 contains the
140: * data for accessing CLOCK_MONOTONIC_RAW. Cacheline 3,4 are internal
141: * variables which are only accessed during timekeeper updates once per
142: * tick.
143: */
144: struct timekeeper {
145: /* Cacheline 0 (together with prepended seqcount of timekeeper core): */
146: struct tk_read_base tkr_mono;
147:
148: /* Cacheline 1: */
149: u64 xtime_sec;
150: unsigned long ktime_sec;
151: struct timespec64 wall_to_monotonic;
152: ktime_t offs_real;
153: ktime_t offs_boot;
154: union {
155: ktime_t offs_tai;
156: ktime_t offs_aux;
157: };
158: u32 coarse_nsec;
159: enum timekeeper_ids id;
160:
161: /* Cacheline 2: */
162: struct tk_read_base tkr_raw;
163: u64 raw_sec;
164:
165: /* Cachline 3 and 4 (timekeeping internal variables): */
166: enum clocksource_ids cs_id;
167: u32 cs_ns_to_cyc_mult;
168: u32 cs_ns_to_cyc_shift;
169: u64 cs_ns_to_cyc_maxns;
170: unsigned int clock_was_set_seq;
171: u8 cs_was_changed_seq;
172: u8 clock_valid;
173:
174: union {
175: struct timespec64 monotonic_to_boot;
176: struct timespec64 monotonic_to_aux;
177: };
178:
179: u64 cycle_interval;
180: u64 xtime_interval;
181: s64 xtime_remainder;
182: u64 raw_interval;
183:
184: ktime_t next_leap_ktime;
185: u64 ntp_tick;
186: s64 ntp_error;
187: u32 ntp_error_shift;
188: u32 ntp_err_mult;
189: u32 skip_second_overflow;
190: s32 tai_offset;
191: };
SourceFile:include/linux/timekeeper_internal.h]
|
| 14/2 |
2026/05/31 13:55 |
llm |
expert |
0m
Model:
Tokens:
input: 14713
output: 37
thoughts: 764
Thoughts: **Benign Data Race in Timekeeping: A Deep Dive**
Okay, so I'm looking at this code and KCSAN is flagging a potential data race. Specifically, it's the `memcpy` write to `tkd->timekeeper` versus the read of `ts.tv_nsec = tk->coarse_nsec`. `coarse_nsec` is a `u32`. This read is wrapped in a standard seqlock loop using `read_seqcount_begin` and `read_seqcount_retry`, which is pretty common.
I know this type of thing is often benign, but I need to be sure. My first thought is, is there any possibility of exploitation or a denial-of-service? The read value is discarded if the sequence number changes, so torn reads just become garbage data. The seqlock retry will prevent using stale data. The question is, could that garbage value cause an issue before the retry?
Let's break down the code execution: We read the sequence number, grab `tk->xtime_sec` and `tk->coarse_nsec`, and then read `offset`. Critically, there are no pointer dereferences based on the read values, no array indexing, and nothing to do a division by zero. So, even if the `u32` value is complete garbage, it won't trigger a fault before the retry. The retry loop guarantees consistency of the values used after the loop completes. The values are guaranteed to be consistent.
Therefore, even if the value is garbage, it's a benign data race. It's not exploitable, and it won't lead to a DoS.
Then, I started to think about how this race might be triggered. It's not some obscure corner case. The read comes from `sendfile64` which updates file times via `file_update_time` and `current_time`, which calls `ktime_get_coarse_real_ts64_mg`, meaning it's triggered by unprivileged processes and user namespaces. The write occurs from `timekeeping_update_from_shadow` triggered by the timer interrupt. This isn't specifically VM-guest-related, network-related, or a peripheral-specific trigger. It can happen during just about any syscall that modifies file times.
Just to be absolutely sure, I have to check if the `read_seqcount_retry` can somehow fail to catch the race. It's designed to catch concurrent writes using `write_seqcount_begin` and `write_seqcount_end`. The issue is, KCSAN is flagging it because there aren't `data_race()` or `READ_ONCE()` annotations. The seqlock reader is supposed to be annotated to avoid the KCSAN warnings and potential compiler optimizations. However, with simple integer assignments, it's pretty benign in practice. It's a textbook benign data race.
Now, let me quickly see if there's been a recent commit that addresses this.
|
| 15/2 |
2026/05/31 13:55 |
tool |
git-log |
10m
Error:
Args: map[CodeRegexp:ktime_get_coarse_real_ts64_mg Count:10]
Results:
|
| 16/2 |
2026/05/31 14:05 |
llm |
expert |
0m
Model:
Tokens:
input: 16189
output: 51
thoughts: 8
Thoughts: Okay, here's my attempt at summarizing the thought process, keeping in mind I'm speaking to a fellow expert:
**Let's Explore PathPrefix**
Alright, let's dive into this. My initial thought is to tackle this with `PathPrefix`. It feels like a natural starting point, doesn't it? Given my experience with routing and URL manipulation, this seems like the most likely first avenue to explore. I'm going to start mentally sketching out how I'd approach implementing this with `PathPrefix` and see if it can be adapted easily.
|
| 17/2 |
2026/05/31 14:05 |
tool |
git-log |
0m
Args: map[CodeRegexp:ktime_get_coarse_real_ts64_mg Count:10 PathPrefix:kernel/time/timekeeping.c]
Results: map[Output:ee3283c608df timekeeping: Add interfaces for handling timestamps with a floor value
70c8fd00a9bd timekeeping: Add interfaces for handling timestamps with a floor value
]
|
| 18/2 |
2026/05/31 14:05 |
llm |
expert |
0m
Model:
Tokens:
input: 16427
output: 25
thoughts: 18
Thoughts: Okay, here's my interpretation of what's going through my mind, delivered in the first person, assuming I'm an expert in this field:
**Initial Assessment of Commit `ee3283c608df`**
Alright, let's dive into commit `ee3283c608df`. My first instinct is to see what the commit *itself* says, but I'm going to quickly skim the surrounding context. Based on the commit ID alone, I'm already mentally running through potential areas where changes are likely to have occurred, based on the patterns I've seen in our repository. Is this a feature addition? A bug fix? Refactoring? Optimization? Or something else entirely? A full SHA like this usually signals a more significant change, or at least one that required careful attention during the creation and merging. I want to build a quick hypothesis about its purpose so that I can process it more efficiently. I'll be looking for clues in the commit message, the changed files, and the code diffs themselves. I'm already forming questions in my mind, such as whether it relates to a specific module, performance bottleneck, or any recent discussions we've had regarding technical debt. Let's see what we're working with here.
|
| 19/2 |
2026/05/31 14:05 |
tool |
git-show |
0m
Args:
Results: map[Output:commit ee3283c608dfa21251b0821d7bb198c7ae3189f6
Author: Jeff Layton <jlayton@kernel.org>
Date: Wed Oct 2 17:27:16 2024 -0400
timekeeping: Add interfaces for handling timestamps with a floor value
Multigrain timestamps allow the kernel to use fine-grained timestamps when
an inode's attributes is being actively observed via ->getattr(). With
this support, it's possible for a file to get a fine-grained timestamp, and
another modified after it to get a coarse-grained stamp that is earlier
than the fine-grained time. If this happens then the files can appear to
have been modified in reverse order, which breaks VFS ordering guarantees
[1].
To prevent this, maintain a floor value for multigrain timestamps.
Whenever a fine-grained timestamp is handed out, record it, and when later
coarse-grained stamps are handed out, ensure they are not earlier than that
value. If the coarse-grained timestamp is earlier than the fine-grained
floor, return the floor value instead.
Add a static singleton atomic64_t into timekeeper.c that is used to keep
track of the latest fine-grained time ever handed out. This is tracked as a
monotonic ktime_t value to ensure that it isn't affected by clock
jumps. Because it is updated at different times than the rest of the
timekeeper object, the floor value is managed independently of the
timekeeper via a cmpxchg() operation, and sits on its own cacheline.
Add two new public interfaces:
- ktime_get_coarse_real_ts64_mg() fills a timespec64 with the later of the
coarse-grained clock and the floor time
- ktime_get_real_ts64_mg() gets the fine-grained clock value, and tries
to swap it into the floor. A timespec64 is filled with the result.
The floor value is global and updated via a single try_cmpxchg(). If
that fails then the operation raced with a concurrent update. Any
concurrent update must be later than the existing floor value, so any
racing tasks can accept any resulting floor value without retrying.
[1]: POSIX requires that files be stamped with realtime clock values, and
makes no provision for dealing with backward clock jumps. If a backward
realtime clock jump occurs, then files can appear to have been modified
in reverse order.
Signed-off-by: Jeff Layton <jlayton@kernel.org>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Tested-by: Randy Dunlap <rdunlap@infradead.org> # documentation bits
Acked-by: John Stultz <jstultz@google.com>
Link: https://lore.kernel.org/all/20241002-mgtime-v10-1-d1c4717f5284@kernel.org
Signed-off-by: Christian Brauner <brauner@kernel.org>
diff --git a/include/linux/timekeeping.h b/include/linux/timekeeping.h
index fc12a9ba2c88..7aa85246c183 100644
--- a/include/linux/timekeeping.h
+++ b/include/linux/timekeeping.h
@@ -45,6 +45,10 @@ extern void ktime_get_real_ts64(struct timespec64 *tv);
extern void ktime_get_coarse_ts64(struct timespec64 *ts);
extern void ktime_get_coarse_real_ts64(struct timespec64 *ts);
+/* Multigrain timestamp interfaces */
+extern void ktime_get_coarse_real_ts64_mg(struct timespec64 *ts);
+extern void ktime_get_real_ts64_mg(struct timespec64 *ts);
+
void getboottime64(struct timespec64 *ts);
/*
diff --git a/kernel/time/timekeeping.c b/kernel/time/timekeeping.c
index 7e6f409bf311..441792c907fa 100644
--- a/kernel/time/timekeeping.c
+++ b/kernel/time/timekeeping.c
@@ -114,6 +114,23 @@ static struct tk_fast tk_fast_raw ____cacheline_aligned = {
.base[1] = FAST_TK_INIT,
};
+/*
+ * Multigrain timestamps require tracking the latest fine-grained timestamp
+ * that has been issued, and never returning a coarse-grained timestamp that is
+ * earlier than that value.
+ *
+ * mg_floor represents the latest fine-grained time that has been handed out as
+ * a file timestamp on the system. This is tracked as a monotonic ktime_t, and
+ * converted to a realtime clock value on an as-needed basis.
+ *
+ * Maintaining mg_floor ensures the multigrain interfaces never issue a
+ * timestamp earlier than one that has been previously issued.
+ *
+ * The exception to this rule is when there is a backward realtime clock jump. If
+ * such an event occurs, a timestamp can appear to be earlier than a previous one.
+ */
+static __cacheline_aligned_in_smp atomic64_t mg_floor;
+
static inline void tk_normalize_xtime(struct timekeeper *tk)
{
while (tk->tkr_mono.xtime_nsec >= ((u64)NSEC_PER_SEC << tk->tkr_mono.shift)) {
@@ -2394,6 +2411,93 @@ void ktime_get_coarse_real_ts64(struct timespec64 *ts)
}
EXPORT_SYMBOL(ktime_get_coarse_real_ts64);
+/**
+ * ktime_get_coarse_real_ts64_mg - return latter of coarse grained time or floor
+ * @ts: timespec64 to be filled
+ *
+ * Fetch the global mg_floor value, convert it to realtime and compare it
+ * to the current coarse-grained time. Fill @ts with whichever is
+ * latest. Note that this is a filesystem-specific interface and should be
+ * avoided outside of that context.
+ */
+void ktime_get_coarse_real_ts64_mg(struct timespec64 *ts)
+{
+ struct timekeeper *tk = &tk_core.timekeeper;
+ u64 floor = atomic64_read(&mg_floor);
+ ktime_t f_real, offset, coarse;
+ unsigned int seq;
+
+ do {
+ seq = read_seqcount_begin(&tk_core.seq);
+ *ts = tk_xtime(tk);
+ offset = tk_core.timekeeper.offs_real;
+ } while (read_seqcount_retry(&tk_core.seq, seq));
+
+ coarse = timespec64_to_ktime(*ts);
+ f_real = ktime_add(floor, offset);
+ if (ktime_after(f_real, coarse))
+ *ts = ktime_to_timespec64(f_real);
+}
+
+/**
+ * ktime_get_real_ts64_mg - attempt to update floor value and return result
+ * @ts: pointer to the timespec to be set
+ *
+ * Get a monotonic fine-grained time value and attempt to swap it into
+ * mg_floor. If that succeeds then accept the new floor value. If it fails
+ * then another task raced in during the interim time and updated the
+ * floor. Since any update to the floor must be later than the previous
+ * floor, either outcome is acceptable.
+ *
+ * Typically this will be called after calling ktime_get_coarse_real_ts64_mg(),
+ * and determining that the resulting coarse-grained timestamp did not effect
+ * a change in ctime. Any more recent floor value would effect a change to
+ * ctime, so there is no need to retry the atomic64_try_cmpxchg() on failure.
+ *
+ * @ts will be filled with the latest floor value, regardless of the outcome of
+ * the cmpxchg. Note that this is a filesystem specific interface and should be
+ * avoided outside of that context.
+ */
+void ktime_get_real_ts64_mg(struct timespec64 *ts)
+{
+ struct timekeeper *tk = &tk_core.timekeeper;
+ ktime_t old = atomic64_read(&mg_floor);
+ ktime_t offset, mono;
+ unsigned int seq;
+ u64 nsecs;
+
+ do {
+ seq = read_seqcount_begin(&tk_core.seq);
+
+ ts->tv_sec = tk->xtime_sec;
+ mono = tk->tkr_mono.base;
+ nsecs = timekeeping_get_ns(&tk->tkr_mono);
+ offset = tk_core.timekeeper.offs_real;
+ } while (read_seqcount_retry(&tk_core.seq, seq));
+
+ mono = ktime_add_ns(mono, nsecs);
+
+ /*
+ * Attempt to update the floor with the new time value. As any
+ * update must be later then the existing floor, and would effect
+ * a change to ctime from the perspective of the current task,
+ * accept the resulting floor value regardless of the outcome of
+ * the swap.
+ */
+ if (atomic64_try_cmpxchg(&mg_floor, &old, mono)) {
+ ts->tv_nsec = 0;
+ timespec64_add_ns(ts, nsecs);
+ } else {
+ /*
+ * Another task changed mg_floor since "old" was fetched.
+ * "old" has been updated with the latest value of "mg_floor".
+ * That value is newer than the previous floor value, which
+ * is enough to effect a change to ctime. Accept it.
+ */
+ *ts = ktime_to_timespec64(ktime_add(old, offset));
+ }
+}
+
void ktime_get_coarse_ts64(struct timespec64 *ts)
{
struct timekeeper *tk = &tk_core.timekeeper;
]
|
| 20/2 |
2026/05/31 14:05 |
llm |
expert |
0m
Model:
Tokens:
input: 19366
output: 42
thoughts: 117
Thoughts: **Investigating a Potential Timekeeping Implementation Discrepancy**
Okay, so I'm looking at commit `ee3283c608dfa21251b0821d7bb198c7ae3189f6`, and something's immediately jumping out at me in the implementation of `ktime_get_coarse_real_ts64_mg`. Specifically, they've used `*ts = tk_xtime(tk);` to assign the timestamp instead of `tk_xtime_coarse(tk)`. That's...interesting. It seems like a potential oversight given the function's name and intended "coarse" time-handling nature.
My immediate question is, did this get corrected in a later commit? I need to quickly scan the subsequent history. I need to see if someone realized the discrepancy and made the proper adjustment to ensure that the coarse-grained time acquisition is, in fact, implemented. Hopefully, this was caught and patched.
|
| 21/2 |
2026/05/31 14:05 |
tool |
git-log |
0m
Args: map[CodeRegexp:tk_xtime_coarse Count:10 PathPrefix:kernel/time/timekeeping.c]
Results: map[Output:b71f9804f66c timekeeping: Prevent coarse clocks going backwards
]
|
| 22/2 |
2026/05/31 14:05 |
llm |
expert |
0m
Model:
Tokens:
input: 19778
output: 27
thoughts: 18
Thoughts: Okay, here's my interpretation of that thought process, assuming I am an expert, presented as a first-person summary:
**Initial Assessment: Navigating the Landscape**
Alright, *b71f9804f66c*... That's a unique identifier, a key, a hash, a commit, or something similar, likely within a codebase, dataset, or computational environment. My immediate focus is on the *context* it implies. Is this a version control reference? A specific data point in a large dataset? Is it a key for a cryptographic operation? Without more surrounding information, I can't definitively pinpoint its meaning, but the format screams a system-specific label. I need to figure out the system it belongs to, and that should dictate my next action.
|
| 23/2 |
2026/05/31 14:06 |
tool |
git-show |
0m
Args:
Results: map[Output:commit b71f9804f66c2592d4c3a2397b7374a4039005a5
Author: Thomas Gleixner <tglx@kernel.org>
Date: Fri Apr 18 22:46:52 2025 -0700
timekeeping: Prevent coarse clocks going backwards
Lei Chen raised an issue with CLOCK_MONOTONIC_COARSE seeing time
inconsistencies. Lei tracked down that this was being caused by the
adjustment:
tk->tkr_mono.xtime_nsec -= offset;
which is made to compensate for the unaccumulated cycles in offset when the
multiplicator is adjusted forward, so that the non-_COARSE clockids don't
see inconsistencies.
However, the _COARSE clockid getter functions use the adjusted xtime_nsec
value directly and do not compensate the negative offset via the
clocksource delta multiplied with the new multiplicator. In that case the
caller can observe time going backwards in consecutive calls.
By design, this negative adjustment should be fine, because the logic run
from timekeeping_adjust() is done after it accumulated approximately
multiplicator * interval_cycles
into xtime_nsec. The accumulated value is always larger then the
mult_adj * offset
value, which is subtracted from xtime_nsec. Both operations are done
together under the tk_core.lock, so the net change to xtime_nsec is always
always be positive.
However, do_adjtimex() calls into timekeeping_advance() as well, to
apply the NTP frequency adjustment immediately. In this case,
timekeeping_advance() does not return early when the offset is smaller
then interval_cycles. In that case there is no time accumulated into
xtime_nsec. But the subsequent call into timekeeping_adjust(), which
modifies the multiplicator, subtracts from xtime_nsec to correct for the
new multiplicator.
Here because there was no accumulation, xtime_nsec becomes smaller than
before, which opens a window up to the next accumulation, where the
_COARSE clockid getters, which don't compensate for the offset, can
observe the inconsistency.
This has been tried to be fixed by forwarding the timekeeper in the case
that adjtimex() adjusts the multiplier, which resets the offset to zero:
757b000f7b93 ("timekeeping: Fix possible inconsistencies in _COARSE clockids")
That works correctly, but unfortunately causes a regression on the
adjtimex() side. There are two issues:
1) The forwarding of the base time moves the update out of the original
period and establishes a new one.
2) The clearing of the accumulated NTP error is changing the behaviour as
well.
User-space expects that multiplier/frequency updates are in effect, when the
syscall returns, so delaying the update to the next tick is not solving the
problem either.
Commit 757b000f7b93 was reverted so that the established expectations of
user space implementations (ntpd, chronyd) are restored, but that obviously
brought the inconsistencies back.
One of the initial approaches to fix this was to establish a separate
storage for the coarse time getter nanoseconds part by calculating it from
the offset. That was dropped on the floor because not having yet another
state to maintain was simpler. But given the result of the above exercise,
this solution turns out to be the right one. Bring it back in a slightly
modified form.
Thus introduce timekeeper::coarse_nsec and store that nanoseconds part in
it, switch the time getter functions and the VDSO update to use that value.
coarse_nsec is set on operations which forward or initialize the timekeeper
and after time was accumulated during a tick. If there is no accumulation
the timestamp is unchanged.
This leaves the adjtimex() behaviour unmodified and prevents coarse time
from going backwards.
[ jstultz: Simplified the coarse_nsec calculation and kept behavior so
coarse clockids aren't adjusted on each inter-tick adjtimex
call, slightly reworked the comments and commit message ]
Fixes: da15cfdae033 ("time: Introduce CLOCK_REALTIME_COARSE")
Reported-by: Lei Chen <lei.chen@smartx.com>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Signed-off-by: John Stultz <jstultz@google.com>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Signed-off-by: Ingo Molnar <mingo@kernel.org>
Link: https://lore.kernel.org/all/20250419054706.2319105-1-jstultz@google.com
Closes: https://lore.kernel.org/lkml/20250310030004.3705801-1-lei.chen@smartx.com/
diff --git a/include/linux/timekeeper_internal.h b/include/linux/timekeeper_internal.h
index e39d4d563b19..785048a3b3e6 100644
--- a/include/linux/timekeeper_internal.h
+++ b/include/linux/timekeeper_internal.h
@@ -51,7 +51,7 @@ struct tk_read_base {
* @offs_real: Offset clock monotonic -> clock realtime
* @offs_boot: Offset clock monotonic -> clock boottime
* @offs_tai: Offset clock monotonic -> clock tai
- * @tai_offset: The current UTC to TAI offset in seconds
+ * @coarse_nsec: The nanoseconds part for coarse time getters
* @tkr_raw: The readout base structure for CLOCK_MONOTONIC_RAW
* @raw_sec: CLOCK_MONOTONIC_RAW time in seconds
* @clock_was_set_seq: The sequence number of clock was set events
@@ -76,6 +76,7 @@ struct tk_read_base {
* ntp shifted nano seconds.
* @ntp_err_mult: Multiplication factor for scaled math conversion
* @skip_second_overflow: Flag used to avoid updating NTP twice with same second
+ * @tai_offset: The current UTC to TAI offset in seconds
*
* Note: For timespec(64) based interfaces wall_to_monotonic is what
* we need to add to xtime (or xtime corrected for sub jiffy times)
@@ -100,7 +101,7 @@ struct tk_read_base {
* which results in the following cacheline layout:
*
* 0: seqcount, tkr_mono
- * 1: xtime_sec ... tai_offset
+ * 1: xtime_sec ... coarse_nsec
* 2: tkr_raw, raw_sec
* 3,4: Internal variables
*
@@ -121,7 +122,7 @@ struct timekeeper {
ktime_t offs_real;
ktime_t offs_boot;
ktime_t offs_tai;
- s32 tai_offset;
+ u32 coarse_nsec;
/* Cacheline 2: */
struct tk_read_base tkr_raw;
@@ -144,6 +145,7 @@ struct timekeeper {
u32 ntp_error_shift;
u32 ntp_err_mult;
u32 skip_second_overflow;
+ s32 tai_offset;
};
#ifdef CONFIG_GENERIC_TIME_VSYSCALL
diff --git a/kernel/time/timekeeping.c b/kernel/time/timekeeping.c
index 1e67d076f195..a009c91f7b05 100644
--- a/kernel/time/timekeeping.c
+++ b/kernel/time/timekeeping.c
@@ -164,10 +164,34 @@ static inline struct timespec64 tk_xtime(const struct timekeeper *tk)
return ts;
}
+static inline struct timespec64 tk_xtime_coarse(const struct timekeeper *tk)
+{
+ struct timespec64 ts;
+
+ ts.tv_sec = tk->xtime_sec;
+ ts.tv_nsec = tk->coarse_nsec;
+ return ts;
+}
+
+/*
+ * Update the nanoseconds part for the coarse time keepers. They can't rely
+ * on xtime_nsec because xtime_nsec could be adjusted by a small negative
+ * amount when the multiplication factor of the clock is adjusted, which
+ * could cause the coarse clocks to go slightly backwards. See
+ * timekeeping_apply_adjustment(). Thus we keep a separate copy for the coarse
+ * clockids which only is updated when the clock has been set or we have
+ * accumulated time.
+ */
+static inline void tk_update_coarse_nsecs(struct timekeeper *tk)
+{
+ tk->coarse_nsec = tk->tkr_mono.xtime_nsec >> tk->tkr_mono.shift;
+}
+
static void tk_set_xtime(struct timekeeper *tk, const struct timespec64 *ts)
{
tk->xtime_sec = ts->tv_sec;
tk->tkr_mono.xtime_nsec = (u64)ts->tv_nsec << tk->tkr_mono.shift;
+ tk_update_coarse_nsecs(tk);
}
static void tk_xtime_add(struct timekeeper *tk, const struct timespec64 *ts)
@@ -175,6 +199,7 @@ static void tk_xtime_add(struct timekeeper *tk, const struct timespec64 *ts)
tk->xtime_sec += ts->tv_sec;
tk->tkr_mono.xtime_nsec += (u64)ts->tv_nsec << tk->tkr_mono.shift;
tk_normalize_xtime(tk);
+ tk_update_coarse_nsecs(tk);
}
static void tk_set_wall_to_mono(struct timekeeper *tk, struct timespec64 wtm)
@@ -708,6 +733,7 @@ static void timekeeping_forward_now(struct timekeeper *tk)
tk_normalize_xtime(tk);
delta -= incr;
}
+ tk_update_coarse_nsecs(tk);
}
/**
@@ -804,8 +830,8 @@ EXPORT_SYMBOL_GPL(ktime_get_with_offset);
ktime_t ktime_get_coarse_with_offset(enum tk_offsets offs)
{
struct timekeeper *tk = &tk_core.timekeeper;
- unsigned int seq;
ktime_t base, *offset = offsets[offs];
+ unsigned int seq;
u64 nsecs;
WARN_ON(timekeeping_suspended);
@@ -813,7 +839,7 @@ ktime_t ktime_get_coarse_with_offset(enum tk_offsets offs)
do {
seq = read_seqcount_begin(&tk_core.seq);
base = ktime_add(tk->tkr_mono.base, *offset);
- nsecs = tk->tkr_mono.xtime_nsec >> tk->tkr_mono.shift;
+ nsecs = tk->coarse_nsec;
} while (read_seqcount_retry(&tk_core.seq, seq));
@@ -2161,7 +2187,7 @@ static bool timekeeping_advance(enum timekeeping_adv_mode mode)
struct timekeeper *real_tk = &tk_core.timekeeper;
unsigned int clock_set = 0;
int shift = 0, maxshift;
- u64 offset;
+ u64 offset, orig_offset;
guard(raw_spinlock_irqsave)(&tk_core.lock);
@@ -2172,7 +2198,7 @@ static bool timekeeping_advance(enum timekeeping_adv_mode mode)
offset = clocksource_delta(tk_clock_read(&tk->tkr_mono),
tk->tkr_mono.cycle_last, tk->tkr_mono.mask,
tk->tkr_mono.clock->max_raw_delta);
-
+ orig_offset = offset;
/* Check if there's really nothing to do */
if (offset < real_tk->cycle_interval && mode == TK_ADV_TICK)
return false;
@@ -2205,6 +2231,14 @@ static bool timekeeping_advance(enum timekeeping_adv_mode mode)
*/
clock_set |= accumulate_nsecs_to_secs(tk);
+ /*
+ * To avoid inconsistencies caused adjtimex TK_ADV_FREQ calls
+ * making small negative adjustments to the base xtime_nsec
+ * value, only update the coarse clocks if we accumulated time
+ */
+ if (orig_offset != offset)
+ tk_update_coarse_nsecs(tk);
+
timekeeping_update_from_shadow(&tk_core, clock_set);
return !!clock_set;
@@ -2248,7 +2282,7 @@ void ktime_get_coarse_real_ts64(struct timespec64 *ts)
do {
seq = read_seqcount_begin(&tk_core.seq);
- *ts = tk_xtime(tk);
+ *ts = tk_xtime_coarse(tk);
} while (read_seqcount_retry(&tk_core.seq, seq));
}
EXPORT_SYMBOL(ktime_get_coarse_real_ts64);
@@ -2271,7 +2305,7 @@ void ktime_get_coarse_real_ts64_mg(struct timespec64 *ts)
do {
seq = read_seqcount_begin(&tk_core.seq);
- *ts = tk_xtime(tk);
+ *ts = tk_xtime_coarse(tk);
offset = tk_core.timekeeper.offs_real;
} while (read_seqcount_retry(&tk_core.seq, seq));
@@ -2350,12 +2384,12 @@ void ktime_get_coarse_ts64(struct timespec64 *ts)
do {
seq = read_seqcount_begin(&tk_core.seq);
- now = tk_xtime(tk);
+ now = tk_xtime_coarse(tk);
mono = tk->wall_to_monotonic;
} while (read_seqcount_retry(&tk_core.seq, seq));
set_normalized_timespec64(ts, now.tv_sec + mono.tv_sec,
- now.tv_nsec + mono.tv_nsec);
+ now.tv_nsec + mono.tv_nsec);
}
EXPORT_SYMBOL(ktime_get_coarse_ts64);
diff --git a/kernel/time/vsyscall.c b/kernel/time/vsyscall.c
index 01c2ab1e8971..32ef27c71b57 100644
--- a/kernel/time/vsyscall.c
+++ b/kernel/time/vsyscall.c
@@ -98,12 +98,12 @@ void update_vsyscall(struct timekeeper *tk)
/* CLOCK_REALTIME_COARSE */
vdso_ts = &vc[CS_HRES_COARSE].basetime[CLOCK_REALTIME_COARSE];
vdso_ts->sec = tk->xtime_sec;
- vdso_ts->nsec = tk->tkr_mono.xtime_nsec >> tk->tkr_mono.shift;
+ vdso_ts->nsec = tk->coarse_nsec;
/* CLOCK_MONOTONIC_COARSE */
vdso_ts = &vc[CS_HRES_COARSE].basetime[CLOCK_MONOTONIC_COARSE];
vdso_ts->sec = tk->xtime_sec + tk->wall_to_monotonic.tv_sec;
- nsec = tk->tkr_mono.xtime_nsec >> tk->tkr_mono.shift;
+ nsec = tk->coarse_nsec;
nsec = nsec + tk->wall_to_monotonic.tv_nsec;
vdso_ts->sec += __iter_div_u64_rem(nsec, NSEC_PER_SEC, &vdso_ts->nsec);
]
|
| 24/2 |
2026/05/31 14:06 |
llm |
expert |
0m
Model:
Tokens:
input: 24266
output: 66
thoughts: 1574
Thoughts: **Analysis of a Benign Data Race in Timekeeping**
Okay, let's break this down. It looks like we're dealing with a KCSAN data race report, and I need to assess its impact. The race is centered around `tk_xtime_coarse`, which was introduced in commit `b71f9804f66c` to address an issue with coarse clocks potentially going backward. The core of the problem lies in how `tk->coarse_nsec` is handled. Specifically, there's a straightforward read of `tk->coarse_nsec` occurring concurrently with a `memcpy` write within `timekeeping_update_from_shadow`. This, by itself, screams seqlock data race.
Since we're dealing with a seqlock, the read operation is governed by `read_seqcount_retry`. This means the read value is checked against a sequence number, and if the number changed during the read, the operation is retried. If the sequence number hasn't changed, then the value is consistent. The crucial aspect here is that the value read isn't used in a way that could cause a fault before the retry check. This is critical, as a stale or garbage read can often lead to crashes.
Now, let's dive deeper. Could the compiler mess with the read, moving it outside the retry loop? No, the `read_seqcount_begin` and `read_seqcount_retry` primitives include memory barriers, specifically `smp_rmb()`, which prevent the compiler from reordering and optimizing this in a dangerous way. Can the read be torn, meaning partially read, potentially resulting in a garbage value? Yes, the read can be torn, and the read value, in the case of a torn value, is assigned to `ts->tv_nsec`. But, if the sequence number remains unchanged, no concurrent write happened, therefore, the read could not have been torn. If the sequence number changes, the loop retries, and the torn value gets discarded. So, tearing is a non-issue.
What about the write? The write is a `memcpy` operation. Even if `memcpy` were implemented in a byte-by-byte or word-by-word fashion, the writer *holds* the seqlock. This is enforced by `write_seqcount_begin` and `write_seqcount_end`, which increment the sequence number before and after the write. This means any read that occurs during a write will detect a sequence number change and retry the read.
So, in essence, this is textbook benign data race within a seqlock loop. This race is not exploitable and cannot cause a denial-of-service condition. Because the read is part of the `current_time` function, which is called from `file_update_time_flags`, which is called from `shmem_file_write_iter`, which is called from `sendfile`. In fact, it is accessible from unprivileged processes via file operations such as `sendfile`, and from userspace. It isn't specific to a VM guest trigger, though it *can* be triggered from a VM guest since it's a general kernel bug.
To summarize, KCSAN flagged this as a data race because the read of `tk->coarse_nsec` isn't marked with `READ_ONCE()` or `data_race()`. However, the seqlock guarantees consistency. Any inconsistent read is caught by `read_seqcount_retry`, and is retried. The read values aren't used in a way that could lead to a fault before the retry check. Therefore, this is benign and has no security impact.
Here's how I'll set the results:
* Exploitable: false
* DenialOfService: false
* Unprivileged: true
* UserNamespace: true
* VMGuestTrigger: false
* VMHostTrigger: false
* NetworkTrigger: false
* RemoteTrigger: false
* PeripheralTrigger: false
* FilesystemTrigger: false
I initially thought that since the bug *can* be triggered inside a VM guest, but is not specific to it, I would set VMGuestTrigger to true. However, because it is more of a core kernel bug triggered by basic system calls I will leave VMGuestTrigger as false, since it is not virtualization-specific. So:
I'm ready to `set-results`.
|
| 25/2 |
2026/05/31 14:06 |
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]
|
| 26/2 |
2026/05/31 14:06 |
llm |
expert |
0m
Model:
Tokens:
input: 26922
output: 517
thoughts: 479
Thoughts: **Final Thoughts on the KCSAN Data Race Report**
Okay, the results are in, and now it's time to craft the final response, clearly explaining my analysis. Let's break this down.
First, the bug report flags a data race, a classic KCSAN catch, specifically between a write operation in `timekeeping_update_from_shadow` and a read within `ktime_get_coarse_real_ts64_mg`, focusing on that `tk_xtime_coarse` inline function.
Digging into the code, here's what's happening. The write is essentially a `memcpy` operation, updating the core timekeeper structure (`tkd->timekeeper`). Importantly, this write is *protected* by a sequence lock. We use `write_seqcount_begin` and `write_seqcount_end` to manage it.
On the read side, we see `ts.tv_nsec = tk->coarse_nsec;` and `ts.tv_sec = tk->xtime_sec;`, which are both happening inside a read-side sequence lock loop, using `read_seqcount_begin` and `read_seqcount_retry`. The system is built around this pattern, as we know.
Now, here's the kicker. KCSAN flags it because it's a plain C read – no `READ_ONCE()` or `data_race()` annotations. That makes sense, but it is standard for these seqlock setups to get flagged. The key is the sequence lock. If a concurrent write is happening, the sequence counter will change. The loop will retry, discarding any potentially inconsistent values. The read values are simple integers, and as an expert, I know they aren't used as pointers or array indices before that retry check; we aren't going to have a fault or out-of-bounds access if the values are garbage.
In conclusion, this is a benign data race. It's safe. There's no way this leads to memory corruption, control flow hijacking, or any exploitable conditions. It won't crash the kernel or cause a denial of service in production builds. It's a false positive, really.
And finally, to address reachability, the read path is easily reachable by any unprivileged user – think file operations like `sendfile` that touch modification times. The write path is triggered by normal timer interrupts, so it's readily accessible from unprivileged processes and user namespaces. So, it's something that shows up, but it's not a real issue in practice.
|
| 27/1 |
2026/05/31 14:06 |
action |
format-explanation |
0m
Results: map[Explanation:**Bug Analysis & Security Impact**
The reported bug is a KCSAN (Kernel Concurrency Sanitizer) data-race between a
timer interrupt updating the system time and a user task reading the time to
update a file's timestamp.
1. **The Write Path**: Triggered by the timer interrupt (`tick_nohz_handler` ->
`update_wall_time` -> `timekeeping_update_from_shadow`). The write operation is
a `memcpy` that updates the global `timekeeper` structure. Crucially, this write
is enclosed within a sequence lock (`write_seqcount_begin` and
`write_seqcount_end`).
2. **The Read Path**: Triggered by a standard file operation (`sendfile64` ->
`file_update_time` -> `ktime_get_coarse_real_ts64_mg`). The read operation
fetches the coarse time (`tk->coarse_nsec` and `tk->xtime_sec`). This read is
enclosed within a sequence lock reader loop (`read_seqcount_begin` and
`read_seqcount_retry`).
**Reasoning:**
This is a textbook example of a benign data race within a seqlock loop. KCSAN
flags this because the reader performs plain C memory accesses (without
`READ_ONCE()` or `data_race()` annotations) while the writer concurrently
modifies the data via `memcpy`.
However, the seqlock mechanism guarantees safety:
* If the read is torn or fetches inconsistent data due to the concurrent write,
the sequence counter will be detected as changed by `read_seqcount_retry()`.
* The loop will then discard the garbage values and retry the read.
* The variables being read are simple integers (`u64` and `u32`). They are not
used as pointers, array indices, or in any arithmetic that could cause a CPU
exception (like division by zero) *before* the sequence counter is validated.
**Conclusion:**
Because the inconsistent data is safely discarded and cannot cause a fault prior
to the retry check, this data race has **no security impact**. It is not
exploitable for privilege escalation or memory corruption, nor can it cause a
Denial of Service (DoS) in production kernels.
The code paths are reachable by any unprivileged user (e.g., by simply reading
time or writing to files), meaning it is accessible from unprivileged processes
and within user namespaces, but it poses no risk.]
|