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);