x86/mce: Decode a kernel instruction to determine if it is copying from user
authorTony Luck <tony.luck@intel.com>
Tue, 6 Oct 2020 21:09:10 +0000 (14:09 -0700)
committerBorislav Petkov <bp@suse.de>
Wed, 7 Oct 2020 09:32:40 +0000 (11:32 +0200)
All instructions copying data between kernel and user memory
are tagged with either _ASM_EXTABLE_UA or _ASM_EXTABLE_CPY
entries in the exception table. ex_fault_handler_type() returns
EX_HANDLER_UACCESS for both of these.

Recovery is only possible when the machine check was triggered
on a read from user memory. In this case the same strategy for
recovery applies as if the user had made the access in ring3. If
the fault was in kernel memory while copying to user there is no
current recovery plan.

For MOV and MOVZ instructions a full decode of the instruction
is done to find the source address. For MOVS instructions
the source address is in the %rsi register. The function
fault_in_kernel_space() determines whether the source address is
kernel or user, upgrade it from "static" so it can be used here.

Co-developed-by: Youquan Song <youquan.song@intel.com>
Signed-off-by: Youquan Song <youquan.song@intel.com>
Signed-off-by: Tony Luck <tony.luck@intel.com>
Signed-off-by: Borislav Petkov <bp@suse.de>
Link: https://lkml.kernel.org/r/20201006210910.21062-7-tony.luck@intel.com
arch/x86/include/asm/traps.h
arch/x86/kernel/cpu/mce/core.c
arch/x86/kernel/cpu/mce/severity.c
arch/x86/mm/fault.c

index 714b1a30e7b09afb8e37f90ff0308fd9207dfa18..df0b7bfc1234951e3b58024957618671420e62f4 100644 (file)
@@ -35,6 +35,8 @@ extern int panic_on_unrecovered_nmi;
 
 void math_emulate(struct math_emu_info *);
 
+bool fault_in_kernel_space(unsigned long address);
+
 #ifdef CONFIG_VMAP_STACK
 void __noreturn handle_stack_overflow(const char *message,
                                      struct pt_regs *regs,
index 5c423c4bab6828299190071c46c0fc6d5dac60da..3d6e1bfa4f9d71c647434bce34ae2586965a0f0e 100644 (file)
@@ -1250,14 +1250,19 @@ static void kill_me_maybe(struct callback_head *cb)
        if (!p->mce_ripv)
                flags |= MF_MUST_KILL;
 
-       if (!memory_failure(p->mce_addr >> PAGE_SHIFT, flags)) {
+       if (!memory_failure(p->mce_addr >> PAGE_SHIFT, flags) &&
+           !(p->mce_kflags & MCE_IN_KERNEL_COPYIN)) {
                set_mce_nospec(p->mce_addr >> PAGE_SHIFT, p->mce_whole_page);
                sync_core();
                return;
        }
 
-       pr_err("Memory error not recovered");
-       kill_me_now(cb);
+       if (p->mce_vaddr != (void __user *)-1l) {
+               force_sig_mceerr(BUS_MCEERR_AR, p->mce_vaddr, PAGE_SHIFT);
+       } else {
+               pr_err("Memory error not recovered");
+               kill_me_now(cb);
+       }
 }
 
 static void queue_task_work(struct mce *m, int kill_it)
index c6494e6579c1e90fd5798652bfd1d14178173354..83df991314c531bea4c1b90b98d2f8f61fab66ab 100644 (file)
@@ -13,6 +13,9 @@
 
 #include <asm/mce.h>
 #include <asm/intel-family.h>
+#include <asm/traps.h>
+#include <asm/insn.h>
+#include <asm/insn-eval.h>
 
 #include "internal.h"
 
@@ -212,6 +215,47 @@ static struct severity {
 #define mc_recoverable(mcg) (((mcg) & (MCG_STATUS_RIPV|MCG_STATUS_EIPV)) == \
                                (MCG_STATUS_RIPV|MCG_STATUS_EIPV))
 
+static bool is_copy_from_user(struct pt_regs *regs)
+{
+       u8 insn_buf[MAX_INSN_SIZE];
+       struct insn insn;
+       unsigned long addr;
+
+       if (copy_from_kernel_nofault(insn_buf, (void *)regs->ip, MAX_INSN_SIZE))
+               return false;
+
+       kernel_insn_init(&insn, insn_buf, MAX_INSN_SIZE);
+       insn_get_opcode(&insn);
+       if (!insn.opcode.got)
+               return false;
+
+       switch (insn.opcode.value) {
+       /* MOV mem,reg */
+       case 0x8A: case 0x8B:
+       /* MOVZ mem,reg */
+       case 0xB60F: case 0xB70F:
+               insn_get_modrm(&insn);
+               insn_get_sib(&insn);
+               if (!insn.modrm.got || !insn.sib.got)
+                       return false;
+               addr = (unsigned long)insn_get_addr_ref(&insn, regs);
+               break;
+       /* REP MOVS */
+       case 0xA4: case 0xA5:
+               addr = regs->si;
+               break;
+       default:
+               return false;
+       }
+
+       if (fault_in_kernel_space(addr))
+               return false;
+
+       current->mce_vaddr = (void __user *)addr;
+
+       return true;
+}
+
 /*
  * If mcgstatus indicated that ip/cs on the stack were
  * no good, then "m->cs" will be zero and we will have
@@ -229,10 +273,17 @@ static int error_context(struct mce *m, struct pt_regs *regs)
 
        if ((m->cs & 3) == 3)
                return IN_USER;
+       if (!mc_recoverable(m->mcgstatus))
+               return IN_KERNEL;
 
        t = ex_get_fault_handler_type(m->ip);
-       if (mc_recoverable(m->mcgstatus) && t == EX_HANDLER_FAULT) {
+       if (t == EX_HANDLER_FAULT) {
+               m->kflags |= MCE_IN_KERNEL_RECOV;
+               return IN_KERNEL_RECOV;
+       }
+       if (t == EX_HANDLER_UACCESS && regs && is_copy_from_user(regs)) {
                m->kflags |= MCE_IN_KERNEL_RECOV;
+               m->kflags |= MCE_IN_KERNEL_COPYIN;
                return IN_KERNEL_RECOV;
        }
 
index 35f1498e98324f50a8d6ca4f670a5e76470409a5..88ae443e4e5f9ee006a128ddee532f4b29abd49f 100644 (file)
@@ -1081,7 +1081,7 @@ access_error(unsigned long error_code, struct vm_area_struct *vma)
        return 0;
 }
 
-static int fault_in_kernel_space(unsigned long address)
+bool fault_in_kernel_space(unsigned long address)
 {
        /*
         * On 64-bit systems, the vsyscall page is at an address above