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>
This commit is contained in:
Michael Neuling 2008-10-23 00:42:36 +00:00 коммит произвёл Paul Mackerras
Родитель b160544ccc
Коммит 16c29d180b
2 изменённых файлов: 30 добавлений и 39 удалений

Просмотреть файл

@ -410,7 +410,7 @@ inline unsigned long copy_fpr_from_user(struct task_struct *task,
* altivec/spe instructions at some point. * altivec/spe instructions at some point.
*/ */
static int save_user_regs(struct pt_regs *regs, struct mcontext __user *frame, 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; 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 * the saved MSR value to indicate that frame->mc_vregs
* contains valid data * contains valid data
*/ */
if (current->thread.used_vsr) { if (current->thread.used_vsr && ctx_has_vsx_region) {
__giveup_vsx(current); __giveup_vsx(current);
if (copy_vsx_to_user(&frame->mc_vsregs, current)) if (copy_vsx_to_user(&frame->mc_vsregs, current))
return 1; return 1;
@ -858,11 +858,11 @@ int handle_rt_signal32(unsigned long sig, struct k_sigaction *ka,
frame = &rt_sf->uc.uc_mcontext; frame = &rt_sf->uc.uc_mcontext;
addr = frame; addr = frame;
if (vdso32_rt_sigtramp && current->mm->context.vdso_base) { 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; goto badframe;
regs->link = current->mm->context.vdso_base + vdso32_rt_sigtramp; regs->link = current->mm->context.vdso_base + vdso32_rt_sigtramp;
} else { } else {
if (save_user_regs(regs, frame, __NR_rt_sigreturn)) if (save_user_regs(regs, frame, __NR_rt_sigreturn, 1))
goto badframe; goto badframe;
regs->link = (unsigned long) frame->tramp; 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) int ctx_size, int r6, int r7, int r8, struct pt_regs *regs)
{ {
unsigned char tmp; unsigned char tmp;
int ctx_has_vsx_region = 0;
#ifdef CONFIG_PPC64 #ifdef CONFIG_PPC64
unsigned long new_msr = 0; unsigned long new_msr = 0;
if (new_ctx && 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; return -EFAULT;
/* /*
* Check that the context is not smaller than the original * 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)) && if ((ctx_size < sizeof(struct ucontext)) &&
(new_msr & MSR_VSX)) (new_msr & MSR_VSX))
return -EINVAL; return -EINVAL;
#ifdef CONFIG_VSX /* Does the context have enough room to store VSX data? */
/* if (ctx_size >= sizeof(struct ucontext))
* If userspace doesn't provide enough room for VSX data, ctx_has_vsx_region = 1;
* 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
#else #else
/* Context size is for future use. Right now, we only make sure /* Context size is for future use. Right now, we only make sure
* we are passed something we understand * we are passed something we understand
@ -985,17 +979,17 @@ long sys_swapcontext(struct ucontext __user *old_ctx,
*/ */
mctx = (struct mcontext __user *) mctx = (struct mcontext __user *)
((unsigned long) &old_ctx->uc_mcontext & ~0xfUL); ((unsigned long) &old_ctx->uc_mcontext & ~0xfUL);
if (!access_ok(VERIFY_WRITE, old_ctx, sizeof(*old_ctx)) if (!access_ok(VERIFY_WRITE, old_ctx, ctx_size)
|| save_user_regs(regs, mctx, 0) || save_user_regs(regs, mctx, 0, ctx_has_vsx_region)
|| put_sigset_t(&old_ctx->uc_sigmask, &current->blocked) || put_sigset_t(&old_ctx->uc_sigmask, &current->blocked)
|| __put_user(to_user_ptr(mctx), &old_ctx->uc_regs)) || __put_user(to_user_ptr(mctx), &old_ctx->uc_regs))
return -EFAULT; return -EFAULT;
} }
if (new_ctx == NULL) if (new_ctx == NULL)
return 0; 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)
|| __get_user(tmp, (u8 __user *) (new_ctx + 1) - 1)) || __get_user(tmp, (u8 __user *) new_ctx + ctx_size - 1))
return -EFAULT; return -EFAULT;
/* /*
@ -1196,11 +1190,11 @@ int handle_signal32(unsigned long sig, struct k_sigaction *ka,
goto badframe; goto badframe;
if (vdso32_sigtramp && current->mm->context.vdso_base) { 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; goto badframe;
regs->link = current->mm->context.vdso_base + vdso32_sigtramp; regs->link = current->mm->context.vdso_base + vdso32_sigtramp;
} else { } else {
if (save_user_regs(regs, &frame->mctx, __NR_sigreturn)) if (save_user_regs(regs, &frame->mctx, __NR_sigreturn, 1))
goto badframe; goto badframe;
regs->link = (unsigned long) frame->mctx.tramp; regs->link = (unsigned long) frame->mctx.tramp;
} }

Просмотреть файл

@ -74,7 +74,8 @@ static const char fmt64[] = KERN_INFO \
*/ */
static long setup_sigcontext(struct sigcontext __user *sc, struct pt_regs *regs, 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 /* 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 * 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 * then out to userspace. Update v_regs to point after the
* VMX data. * VMX data.
*/ */
if (current->thread.used_vsr) { if (current->thread.used_vsr && ctx_has_vsx_region) {
__giveup_vsx(current); __giveup_vsx(current);
v_regs += ELF_NVRREG; v_regs += ELF_NVRREG;
err |= copy_vsx_to_user(v_regs, current); err |= copy_vsx_to_user(v_regs, current);
@ -282,9 +283,10 @@ int sys_swapcontext(struct ucontext __user *old_ctx,
unsigned char tmp; unsigned char tmp;
sigset_t set; sigset_t set;
unsigned long new_msr = 0; unsigned long new_msr = 0;
int ctx_has_vsx_region = 0;
if (new_ctx && 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; return -EFAULT;
/* /*
* Check that the context is not smaller than the original * 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)) && if ((ctx_size < sizeof(struct ucontext)) &&
(new_msr & MSR_VSX)) (new_msr & MSR_VSX))
return -EINVAL; return -EINVAL;
#ifdef CONFIG_VSX /* Does the context have enough room to store VSX data? */
/* if (ctx_size >= sizeof(struct ucontext))
* If userspace doesn't provide enough room for VSX data, ctx_has_vsx_region = 1;
* 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
if (old_ctx != NULL) { if (old_ctx != NULL) {
if (!access_ok(VERIFY_WRITE, old_ctx, sizeof(*old_ctx)) if (!access_ok(VERIFY_WRITE, old_ctx, ctx_size)
|| setup_sigcontext(&old_ctx->uc_mcontext, regs, 0, NULL, 0) || setup_sigcontext(&old_ctx->uc_mcontext, regs, 0, NULL, 0,
ctx_has_vsx_region)
|| __copy_to_user(&old_ctx->uc_sigmask, || __copy_to_user(&old_ctx->uc_sigmask,
&current->blocked, sizeof(sigset_t))) &current->blocked, sizeof(sigset_t)))
return -EFAULT; return -EFAULT;
} }
if (new_ctx == NULL) if (new_ctx == NULL)
return 0; 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)
|| __get_user(tmp, (u8 __user *) (new_ctx + 1) - 1)) || __get_user(tmp, (u8 __user *) new_ctx + ctx_size - 1))
return -EFAULT; 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); &frame->uc.uc_stack.ss_flags);
err |= __put_user(current->sas_ss_size, &frame->uc.uc_stack.ss_size); err |= __put_user(current->sas_ss_size, &frame->uc.uc_stack.ss_size);
err |= setup_sigcontext(&frame->uc.uc_mcontext, regs, signr, NULL, 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)); err |= __copy_to_user(&frame->uc.uc_sigmask, set, sizeof(*set));
if (err) if (err)
goto badframe; goto badframe;