From: Adrian Hunter <adrian.hunter@intel.com> Date: 2024-03-25 06:40:39
Hi
Kernel timekeeping calculates a clock value by keeping a base value and
adding the number of nanoseconds since that time. Those nanoseconds are
calculated from the clocksource delta. Then periodically, the base value is
moved forwards (refer timekeeping_advance()) which is done by the local
timer interrupt handler. It is designed such that there will always be a
timer interrupt before the delta becomes big enough to overflow the 64-bit
multiplication used in the conversion of delta to nanoseconds (refer
timekeeping_delta_to_ns()). Obviously if timer interrupts are stopped, then
the multiplication does eventually overflow.
Timekeeping multiplication overflow results in a "time loop", typically
cycling about every 15 minutes with x86 TSC, for example starting at 10:00:
10:00, 10:01, 10:02 ... 10:15, 10:00, 10:01, ... 10:15, 10:00, 10:01 ...
Because a VMM can deliberately stop timer interrupts for a guest, a virtual
machine can be exposed to this issue.
TDX maintains a monotonically increasing virtual TSC for a TDX guest, so
the overflow is allowing a backwards movement of timekeeping that would not
happen otherwise.
It is considered this could break security of cryptographic protocols that
rely on the timestamps for freshness / replay protection, and consequently
the kernel should prevent such a time loop.
Handle multiplication overflows by falling back to higher precision
calculation when the possibility of an overflow is detected.
Extend the facility also to VDSO, dependent on new config option
GENERIC_VDSO_OVERFLOW_PROTECT which is selected by x86 only, so other
architectures are not affected. The result is a calculation that has
similar performance as before. Most machines showed performance benefit,
except Skylake-based hardware such as Intel Kaby Lake which was seen <1%
worse.
Changes in V2:
vdso: Consolidate vdso_calc_delta()
Keep powerpc comment about mask
Move ifdef out of function
vdso: Consolidate nanoseconds calculation
Adjusted due to changes in "vdso: Consolidate vdso_calc_delta()"
Adrian Hunter (19):
vdso: Consolidate vdso_calc_delta()
vdso: Consolidate nanoseconds calculation
vdso: Add CONFIG_GENERIC_VDSO_OVERFLOW_PROTECT
math64: Tidy mul_u64_u32_shr()
vdso: math64: Provide mul_u64_u32_add_u64_shr()
vdso: Add vdso_data::max_cycles
vdso: Make delta calculation overflow safe
x86/vdso: Make delta calculation overflow safe
timekeeping: Move timekeeping helper functions
timekeeping: Rename fast_tk_get_delta_ns() to __timekeeping_get_ns()
timekeeping: Tidy timekeeping_cycles_to_ns() slightly
timekeeping: Reuse timekeeping_cycles_to_ns()
timekeeping: Refactor timekeeping helpers
timekeeping: Consolidate timekeeping helpers
timekeeping: Fold in timekeeping_delta_to_ns()
timekeeping: Prepare timekeeping_cycles_to_ns() for overflow safety
timekeeping: Make delta calculation overflow safe
timekeeping: Let timekeeping_cycles_to_ns() handle both under and overflow
clocksource: Make watchdog and suspend-timing multiplication overflow safe
arch/powerpc/include/asm/vdso/gettimeofday.h | 26 +++----
arch/s390/include/asm/vdso/gettimeofday.h | 7 +-
arch/x86/Kconfig | 1 +
arch/x86/include/asm/vdso/gettimeofday.h | 42 +++++++----
include/linux/math64.h | 8 +-
include/vdso/datapage.h | 4 +
include/vdso/math64.h | 38 ++++++++++
kernel/time/clocksource.c | 42 +++++------
kernel/time/timekeeping.c | 106 ++++++++++++++-------------
kernel/time/vsyscall.c | 6 ++
lib/vdso/Kconfig | 7 ++
lib/vdso/gettimeofday.c | 55 +++++++++-----
12 files changed, 208 insertions(+), 134 deletions(-)
Regards
Adrian
From: Adrian Hunter <adrian.hunter@intel.com> Date: 2024-03-25 06:40:45
Consolidate vdso_calc_delta(), in preparation for further simplification.
Suggested-by: Thomas Gleixner <redacted>
Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
---
Changes in V2:
Keep powerpc comment about mask
Move ifdef out of function
arch/powerpc/include/asm/vdso/gettimeofday.h | 26 +++++++++-----------
arch/s390/include/asm/vdso/gettimeofday.h | 7 ++----
lib/vdso/gettimeofday.c | 9 ++++++-
3 files changed, 21 insertions(+), 21 deletions(-)
@@ -118,7 +116,7 @@ static __always_inline int do_hres(const struct vdso_data *vd, clockid_t clk,struct__kernel_timespec*ts){conststructvdso_timestamp*vdso_ts=&vd->basetime[clk];-u64cycles,last,sec,ns;+u64cycles,sec,ns;u32seq;/* Allows to compile the high resolution parts out */
From: Adrian Hunter <adrian.hunter@intel.com> Date: 2024-03-25 06:40:58
Add CONFIG_GENERIC_VDSO_OVERFLOW_PROTECT in preparation to add
multiplication overflow protection to the VDSO time getter functions.
Suggested-by: Thomas Gleixner <redacted>
Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
---
lib/vdso/Kconfig | 7 +++++++
1 file changed, 7 insertions(+)
From: Adrian Hunter <adrian.hunter@intel.com> Date: 2024-03-25 06:41:05
Put together declaration and initialization of local variables.
Suggested-by: Thomas Gleixner <redacted>
Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
---
include/linux/math64.h | 6 +-----
1 file changed, 1 insertion(+), 5 deletions(-)
From: Adrian Hunter <adrian.hunter@intel.com> Date: 2024-03-25 06:41:11
Provide mul_u64_u32_add_u64_shr() which is a calculation that will be used
by timekeeping and VDSO.
Place #include <vdso/math64.h> after #include <asm/div64.h> to allow
architecture-specific overrides, at least for the kernel.
Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
---
include/linux/math64.h | 2 +-
include/vdso/math64.h | 38 ++++++++++++++++++++++++++++++++++++++
2 files changed, 39 insertions(+), 1 deletion(-)
From: Adrian Hunter <adrian.hunter@intel.com> Date: 2024-03-25 06:41:18
Add vdso_data::max_cycles in preparation to use it to detect potential
multiplication overflow.
Suggested-by: Thomas Gleixner <redacted>
Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
---
include/vdso/datapage.h | 4 ++++
kernel/time/vsyscall.c | 6 ++++++
2 files changed, 10 insertions(+)
From: Adrian Hunter <adrian.hunter@intel.com> Date: 2024-03-25 06:41:26
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, enabled by config option
CONFIG_GENERIC_VDSO_OVERFLOW_PROTECT. Check against max_cycles, falling
back to a slower higher precision calculation.
Suggested-by: Thomas Gleixner <redacted>
Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
---
lib/vdso/gettimeofday.c | 17 ++++++++++++++++-
1 file changed, 16 insertions(+), 1 deletion(-)
From: Adrian Hunter <adrian.hunter@intel.com> Date: 2024-03-25 06:41:31
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. Select GENERIC_VDSO_OVERFLOW_PROTECT so that
max_cycles is made available in the VDSO data page. Check against
max_cycles, falling back to a slower higher precision calculation. Take
advantage of the opportunity to move masking and negative motion check
into the slow path.
The result is a calculation that has similar performance as before. Newer
machines showed performance benefit, whereas older Skylake-based hardware
such as Intel Kaby Lake was seen <1% worse.
Suggested-by: Thomas Gleixner <redacted>
Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
---
arch/x86/Kconfig | 1 +
arch/x86/include/asm/vdso/gettimeofday.h | 29 +++++++++++++++++-------
2 files changed, 22 insertions(+), 8 deletions(-)
@@ -381,6 +381,23 @@ static inline u64 timekeeping_delta_to_ns(const struct tk_read_base *tkr, u64 dereturnnsec;}+staticinlineu64timekeeping_cycles_to_ns(conststructtk_read_base*tkr,u64cycles)+{+u64delta;++/* calculate the delta since the last update_wall_time */+delta=clocksource_delta(cycles,tkr->cycle_last,tkr->mask);+returntimekeeping_delta_to_ns(tkr,delta);+}++static__always_inlineu64fast_tk_get_delta_ns(structtk_read_base*tkr)+{+u64delta,cycles=tk_clock_read(tkr);++delta=clocksource_delta(cycles,tkr->cycle_last,tkr->mask);+returntimekeeping_delta_to_ns(tkr,delta);+}+staticinlineu64timekeeping_get_ns(conststructtk_read_base*tkr){u64delta;
@@ -389,15 +406,6 @@ static inline u64 timekeeping_get_ns(const struct tk_read_base *tkr)returntimekeeping_delta_to_ns(tkr,delta);}-staticinlineu64timekeeping_cycles_to_ns(conststructtk_read_base*tkr,u64cycles)-{-u64delta;--/* calculate the delta since the last update_wall_time */-delta=clocksource_delta(cycles,tkr->cycle_last,tkr->mask);-returntimekeeping_delta_to_ns(tkr,delta);-}-/***update_fast_timekeeper-UpdatethefastandNMIsafemonotonictimekeeper.*@tkr:Timekeepingreadoutbasefromwhichwetaketheupdate
From: Adrian Hunter <adrian.hunter@intel.com> Date: 2024-03-25 06:41:44
Rename fast_tk_get_delta_ns() to __timekeeping_get_ns() to prepare for its
reuse as a general timekeeping helper function.
Suggested-by: Thomas Gleixner <redacted>
Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
---
kernel/time/timekeeping.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
From: Adrian Hunter <adrian.hunter@intel.com> Date: 2024-03-25 06:41:51
Put together declaration and initialization of the local variable 'delta'.
Suggested-by: Thomas Gleixner <redacted>
Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
---
kernel/time/timekeeping.c | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
@@ -383,10 +383,9 @@ static inline u64 timekeeping_delta_to_ns(const struct tk_read_base *tkr, u64 destaticinlineu64timekeeping_cycles_to_ns(conststructtk_read_base*tkr,u64cycles){-u64delta;+/* Calculate the delta since the last update_wall_time() */+u64delta=clocksource_delta(cycles,tkr->cycle_last,tkr->mask);-/* calculate the delta since the last update_wall_time */-delta=clocksource_delta(cycles,tkr->cycle_last,tkr->mask);returntimekeeping_delta_to_ns(tkr,delta);}
From: Adrian Hunter <adrian.hunter@intel.com> Date: 2024-03-25 06:42:14
Simplify use of timekeeping sanity checking, in preparation for
consolidating timekeeping helpers. This works towards eliminating
timekeeping_delta_to_ns() in favour of timekeeping_cycles_to_ns().
Suggested-by: Thomas Gleixner <redacted>
Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
---
kernel/time/timekeeping.c | 20 ++++++--------------
1 file changed, 6 insertions(+), 14 deletions(-)
From: Adrian Hunter <adrian.hunter@intel.com> Date: 2024-03-25 06:42:17
Consolidate timekeeping helpers, making use of timekeeping_cycles_to_ns()
in preference to directly using timekeeping_delta_to_ns().
Suggested-by: Thomas Gleixner <redacted>
Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
---
kernel/time/timekeeping.c | 14 ++++++++------
1 file changed, 8 insertions(+), 6 deletions(-)
@@ -266,22 +268,22 @@ static inline u64 timekeeping_debug_get_delta(const struct tk_read_base *tkr)*/if(unlikely((~delta&mask)<(mask>>3))){tk->underflow_seen=1;-delta=0;+now=last;}/* Cap delta value to the max_cycles values to avoid mult overflows */if(unlikely(delta>max)){tk->overflow_seen=1;-delta=tkr->clock->max_cycles;+now=last+max;}-returndelta;+returntimekeeping_cycles_to_ns(tkr,now);}#elsestaticinlinevoidtimekeeping_check_update(structtimekeeper*tk,u64offset){}-staticinlineu64timekeeping_debug_get_delta(conststructtk_read_base*tkr)+staticinlineu64timekeeping_debug_get_ns(conststructtk_read_base*tkr){BUG();}
From: Adrian Hunter <adrian.hunter@intel.com> Date: 2024-03-25 06:42:19
timekeeping_delta_to_ns() is now called only from
timekeeping_cycles_to_ns(), and it is not useful otherwise. Simplify by
folding it into timekeeping_cycles_to_ns().
Suggested-by: Thomas Gleixner <redacted>
Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
---
kernel/time/timekeeping.c | 13 +------------
1 file changed, 1 insertion(+), 12 deletions(-)
From: Adrian Hunter <adrian.hunter@intel.com> Date: 2024-03-25 06:42:25
Open code clocksource_delta() in timekeeping_cycles_to_ns() so that
overflow safety can be added efficiently.
Suggested-by: Thomas Gleixner <redacted>
Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
---
kernel/time/timekeeping.c | 12 +++++++++++-
1 file changed, 11 insertions(+), 1 deletion(-)
@@ -367,7 +367,17 @@ static void tk_setup_internals(struct timekeeper *tk, struct clocksource *clock)staticinlineu64timekeeping_cycles_to_ns(conststructtk_read_base*tkr,u64cycles){/* Calculate the delta since the last update_wall_time() */-u64delta=clocksource_delta(cycles,tkr->cycle_last,tkr->mask);+u64mask=tkr->mask,delta=(cycles-tkr->cycle_last)&mask;++if(IS_ENABLED(CONFIG_CLOCKSOURCE_VALIDATE_LAST_CYCLE)){+/*+*HandleclocksourceinconsistencybetweenCPUstoprevent+*timefromgoingbackwardsbycheckingfortheMSBofthe+*maskbeingsetinthedelta.+*/+if(unlikely(delta&~(mask>>1)))+returntkr->xtime_nsec>>tkr->shift;+}return((delta*tkr->mult)+tkr->xtime_nsec)>>tkr->shift;}
From: Adrian Hunter <adrian.hunter@intel.com> Date: 2024-03-25 06:42:33
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 <redacted>
Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
---
kernel/time/timekeeping.c | 40 ++++++++++++++++++++++++++++-----------
1 file changed, 29 insertions(+), 11 deletions(-)
From: Adrian Hunter <adrian.hunter@intel.com> Date: 2024-03-25 06:42:39
For the case !CONFIG_CLOCKSOURCE_VALIDATE_LAST_CYCLE, forego overflow
protection in the range (mask << 1) < delta <= mask, and interpret it
always as an inconsistency between CPU clock values. That allows
slightly neater code, and it is on a slow path so has no effect on
performance.
Suggested-by: Thomas Gleixner <redacted>
Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
---
kernel/time/timekeeping.c | 31 +++++++++++++------------------
1 file changed, 13 insertions(+), 18 deletions(-)
@@ -266,17 +266,14 @@ static inline u64 timekeeping_debug_get_ns(const struct tk_read_base *tkr)*Trytocatchunderflowsbycheckingifweareseeingsmall*mask-relativenegativevalues.*/-if(unlikely((~delta&mask)<(mask>>3))){+if(unlikely((~delta&mask)<(mask>>3)))tk->underflow_seen=1;-now=last;-}-/* Cap delta value to the max_cycles values to avoid mult overflows */-if(unlikely(delta>max)){+/* Check for multiplication overflows */+if(unlikely(delta>max))tk->overflow_seen=1;-now=last+max;-}+/* timekeeping_cycles_to_ns() handles both under and overflow */returntimekeeping_cycles_to_ns(tkr,now);}#else
From: Adrian Hunter <adrian.hunter@intel.com> Date: 2024-03-25 06:42:44
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 clocksource_cyc2ns() calculation will eventually overflow.
Add protection against that. Simplify by folding together
clocksource_delta() and clocksource_cyc2ns() into cycles_to_nsec_safe().
Check against max_cycles, falling back to a slower higher precision
calculation.
Suggested-by: Thomas Gleixner <redacted>
Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
---
kernel/time/clocksource.c | 42 +++++++++++++++++++--------------------
1 file changed, 20 insertions(+), 22 deletions(-)
@@ -456,12 +462,8 @@ static void clocksource_watchdog(struct timer_list *unused)continue;}-delta=clocksource_delta(wdnow,cs->wd_last,watchdog->mask);-wd_nsec=clocksource_cyc2ns(delta,watchdog->mult,-watchdog->shift);--delta=clocksource_delta(csnow,cs->cs_last,cs->mask);-cs_nsec=clocksource_cyc2ns(delta,cs->mult,cs->shift);+wd_nsec=cycles_to_nsec_safe(watchdog,cs->wd_last,wdnow);+cs_nsec=cycles_to_nsec_safe(cs,cs->cs_last,csnow);wdlast=cs->wd_last;/* save these in case we print them */cslast=cs->cs_last;cs->cs_last=csnow;
On Mon, Mar 25, 2024, at 07:40, Adrian Hunter wrote:
Extend the facility also to VDSO, dependent on new config option
GENERIC_VDSO_OVERFLOW_PROTECT which is selected by x86 only, so other
architectures are not affected. The result is a calculation that has
similar performance as before. Most machines showed performance benefit,
except Skylake-based hardware such as Intel Kaby Lake which was seen <1%
worse.
I've read through the series, and this pretty much all makes sense,
nice work!
There are a few patches that just rearrange the local variable
declarations to save a few lines, and I don't see those as an
improvement, but they also don't hurt aside from distracting
slightly from the real changes.
Arnd
From: Peter Zijlstra <peterz@infradead.org> Date: 2024-04-24 15:11:50
On Mon, Mar 25, 2024 at 08:40:08AM +0200, Adrian Hunter wrote:
Put together declaration and initialization of local variables.
Suggested-by: Thomas Gleixner <redacted>
Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
---
Nothing wrong with this patch, but it is highly unlikely this code is
actually tested much. Most (sane) architectures will use the __int128
version.