diff mbox series

efi/x86-mixed: move unmitigated RET into .rodata

Message ID 20220815132028.732531-1-ardb@kernel.org
State Accepted
Commit 6c3a9c9ae02a16295ea144dc431aaac2c20dbffd
Headers show
Series efi/x86-mixed: move unmitigated RET into .rodata | expand

Commit Message

Ard Biesheuvel Aug. 15, 2022, 1:20 p.m. UTC
Move the EFI mixed mode return trampoline RET into .rodata, so it is
normally mapped without executable permissions.  And given that this
snippet of code is really the only kernel code that we ever execute via
this 1:1 mapping, let's unmap the 1:1 mapping of the kernel .text, and
only map the page that covers the return trampoline with executable
permissions.

Note that the remainder of .rodata needs to remain mapped into the 1:1
mapping with RO/NX permissions, as literal GUIDs and strings may be
passed to the variable routines.

Signed-off-by: Ard Biesheuvel <ardb@kernel.org>
---
 arch/x86/platform/efi/efi_64.c       | 18 +++++++++++++-----
 arch/x86/platform/efi/efi_thunk_64.S |  8 +++++---
 2 files changed, 18 insertions(+), 8 deletions(-)

Comments

Borislav Petkov Aug. 15, 2022, 7:46 p.m. UTC | #1
On Mon, Aug 15, 2022 at 03:20:28PM +0200, Ard Biesheuvel wrote:
> Move the EFI mixed mode return trampoline RET into .rodata, so it is
> normally mapped without executable permissions.  And given that this
> snippet of code is really the only kernel code that we ever execute via
> this 1:1 mapping, let's unmap the 1:1 mapping of the kernel .text, and
> only map the page that covers the return trampoline with executable
> permissions.
> 
> Note that the remainder of .rodata needs to remain mapped into the 1:1
> mapping with RO/NX permissions, as literal GUIDs and strings may be
> passed to the variable routines.
> 
> Signed-off-by: Ard Biesheuvel <ardb@kernel.org>
> ---
>  arch/x86/platform/efi/efi_64.c       | 18 +++++++++++++-----
>  arch/x86/platform/efi/efi_thunk_64.S |  8 +++++---
>  2 files changed, 18 insertions(+), 8 deletions(-)

Acked-by: Borislav Petkov <bp@suse.de>

For some reason, objtool is not happy here:

vmlinux.o: warning: objtool: efi_thunk_query_variable_info_nonblocking+0x1ba: unreachable instruction
Ard Biesheuvel Aug. 15, 2022, 7:57 p.m. UTC | #2
On Mon, 15 Aug 2022 at 21:46, Borislav Petkov <bp@alien8.de> wrote:
>
> On Mon, Aug 15, 2022 at 03:20:28PM +0200, Ard Biesheuvel wrote:
> > Move the EFI mixed mode return trampoline RET into .rodata, so it is
> > normally mapped without executable permissions.  And given that this
> > snippet of code is really the only kernel code that we ever execute via
> > this 1:1 mapping, let's unmap the 1:1 mapping of the kernel .text, and
> > only map the page that covers the return trampoline with executable
> > permissions.
> >
> > Note that the remainder of .rodata needs to remain mapped into the 1:1
> > mapping with RO/NX permissions, as literal GUIDs and strings may be
> > passed to the variable routines.
> >
> > Signed-off-by: Ard Biesheuvel <ardb@kernel.org>
> > ---
> >  arch/x86/platform/efi/efi_64.c       | 18 +++++++++++++-----
> >  arch/x86/platform/efi/efi_thunk_64.S |  8 +++++---
> >  2 files changed, 18 insertions(+), 8 deletions(-)
>
> Acked-by: Borislav Petkov <bp@suse.de>
>
> For some reason, objtool is not happy here:
>
> vmlinux.o: warning: objtool: efi_thunk_query_variable_info_nonblocking+0x1ba: unreachable instruction
>

