x86: Keep current stack in NMI breakpoints
authorSteven Rostedt <srostedt@redhat.com>
Fri, 9 Dec 2011 08:02:19 +0000 (03:02 -0500)
committerSteven Rostedt <rostedt@goodmis.org>
Wed, 21 Dec 2011 20:38:55 +0000 (15:38 -0500)
We want to allow NMI handlers to have breakpoints to be able to
remove stop_machine from ftrace, kprobes and jump_labels. But if
an NMI interrupts a current breakpoint, and then it triggers a
breakpoint itself, it will switch to the breakpoint stack and
corrupt the data on it for the breakpoint processing that it
interrupted.

Instead, have the NMI check if it interrupted breakpoint processing
by checking if the stack that is currently used is a breakpoint
stack. If it is, then load a special IDT that changes the IST
for the debug exception to keep the same stack in kernel context.
When the NMI is done, it puts it back.

This way, if the NMI does trigger a breakpoint, it will keep
using the same stack and not stomp on the breakpoint data for
the breakpoint it interrupted.

Suggested-by: Peter Zijlstra <peterz@infradead.org>
Signed-off-by: Steven Rostedt <rostedt@goodmis.org>
arch/x86/include/asm/desc.h
arch/x86/include/asm/processor.h
arch/x86/kernel/cpu/common.c
arch/x86/kernel/head_64.S
arch/x86/kernel/nmi.c
arch/x86/kernel/traps.c

index 41935fadfdfcca18be2ca724b34e1c45d3116ac1..e95822d683f4639f7211d4f3775975fcd87466ad 100644 (file)
@@ -35,6 +35,8 @@ static inline void fill_ldt(struct desc_struct *desc, const struct user_desc *in
 
 extern struct desc_ptr idt_descr;
 extern gate_desc idt_table[];
+extern struct desc_ptr nmi_idt_descr;
+extern gate_desc nmi_idt_table[];
 
 struct gdt_page {
        struct desc_struct gdt[GDT_ENTRIES];
@@ -307,6 +309,16 @@ static inline void set_desc_limit(struct desc_struct *desc, unsigned long limit)
        desc->limit = (limit >> 16) & 0xf;
 }
 
+#ifdef CONFIG_X86_64
+static inline void set_nmi_gate(int gate, void *addr)
+{
+       gate_desc s;
+
+       pack_gate(&s, GATE_INTERRUPT, (unsigned long)addr, 0, 0, __KERNEL_CS);
+       write_idt_entry(nmi_idt_table, gate, &s);
+}
+#endif
+
 static inline void _set_gate(int gate, unsigned type, void *addr,
                             unsigned dpl, unsigned ist, unsigned seg)
 {
index b650435ffb538d97df113a7b662975e88a60c375..4b39d6d7e3a15bdd30a7c02aea981638241b85c6 100644 (file)
@@ -402,6 +402,9 @@ DECLARE_PER_CPU(char *, irq_stack_ptr);
 DECLARE_PER_CPU(unsigned int, irq_count);
 extern unsigned long kernel_eflags;
 extern asmlinkage void ignore_sysret(void);
+int is_debug_stack(unsigned long addr);
+void debug_stack_set_zero(void);
+void debug_stack_reset(void);
 #else  /* X86_64 */
 #ifdef CONFIG_CC_STACKPROTECTOR
 /*
@@ -416,6 +419,9 @@ struct stack_canary {
 };
 DECLARE_PER_CPU_ALIGNED(struct stack_canary, stack_canary);
 #endif
+static inline int is_debug_stack(unsigned long addr) { return 0; }
+static inline void debug_stack_set_zero(void) { }
+static inline void debug_stack_reset(void) { }
 #endif /* X86_64 */
 
 extern unsigned int xstate_size;
index aa003b13a83131db63e3f594c0cadb34e9f89b93..caa404556b9cee2851028b066b3df29ad50333a0 100644 (file)
@@ -1026,6 +1026,8 @@ __setup("clearcpuid=", setup_disablecpuid);
 
 #ifdef CONFIG_X86_64
 struct desc_ptr idt_descr = { NR_VECTORS * 16 - 1, (unsigned long) idt_table };
+struct desc_ptr nmi_idt_descr = { NR_VECTORS * 16 - 1,
+                                   (unsigned long) nmi_idt_table };
 
 DEFINE_PER_CPU_FIRST(union irq_stack_union,
                     irq_stack_union) __aligned(PAGE_SIZE);
@@ -1090,6 +1092,24 @@ unsigned long kernel_eflags;
  */
 DEFINE_PER_CPU(struct orig_ist, orig_ist);
 
+static DEFINE_PER_CPU(unsigned long, debug_stack_addr);
+
+int is_debug_stack(unsigned long addr)
+{
+       return addr <= __get_cpu_var(debug_stack_addr) &&
+               addr > (__get_cpu_var(debug_stack_addr) - DEBUG_STKSZ);
+}
+
+void debug_stack_set_zero(void)
+{
+       load_idt((const struct desc_ptr *)&nmi_idt_descr);
+}
+
+void debug_stack_reset(void)
+{
+       load_idt((const struct desc_ptr *)&idt_descr);
+}
+
 #else  /* CONFIG_X86_64 */
 
 DEFINE_PER_CPU(struct task_struct *, current_task) = &init_task;
@@ -1208,6 +1228,8 @@ void __cpuinit cpu_init(void)
                        estacks += exception_stack_sizes[v];
                        oist->ist[v] = t->x86_tss.ist[v] =
                                        (unsigned long)estacks;
+                       if (v == DEBUG_STACK-1)
+                               per_cpu(debug_stack_addr, cpu) = (unsigned long)estacks;
                }
        }
 
