powerpc: Fix swapcontext system for VSX + old ucontext size
authorMichael Neuling <mikey@neuling.org>
Thu, 23 Oct 2008 00:42:36 +0000 (00:42 +0000)
committerPaul Mackerras <paulus@samba.org>
Fri, 31 Oct 2008 05:12:00 +0000 (16:12 +1100)
Since VSX support was added, we now have two sizes of ucontext_t;
the older, smaller size without the extra VSX state, and the new
larger size with the extra VSX state.  A program using the
sys_swapcontext system call and supplying smaller ucontext_t
structures will currently get an EINVAL error if the task has
used VSX (e.g. because of calling library code that uses VSX) and
the old_ctx argument is non-NULL (i.e. the program is asking for
its current context to be saved).  Thus the program will start
getting EINVAL errors on calls that previously worked.

This commit changes this behaviour so that we don't send an EINVAL in
this case.  It will now return the smaller context but the VSX MSR bit
will always be cleared to indicate that the ucontext_t doesn't include
the extra VSX state, even if the task has executed VSX instructions.

Both 32 and 64 bit cases are updated.

[paulus@samba.org - also fix some access_ok() and get_user() calls]

Thanks to Ben Herrenschmidt for noticing this problem.

Signed-off-by: Michael Neuling <mikey@neuling.org>
Signed-off-by: Paul Mackerras <paulus@samba.org>
arch/powerpc/kernel/signal_32.c
arch/powerpc/kernel/signal_64.c

