]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
stub: allocate and zero enough space in legacy x86 handover protocol
authorLuca Boccassi <bluca@debian.org>
Wed, 31 Jul 2024 00:45:06 +0000 (01:45 +0100)
committerLuca Boccassi <bluca@debian.org>
Wed, 31 Jul 2024 00:46:25 +0000 (01:46 +0100)
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

src/boot/efi/boot.c
src/boot/efi/linux.c
src/boot/efi/linux.h
src/boot/efi/linux_x86.c
src/boot/efi/pe.c
src/boot/efi/pe.h

index fa2e5eb0842fb149264c41f24ec8b522de685ce7..097dceb95888dd7659e2afeefdb939a81cd8031e 100644 (file)
@@ -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");
index 706648b49d32de507f9978f46a03dc920afd2398..54f52645d24f58fbdcc30b9aad88d658544f6554 100644 (file)
@@ -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");
index 1bee8907c407fb1abb810e99925b8304d87100a1..681d12c0c1cfa922d824afda05a8451fb54c1791 100644 (file)
@@ -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);
index c456209831d5017ac508b3c8f7a4c9fead2c46b4..db185243effb1a65861002ba2b931930aa8e130c 100644 (file)
@@ -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;
 
index 587960e321038c9cc9ee7c5f8503cac4eaa96903..5817806d72badc15aef97e57031b08903f9c8818 100644 (file)
@@ -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;
index e55ac5daa96a12d6df9c867aa316cf6d96ae6953..863f870b9f1a242bfcad514fe04b8d565398ab37 100644 (file)
@@ -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);