I'm not seeing that warning. Any config in particular beyond
x86_64_defconfig that you have enabled?
I'm using Debian GCC 12.1.0 btw
Peter Zijlstra Aug. 16, 2022, 10:17 a.m. UTC | #3
On Tue, Aug 16, 2022 at 09:04:56AM +0200, Ard Biesheuvel wrote:
> (cc Peter and Josh)
> 
> On Mon, 15 Aug 2022 at 22:18, Borislav Petkov <bp@alien8.de> wrote:
> >
> > On Mon, Aug 15, 2022 at 09:57:50PM +0200, Ard Biesheuvel wrote:
> > > I'm not seeing that warning. Any config in particular beyond
> > > x86_64_defconfig that you have enabled?
> >
> > It is my workstation's tailored config. Attached.
> >
> > > I'm using Debian GCC 12.1.0 btw
> >
> > gcc (Debian 11.2.0-19) 11.2.0
> >
> 
> Complete thread here:
> https://lore.kernel.org/all/20220815132028.732531-1-ardb@kernel.org/
> 
> On Mon, 15 Aug 2022 at 22:18, Borislav Petkov <bp@alien8.de> wrote:
> >
> > For some reason, objtool is not happy here:
> > vmlinux.o: warning: objtool: efi_thunk_query_variable_info_nonblocking+0x1ba: unreachable instruction
> 
> [which is the instruction right after the call to __efi_thunk64()]
> 
> However, with the same config but without the patch (i.e., v6.0-rc1
> with nothing on top), I see:
> 
> vmlinux.o: warning: objtool: sme_enable+0x71: unreachable instruction
> 
> It appears that objtool is making inferences about whether or not
> __efi_thunk64() returns, even though it is marked as
> STACK_FRAME_NON_STANDARD. And note that I am not seeing any of these
> with x86_64_defconfig, only with Boris's config (attached)

STACK_FRAME_NON_STANDARD has no bearing on a call to that symbol being
noreturn or not.

noreturn is a bit of a pain point in that the compiler leaves no clue
in the object file. Objtool has a bunch of heuristics to guess at
noreturn, but the only surefire way to make things consistent is to
annotate the function with __noreturn and add it to the
global_noreturns[] array in tools/objtool/check.c

Alternatively, if objtool guesses wrong, you can annotate the assembler
with 'REACHABLE'.

Josh; should we create a config file for objtool to contain many of this
stuff? Then again, parsing a config file over and over and over again
isn't going to make it faster :/
Ard Biesheuvel Aug. 16, 2022, 10:28 a.m. UTC | #4
On Tue, 16 Aug 2022 at 12:17, Peter Zijlstra <peterz@infradead.org> wrote:
>
> On Tue, Aug 16, 2022 at 09:04:56AM +0200, Ard Biesheuvel wrote:
> > (cc Peter and Josh)
> >
> > On Mon, 15 Aug 2022 at 22:18, Borislav Petkov <bp@alien8.de> wrote:
...
> > > For some reason, objtool is not happy here:
> > > vmlinux.o: warning: objtool: efi_thunk_query_variable_info_nonblocking+0x1ba: unreachable instruction
> >
> > [which is the instruction right after the call to __efi_thunk64()]
> >
> > However, with the same config but without the patch (i.e., v6.0-rc1
> > with nothing on top), I see:
> >
> > vmlinux.o: warning: objtool: sme_enable+0x71: unreachable instruction
> >
> > It appears that objtool is making inferences about whether or not
> > __efi_thunk64() returns, even though it is marked as
> > STACK_FRAME_NON_STANDARD. And note that I am not seeing any of these
> > with x86_64_defconfig, only with Boris's config (attached)
>
> STACK_FRAME_NON_STANDARD has no bearing on a call to that symbol being
> noreturn or not.
>
> noreturn is a bit of a pain point in that the compiler leaves no clue
> in the object file. Objtool has a bunch of heuristics to guess at
> noreturn, but the only surefire way to make things consistent is to
> annotate the function with __noreturn and add it to the
> global_noreturns[] array in tools/objtool/check.c
>
> Alternatively, if objtool guesses wrong, you can annotate the assembler
> with 'REACHABLE'.
>

