x86/fpu: Fix irq_fpu_usable() to return false during CPU onlining
irq_fpu_usable() incorrectly returned true before the FPU is
initialized. The x86 CPU onlining code can call sha256() to checksum
AMD microcode images, before the FPU is initialized. Since sha256()
recently gained a kernel-mode FPU optimized code path, a crash occurred
in kernel_fpu_begin_mask() during hotplug CPU onlining.
(The crash did not occur during boot-time CPU onlining, since the
optimized sha256() code is not enabled until subsys_initcalls run.)
Fix this by making irq_fpu_usable() return false before fpu__init_cpu()
has run. To do this without adding any additional overhead to
irq_fpu_usable(), replace the existing per-CPU bool in_kernel_fpu with
kernel_fpu_allowed which tracks both initialization and usage rather
than just usage. The initial state is false; FPU initialization sets it
to true; kernel-mode FPU sections toggle it to false and then back to
true; and CPU offlining restores it to the initial state of false.
Fixes: 11d7956d52 ("crypto: x86/sha256 - implement library instead of shash")
Reported-by: Ayush Jain <Ayush.Jain3@amd.com>
Closes: https://lore.kernel.org/r/20250516112217.GBaCcf6Yoc6LkIIryP@fat_crate.local
Signed-off-by: Eric Biggers <ebiggers@google.com>
Tested-by: Ayush Jain <Ayush.Jain3@amd.com>
Reviewed-by: Thomas Gleixner <tglx@linutronix.de>
Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
pull/1250/head
parent
61fc01f8f7
commit
2297554f01
|
|
@ -126,6 +126,7 @@ static inline void fpstate_init_soft(struct swregs_state *soft) {}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* State tracking */
|
/* State tracking */
|
||||||
|
DECLARE_PER_CPU(bool, kernel_fpu_allowed);
|
||||||
DECLARE_PER_CPU(struct fpu *, fpu_fpregs_owner_ctx);
|
DECLARE_PER_CPU(struct fpu *, fpu_fpregs_owner_ctx);
|
||||||
|
|
||||||
/* Process cleanup */
|
/* Process cleanup */
|
||||||
|
|
|
||||||
|
|
@ -43,8 +43,11 @@ struct fpu_state_config fpu_user_cfg __ro_after_init;
|
||||||
*/
|
*/
|
||||||
struct fpstate init_fpstate __ro_after_init;
|
struct fpstate init_fpstate __ro_after_init;
|
||||||
|
|
||||||
/* Track in-kernel FPU usage */
|
/*
|
||||||
static DEFINE_PER_CPU(bool, in_kernel_fpu);
|
* Track FPU initialization and kernel-mode usage. 'true' means the FPU is
|
||||||
|
* initialized and is not currently being used by the kernel:
|
||||||
|
*/
|
||||||
|
DEFINE_PER_CPU(bool, kernel_fpu_allowed);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Track which context is using the FPU on the CPU:
|
* Track which context is using the FPU on the CPU:
|
||||||
|
|
@ -61,15 +64,18 @@ bool irq_fpu_usable(void)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* In kernel FPU usage already active? This detects any explicitly
|
* Return false in the following cases:
|
||||||
* nested usage in task or softirq context, which is unsupported. It
|
*
|
||||||
* also detects attempted usage in a hardirq that has interrupted a
|
* - FPU is not yet initialized. This can happen only when the call is
|
||||||
* kernel-mode FPU section.
|
* coming from CPU onlining, for example for microcode checksumming.
|
||||||
|
* - The kernel is already using the FPU, either because of explicit
|
||||||
|
* nesting (which should never be done), or because of implicit
|
||||||
|
* nesting when a hardirq interrupted a kernel-mode FPU section.
|
||||||
|
*
|
||||||
|
* The single boolean check below handles both cases:
|
||||||
*/
|
*/
|
||||||
if (this_cpu_read(in_kernel_fpu)) {
|
if (!this_cpu_read(kernel_fpu_allowed))
|
||||||
WARN_ON_FPU(!in_hardirq());
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* When not in NMI or hard interrupt context, FPU can be used in:
|
* When not in NMI or hard interrupt context, FPU can be used in:
|
||||||
|
|
@ -431,9 +437,10 @@ void kernel_fpu_begin_mask(unsigned int kfpu_mask)
|
||||||
fpregs_lock();
|
fpregs_lock();
|
||||||
|
|
||||||
WARN_ON_FPU(!irq_fpu_usable());
|
WARN_ON_FPU(!irq_fpu_usable());
|
||||||
WARN_ON_FPU(this_cpu_read(in_kernel_fpu));
|
|
||||||
|
|
||||||
this_cpu_write(in_kernel_fpu, true);
|
/* Toggle kernel_fpu_allowed to false: */
|
||||||
|
WARN_ON_FPU(!this_cpu_read(kernel_fpu_allowed));
|
||||||
|
this_cpu_write(kernel_fpu_allowed, false);
|
||||||
|
|
||||||
if (!(current->flags & (PF_KTHREAD | PF_USER_WORKER)) &&
|
if (!(current->flags & (PF_KTHREAD | PF_USER_WORKER)) &&
|
||||||
!test_thread_flag(TIF_NEED_FPU_LOAD)) {
|
!test_thread_flag(TIF_NEED_FPU_LOAD)) {
|
||||||
|
|
@ -453,9 +460,10 @@ EXPORT_SYMBOL_GPL(kernel_fpu_begin_mask);
|
||||||
|
|
||||||
void kernel_fpu_end(void)
|
void kernel_fpu_end(void)
|
||||||
{
|
{
|
||||||
WARN_ON_FPU(!this_cpu_read(in_kernel_fpu));
|
/* Toggle kernel_fpu_allowed back to true: */
|
||||||
|
WARN_ON_FPU(this_cpu_read(kernel_fpu_allowed));
|
||||||
|
this_cpu_write(kernel_fpu_allowed, true);
|
||||||
|
|
||||||
this_cpu_write(in_kernel_fpu, false);
|
|
||||||
if (!irqs_disabled())
|
if (!irqs_disabled())
|
||||||
fpregs_unlock();
|
fpregs_unlock();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,9 @@ void fpu__init_cpu(void)
|
||||||
{
|
{
|
||||||
fpu__init_cpu_generic();
|
fpu__init_cpu_generic();
|
||||||
fpu__init_cpu_xstate();
|
fpu__init_cpu_xstate();
|
||||||
|
|
||||||
|
/* Start allowing kernel-mode FPU: */
|
||||||
|
this_cpu_write(kernel_fpu_allowed, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool __init fpu__probe_without_cpuid(void)
|
static bool __init fpu__probe_without_cpuid(void)
|
||||||
|
|
|
||||||
|
|
@ -1188,6 +1188,12 @@ void cpu_disable_common(void)
|
||||||
|
|
||||||
remove_siblinginfo(cpu);
|
remove_siblinginfo(cpu);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Stop allowing kernel-mode FPU. This is needed so that if the CPU is
|
||||||
|
* brought online again, the initial state is not allowed:
|
||||||
|
*/
|
||||||
|
this_cpu_write(kernel_fpu_allowed, false);
|
||||||
|
|
||||||
/* It's now safe to remove this processor from the online map */
|
/* It's now safe to remove this processor from the online map */
|
||||||
lock_vector_lock();
|
lock_vector_lock();
|
||||||
remove_cpu_from_maps(cpu);
|
remove_cpu_from_maps(cpu);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue