summaryrefslogtreecommitdiff
path: root/src/boot
diff options
context:
space:
mode:
authorJan Janssen <medhefgo@web.de>2023-05-01 11:32:30 +0200
committerJan Janssen <medhefgo@web.de>2023-05-01 19:08:12 +0200
commit669f16acf7af9d91eaf1c4d5457c79dde97a7858 (patch)
tree74cc9fca2fe045cead8513f3b886fca453189990 /src/boot
parent6fd3ee69169af2b34ca636bccde57e9cef71260d (diff)
downloadsystemd-669f16acf7af9d91eaf1c4d5457c79dde97a7858.tar.gz
stub: Relocate kernels below 4G for EFI handover
Old kernels can fail to boot when they are located above the 4G boundary even if they claim to support it. Fixes: #27472
Diffstat (limited to 'src/boot')
-rw-r--r--src/boot/efi/linux_x86.c28
1 files changed, 20 insertions, 8 deletions
diff --git a/src/boot/efi/linux_x86.c b/src/boot/efi/linux_x86.c
index 5021b076cb..f5422e86f2 100644
--- a/src/boot/efi/linux_x86.c
+++ b/src/boot/efi/linux_x86.c
@@ -104,8 +104,7 @@ static void linux_efi_handover(EFI_HANDLE parent, uintptr_t kernel, BootParams *
kernel += (params->hdr.setup_sects + 1) * KERNEL_SECTOR_SIZE; /* 32bit entry address. */
- /* Old kernels needs this set, while newer ones seem to ignore this. Note that this gets truncated on
- * above 4G boots, which is fine as long as we do not use the value to jump to kernel entry. */
+ /* Old kernels needs this set, while newer ones seem to ignore this. */
params->hdr.code32_start = kernel;
#ifdef __x86_64__
@@ -153,12 +152,25 @@ EFI_STATUS linux_exec_efi_handover(
bool can_4g = image_params->hdr.version >= SETUP_VERSION_2_12 &&
FLAGS_SET(image_params->hdr.xloadflags, XLF_CAN_BE_LOADED_ABOVE_4G);
- if (!can_4g && POINTER_TO_PHYSICAL_ADDRESS(linux_buffer) + linux_length > UINT32_MAX)
- return log_error_status(
- EFI_UNSUPPORTED,
- "Unified kernel image was loaded above 4G, but kernel lacks support.");
- if (!can_4g && POINTER_TO_PHYSICAL_ADDRESS(initrd_buffer) + initrd_length > UINT32_MAX)
- return log_error_status(EFI_UNSUPPORTED, "Initrd is above 4G, but kernel lacks support.");
+ /* 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. */
+ _cleanup_pages_ Pages linux_relocated = {};
+ if (POINTER_TO_PHYSICAL_ADDRESS(linux_buffer) + linux_length > UINT32_MAX) {
+ linux_relocated = xmalloc_pages(
+ AllocateMaxAddress, EfiLoaderCode, EFI_SIZE_TO_PAGES(linux_length), UINT32_MAX);
+ linux_buffer = memcpy(
+ PHYSICAL_ADDRESS_TO_POINTER(linux_relocated.addr), linux_buffer, linux_length);
+ }
+
+ _cleanup_pages_ Pages initrd_relocated = {};
+ if (!can_4g && POINTER_TO_PHYSICAL_ADDRESS(initrd_buffer) + initrd_length > UINT32_MAX) {
+ initrd_relocated = xmalloc_pages(
+ AllocateMaxAddress, EfiLoaderData, EFI_SIZE_TO_PAGES(initrd_length), UINT32_MAX);
+ initrd_buffer = memcpy(
+ PHYSICAL_ADDRESS_TO_POINTER(initrd_relocated.addr),
+ initrd_buffer,
+ initrd_length);
+ }
_cleanup_pages_ Pages boot_params_page = xmalloc_pages(
can_4g ? AllocateAnyPages : AllocateMaxAddress,