um: Make stack trace reliable against kernel mode faults
authorRichard Weinberger <richard@nod.at>
Mon, 23 Sep 2013 15:38:02 +0000 (17:38 +0200)
committerRichard Weinberger <richard@nod.at>
Sun, 17 Nov 2013 10:27:30 +0000 (11:27 +0100)
As UML uses an alternative signal stack we cannot use
the current stack pointer for stack dumping if UML itself
dies by SIGSEGV. To bypass this issue we save regs taken
from mcontext in our segv handler into thread_struct and
use these regs to obtain the stack pointer in show_stack().

Signed-off-by: Richard Weinberger <richard@nod.at>
arch/um/include/asm/processor-generic.h
arch/um/include/shared/os.h
arch/um/kernel/sysrq.c
arch/um/kernel/trap.c
arch/um/os-Linux/signal.c

index c03cd5a02364493fe83c81261eb45c0689bcfed8..90469031297bbdaa183bc24d11efb5b709a8592d 100644 (file)
@@ -21,6 +21,7 @@ struct mm_struct;
 struct thread_struct {
        struct task_struct *saved_task;
        struct pt_regs regs;
+       struct pt_regs *segv_regs;
        int singlestep_syscall;
        void *fault_addr;
        jmp_buf *fault_catcher;
index 021104d98cb351d66f9f44353a6ddfa2da71b23d..75298d3358e7f3d2c7ff4e2b2c5ed8b81a8dca6d 100644 (file)
@@ -227,6 +227,7 @@ extern void block_signals(void);
 extern void unblock_signals(void);
 extern int get_signals(void);
 extern int set_signals(int enable);
+extern int os_is_signal_stack(void);
 
 /* util.c */
 extern void stack_protections(unsigned long address);
index 33cc72e26c6ef63860fe04bc6e61b4daff053202..7122bf9c753e52c8acc073ba3b7494dbf9f28bdd 100644 (file)
@@ -12,6 +12,7 @@
 #include <linux/module.h>
 #include <linux/sched.h>
 #include <asm/sysrq.h>
+#include <os.h>
 
 struct stack_frame {
        struct stack_frame *next_frame;
@@ -48,29 +49,42 @@ static void print_stack_trace(unsigned long *sp, unsigned long bp)
 /*Stolen from arch/i386/kernel/traps.c */
 static const int kstack_depth_to_print = 24;
 
-static unsigned long get_frame_pointer(struct task_struct *task)
+static unsigned long get_frame_pointer(struct task_struct *task,
+                                      struct pt_regs *segv_regs)
 {
        if (!task || task == current)
-               return current_bp();
+               return segv_regs ? PT_REGS_BP(segv_regs) : current_bp();
        else
                return KSTK_EBP(task);
 }
 
+static unsigned long *get_stack_pointer(struct task_struct *task,
+                                       struct pt_regs *segv_regs)
+{
+       if (!task || task == current)
+               return segv_regs ? (unsigned long *)PT_REGS_SP(segv_regs) : current_sp();
+       else
+               return (unsigned long *)KSTK_ESP(task);
+}
+
 void show_stack(struct task_struct *task, unsigned long *stack)
 {
        unsigned long *sp = stack, bp = 0;
+       struct pt_regs *segv_regs = current->thread.segv_regs;
        int i;
 
+       if (!segv_regs && os_is_signal_stack()) {
+               printk(KERN_ERR "Received SIGSEGV in SIGSEGV handler,"
+                               " aborting stack trace!\n");
+               return;
+       }
+
 #ifdef CONFIG_FRAME_POINTER
-       bp = get_frame_pointer(task);
+       bp = get_frame_pointer(task, segv_regs);
 #endif
 
-       if (!stack) {
-               if (!task || task == current)
-                       sp = current_sp();
-               else
-                       sp = (unsigned long *)KSTK_ESP(task);
-       }
+       if (!stack)
+               sp = get_stack_pointer(task, segv_regs);
 
        printk(KERN_INFO "Stack:\n");
        stack = sp;
index 5c3aef74237ffda72a0b252d4ee4b118d14a969a..974b87474a9900f1909f845d449eba90dc9b8338 100644 (file)
@@ -206,9 +206,12 @@ unsigned long segv(struct faultinfo fi, unsigned long ip, int is_user,
        int is_write = FAULT_WRITE(fi);
        unsigned long address = FAULT_ADDRESS(fi);
 
+       if (regs)
+               current->thread.segv_regs = container_of(regs, struct pt_regs, regs);
+
        if (!is_user && (address >= start_vm) && (address < end_vm)) {
                flush_tlb_kernel_vm();
-               return 0;
+               goto out;
        }
        else if (current->mm == NULL) {
                show_regs(container_of(regs, struct pt_regs, regs));
@@ -230,7 +233,7 @@ unsigned long segv(struct faultinfo fi, unsigned long ip, int is_user,
 
        catcher = current->thread.fault_catcher;
        if (!err)
-               return 0;
+               goto out;
        else if (catcher != NULL) {
                current->thread.fault_addr = (void *) address;
                UML_LONGJMP(catcher, 1);
@@ -238,7 +241,7 @@ unsigned long segv(struct faultinfo fi, unsigned long ip, int is_user,
        else if (current->thread.fault_addr != NULL)
                panic("fault_addr set but no fault catcher");
        else if (!is_user && arch_fixup(ip, regs))
-               return 0;
+               goto out;
 
        if (!is_user) {
                show_regs(container_of(regs, struct pt_regs, regs));
@@ -262,6 +265,11 @@ unsigned long segv(struct faultinfo fi, unsigned long ip, int is_user,
                current->thread.arch.faultinfo = fi;
                force_sig_info(SIGSEGV, &si, current);
        }
+
+out:
+       if (regs)
+               current->thread.segv_regs = NULL;
+
        return 0;
 }
 
index 905924b773d345b8f49de2905e62a546f6461cd9..7b605e4dfffa91305e763e7698832edcbf80d3bd 100644 (file)
@@ -304,3 +304,11 @@ int set_signals(int enable)
 
        return ret;
 }
+
+int os_is_signal_stack(void)
+{
+       stack_t ss;
+       sigaltstack(NULL, &ss);
+
+       return ss.ss_flags & SS_ONSTACK;
+}