syzbot
KCSAN: data-race in ktime_get_coarse_ts64 / timekeeping_update_from_shadow (4)
Status:
moderation: reported on 2026/11/15 04:51
Subsystems:
kernel
Labels:
prio:low
race:benign
[Documentation on labels]
Reported-by: syzbot+708f1141bf273f4c09e6@syzkaller.appspotmail.com
First crash: 7h26m, last: 7h26m
✨ AI-generated root cause summary:
In ktime_get_coarse_ts64(), plain reads of timekeeper fields (tk_xtime_coarse() reading tk->xtime_sec and tk->coarse_nsec, and tk->wall_to_monotonic) occur inside a sequence counter read section (read_seqcount_begin(&tk_core.seq) ... read_seqcount_retry(&tk_core.seq, seq)). Concurrently, timer interrupt processing on another CPU in timekeeping_update_from_shadow() copies updated timekeeper data into tk_core.timekeeper via memcpy(&tkd->timekeeper, tk, sizeof(*tk)) inside a write_seqcount_begin() ... write_seqcount_end() critical section. KCSAN attempts to suppress reports for seqlock readers by marking the subsequent memory accesses as atomic via kcsan_atomic_next(KCSAN_SEQLOCK_REGION_MAX). When the calling thread is preempted or executes nested code that clears or exhausts current->kcsan_ctx.atomic_next before tk->coarse_nsec is read, KCSAN observes an unannotated plain read racing with the concurrent memcpy() and flags a data race.
▶
▼
✨ Full AI root cause analysis
Technical Root Cause Analysis
Overview and Files Involved
- Source File:
kernel/time/timekeeping.c
- Functions:
ktime_get_coarse_ts64(), tk_xtime_coarse(), and timekeeping_update_from_shadow()
- Structs and Fields:
struct tk_data tk_core (timekeeper_data[TIMEKEEPER_CORE]), containing seqcount_raw_spinlock_t seq and struct timekeeper timekeeper.
struct timekeeper: specifically tk->coarse_nsec, tk->xtime_sec, and tk->wall_to_monotonic.
Detailed Mechanism and Invariant Violation
-
Reader Side (ktime_get_coarse_ts64):
In kernel/time/timekeeping.c:
void ktime_get_coarse_ts64(struct timespec64 *ts)
{
struct timekeeper *tk = &tk_core.timekeeper;
struct timespec64 now, mono;
unsigned int seq;
do {
seq = read_seqcount_begin(&tk_core.seq);
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);
}
tk_xtime_coarse(tk) performs plain (unannotated) reads of tk->xtime_sec and tk->coarse_nsec:
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;
}
These reads are protected against writer inconsistency via the sequence counter tk_core.seq.
-
Writer Side (timekeeping_update_from_shadow):
During timer interrupt processing (update_wall_time() -> __timekeeping_advance() -> timekeeping_update_from_shadow()), CPU 0 acquires write_seqcount_begin(&tkd->seq) and writes updated timekeeper state to tk_core.timekeeper using memcpy():
WRITE_ONCE(tkd->timekeeper.xtime_sec, tk->xtime_sec);
WRITE_ONCE(tkd->timekeeper.ktime_sec, tk->ktime_sec);
memcpy(&tkd->timekeeper, tk, sizeof(*tk));
write_seqcount_end(&tkd->seq);
-
C-Level Concurrency and KCSAN Interaction:
- Under the C memory model, concurrent plain reads (
tk->coarse_nsec) and plain writes (memcpy) constitute a data race. In the kernel, sequence locks and sequence counters allow lockless reads racing with writes, discarding torn or partially updated reads via read_seqcount_retry().
- To accommodate raw
seqcount_t readers without false-positive data race reports on every reader access, KCSAN's read_seqcount_begin() invokes kcsan_atomic_next(KCSAN_SEQLOCK_REGION_MAX) (in include/linux/seqlock.h), which sets get_ctx()->atomic_next = 1000. This temporarily marks the subsequent 1000 memory accesses by that context as atomic so should_watch() will not install watchpoints.
- However,
read_seqcount_begin() does not disable preemption, and the kernel in the report is running with lazy preemption (PREEMPT(lazy)). The reader is running a BPF test program pinned via bpf_prog_run_pin_on_cpu(), which uses migrate_disable() but keeps preemption enabled.
- When task 7067 is preempted or executes nested kernel code between
read_seqcount_begin(&tk_core.seq) and tk_xtime_coarse(), current->kcsan_ctx.atomic_next can either be decremented to 0 by >1000 intervening instrumented operations or cleared entirely to 0 by any nested read_seqcount_retry() (which calls kcsan_atomic_next(0)).
- Once
atomic_next == 0, should_watch() in kernel/kcsan/core.c treats ts.tv_nsec = tk->coarse_nsec as a normal, non-atomic plain read and sets a KCSAN watchpoint.
- When the timer interrupt on CPU 0 invokes
timekeeping_update_from_shadow(), check_access() calls find_watchpoint() on &tkd->timekeeper and hits task 7067's watchpoint. Because a non-atomic read racing with a write is flagged by KCSAN regardless of whether the writer called kcsan_nestable_atomic_begin() via write_seqcount_begin(), KCSAN reports a data-race between ktime_get_coarse_ts64() and timekeeping_update_from_shadow().
Sample crash report:
* Struck through repros no longer work on HEAD.