x86/tsc: Verify TSC_ADJUST from idle
authorThomas Gleixner <tglx@linutronix.de>
Sat, 19 Nov 2016 13:47:37 +0000 (13:47 +0000)
committerThomas Gleixner <tglx@linutronix.de>
Tue, 29 Nov 2016 18:23:16 +0000 (19:23 +0100)
When entering idle, it's a good oportunity to verify that the TSC_ADJUST
MSR has not been tampered with (BIOS hiding SMM cycles). If tampering is
detected, emit a warning and restore it to the previous value.

This is especially important for machines, which mark the TSC reliable
because there is no watchdog clocksource available (SoCs).

This is not sufficient for HPC (NOHZ_FULL) situations where a CPU never
goes idle, but adding a timer to do the check periodically is not an option
either. On a machine, which has this issue, the check triggeres right
during boot, so there is a decent chance that the sysadmin will notice.

Rate limit the check to once per second and warn only once per cpu.

Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Reviewed-by: Ingo Molnar <mingo@kernel.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Yinghai Lu <yinghai@kernel.org>
Cc: Borislav Petkov <bp@alien8.de>
Link: http://lkml.kernel.org/r/20161119134017.732180441@linutronix.de
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
arch/x86/include/asm/tsc.h
arch/x86/kernel/process.c
arch/x86/kernel/tsc_sync.c

index 1ec0595867d904cf1face496e087fe47157e29b8..b896e9ee65bc6d35975de61e7226b1ddb36b2c1f 100644 (file)
@@ -50,8 +50,10 @@ extern void check_tsc_sync_target(void);
 
 #ifdef CONFIG_X86_TSC
 extern void tsc_store_and_check_tsc_adjust(void);
+extern void tsc_verify_tsc_adjust(void);
 #else
 static inline void tsc_store_and_check_tsc_adjust(void) { }
+static inline void tsc_verify_tsc_adjust(void) { }
 #endif
 
 extern int notsc_setup(char *);
index 0888a879120fcb948ec894d96ddee17c36997572..4fe5dc84da4faba4ac706238c636ea60b3cfcebc 100644 (file)
@@ -277,6 +277,7 @@ void exit_idle(void)
 
 void arch_cpu_idle_enter(void)
 {
+       tsc_verify_tsc_adjust();
        local_touch_nmi();
        enter_idle();
 }
index bd2bd5e89d96d95692d6a879d9aa3bf4f8c2fb4e..f713e84d1cb4cc290c08cb3009d1498b5c65c5c2 100644 (file)
 #include <asm/tsc.h>
 
 struct tsc_adjust {
-       s64     bootval;
-       s64     adjusted;
+       s64             bootval;
+       s64             adjusted;
+       unsigned long   nextcheck;
+       bool            warned;
 };
 
 static DEFINE_PER_CPU(struct tsc_adjust, tsc_adjust);
 
+void tsc_verify_tsc_adjust(void)
+{
+       struct tsc_adjust *adj = this_cpu_ptr(&tsc_adjust);
+       s64 curval;
+
+       if (!boot_cpu_has(X86_FEATURE_TSC_ADJUST))
+               return;
+
+       /* Rate limit the MSR check */
+       if (time_before(jiffies, adj->nextcheck))
+               return;
+
+       adj->nextcheck = jiffies + HZ;
+
+       rdmsrl(MSR_IA32_TSC_ADJUST, curval);
+       if (adj->adjusted == curval)
+               return;
+
+       /* Restore the original value */
+       wrmsrl(MSR_IA32_TSC_ADJUST, adj->adjusted);
+
+       if (!adj->warned) {
+               pr_warn(FW_BUG "TSC ADJUST differs: CPU%u %lld --> %lld. Restoring\n",
+                       smp_processor_id(), adj->adjusted, curval);
+               adj->warned = true;
+       }
+}
+
 #ifndef CONFIG_SMP
 void __init tsc_store_and_check_tsc_adjust(void)
 {
@@ -40,6 +70,7 @@ void __init tsc_store_and_check_tsc_adjust(void)
        rdmsrl(MSR_IA32_TSC_ADJUST, bootval);
        cur->bootval = bootval;
        cur->adjusted = bootval;
+       cur->nextcheck = jiffies + HZ;
        pr_info("TSC ADJUST: Boot CPU0: %lld\n", bootval);
 }
 
@@ -59,6 +90,8 @@ void tsc_store_and_check_tsc_adjust(void)
 
        rdmsrl(MSR_IA32_TSC_ADJUST, bootval);
        cur->bootval = bootval;
+       cur->nextcheck = jiffies + HZ;
+       cur->warned = false;
 
        /*
         * Check whether this CPU is the first in a package to come up. In