From e4cb6a2eae5723747670371a115ba5ed0ef2c9cb Mon Sep 17 00:00:00 2001 From: Tony Luck Date: Sun, 1 Nov 2020 20:53:09 +0800 Subject: [PATCH 2499/2944] x86/mce: Decode a kernel instruction to determine if it is copying from user fix #31317281 commit 300638101329e8f1569115f3d7197ef5ef754a3a upstream Backport summary: Backport to kernel 4.19.57 to enhance MCA-R for copyin 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 Signed-off-by: Youquan Song Signed-off-by: Tony Luck Signed-off-by: Borislav Petkov Link: https://lkml.kernel.org/r/20201006210910.21062-7-tony.luck@intel.com Signed-off-by: Youquan Song Signed-off-by: Wetp Zhang Reviewed-by: Artie Ding --- arch/x86/include/asm/traps.h | 2 ++ arch/x86/kernel/cpu/mcheck/mce-severity.c | 54 +++++++++++++++++++++++++++++-- arch/x86/kernel/cpu/mcheck/mce.c | 11 +++++-- arch/x86/mm/fault.c | 2 +- 4 files changed, 63 insertions(+), 6 deletions(-) diff --git a/arch/x86/include/asm/traps.h b/arch/x86/include/asm/traps.h index 12700ab..7f1f133 100644 --- a/arch/x86/include/asm/traps.h +++ b/arch/x86/include/asm/traps.h @@ -109,6 +109,8 @@ static inline int get_si_code(unsigned long condition) asmlinkage void smp_deferred_error_interrupt(struct pt_regs *regs); #endif +bool fault_in_kernel_space(unsigned long address); + #ifdef CONFIG_VMAP_STACK void __noreturn handle_stack_overflow(const char *message, struct pt_regs *regs, diff --git a/arch/x86/kernel/cpu/mcheck/mce-severity.c b/arch/x86/kernel/cpu/mcheck/mce-severity.c index 443dcb8..2edf2e5 100644 --- a/arch/x86/kernel/cpu/mcheck/mce-severity.c +++ b/arch/x86/kernel/cpu/mcheck/mce-severity.c @@ -14,7 +14,9 @@ #include #include #include - +#include +#include +#include #include "mce-internal.h" #include @@ -219,6 +221,47 @@ #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 (probe_kernel_read(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 @@ -236,10 +279,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; } diff --git a/arch/x86/kernel/cpu/mcheck/mce.c b/arch/x86/kernel/cpu/mcheck/mce.c index 33d30bf..7be08b8 100644 --- a/arch/x86/kernel/cpu/mcheck/mce.c +++ b/arch/x86/kernel/cpu/mcheck/mce.c @@ -1181,13 +1181,18 @@ 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); 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, current); + } else { + pr_err("Memory error not recovered"); + kill_me_now(cb); + } } static void queue_task_work(struct mce *m, int kill_it) diff --git a/arch/x86/mm/fault.c b/arch/x86/mm/fault.c index c61acf6..1b17c23 100644 --- a/arch/x86/mm/fault.c +++ b/arch/x86/mm/fault.c @@ -1197,7 +1197,7 @@ static int spurious_fault_check(unsigned long error_code, pte_t *pte) return 0; } -static int fault_in_kernel_space(unsigned long address) +bool fault_in_kernel_space(unsigned long address) { return address >= TASK_SIZE_MAX; } -- 1.8.3.1