Skip to content

Commit 59f5ede

Browse files
committed
x86/fpu: Prevent FPU state corruption
The FPU usage related to task FPU management is either protected by disabling interrupts (switch_to, return to user) or via fpregs_lock() which is a wrapper around local_bh_disable(). When kernel code wants to use the FPU then it has to check whether it is possible by calling irq_fpu_usable(). But the condition in irq_fpu_usable() is wrong. It allows FPU to be used when: !in_interrupt() || interrupted_user_mode() || interrupted_kernel_fpu_idle() The latter is checking whether some other context already uses FPU in the kernel, but if that's not the case then it allows FPU to be used unconditionally even if the calling context interrupted a fpregs_lock() critical region. If that happens then the FPU state of the interrupted context becomes corrupted. Allow in kernel FPU usage only when no other context has in kernel FPU usage and either the calling context is not hard interrupt context or the hard interrupt did not interrupt a local bottomhalf disabled region. It's hard to find a proper Fixes tag as the condition was broken in one way or the other for a very long time and the eager/lazy FPU changes caused a lot of churn. Picked something remotely connected from the history. This survived undetected for quite some time as FPU usage in interrupt context is rare, but the recent changes to the random code unearthed it at least on a kernel which had FPU debugging enabled. There is probably a higher rate of silent corruption as not all issues can be detected by the FPU debugging code. This will be addressed in a subsequent change. Fixes: 5d2bd70 ("x86, fpu: decouple non-lazy/eager fpu restore from xsave") Reported-by: Filipe Manana <fdmanana@suse.com> Signed-off-by: Thomas Gleixner <tglx@linutronix.de> Tested-by: Filipe Manana <fdmanana@suse.com> Reviewed-by: Borislav Petkov <bp@suse.de> Cc: stable@vger.kernel.org Link: https://lore.kernel.org/r/20220501193102.588689270@linutronix.de
1 parent 770fb09 commit 59f5ede

1 file changed

Lines changed: 26 additions & 41 deletions

File tree

arch/x86/kernel/fpu/core.c

Lines changed: 26 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -41,60 +41,45 @@ struct fpu_state_config fpu_user_cfg __ro_after_init;
4141
*/
4242
struct fpstate init_fpstate __ro_after_init;
4343

44-
/*
45-
* Track whether the kernel is using the FPU state
46-
* currently.
47-
*
48-
* This flag is used:
49-
*
50-
* - by IRQ context code to potentially use the FPU
51-
* if it's unused.
52-
*
53-
* - to debug kernel_fpu_begin()/end() correctness
54-
*/
44+
/* Track in-kernel FPU usage */
5545
static DEFINE_PER_CPU(bool, in_kernel_fpu);
5646

5747
/*
5848
* Track which context is using the FPU on the CPU:
5949
*/
6050
DEFINE_PER_CPU(struct fpu *, fpu_fpregs_owner_ctx);
6151

62-
static bool kernel_fpu_disabled(void)
63-
{
64-
return this_cpu_read(in_kernel_fpu);
65-
}
66-
67-
static bool interrupted_kernel_fpu_idle(void)
68-
{
69-
return !kernel_fpu_disabled();
70-
}
71-
72-
/*
73-
* Were we in user mode (or vm86 mode) when we were
74-
* interrupted?
75-
*
76-
* Doing kernel_fpu_begin/end() is ok if we are running
77-
* in an interrupt context from user mode - we'll just
78-
* save the FPU state as required.
79-
*/
80-
static bool interrupted_user_mode(void)
81-
{
82-
struct pt_regs *regs = get_irq_regs();
83-
return regs && user_mode(regs);
84-
}
85-
8652
/*
8753
* Can we use the FPU in kernel mode with the
8854
* whole "kernel_fpu_begin/end()" sequence?
89-
*
90-
* It's always ok in process context (ie "not interrupt")
91-
* but it is sometimes ok even from an irq.
9255
*/
9356
bool irq_fpu_usable(void)
9457
{
95-
return !in_interrupt() ||
96-
interrupted_user_mode() ||
97-
interrupted_kernel_fpu_idle();
58+
if (WARN_ON_ONCE(in_nmi()))
59+
return false;
60+
61+
/* In kernel FPU usage already active? */
62+
if (this_cpu_read(in_kernel_fpu))
63+
return false;
64+
65+
/*
66+
* When not in NMI or hard interrupt context, FPU can be used in:
67+
*
68+
* - Task context except from within fpregs_lock()'ed critical
69+
* regions.
70+
*
71+
* - Soft interrupt processing context which cannot happen
72+
* while in a fpregs_lock()'ed critical region.
73+
*/
74+
if (!in_hardirq())
75+
return true;
76+
77+
/*
78+
* In hard interrupt context it's safe when soft interrupts
79+
* are enabled, which means the interrupt did not hit in
80+
* a fpregs_lock()'ed critical region.
81+
*/
82+
return !softirq_count();
9883
}
9984
EXPORT_SYMBOL(irq_fpu_usable);
10085

0 commit comments

Comments
 (0)