powerpc/64s: Fix HV NMI vs HV interrupt recoverability test
authorNicholas Piggin <npiggin@gmail.com>
Tue, 26 Feb 2019 08:51:07 +0000 (18:51 +1000)
committerMichael Ellerman <mpe@ellerman.id.au>
Tue, 26 Feb 2019 12:28:24 +0000 (23:28 +1100)
HV interrupts that use HSRR registers do not enter with MSR[RI] clear,
but their entry code is not recoverable vs NMI, due to shared use of
HSPRG1 as a scratch register to save r13.

This means that a system reset or machine check that hits in HSRR
interrupt entry can cause r13 to be silently corrupted.

Fix this by marking NMIs non-recoverable if they land in HV interrupt
ranges.

Signed-off-by: Nicholas Piggin <npiggin@gmail.com>
Signed-off-by: Michael Ellerman <mpe@ellerman.id.au>
arch/powerpc/include/asm/asm-prototypes.h
arch/powerpc/include/asm/nmi.h
arch/powerpc/kernel/exceptions-64s.S
arch/powerpc/kernel/mce.c
arch/powerpc/kernel/traps.c

index 1484df6779ab42afbd69716ea7e3667d6b819537..e01f31fb0865ce9252142d274ad62ffbaae03232 100644 (file)
@@ -51,6 +51,14 @@ int exit_vmx_usercopy(void);
 int enter_vmx_ops(void);
 void *exit_vmx_ops(void *dest);
 
+/* Exceptions */
+#ifdef CONFIG_PPC_POWERNV
+extern unsigned long real_trampolines_start;
+extern unsigned long real_trampolines_end;
+extern unsigned long virt_trampolines_start;
+extern unsigned long virt_trampolines_end;
+#endif
+
 /* Traps */
 long machine_check_early(struct pt_regs *regs);
 long hmi_exception_realmode(struct pt_regs *regs);
index bd9ba8defd7258ab6e853be0c39d7290f9f02393..84b4cfe73edddec59abd875d6e807dce7c251a2e 100644 (file)
@@ -14,4 +14,6 @@ extern void arch_trigger_cpumask_backtrace(const cpumask_t *mask,
 #define arch_trigger_cpumask_backtrace arch_trigger_cpumask_backtrace
 #endif
 
+extern void hv_nmi_check_nonrecoverable(struct pt_regs *regs);
+
 #endif /* _ASM_NMI_H */
index b179b8b5d3f001d7fa498e96993421eb898fee44..76442af8c1913147cddf17631f8c3c4a8f5b25a0 100644 (file)
@@ -68,6 +68,14 @@ OPEN_FIXED_SECTION(real_vectors,        0x0100, 0x1900)
 OPEN_FIXED_SECTION(real_trampolines,    0x1900, 0x4000)
 OPEN_FIXED_SECTION(virt_vectors,        0x4000, 0x5900)
 OPEN_FIXED_SECTION(virt_trampolines,    0x5900, 0x7000)
+
+#ifdef CONFIG_PPC_POWERNV
+       .globl real_trampolines_start
+       .globl real_trampolines_end
+       .globl virt_trampolines_start
+       .globl virt_trampolines_end
+#endif
+
 #if defined(CONFIG_PPC_PSERIES) || defined(CONFIG_PPC_POWERNV)
 /*
  * Data area reserved for FWNMI option.
index d501b48f287ece90a39f86be1370c222d74f1a8e..b5fec1f9751a13a04d2b280c6e84c3eaba8dbd47 100644 (file)
@@ -31,6 +31,7 @@
 
 #include <asm/machdep.h>
 #include <asm/mce.h>
+#include <asm/nmi.h>
 
 static DEFINE_PER_CPU(int, mce_nest_count);
 static DEFINE_PER_CPU(struct machine_check_event[MAX_MC_EVT], mce_event);
@@ -490,6 +491,8 @@ long machine_check_early(struct pt_regs *regs)
 {
        long handled = 0;
 
+       hv_nmi_check_nonrecoverable(regs);
+
        /*
         * See if platform is capable of handling machine check.
         */
index b25bc8af7d38400d7b123eaabe794f47e16393d9..eee8f843f3d67f7d8d9888e7c1c47c51e324a0e6 100644 (file)
@@ -369,6 +369,70 @@ void _exception(int signr, struct pt_regs *regs, int code, unsigned long addr)
        force_sig_fault(signr, code, (void __user *)addr, current);
 }
 
+/*
+ * The interrupt architecture has a quirk in that the HV interrupts excluding
+ * the NMIs (0x100 and 0x200) do not clear MSR[RI] at entry. The first thing
+ * that an interrupt handler must do is save off a GPR into a scratch register,
+ * and all interrupts on POWERNV (HV=1) use the HSPRG1 register as scratch.
+ * Therefore an NMI can clobber an HV interrupt's live HSPRG1 without noticing
+ * that it is non-reentrant, which leads to random data corruption.
+ *
+ * The solution is for NMI interrupts in HV mode to check if they originated
+ * from these critical HV interrupt regions. If so, then mark them not
+ * recoverable.
+ *
+ * An alternative would be for HV NMIs to use SPRG for scratch to avoid the
+ * HSPRG1 clobber, however this would cause guest SPRG to be clobbered. Linux
+ * guests should always have MSR[RI]=0 when its scratch SPRG is in use, so
+ * that would work. However any other guest OS that may have the SPRG live
+ * and MSR[RI]=1 could encounter silent corruption.
+ *
+ * Builds that do not support KVM could take this second option to increase
+ * the recoverability of NMIs.
+ */
+void hv_nmi_check_nonrecoverable(struct pt_regs *regs)
+{
+#ifdef CONFIG_PPC_POWERNV
+       unsigned long kbase = (unsigned long)_stext;
+       unsigned long nip = regs->nip;
+
+       if (!(regs->msr & MSR_RI))
+               return;
+       if (!(regs->msr & MSR_HV))
+               return;
+       if (regs->msr & MSR_PR)
+               return;
+
+       /*
+        * Now test if the interrupt has hit a range that may be using
+        * HSPRG1 without having RI=0 (i.e., an HSRR interrupt). The
+        * problem ranges all run un-relocated. Test real and virt modes
+        * at the same time by droping the high bit of the nip (virt mode
+        * entry points still have the +0x4000 offset).
+        */
+       nip &= ~0xc000000000000000ULL;
+       if ((nip >= 0x500 && nip < 0x600) || (nip >= 0x4500 && nip < 0x4600))
+               goto nonrecoverable;
+       if ((nip >= 0x980 && nip < 0xa00) || (nip >= 0x4980 && nip < 0x4a00))
+               goto nonrecoverable;
+       if ((nip >= 0xe00 && nip < 0xec0) || (nip >= 0x4e00 && nip < 0x4ec0))
+               goto nonrecoverable;
+       if ((nip >= 0xf80 && nip < 0xfa0) || (nip >= 0x4f80 && nip < 0x4fa0))
+               goto nonrecoverable;
+       /* Trampoline code runs un-relocated so subtract kbase. */
+       if (nip >= real_trampolines_start - kbase &&
+                       nip < real_trampolines_end - kbase)
+               goto nonrecoverable;
+       if (nip >= virt_trampolines_start - kbase &&
+                       nip < virt_trampolines_end - kbase)
+               goto nonrecoverable;
+       return;
+
+nonrecoverable:
+       regs->msr &= ~MSR_RI;
+#endif
+}
+
 void system_reset_exception(struct pt_regs *regs)
 {
        /*
@@ -379,6 +443,8 @@ void system_reset_exception(struct pt_regs *regs)
        if (!nested)
                nmi_enter();
 
+       hv_nmi_check_nonrecoverable(regs);
+
        __this_cpu_inc(irq_stat.sreset_irqs);
 
        /* See if any machine dependent calls */