From 16c29d180becc5bdf92fd0fc7314a44a671b5f4e Mon Sep 17 00:00:00 2001 From: Michael Neuling Date: Thu, 23 Oct 2008 00:42:36 +0000 Subject: 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 Signed-off-by: Paul Mackerras --- arch/powerpc/kernel/signal_32.c | 36 +++++++++++++++--------------------- arch/powerpc/kernel/signal_64.c | 33 +++++++++++++++------------------ 2 files changed, 30 insertions(+), 39 deletions(-) (limited to 'arch') diff --git a/arch/powerpc/kernel/signal_32.c b/arch/powerpc/kernel/signal_32.c index 3e80aa32b8b0..a6a43103655e 100644 --- a/arch/powerpc/kernel/signal_32.c +++ b/arch/powerpc/kernel/signal_32.c @@ -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, ¤t->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; } diff --git a/arch/powerpc/kernel/signal_64.c b/arch/powerpc/kernel/signal_64.c index c6a8f2326b6f..e132891d3cea 100644 --- a/arch/powerpc/kernel/signal_64.c +++ b/arch/powerpc/kernel/signal_64.c @@ -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, ¤t->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; -- cgit v1.2.2