diff mbox series

[v2,14/14] x86/fault, x86/efi: Fix and rename efi_recover_from_page_fault()

Message ID f43b1e80830dc78ed60ed8b0826f4f189254570c.1612924255.git.luto@kernel.org
State Accepted
Commit c46f52231e79af025e2c89e889d69ec20a4c024f
Headers show
Series None | expand

Commit Message

Andy Lutomirski Feb. 10, 2021, 2:33 a.m. UTC
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.  We could do better by using a special CS for
calls into EFI.

Cc: Dave Hansen <dave.hansen@linux.intel.com>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Ard Biesheuvel <ardb@kernel.org>
Cc: linux-efi@vger.kernel.org
Signed-off-by: Andy Lutomirski <luto@kernel.org>
---
 arch/x86/include/asm/efi.h     |  2 +-
 arch/x86/mm/fault.c            | 11 ++++++-----
 arch/x86/platform/efi/quirks.c | 16 ++++++++++++----
 3 files changed, 19 insertions(+), 10 deletions(-)

Comments

Ard Biesheuvel Feb. 11, 2021, 8:38 a.m. UTC | #1
On Wed, 10 Feb 2021 at 03:34, Andy Lutomirski <luto@kernel.org> wrote:
>

> 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.  We could do better by using a special CS for

> calls into EFI.

>

> Cc: Dave Hansen <dave.hansen@linux.intel.com>

> Cc: Peter Zijlstra <peterz@infradead.org>

> Cc: Ard Biesheuvel <ardb@kernel.org>

> Cc: linux-efi@vger.kernel.org

> Signed-off-by: Andy Lutomirski <luto@kernel.org>


Acked-by: Ard Biesheuvel <ardb@kernel.org>


> ---

>  arch/x86/include/asm/efi.h     |  2 +-

>  arch/x86/mm/fault.c            | 11 ++++++-----

>  arch/x86/platform/efi/quirks.c | 16 ++++++++++++----

>  3 files changed, 19 insertions(+), 10 deletions(-)

>

> diff --git a/arch/x86/include/asm/efi.h b/arch/x86/include/asm/efi.h

> index c98f78330b09..4b7706ddd8b6 100644

> --- a/arch/x86/include/asm/efi.h

> +++ b/arch/x86/include/asm/efi.h

> @@ -150,7 +150,7 @@ extern void __init efi_apply_memmap_quirks(void);

>  extern int __init efi_reuse_config(u64 tables, int nr_tables);

>  extern void efi_delete_dummy_variable(void);

>  extern void efi_switch_mm(struct mm_struct *mm);

> -extern void efi_recover_from_page_fault(unsigned long phys_addr);

> +extern void efi_crash_gracefully_on_page_fault(unsigned long phys_addr);

>  extern void efi_free_boot_services(void);

>

>  /* kexec external ABI */

> diff --git a/arch/x86/mm/fault.c b/arch/x86/mm/fault.c

> index eed217d4a877..dfdd56d9c020 100644

> --- a/arch/x86/mm/fault.c

> +++ b/arch/x86/mm/fault.c

> @@ -16,7 +16,7 @@

>  #include <linux/prefetch.h>            /* prefetchw                    */

>  #include <linux/context_tracking.h>    /* exception_enter(), ...       */

>  #include <linux/uaccess.h>             /* faulthandler_disabled()      */

> -#include <linux/efi.h>                 /* efi_recover_from_page_fault()*/

> +#include <linux/efi.h>                 /* efi_crash_gracefully_on_page_fault()*/

>  #include <linux/mm_types.h>

>

>  #include <asm/cpufeature.h>            /* boot_cpu_has, ...            */

> @@ -25,7 +25,7 @@

>  #include <asm/vsyscall.h>              /* emulate_vsyscall             */

>  #include <asm/vm86.h>                  /* struct vm86                  */

>  #include <asm/mmu_context.h>           /* vma_pkey()                   */

> -#include <asm/efi.h>                   /* efi_recover_from_page_fault()*/

> +#include <asm/efi.h>                   /* efi_crash_gracefully_on_page_fault()*/

>  #include <asm/desc.h>                  /* store_idt(), ...             */

>  #include <asm/cpu_entry_area.h>                /* exception stack              */

>  #include <asm/pgtable_areas.h>         /* VMALLOC_START, ...           */

