Skip to content

Commit 2297554

Browse files
ebiggersherbertx
authored andcommitted
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: 11d7956 ("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>
1 parent 61fc01f commit 2297554

File tree

4 files changed

+31
-13
lines changed

4 files changed

+31
-13
lines changed

arch/x86/include/asm/fpu/api.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ static inline void fpstate_init_soft(struct swregs_state *soft) {}
126126
#endif
127127

128128
/* State tracking */
129+
DECLARE_PER_CPU(bool, kernel_fpu_allowed);
129130
DECLARE_PER_CPU(struct fpu *, fpu_fpregs_owner_ctx);
130131

131132
/* Process cleanup */

arch/x86/kernel/fpu/core.c

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,11 @@ struct fpu_state_config fpu_user_cfg __ro_after_init;
4343
*/
4444
struct fpstate init_fpstate __ro_after_init;
4545

46-
/* Track in-kernel FPU usage */
47-
static DEFINE_PER_CPU(bool, in_kernel_fpu);
46+
/*
47+
* Track FPU initialization and kernel-mode usage. 'true' means the FPU is
48+
* initialized and is not currently being used by the kernel:
49+
*/
50+
DEFINE_PER_CPU(bool, kernel_fpu_allowed);
4851

4952
/*
5053
* Track which context is using the FPU on the CPU:
@@ -61,15 +64,18 @@ bool irq_fpu_usable(void)
6164
return false;
6265

6366
/*
64-
* In kernel FPU usage already active? This detects any explicitly
65-
* nested usage in task or softirq context, which is unsupported. It
66-
* also detects attempted usage in a hardirq that has interrupted a
67-
* kernel-mode FPU section.
67+
* Return false in the following cases:
68+
*
69+
* - FPU is not yet initialized. This can happen only when the call is
70+
* coming from CPU onlining, for example for microcode checksumming.
71+
* - The kernel is already using the FPU, either because of explicit
72+
* nesting (which should never be done), or because of implicit
73+
* nesting when a hardirq interrupted a kernel-mode FPU section.
74+
*
75+
* The single boolean check below handles both cases:
6876
*/
69-
if (this_cpu_read(in_kernel_fpu)) {
70-
WARN_ON_FPU(!in_hardirq());
77+
if (!this_cpu_read(kernel_fpu_allowed))
7178
return false;
72-
}
7379

7480
/*
7581
* 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)
431437
fpregs_lock();
432438

433439
WARN_ON_FPU(!irq_fpu_usable());
434-
WARN_ON_FPU(this_cpu_read(in_kernel_fpu));
435440

436-
this_cpu_write(in_kernel_fpu, true);
441+
/* Toggle kernel_fpu_allowed to false: */
442+
WARN_ON_FPU(!this_cpu_read(kernel_fpu_allowed));
443+
this_cpu_write(kernel_fpu_allowed, false);
437444

438445
if (!(current->flags & (PF_KTHREAD | PF_USER_WORKER)) &&
439446
!test_thread_flag(TIF_NEED_FPU_LOAD)) {
@@ -453,9 +460,10 @@ EXPORT_SYMBOL_GPL(kernel_fpu_begin_mask);
453460

454461
void kernel_fpu_end(void)
455462
{
456-
WARN_ON_FPU(!this_cpu_read(in_kernel_fpu));
463+
/* Toggle kernel_fpu_allowed back to true: */
464+
WARN_ON_FPU(this_cpu_read(kernel_fpu_allowed));
465+
this_cpu_write(kernel_fpu_allowed, true);
457466

458-
this_cpu_write(in_kernel_fpu, false);
459467
if (!irqs_disabled())
460468
fpregs_unlock();
461469
}

arch/x86/kernel/fpu/init.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ void fpu__init_cpu(void)
5151
{
5252
fpu__init_cpu_generic();
5353
fpu__init_cpu_xstate();
54+
55+
/* Start allowing kernel-mode FPU: */
56+
this_cpu_write(kernel_fpu_allowed, true);
5457
}
5558

5659
static bool __init fpu__probe_without_cpuid(void)

arch/x86/kernel/smpboot.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1188,6 +1188,12 @@ void cpu_disable_common(void)
11881188

11891189
remove_siblinginfo(cpu);
11901190

1191+
/*
1192+
* Stop allowing kernel-mode FPU. This is needed so that if the CPU is
1193+
* brought online again, the initial state is not allowed:
1194+
*/
1195+
this_cpu_write(kernel_fpu_allowed, false);
1196+
11911197
/* It's now safe to remove this processor from the online map */
11921198
lock_vector_lock();
11931199
remove_cpu_from_maps(cpu);

0 commit comments

Comments
 (0)