Re: [PATCH] x86/efistub: Add missing boot_params for mixed mode compat entry

From: Hans de Goede
Date: Mon Mar 25 2024 - 11:50:45 EST


Hi,

On 3/25/24 9:39 AM, Ard Biesheuvel wrote:
> From: Ard Biesheuvel <ardb@xxxxxxxxxx>
>
> The native EFI stub entry point does not take a struct boot_params from
> the boot loader, but creates it from scratch, and populates only the
> fields that still have meaning in this context (command line, initrd
> base and size, etc)
>
> The original mixed mode implementation used the EFI handover protocol,
> where the boot loader (i.e., GRUB) populates a struct boot_params and
> passes it to a special EFI entry point that takes the struct boot_params
> pointer as the third argument.
>
> When the new mixed mode implementation was introduced, using a special
> 32-bit PE entrypoint in the 64-bit kernel, it adopted the usual
> prototype, and relied on the EFI stub to create the struct boot_params
> as usual. This is preferred because it makes the bootloader side much
> easier to implement, as it does not need any x86-specific knowledge on
> how struct boot_params and struct setup_header are put together.
>
> However, one thing was missed: EFI mixed mode goes through startup_32()
> *before* entering the 64-bit EFI stub, which is difficult to avoid given
> that 64-bit execution requires page tables, which can only be populated
> using 32-bit code, and this piece is what the mixed mode EFI stub relies
> on. startup_32() accesses a couple of struct boot_params fields to
> decide where to place the page tables.
>
> startup_32() turns out to be quite tolerant to bogus struct boot_params,
> given that ESI used to contain junk when entering via the new mixed mode
> protocol. Only when commit
>
> e2ab9eab324c ("x86/boot/compressed: Move 32-bit entrypoint code into .text section")
>
> started to zero ESI explicitly when entering via this boot path, boot
> failures started to appear on some systems, presumably ones that unmap
> page 0x0 or map it read-only.
>
> The solution is to pass a special, temporary struct boot_params to
> startup_32() via ESI, one that is sufficient for getting it to create
> the page tables correctly and is discarded right after. This means
> setting a minimal alignment of 4k, only to get the statically allocated
> page tables line up correctly, and setting init_size to the executable
> image size (_end - startup_32). This ensures that the page tables are
> covered by the static footprint of the PE image.
>
> Given that EFI boot no longer calls the decompressor and no longer pads
> the image to permit the decompressor to execute in place, the same
> temporary struct boot_params should be used in the EFI handover protocol
> based mixed mode implementation as well, to prevent the page tables from
> being placed outside of allocated memory.
>
> Cc: Hans de Goede <hdegoede@xxxxxxxxxx>
> Fixes: e2ab9eab324c ("x86/boot/compressed: Move 32-bit entrypoint code into .text section")
> Closes: https://lore.kernel.org/all/20240321150510.GI8211@xxxxxxxxxxxxx/
> Reported-by: Clayton Craft <clayton@xxxxxxxxxxxxx>
> Signed-off-by: Ard Biesheuvel <ardb@xxxxxxxxxx>

I have given this a test run (on top of 6.9-rc1) on one of my
Bay Trail mixed mode tablets and the tablet still boots fine:

Tested-by: Hans de Goede <hdegoede@xxxxxxxxxx>

Regards,

Hans



> ---
> arch/x86/boot/compressed/efi_mixed.S | 20 +++++++++++++++-----
> 1 file changed, 15 insertions(+), 5 deletions(-)
>
> diff --git a/arch/x86/boot/compressed/efi_mixed.S b/arch/x86/boot/compressed/efi_mixed.S
> index 07873f269b7b..c7c108c0bcf0 100644
> --- a/arch/x86/boot/compressed/efi_mixed.S
> +++ b/arch/x86/boot/compressed/efi_mixed.S
> @@ -15,10 +15,12 @@
> */
>
> #include <linux/linkage.h>
> +#include <asm/asm-offsets.h>
> #include <asm/msr.h>
> #include <asm/page_types.h>
> #include <asm/processor-flags.h>
> #include <asm/segment.h>
> +#include <asm/setup.h>
>
> .code64
> .text
> @@ -155,6 +157,7 @@ SYM_FUNC_END(__efi64_thunk)
> SYM_FUNC_START(efi32_stub_entry)
> call 1f
> 1: popl %ecx
> + leal (efi32_boot_args - 1b)(%ecx), %ebx
>
> /* Clear BSS */
> xorl %eax, %eax
> @@ -169,6 +172,7 @@ SYM_FUNC_START(efi32_stub_entry)
> popl %ecx
> popl %edx
> popl %esi
> + movl %esi, 8(%ebx)
> jmp efi32_entry
> SYM_FUNC_END(efi32_stub_entry)
> #endif
> @@ -245,8 +249,6 @@ SYM_FUNC_END(efi_enter32)
> *
> * Arguments: %ecx image handle
> * %edx EFI system table pointer
> - * %esi struct bootparams pointer (or NULL when not using
> - * the EFI handover protocol)
> *
> * Since this is the point of no return for ordinary execution, no registers
> * are considered live except for the function parameters. [Note that the EFI
> @@ -272,9 +274,18 @@ SYM_FUNC_START_LOCAL(efi32_entry)
> leal (efi32_boot_args - 1b)(%ebx), %ebx
> movl %ecx, 0(%ebx)
> movl %edx, 4(%ebx)
> - movl %esi, 8(%ebx)
> movb $0x0, 12(%ebx) // efi_is64
>
> + /*
> + * Allocate some memory for a temporary struct boot_params, which only
> + * needs the minimal pieces that will get us through startup_32().
> + */
> + subl $PARAM_SIZE, %esp
> + movl %esp, %esi
> + movl $PAGE_SIZE, BP_kernel_alignment(%esi)
> + movl $_end - 1b, BP_init_size(%esi)
> + subl $startup_32 - 1b, BP_init_size(%esi)
> +
> /* Disable paging */
> movl %cr0, %eax
> btrl $X86_CR0_PG_BIT, %eax
> @@ -300,8 +311,7 @@ SYM_FUNC_START(efi32_pe_entry)
>
> movl 8(%ebp), %ecx // image_handle
> movl 12(%ebp), %edx // sys_table
> - xorl %esi, %esi
> - jmp efi32_entry // pass %ecx, %edx, %esi
> + jmp efi32_entry // pass %ecx, %edx
> // no other registers remain live
>
> 2: popl %edi // restore callee-save registers