[PATCH RFC 3/8] x86/efi: Save kernel context before calling EFI Runtime Services

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



From: Sai Praneeth <sai.praneeth.prakhya@xxxxxxxxx>

After the kernel is up and running, the only time firmware executes is
when an EFI Runtime Service is invoked by kernel. When invoked, some
buggy implementations of EFI Runtime Service could access memory regions
which it shouldn't. This will cause a page fault in ring 0 and if
unhandled it hangs the kernel.

The obvious way to avoid such hangs is to handle the page fault.
Remember the sequence of things that lead us to page fault.
1. A user has requested kernel to execute some EFI Runtime Service
2. Kernel prepares and calls requested EFI Runtime Service
3. Requested EFI Runtime Service is buggy and hence caused a page fault
4. The kernel gets back control and it's in interrupt mode
If the page fault is handled successfully kernel would be returning
control to EFI Runtime Service which in turn returns control back to
kernel. But the kernel cannot map the requested efi region because it's
long gone. We cannot either mark EFI regions as reserved and dynamically
allow access because it will make the kernel un-bootable.

The proposed solution here is to save the kernel context before giving
away control to firmware (i.e. in step 2) and if the firmware
misbehaves, in the page fault handler, we roll back to the saved kernel
context. This saves us from executing buggy firmware further and saving
ourselves from hanging.

Saving kernel context means saving the stack pointer and the instruction
that gets executed when firmware returns. In the page fault handler we
fix up these two things (RIP and RSP) so that when returning from page
fault handler it looks as if firmware has called RET.

UEFI specification v2.7, section 2.3.4 "Calling Conventions for X64
platforms" says that "The registers RBX, RBP, RDI, RSI, R12, R13, R14,
R15, and XMM6-XMM15 are considered nonvolatile and must be saved and
restored by a function that uses them". This means that any EFI Runtime
Service that uses the above mentioned registers will save/restore its
value. Hence, to emulate the same behaviour we save/restore these
registers each and every time we call EFI Runtime Service.

Signed-off-by: Sai Praneeth Prakhya <sai.praneeth.prakhya@xxxxxxxxx>
Suggested-by: Matt Fleming <matt@xxxxxxxxxxxxxxxxxxx>
Based-on-code-from: Ricardo Neri <ricardo.neri@xxxxxxxxx>
Cc: Al Stone <astone@xxxxxxxxxx>
Cc: Lee Chun-Yi <jlee@xxxxxxxx>
Cc: Borislav Petkov <bp@xxxxxxxxx>
Cc: Bhupesh Sharma <bhsharma@xxxxxxxxxx>
Cc: Ard Biesheuvel <ard.biesheuvel@xxxxxxxxxx>
---
 arch/x86/include/asm/efi.h          |   3 ++
 arch/x86/platform/efi/efi_64.c      |   7 +++
 arch/x86/platform/efi/efi_stub_64.S | 101 +++++++++++++++++++++++++++++++++++-
 arch/x86/platform/efi/quirks.c      |   4 ++
 4 files changed, 114 insertions(+), 1 deletion(-)