So the problem here is that the function is *not* __noreturn, and
objtool thinks it is, resulting in the instruction after the call to
be misidentified as unreachable.
The function is called from C code, so we cannot easily annotate the
instruction after the call as REACHABLE.

I've patched it up by adding a dummy, unreachable RET into
__efi64_thunk(), which seems to make objtool happy for now.
diff mbox series

Patch

diff --git a/arch/x86/platform/efi/efi_64.c b/arch/x86/platform/efi/efi_64.c
index 1f3675453a57..b36596bf0fc3 100644
--- a/arch/x86/platform/efi/efi_64.c
+++ b/arch/x86/platform/efi/efi_64.c
@@ -176,7 +176,8 @@  virt_to_phys_or_null_size(void *va, unsigned long size)
 
 int __init efi_setup_page_tables(unsigned long pa_memmap, unsigned num_pages)
 {
-	unsigned long pfn, text, pf, rodata;
+	extern const u8 __efi64_thunk_ret_tramp[];
+	unsigned long pfn, text, pf, rodata, tramp;
 	struct page *page;
 	unsigned npages;
 	pgd_t *pgd = efi_mm.pgd;
@@ -238,11 +239,9 @@  int __init efi_setup_page_tables(unsigned long pa_memmap, unsigned num_pages)
 
 	npages = (_etext - _text) >> PAGE_SHIFT;
 	text = __pa(_text);
-	pfn = text >> PAGE_SHIFT;
 
-	pf = _PAGE_ENC;
-	if (kernel_map_pages_in_pgd(pgd, pfn, text, npages, pf)) {
-		pr_err("Failed to map kernel text 1:1\n");
+	if (kernel_unmap_pages_in_pgd(pgd, text, npages)) {
+		pr_err("Failed to unmap kernel text 1:1 mapping\n");
 		return 1;
 	}
 
@@ -256,6 +255,15 @@  int __init efi_setup_page_tables(unsigned long pa_memmap, unsigned num_pages)
 		return 1;
 	}
 
+	tramp = __pa(__efi64_thunk_ret_tramp);
+	pfn = tramp >> PAGE_SHIFT;
+
+	pf = _PAGE_ENC;
+	if (kernel_map_pages_in_pgd(pgd, pfn, tramp, 1, pf)) {
+		pr_err("Failed to map mixed mode return trampoline\n");
+		return 1;
+	}
+
 	return 0;
 }
 
diff --git a/arch/x86/platform/efi/efi_thunk_64.S b/arch/x86/platform/efi/efi_thunk_64.S
index 4e5257a4811b..e436ce03741e 100644
--- a/arch/x86/platform/efi/efi_thunk_64.S
+++ b/arch/x86/platform/efi/efi_thunk_64.S
@@ -23,7 +23,6 @@ 
 #include <linux/objtool.h>
 #include <asm/page_types.h>
 #include <asm/segment.h>
-#include <asm/nospec-branch.h>
 
 	.text
 	.code64
@@ -72,11 +71,14 @@  STACK_FRAME_NON_STANDARD __efi64_thunk
 	pushq	$__KERNEL32_CS
 	pushq	%rdi			/* EFI runtime service address */
 	lretq
+SYM_FUNC_END(__efi64_thunk)
 
+	.section ".rodata", "a", @progbits
+	.balign	16
+SYM_DATA_START(__efi64_thunk_ret_tramp)
 1:	movq	0x20(%rsp), %rsp
 	pop	%rbx
 	pop	%rbp
-	ANNOTATE_UNRET_SAFE
 	ret
 	int3
 
@@ -84,7 +86,7 @@  STACK_FRAME_NON_STANDARD __efi64_thunk
 2:	pushl	$__KERNEL_CS
 	pushl	%ebp
 	lret
-SYM_FUNC_END(__efi64_thunk)
+SYM_DATA_END(__efi64_thunk_ret_tramp)
 
 	.bss
 	.balign 8