Skip to content

Commit 59c1dcb

Browse files
thejhsuryasaimadhu
authored andcommitted
x86/traps: Print address on #GP
A frequent cause of #GP exceptions are memory accesses to non-canonical addresses. Unlike #PF, #GP doesn't report a fault address in CR2, so the kernel doesn't currently print the fault address for a #GP. Luckily, the necessary infrastructure for decoding x86 instructions and computing the memory address being accessed is already present. Hook it up to the #GP handler so that the address operand of the faulting instruction can be figured out and printed. Distinguish two cases: a) (Part of) the memory range being accessed lies in the non-canonical address range; in this case, it is likely that the decoded address is actually the one that caused the #GP. b) The entire memory range of the decoded operand lies in canonical address space; the #GP may or may not be related in some way to the computed address. Print it, but with hedging language in the message. While it is already possible to compute the faulting address manually by disassembling the opcode dump and evaluating the instruction against the register dump, this should make it slightly easier to identify crashes at a glance. Note that the operand length which comes from the instruction decoder and is used to determine whether the access straddles into non-canonical address space, is currently somewhat unreliable; but it should be good enough, considering that Linux on x86-64 never maps the page directly before the start of the non-canonical range anyway, and therefore the case where a memory range begins in that page and potentially straddles into the non-canonical range should be fairly uncommon. In the case the address is still computed wrongly, it only influences whether the error message claims that the access is canonical. [ bp: Remove ambiguous "we", massage, reflow comments and spacing. ] Signed-off-by: Jann Horn <[email protected]> Signed-off-by: Borislav Petkov <[email protected]> Reviewed-by: Sean Christopherson <[email protected]> Tested-by: Sean Christopherson <[email protected]> Cc: Alexander Potapenko <[email protected]> Cc: Andrey Konovalov <[email protected]> Cc: Andrey Ryabinin <[email protected]> Cc: Andy Lutomirski <[email protected]> Cc: Dmitry Vyukov <[email protected]> Cc: "Eric W. Biederman" <[email protected]> Cc: "H. Peter Anvin" <[email protected]> Cc: Ingo Molnar <[email protected]> Cc: [email protected] Cc: Masami Hiramatsu <[email protected]> Cc: Peter Zijlstra <[email protected]> Cc: Thomas Gleixner <[email protected]> Cc: x86-ml <[email protected]> Link: https://lkml.kernel.org/r/[email protected]
1 parent 7be4412 commit 59c1dcb

File tree

1 file changed

+67
-5
lines changed

1 file changed

+67
-5
lines changed

arch/x86/kernel/traps.c

Lines changed: 67 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@
5656
#include <asm/mpx.h>
5757
#include <asm/vm86.h>
5858
#include <asm/umip.h>
59+
#include <asm/insn.h>
60+
#include <asm/insn-eval.h>
5961

6062
#ifdef CONFIG_X86_64
6163
#include <asm/x86_init.h>
@@ -518,10 +520,53 @@ dotraplinkage void do_bounds(struct pt_regs *regs, long error_code)
518520
do_trap(X86_TRAP_BR, SIGSEGV, "bounds", regs, error_code, 0, NULL);
519521
}
520522

521-
dotraplinkage void
522-
do_general_protection(struct pt_regs *regs, long error_code)
523+
enum kernel_gp_hint {
524+
GP_NO_HINT,
525+
GP_NON_CANONICAL,
526+
GP_CANONICAL
527+
};
528+
529+
/*
530+
* When an uncaught #GP occurs, try to determine the memory address accessed by
531+
* the instruction and return that address to the caller. Also, try to figure
532+
* out whether any part of the access to that address was non-canonical.
533+
*/
534+
static enum kernel_gp_hint get_kernel_gp_address(struct pt_regs *regs,
535+
unsigned long *addr)
523536
{
524-
const char *desc = "general protection fault";
537+
u8 insn_buf[MAX_INSN_SIZE];
538+
struct insn insn;
539+
540+
if (probe_kernel_read(insn_buf, (void *)regs->ip, MAX_INSN_SIZE))
541+
return GP_NO_HINT;
542+
543+
kernel_insn_init(&insn, insn_buf, MAX_INSN_SIZE);
544+
insn_get_modrm(&insn);
545+
insn_get_sib(&insn);
546+
547+
*addr = (unsigned long)insn_get_addr_ref(&insn, regs);
548+
if (*addr == -1UL)
549+
return GP_NO_HINT;
550+
551+
#ifdef CONFIG_X86_64
552+
/*
553+
* Check that:
554+
* - the operand is not in the kernel half
555+
* - the last byte of the operand is not in the user canonical half
556+
*/
557+
if (*addr < ~__VIRTUAL_MASK &&
558+
*addr + insn.opnd_bytes - 1 > __VIRTUAL_MASK)
559+
return GP_NON_CANONICAL;
560+
#endif
561+
562+
return GP_CANONICAL;
563+
}
564+
565+
#define GPFSTR "general protection fault"
566+
567+
dotraplinkage void do_general_protection(struct pt_regs *regs, long error_code)
568+
{
569+
char desc[sizeof(GPFSTR) + 50 + 2*sizeof(unsigned long) + 1] = GPFSTR;
525570
struct task_struct *tsk;
526571

527572
RCU_LOCKDEP_WARN(!rcu_is_watching(), "entry code didn't wake RCU");
@@ -540,6 +585,9 @@ do_general_protection(struct pt_regs *regs, long error_code)
540585

541586
tsk = current;
542587
if (!user_mode(regs)) {
588+
enum kernel_gp_hint hint = GP_NO_HINT;
589+
unsigned long gp_addr;
590+
543591
if (fixup_exception(regs, X86_TRAP_GP, error_code, 0))
544592
return;
545593

@@ -556,8 +604,22 @@ do_general_protection(struct pt_regs *regs, long error_code)
556604
return;
557605

558606
if (notify_die(DIE_GPF, desc, regs, error_code,
559-
X86_TRAP_GP, SIGSEGV) != NOTIFY_STOP)
560-
die(desc, regs, error_code);
607+
X86_TRAP_GP, SIGSEGV) == NOTIFY_STOP)
608+
return;
609+
610+
if (error_code)
611+
snprintf(desc, sizeof(desc), "segment-related " GPFSTR);
612+
else
613+
hint = get_kernel_gp_address(regs, &gp_addr);
614+
615+
if (hint != GP_NO_HINT)
616+
snprintf(desc, sizeof(desc), GPFSTR ", %s 0x%lx",
617+
(hint == GP_NON_CANONICAL) ?
618+
"probably for non-canonical address" :
619+
"maybe for address",
620+
gp_addr);
621+
622+
die(desc, regs, error_code);
561623
return;
562624
}
563625

0 commit comments

Comments
 (0)