diff --git a/arch/x86/include/asm/efi.h b/arch/x86/include/asm/efi.h
index c97f2e955cab..47202b9e1b8e 100644
--- a/arch/x86/include/asm/efi.h
+++ b/arch/x86/include/asm/efi.h
@@ -121,6 +121,9 @@ extern void __iomem *__efi_init_fixup efi_ioremap(unsigned long addr,
 
 #endif /* CONFIG_X86_32 */
 
+extern u64 xmm_regs_rsp;
+extern u64 core_regs_rsp;
+extern u64 exit_fw_ctx_rip;
 extern struct efi_scratch efi_scratch;
 extern void __init efi_set_executable(efi_memory_desc_t *md, bool executable);
 extern int __init efi_memblock_x86_reserve_range(void);
diff --git a/arch/x86/platform/efi/efi_64.c b/arch/x86/platform/efi/efi_64.c
index a04298312fdd..7787bc2e58fb 100644
--- a/arch/x86/platform/efi/efi_64.c
+++ b/arch/x86/platform/efi/efi_64.c
@@ -627,6 +627,13 @@ void __init efi_dump_pagetable(void)
  */
 void efi_switch_mm(struct mm_struct *mm)
 {
+	/*
+	 * Used by efi page fault handler (efi_illegal_accesses_fixup()) to
+	 * check if it was indeed invoked in firmware context.
+	 */
+	xmm_regs_rsp = 0;
+	exit_fw_ctx_rip = 0;
+
 	task_lock(current);
 	efi_scratch.prev_mm = current->active_mm;
 	current->active_mm = mm;
diff --git a/arch/x86/platform/efi/efi_stub_64.S b/arch/x86/platform/efi/efi_stub_64.S
index 74628ec78f29..c86825c01b4c 100644
--- a/arch/x86/platform/efi/efi_stub_64.S
+++ b/arch/x86/platform/efi/efi_stub_64.S
@@ -39,6 +39,101 @@
 	mov %rsi, %cr0;			\
 	mov (%rsp), %rsp
 
+#define SAVE_CORE_REGS_CALLEE		\
+	pushq %rbx;			\
+	pushq %rdi;			\
+	pushq %rsi;			\
+	pushq %r12;			\
+	pushq %r13;			\
+	pushq %r14;			\
+	pushq %r15
+
+#define RESTORE_CORE_REGS_CALLEE	\
+	popq %r15;			\
+	popq %r14;			\
+	popq %r13;			\
+	popq %r12;			\
+	popq %rsi;			\
+	popq %rdi;			\
+	popq %rbx
+
+#define SAVE_XMM_REGS_CALLEE		\
+	subq $0xb0, %rsp;		\
+	and $~0xf, %rsp	;		\
+	movaps %xmm6, 0xa0(%rsp);	\
+	movaps %xmm7, 0x90(%rsp);	\
+	movaps %xmm8, 0x80(%rsp);	\
+	movaps %xmm9, 0x70(%rsp);	\
+	movaps %xmm10, 0x60(%rsp);	\
+	movaps %xmm11, 0x50(%rsp);	\
+	movaps %xmm12, 0x40(%rsp);	\
+	movaps %xmm13, 0x30(%rsp);	\
+	movaps %xmm14, 0x20(%rsp);	\
+	movaps %xmm15, 0x10(%rsp)
+
+/*
+ * We don't adjust stack pointer before restoring XMM registers because,
+ * if the firmware was indeed buggy and if we had taken page fault
+ * handler path, the stack pointer and instruction pointer gets adjusted
+ * there. If the firmware isn't buggy, firmware does it for us.
+ */
+#define RESTORE_XMM_REGS_CALLEE		\
+	movaps 0xa0(%rsp), %xmm6;	\
+	movaps 0x90(%rsp), %xmm7;	\
+	movaps 0x80(%rsp), %xmm8;	\
+	movaps 0x70(%rsp), %xmm9;	\
+	movaps 0x60(%rsp), %xmm10;	\
+	movaps 0x50(%rsp), %xmm11;	\
+	movaps 0x40(%rsp), %xmm12;	\
+	movaps 0x30(%rsp), %xmm13;	\
+	movaps 0x20(%rsp), %xmm14;	\
+	movaps 0x10(%rsp), %xmm15
+
+/*
+ * When CONFIG_EFI_WARN_ON_ILLEGAL_ACCESSES is enabled, save kernel
+ * context before calling firmware. This is to exit firmware context
+ * and roll back to saved context in case firmware misbehaves.
+ *
+ * Saving kernel context means
+ * 1.  Saving the instruction that gets executed when firmware returns and
+ * 2.  Saving all the registers that are callee saved ([1] RBX, RBP, RDI,
+ *  RSI, R12, R13, R14, R15, and XMM6-XMM15).
+ *  Save/restore these registers because kernel expects them to retain
+ *  values across firmware calls. When firmware executes it might have
+ *  used these registers and could have page faulted, in the page fault
+ *  handler, while exiting firmware, set the stack pointer so that we
+ *  could restore these registers. Also, RBP is not saved/restored along
+ *  with other registers because the page fault handler does that for us.
+ *
+ * This also means that when the firmware isn't buggy we are performing
+ * unnecessary save/restore, but that should be fine given the advantage
+ * of safely rescuing ourselves from illegal accesses by firmware.
+ *
+ * [1] UEFI specification v2.7, section 2.3.4 "Calling Conventions for
+ * X64 platforms" says that "The registers RBX, RBP, RDI, RSI, R12, R13,
+ * R14, R15, and XMM6-XMM15 are considered nonvolatile and must be saved
+ * and restored by a function that uses them". This means that any EFI
+ * Runtime Service that uses the above mentioned registers will
+ * save/restore its value.
+ */
+#ifdef CONFIG_EFI_WARN_ON_ILLEGAL_ACCESSES
+#define EFI_CALL_RTS				\
+	pushq %r15;				\
+	leaq 1f(%rip), %r15;			\
+	movq %r15, exit_fw_ctx_rip(%rip);	\
+	popq %r15;				\
+	SAVE_CORE_REGS_CALLEE;			\
+	movq %rsp, core_regs_rsp(%rip);		\
+	SAVE_XMM_REGS_CALLEE;			\
+	movq %rsp, xmm_regs_rsp(%rip);		\
+	call *%rdi;				\
+1:	RESTORE_XMM_REGS_CALLEE;		\
+	movq core_regs_rsp(%rip), %rsp;		\
+	RESTORE_CORE_REGS_CALLEE
+#else
+#define EFI_CALL_RTS	call *%rdi;
+#endif
+
 ENTRY(efi_call)
 	pushq %rbp
 	movq %rsp, %rbp
@@ -50,9 +145,13 @@ ENTRY(efi_call)
 	mov %r8, %r9
 	mov %rcx, %r8
 	mov %rsi, %rcx
-	call *%rdi
+	EFI_CALL_RTS
 	addq $48, %rsp
 	RESTORE_XMM
 	popq %rbp
 	ret
 ENDPROC(efi_call)
+
+.comm xmm_regs_rsp, 64
+.comm core_regs_rsp, 64
+.comm exit_fw_ctx_rip, 64
diff --git a/arch/x86/platform/efi/quirks.c b/arch/x86/platform/efi/quirks.c
index 84b213a1460a..a3a1ae6a2562 100644
--- a/arch/x86/platform/efi/quirks.c
+++ b/arch/x86/platform/efi/quirks.c
@@ -79,6 +79,10 @@ static const efi_char16_t efi_dummy_name[] = L"DUMMY";
 
 static bool efi_no_storage_paranoia;
 
+u64 xmm_regs_rsp __aligned(16) = 0;
+u64 core_regs_rsp __aligned(16) = 0;
+u64 exit_fw_ctx_rip __aligned(16) = 0;
+
 /*
  * Some firmware implementations refuse to boot if there's insufficient
  * space in the variable store. The implementation of garbage collection
-- 
2.7.4

--
To unsubscribe from this list: send the line "unsubscribe linux-efi" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html



[Index of Archives]     [Linux ARM Kernel]     [Linux ARM]     [Linux Omap]     [Fedora ARM]     [IETF Annouce]     [Security]     [Bugtraq]     [Linux OMAP]     [Linux MIPS]     [ECOS]     [Asterisk Internet PBX]     [Linux API]

  Powered by Linux