From 7117334f022ba0cc7475f688efa68dd56e60f65b Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Fri, 11 Sep 2026 11:09:21 +0200 Subject: [PATCH 01/26] posix-timers: Move posixtimer_exec_cleanup() out of exec.c Move it to the POSIX timer code and provide a proper stub when POSIX timers are disabled in Kconfig. No functional change. Signed-off-by: Thomas Gleixner Tested-by: Kijo Park Reviewed-by: Frederic Weisbecker Reviewed-by: Oleg Nesterov Link: https://patch.msgid.link/20260911090541.680668094@kernel.org --- fs/exec.c | 13 +------------ include/linux/posix-timers.h | 3 +++ kernel/time/posix-timers.c | 9 +++++++++ 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/fs/exec.c b/fs/exec.c index 819643408e6d..1fc567155f34 100644 --- a/fs/exec.c +++ b/fs/exec.c @@ -1115,17 +1115,6 @@ static struct file *bprm_identity_file(const struct linux_binprm *bprm) return bprm->file; } -static void posixtimer_exec(struct task_struct *me) -{ -#ifdef CONFIG_POSIX_TIMERS - spin_lock_irq(&me->sighand->siglock); - posix_cpu_timers_exit(me); - spin_unlock_irq(&me->sighand->siglock); - exit_itimers(me); - flush_itimer_signals(); -#endif -} - /* * Calling this is the point of no return. None of the failures will be * seen by userspace since either the process is already taking a fatal @@ -1171,7 +1160,7 @@ int begin_new_exec(struct linux_binprm * bprm) * timer would not remove an enqueued timer because the TID lookup * of the old TID fails. */ - posixtimer_exec(me); + posixtimer_exec(); /* see the comment in check_unsafe_exec() */ current->fs->in_exec = 0; diff --git a/include/linux/posix-timers.h b/include/linux/posix-timers.h index 9a1a0c61361c..7b31da68363c 100644 --- a/include/linux/posix-timers.h +++ b/include/linux/posix-timers.h @@ -232,6 +232,8 @@ void set_process_cpu_timer(struct task_struct *task, unsigned int clock_idx, int update_rlimit_cpu(struct task_struct *task, unsigned long rlim_new); #ifdef CONFIG_POSIX_TIMERS +void posixtimer_exec(void); + static inline void posixtimer_putref(struct k_itimer *tmr) { if (rcuref_put(&tmr->rcuref)) @@ -259,6 +261,7 @@ static inline bool posixtimer_valid(const struct k_itimer *timer) return !(val & 0x1UL); } #else /* CONFIG_POSIX_TIMERS */ +static inline void posixtimer_exec(void) { } static inline void posixtimer_sigqueue_getref(struct sigqueue *q) { } static inline void posixtimer_sigqueue_putref(struct sigqueue *q) { } #endif /* !CONFIG_POSIX_TIMERS */ diff --git a/kernel/time/posix-timers.c b/kernel/time/posix-timers.c index 436ba794cc0b..9611ed4014d1 100644 --- a/kernel/time/posix-timers.c +++ b/kernel/time/posix-timers.c @@ -1120,6 +1120,15 @@ void exit_itimers(struct task_struct *tsk) } } +void posixtimer_exec(void) +{ + scoped_guard(spinlock_irq, ¤t->sighand->siglock) + posix_cpu_timers_exit(current); + + exit_itimers(current); + flush_itimer_signals(); +} + SYSCALL_DEFINE2(clock_settime, const clockid_t, which_clock, const struct __kernel_timespec __user *, tp) { From 24adce0b86c940ac3dcee0bcd75e799634fc9664 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Fri, 11 Sep 2026 11:09:26 +0200 Subject: [PATCH 02/26] posix-timers: Move POSIX timer group exit related code out of do_exit() Move the POSIX timer group exit handling into the posix timer code and provide a proper stub when POSIX timers are disabled in Kconfig. No functional change. Signed-off-by: Thomas Gleixner Tested-by: Kijo Park Reviewed-by: Frederic Weisbecker Reviewed-by: Oleg Nesterov Link: https://patch.msgid.link/20260911090541.733966325@kernel.org --- include/linux/posix-timers.h | 2 ++ include/linux/sched/task.h | 1 - kernel/exit.c | 7 +++---- kernel/time/posix-timers.c | 16 +++++++++------- 4 files changed, 14 insertions(+), 12 deletions(-) diff --git a/include/linux/posix-timers.h b/include/linux/posix-timers.h index 7b31da68363c..3050f9e44fee 100644 --- a/include/linux/posix-timers.h +++ b/include/linux/posix-timers.h @@ -233,6 +233,7 @@ int update_rlimit_cpu(struct task_struct *task, unsigned long rlim_new); #ifdef CONFIG_POSIX_TIMERS void posixtimer_exec(void); +void posixtimer_exit(void); static inline void posixtimer_putref(struct k_itimer *tmr) { @@ -262,6 +263,7 @@ static inline bool posixtimer_valid(const struct k_itimer *timer) } #else /* CONFIG_POSIX_TIMERS */ static inline void posixtimer_exec(void) { } +static inline void posixtimer_exit(void) { } static inline void posixtimer_sigqueue_getref(struct sigqueue *q) { } static inline void posixtimer_sigqueue_putref(struct sigqueue *q) { } #endif /* !CONFIG_POSIX_TIMERS */ diff --git a/include/linux/sched/task.h b/include/linux/sched/task.h index e0c1ca8c6a18..90ed5bc3c7af 100644 --- a/include/linux/sched/task.h +++ b/include/linux/sched/task.h @@ -94,7 +94,6 @@ static inline void exit_thread(struct task_struct *tsk) extern __noreturn void do_group_exit(int); extern void exit_files(struct task_struct *); -extern void exit_itimers(struct task_struct *); extern pid_t kernel_clone(struct kernel_clone_args *kargs); struct task_struct *copy_process(struct pid *pid, int trace, int node, diff --git a/kernel/exit.c b/kernel/exit.c index 424c44a42a4d..4ca9759ea6ad 100644 --- a/kernel/exit.c +++ b/kernel/exit.c @@ -966,13 +966,12 @@ void __noreturn do_exit(long code) panic("Attempted to kill init! exitcode=0x%08x\n", tsk->signal->group_exit_code ?: (int)code); -#ifdef CONFIG_POSIX_TIMERS - hrtimer_cancel(&tsk->signal->real_timer); - exit_itimers(tsk); -#endif + posixtimer_exit(); + if (tsk->mm) setmax_mm_hiwater_rss(&tsk->signal->maxrss, tsk->mm); } + acct_collect(code, group_dead); if (group_dead) tty_audit_exit(); diff --git a/kernel/time/posix-timers.c b/kernel/time/posix-timers.c index 9611ed4014d1..8fb040a040cb 100644 --- a/kernel/time/posix-timers.c +++ b/kernel/time/posix-timers.c @@ -1077,13 +1077,9 @@ SYSCALL_DEFINE1(timer_delete, timer_t, timer_id) return 0; } -/* - * Invoked from do_exit() when the last thread of a thread group exits. - * At that point no other task can access the timers of the dying - * task anymore. - */ -void exit_itimers(struct task_struct *tsk) +static void posixtimer_delete_timers(void) { + struct task_struct *tsk = current; struct hlist_head timers; struct hlist_node *next; struct k_itimer *timer; @@ -1120,12 +1116,18 @@ void exit_itimers(struct task_struct *tsk) } } +void posixtimer_exit(void) +{ + hrtimer_cancel(¤t->signal->real_timer); + posixtimer_delete_timers(); +} + void posixtimer_exec(void) { scoped_guard(spinlock_irq, ¤t->sighand->siglock) posix_cpu_timers_exit(current); - exit_itimers(current); + posixtimer_delete_timers(); flush_itimer_signals(); } From 8e9ed3b66f409e324dade4a07fc08dae43160ec6 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Fri, 11 Sep 2026 11:09:31 +0200 Subject: [PATCH 03/26] posix-cpu-timers: Move inlines out of public header They are only used in the POSIX CPU timer code. No point in exposing them globally and parsing them for nothing. Signed-off-by: Thomas Gleixner Tested-by: Kijo Park Reviewed-by: Frederic Weisbecker Reviewed-by: Oleg Nesterov Link: https://patch.msgid.link/20260911090541.785749315@kernel.org --- include/linux/posix-timers.h | 32 -------------------------------- kernel/time/posix-cpu-timers.c | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/include/linux/posix-timers.h b/include/linux/posix-timers.h index 3050f9e44fee..0033a065e98a 100644 --- a/include/linux/posix-timers.h +++ b/include/linux/posix-timers.h @@ -66,38 +66,6 @@ struct cpu_timer { struct task_struct __rcu *handling; }; -static inline bool cpu_timer_enqueue(struct timerqueue_head *head, - struct cpu_timer *ctmr) -{ - ctmr->head = head; - return timerqueue_add(head, &ctmr->node); -} - -static inline bool cpu_timer_queued(struct cpu_timer *ctmr) -{ - return !!ctmr->head; -} - -static inline bool cpu_timer_dequeue(struct cpu_timer *ctmr) -{ - if (cpu_timer_queued(ctmr)) { - timerqueue_del(ctmr->head, &ctmr->node); - ctmr->head = NULL; - return true; - } - return false; -} - -static inline u64 cpu_timer_getexpires(struct cpu_timer *ctmr) -{ - return ctmr->node.expires; -} - -static inline void cpu_timer_setexpires(struct cpu_timer *ctmr, u64 exp) -{ - ctmr->node.expires = exp; -} - static inline void posix_cputimers_init(struct posix_cputimers *pct) { memset(pct, 0, sizeof(*pct)); diff --git a/kernel/time/posix-cpu-timers.c b/kernel/time/posix-cpu-timers.c index d73d31c7994f..e6358342c84c 100644 --- a/kernel/time/posix-cpu-timers.c +++ b/kernel/time/posix-cpu-timers.c @@ -438,6 +438,38 @@ static void trigger_base_recalc_expires(struct k_itimer *timer, base->nextevt = 0; } +static inline bool cpu_timer_enqueue(struct timerqueue_head *head, + struct cpu_timer *ctmr) +{ + ctmr->head = head; + return timerqueue_add(head, &ctmr->node); +} + +static inline bool cpu_timer_queued(struct cpu_timer *ctmr) +{ + return !!ctmr->head; +} + +static inline bool cpu_timer_dequeue(struct cpu_timer *ctmr) +{ + if (cpu_timer_queued(ctmr)) { + timerqueue_del(ctmr->head, &ctmr->node); + ctmr->head = NULL; + return true; + } + return false; +} + +static inline u64 cpu_timer_getexpires(struct cpu_timer *ctmr) +{ + return ctmr->node.expires; +} + +static inline void cpu_timer_setexpires(struct cpu_timer *ctmr, u64 exp) +{ + ctmr->node.expires = exp; +} + /* * Dequeue the timer and reset the base if it was its earliest expiration. * It makes sure the next tick recalculates the base next expiration so we From 760ad335d600b1d45f05bc5fad59c05d909710be Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Fri, 11 Sep 2026 11:09:35 +0200 Subject: [PATCH 04/26] posix-cpu-timers: Use PF_EXITING to indicate exit Right now POSIX CPU timers use task::exit_state to check whether a task is exiting. That works correctly, but exit_state is set later in do_exit() and too late for allowing to cleanup POSIX timers earlier. It does not matter in case of exit whether the cutoff is a bit earlier. Signed-off-by: Thomas Gleixner Tested-by: Kijo Park Reviewed-by: Frederic Weisbecker Link: https://patch.msgid.link/20260911090541.837831386@kernel.org --- kernel/time/posix-cpu-timers.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/time/posix-cpu-timers.c b/kernel/time/posix-cpu-timers.c index e6358342c84c..cf4bd1f9d74c 100644 --- a/kernel/time/posix-cpu-timers.c +++ b/kernel/time/posix-cpu-timers.c @@ -1505,7 +1505,7 @@ void run_posix_cpu_timers(void) * posix_cpu_timer_del() may fail to lock_task_sighand(tsk) and * miss timer->it.cpu.firing != 0. */ - if (tsk->exit_state) + if (tsk->flags & PF_EXITING) return; /* From 54ad1e0ea42c3dc25de39b09220af408e4913820 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Fri, 11 Sep 2026 11:09:40 +0200 Subject: [PATCH 05/26] posix-cpu-timers: Prevent enqueueing when PF_EXITING is set To prepare for cleaning up POSIX CPU timers in do_exit(), prevent enqueueing POSIX CPU timers on a task which has PF_EXITING set. Queueing a timer on such a task is pointless because the task won't expire the timer anymore. The same applies to process wide timers when tsk::signal::flags has SIGNAL_GROUP_EXIT set. Pretending that the timer is armed allows to keep the POSIX timer mechanism "working" so that the timer stays accessible up to the point where a task is unhashed. Signed-off-by: Thomas Gleixner Tested-by: Kijo Park Reviewed-by: Oleg Nesterov Reviewed-by: Frederic Weisbecker Link: https://patch.msgid.link/20260911090541.892445443@kernel.org --- kernel/time/posix-cpu-timers.c | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/kernel/time/posix-cpu-timers.c b/kernel/time/posix-cpu-timers.c index cf4bd1f9d74c..ddaf81bb23fa 100644 --- a/kernel/time/posix-cpu-timers.c +++ b/kernel/time/posix-cpu-timers.c @@ -628,6 +628,7 @@ static int posix_cpu_timer_del(struct k_itimer *timer) } if (!ret) { + WARN_ON_ONCE(cpu_timer_queued(&timer->it.cpu)); put_pid(timer->it.cpu.pid); timer->it_status = POSIX_TIMER_DISARMED; } @@ -674,6 +675,27 @@ void posix_cpu_timers_exit_group(struct task_struct *tsk) cleanup_timers(&tsk->signal->posix_cputimers); } +/* + * This function validates that POSIX CPU timers can be safely enqueued on the + * target task. + * + * Enqueue is allowed when PF_EXITING is not set. If set then it is only allowed + * for process shared timers (type = PIDTYPE_TGID) as long as tsk::signal::flags + * does not have SIGNAL_GROUP_EXIT set. PIDTYPE_PID targets are not allowed at + * all when the task has PF_EXITING set. + * + * This guarantees that after the POSIX timer cleanup in posixtimer_exit() no + * POSIX CPU timers are queued on the task or in case of a group exit on the + * process. + */ +static inline bool task_can_enqueue_timer(struct task_struct *tsk, enum pid_type type) +{ + if (likely(!(tsk->flags & PF_EXITING))) + return true; + + return type == PIDTYPE_TGID && !(tsk->signal->flags & SIGNAL_GROUP_EXIT); +} + /* * Insert the timer on the appropriate list before any timers that * expire later. This must be called with the sighand lock held. @@ -684,7 +706,13 @@ static void arm_timer(struct k_itimer *timer, struct task_struct *p) struct cpu_timer *ctmr = &timer->it.cpu; u64 newexp = cpu_timer_getexpires(ctmr); + lockdep_assert_held(&p->sighand->siglock); + timer->it_status = POSIX_TIMER_ARMED; + + if (unlikely(!task_can_enqueue_timer(p, clock_pid_type(timer->it_clock)))) + return; + if (!cpu_timer_enqueue(&base->tqhead, ctmr)) return; From 7b07d15ed1d78cb49309754a2d14dacdddb058c7 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Fri, 11 Sep 2026 11:09:44 +0200 Subject: [PATCH 06/26] posix-timers: Handle exit in do_exit() completely Now that POSIX CPU timers cannot be enqueued on a task after PF_EXITING is set and process wide timers cannot be enqueued when PF_EXITING is set and the last thread in the group is exiting, it is possible to mop up POSIX timers in do_exit() completely. This requires to cancel an eventually pending POSIX CPU timer task work right there because do_exit() invokes exit_task_work() later, which would be acting on torn down data. Signed-off-by: Thomas Gleixner Tested-by: Kijo Park Reviewed-by: Oleg Nesterov Reviewed-by: Frederic Weisbecker Link: https://patch.msgid.link/20260911090541.947206186@kernel.org --- include/linux/posix-timers.h | 6 ++--- kernel/exit.c | 10 ++------- kernel/time/posix-cpu-timers.c | 41 ++++++++++++++++++++++++++++------ kernel/time/posix-timers.c | 15 ++++++++----- kernel/time/posix-timers.h | 3 +++ 5 files changed, 50 insertions(+), 25 deletions(-) diff --git a/include/linux/posix-timers.h b/include/linux/posix-timers.h index 0033a065e98a..00767acbc111 100644 --- a/include/linux/posix-timers.h +++ b/include/linux/posix-timers.h @@ -192,8 +192,6 @@ struct k_itimer { } ____cacheline_aligned_in_smp; void run_posix_cpu_timers(void); -void posix_cpu_timers_exit(struct task_struct *task); -void posix_cpu_timers_exit_group(struct task_struct *task); void set_process_cpu_timer(struct task_struct *task, unsigned int clock_idx, u64 *newval, u64 *oldval); @@ -201,7 +199,7 @@ int update_rlimit_cpu(struct task_struct *task, unsigned long rlim_new); #ifdef CONFIG_POSIX_TIMERS void posixtimer_exec(void); -void posixtimer_exit(void); +void posixtimer_exit(bool group_dead); static inline void posixtimer_putref(struct k_itimer *tmr) { @@ -231,7 +229,7 @@ static inline bool posixtimer_valid(const struct k_itimer *timer) } #else /* CONFIG_POSIX_TIMERS */ static inline void posixtimer_exec(void) { } -static inline void posixtimer_exit(void) { } +static inline void posixtimer_exit(bool group_dead) { } static inline void posixtimer_sigqueue_getref(struct sigqueue *q) { } static inline void posixtimer_sigqueue_putref(struct sigqueue *q) { } #endif /* !CONFIG_POSIX_TIMERS */ diff --git a/kernel/exit.c b/kernel/exit.c index 4ca9759ea6ad..06a1eb1fdfcd 100644 --- a/kernel/exit.c +++ b/kernel/exit.c @@ -167,12 +167,6 @@ static void __exit_signal(struct release_task_post *post, struct task_struct *ts lockdep_tasklist_lock_is_held()); spin_lock(&sighand->siglock); -#ifdef CONFIG_POSIX_TIMERS - posix_cpu_timers_exit(tsk); - if (group_dead) - posix_cpu_timers_exit_group(tsk); -#endif - if (group_dead) { tty = sig->tty; sig->tty = NULL; @@ -966,12 +960,12 @@ void __noreturn do_exit(long code) panic("Attempted to kill init! exitcode=0x%08x\n", tsk->signal->group_exit_code ?: (int)code); - posixtimer_exit(); - if (tsk->mm) setmax_mm_hiwater_rss(&tsk->signal->maxrss, tsk->mm); } + posixtimer_exit(group_dead); + acct_collect(code, group_dead); if (group_dead) tty_audit_exit(); diff --git a/kernel/time/posix-cpu-timers.c b/kernel/time/posix-cpu-timers.c index ddaf81bb23fa..9f90500894dc 100644 --- a/kernel/time/posix-cpu-timers.c +++ b/kernel/time/posix-cpu-timers.c @@ -661,18 +661,29 @@ static void cleanup_timers(struct posix_cputimers *pct) cleanup_timerqueue(&pct->bases[CPUCLOCK_SCHED].tqhead); } +static inline void posix_cpu_timers_exit_work(void); + /* - * These are both called with the siglock held, when the current thread - * is being reaped. When the final (leader) thread in the group is reaped, - * posix_cpu_timers_exit_group will be called after posix_cpu_timers_exit. + * Invoked from posixtimer_exit_task() after PF_EXITING was set in tsk::flags or + * from posixtimer_exec_cleanup(). */ -void posix_cpu_timers_exit(struct task_struct *tsk) +void posix_cpu_timers_exit_task(void) { - cleanup_timers(&tsk->posix_cputimers); + posix_cpu_timers_exit_work(); + + guard(spinlock_irq)(¤t->sighand->siglock); + cleanup_timers(¤t->posix_cputimers); } -void posix_cpu_timers_exit_group(struct task_struct *tsk) + +/* + * Invoked from posixtimer_exit_group() after PF_EXITING was set in tsk::flags. + */ +void posix_cpu_timers_exit_group(void) { - cleanup_timers(&tsk->signal->posix_cputimers); + posix_cpu_timers_exit_task(); + + guard(spinlock_irq)(¤t->sighand->siglock); + cleanup_timers(¤t->signal->posix_cputimers); } /* @@ -1257,6 +1268,20 @@ static void posix_cpu_timers_work(struct callback_head *work) mutex_unlock(&cw->mutex); } +static inline void posix_cpu_timers_exit_work(void) +{ + /* Canceling the work is only valid for exit() but not for exec() */ + if (!(current->flags & PF_EXITING)) + return; + /* + * current->flags has PF_EXITING set so this can be done lockless and + * with interrupts enabled as PF_EXITING prevents the interrupt from + * scheduling the work. + */ + if (current->posix_cputimers_work.scheduled) + task_work_cancel(current, ¤t->posix_cputimers_work.work); +} + /* * Invoked from the posix-timer core when a cancel operation failed because * the timer is marked firing. The caller holds rcu_read_lock(), which @@ -1387,6 +1412,8 @@ static inline void __run_posix_cpu_timers(struct task_struct *tsk) lockdep_posixtimer_exit(); } +static inline void posix_cpu_timers_exit_work(void) { } + static void posix_cpu_timer_wait_running(struct k_itimer *timr) { cpu_relax(); diff --git a/kernel/time/posix-timers.c b/kernel/time/posix-timers.c index 8fb040a040cb..188dbedbffca 100644 --- a/kernel/time/posix-timers.c +++ b/kernel/time/posix-timers.c @@ -1116,17 +1116,20 @@ static void posixtimer_delete_timers(void) } } -void posixtimer_exit(void) +void posixtimer_exit(bool group_dead) { - hrtimer_cancel(¤t->signal->real_timer); - posixtimer_delete_timers(); + if (group_dead) { + hrtimer_cancel(¤t->signal->real_timer); + posix_cpu_timers_exit_group(); + posixtimer_delete_timers(); + } else { + posix_cpu_timers_exit_task(); + } } void posixtimer_exec(void) { - scoped_guard(spinlock_irq, ¤t->sighand->siglock) - posix_cpu_timers_exit(current); - + posix_cpu_timers_exit_task(); posixtimer_delete_timers(); flush_itimer_signals(); } diff --git a/kernel/time/posix-timers.h b/kernel/time/posix-timers.h index 4ea9611dd716..79fd7ea71046 100644 --- a/kernel/time/posix-timers.h +++ b/kernel/time/posix-timers.h @@ -51,3 +51,6 @@ int common_timer_set(struct k_itimer *timr, int flags, struct itimerspec64 *old_setting); void posix_timer_set_common(struct k_itimer *timer, struct itimerspec64 *new_setting); int common_timer_del(struct k_itimer *timer); + +void posix_cpu_timers_exit_task(void); +void posix_cpu_timers_exit_group(void); From d166a1cd901615280be050929e5a5c9e4d300a62 Mon Sep 17 00:00:00 2001 From: "Paul E. McKenney" Date: Fri, 18 Sep 2026 17:14:17 -0700 Subject: [PATCH 07/26] hrtimer: Mark data-racy accesses to hrtimer_sleeper ->task field The hrtimer_sleeper structure's ->task field is used as a flag to indicate that the associated hrtimer has expired. This means that the hrtimer handler can be storing to this field while other code is loading from it to check for expiry. Note that additional races appear for hrtimers that can be restarted, which could be argued to be a user error. However, that is no reason to let the compiler introduce additional confusion. Therefore, mark data-racy accesses to the hrtimer_sleeper ->task field using READ_ONCE() (using a new hrtimer_sleeper_task_get() access function) and WRITE_ONCE() (using a new hrtimer_sleeper_task_set() access function). KCSAN located this issue. Signed-off-by: Paul E. McKenney Signed-off-by: Thomas Gleixner Reviewed-by: Dmitry Ilvokhin Link: https://patch.msgid.link/20260919001428.3133388-1-paulmck@kernel.org --- include/linux/hrtimer.h | 8 ++++++++ kernel/time/hrtimer.c | 14 +++++++------- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/include/linux/hrtimer.h b/include/linux/hrtimer.h index 29072d89e5cb..d44549c0a2c1 100644 --- a/include/linux/hrtimer.h +++ b/include/linux/hrtimer.h @@ -320,6 +320,14 @@ extern int schedule_hrtimeout_range_clock(ktime_t *expires, const enum hrtimer_mode mode, clockid_t clock_id); extern int schedule_hrtimeout(ktime_t *expires, const enum hrtimer_mode mode); +static inline struct task_struct *hrtimer_sleeper_task_get(struct hrtimer_sleeper *sl) +{ + return READ_ONCE(sl->task); +} +static inline void hrtimer_sleeper_task_set(struct hrtimer_sleeper *sl, struct task_struct *t) +{ + WRITE_ONCE(sl->task, t); +} /* Soft interrupt function to run the hrtimer queues: */ extern void hrtimer_run_queues(void); diff --git a/kernel/time/hrtimer.c b/kernel/time/hrtimer.c index cbf1693c86b3..7b44a35478d4 100644 --- a/kernel/time/hrtimer.c +++ b/kernel/time/hrtimer.c @@ -2323,9 +2323,9 @@ void hrtimer_run_queues(void) static enum hrtimer_restart hrtimer_wakeup(struct hrtimer *timer) { struct hrtimer_sleeper *t = container_of(timer, struct hrtimer_sleeper, timer); - struct task_struct *task = t->task; + struct task_struct *task = hrtimer_sleeper_task_get(t); - t->task = NULL; + hrtimer_sleeper_task_set(t, NULL); if (task) wake_up_process(task); @@ -2354,7 +2354,7 @@ void hrtimer_sleeper_start_expires(struct hrtimer_sleeper *sl, enum hrtimer_mode /* If already expired, clear the task pointer and set current state to running */ if (!hrtimer_start_expires_user(&sl->timer, mode)) { - sl->task = NULL; + hrtimer_sleeper_task_set(sl, NULL); __set_current_state(TASK_RUNNING); } } @@ -2388,7 +2388,7 @@ static void __hrtimer_setup_sleeper(struct hrtimer_sleeper *sl, clockid_t clock_ } __hrtimer_setup(&sl->timer, hrtimer_wakeup, clock_id, mode); - sl->task = current; + hrtimer_sleeper_task_set(sl, current); } /** @@ -2432,17 +2432,17 @@ static int __sched do_nanosleep(struct hrtimer_sleeper *t, enum hrtimer_mode mod set_current_state(TASK_INTERRUPTIBLE|TASK_FREEZABLE); hrtimer_sleeper_start_expires(t, mode); - if (likely(t->task)) + if (likely(hrtimer_sleeper_task_get(t))) schedule(); hrtimer_cancel(&t->timer); mode = HRTIMER_MODE_ABS; - } while (t->task && !signal_pending(current)); + } while (hrtimer_sleeper_task_get(t) && !signal_pending(current)); __set_current_state(TASK_RUNNING); - if (!t->task) + if (!hrtimer_sleeper_task_get(t)) return 0; restart = ¤t->restart_block; From cefc1a24ace53c9b26f5adee1f60a439cc0d53a4 Mon Sep 17 00:00:00 2001 From: "Paul E. McKenney" Date: Fri, 18 Sep 2026 17:14:18 -0700 Subject: [PATCH 08/26] aio: Use accessor for hrtimer_sleeper ->task field The hrtimer_sleeper structure's ->task field is used as a flag to indicate that the associated hrtimer has expired. This means that the hrtimer handler can be storing to this field while other code is loading from it to check for expiry. Note that additional races appear for hrtimers that can be restarted, which could be argued to be a user error. However, that is no reason to let the compiler introduce additional confusion, and to this end, the hrtimer_sleeper_task_get() was introduced, use of which also has the benefit of avoiding open-code access to hrtimer_sleeper innards. KCSAN located this issue. Signed-off-by: Paul E. McKenney Signed-off-by: Thomas Gleixner Reviewed-by: Christian Brauner (Amutable) Acked-by: Jan Kara Link: https://patch.msgid.link/20260919001428.3133388-2-paulmck@kernel.org --- fs/aio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/aio.c b/fs/aio.c index d78acc69f487..8130d9e637ea 100644 --- a/fs/aio.c +++ b/fs/aio.c @@ -1402,7 +1402,7 @@ static long read_events(struct kioctx *ctx, long min_nr, long nr, w.min_nr = min_nr - ret; ret2 = prepare_to_wait_event(&ctx->wait, &w.w, TASK_INTERRUPTIBLE); - if (!ret2 && !t.task) + if (!ret2 && !hrtimer_sleeper_task_get(&t)) ret2 = -ETIME; if (aio_read_events(ctx, min_nr, nr, event, &ret) || ret2) From 66b29025b6761d9e0a8bd4d1b813b1f2c3c1a28e Mon Sep 17 00:00:00 2001 From: "Paul E. McKenney" Date: Fri, 18 Sep 2026 17:14:19 -0700 Subject: [PATCH 09/26] wait: Use accessor for hrtimer_sleeper ->task field The hrtimer_sleeper structure's ->task field is used as a flag to indicate that the associated hrtimer has expired. This means that the hrtimer handler can be storing to this field while other code is loading from it to check for expiry. Note that additional races appear for hrtimers that can be restarted, which could be argued to be a user error. However, that is no reason to let the compiler introduce additional confusion, and to this end, the hrtimer_sleeper_task_get() was introduced, use of which also has the benefit of avoiding open-code access to hrtimer_sleeper innards. Therefore, apply this accessor to the __wait_event_hrtimeout() macro. KCSAN located this issue. Signed-off-by: Paul E. McKenney Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260919001428.3133388-3-paulmck@kernel.org --- include/linux/wait.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/wait.h b/include/linux/wait.h index 7e215330199c..c2af98b0074d 100644 --- a/include/linux/wait.h +++ b/include/linux/wait.h @@ -556,7 +556,7 @@ do { \ } \ \ __ret = ___wait_event(wq_head, condition, state, 0, 0, \ - if (!__t.task) { \ + if (!hrtimer_sleeper_task_get(&__t)) { \ __ret = -ETIME; \ break; \ } \ From 851c30277918e1f1aad12394235d87c00afec464 Mon Sep 17 00:00:00 2001 From: "Paul E. McKenney" Date: Fri, 18 Sep 2026 17:14:20 -0700 Subject: [PATCH 10/26] io-uring/rw: Use accessor for hrtimer_sleeper ->task field The hrtimer_sleeper structure's ->task field is used as a flag to indicate that the associated hrtimer has expired. This means that the hrtimer handler can be storing to this field while other code is loading from it to check for expiry. Note that additional races appear for hrtimers that can be restarted, which could be argued to be a user error. However, that is no reason to let the compiler introduce additional confusion, and to this end, the hrtimer_sleeper_task_get() was introduced, use of which also has the benefit of avoiding open-code access to hrtimer_sleeper innards. Therefore, apply this accessor to the io_hybrid_iopoll_delay() function. KCSAN located this issue. Signed-off-by: Paul E. McKenney Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260919001428.3133388-4-paulmck@kernel.org --- io_uring/rw.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/io_uring/rw.c b/io_uring/rw.c index 432820f86251..d9b1e096f07b 100644 --- a/io_uring/rw.c +++ b/io_uring/rw.c @@ -1295,7 +1295,7 @@ static u64 io_hybrid_iopoll_delay(struct io_ring_ctx *ctx, struct io_kiocb *req) set_current_state(TASK_INTERRUPTIBLE); hrtimer_sleeper_start_expires(&timer, mode); - if (timer.task) + if (hrtimer_sleeper_task_get(&timer)) io_schedule(); hrtimer_cancel(&timer.timer); From fa3d486086d6948a6cf09b6d94cb1f56df2b1a6b Mon Sep 17 00:00:00 2001 From: "Paul E. McKenney" Date: Fri, 18 Sep 2026 17:14:21 -0700 Subject: [PATCH 11/26] futex: Use accessor for hrtimer_sleeper ->task field in waitwake.c MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The hrtimer_sleeper structure's ->task field is used as a flag to indicate that the associated hrtimer has expired. This means that the hrtimer handler can be storing to this field while other code is loading from it to check for expiry. Note that additional races appear for hrtimers that can be restarted, which could be argued to be a user error. However, that is no reason to let the compiler introduce additional confusion, and to this end, the hrtimer_sleeper_task_get() was introduced, use of which also has the benefit of avoiding open-code access to hrtimer_sleeper innards. Therefore, apply this accessor to kernel/futex/waitwake.c. KCSAN located this issue. Signed-off-by: Paul E. McKenney Signed-off-by: Thomas Gleixner Reviewed-by: André Almeida Reviewed-by: Dmitry Ilvokhin Link: https://patch.msgid.link/20260919001428.3133388-5-paulmck@kernel.org --- kernel/futex/waitwake.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/kernel/futex/waitwake.c b/kernel/futex/waitwake.c index d4483d15d30a..cf18309e5770 100644 --- a/kernel/futex/waitwake.c +++ b/kernel/futex/waitwake.c @@ -383,7 +383,7 @@ void futex_do_wait(struct futex_q *q, struct hrtimer_sleeper *timeout) * flagged for rescheduling. Only call schedule if there * is no timeout, or if it has yet to expire. */ - if (!timeout || timeout->task) + if (!timeout || hrtimer_sleeper_task_get(timeout)) schedule(); } __set_current_state(TASK_RUNNING); @@ -539,7 +539,7 @@ int futex_wait_multiple_setup(struct futex_vector *vs, int count, int *woken) static void futex_sleep_multiple(struct futex_vector *vs, unsigned int count, struct hrtimer_sleeper *to) { - if (to && !to->task) + if (to && !hrtimer_sleeper_task_get(to)) return; for (; count; count--, vs++) { @@ -590,7 +590,7 @@ int futex_wait_multiple(struct futex_vector *vs, unsigned int count, if (ret >= 0) return ret; - if (to && !to->task) + if (to && !hrtimer_sleeper_task_get(to)) return -ETIMEDOUT; else if (signal_pending(current)) return -ERESTARTSYS; @@ -725,7 +725,7 @@ int __futex_wait(u32 __user *uaddr, unsigned int flags, u32 val, if (!futex_unqueue(&q)) return 0; - if (to && !to->task) + if (to && !hrtimer_sleeper_task_get(to)) return -ETIMEDOUT; /* From 9b7bcd671e9c979f16c48589f4b9b6c684ab052f Mon Sep 17 00:00:00 2001 From: "Paul E. McKenney" Date: Fri, 18 Sep 2026 17:14:22 -0700 Subject: [PATCH 12/26] timers: Use accessor for hrtimer_sleeper ->task field in sleep_timeout.c The hrtimer_sleeper structure's ->task field is used as a flag to indicate that the associated hrtimer has expired. This means that the hrtimer handler can be storing to this field while other code is loading from it to check for expiry. Note that additional races appear for hrtimers that can be restarted, which could be argued to be a user error. However, that is no reason to let the compiler introduce additional confusion, and to this end, the hrtimer_sleeper_task_get() was introduced, use of which also has the benefit of avoiding open-code access to hrtimer_sleeper innards. Therefore, apply this accessor to schedule_hrtimeout_range_clock(). KCSAN located this issue. Signed-off-by: Paul E. McKenney Signed-off-by: Thomas Gleixner Reviewed-by: Dmitry Ilvokhin Link: https://patch.msgid.link/20260919001428.3133388-6-paulmck@kernel.org --- kernel/time/sleep_timeout.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kernel/time/sleep_timeout.c b/kernel/time/sleep_timeout.c index 3c90574bd904..ad8c415851ae 100644 --- a/kernel/time/sleep_timeout.c +++ b/kernel/time/sleep_timeout.c @@ -212,7 +212,7 @@ int __sched schedule_hrtimeout_range_clock(ktime_t *expires, u64 delta, hrtimer_set_expires_range_ns(&t.timer, *expires, delta); hrtimer_sleeper_start_expires(&t, mode); - if (likely(t.task)) + if (likely(hrtimer_sleeper_task_get(&t))) schedule(); hrtimer_cancel(&t.timer); @@ -220,7 +220,7 @@ int __sched schedule_hrtimeout_range_clock(ktime_t *expires, u64 delta, __set_current_state(TASK_RUNNING); - return !t.task ? 0 : -EINTR; + return !hrtimer_sleeper_task_get(&t) ? 0 : -EINTR; } EXPORT_SYMBOL_GPL(schedule_hrtimeout_range_clock); From 3990d196954a06cf9fb1bb4efd142d57c30a8e21 Mon Sep 17 00:00:00 2001 From: "Paul E. McKenney" Date: Fri, 18 Sep 2026 17:14:23 -0700 Subject: [PATCH 13/26] net: pktgen: Use accessor for hrtimer_sleeper ->task field The hrtimer_sleeper structure's ->task field is used as a flag to indicate that the associated hrtimer has expired. This means that the hrtimer handler can be storing to this field while other code is loading from it to check for expiry. Note that additional races appear for hrtimers that can be restarted, which could be argued to be a user error. However, that is no reason to let the compiler introduce additional confusion, and to this end, the hrtimer_sleeper_task_get() was introduced, use of which also has the benefit of avoiding open-code access to hrtimer_sleeper innards. Therefore, apply this accessor to the pktgen spin() function. KCSAN located this issue. Signed-off-by: Paul E. McKenney Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260919001428.3133388-7-paulmck@kernel.org --- net/core/pktgen.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/core/pktgen.c b/net/core/pktgen.c index 7f81aed46672..784d8398fb7a 100644 --- a/net/core/pktgen.c +++ b/net/core/pktgen.c @@ -2346,11 +2346,11 @@ static void spin(struct pktgen_dev *pkt_dev, ktime_t spin_until) set_current_state(TASK_INTERRUPTIBLE); hrtimer_sleeper_start_expires(&t, HRTIMER_MODE_ABS); - if (likely(t.task)) + if (likely(hrtimer_sleeper_task_get(&t))) schedule(); hrtimer_cancel(&t.timer); - } while (t.task && pkt_dev->running && !signal_pending(current)); + } while (hrtimer_sleeper_task_get(&t) && pkt_dev->running && !signal_pending(current)); __set_current_state(TASK_RUNNING); end_time = ktime_get(); } From 9cd2f4e304d921459a9348323aff518aee7fdc06 Mon Sep 17 00:00:00 2001 From: "Paul E. McKenney" Date: Fri, 18 Sep 2026 17:14:24 -0700 Subject: [PATCH 14/26] rtmutex: Use accessor for hrtimer_sleeper ->task field The hrtimer_sleeper structure's ->task field is used as a flag to indicate that the associated hrtimer has expired. This means that the hrtimer handler can be storing to this field while other code is loading from it to check for expiry. Note that additional races appear for hrtimers that can be restarted, which could be argued to be a user error. However, that is no reason to let the compiler introduce additional confusion, and to this end, the hrtimer_sleeper_task_get() was introduced, use of which also has the benefit of avoiding open-code access to hrtimer_sleeper innards. Therefore, apply this accessor to rt_mutex_slowlock_block(). KCSAN located this issue. Signed-off-by: Paul E. McKenney Signed-off-by: Thomas Gleixner Reviewed-by: Dmitry Ilvokhin Link: https://patch.msgid.link/20260919001428.3133388-8-paulmck@kernel.org --- kernel/locking/rtmutex.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/locking/rtmutex.c b/kernel/locking/rtmutex.c index 4728631ae719..5a9534c715b8 100644 --- a/kernel/locking/rtmutex.c +++ b/kernel/locking/rtmutex.c @@ -1644,7 +1644,7 @@ static int __sched rt_mutex_slowlock_block(struct rt_mutex_base *lock, break; } - if (timeout && !timeout->task) { + if (timeout && !hrtimer_sleeper_task_get(timeout)) { ret = -ETIMEDOUT; break; } From 7eed1771a6e84101c7cdf8b7d487f01b6420ba8c Mon Sep 17 00:00:00 2001 From: "Paul E. McKenney" Date: Fri, 18 Sep 2026 17:14:25 -0700 Subject: [PATCH 15/26] futex: Use accessor for hrtimer_sleeper ->task field in requeue MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The hrtimer_sleeper structure's ->task field is used as a flag to indicate that the associated hrtimer has expired. This means that the hrtimer handler can be storing to this field while other code is loading from it to check for expiry. Note that additional races appear for hrtimers that can be restarted, which could be argued to be a user error. However, that is no reason to let the compiler introduce additional confusion, and to this end, the hrtimer_sleeper_task_get() was introduced, use of which also has the benefit of avoiding open-code access to hrtimer_sleeper innards. Therefore, apply this accessor to handle_early_requeue_pi_wakeup(). KCSAN located this issue. Signed-off-by: Paul E. McKenney Signed-off-by: Thomas Gleixner Reviewed-by: André Almeida Reviewed-by: Dmitry Ilvokhin Link: https://patch.msgid.link/20260919001428.3133388-9-paulmck@kernel.org --- kernel/futex/requeue.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/futex/requeue.c b/kernel/futex/requeue.c index b3f4a4bccb12..842d852302dd 100644 --- a/kernel/futex/requeue.c +++ b/kernel/futex/requeue.c @@ -744,7 +744,7 @@ int handle_early_requeue_pi_wakeup(struct futex_hash_bucket *hb, /* Handle spurious wakeups gracefully */ ret = -EWOULDBLOCK; - if (timeout && !timeout->task) + if (timeout && !hrtimer_sleeper_task_get(timeout)) ret = -ETIMEDOUT; else if (signal_pending(current)) ret = -ERESTARTNOINTR; From 15f84398330ce1c0aa3e1e3edd0c915324bed26b Mon Sep 17 00:00:00 2001 From: "Paul E. McKenney" Date: Fri, 18 Sep 2026 17:14:26 -0700 Subject: [PATCH 16/26] hrtimer: Update hrtimer_resolution only if value changes Currently, the hrtimer_switch_to_hres() function unconditionally updates hrtimer_resolution to HIGH_RES_NSEC. This causes KCSAN false positives in kernels built with CONFIG_KCSAN_STRICT=y. It also causes unnecessary cache-line traffic on the many code paths that load hrtimer_resolution, which is especially regrettable because the value of hrtimer_resolution normally changes only once during the lifetime of a given kernel. Therefore, update the value of hrtimer_resolution to HIGH_RES_NSEC only if it does not already have that value. Signed-off-by: Paul E. McKenney Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260919001428.3133388-10-paulmck@kernel.org --- kernel/time/hrtimer.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/kernel/time/hrtimer.c b/kernel/time/hrtimer.c index 7b44a35478d4..205406460993 100644 --- a/kernel/time/hrtimer.c +++ b/kernel/time/hrtimer.c @@ -780,7 +780,8 @@ static void hrtimer_switch_to_hres(void) return; } base->hres_active = true; - hrtimer_resolution = HIGH_RES_NSEC; + if (hrtimer_resolution != HIGH_RES_NSEC) + hrtimer_resolution = HIGH_RES_NSEC; tick_setup_sched_timer(true); /* "Retrigger" the interrupt to get things going */ From ad80926d780340ab08799cdb38be010e0dce8ec0 Mon Sep 17 00:00:00 2001 From: "Paul E. McKenney" Date: Fri, 18 Sep 2026 17:14:27 -0700 Subject: [PATCH 17/26] hrtimer: Mark the hrtimer_sleeper structure's ->task field __private The hrtimer_sleeper structure's ->task field is now used only by the hrtimer_sleeper_task_get() and hrtimer_sleeper_task_set() functions, and there is no reason for it to be directly accessed anywhere else. Therefore, mark this field __private and use ACCESS_PRIVATE() in hrtimer_sleeper_task_get() and hrtimer_sleeper_task_set(). Suggested-by: Thomas Gleixner Signed-off-by: Paul E. McKenney Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260919001428.3133388-11-paulmck@kernel.org --- include/linux/hrtimer.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/linux/hrtimer.h b/include/linux/hrtimer.h index d44549c0a2c1..cad8482337cb 100644 --- a/include/linux/hrtimer.h +++ b/include/linux/hrtimer.h @@ -72,7 +72,7 @@ enum hrtimer_mode { */ struct hrtimer_sleeper { struct hrtimer timer; - struct task_struct *task; + struct task_struct *__private task; }; static inline void hrtimer_set_expires(struct hrtimer *timer, ktime_t time) @@ -322,11 +322,11 @@ extern int schedule_hrtimeout_range_clock(ktime_t *expires, extern int schedule_hrtimeout(ktime_t *expires, const enum hrtimer_mode mode); static inline struct task_struct *hrtimer_sleeper_task_get(struct hrtimer_sleeper *sl) { - return READ_ONCE(sl->task); + return READ_ONCE(ACCESS_PRIVATE(sl, task)); } static inline void hrtimer_sleeper_task_set(struct hrtimer_sleeper *sl, struct task_struct *t) { - WRITE_ONCE(sl->task, t); + WRITE_ONCE(ACCESS_PRIVATE(sl, task), t); } /* Soft interrupt function to run the hrtimer queues: */ From 5dffe33bfdafcc768d090c55479f515585509ffa Mon Sep 17 00:00:00 2001 From: "Paul E. McKenney" Date: Fri, 18 Sep 2026 17:14:28 -0700 Subject: [PATCH 18/26] hrtimer: Apply READ_ONCE() to lockless base->running loads Updates to base->running are protected by the hrtimer base lock, but some loads are lockless. Therefore, prevent compiler mischief by applying READ_ONCE() to the lockless loads. KCSAN located this issue. Signed-off-by: Paul E. McKenney Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260919001428.3133388-12-paulmck@kernel.org --- kernel/time/hrtimer.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/kernel/time/hrtimer.c b/kernel/time/hrtimer.c index 205406460993..17dd38a6cee7 100644 --- a/kernel/time/hrtimer.c +++ b/kernel/time/hrtimer.c @@ -2004,7 +2004,7 @@ bool hrtimer_active(const struct hrtimer *timer) base = READ_ONCE(timer->base); seq = raw_read_seqcount_begin(&base->seq); - if (timer->is_queued || base->running == timer) + if (timer->is_queued || READ_ONCE(base->running) == timer) return true; } while (read_seqcount_retry(&base->seq, seq) || base != READ_ONCE(timer->base)); @@ -2041,7 +2041,7 @@ static void __run_hrtimer(struct hrtimer_cpu_base *cpu_base, struct hrtimer_cloc lockdep_assert_held(&cpu_base->lock); debug_hrtimer_deactivate(timer); - base->running = timer; + WRITE_ONCE(base->running, timer); /* * Separate the ->running assignment from the ->is_queued assignment. @@ -2100,7 +2100,7 @@ static void __run_hrtimer(struct hrtimer_cpu_base *cpu_base, struct hrtimer_cloc raw_write_seqcount_barrier(&base->seq); WARN_ON_ONCE(base->running != timer); - base->running = NULL; + WRITE_ONCE(base->running, NULL); } static void __hrtimer_run_queues(struct hrtimer_cpu_base *cpu_base, ktime_t now, From 1159ad0a6aaa414dc726a33ccdcb702752d1b8eb Mon Sep 17 00:00:00 2001 From: "Paul E. McKenney" Date: Fri, 18 Sep 2026 17:25:22 -0700 Subject: [PATCH 19/26] timers: Mark racy updates to hlist_node::pprev field hlist_unhashed_lockless() locklessly samples the hlist_node structure's pprev field, but detach_timer(), hlist_move_list(), and hlist_splice_init() all use plain C-language stores to update this field, despite the "The READ_ONCE() is paired with the various WRITE_ONCE() in hlist helpers that are defined below" in the hlist_unhashed_lockless() header comment. Therefore use WRITE_ONCE() for these hlist_node::pprev updates. KCSAN located this issue. Signed-off-by: Paul E. McKenney Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260919002523.3133928-1-paulmck@kernel.org --- include/linux/list.h | 6 +++--- kernel/time/timer.c | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/include/linux/list.h b/include/linux/list.h index 77fb62f79928..e3753695e76c 100644 --- a/include/linux/list.h +++ b/include/linux/list.h @@ -1172,7 +1172,7 @@ static inline void hlist_move_list(struct hlist_head *old, { new->first = old->first; if (new->first) - new->first->pprev = &new->first; + WRITE_ONCE(new->first->pprev, &new->first); old->first = NULL; } @@ -1189,10 +1189,10 @@ static inline void hlist_splice_init(struct hlist_head *from, struct hlist_head *to) { if (to->first) - to->first->pprev = &last->next; + WRITE_ONCE(to->first->pprev, &last->next); last->next = to->first; to->first = from->first; - from->first->pprev = &to->first; + WRITE_ONCE(from->first->pprev, &to->first); from->first = NULL; } diff --git a/kernel/time/timer.c b/kernel/time/timer.c index ae9abf14688e..42afdcb229d8 100644 --- a/kernel/time/timer.c +++ b/kernel/time/timer.c @@ -890,7 +890,7 @@ static inline void detach_timer(struct timer_list *timer, bool clear_pending) __hlist_del(entry); if (clear_pending) - entry->pprev = NULL; + WRITE_ONCE(entry->pprev, NULL); entry->next = LIST_POISON2; } From bb41ece16463b76b4d73fc47e6648fd823236765 Mon Sep 17 00:00:00 2001 From: "Paul E. McKenney" Date: Fri, 18 Sep 2026 17:25:23 -0700 Subject: [PATCH 20/26] timers/migration: Mark racy updates to tmigr_event::ignore field The tmigr_event structure's ignore field is sometimes accessed locklessly, but the __tmigr_cpu_activate(), tmigr_cpu_new_timer(), and __tmigr_cpu_deactivate() functions do not mark accesses to this field. Therefore, use READ_ONCE() for the tmigr_cpu_new_timer() function's lockless load and WRITE_ONCE() for the __tmigr_cpu_activate() and __tmigr_cpu_deactivate() functions' stores. KCSAN located this issue. Signed-off-by: Paul E. McKenney Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260919002523.3133928-2-paulmck@kernel.org --- kernel/time/timer_migration.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/kernel/time/timer_migration.c b/kernel/time/timer_migration.c index 059d43355e65..f920e73fff51 100644 --- a/kernel/time/timer_migration.c +++ b/kernel/time/timer_migration.c @@ -715,7 +715,7 @@ static void __tmigr_cpu_activate(struct tmigr_cpu *tmc) trace_tmigr_cpu_active(tmc); - tmc->cpuevt.ignore = true; + WRITE_ONCE(tmc->cpuevt.ignore, true); WRITE_ONCE(tmc->wakeup, KTIME_MAX); walk_groups(&tmigr_active_up, &data, tmc); @@ -1258,7 +1258,7 @@ u64 tmigr_cpu_new_timer(u64 nextexp) ret = READ_ONCE(tmc->wakeup); if (nextexp != KTIME_MAX) { if (nextexp != tmc->cpuevt.nextevt.expires || - tmc->cpuevt.ignore) { + READ_ONCE(tmc->cpuevt.ignore)) { ret = tmigr_new_timer(tmc, nextexp); /* * Make sure the reevaluation of timers in idle path @@ -1362,7 +1362,7 @@ static u64 __tmigr_cpu_deactivate(struct tmigr_cpu *tmc, u64 nextexp) * or CPU goes offline. */ if (nextexp != KTIME_MAX) - tmc->cpuevt.ignore = false; + WRITE_ONCE(tmc->cpuevt.ignore, false); walk_groups(&tmigr_inactive_up, &data, tmc); return data.firstexp; From bc5b66c300b87544e6861b8dff8c45958603cba5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Thu, 3 Sep 2026 09:11:47 +0200 Subject: [PATCH 21/26] timekeeping: Use READ_ONCE/WRITE_ONCE() for ktime_sec to prevent tearing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The timekeeper update path uses a bulk memcpy() to synchronize the timekeeper structure, which is not guaranteed to be atomic. This allows for torn reads in ktime_get_seconds() which bypasses the sequence counter protection for performance. To prevent reading a torn ktime_sec value, enforce atomic-like access by using WRITE_ONCE() for the critical field before the bulk memcpy() in timekeeping_update_from_shadow(). Correspondingly, use READ_ONCE() in ktime_get_seconds() to ensure a fresh, consistent load from memory. The same was done for xtime_sec and ktime_get_real_seconds() in commit d7fc133bf91f ("timekeeping: Use READ_ONCE/WRITE_ONCE() for xtime_sec to prevent tearing"). Signed-off-by: Thomas Weißschuh (Schneider Electric) Signed-off-by: Thomas Gleixner Acked-by: John Stultz Link: https://patch.msgid.link/20260903-timekeeping-ktime_sec-v1-1-b33017536aa9@linutronix.de --- kernel/time/timekeeping.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/kernel/time/timekeeping.c b/kernel/time/timekeeping.c index ea2e6e55f37b..d54c4d303db6 100644 --- a/kernel/time/timekeeping.c +++ b/kernel/time/timekeeping.c @@ -861,8 +861,10 @@ static void timekeeping_update_from_shadow(struct tk_data *tkd, unsigned int act * * Write xtime_sec first so that even if the memcpy() tears the store * data integrity is provided for ktime_get_real_seconds(). + * The same goes for ktime_sec and ktime_get_seconds(). */ 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); } @@ -1169,7 +1171,7 @@ time64_t ktime_get_seconds(void) struct timekeeper *tk = &tk_core.timekeeper; WARN_ON(timekeeping_suspended); - return tk->ktime_sec; + return READ_ONCE(tk->ktime_sec); } EXPORT_SYMBOL_GPL(ktime_get_seconds); From 2927f7ca78448781e11a4623d347cd653c28adf6 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Fri, 25 Sep 2026 18:57:01 +0200 Subject: [PATCH 22/26] time: Prevent time64_to_tm() day truncation on 32-bit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit time64_to_tm() accepts a 64-bit seconds value, but stores the quotient in long. On a 32-bit kernel the day count therefore wraps when it reaches 2^31, even though the corresponding year remains representable in struct tm. This is reachable when formatting externally supplied timestamps. For example, NILFS recovery copies the little-endian 64-bit ss_create field from an on-disk segment summary into time64_t. Its sysfs attributes then print that value with %ptTs, whose formatter calls time64_to_tm(). A corrupted image can consequently produce an architecture-dependent printed date. Keep the day quotient in s64 while leaving the seconds-within-day remainder as long. Use div_s64_rem() for the weekday calculation so 32-bit builds do not require compiler runtime division helpers. Fixes: e6c2682a1da3 ("time: Add time64_to_tm()") Signed-off-by: Karl Mehltretter Signed-off-by: Thomas Gleixner Assisted-by: LLM Reviewed-by: Thomas Weißschuh Link: https://patch.msgid.link/20260925165702.51677-2-kmehltretter@gmail.com --- kernel/time/timeconv.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/kernel/time/timeconv.c b/kernel/time/timeconv.c index 59b922c826e7..aed3af950fa0 100644 --- a/kernel/time/timeconv.c +++ b/kernel/time/timeconv.c @@ -49,8 +49,9 @@ void time64_to_tm(time64_t totalsecs, int offset, struct tm *result) u32 u32tmp, day_of_century, year_of_century, day_of_year, month, day; u64 u64tmp, udays, century, year; bool is_Jan_or_Feb, is_leap_year; - long days, rem; int remainder; + long rem; + s64 days; days = div_s64_rem(totalsecs, SECS_PER_DAY, &remainder); rem = remainder; @@ -70,7 +71,8 @@ void time64_to_tm(time64_t totalsecs, int offset, struct tm *result) result->tm_sec = rem % 60; /* January 1, 1970 was a Thursday. */ - result->tm_wday = (4 + days) % 7; + div_s64_rem(days + 4, 7, &remainder); + result->tm_wday = remainder; if (result->tm_wday < 0) result->tm_wday += 7; From 3945c4a3ea151f777b4b098ebeecd2f85c20244f Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Fri, 25 Sep 2026 18:57:02 +0200 Subject: [PATCH 23/26] time/kunit: Add time64_to_tm() case beyond 32-bit day count MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The existing date range test covers +/-80,000 years around the epoch, far below the day count at which a 32-bit long wraps (2^31 days, roughly 5.88 million years). Add a case at exactly 2^31 days after the epoch. Without the preceding fix a 32-bit kernel returns tm_year=-5879541 and wrong month, day, day-of-year and weekday; with it the result matches 64-bit kernels. Signed-off-by: Karl Mehltretter Signed-off-by: Thomas Gleixner Assisted-by: LLM Reviewed-by: Thomas Weißschuh Link: https://patch.msgid.link/20260925165702.51677-3-kmehltretter@gmail.com --- kernel/time/time_test.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/kernel/time/time_test.c b/kernel/time/time_test.c index 1b99180da288..8b718767b3ba 100644 --- a/kernel/time/time_test.c +++ b/kernel/time/time_test.c @@ -87,8 +87,24 @@ static void time64_to_tm_test_date_range(struct kunit *test) } } +static void time64_to_tm_test_wide_day_count(struct kunit *test) +{ + /* 2^31 days: the first count that does not fit in a 32-bit long. */ + time64_t timestamp = (1LL << 31) * 86400; + struct tm result; + + time64_to_tm(timestamp, 0, &result); + + KUNIT_EXPECT_EQ(test, result.tm_year, 5879680); + KUNIT_EXPECT_EQ(test, result.tm_mon, 6); + KUNIT_EXPECT_EQ(test, result.tm_mday, 12); + KUNIT_EXPECT_EQ(test, result.tm_yday, 193); + KUNIT_EXPECT_EQ(test, result.tm_wday, 6); +} + static struct kunit_case time_test_cases[] = { KUNIT_CASE_SLOW(time64_to_tm_test_date_range), + KUNIT_CASE(time64_to_tm_test_wide_day_count), {} }; From a252cb93e1568d9f0754b9b2ac4829d896daa14e Mon Sep 17 00:00:00 2001 From: Eva Kurchatova Date: Thu, 17 Sep 2026 04:52:31 +0300 Subject: [PATCH 24/26] selftests: timers: Count what tick is worth in the drift estimate raw_skew measures the drift between CLOCK_MONOTONIC and CLOCK_MONOTONIC_RAW and compares it against the adjustment adjtimex() reports, but for the latter it reads only tx.freq. The kernel takes the tick value into the adjustment as well, worth a microsecond of the tick length per unit, a hundred ppm each, and a time synchronisation daemon does put the coarse part of its correction there. Where it does, the two numbers cannot meet: # Estimating clock drift: -51.724(est) 48.277(act) [FAILED] On the machine measured, chronyd held freq at 48.278 ppm with tick at 9999, so the correction really applied is -51.722 ppm, which is what the clocks show. The skip for an externally adjusted clock does not catch this: the clock is steady, the offset is zero, and neither freq nor tick moves during the run. Count what tick is worth alongside freq. On the same machine: # Estimating clock drift: -51.724(est) -51.723(act) [OK] Signed-off-by: Eva Kurchatova Signed-off-by: Thomas Gleixner Acked-by: John Stultz Link: https://patch.msgid.link/20260917015237.2462954-1-eva.kurchatova@virtuozzo.com --- tools/testing/selftests/timers/raw_skew.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tools/testing/selftests/timers/raw_skew.c b/tools/testing/selftests/timers/raw_skew.c index 0c87a8fb0d7f..dfca3e2d50d2 100644 --- a/tools/testing/selftests/timers/raw_skew.c +++ b/tools/testing/selftests/timers/raw_skew.c @@ -91,6 +91,7 @@ int main(int argc, char **argv) { struct timespec mon, raw, start, end; long long delta1, delta2, interval, eppm, ppm; + long tick_nom; struct timex tx1, tx2; setbuf(stdout, NULL); @@ -129,6 +130,17 @@ int main(int argc, char **argv) /* Avg the two actual freq samples adjtimex gave us */ ppm = (long long)(tx1.freq + tx2.freq) * 1000 / 2; ppm = shift_right(ppm, 16); + + /* + * The tick value holds the coarse part of the adjustment, a + * microsecond of the tick length per unit, and time sync daemons + * do put part of their correction there. What it is worth has to + * be counted in as well, or a clock disciplined through it looks + * off by a hundred ppm a unit against what the two clocks show. + */ + tick_nom = USEC_PER_SEC / sysconf(_SC_CLK_TCK); + ppm += (long long)(tx1.tick + tx2.tick - 2 * tick_nom) * + (USEC_PER_SEC / tick_nom) * 1000 / 2; printf(" %lld.%i(act)", ppm/1000, abs((int)(ppm%1000))); if (llabs(eppm - ppm) > 1000) { From b03638013add0609d35aaa8291548dea92cb9484 Mon Sep 17 00:00:00 2001 From: Eva Kurchatova Date: Thu, 17 Sep 2026 04:52:32 +0300 Subject: [PATCH 25/26] selftests: timers: Measure the CPU timers on the clock they count Commit b00385b8d081 ("selftests/posix_timers: Use CLOCK_THREAD_CPUTIME_ID for ITIMER_PROF measurements") gave ITIMER_PROF the clock it counts, as measuring a CPU timer against the wall says nothing on a machine where the task does not have a CPU to itself. The other CPU timers are still measured against CLOCK_REALTIME and fail the same way. With four busy loops on the two CPUs of a test machine: not ok 2 ITIMER_VIRTUAL not ok 5 timer_create() per CLOCK_THREAD_CPUTIME_ID not ok 6 timer_create() per CLOCK_PROCESS_CPUTIME_ID ITIMER_VIRTUAL counts the time the task spends in userspace, so take CLOCK_THREAD_CPUTIME_ID for it as well; the loop it runs stays in userspace, so what that clock adds for system time is far inside the half a second the check allows. check_timer_create() arms its timer on the clock it is given, so measure on that one. The test then passes whole on that machine, 19 of 19, busy or idle. Signed-off-by: Eva Kurchatova Signed-off-by: Thomas Gleixner Acked-by: John Stultz Link: https://patch.msgid.link/20260917015237.2462954-2-eva.kurchatova@virtuozzo.com --- tools/testing/selftests/timers/posix_timers.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tools/testing/selftests/timers/posix_timers.c b/tools/testing/selftests/timers/posix_timers.c index a92d4b957747..52b7289e1450 100644 --- a/tools/testing/selftests/timers/posix_timers.c +++ b/tools/testing/selftests/timers/posix_timers.c @@ -113,9 +113,10 @@ static void check_itimer(int which, const char *name) done = 0; - if (which == ITIMER_VIRTUAL) + if (which == ITIMER_VIRTUAL) { + clock_id = CLOCK_THREAD_CPUTIME_ID; signal(SIGVTALRM, sig_handler); - else if (which == ITIMER_PROF) { + } else if (which == ITIMER_PROF) { clock_id = CLOCK_THREAD_CPUTIME_ID; signal(SIGPROF, sig_handler); } @@ -148,7 +149,7 @@ static void check_timer_create(int which) struct itimerspec val = { .it_value.tv_sec = DELAY, }; - int clock_id = CLOCK_REALTIME; + int clock_id = which; timer_t id; done = 0; From 348f54c435bf3177baa78201952810b255d44b30 Mon Sep 17 00:00:00 2001 From: Qingshuang Fu Date: Tue, 25 Aug 2026 14:46:05 +0800 Subject: [PATCH 26/26] selftests/timers: clocksource-switch: Fix unchecked open()/read() get_clocksources() and get_cur_clocksource() open and read the clocksource sysfs files without checking the return values and without NUL-terminating the buffer. If open() fails, read() is called on fd -1 and returns -1, which is stored in a size_t as SIZE_MAX. The token-scanning loop in get_clocksources() then walks past the uninitialized stack buffer, reading and writing out of bounds. Even on success the buffer is not NUL-terminated, so a fully read buffer makes the inner scan run past the data, and get_cur_clocksource() lets change_clocksource() call strlen() on a non-terminated buffer. Check the open()/read() results, use ssize_t for the read length, NUL-terminate the buffer, and close the fd in get_cur_clocksource(). Fixes: 7290ce1423c3 ("selftests/timers: Add clocksource-switch test from timetest suite") Signed-off-by: Qingshuang Fu Signed-off-by: Thomas Gleixner Acked-by: John Stultz Link: https://patch.msgid.link/20260825064605.472889-1-fffsqian@163.com --- .../selftests/timers/clocksource-switch.c | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/tools/testing/selftests/timers/clocksource-switch.c b/tools/testing/selftests/timers/clocksource-switch.c index db62a764c29e..2e86f56d953e 100644 --- a/tools/testing/selftests/timers/clocksource-switch.c +++ b/tools/testing/selftests/timers/clocksource-switch.c @@ -40,16 +40,23 @@ int get_clocksources(char list[][30]) { int fd, i; - size_t size; + ssize_t size; char buf[512]; char *head, *tmp; fd = open("/sys/devices/system/clocksource/clocksource0/available_clocksource", O_RDONLY); + if (fd < 0) + return 0; - size = read(fd, buf, 512); + size = read(fd, buf, sizeof(buf) - 1); close(fd); + if (size <= 0) + return 0; + + buf[size] = '\0'; + for (i = 0; i < 10; i++) list[i][0] = '\0'; @@ -74,11 +81,21 @@ int get_clocksources(char list[][30]) int get_cur_clocksource(char *buf, size_t size) { + ssize_t len; int fd; fd = open("/sys/devices/system/clocksource/clocksource0/current_clocksource", O_RDONLY); + if (fd < 0) + return -1; - size = read(fd, buf, size); + len = read(fd, buf, size - 1); + + close(fd); + + if (len <= 0) + return -1; + + buf[len] = '\0'; return 0; }