sparc64: Make corrupted user stacks more debuggable.
authorDavid Miller <davem@redhat.com>
Fri, 26 Oct 2018 03:36:46 +0000 (20:36 -0700)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Sun, 4 Nov 2018 13:50:54 +0000 (14:50 +0100)
[ Upstream commit 5b4fc3882a649c9411dd0dcad2ddb78e911d340e ]

Right now if we get a corrupted user stack frame we do a
do_exit(SIGILL) which is not helpful.

If under a debugger, this behavior causes the inferior process to
exit.  So the register and other state cannot be examined at the time
of the event.

Instead, conditionally log a rate limited kernel log message and then
force a SIGSEGV.

With bits and ideas borrowed (as usual) from powerpc.

Signed-off-by: David S. Miller <davem@davemloft.net>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
arch/sparc/include/asm/switch_to_64.h
arch/sparc/kernel/process_64.c
arch/sparc/kernel/rtrap_64.S
arch/sparc/kernel/signal32.c
arch/sparc/kernel/signal_64.c

index 4ff29b1..b1d4e2e 100644 (file)
@@ -67,6 +67,7 @@ do {  save_and_clear_fpu();                                           \
 } while(0)
 
 void synchronize_user_stack(void);
-void fault_in_user_windows(void);
+struct pt_regs;
+void fault_in_user_windows(struct pt_regs *);
 
 #endif /* __SPARC64_SWITCH_TO_64_H */
index 6c08608..59eaf62 100644 (file)
@@ -36,6 +36,7 @@
 #include <linux/sysrq.h>
 #include <linux/nmi.h>
 #include <linux/context_tracking.h>
+#include <linux/signal.h>
 
 #include <linux/uaccess.h>
 #include <asm/page.h>
@@ -521,7 +522,12 @@ static void stack_unaligned(unsigned long sp)
        force_sig_fault(SIGBUS, BUS_ADRALN, (void __user *) sp, 0, current);
 }
 
-void fault_in_user_windows(void)
+static const char uwfault32[] = KERN_INFO \
+       "%s[%d]: bad register window fault: SP %08lx (orig_sp %08lx) TPC %08lx O7 %08lx\n";
+static const char uwfault64[] = KERN_INFO \
+       "%s[%d]: bad register window fault: SP %016lx (orig_sp %016lx) TPC %08lx O7 %016lx\n";
+
+void fault_in_user_windows(struct pt_regs *regs)
 {
        struct thread_info *t = current_thread_info();
        unsigned long window;
@@ -534,9 +540,9 @@ void fault_in_user_windows(void)
                do {
                        struct reg_window *rwin = &t->reg_window[window];
                        int winsize = sizeof(struct reg_window);
-                       unsigned long sp;
+                       unsigned long sp, orig_sp;
 
-                       sp = t->rwbuf_stkptrs[window];
+                       orig_sp = sp = t->rwbuf_stkptrs[window];
 
                        if (test_thread_64bit_stack(sp))
                                sp += STACK_BIAS;
@@ -547,8 +553,16 @@ void fault_in_user_windows(void)
                                stack_unaligned(sp);
 
                        if (unlikely(copy_to_user((char __user *)sp,
-                                                 rwin, winsize)))
+                                                 rwin, winsize))) {
+                               if (show_unhandled_signals)
+                                       printk_ratelimited(is_compat_task() ?
+                                                          uwfault32 : uwfault64,
+                                                          current->comm, current->pid,
+                                                          sp, orig_sp,
+                                                          regs->tpc,
+                                                          regs->u_regs[UREG_I7]);
                                goto barf;
+                       }
                } while (window--);
        }
        set_thread_wsaved(0);
@@ -556,8 +570,7 @@ void fault_in_user_windows(void)
 
 barf:
        set_thread_wsaved(window + 1);
-       user_exit();
-       do_exit(SIGILL);
+       force_sig(SIGSEGV, current);
 }
 
 asmlinkage long sparc_do_fork(unsigned long clone_flags,
index 4073e2b..29aa34f 100644 (file)
@@ -39,6 +39,7 @@ __handle_preemption:
                 wrpr                   %g0, RTRAP_PSTATE_IRQOFF, %pstate
 
 __handle_user_windows:
+               add                     %sp, PTREGS_OFF, %o0
                call                    fault_in_user_windows
 661:            wrpr                   %g0, RTRAP_PSTATE, %pstate
                /* If userspace is using ADI, it could potentially pass
index 44d379d..4c5b3fc 100644 (file)
@@ -371,7 +371,11 @@ static int setup_frame32(struct ksignal *ksig, struct pt_regs *regs,
                get_sigframe(ksig, regs, sigframe_size);
        
        if (invalid_frame_pointer(sf, sigframe_size)) {
-               do_exit(SIGILL);
+               if (show_unhandled_signals)
+                       pr_info("%s[%d] bad frame in setup_frame32: %08lx TPC %08lx O7 %08lx\n",
+                               current->comm, current->pid, (unsigned long)sf,
+                               regs->tpc, regs->u_regs[UREG_I7]);
+               force_sigsegv(ksig->sig, current);
                return -EINVAL;
        }
 
@@ -501,7 +505,11 @@ static int setup_rt_frame32(struct ksignal *ksig, struct pt_regs *regs,
                get_sigframe(ksig, regs, sigframe_size);
        
        if (invalid_frame_pointer(sf, sigframe_size)) {
-               do_exit(SIGILL);
+               if (show_unhandled_signals)
+                       pr_info("%s[%d] bad frame in setup_rt_frame32: %08lx TPC %08lx O7 %08lx\n",
+                               current->comm, current->pid, (unsigned long)sf,
+                               regs->tpc, regs->u_regs[UREG_I7]);
+               force_sigsegv(ksig->sig, current);
                return -EINVAL;
        }
 
index 48366e5..e9de180 100644 (file)
@@ -370,7 +370,11 @@ setup_rt_frame(struct ksignal *ksig, struct pt_regs *regs)
                get_sigframe(ksig, regs, sf_size);
 
        if (invalid_frame_pointer (sf)) {
-               do_exit(SIGILL);        /* won't return, actually */
+               if (show_unhandled_signals)
+                       pr_info("%s[%d] bad frame in setup_rt_frame: %016lx TPC %016lx O7 %016lx\n",
+                               current->comm, current->pid, (unsigned long)sf,
+                               regs->tpc, regs->u_regs[UREG_I7]);
+               force_sigsegv(ksig->sig, current);
                return -EINVAL;
        }