parisc: Fix kernel panic due invalid values in IAOQ0 or IAOQ1
[platform/kernel/linux-rpi.git] / arch / parisc / kernel / ptrace.c
index 2582df1..de2998c 100644 (file)
@@ -167,6 +167,9 @@ long arch_ptrace(struct task_struct *child, long request,
                if ((addr & (sizeof(unsigned long)-1)) ||
                     addr >= sizeof(struct pt_regs))
                        break;
+               if (addr == PT_IAOQ0 || addr == PT_IAOQ1) {
+                       data |= 3; /* ensure userspace privilege */
+               }
                if ((addr >= PT_GR1 && addr <= PT_GR31) ||
                                addr == PT_IAOQ0 || addr == PT_IAOQ1 ||
                                (addr >= PT_FR0 && addr <= PT_FR31 + 4) ||
@@ -228,16 +231,18 @@ long arch_ptrace(struct task_struct *child, long request,
 
 static compat_ulong_t translate_usr_offset(compat_ulong_t offset)
 {
-       if (offset < 0)
-               return sizeof(struct pt_regs);
-       else if (offset <= 32*4)        /* gr[0..31] */
-               return offset * 2 + 4;
-       else if (offset <= 32*4+32*8)   /* gr[0..31] + fr[0..31] */
-               return offset + 32*4;
-       else if (offset < sizeof(struct pt_regs)/2 + 32*4)
-               return offset * 2 + 4 - 32*8;
+       compat_ulong_t pos;
+
+       if (offset < 32*4)      /* gr[0..31] */
+               pos = offset * 2 + 4;
+       else if (offset < 32*4+32*8)    /* fr[0] ... fr[31] */
+               pos = (offset - 32*4) + PT_FR0;
+       else if (offset < sizeof(struct pt_regs)/2 + 32*4) /* sr[0] ... ipsw */
+               pos = (offset - 32*4 - 32*8) * 2 + PT_SR0 + 4;
        else
-               return sizeof(struct pt_regs);
+               pos = sizeof(struct pt_regs);
+
+       return pos;
 }
 
 long compat_arch_ptrace(struct task_struct *child, compat_long_t request,
@@ -281,9 +286,12 @@ long compat_arch_ptrace(struct task_struct *child, compat_long_t request,
                        addr = translate_usr_offset(addr);
                        if (addr >= sizeof(struct pt_regs))
                                break;
+                       if (addr == PT_IAOQ0+4 || addr == PT_IAOQ1+4) {
+                               data |= 3; /* ensure userspace privilege */
+                       }
                        if (addr >= PT_FR0 && addr <= PT_FR31 + 4) {
                                /* Special case, fp regs are 64 bits anyway */
-                               *(__u64 *) ((char *) task_regs(child) + addr) = data;
+                               *(__u32 *) ((char *) task_regs(child) + addr) = data;
                                ret = 0;
                        }
                        else if ((addr >= PT_GR1+4 && addr <= PT_GR31+4) ||
@@ -308,15 +316,29 @@ long compat_arch_ptrace(struct task_struct *child, compat_long_t request,
 
 long do_syscall_trace_enter(struct pt_regs *regs)
 {
-       if (test_thread_flag(TIF_SYSCALL_TRACE) &&
-           tracehook_report_syscall_entry(regs)) {
+       if (test_thread_flag(TIF_SYSCALL_TRACE)) {
+               int rc = tracehook_report_syscall_entry(regs);
+
                /*
-                * Tracing decided this syscall should not happen or the
-                * debugger stored an invalid system call number. Skip
-                * the system call and the system call restart handling.
+                * As tracesys_next does not set %r28 to -ENOSYS
+                * when %r20 is set to -1, initialize it here.
                 */
-               regs->gr[20] = -1UL;
-               goto out;
+               regs->gr[28] = -ENOSYS;
+
+               if (rc) {
+                       /*
+                        * A nonzero return code from
+                        * tracehook_report_syscall_entry() tells us
+                        * to prevent the syscall execution.  Skip
+                        * the syscall call and the syscall restart handling.
+                        *
+                        * Note that the tracer may also just change
+                        * regs->gr[20] to an invalid syscall number,
+                        * that is handled by tracesys_next.
+                        */
+                       regs->gr[20] = -1UL;
+                       return -1;
+               }
        }
 
        /* Do the secure computing check after ptrace. */
@@ -340,7 +362,6 @@ long do_syscall_trace_enter(struct pt_regs *regs)
                        regs->gr[24] & 0xffffffff,
                        regs->gr[23] & 0xffffffff);
 
-out:
        /*
         * Sign extend the syscall number to 64bit since it may have been
         * modified by a compat ptrace call
@@ -483,7 +504,8 @@ static void set_reg(struct pt_regs *regs, int num, unsigned long val)
                        return;
        case RI(iaoq[0]):
        case RI(iaoq[1]):
-                       regs->iaoq[num - RI(iaoq[0])] = val;
+                       /* set 2 lowest bits to ensure userspace privilege: */
+                       regs->iaoq[num - RI(iaoq[0])] = val | 3;
                        return;
        case RI(sar):   regs->sar = val;
                        return;