From: Paul Mackerras Date: Wed, 11 Jan 2006 11:11:39 +0000 (+1100) Subject: [PATCH] powerpc: Avoid potential FP corruption with preempt and UP X-Git-Tag: v2.6.16-rc1~184^2~15 X-Git-Url: https://err.no/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=5388fb1025443ec223ba556b10efc4c5f83f8682;p=linux-2.6 [PATCH] powerpc: Avoid potential FP corruption with preempt and UP 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 --- diff --git a/arch/powerpc/kernel/process.c b/arch/powerpc/kernel/process.c index 105d5609ff..913f90692a 100644 --- a/arch/powerpc/kernel/process.c +++ b/arch/powerpc/kernel/process.c @@ -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; @@ -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) { diff --git a/arch/powerpc/kernel/signal_32.c b/arch/powerpc/kernel/signal_32.c index d3f0b6d452..177bba78fb 100644 --- a/arch/powerpc/kernel/signal_32.c +++ b/arch/powerpc/kernel/signal_32.c @@ -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); @@ -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; } diff --git a/arch/powerpc/kernel/signal_64.c b/arch/powerpc/kernel/signal_64.c index 5462bef898..7b9d999e21 100644 --- a/arch/powerpc/kernel/signal_64.c +++ b/arch/powerpc/kernel/signal_64.c @@ -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(¤t->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(¤t->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]); @@ -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); diff --git a/include/asm-powerpc/system.h b/include/asm-powerpc/system.h index 0c58e32a95..4c888303e8 100644 --- a/include/asm-powerpc/system.h +++ b/include/asm-powerpc/system.h @@ -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