Skip to content

Commit 2c340aa

Browse files
committed
x86/efi: Restore IRQ state in EFI page fault handler
The kernel's softirq API does not permit re-enabling softirqs while IRQs are disabled. The reason for this is that local_bh_enable() will not only re-enable delivery of softirqs over the back of IRQs, it will also handle any pending softirqs immediately, regardless of whether IRQs are enabled at that point. For this reason, commit d021985 ("x86/fpu: Improve crypto performance by making kernel-mode FPU reliably usable in softirqs") disables softirqs only when IRQs are enabled, as it is not permitted otherwise, but also unnecessary, given that asynchronous softirq delivery never happens to begin with while IRQs are disabled. However, this does mean that entering a kernel mode FPU section with IRQs enabled and leaving it with IRQs disabled leads to problems, as identified by Sashiko [0]: the EFI page fault handler is called from page_fault_oops() with IRQs disabled, and thus ends the kernel mode FPU section with IRQs disabled as well, regardless of whether IRQs were enabled when it was started. This may result in schedule() being called with a non-zero preempt_count, causing a BUG(). So take care to re-enable IRQs when handling any EFI page faults if they were taken with IRQs enabled. [0] https://sashiko.dev/#/patchset/20260430074107.27051-1-ivan.hu%40canonical.com Cc: Eric Biggers <ebiggers@kernel.org> Cc: Ivan Hu <ivan.hu@canonical.com> Cc: x86@kernel.org Cc: <stable@vger.kernel.org> Fixes: d021985 ("x86/fpu: Improve crypto performance by making kernel-mode FPU reliably usable in softirqs") Reviewed-by: Eric Biggers <ebiggers@kernel.org> Signed-off-by: Ard Biesheuvel <ardb@kernel.org>
1 parent 088f65e commit 2c340aa

3 files changed

Lines changed: 13 additions & 3 deletions

File tree

arch/x86/include/asm/efi.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,8 @@ extern void __init efi_dump_pagetable(void);
137137
extern void __init efi_apply_memmap_quirks(void);
138138
extern int __init efi_reuse_config(u64 tables, int nr_tables);
139139
extern void efi_delete_dummy_variable(void);
140-
extern void efi_crash_gracefully_on_page_fault(unsigned long phys_addr);
140+
extern void efi_crash_gracefully_on_page_fault(unsigned long phys_addr,
141+
const struct pt_regs *regs);
141142
extern void efi_unmap_boot_services(void);
142143

143144
void arch_efi_call_virt_setup(void);

arch/x86/mm/fault.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -686,7 +686,7 @@ page_fault_oops(struct pt_regs *regs, unsigned long error_code,
686686
* avoid hanging the system.
687687
*/
688688
if (IS_ENABLED(CONFIG_EFI))
689-
efi_crash_gracefully_on_page_fault(address);
689+
efi_crash_gracefully_on_page_fault(address, regs);
690690

691691
/* Only not-present faults should be handled by KFENCE. */
692692
if (!(error_code & X86_PF_PROT) &&

arch/x86/platform/efi/quirks.c

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -761,7 +761,8 @@ int efi_capsule_setup_info(struct capsule_info *cap_info, void *kbuff,
761761
* @return: Returns, if the page fault is not handled. This function
762762
* will never return if the page fault is handled successfully.
763763
*/
764-
void efi_crash_gracefully_on_page_fault(unsigned long phys_addr)
764+
void efi_crash_gracefully_on_page_fault(unsigned long phys_addr,
765+
const struct pt_regs *regs)
765766
{
766767
if (!IS_ENABLED(CONFIG_X86_64))
767768
return;
@@ -810,6 +811,14 @@ void efi_crash_gracefully_on_page_fault(unsigned long phys_addr)
810811
return;
811812
}
812813

814+
/*
815+
* The API does not permit entering a kernel mode FPU section with
816+
* interrupts enabled and leaving it with interrupts disabled. So
817+
* re-enable interrupts now if they were enabled when the page fault
818+
* occurred.
819+
*/
820+
local_irq_restore(regs->flags);
821+
813822
/*
814823
* Before calling EFI Runtime Service, the kernel has switched the
815824
* calling process to efi_mm. Hence, switch back to task_mm.

0 commit comments

Comments
 (0)