Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

x86/{fault,efi}: Fix and rename efi_recover_from_page_fault()

efi_recover_from_page_fault() doesn't recover -- it does a special EFI
mini-oops. Rename it to make it clear that it crashes.

While renaming it, I noticed a blatant bug: a page fault oops in a
different thread happening concurrently with an EFI runtime service call
would be misinterpreted as an EFI page fault. Fix that.

This isn't quite exact. The situation could be improved by using a
special CS for calls into EFI.

[ bp: Massage commit message and simplify in interrupt check. ]

Signed-off-by: Andy Lutomirski <luto@kernel.org>
Signed-off-by: Borislav Petkov <bp@suse.de>
Link: https://lkml.kernel.org/r/f43b1e80830dc78ed60ed8b0826f4f189254570c.1612924255.git.luto@kernel.org

authored by

Andy Lutomirski and committed by
Borislav Petkov
c46f5223 ca247283

+20 -11
+1 -1
arch/x86/include/asm/efi.h
··· 150 150 extern int __init efi_reuse_config(u64 tables, int nr_tables); 151 151 extern void efi_delete_dummy_variable(void); 152 152 extern void efi_switch_mm(struct mm_struct *mm); 153 - extern void efi_recover_from_page_fault(unsigned long phys_addr); 153 + extern void efi_crash_gracefully_on_page_fault(unsigned long phys_addr); 154 154 extern void efi_free_boot_services(void); 155 155 156 156 /* kexec external ABI */
+6 -5
arch/x86/mm/fault.c
··· 16 16 #include <linux/prefetch.h> /* prefetchw */ 17 17 #include <linux/context_tracking.h> /* exception_enter(), ... */ 18 18 #include <linux/uaccess.h> /* faulthandler_disabled() */ 19 - #include <linux/efi.h> /* efi_recover_from_page_fault()*/ 19 + #include <linux/efi.h> /* efi_crash_gracefully_on_page_fault()*/ 20 20 #include <linux/mm_types.h> 21 21 22 22 #include <asm/cpufeature.h> /* boot_cpu_has, ... */ ··· 25 25 #include <asm/vsyscall.h> /* emulate_vsyscall */ 26 26 #include <asm/vm86.h> /* struct vm86 */ 27 27 #include <asm/mmu_context.h> /* vma_pkey() */ 28 - #include <asm/efi.h> /* efi_recover_from_page_fault()*/ 28 + #include <asm/efi.h> /* efi_crash_gracefully_on_page_fault()*/ 29 29 #include <asm/desc.h> /* store_idt(), ... */ 30 30 #include <asm/cpu_entry_area.h> /* exception stack */ 31 31 #include <asm/pgtable_areas.h> /* VMALLOC_START, ... */ ··· 701 701 #endif 702 702 703 703 /* 704 - * Buggy firmware could access regions which might page fault, try to 705 - * recover from such faults. 704 + * Buggy firmware could access regions which might page fault. If 705 + * this happens, EFI has a special OOPS path that will try to 706 + * avoid hanging the system. 706 707 */ 707 708 if (IS_ENABLED(CONFIG_EFI)) 708 - efi_recover_from_page_fault(address); 709 + efi_crash_gracefully_on_page_fault(address); 709 710 710 711 oops: 711 712 /*
+13 -5
arch/x86/platform/efi/quirks.c
··· 687 687 * @return: Returns, if the page fault is not handled. This function 688 688 * will never return if the page fault is handled successfully. 689 689 */ 690 - void efi_recover_from_page_fault(unsigned long phys_addr) 690 + void efi_crash_gracefully_on_page_fault(unsigned long phys_addr) 691 691 { 692 692 if (!IS_ENABLED(CONFIG_X86_64)) 693 693 return; 694 694 695 695 /* 696 - * Make sure that an efi runtime service caused the page fault. 696 + * If we get an interrupt/NMI while processing an EFI runtime service 697 + * then this is a regular OOPS, not an EFI failure. 697 698 */ 698 - if (efi_rts_work.efi_rts_id == EFI_NONE) 699 + if (in_interrupt()) 700 + return; 701 + 702 + /* 703 + * Make sure that an efi runtime service caused the page fault. 704 + * READ_ONCE() because we might be OOPSing in a different thread, 705 + * and we don't want to trip KTSAN while trying to OOPS. 706 + */ 707 + if (READ_ONCE(efi_rts_work.efi_rts_id) == EFI_NONE || 708 + current_work() != &efi_rts_work.work) 699 709 return; 700 710 701 711 /* ··· 757 747 set_current_state(TASK_IDLE); 758 748 schedule(); 759 749 } 760 - 761 - return; 762 750 }