> @@ -700,11 +700,12 @@ page_fault_oops(struct pt_regs *regs, unsigned long error_code,

>  #endif

>

>         /*

> -        * Buggy firmware could access regions which might page fault, try to

> -        * recover from such faults.

> +        * Buggy firmware could access regions which might page fault.  If

> +        * this happens, EFI has a special OOPS path that will try to

> +        * avoid hanging the system.

>          */

>         if (IS_ENABLED(CONFIG_EFI))

> -               efi_recover_from_page_fault(address);

> +               efi_crash_gracefully_on_page_fault(address);

>

>  oops:

>         /*

> diff --git a/arch/x86/platform/efi/quirks.c b/arch/x86/platform/efi/quirks.c

> index 5a40fe411ebd..0463ef9cddd6 100644

> --- a/arch/x86/platform/efi/quirks.c

> +++ b/arch/x86/platform/efi/quirks.c

> @@ -687,15 +687,25 @@ int efi_capsule_setup_info(struct capsule_info *cap_info, void *kbuff,

>   * @return: Returns, if the page fault is not handled. This function

>   * will never return if the page fault is handled successfully.

>   */

> -void efi_recover_from_page_fault(unsigned long phys_addr)

> +void efi_crash_gracefully_on_page_fault(unsigned long phys_addr)

>  {

>         if (!IS_ENABLED(CONFIG_X86_64))

>                 return;

>

> +       /*

> +        * If we are in an interrupt nested inside an EFI runtime service,

> +        * then this is a regular OOPS, not an EFI failure.

> +        */

> +       if (in_interrupt() || in_nmi() || in_softirq())

> +               return;

> +

>         /*

>          * Make sure that an efi runtime service caused the page fault.

> +        * READ_ONCE() because we might be OOPSing in a different thread,

> +        * and we don't want to trip KTSAN while trying to OOPS.

>          */

> -       if (efi_rts_work.efi_rts_id == EFI_NONE)

> +       if (READ_ONCE(efi_rts_work.efi_rts_id) == EFI_NONE ||

> +           current_work() != &efi_rts_work.work)

>                 return;

>

>         /*

> @@ -747,6 +757,4 @@ void efi_recover_from_page_fault(unsigned long phys_addr)

>                 set_current_state(TASK_IDLE);

>                 schedule();

>         }

> -

> -       return;

>  }

> --

> 2.29.2

>
diff mbox series

Patch

diff --git a/arch/x86/include/asm/efi.h b/arch/x86/include/asm/efi.h
index c98f78330b09..4b7706ddd8b6 100644
--- a/arch/x86/include/asm/efi.h
+++ b/arch/x86/include/asm/efi.h
@@ -150,7 +150,7 @@  extern void __init efi_apply_memmap_quirks(void);
 extern int __init efi_reuse_config(u64 tables, int nr_tables);
 extern void efi_delete_dummy_variable(void);
 extern void efi_switch_mm(struct mm_struct *mm);
-extern void efi_recover_from_page_fault(unsigned long phys_addr);
+extern void efi_crash_gracefully_on_page_fault(unsigned long phys_addr);
 extern void efi_free_boot_services(void);
 
 /* kexec external ABI */
diff --git a/arch/x86/mm/fault.c b/arch/x86/mm/fault.c
index eed217d4a877..dfdd56d9c020 100644
--- a/arch/x86/mm/fault.c
+++ b/arch/x86/mm/fault.c
@@ -16,7 +16,7 @@ 
 #include <linux/prefetch.h>		/* prefetchw			*/
 #include <linux/context_tracking.h>	/* exception_enter(), ...	*/
 #include <linux/uaccess.h>		/* faulthandler_disabled()	*/
-#include <linux/efi.h>			/* efi_recover_from_page_fault()*/
+#include <linux/efi.h>			/* efi_crash_gracefully_on_page_fault()*/
 #include <linux/mm_types.h>
 
 #include <asm/cpufeature.h>		/* boot_cpu_has, ...		*/
@@ -25,7 +25,7 @@ 
 #include <asm/vsyscall.h>		/* emulate_vsyscall		*/
 #include <asm/vm86.h>			/* struct vm86			*/
 #include <asm/mmu_context.h>		/* vma_pkey()			*/
-#include <asm/efi.h>			/* efi_recover_from_page_fault()*/
+#include <asm/efi.h>			/* efi_crash_gracefully_on_page_fault()*/
 #include <asm/desc.h>			/* store_idt(), ...		*/
 #include <asm/cpu_entry_area.h>		/* exception stack		*/
 #include <asm/pgtable_areas.h>		/* VMALLOC_START, ...		*/
@@ -700,11 +700,12 @@  page_fault_oops(struct pt_regs *regs, unsigned long error_code,
 #endif
 
 	/*
-	 * Buggy firmware could access regions which might page fault, try to
-	 * recover from such faults.
+	 * Buggy firmware could access regions which might page fault.  If
+	 * this happens, EFI has a special OOPS path that will try to
+	 * avoid hanging the system.
 	 */
 	if (IS_ENABLED(CONFIG_EFI))
-		efi_recover_from_page_fault(address);
+		efi_crash_gracefully_on_page_fault(address);
 
 oops:
 	/*
diff --git a/arch/x86/platform/efi/quirks.c b/arch/x86/platform/efi/quirks.c
index 5a40fe411ebd..0463ef9cddd6 100644
--- a/arch/x86/platform/efi/quirks.c
+++ b/arch/x86/platform/efi/quirks.c
@@ -687,15 +687,25 @@  int efi_capsule_setup_info(struct capsule_info *cap_info, void *kbuff,
  * @return: Returns, if the page fault is not handled. This function
  * will never return if the page fault is handled successfully.
  */
-void efi_recover_from_page_fault(unsigned long phys_addr)
+void efi_crash_gracefully_on_page_fault(unsigned long phys_addr)
 {
 	if (!IS_ENABLED(CONFIG_X86_64))
 		return;
 
+	/*
+	 * If we are in an interrupt nested inside an EFI runtime service,
+	 * then this is a regular OOPS, not an EFI failure.
+	 */
+	if (in_interrupt() || in_nmi() || in_softirq())
+		return;
+
 	/*
 	 * Make sure that an efi runtime service caused the page fault.
+	 * READ_ONCE() because we might be OOPSing in a different thread,
+	 * and we don't want to trip KTSAN while trying to OOPS.
 	 */
-	if (efi_rts_work.efi_rts_id == EFI_NONE)
+	if (READ_ONCE(efi_rts_work.efi_rts_id) == EFI_NONE ||
+	    current_work() != &efi_rts_work.work)
 		return;
 
 	/*
@@ -747,6 +757,4 @@  void efi_recover_from_page_fault(unsigned long phys_addr)
 		set_current_state(TASK_IDLE);
 		schedule();
 	}
-
-	return;
 }