Skip to content

Commit

Permalink
[PATCH] powerpc: Avoid potential FP corruption with preempt and UP
Browse files Browse the repository at this point in the history
Heikki Lindholm pointed out that there was a potential race with the
lazy CPU state (FP, VR, EVR) stuff if preempt is enabled.  The race
is that in the process of restoring FP state on sigreturn, the task
gets preempted by a user task that wants to use the FPU.  It will take
an FP unavailable exception, which will write the current FPU state
to the thread_struct, overwriting the values which sigreturn has
stored.  Note that this can only happen on UP since we don't implement
lazy CPU state on SMP.

The fix is to flush the lazy CPU state before updating the
thread_struct.  To do this we re-use the flush_lazy_cpu_state()
function from process.c.

Signed-off-by: Paul Mackerras <paulus@samba.org>
  • Loading branch information
Paul Mackerras committed Jan 12, 2006
1 parent 593195f commit 5388fb1
Show file tree
Hide file tree
Showing 4 changed files with 31 additions and 24 deletions.
6 changes: 3 additions & 3 deletions arch/powerpc/kernel/process.c
Original file line number Diff line number Diff line change
Expand Up @@ -201,13 +201,13 @@ int dump_spe(struct pt_regs *regs, elf_vrregset_t *evrregs)
}
#endif /* CONFIG_SPE */

#ifndef CONFIG_SMP
/*
* If we are doing lazy switching of CPU state (FP, altivec or SPE),
* and the current task has some state, discard it.
*/
static inline void discard_lazy_cpu_state(void)
void discard_lazy_cpu_state(void)
{
#ifndef CONFIG_SMP
preempt_disable();
if (last_task_used_math == current)
last_task_used_math = NULL;
Expand All @@ -220,8 +220,8 @@ static inline void discard_lazy_cpu_state(void)
last_task_used_spe = NULL;
#endif
preempt_enable();
#endif /* CONFIG_SMP */
}
#endif /* CONFIG_SMP */

int set_dabr(unsigned long dabr)
{
Expand Down
21 changes: 9 additions & 12 deletions arch/powerpc/kernel/signal_32.c
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,15 @@ static long restore_user_regs(struct pt_regs *regs,
if (err)
return 1;

/*
* Do this before updating the thread state in
* current->thread.fpr/vr/evr. That way, if we get preempted
* and another task grabs the FPU/Altivec/SPE, it won't be
* tempted to save the current CPU state into the thread_struct
* and corrupt what we are writing there.
*/
discard_lazy_cpu_state();

/* force the process to reload the FP registers from
current->thread when it next does FP instructions */
regs->msr &= ~(MSR_FP | MSR_FE0 | MSR_FE1);
Expand Down Expand Up @@ -538,18 +547,6 @@ static long restore_user_regs(struct pt_regs *regs,
return 1;
#endif /* CONFIG_SPE */

#ifndef CONFIG_SMP
preempt_disable();
if (last_task_used_math == current)
last_task_used_math = NULL;
if (last_task_used_altivec == current)
last_task_used_altivec = NULL;
#ifdef CONFIG_SPE
if (last_task_used_spe == current)
last_task_used_spe = NULL;
#endif
preempt_enable();
#endif
return 0;
}

Expand Down
20 changes: 11 additions & 9 deletions arch/powerpc/kernel/signal_64.c
Original file line number Diff line number Diff line change
Expand Up @@ -207,10 +207,20 @@ static long restore_sigcontext(struct pt_regs *regs, sigset_t *set, int sig,

if (!sig)
regs->gpr[13] = save_r13;
err |= __copy_from_user(&current->thread.fpr, &sc->fp_regs, FP_REGS_SIZE);
if (set != NULL)
err |= __get_user(set->sig[0], &sc->oldmask);

/*
* Do this before updating the thread state in
* current->thread.fpr/vr. That way, if we get preempted
* and another task grabs the FPU/Altivec, it won't be
* tempted to save the current CPU state into the thread_struct
* and corrupt what we are writing there.
*/
discard_lazy_cpu_state();

err |= __copy_from_user(&current->thread.fpr, &sc->fp_regs, FP_REGS_SIZE);

#ifdef CONFIG_ALTIVEC
err |= __get_user(v_regs, &sc->v_regs);
err |= __get_user(msr, &sc->gp_regs[PT_MSR]);
Expand All @@ -229,14 +239,6 @@ static long restore_sigcontext(struct pt_regs *regs, sigset_t *set, int sig,
current->thread.vrsave = 0;
#endif /* CONFIG_ALTIVEC */

#ifndef CONFIG_SMP
preempt_disable();
if (last_task_used_math == current)
last_task_used_math = NULL;
if (last_task_used_altivec == current)
last_task_used_altivec = NULL;
preempt_enable();
#endif
/* Force reload of FP/VEC */
regs->msr &= ~(MSR_FP | MSR_FE0 | MSR_FE1 | MSR_VEC);

Expand Down
8 changes: 8 additions & 0 deletions include/asm-powerpc/system.h
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,14 @@ extern int fix_alignment(struct pt_regs *);
extern void cvt_fd(float *from, double *to, struct thread_struct *thread);
extern void cvt_df(double *from, float *to, struct thread_struct *thread);

#ifndef CONFIG_SMP
extern void discard_lazy_cpu_state(void);
#else
static inline void discard_lazy_cpu_state(void)
{
}
#endif

#ifdef CONFIG_ALTIVEC
extern void flush_altivec_to_thread(struct task_struct *);
#else
Expand Down

0 comments on commit 5388fb1

Please sign in to comment.