From: Luca Boccassi Date: Wed, 31 Jul 2024 00:45:06 +0000 (+0100) Subject: stub: allocate and zero enough space in legacy x86 handover protocol X-Git-Tag: v257-rc1~793^2~1 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=19812661f1f65ebe777d1626b5abf6475faababc;p=thirdparty%2Fsystemd.git 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 --- diff --git a/src/boot/efi/boot.c b/src/boot/efi/boot.c index fa2e5eb0842..097dceb9588 100644 --- a/src/boot/efi/boot.c +++ b/src/boot/efi/boot.c @@ -2446,7 +2446,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 706648b49d3..54f52645d24 100644 --- a/src/boot/efi/linux.c +++ b/src/boot/efi/linux.c @@ -96,6 +96,7 @@ EFI_STATUS linux_exec( const struct iovec *kernel, const struct iovec *initrd) { + size_t kernel_size_in_memory = 0; uint32_t compat_address; EFI_STATUS err; @@ -103,7 +104,7 @@ EFI_STATUS linux_exec( assert(iovec_is_set(kernel)); assert(iovec_is_valid(initrd)); - err = pe_kernel_info(kernel->iov_base, &compat_address); + err = pe_kernel_info(kernel->iov_base, &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 @@ -112,7 +113,8 @@ EFI_STATUS linux_exec( parent, cmdline, kernel, - initrd); + initrd, + 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 1bee8907c40..681d12c0c1c 100644 --- a/src/boot/efi/linux.h +++ b/src/boot/efi/linux.h @@ -13,4 +13,5 @@ EFI_STATUS linux_exec_efi_handover( EFI_HANDLE parent, const char16_t *cmdline, const struct iovec *kernel, - const struct iovec *initrd); + const struct iovec *initrd, + size_t kernel_size_in_memory); diff --git a/src/boot/efi/linux_x86.c b/src/boot/efi/linux_x86.c index c456209831d..db185243eff 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 @@ -124,7 +125,8 @@ EFI_STATUS linux_exec_efi_handover( EFI_HANDLE parent, const char16_t *cmdline, const struct iovec *kernel, - const struct iovec *initrd) { + const struct iovec *initrd, + size_t kernel_size_in_memory) { assert(parent); assert(iovec_is_set(kernel)); @@ -151,14 +153,23 @@ 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 = {}; const void *linux_buffer; - if (POINTER_TO_PHYSICAL_ADDRESS(kernel->iov_base) + kernel->iov_len > UINT32_MAX) { + if (POINTER_TO_PHYSICAL_ADDRESS(kernel->iov_base) + kernel->iov_len > UINT32_MAX || kernel_size_in_memory > kernel->iov_len) { linux_relocated = xmalloc_pages( - AllocateMaxAddress, EfiLoaderCode, EFI_SIZE_TO_PAGES(kernel->iov_len), UINT32_MAX); + AllocateMaxAddress, + EfiLoaderCode, + EFI_SIZE_TO_PAGES(kernel_size_in_memory > kernel->iov_len ? kernel_size_in_memory : kernel->iov_len), + UINT32_MAX); linux_buffer = memcpy( PHYSICAL_ADDRESS_TO_POINTER(linux_relocated.addr), kernel->iov_base, kernel->iov_len); + if (kernel_size_in_memory > kernel->iov_len) + memzero((uint8_t *) linux_buffer + kernel->iov_len, kernel_size_in_memory - kernel->iov_len); } else linux_buffer = kernel->iov_base; diff --git a/src/boot/efi/pe.c b/src/boot/efi/pe.c index 587960e3210..5817806d72b 100644 --- a/src/boot/efi/pe.c +++ b/src/boot/efi/pe.c @@ -278,7 +278,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); @@ -290,6 +290,11 @@ EFI_STATUS pe_kernel_info(const void *base, uint32_t *ret_compat_address) { if (!verify_pe(dos, 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 e55ac5daa96..863f870b9f1 100644 --- a/src/boot/efi/pe.h +++ b/src/boot/efi/pe.h @@ -26,4 +26,4 @@ EFI_STATUS pe_file_locate_sections( const char *const section_names[], PeSectionVector sections[]); -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);