index e11e39478a4978643559e650844cb9941c876523..40f4eb3766d1d2a669790d36d3c6e28eff42b87b 100644 (file)
@@ -417,6 +417,10 @@ ENTRY(phys_base)
 ENTRY(idt_table)
        .skip IDT_ENTRIES * 16
 
+       .align L1_CACHE_BYTES
+ENTRY(nmi_idt_table)
+       .skip IDT_ENTRIES * 16
+
        __PAGE_ALIGNED_BSS
        .align PAGE_SIZE
 ENTRY(empty_zero_page)
index e88f37b58dddeeaecfb8951649db799986b7e247..de8d4b333f405ab875462ea49c9b2b2cf7d2400e 100644 (file)
@@ -408,6 +408,18 @@ static notrace __kprobes void default_do_nmi(struct pt_regs *regs)
 dotraplinkage notrace __kprobes void
 do_nmi(struct pt_regs *regs, long error_code)
 {
+       int update_debug_stack = 0;
+
+       /*
+        * If we interrupted a breakpoint, it is possible that
+        * the nmi handler will have breakpoints too. We need to
+        * change the IDT such that breakpoints that happen here
+        * continue to use the NMI stack.
+        */
+       if (unlikely(is_debug_stack(regs->sp))) {
+               debug_stack_set_zero();
+               update_debug_stack = 1;
+       }
        nmi_enter();
 
        inc_irq_stat(__nmi_count);
@@ -416,6 +428,9 @@ do_nmi(struct pt_regs *regs, long error_code)
                default_do_nmi(regs);
 
        nmi_exit();
+
+       if (unlikely(update_debug_stack))
+               debug_stack_reset();
 }
 
 void stop_nmi(void)
index a8e3eb83466cd191afa95dc1ba6839f317955b68..a93c5cabc36adb7530b46e6591c0b16e55a6bf69 100644 (file)
@@ -723,4 +723,10 @@ void __init trap_init(void)
        cpu_init();
 
        x86_init.irqs.trap_init();
+
+#ifdef CONFIG_X86_64
+       memcpy(&nmi_idt_table, &idt_table, IDT_ENTRIES * 16);
+       set_nmi_gate(1, &debug);
+       set_nmi_gate(3, &int3);
+#endif
 }