From 7941c6020567f4c4fb6561575aa5e902a5306038 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Wed, 31 Jul 2024 01:45:06 +0100 Subject: [PATCH] stub: allocate and zero enough space in legacy x86 handover protocol A PE image's memory footprint might be larger than its file size due to uninitialized memory sections. Normally all PE headers should be parsed to check the actual required size, but the legacy EFI handover protocol is only used for x86 Linux bzImages, so we know only the last section will require extra memory. Use SizeOfImage from the PE header and if it is larger than the file size, allocate and zero extra memory before using it. Fixes https://github.com/systemd/systemd/issues/33816 (cherry picked from commit 19812661f1f65ebe777d1626b5abf6475faababc) (cherry picked from commit 84111f8916340e3e67d8166eb1d9938da94ce669) --- src/boot/efi/boot.c | 2 +- src/boot/efi/linux.c | 6 ++++-- src/boot/efi/linux.h | 3 ++- src/boot/efi/linux_x86.c | 19 +++++++++++++++---- src/boot/efi/pe.c | 7 ++++++- src/boot/efi/pe.h | 2 +- 6 files changed, 29 insertions(+), 10 deletions(-) diff --git a/src/boot/efi/boot.c b/src/boot/efi/boot.c index c047fcdfd4c..0907733c43f 100644 --- a/src/boot/efi/boot.c +++ b/src/boot/efi/boot.c @@ -2391,7 +2391,7 @@ static EFI_STATUS image_start( if (err == EFI_UNSUPPORTED && entry->type == LOADER_LINUX) { uint32_t compat_address; - err = pe_kernel_info(loaded_image->ImageBase, &compat_address); + err = pe_kernel_info(loaded_image->ImageBase, &compat_address, /* ret_size_in_memory= */ NULL); if (err != EFI_SUCCESS) { if (err != EFI_UNSUPPORTED) return log_error_status(err, "Error finding kernel compat entry address: %m"); diff --git a/src/boot/efi/linux.c b/src/boot/efi/linux.c index 65bc176df77..c8e595188b6 100644 --- a/src/boot/efi/linux.c +++ b/src/boot/efi/linux.c @@ -98,6 +98,7 @@ EFI_STATUS linux_exec( const void *initrd_buffer, size_t initrd_length) { + size_t kernel_size_in_memory = 0; uint32_t compat_address; EFI_STATUS err; @@ -105,7 +106,7 @@ EFI_STATUS linux_exec( assert(linux_buffer && linux_length > 0); assert(initrd_buffer || initrd_length == 0); - err = pe_kernel_info(linux_buffer, &compat_address); + err = pe_kernel_info(linux_buffer, &compat_address, &kernel_size_in_memory); #if defined(__i386__) || defined(__x86_64__) if (err == EFI_UNSUPPORTED) /* Kernel is too old to support LINUX_INITRD_MEDIA_GUID, try the deprecated EFI handover @@ -116,7 +117,8 @@ EFI_STATUS linux_exec( linux_buffer, linux_length, initrd_buffer, - initrd_length); + initrd_length, + kernel_size_in_memory); #endif if (err != EFI_SUCCESS) return log_error_status(err, "Bad kernel image: %m"); diff --git a/src/boot/efi/linux.h b/src/boot/efi/linux.h index 46b5f4f4d7c..0d74c6a3a72 100644 --- a/src/boot/efi/linux.h +++ b/src/boot/efi/linux.h @@ -16,4 +16,5 @@ EFI_STATUS linux_exec_efi_handover( const void *linux_buffer, size_t linux_length, const void *initrd_buffer, - size_t initrd_length); + size_t initrd_length, + size_t kernel_size_in_memory); diff --git a/src/boot/efi/linux_x86.c b/src/boot/efi/linux_x86.c index 757902daac5..3e42361812f 100644 --- a/src/boot/efi/linux_x86.c +++ b/src/boot/efi/linux_x86.c @@ -13,6 +13,7 @@ #include "initrd.h" #include "linux.h" #include "macro-fundamental.h" +#include "memory-util-fundamental.h" #include "util.h" #define KERNEL_SECTOR_SIZE 512u @@ -126,7 +127,8 @@ EFI_STATUS linux_exec_efi_handover( const void *linux_buffer, size_t linux_length, const void *initrd_buffer, - size_t initrd_length) { + size_t initrd_length, + size_t kernel_size_in_memory) { assert(parent); assert(linux_buffer); @@ -153,13 +155,22 @@ EFI_STATUS linux_exec_efi_handover( FLAGS_SET(image_params->hdr.xloadflags, XLF_CAN_BE_LOADED_ABOVE_4G); /* There is no way to pass the high bits of code32_start. Newer kernels seems to handle this - * just fine, but older kernels will fail even if they otherwise have above 4G boot support. */ + * just fine, but older kernels will fail even if they otherwise have above 4G boot support. + * A PE image's memory footprint can be larger than its file size, due to unallocated virtual + * memory sections. While normally all PE headers should be taken into account, this case only + * involves x86 Linux bzImage kernel images, for which unallocated areas are only part of the last + * header, so parsing SizeOfImage and zeroeing the buffer past the image size is enough. */ _cleanup_pages_ Pages linux_relocated = {}; - if (POINTER_TO_PHYSICAL_ADDRESS(linux_buffer) + linux_length > UINT32_MAX) { + if (POINTER_TO_PHYSICAL_ADDRESS(linux_buffer) + linux_length > UINT32_MAX || kernel_size_in_memory > linux_length) { linux_relocated = xmalloc_pages( - AllocateMaxAddress, EfiLoaderCode, EFI_SIZE_TO_PAGES(linux_length), UINT32_MAX); + AllocateMaxAddress, + EfiLoaderCode, + EFI_SIZE_TO_PAGES(kernel_size_in_memory > linux_length ? kernel_size_in_memory : linux_length), + UINT32_MAX); linux_buffer = memcpy( PHYSICAL_ADDRESS_TO_POINTER(linux_relocated.addr), linux_buffer, linux_length); + if (kernel_size_in_memory > linux_length) + memzero((uint8_t *) linux_buffer + linux_length, kernel_size_in_memory - linux_length); } _cleanup_pages_ Pages initrd_relocated = {}; diff --git a/src/boot/efi/pe.c b/src/boot/efi/pe.c index 829266b7f59..fdca0c93fee 100644 --- a/src/boot/efi/pe.c +++ b/src/boot/efi/pe.c @@ -209,7 +209,7 @@ static uint32_t get_compatibility_entry_address(const DosFileHeader *dos, const return 0; } -EFI_STATUS pe_kernel_info(const void *base, uint32_t *ret_compat_address) { +EFI_STATUS pe_kernel_info(const void *base, uint32_t *ret_compat_address, size_t *ret_size_in_memory) { assert(base); assert(ret_compat_address); @@ -221,6 +221,11 @@ EFI_STATUS pe_kernel_info(const void *base, uint32_t *ret_compat_address) { if (!verify_pe(pe, /* allow_compatibility= */ true)) return EFI_LOAD_ERROR; + /* When allocating we need to also consider the virtual/uninitialized data sections, so parse it out + * of the SizeOfImage field in the PE header and return it */ + if (ret_size_in_memory) + *ret_size_in_memory = pe->OptionalHeader.SizeOfImage; + /* Support for LINUX_INITRD_MEDIA_GUID was added in kernel stub 1.0. */ if (pe->OptionalHeader.MajorImageVersion < 1) return EFI_UNSUPPORTED; diff --git a/src/boot/efi/pe.h b/src/boot/efi/pe.h index 7e2258fceb9..860cfe5e27b 100644 --- a/src/boot/efi/pe.h +++ b/src/boot/efi/pe.h @@ -16,4 +16,4 @@ EFI_STATUS pe_file_locate_sections( size_t *offsets, size_t *sizes); -EFI_STATUS pe_kernel_info(const void *base, uint32_t *ret_compat_address); +EFI_STATUS pe_kernel_info(const void *base, uint32_t *ret_compat_address, size_t *ret_size_in_memory);