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
This commit is contained in:
Luca Boccassi
2024-07-31 01:45:06 +01:00
parent 7767896d12
commit 19812661f1
6 changed files with 29 additions and 10 deletions

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

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

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

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

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

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