x86: Add counter when debug stack is used with interrupts enabled
authorSteven Rostedt <srostedt@redhat.com>
Fri, 16 Dec 2011 16:43:02 +0000 (11:43 -0500)
committerSteven Rostedt <rostedt@goodmis.org>
Wed, 21 Dec 2011 20:38:56 +0000 (15:38 -0500)
Mathieu Desnoyers pointed out a case that can cause issues with
NMIs running on the debug stack:

  int3 -> interrupt -> NMI -> int3

Because the interrupt changes the stack, the NMI will not see that
it preempted the debug stack. Looking deeper at this case,
interrupts only happen when the int3 is from userspace or in
an a location in the exception table (fixup).

  userspace -> int3 -> interurpt -> NMI -> int3

All other int3s that happen in the kernel should be processed
without ever enabling interrupts, as the do_trap() call will
panic the kernel if it is called to process any other location
within the kernel.

Adding a counter around the sections that enable interrupts while
using the debug stack allows the NMI to also check that case.
If the NMI sees that it either interrupted a task using the debug
stack or the debug counter is non-zero, then it will have to
change the IDT table to make the int3 not change stacks (which will
corrupt the stack if it does).

Note, I had to move the debug_usage functions out of processor.h
and into debugreg.h because of the static inlined functions to
inc and dec the debug_usage counter. __get_cpu_var() requires
smp.h which includes processor.h, and would fail to build.

Link: http://lkml.kernel.org/r/1323976535.23971.112.camel@gandalf.stny.rr.com
Reported-by: Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: H. Peter Anvin <hpa@linux.intel.com>
Cc: Thomas Gleixner <tglx@linutronix.de>
Cc: Paul Turner <pjt@google.com>
Cc: Frederic Weisbecker <fweisbec@gmail.com>
Signed-off-by: Steven Rostedt <rostedt@goodmis.org>
arch/x86/include/asm/debugreg.h
arch/x86/include/asm/processor.h
arch/x86/kernel/cpu/common.c
arch/x86/kernel/traps.c

index 078ad0caefc6115612d8ee60f8ba7da122346516..b903d5ea394111edc965c351103953a62b5357a6 100644 (file)
@@ -101,6 +101,28 @@ extern void aout_dump_debugregs(struct user *dump);
 
 extern void hw_breakpoint_restore(void);
 
+#ifdef CONFIG_X86_64
+DECLARE_PER_CPU(int, debug_stack_usage);
+static inline void debug_stack_usage_inc(void)
+{
+       __get_cpu_var(debug_stack_usage)++;
+}
+static inline void debug_stack_usage_dec(void)
+{
+       __get_cpu_var(debug_stack_usage)--;
+}
+int is_debug_stack(unsigned long addr);
+void debug_stack_set_zero(void);
+void debug_stack_reset(void);
+#else /* !X86_64 */
+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) { }
+static inline void debug_stack_usage_inc(void) { }
+static inline void debug_stack_usage_dec(void) { }
+#endif /* X86_64 */
+
+
 #endif /* __KERNEL__ */
 
 #endif /* _ASM_X86_DEBUGREG_H */
index 4b39d6d7e3a15bdd30a7c02aea981638241b85c6..b650435ffb538d97df113a7b662975e88a60c375 100644 (file)
@@ -402,9 +402,6 @@ 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
 /*
@@ -419,9 +416,6 @@ 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 caa404556b9cee2851028b066b3df29ad50333a0..266e4649b1daf704e64123978ec0bb743d463d10 100644 (file)
@@ -1093,11 +1093,13 @@ unsigned long kernel_eflags;
 DEFINE_PER_CPU(struct orig_ist, orig_ist);
 
 static DEFINE_PER_CPU(unsigned long, debug_stack_addr);
+DEFINE_PER_CPU(int, debug_stack_usage);
 
 int is_debug_stack(unsigned long addr)
 {
-       return addr <= __get_cpu_var(debug_stack_addr) &&
-               addr > (__get_cpu_var(debug_stack_addr) - DEBUG_STKSZ);
+       return __get_cpu_var(debug_stack_usage) ||
+               (addr <= __get_cpu_var(debug_stack_addr) &&
+                addr > (__get_cpu_var(debug_stack_addr) - DEBUG_STKSZ));
 }
 
 void debug_stack_set_zero(void)
index a93c5cabc36adb7530b46e6591c0b16e55a6bf69..0072b38e3ea1e6ed919062df08be04c46f8c0b0b 100644 (file)
@@ -316,9 +316,15 @@ dotraplinkage void __kprobes do_int3(struct pt_regs *regs, long error_code)
                return;
 #endif
 
+       /*
+        * Let others (NMI) know that the debug stack is in use
+        * as we may switch to the interrupt stack.
+        */
+       debug_stack_usage_inc();
        preempt_conditional_sti(regs);
        do_trap(3, SIGTRAP, "int3", regs, error_code, NULL);
        preempt_conditional_cli(regs);
+       debug_stack_usage_dec();
 }
 
 #ifdef CONFIG_X86_64
@@ -411,6 +417,12 @@ dotraplinkage void __kprobes do_debug(struct pt_regs *regs, long error_code)
                                                        SIGTRAP) == NOTIFY_STOP)
                return;
 
+       /*
+        * Let others (NMI) know that the debug stack is in use
+        * as we may switch to the interrupt stack.
+        */
+       debug_stack_usage_inc();
+
        /* It's safe to allow irq's after DR6 has been saved */
        preempt_conditional_sti(regs);
 
@@ -418,6 +430,7 @@ dotraplinkage void __kprobes do_debug(struct pt_regs *regs, long error_code)
                handle_vm86_trap((struct kernel_vm86_regs *) regs,
                                error_code, 1);
                preempt_conditional_cli(regs);
+               debug_stack_usage_dec();
                return;
        }
 
@@ -437,6 +450,7 @@ dotraplinkage void __kprobes do_debug(struct pt_regs *regs, long error_code)
        if (tsk->thread.debugreg6 & (DR_STEP | DR_TRAP_BITS) || user_icebp)
                send_sigtrap(tsk, regs, error_code, si_code);
        preempt_conditional_cli(regs);
+       debug_stack_usage_dec();
 
        return;
 }