]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
stub: Relocate kernels below 4G for EFI handover
authorJan Janssen <medhefgo@web.de>
Mon, 1 May 2023 09:32:30 +0000 (11:32 +0200)
committerJan Janssen <medhefgo@web.de>
Mon, 1 May 2023 17:08:12 +0000 (19:08 +0200)
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

index 5021b076cb2391cd94170e3711d3ecfc26dfbf05..f5422e86f2af89b947a6ab4fb4dffd31feb77ac2 100644 (file)
@@ -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,