powerpc: Fix swapcontext system for VSX + old ucontext size

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>
diff --git a/arch/powerpc/kernel/signal_32.c b/arch/powerpc/kernel/signal_32.c
index 3e80aa3..a6a4310 100644
--- a/arch/powerpc/kernel/signal_32.c
+++ b/arch/powerpc/kernel/signal_32.c
@@ -410,7 +410,7 @@
  * 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 @@
 	 * 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 @@
 	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 @@
 		     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 @@
 	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 @@
 		 */
 		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 @@
 		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;
 	}