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");
const struct iovec *kernel,
const struct iovec *initrd) {
+ size_t kernel_size_in_memory = 0;
uint32_t compat_address;
EFI_STATUS err;
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
parent,
cmdline,
kernel,
- initrd);
+ initrd,
+ kernel_size_in_memory);
#endif
if (err != EFI_SUCCESS)
return log_error_status(err, "Bad kernel image: %m");
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);
#include "initrd.h"
#include "linux.h"
#include "macro-fundamental.h"
+#include "memory-util-fundamental.h"
#include "util.h"
#define KERNEL_SECTOR_SIZE 512u
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));
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;
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);
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;
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);