sparc32: fix register window handling in genregs32_[gs]et()
authorAl Viro <viro@zeniv.linux.org.uk>
Sun, 17 May 2020 16:20:40 +0000 (12:20 -0400)
committerAl Viro <viro@zeniv.linux.org.uk>
Wed, 20 May 2020 17:29:37 +0000 (13:29 -0400)
It needs access_process_vm() if the traced process does not share
mm with the caller.  Solution is similar to what sparc64 does.
Note that genregs32_set() is only ever called with pos being 0
or 32 * sizeof(u32) (the latter - as part of PTRACE_SETREGS
handling).

Cc: stable@kernel.org
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
arch/sparc/kernel/ptrace_32.c

index 16b50af..60f7205 100644 (file)
@@ -46,82 +46,79 @@ enum sparc_regset {
        REGSET_FP,
 };
 
+static int regwindow32_get(struct task_struct *target,
+                          const struct pt_regs *regs,
+                          u32 *uregs)
+{
+       unsigned long reg_window = regs->u_regs[UREG_I6];
+       int size = 16 * sizeof(u32);
+
+       if (target == current) {
+               if (copy_from_user(uregs, (void __user *)reg_window, size))
+                       return -EFAULT;
+       } else {
+               if (access_process_vm(target, reg_window, uregs, size,
+                                     FOLL_FORCE) != size)
+                       return -EFAULT;
+       }
+       return 0;
+}
+
+static int regwindow32_set(struct task_struct *target,
+                          const struct pt_regs *regs,
+                          u32 *uregs)
+{
+       unsigned long reg_window = regs->u_regs[UREG_I6];
+       int size = 16 * sizeof(u32);
+
+       if (target == current) {
+               if (copy_to_user((void __user *)reg_window, uregs, size))
+                       return -EFAULT;
+       } else {
+               if (access_process_vm(target, reg_window, uregs, size,
+                                     FOLL_FORCE | FOLL_WRITE) != size)
+                       return -EFAULT;
+       }
+       return 0;
+}
+
 static int genregs32_get(struct task_struct *target,
                         const struct user_regset *regset,
                         unsigned int pos, unsigned int count,
                         void *kbuf, void __user *ubuf)
 {
        const struct pt_regs *regs = target->thread.kregs;
-       unsigned long __user *reg_window;
-       unsigned long *k = kbuf;
-       unsigned long __user *u = ubuf;
-       unsigned long reg;
+       u32 uregs[16];
+       int ret;
 
        if (target == current)
                flush_user_windows();
 
-       pos /= sizeof(reg);
-       count /= sizeof(reg);
-
-       if (kbuf) {
-               for (; count > 0 && pos < 16; count--)
-                       *k++ = regs->u_regs[pos++];
-
-               reg_window = (unsigned long __user *) regs->u_regs[UREG_I6];
-               reg_window -= 16;
-               for (; count > 0 && pos < 32; count--) {
-                       if (get_user(*k++, &reg_window[pos++]))
-                               return -EFAULT;
-               }
-       } else {
-               for (; count > 0 && pos < 16; count--) {
-                       if (put_user(regs->u_regs[pos++], u++))
-                               return -EFAULT;
-               }
-
-               reg_window = (unsigned long __user *) regs->u_regs[UREG_I6];
-               reg_window -= 16;
-               for (; count > 0 && pos < 32; count--) {
-                       if (get_user(reg, &reg_window[pos++]) ||
-                           put_user(reg, u++))
-                               return -EFAULT;
-               }
-       }
-       while (count > 0) {
-               switch (pos) {
-               case 32: /* PSR */
-                       reg = regs->psr;
-                       break;
-               case 33: /* PC */
-                       reg = regs->pc;
-                       break;
-               case 34: /* NPC */
-                       reg = regs->npc;
-                       break;
-               case 35: /* Y */
-                       reg = regs->y;
-                       break;
-               case 36: /* WIM */
-               case 37: /* TBR */
-                       reg = 0;
-                       break;
-               default:
-                       goto finish;
-               }
+       ret = user_regset_copyout(&pos, &count, &kbuf, &ubuf,
+                                 regs->u_regs,
+                                 0, 16 * sizeof(u32));
+       if (ret || !count)
+               return ret;
 
-               if (kbuf)
-                       *k++ = reg;
-               else if (put_user(reg, u++))
+       if (pos < 32 * sizeof(u32)) {
+               if (regwindow32_get(target, regs, uregs))
                        return -EFAULT;
-               pos++;
-               count--;
+               ret = user_regset_copyout(&pos, &count, &kbuf, &ubuf,
+                                         uregs,
+                                         16 * sizeof(u32), 32 * sizeof(u32));
+               if (ret || !count)
+                       return ret;
        }
-finish:
-       pos *= sizeof(reg);
-       count *= sizeof(reg);
 
-       return user_regset_copyout_zero(&pos, &count, &kbuf, &ubuf,
-                                       38 * sizeof(reg), -1);
+       uregs[0] = regs->psr;
+       uregs[1] = regs->pc;
+       uregs[2] = regs->npc;
+       uregs[3] = regs->y;
+       uregs[4] = 0;   /* WIM */
+       uregs[5] = 0;   /* TBR */
+       return user_regset_copyout(&pos, &count, &kbuf, &ubuf,
+                                 uregs,
+                                 32 * sizeof(u32), 38 * sizeof(u32));
 }
 
 static int genregs32_set(struct task_struct *target,
@@ -130,82 +127,53 @@ static int genregs32_set(struct task_struct *target,
                         const void *kbuf, const void __user *ubuf)
 {
        struct pt_regs *regs = target->thread.kregs;
-       unsigned long __user *reg_window;
-       const unsigned long *k = kbuf;
-       const unsigned long __user *u = ubuf;
-       unsigned long reg;
+       u32 uregs[16];
+       u32 psr;
+       int ret;
 
        if (target == current)
                flush_user_windows();
 
-       pos /= sizeof(reg);
-       count /= sizeof(reg);
-
-       if (kbuf) {
-               for (; count > 0 && pos < 16; count--)
-                       regs->u_regs[pos++] = *k++;
-
-               reg_window = (unsigned long __user *) regs->u_regs[UREG_I6];
-               reg_window -= 16;
-               for (; count > 0 && pos < 32; count--) {
-                       if (put_user(*k++, &reg_window[pos++]))
-                               return -EFAULT;
-               }
-       } else {
-               for (; count > 0 && pos < 16; count--) {
-                       if (get_user(reg, u++))
-                               return -EFAULT;
-                       regs->u_regs[pos++] = reg;
-               }
-
-               reg_window = (unsigned long __user *) regs->u_regs[UREG_I6];
-               reg_window -= 16;
-               for (; count > 0 && pos < 32; count--) {
-                       if (get_user(reg, u++) ||
-                           put_user(reg, &reg_window[pos++]))
-                               return -EFAULT;
-               }
-       }
-       while (count > 0) {
-               unsigned long psr;
+       ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf,
+                                regs->u_regs,
+                                0, 16 * sizeof(u32));
+       if (ret || !count)
+               return ret;
 
-               if (kbuf)
-                       reg = *k++;
-               else if (get_user(reg, u++))
+       if (pos < 32 * sizeof(u32)) {
+               if (regwindow32_get(target, regs, uregs))
                        return -EFAULT;
-
-               switch (pos) {
-               case 32: /* PSR */
-                       psr = regs->psr;
-                       psr &= ~(PSR_ICC | PSR_SYSCALL);
-                       psr |= (reg & (PSR_ICC | PSR_SYSCALL));
-                       regs->psr = psr;
-                       break;
-               case 33: /* PC */
-                       regs->pc = reg;
-                       break;
-               case 34: /* NPC */
-                       regs->npc = reg;
-                       break;
-               case 35: /* Y */
-                       regs->y = reg;
-                       break;
-               case 36: /* WIM */
-               case 37: /* TBR */
-                       break;
-               default:
-                       goto finish;
-               }
-
-               pos++;
-               count--;
+               ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf,
+                                        uregs,
+                                        16 * sizeof(u32), 32 * sizeof(u32));
+               if (ret)
+                       return ret;
+               if (regwindow32_set(target, regs, uregs))
+                       return -EFAULT;
+               if (!count)
+                       return 0;
        }
-finish:
-       pos *= sizeof(reg);
-       count *= sizeof(reg);
-
+       ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf,
+                                &psr,
+                                32 * sizeof(u32), 33 * sizeof(u32));
+       if (ret)
+               return ret;
+       regs->psr = (regs->psr & ~(PSR_ICC | PSR_SYSCALL)) |
+                   (psr & (PSR_ICC | PSR_SYSCALL));
+       if (!count)
+               return 0;
+       ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf,
+                                &regs->pc,
+                                33 * sizeof(u32), 34 * sizeof(u32));
+       if (ret || !count)
+               return ret;
+       ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf,
+                                &regs->y,
+                                34 * sizeof(u32), 35 * sizeof(u32));
+       if (ret || !count)
+               return ret;
        return user_regset_copyin_ignore(&pos, &count, &kbuf, &ubuf,
-                                        38 * sizeof(reg), -1);
+                                        35 * sizeof(u32), 38 * sizeof(u32));
 }
 
 static int fpregs32_get(struct task_struct *target,