diff options
author | Jan Janssen <medhefgo@web.de> | 2023-05-01 11:32:30 +0200 |
---|---|---|
committer | Jan Janssen <medhefgo@web.de> | 2023-05-01 19:08:12 +0200 |
commit | 669f16acf7af9d91eaf1c4d5457c79dde97a7858 (patch) | |
tree | 74cc9fca2fe045cead8513f3b886fca453189990 | |
parent | 6fd3ee69169af2b34ca636bccde57e9cef71260d (diff) | |
download | systemd-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
-rw-r--r-- | src/boot/efi/linux_x86.c | 28 |
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, |