From 669f16acf7af9d91eaf1c4d5457c79dde97a7858 Mon Sep 17 00:00:00 2001 From: Jan Janssen Date: Mon, 1 May 2023 11:32:30 +0200 Subject: 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 --- src/boot/efi/linux_x86.c | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) (limited to 'src/boot') 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, -- cgit v1.2.1