From: Ard Biesheuvel ardb@kernel.org
mainline inclusion from mainline-v6.1-rc4 commit 23715a26c8d812912a70c6ac1ce67af649b95914 category: bugfix bugzilla: https://gitee.com/openeuler/kernel/issues/I67RIP CVE: NA
backport: - using ENTRY/ENDPROC instead of SYM_FUNC_START/END - add include to fix compile error
--------------------------------
Unlike x86, which has machinery to deal with page faults that occur during the execution of EFI runtime services, arm64 has nothing like that, and a synchronous exception raised by firmware code brings down the whole system.
With more EFI based systems appearing that were not built to run Linux (such as the Windows-on-ARM laptops based on Qualcomm SOCs), as well as the introduction of PRM (platform specific firmware routines that are callable just like EFI runtime services), we are more likely to run into issues of this sort, and it is much more likely that we can identify and work around such issues if they don't bring down the system entirely.
Since we already use a EFI runtime services call wrapper in assembler, we can quite easily add some code that captures the execution state at the point where the call is made, allowing us to revert to this state and proceed execution if the call triggered a synchronous exception.
Given that the kernel and the firmware don't share any data structures that could end up in an indeterminate state, we can happily continue running, as long as we mark the EFI runtime services as unavailable from that point on.
Signed-off-by: Ard Biesheuvel ardb@kernel.org Acked-by: Catalin Marinas catalin.marinas@arm.com
Conflicts: arch/arm64/kernel/efi-rt-wrapper.S
Signed-off-by: Ding Hui dinghui@sangfor.com.cn --- arch/arm64/include/asm/efi.h | 9 ++++++++ arch/arm64/kernel/efi-rt-wrapper.S | 35 ++++++++++++++++++++++++++++-- arch/arm64/kernel/efi.c | 26 ++++++++++++++++++++++ arch/arm64/mm/fault.c | 4 ++++ 4 files changed, 72 insertions(+), 2 deletions(-)
diff --git a/arch/arm64/include/asm/efi.h b/arch/arm64/include/asm/efi.h index d3cb42fd51ec..f99e6291d7f5 100644 --- a/arch/arm64/include/asm/efi.h +++ b/arch/arm64/include/asm/efi.h @@ -11,11 +11,20 @@ #include <asm/neon.h> #include <asm/ptrace.h> #include <asm/tlbflush.h> +#include <linux/efi.h>
#ifdef CONFIG_EFI extern void efi_init(void); + +bool efi_runtime_fixup_exception(struct pt_regs *regs, const char *msg); #else #define efi_init() + +static inline +bool efi_runtime_fixup_exception(struct pt_regs *regs, const char *msg) +{ + return false; +} #endif
int efi_create_mapping(struct mm_struct *mm, efi_memory_desc_t *md); diff --git a/arch/arm64/kernel/efi-rt-wrapper.S b/arch/arm64/kernel/efi-rt-wrapper.S index e02329e79ee1..1d4312c3a4b0 100644 --- a/arch/arm64/kernel/efi-rt-wrapper.S +++ b/arch/arm64/kernel/efi-rt-wrapper.S @@ -7,9 +7,11 @@ */
#include <linux/linkage.h> +#include <asm/assembler.h> +#include <asm/alternative.h>
ENTRY(__efi_rt_asm_wrapper) - stp x29, x30, [sp, #-32]! + stp x29, x30, [sp, #-112]! mov x29, sp
/* @@ -19,6 +21,20 @@ ENTRY(__efi_rt_asm_wrapper) */ stp x1, x18, [sp, #16]
+ /* + * Preserve all callee saved registers and record the stack pointer + * value in a per-CPU variable so we can recover from synchronous + * exceptions occurring while running the firmware routines. + */ + stp x19, x20, [sp, #32] + stp x21, x22, [sp, #48] + stp x23, x24, [sp, #64] + stp x25, x26, [sp, #80] + stp x27, x28, [sp, #96] + + adr_this_cpu x8, __efi_rt_asm_recover_sp, x9 + str x29, [x8] + /* * We are lucky enough that no EFI runtime services take more than * 5 arguments, so all are passed in registers rather than via the @@ -34,7 +50,7 @@ ENTRY(__efi_rt_asm_wrapper)
ldp x1, x2, [sp, #16] cmp x2, x18 - ldp x29, x30, [sp], #32 + ldp x29, x30, [sp], #112 b.ne 0f ret 0: @@ -48,3 +64,18 @@ ENTRY(__efi_rt_asm_wrapper) mov x18, x2 b efi_handle_corrupted_x18 // tail call ENDPROC(__efi_rt_asm_wrapper) + +ENTRY(__efi_rt_asm_recover) + ldr_this_cpu x8, __efi_rt_asm_recover_sp, x9 + mov sp, x8 + + ldp x0, x18, [sp, #16] + ldp x19, x20, [sp, #32] + ldp x21, x22, [sp, #48] + ldp x23, x24, [sp, #64] + ldp x25, x26, [sp, #80] + ldp x27, x28, [sp, #96] + ldp x29, x30, [sp], #112 + + b efi_handle_runtime_exception +ENDPROC(__efi_rt_asm_recover) diff --git a/arch/arm64/kernel/efi.c b/arch/arm64/kernel/efi.c index 4f9acb5fbe97..5704ac434897 100644 --- a/arch/arm64/kernel/efi.c +++ b/arch/arm64/kernel/efi.c @@ -13,6 +13,7 @@
#include <linux/efi.h> #include <linux/init.h> +#include <linux/percpu.h>
#include <asm/efi.h>
@@ -132,3 +133,28 @@ asmlinkage efi_status_t efi_handle_corrupted_x18(efi_status_t s, const char *f) pr_err_ratelimited(FW_BUG "register x18 corrupted by EFI %s\n", f); return s; } + +asmlinkage DEFINE_PER_CPU(u64, __efi_rt_asm_recover_sp); + +asmlinkage efi_status_t __efi_rt_asm_recover(void); + +asmlinkage efi_status_t efi_handle_runtime_exception(const char *f) +{ + pr_err(FW_BUG "Synchronous exception occurred in EFI runtime service %s()\n", f); + clear_bit(EFI_RUNTIME_SERVICES, &efi.flags); + return EFI_ABORTED; +} + +bool efi_runtime_fixup_exception(struct pt_regs *regs, const char *msg) +{ + /* Check whether the exception occurred while running the firmware */ + if (current_work() != &efi_rts_work.work || regs->pc >= TASK_SIZE_64) + return false; + + pr_err(FW_BUG "Unable to handle %s in EFI runtime service\n", msg); + add_taint(TAINT_FIRMWARE_WORKAROUND, LOCKDEP_STILL_OK); + dump_stack(); + + regs->pc = (u64)__efi_rt_asm_recover; + return true; +} diff --git a/arch/arm64/mm/fault.c b/arch/arm64/mm/fault.c index df456030136a..05cf6f1ebb2a 100644 --- a/arch/arm64/mm/fault.c +++ b/arch/arm64/mm/fault.c @@ -37,6 +37,7 @@ #include <asm/bug.h> #include <asm/cmpxchg.h> #include <asm/cpufeature.h> +#include <asm/efi.h> #include <asm/exception.h> #include <asm/daifflags.h> #include <asm/debug-monitors.h> @@ -332,6 +333,9 @@ static void __do_kernel_fault(unsigned long addr, unsigned int esr, msg = "paging request"; }
+ if (efi_runtime_fixup_exception(regs, msg)) + return; + die_kernel_fault(msg, addr, esr, regs); }