index 3e80aa32b8b09e1daf432cb7e112d9a04be7ce4a..a6a43103655e8625865b021b5cf3d59e2cee0221 100644 (file)
@@ -410,7 +410,7 @@ inline unsigned long copy_fpr_from_user(struct task_struct *task,
  * altivec/spe instructions at some point.
  */
 static int save_user_regs(struct pt_regs *regs, struct mcontext __user *frame,
-               int sigret)
+               int sigret, int ctx_has_vsx_region)
 {
        unsigned long msr = regs->msr;
 
@@ -451,7 +451,7 @@ static int save_user_regs(struct pt_regs *regs, struct mcontext __user *frame,
         * the saved MSR value to indicate that frame->mc_vregs
         * contains valid data
         */
-       if (current->thread.used_vsr) {
+       if (current->thread.used_vsr && ctx_has_vsx_region) {
                __giveup_vsx(current);
                if (copy_vsx_to_user(&frame->mc_vsregs, current))
                        return 1;
@@ -858,11 +858,11 @@ int handle_rt_signal32(unsigned long sig, struct k_sigaction *ka,
        frame = &rt_sf->uc.uc_mcontext;
        addr = frame;
        if (vdso32_rt_sigtramp && current->mm->context.vdso_base) {
-               if (save_user_regs(regs, frame, 0))
+               if (save_user_regs(regs, frame, 0, 1))
                        goto badframe;
                regs->link = current->mm->context.vdso_base + vdso32_rt_sigtramp;
        } else {
-               if (save_user_regs(regs, frame, __NR_rt_sigreturn))
+               if (save_user_regs(regs, frame, __NR_rt_sigreturn, 1))
                        goto badframe;
                regs->link = (unsigned long) frame->tramp;
        }
@@ -936,12 +936,13 @@ long sys_swapcontext(struct ucontext __user *old_ctx,
                     int ctx_size, int r6, int r7, int r8, struct pt_regs *regs)
 {
        unsigned char tmp;
+       int ctx_has_vsx_region = 0;
 
 #ifdef CONFIG_PPC64
        unsigned long new_msr = 0;
 
        if (new_ctx &&
-           __get_user(new_msr, &new_ctx->uc_mcontext.mc_gregs[PT_MSR]))
+           get_user(new_msr, &new_ctx->uc_mcontext.mc_gregs[PT_MSR]))
                return -EFAULT;
        /*
         * Check that the context is not smaller than the original
@@ -956,16 +957,9 @@ long sys_swapcontext(struct ucontext __user *old_ctx,
        if ((ctx_size < sizeof(struct ucontext)) &&
            (new_msr & MSR_VSX))
                return -EINVAL;
-#ifdef CONFIG_VSX
-       /*
-        * If userspace doesn't provide enough room for VSX data,
-        * but current thread has used VSX, we don't have anywhere
-        * to store the full context back into.
-        */
-       if ((ctx_size < sizeof(struct ucontext)) &&
-           (current->thread.used_vsr && old_ctx))
-               return -EINVAL;
-#endif
+       /* Does the context have enough room to store VSX data? */
+       if (ctx_size >= sizeof(struct ucontext))
+               ctx_has_vsx_region = 1;
 #else
        /* Context size is for future use. Right now, we only make sure
         * we are passed something we understand
@@ -985,17 +979,17 @@ long sys_swapcontext(struct ucontext __user *old_ctx,
                 */
                mctx = (struct mcontext __user *)
                        ((unsigned long) &old_ctx->uc_mcontext & ~0xfUL);
-               if (!access_ok(VERIFY_WRITE, old_ctx, sizeof(*old_ctx))
-                   || save_user_regs(regs, mctx, 0)
+               if (!access_ok(VERIFY_WRITE, old_ctx, ctx_size)
+                   || save_user_regs(regs, mctx, 0, ctx_has_vsx_region)
                    || put_sigset_t(&old_ctx->uc_sigmask, &current->blocked)
                    || __put_user(to_user_ptr(mctx), &old_ctx->uc_regs))
                        return -EFAULT;
        }
        if (new_ctx == NULL)
                return 0;
-       if (!access_ok(VERIFY_READ, new_ctx, sizeof(*new_ctx))
+       if (!access_ok(VERIFY_READ, new_ctx, ctx_size)
            || __get_user(tmp, (u8 __user *) new_ctx)
-           || __get_user(tmp, (u8 __user *) (new_ctx + 1) - 1))
+           || __get_user(tmp, (u8 __user *) new_ctx + ctx_size - 1))
                return -EFAULT;
 
        /*
@@ -1196,11 +1190,11 @@ int handle_signal32(unsigned long sig, struct k_sigaction *ka,
                goto badframe;
 
        if (vdso32_sigtramp && current->mm->context.vdso_base) {
-               if (save_user_regs(regs, &frame->mctx, 0))
+               if (save_user_regs(regs, &frame->mctx, 0, 1))
                        goto badframe;
                regs->link = current->mm->context.vdso_base + vdso32_sigtramp;
        } else {
-               if (save_user_regs(regs, &frame->mctx, __NR_sigreturn))
+               if (save_user_regs(regs, &frame->mctx, __NR_sigreturn, 1))
                        goto badframe;
                regs->link = (unsigned long) frame->mctx.tramp;
        }
index c6a8f2326b6f246fc077b25c2b7b1d9460f9e6cc..e132891d3cea00e947750aa53ec97288a6f4c839 100644 (file)
@@ -74,7 +74,8 @@ static const char fmt64[] = KERN_INFO \
  */
 
 static long setup_sigcontext(struct sigcontext __user *sc, struct pt_regs *regs,
-                int signr, sigset_t *set, unsigned long handler)
+                int signr, sigset_t *set, unsigned long handler,
+                int ctx_has_vsx_region)
 {
        /* When CONFIG_ALTIVEC is set, we _always_ setup v_regs even if the
         * process never used altivec yet (MSR_VEC is zero in pt_regs of
@@ -121,7 +122,7 @@ static long setup_sigcontext(struct sigcontext __user *sc, struct pt_regs *regs,
         * then out to userspace.  Update v_regs to point after the
         * VMX data.
         */
-       if (current->thread.used_vsr) {
+       if (current->thread.used_vsr && ctx_has_vsx_region) {
                __giveup_vsx(current);
                v_regs += ELF_NVRREG;
                err |= copy_vsx_to_user(v_regs, current);
@@ -282,9 +283,10 @@ int sys_swapcontext(struct ucontext __user *old_ctx,
        unsigned char tmp;
        sigset_t set;
        unsigned long new_msr = 0;
+       int ctx_has_vsx_region = 0;
 
        if (new_ctx &&
-           __get_user(new_msr, &new_ctx->uc_mcontext.gp_regs[PT_MSR]))
+           get_user(new_msr, &new_ctx->uc_mcontext.gp_regs[PT_MSR]))
                return -EFAULT;
        /*
         * Check that the context is not smaller than the original
@@ -299,28 +301,23 @@ int sys_swapcontext(struct ucontext __user *old_ctx,
        if ((ctx_size < sizeof(struct ucontext)) &&
            (new_msr & MSR_VSX))
                return -EINVAL;
-#ifdef CONFIG_VSX
-       /*
-        * If userspace doesn't provide enough room for VSX data,
-        * but current thread has used VSX, we don't have anywhere
-        * to store the full context back into.
-        */
-       if ((ctx_size < sizeof(struct ucontext)) &&
-           (current->thread.used_vsr && old_ctx))
-               return -EINVAL;
-#endif
+       /* Does the context have enough room to store VSX data? */
+       if (ctx_size >= sizeof(struct ucontext))
+               ctx_has_vsx_region = 1;
+
        if (old_ctx != NULL) {
-               if (!access_ok(VERIFY_WRITE, old_ctx, sizeof(*old_ctx))
-                   || setup_sigcontext(&old_ctx->uc_mcontext, regs, 0, NULL, 0)
+               if (!access_ok(VERIFY_WRITE, old_ctx, ctx_size)
+                   || setup_sigcontext(&old_ctx->uc_mcontext, regs, 0, NULL, 0,
+                                       ctx_has_vsx_region)
                    || __copy_to_user(&old_ctx->uc_sigmask,
                                      &current->blocked, sizeof(sigset_t)))
                        return -EFAULT;
        }
        if (new_ctx == NULL)
                return 0;
-       if (!access_ok(VERIFY_READ, new_ctx, sizeof(*new_ctx))
+       if (!access_ok(VERIFY_READ, new_ctx, ctx_size)
            || __get_user(tmp, (u8 __user *) new_ctx)
-           || __get_user(tmp, (u8 __user *) (new_ctx + 1) - 1))
+           || __get_user(tmp, (u8 __user *) new_ctx + ctx_size - 1))
                return -EFAULT;
 
        /*
@@ -423,7 +420,7 @@ int handle_rt_signal64(int signr, struct k_sigaction *ka, siginfo_t *info,
                          &frame->uc.uc_stack.ss_flags);
        err |= __put_user(current->sas_ss_size, &frame->uc.uc_stack.ss_size);
        err |= setup_sigcontext(&frame->uc.uc_mcontext, regs, signr, NULL,
-                               (unsigned long)ka->sa.sa_handler);
+                               (unsigned long)ka->sa.sa_handler, 1);
        err |= __copy_to_user(&frame->uc.uc_sigmask, set, sizeof(*set));
        if (err)
                goto badframe;