Commit 5a1aca44 authored by Maciej W. Rozycki's avatar Maciej W. Rozycki Committed by Ralf Baechle

MIPS: Fix FCSR Cause bit handling for correct SIGFPE issue

Sanitize FCSR Cause bit handling, following a trail of past attempts:

* commit 42495484 ("MIPS: ptrace: Fix FP context restoration FCSR
regression"),

* commit 443c4403 ("MIPS: Always clear FCSR cause bits after
emulation"),

* commit 64bedffe ("MIPS: Clear [MSA]FPE CSR.Cause after
notify_die()"),

* commit b1442d39 ("MIPS: Prevent user from setting FCSR cause
bits"),

* commit b54d2901517d ("Properly handle branch delay slots in connection
with signals.").

Specifically do not mask these bits out in ptrace(2) processing and send
a SIGFPE signal instead whenever a matching pair of an FCSR Cause and
Enable bit is seen as execution of an affected context is about to
resume.  Only then clear Cause bits, and even then do not clear any bits
that are set but masked with the respective Enable bits.  Adjust Cause
bit clearing throughout code likewise, except within the FPU emulator
proper where they are set according to IEEE 754 exceptions raised as the
operation emulated executed.  Do so so that any IEEE 754 exceptions
subject to their default handling are recorded like with operations
executed by FPU hardware.
Signed-off-by: default avatarMaciej W. Rozycki <macro@imgtec.com>
Cc: Paul Burton <paul.burton@imgtec.com>
Cc: James Hogan <james.hogan@imgtec.com>
Cc: linux-mips@linux-mips.org
Cc: linux-kernel@vger.kernel.org
Patchwork: https://patchwork.linux-mips.org/patch/14460/Signed-off-by: default avatarRalf Baechle <ralf@linux-mips.org>
parent c9e56039
...@@ -63,6 +63,8 @@ do { \ ...@@ -63,6 +63,8 @@ do { \
extern int fpu_emulator_cop1Handler(struct pt_regs *xcp, extern int fpu_emulator_cop1Handler(struct pt_regs *xcp,
struct mips_fpu_struct *ctx, int has_fpu, struct mips_fpu_struct *ctx, int has_fpu,
void *__user *fault_addr); void *__user *fault_addr);
void force_fcr31_sig(unsigned long fcr31, void __user *fault_addr,
struct task_struct *tsk);
int process_fpemu_return(int sig, void __user *fault_addr, int process_fpemu_return(int sig, void __user *fault_addr,
unsigned long fcr31); unsigned long fcr31);
int isBranchInstr(struct pt_regs *regs, struct mm_decoded_insn dec_insn, int isBranchInstr(struct pt_regs *regs, struct mm_decoded_insn dec_insn,
...@@ -81,4 +83,15 @@ static inline void fpu_emulator_init_fpu(void) ...@@ -81,4 +83,15 @@ static inline void fpu_emulator_init_fpu(void)
set_fpr64(&t->thread.fpu.fpr[i], 0, SIGNALLING_NAN); set_fpr64(&t->thread.fpu.fpr[i], 0, SIGNALLING_NAN);
} }
/*
* Mask the FCSR Cause bits according to the Enable bits, observing
* that Unimplemented is always enabled.
*/
static inline unsigned long mask_fcr31_x(unsigned long fcr31)
{
return fcr31 & (FPU_CSR_UNI_X |
((fcr31 & FPU_CSR_ALL_E) <<
(ffs(FPU_CSR_ALL_X) - ffs(FPU_CSR_ALL_E))));
}
#endif /* _ASM_FPU_EMULATOR_H */ #endif /* _ASM_FPU_EMULATOR_H */
...@@ -75,6 +75,22 @@ do { if (cpu_has_rw_llb) { \ ...@@ -75,6 +75,22 @@ do { if (cpu_has_rw_llb) { \
} \ } \
} while (0) } while (0)
/*
* Check FCSR for any unmasked exceptions pending set with `ptrace',
* clear them and send a signal.
*/
#define __sanitize_fcr31(next) \
do { \
unsigned long fcr31 = mask_fcr31_x(next->thread.fpu.fcr31); \
void __user *pc; \
\
if (unlikely(fcr31)) { \
pc = (void __user *)task_pt_regs(next)->cp0_epc; \
next->thread.fpu.fcr31 &= ~fcr31; \
force_fcr31_sig(fcr31, pc, next); \
} \
} while (0)
/* /*
* For newly created kernel threads switch_to() will return to * For newly created kernel threads switch_to() will return to
* ret_from_kernel_thread, newly created user threads to ret_from_fork. * ret_from_kernel_thread, newly created user threads to ret_from_fork.
...@@ -85,6 +101,8 @@ do { if (cpu_has_rw_llb) { \ ...@@ -85,6 +101,8 @@ do { if (cpu_has_rw_llb) { \
do { \ do { \
__mips_mt_fpaff_switch_to(prev); \ __mips_mt_fpaff_switch_to(prev); \
lose_fpu_inatomic(1, prev); \ lose_fpu_inatomic(1, prev); \
if (tsk_used_math(next)) \
__sanitize_fcr31(next); \
if (cpu_has_dsp) { \ if (cpu_has_dsp) { \
__save_dsp(prev); \ __save_dsp(prev); \
__restore_dsp(next); \ __restore_dsp(next); \
......
...@@ -899,7 +899,7 @@ static inline int mipsr2_find_op_func(struct pt_regs *regs, u32 inst, ...@@ -899,7 +899,7 @@ static inline int mipsr2_find_op_func(struct pt_regs *regs, u32 inst,
* mipsr2_decoder: Decode and emulate a MIPS R2 instruction * mipsr2_decoder: Decode and emulate a MIPS R2 instruction
* @regs: Process register set * @regs: Process register set
* @inst: Instruction to decode and emulate * @inst: Instruction to decode and emulate
* @fcr31: Floating Point Control and Status Register returned * @fcr31: Floating Point Control and Status Register Cause bits returned
*/ */
int mipsr2_decoder(struct pt_regs *regs, u32 inst, unsigned long *fcr31) int mipsr2_decoder(struct pt_regs *regs, u32 inst, unsigned long *fcr31)
{ {
...@@ -1172,13 +1172,13 @@ int mipsr2_decoder(struct pt_regs *regs, u32 inst, unsigned long *fcr31) ...@@ -1172,13 +1172,13 @@ int mipsr2_decoder(struct pt_regs *regs, u32 inst, unsigned long *fcr31)
err = fpu_emulator_cop1Handler(regs, &current->thread.fpu, 0, err = fpu_emulator_cop1Handler(regs, &current->thread.fpu, 0,
&fault_addr); &fault_addr);
*fcr31 = current->thread.fpu.fcr31;
/* /*
* We can't allow the emulated instruction to leave any of * We can't allow the emulated instruction to leave any
* the cause bits set in $fcr31. * enabled Cause bits set in $fcr31.
*/ */
current->thread.fpu.fcr31 &= ~FPU_CSR_ALL_X; *fcr31 = res = mask_fcr31_x(current->thread.fpu.fcr31);
current->thread.fpu.fcr31 &= ~res;
/* /*
* this is a tricky issue - lose_fpu() uses LL/SC atomics * this is a tricky issue - lose_fpu() uses LL/SC atomics
......
...@@ -79,16 +79,15 @@ void ptrace_disable(struct task_struct *child) ...@@ -79,16 +79,15 @@ void ptrace_disable(struct task_struct *child)
} }
/* /*
* Poke at FCSR according to its mask. Don't set the cause bits as * Poke at FCSR according to its mask. Set the Cause bits even
* this is currently not handled correctly in FP context restoration * if a corresponding Enable bit is set. This will be noticed at
* and will cause an oops if a corresponding enable bit is set. * the time the thread is switched to and SIGFPE thrown accordingly.
*/ */
static void ptrace_setfcr31(struct task_struct *child, u32 value) static void ptrace_setfcr31(struct task_struct *child, u32 value)
{ {
u32 fcr31; u32 fcr31;
u32 mask; u32 mask;
value &= ~FPU_CSR_ALL_X;
fcr31 = child->thread.fpu.fcr31; fcr31 = child->thread.fpu.fcr31;
mask = boot_cpu_data.fpu_msk31; mask = boot_cpu_data.fpu_msk31;
child->thread.fpu.fcr31 = (value & ~mask) | (fcr31 & mask); child->thread.fpu.fcr31 = (value & ~mask) | (fcr31 & mask);
......
...@@ -708,6 +708,32 @@ asmlinkage void do_ov(struct pt_regs *regs) ...@@ -708,6 +708,32 @@ asmlinkage void do_ov(struct pt_regs *regs)
exception_exit(prev_state); exception_exit(prev_state);
} }
/*
* Send SIGFPE according to FCSR Cause bits, which must have already
* been masked against Enable bits. This is impotant as Inexact can
* happen together with Overflow or Underflow, and `ptrace' can set
* any bits.
*/
void force_fcr31_sig(unsigned long fcr31, void __user *fault_addr,
struct task_struct *tsk)
{
struct siginfo si = { .si_addr = fault_addr, .si_signo = SIGFPE };
if (fcr31 & FPU_CSR_INV_X)
si.si_code = FPE_FLTINV;
else if (fcr31 & FPU_CSR_DIV_X)
si.si_code = FPE_FLTDIV;
else if (fcr31 & FPU_CSR_OVF_X)
si.si_code = FPE_FLTOVF;
else if (fcr31 & FPU_CSR_UDF_X)
si.si_code = FPE_FLTUND;
else if (fcr31 & FPU_CSR_INE_X)
si.si_code = FPE_FLTRES;
else
si.si_code = __SI_FAULT;
force_sig_info(SIGFPE, &si, tsk);
}
int process_fpemu_return(int sig, void __user *fault_addr, unsigned long fcr31) int process_fpemu_return(int sig, void __user *fault_addr, unsigned long fcr31)
{ {
struct siginfo si = { 0 }; struct siginfo si = { 0 };
...@@ -718,27 +744,7 @@ int process_fpemu_return(int sig, void __user *fault_addr, unsigned long fcr31) ...@@ -718,27 +744,7 @@ int process_fpemu_return(int sig, void __user *fault_addr, unsigned long fcr31)
return 0; return 0;
case SIGFPE: case SIGFPE:
si.si_addr = fault_addr; force_fcr31_sig(fcr31, fault_addr, current);
si.si_signo = sig;
/*
* Inexact can happen together with Overflow or Underflow.
* Respect the mask to deliver the correct exception.
*/
fcr31 &= (fcr31 & FPU_CSR_ALL_E) <<
(ffs(FPU_CSR_ALL_X) - ffs(FPU_CSR_ALL_E));
if (fcr31 & FPU_CSR_INV_X)
si.si_code = FPE_FLTINV;
else if (fcr31 & FPU_CSR_DIV_X)
si.si_code = FPE_FLTDIV;
else if (fcr31 & FPU_CSR_OVF_X)
si.si_code = FPE_FLTOVF;
else if (fcr31 & FPU_CSR_UDF_X)
si.si_code = FPE_FLTUND;
else if (fcr31 & FPU_CSR_INE_X)
si.si_code = FPE_FLTRES;
else
si.si_code = __SI_FAULT;
force_sig_info(sig, &si, current);
return 1; return 1;
case SIGBUS: case SIGBUS:
...@@ -802,13 +808,13 @@ static int simulate_fp(struct pt_regs *regs, unsigned int opcode, ...@@ -802,13 +808,13 @@ static int simulate_fp(struct pt_regs *regs, unsigned int opcode,
/* Run the emulator */ /* Run the emulator */
sig = fpu_emulator_cop1Handler(regs, &current->thread.fpu, 1, sig = fpu_emulator_cop1Handler(regs, &current->thread.fpu, 1,
&fault_addr); &fault_addr);
fcr31 = current->thread.fpu.fcr31;
/* /*
* We can't allow the emulated instruction to leave any of * We can't allow the emulated instruction to leave any
* the cause bits set in $fcr31. * enabled Cause bits set in $fcr31.
*/ */
current->thread.fpu.fcr31 &= ~FPU_CSR_ALL_X; fcr31 = mask_fcr31_x(current->thread.fpu.fcr31);
current->thread.fpu.fcr31 &= ~fcr31;
/* Restore the hardware register state */ /* Restore the hardware register state */
own_fpu(1); own_fpu(1);
...@@ -834,7 +840,7 @@ asmlinkage void do_fpe(struct pt_regs *regs, unsigned long fcr31) ...@@ -834,7 +840,7 @@ asmlinkage void do_fpe(struct pt_regs *regs, unsigned long fcr31)
goto out; goto out;
/* Clear FCSR.Cause before enabling interrupts */ /* Clear FCSR.Cause before enabling interrupts */
write_32bit_cp1_register(CP1_STATUS, fcr31 & ~FPU_CSR_ALL_X); write_32bit_cp1_register(CP1_STATUS, fcr31 & ~mask_fcr31_x(fcr31));
local_irq_enable(); local_irq_enable();
die_if_kernel("FP exception in kernel code", regs); die_if_kernel("FP exception in kernel code", regs);
...@@ -856,13 +862,13 @@ asmlinkage void do_fpe(struct pt_regs *regs, unsigned long fcr31) ...@@ -856,13 +862,13 @@ asmlinkage void do_fpe(struct pt_regs *regs, unsigned long fcr31)
/* Run the emulator */ /* Run the emulator */
sig = fpu_emulator_cop1Handler(regs, &current->thread.fpu, 1, sig = fpu_emulator_cop1Handler(regs, &current->thread.fpu, 1,
&fault_addr); &fault_addr);
fcr31 = current->thread.fpu.fcr31;
/* /*
* We can't allow the emulated instruction to leave any of * We can't allow the emulated instruction to leave any
* the cause bits set in $fcr31. * enabled Cause bits set in $fcr31.
*/ */
current->thread.fpu.fcr31 &= ~FPU_CSR_ALL_X; fcr31 = mask_fcr31_x(current->thread.fpu.fcr31);
current->thread.fpu.fcr31 &= ~fcr31;
/* Restore the hardware register state */ /* Restore the hardware register state */
own_fpu(1); /* Using the FPU again. */ own_fpu(1); /* Using the FPU again. */
...@@ -1427,13 +1433,13 @@ asmlinkage void do_cpu(struct pt_regs *regs) ...@@ -1427,13 +1433,13 @@ asmlinkage void do_cpu(struct pt_regs *regs)
sig = fpu_emulator_cop1Handler(regs, &current->thread.fpu, 0, sig = fpu_emulator_cop1Handler(regs, &current->thread.fpu, 0,
&fault_addr); &fault_addr);
fcr31 = current->thread.fpu.fcr31;
/* /*
* We can't allow the emulated instruction to leave * We can't allow the emulated instruction to leave
* any of the cause bits set in $fcr31. * any enabled Cause bits set in $fcr31.
*/ */
current->thread.fpu.fcr31 &= ~FPU_CSR_ALL_X; fcr31 = mask_fcr31_x(current->thread.fpu.fcr31);
current->thread.fpu.fcr31 &= ~fcr31;
/* Send a signal if required. */ /* Send a signal if required. */
if (!process_fpemu_return(sig, fault_addr, fcr31) && !err) if (!process_fpemu_return(sig, fault_addr, fcr31) && !err)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment