Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

timekeeping: Make delta calculation overflow safe

Kernel timekeeping is designed to keep the change in cycles (since the last
timer interrupt) below max_cycles, which prevents multiplication overflow
when converting cycles to nanoseconds. However, if timer interrupts stop,
the calculation will eventually overflow.

Add protection against that. In timekeeping_cycles_to_ns() calculation,
check against max_cycles, falling back to a slower higher precision
calculation. In timekeeping_forward_now(), process delta in chunks of at
most max_cycles.

Suggested-by: Thomas Gleixner <tglx@linutronix.de>
Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Link: https://lore.kernel.org/r/20240325064023.2997-18-adrian.hunter@intel.com

authored by

Adrian Hunter and committed by
Thomas Gleixner
fcf190c3 e809a80a

+29 -11
+29 -11
kernel/time/timekeeping.c
··· 364 364 } 365 365 366 366 /* Timekeeper helper functions. */ 367 + static noinline u64 delta_to_ns_safe(const struct tk_read_base *tkr, u64 delta) 368 + { 369 + return mul_u64_u32_add_u64_shr(delta, tkr->mult, tkr->xtime_nsec, tkr->shift); 370 + } 371 + 367 372 static inline u64 timekeeping_cycles_to_ns(const struct tk_read_base *tkr, u64 cycles) 368 373 { 369 374 /* Calculate the delta since the last update_wall_time() */ 370 375 u64 mask = tkr->mask, delta = (cycles - tkr->cycle_last) & mask; 371 376 372 - if (IS_ENABLED(CONFIG_CLOCKSOURCE_VALIDATE_LAST_CYCLE)) { 373 - /* 374 - * Handle clocksource inconsistency between CPUs to prevent 375 - * time from going backwards by checking for the MSB of the 376 - * mask being set in the delta. 377 - */ 378 - if (unlikely(delta & ~(mask >> 1))) 379 - return tkr->xtime_nsec >> tkr->shift; 377 + /* 378 + * This detects the case where the delta overflows the multiplication 379 + * with tkr->mult. 380 + */ 381 + if (unlikely(delta > tkr->clock->max_cycles)) { 382 + if (IS_ENABLED(CONFIG_CLOCKSOURCE_VALIDATE_LAST_CYCLE)) { 383 + /* 384 + * Handle clocksource inconsistency between CPUs to prevent 385 + * time from going backwards by checking for the MSB of the 386 + * mask being set in the delta. 387 + */ 388 + if (unlikely(delta & ~(mask >> 1))) 389 + return tkr->xtime_nsec >> tkr->shift; 390 + } 391 + 392 + return delta_to_ns_safe(tkr, delta); 380 393 } 381 394 382 395 return ((delta * tkr->mult) + tkr->xtime_nsec) >> tkr->shift; ··· 802 789 tk->tkr_mono.cycle_last = cycle_now; 803 790 tk->tkr_raw.cycle_last = cycle_now; 804 791 805 - tk->tkr_mono.xtime_nsec += delta * tk->tkr_mono.mult; 806 - tk->tkr_raw.xtime_nsec += delta * tk->tkr_raw.mult; 792 + while (delta > 0) { 793 + u64 max = tk->tkr_mono.clock->max_cycles; 794 + u64 incr = delta < max ? delta : max; 807 795 808 - tk_normalize_xtime(tk); 796 + tk->tkr_mono.xtime_nsec += incr * tk->tkr_mono.mult; 797 + tk->tkr_raw.xtime_nsec += incr * tk->tkr_raw.mult; 798 + tk_normalize_xtime(tk); 799 + delta -= incr; 800 + } 809 801 } 810 802 811 803 /**