Home Home > GIT Browse
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBorislav Petkov <bp@suse.de>2018-01-12 21:55:29 +0100
committerBorislav Petkov <bp@suse.de>2018-01-12 21:55:33 +0100
commit3fe48f5814ca83bc3162c9926236edff3f33e2ed (patch)
treeb33888087f2e55f84f831889962755543d8cfbf8
parent7171dc2cc12f38352b1338284057aa039d2b79f8 (diff)
x86/unwinder: Handle stack overflows more gracefully
(bsc#1068032 CVE-2017-5754).
-rw-r--r--patches.arch/03-x86-unwinder-handle-stack-overflows-more-gracefully.patch317
-rw-r--r--series.conf1
2 files changed, 318 insertions, 0 deletions
diff --git a/patches.arch/03-x86-unwinder-handle-stack-overflows-more-gracefully.patch b/patches.arch/03-x86-unwinder-handle-stack-overflows-more-gracefully.patch
new file mode 100644
index 0000000000..2229d0faef
--- /dev/null
+++ b/patches.arch/03-x86-unwinder-handle-stack-overflows-more-gracefully.patch
@@ -0,0 +1,317 @@
+From: Josh Poimboeuf <jpoimboe@redhat.com>
+Date: Mon, 4 Dec 2017 15:07:09 +0100
+Subject: x86/unwinder: Handle stack overflows more gracefully
+Git-commit: b02fcf9ba1211097754b286043cd87a8b4907e75
+Patch-mainline: v4.15-rc5
+References: bsc#1068032 CVE-2017-5754
+
+There are at least two unwinder bugs hindering the debugging of
+stack-overflow crashes:
+
+- It doesn't deal gracefully with the case where the stack overflows and
+ the stack pointer itself isn't on a valid stack but the
+ to-be-dereferenced data *is*.
+
+- The ORC oops dump code doesn't know how to print partial pt_regs, for the
+ case where if we get an interrupt/exception in *early* entry code
+ before the full pt_regs have been saved.
+
+Fix both issues.
+
+http://lkml.kernel.org/r/20171126024031.uxi4numpbjm5rlbr@treble
+
+Signed-off-by: Josh Poimboeuf <jpoimboe@redhat.com>
+Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
+Reviewed-by: Borislav Petkov <bpetkov@suse.de>
+Cc: Andy Lutomirski <luto@kernel.org>
+Cc: Boris Ostrovsky <boris.ostrovsky@oracle.com>
+Cc: Borislav Petkov <bp@alien8.de>
+Cc: Brian Gerst <brgerst@gmail.com>
+Cc: Dave Hansen <dave.hansen@intel.com>
+Cc: Dave Hansen <dave.hansen@linux.intel.com>
+Cc: David Laight <David.Laight@aculab.com>
+Cc: Denys Vlasenko <dvlasenk@redhat.com>
+Cc: Eduardo Valentin <eduval@amazon.com>
+Cc: Greg KH <gregkh@linuxfoundation.org>
+Cc: H. Peter Anvin <hpa@zytor.com>
+Cc: Juergen Gross <jgross@suse.com>
+Cc: Linus Torvalds <torvalds@linux-foundation.org>
+Cc: Peter Zijlstra <peterz@infradead.org>
+Cc: Rik van Riel <riel@redhat.com>
+Cc: Will Deacon <will.deacon@arm.com>
+Cc: aliguori@amazon.com
+Cc: daniel.gruss@iaik.tugraz.at
+Cc: hughd@google.com
+Cc: keescook@google.com
+Link: https://lkml.kernel.org/r/20171204150605.071425003@linutronix.de
+Signed-off-by: Ingo Molnar <mingo@kernel.org>
+Acked-by: Borislav Petkov <bp@suse.de>
+---
+ arch/x86/include/asm/kdebug.h | 1
+ arch/x86/include/asm/unwind.h | 7 +++
+ arch/x86/kernel/dumpstack.c | 32 ++++++++++++++---
+ arch/x86/kernel/process_64.c | 12 ++----
+ arch/x86/kernel/unwind_orc.c | 76 ++++++++++++++----------------------------
+ 5 files changed, 66 insertions(+), 62 deletions(-)
+
+--- a/arch/x86/include/asm/kdebug.h
++++ b/arch/x86/include/asm/kdebug.h
+@@ -25,6 +25,7 @@ extern void die(const char *, struct pt_
+ extern int __must_check __die(const char *, struct pt_regs *, long);
+ extern void show_stack_regs(struct pt_regs *regs);
+ extern void __show_regs(struct pt_regs *regs, int all);
++extern void show_iret_regs(struct pt_regs *regs);
+ extern unsigned long oops_begin(void);
+ extern void oops_end(unsigned long, struct pt_regs *, int signr);
+
+--- a/arch/x86/include/asm/unwind.h
++++ b/arch/x86/include/asm/unwind.h
+@@ -6,6 +6,9 @@
+ #include <asm/ptrace.h>
+ #include <asm/stacktrace.h>
+
++#define IRET_FRAME_OFFSET (offsetof(struct pt_regs, ip))
++#define IRET_FRAME_SIZE (sizeof(struct pt_regs) - IRET_FRAME_OFFSET)
++
+ struct unwind_state {
+ struct stack_info stack_info;
+ unsigned long stack_mask;
+@@ -51,6 +54,10 @@ void unwind_start(struct unwind_state *s
+ }
+
+ #if defined(CONFIG_UNWINDER_ORC) || defined(CONFIG_UNWINDER_FRAME_POINTER)
++/*
++ * WARNING: The entire pt_regs may not be safe to dereference. In some cases,
++ * only the iret frame registers are accessible. Use with caution!
++ */
+ static inline struct pt_regs *unwind_get_entry_regs(struct unwind_state *state)
+ {
+ if (unwind_done(state))
+--- a/arch/x86/kernel/dumpstack.c
++++ b/arch/x86/kernel/dumpstack.c
+@@ -50,6 +50,28 @@ static void printk_stack_address(unsigne
+ printk("%s %s%pB\n", log_lvl, reliable ? "" : "? ", (void *)address);
+ }
+
++void show_iret_regs(struct pt_regs *regs)
++{
++ printk(KERN_DEFAULT "RIP: %04x:%pS\n", (int)regs->cs, (void *)regs->ip);
++ printk(KERN_DEFAULT "RSP: %04x:%016lx EFLAGS: %08lx", (int)regs->ss,
++ regs->sp, regs->flags);
++}
++
++static void show_regs_safe(struct stack_info *info, struct pt_regs *regs)
++{
++ if (on_stack(info, regs, sizeof(*regs)))
++ __show_regs(regs, 0);
++ else if (on_stack(info, (void *)regs + IRET_FRAME_OFFSET,
++ IRET_FRAME_SIZE)) {
++ /*
++ * When an interrupt or exception occurs in entry code, the
++ * full pt_regs might not have been saved yet. In that case
++ * just print the iret frame.
++ */
++ show_iret_regs(regs);
++ }
++}
++
+ void show_trace_log_lvl(struct task_struct *task, struct pt_regs *regs,
+ unsigned long *stack, char *log_lvl)
+ {
+@@ -94,8 +116,8 @@ void show_trace_log_lvl(struct task_stru
+ if (stack_name)
+ printk("%s <%s>\n", log_lvl, stack_name);
+
+- if (regs && on_stack(&stack_info, regs, sizeof(*regs)))
+- __show_regs(regs, 0);
++ if (regs)
++ show_regs_safe(&stack_info, regs);
+
+ /*
+ * Scan the stack, printing any text addresses we find. At the
+@@ -119,7 +141,7 @@ void show_trace_log_lvl(struct task_stru
+
+ /*
+ * Don't print regs->ip again if it was already printed
+- * by __show_regs() below.
++ * by show_regs_safe() below.
+ */
+ if (regs && stack == &regs->ip)
+ goto next;
+@@ -155,8 +177,8 @@ next:
+
+ /* if the frame has entry regs, print them */
+ regs = unwind_get_entry_regs(&state);
+- if (regs && on_stack(&stack_info, regs, sizeof(*regs)))
+- __show_regs(regs, 0);
++ if (regs)
++ show_regs_safe(&stack_info, regs);
+ }
+
+ if (stack_name)
+--- a/arch/x86/kernel/process_64.c
++++ b/arch/x86/kernel/process_64.c
+@@ -69,10 +69,8 @@ void __show_regs(struct pt_regs *regs, i
+ unsigned int fsindex, gsindex;
+ unsigned int ds, cs, es;
+
+- printk(KERN_DEFAULT "RIP: %04lx:%pS\n", regs->cs & 0xffff,
+- (void *)regs->ip);
+- printk(KERN_DEFAULT "RSP: %04lx:%016lx EFLAGS: %08lx", regs->ss,
+- regs->sp, regs->flags);
++ show_iret_regs(regs);
++
+ if (regs->orig_ax != -1)
+ pr_cont(" ORIG_RAX: %016lx\n", regs->orig_ax);
+ else
+@@ -89,6 +87,9 @@ void __show_regs(struct pt_regs *regs, i
+ printk(KERN_DEFAULT "R13: %016lx R14: %016lx R15: %016lx\n",
+ regs->r13, regs->r14, regs->r15);
+
++ if (!all)
++ return;
++
+ asm("movl %%ds,%0" : "=r" (ds));
+ asm("movl %%cs,%0" : "=r" (cs));
+ asm("movl %%es,%0" : "=r" (es));
+@@ -99,9 +100,6 @@ void __show_regs(struct pt_regs *regs, i
+ rdmsrl(MSR_GS_BASE, gs);
+ rdmsrl(MSR_KERNEL_GS_BASE, shadowgs);
+
+- if (!all)
+- return;
+-
+ cr0 = read_cr0();
+ cr2 = read_cr2();
+ cr3 = __read_cr3();
+--- a/arch/x86/kernel/unwind_orc.c
++++ b/arch/x86/kernel/unwind_orc.c
+@@ -253,22 +253,15 @@ unsigned long *unwind_get_return_address
+ return NULL;
+ }
+
+-static bool stack_access_ok(struct unwind_state *state, unsigned long addr,
++static bool stack_access_ok(struct unwind_state *state, unsigned long _addr,
+ size_t len)
+ {
+ struct stack_info *info = &state->stack_info;
++ void *addr = (void *)_addr;
+
+- /*
+- * If the address isn't on the current stack, switch to the next one.
+- *
+- * We may have to traverse multiple stacks to deal with the possibility
+- * that info->next_sp could point to an empty stack and the address
+- * could be on a subsequent stack.
+- */
+- while (!on_stack(info, (void *)addr, len))
+- if (get_stack_info(info->next_sp, state->task, info,
+- &state->stack_mask))
+- return false;
++ if (!on_stack(info, addr, len) &&
++ (get_stack_info(addr, state->task, info, &state->stack_mask)))
++ return false;
+
+ return true;
+ }
+@@ -283,42 +276,32 @@ static bool deref_stack_reg(struct unwin
+ return true;
+ }
+
+-#define REGS_SIZE (sizeof(struct pt_regs))
+-#define SP_OFFSET (offsetof(struct pt_regs, sp))
+-#define IRET_REGS_SIZE (REGS_SIZE - offsetof(struct pt_regs, ip))
+-#define IRET_SP_OFFSET (SP_OFFSET - offsetof(struct pt_regs, ip))
+-
+ static bool deref_stack_regs(struct unwind_state *state, unsigned long addr,
+- unsigned long *ip, unsigned long *sp, bool full)
++ unsigned long *ip, unsigned long *sp)
+ {
+- size_t regs_size = full ? REGS_SIZE : IRET_REGS_SIZE;
+- size_t sp_offset = full ? SP_OFFSET : IRET_SP_OFFSET;
+- struct pt_regs *regs = (struct pt_regs *)(addr + regs_size - REGS_SIZE);
+-
+- if (IS_ENABLED(CONFIG_X86_64)) {
+- if (!stack_access_ok(state, addr, regs_size))
+- return false;
+-
+- *ip = regs->ip;
+- *sp = regs->sp;
++ struct pt_regs *regs = (struct pt_regs *)addr;
+
+- return true;
+- }
++ /* x86-32 support will be more complicated due to the &regs->sp hack */
++ BUILD_BUG_ON(IS_ENABLED(CONFIG_X86_32));
+
+- if (!stack_access_ok(state, addr, sp_offset))
++ if (!stack_access_ok(state, addr, sizeof(struct pt_regs)))
+ return false;
+
+ *ip = regs->ip;
++ *sp = regs->sp;
++ return true;
++}
+
+- if (user_mode(regs)) {
+- if (!stack_access_ok(state, addr + sp_offset,
+- REGS_SIZE - SP_OFFSET))
+- return false;
+-
+- *sp = regs->sp;
+- } else
+- *sp = (unsigned long)&regs->sp;
++static bool deref_stack_iret_regs(struct unwind_state *state, unsigned long addr,
++ unsigned long *ip, unsigned long *sp)
++{
++ struct pt_regs *regs = (void *)addr - IRET_FRAME_OFFSET;
+
++ if (!stack_access_ok(state, addr, IRET_FRAME_SIZE))
++ return false;
++
++ *ip = regs->ip;
++ *sp = regs->sp;
+ return true;
+ }
+
+@@ -327,7 +310,6 @@ bool unwind_next_frame(struct unwind_sta
+ unsigned long ip_p, sp, orig_ip, prev_sp = state->sp;
+ enum stack_type prev_type = state->stack_info.type;
+ struct orc_entry *orc;
+- struct pt_regs *ptregs;
+ bool indirect = false;
+
+ if (unwind_done(state))
+@@ -435,7 +417,7 @@ bool unwind_next_frame(struct unwind_sta
+ break;
+
+ case ORC_TYPE_REGS:
+- if (!deref_stack_regs(state, sp, &state->ip, &state->sp, true)) {
++ if (!deref_stack_regs(state, sp, &state->ip, &state->sp)) {
+ orc_warn("can't dereference registers at %p for ip %pB\n",
+ (void *)sp, (void *)orig_ip);
+ goto done;
+@@ -447,20 +429,14 @@ bool unwind_next_frame(struct unwind_sta
+ break;
+
+ case ORC_TYPE_REGS_IRET:
+- if (!deref_stack_regs(state, sp, &state->ip, &state->sp, false)) {
++ if (!deref_stack_iret_regs(state, sp, &state->ip, &state->sp)) {
+ orc_warn("can't dereference iret registers at %p for ip %pB\n",
+ (void *)sp, (void *)orig_ip);
+ goto done;
+ }
+
+- ptregs = container_of((void *)sp, struct pt_regs, ip);
+- if ((unsigned long)ptregs >= prev_sp &&
+- on_stack(&state->stack_info, ptregs, REGS_SIZE)) {
+- state->regs = ptregs;
+- state->full_regs = false;
+- } else
+- state->regs = NULL;
+-
++ state->regs = (void *)sp - IRET_FRAME_OFFSET;
++ state->full_regs = false;
+ state->signal = true;
+ break;
+
diff --git a/series.conf b/series.conf
index 82093cd844..1e01caf990 100644
--- a/series.conf
+++ b/series.conf
@@ -7374,6 +7374,7 @@
# KPTI bsc#1068032 CVE-2017-5754, part 2, prep entry_64.S stuff
patches.arch/01-x86-entry-64-paravirt-use-paravirt-safe-macro-to-access-eflags.patch
patches.arch/02-x86-unwinder-orc-dont-bail-on-stack-overflow.patch
+ patches.arch/03-x86-unwinder-handle-stack-overflows-more-gracefully.patch
########################################################
# Staging tree patches