Skip to content

Commit 3006381

Browse files
aeglsuryasaimadhu
authored andcommitted
x86/mce: Decode a kernel instruction to determine if it is copying from user
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
1 parent c0ab7ff commit 3006381

4 files changed

Lines changed: 63 additions & 5 deletions

File tree

arch/x86/include/asm/traps.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ extern int panic_on_unrecovered_nmi;
3535

3636
void math_emulate(struct math_emu_info *);
3737

38+
bool fault_in_kernel_space(unsigned long address);
39+
3840
#ifdef CONFIG_VMAP_STACK
3941
void __noreturn handle_stack_overflow(const char *message,
4042
struct pt_regs *regs,

arch/x86/kernel/cpu/mce/core.c

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1250,14 +1250,19 @@ static void kill_me_maybe(struct callback_head *cb)
12501250
if (!p->mce_ripv)
12511251
flags |= MF_MUST_KILL;
12521252

1253-
if (!memory_failure(p->mce_addr >> PAGE_SHIFT, flags)) {
1253+
if (!memory_failure(p->mce_addr >> PAGE_SHIFT, flags) &&
1254+
!(p->mce_kflags & MCE_IN_KERNEL_COPYIN)) {
12541255
set_mce_nospec(p->mce_addr >> PAGE_SHIFT, p->mce_whole_page);
12551256
sync_core();
12561257
return;
12571258
}
12581259

1259-
pr_err("Memory error not recovered");
1260-
kill_me_now(cb);
1260+
if (p->mce_vaddr != (void __user *)-1l) {
1261+
force_sig_mceerr(BUS_MCEERR_AR, p->mce_vaddr, PAGE_SHIFT);
1262+
} else {
1263+
pr_err("Memory error not recovered");
1264+
kill_me_now(cb);
1265+
}
12611266
}
12621267

12631268
static void queue_task_work(struct mce *m, int kill_it)

arch/x86/kernel/cpu/mce/severity.c

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313

1414
#include <asm/mce.h>
1515
#include <asm/intel-family.h>
16+
#include <asm/traps.h>
17+
#include <asm/insn.h>
18+
#include <asm/insn-eval.h>
1619

1720
#include "internal.h"
1821

@@ -212,6 +215,47 @@ static struct severity {
212215
#define mc_recoverable(mcg) (((mcg) & (MCG_STATUS_RIPV|MCG_STATUS_EIPV)) == \
213216
(MCG_STATUS_RIPV|MCG_STATUS_EIPV))
214217

218+
static bool is_copy_from_user(struct pt_regs *regs)
219+
{
220+
u8 insn_buf[MAX_INSN_SIZE];
221+
struct insn insn;
222+
unsigned long addr;
223+
224+
if (copy_from_kernel_nofault(insn_buf, (void *)regs->ip, MAX_INSN_SIZE))
225+
return false;
226+
227+
kernel_insn_init(&insn, insn_buf, MAX_INSN_SIZE);
228+
insn_get_opcode(&insn);
229+
if (!insn.opcode.got)
230+
return false;
231+
232+
switch (insn.opcode.value) {
233+
/* MOV mem,reg */
234+
case 0x8A: case 0x8B:
235+
/* MOVZ mem,reg */
236+
case 0xB60F: case 0xB70F:
237+
insn_get_modrm(&insn);
238+
insn_get_sib(&insn);
239+
if (!insn.modrm.got || !insn.sib.got)
240+
return false;
241+
addr = (unsigned long)insn_get_addr_ref(&insn, regs);
242+
break;
243+
/* REP MOVS */
244+
case 0xA4: case 0xA5:
245+
addr = regs->si;
246+
break;
247+
default:
248+
return false;
249+
}
250+
251+
if (fault_in_kernel_space(addr))
252+
return false;
253+
254+
current->mce_vaddr = (void __user *)addr;
255+
256+
return true;
257+
}
258+
215259
/*
216260
* If mcgstatus indicated that ip/cs on the stack were
217261
* 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)
229273

230274
if ((m->cs & 3) == 3)
231275
return IN_USER;
276+
if (!mc_recoverable(m->mcgstatus))
277+
return IN_KERNEL;
232278

233279
t = ex_get_fault_handler_type(m->ip);
234-
if (mc_recoverable(m->mcgstatus) && t == EX_HANDLER_FAULT) {
280+
if (t == EX_HANDLER_FAULT) {
281+
m->kflags |= MCE_IN_KERNEL_RECOV;
282+
return IN_KERNEL_RECOV;
283+
}
284+
if (t == EX_HANDLER_UACCESS && regs && is_copy_from_user(regs)) {
235285
m->kflags |= MCE_IN_KERNEL_RECOV;
286+
m->kflags |= MCE_IN_KERNEL_COPYIN;
236287
return IN_KERNEL_RECOV;
237288
}
238289

arch/x86/mm/fault.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1081,7 +1081,7 @@ access_error(unsigned long error_code, struct vm_area_struct *vma)
10811081
return 0;
10821082
}
10831083

1084-
static int fault_in_kernel_space(unsigned long address)
1084+
bool fault_in_kernel_space(unsigned long address)
10851085
{
10861086
/*
10871087
* On 64-bit systems, the vsyscall page is at an address above

0 commit comments

Comments
 (0)