Skip to content

Commit

Permalink
stub: allocate and zero enough space in legacy x86 handover protocol
Browse files Browse the repository at this point in the history
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 systemd/systemd#33816

(cherry picked from commit 19812661f1f65ebe777d1626b5abf6475faababc)
(cherry picked from commit 84111f8916340e3e67d8166eb1d9938da94ce669)
  • Loading branch information
bluca committed Aug 16, 2024
1 parent 84c4a44 commit 7941c60
Show file tree
Hide file tree
Showing 6 changed files with 29 additions and 10 deletions.
2 changes: 1 addition & 1 deletion src/boot/efi/boot.c
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
6 changes: 4 additions & 2 deletions src/boot/efi/linux.c
Original file line number Diff line number Diff line change
Expand Up @@ -98,14 +98,15 @@ 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;

assert(parent);
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
Expand All @@ -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");
Expand Down
3 changes: 2 additions & 1 deletion src/boot/efi/linux.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
19 changes: 15 additions & 4 deletions src/boot/efi/linux_x86.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand All @@ -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 = {};
Expand Down
7 changes: 6 additions & 1 deletion src/boot/efi/pe.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion src/boot/efi/pe.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);

0 comments on commit 7941c60

Please sign in to comment.