diff --git a/src/boot/efi/boot.c b/src/boot/efi/boot.c index 37bd6c756a..bb8e8417e4 100644 --- a/src/boot/efi/boot.c +++ b/src/boot/efi/boot.c @@ -2417,14 +2417,13 @@ static EFI_STATUS image_start( if (EFI_ERROR(err)) return log_error_status_stall(err, L"Error registering initrd: %r", err); + EFI_LOADED_IMAGE *loaded_image; + err = BS->HandleProtocol(image, &LoadedImageProtocol, (void **) &loaded_image); + if (EFI_ERROR(err)) + return log_error_status_stall(err, L"Error getting LoadedImageProtocol handle: %r", err); + CHAR16 *options = options_initrd ?: entry->options; if (options) { - EFI_LOADED_IMAGE *loaded_image; - - err = BS->OpenProtocol(image, &LoadedImageProtocol, (void **)&loaded_image, - parent_image, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL); - if (EFI_ERROR(err)) - return log_error_status_stall(err, L"Error getting LoadedImageProtocol handle: %r", err); loaded_image->LoadOptions = options; loaded_image->LoadOptionsSize = StrSize(options); @@ -2435,10 +2434,29 @@ static EFI_STATUS image_start( efivar_set_time_usec(LOADER_GUID, L"LoaderTimeExecUSec", 0); err = BS->StartImage(image, NULL, NULL); graphics_mode(FALSE); - if (err != EFI_SUCCESS) - return log_error_status_stall(err, L"Failed to execute %s (%s): %r", entry->title_show, entry->loader, err); + if (err == EFI_SUCCESS) + return EFI_SUCCESS; - return EFI_SUCCESS; + /* Try calling the kernel compat entry point if one exists. */ + if (err == EFI_UNSUPPORTED && entry->type == LOADER_LINUX) { + UINT32 kernel_entry_address; + + err = pe_alignment_info(loaded_image->ImageBase, &kernel_entry_address, NULL, NULL); + if (EFI_ERROR(err)) { + if (err != EFI_UNSUPPORTED) + return log_error_status_stall(err, L"Error finding kernel compat entry address: %r", err); + } else { + EFI_IMAGE_ENTRY_POINT kernel_entry = + (EFI_IMAGE_ENTRY_POINT) ((UINT8 *) loaded_image->ImageBase + kernel_entry_address); + + err = kernel_entry(image, ST); + graphics_mode(FALSE); + if (err == EFI_SUCCESS) + return EFI_SUCCESS; + } + } + + return log_error_status_stall(err, L"Failed to execute %s (%s): %r", entry->title_show, entry->loader, err); } static void config_free(Config *config) { diff --git a/src/boot/efi/pe.c b/src/boot/efi/pe.c index e16716498f..568dcf4012 100644 --- a/src/boot/efi/pe.c +++ b/src/boot/efi/pe.c @@ -12,17 +12,22 @@ #define MAX_SECTIONS 96 #if defined(__i386__) - #define TARGET_MACHINE_TYPE EFI_IMAGE_MACHINE_IA32 +# define TARGET_MACHINE_TYPE EFI_IMAGE_MACHINE_IA32 +# define TARGET_MACHINE_TYPE_COMPATIBILITY EFI_IMAGE_MACHINE_X64 #elif defined(__x86_64__) - #define TARGET_MACHINE_TYPE EFI_IMAGE_MACHINE_X64 +# define TARGET_MACHINE_TYPE EFI_IMAGE_MACHINE_X64 #elif defined(__aarch64__) - #define TARGET_MACHINE_TYPE EFI_IMAGE_MACHINE_AARCH64 +# define TARGET_MACHINE_TYPE EFI_IMAGE_MACHINE_AARCH64 #elif defined(__arm__) - #define TARGET_MACHINE_TYPE EFI_IMAGE_MACHINE_ARMTHUMB_MIXED +# define TARGET_MACHINE_TYPE EFI_IMAGE_MACHINE_ARMTHUMB_MIXED #elif defined(__riscv) && __riscv_xlen == 64 - #define TARGET_MACHINE_TYPE EFI_IMAGE_MACHINE_RISCV64 +# define TARGET_MACHINE_TYPE EFI_IMAGE_MACHINE_RISCV64 #else - #error Unknown EFI arch +# error Unknown EFI arch +#endif + +#ifndef TARGET_MACHINE_TYPE_COMPATIBILITY +# define TARGET_MACHINE_TYPE_COMPATIBILITY 0 #endif struct DosFileHeader { @@ -117,10 +122,11 @@ static inline BOOLEAN verify_dos(const struct DosFileHeader *dos) { return CompareMem(dos->Magic, DOS_FILE_MAGIC, STRLEN(DOS_FILE_MAGIC)) == 0; } -static inline BOOLEAN verify_pe(const struct PeFileHeader *pe) { +static inline BOOLEAN verify_pe(const struct PeFileHeader *pe, BOOLEAN allow_compatibility) { assert(pe); return CompareMem(pe->Magic, PE_FILE_MAGIC, STRLEN(PE_FILE_MAGIC)) == 0 && - pe->FileHeader.Machine == TARGET_MACHINE_TYPE && + (pe->FileHeader.Machine == TARGET_MACHINE_TYPE || + (allow_compatibility && pe->FileHeader.Machine == TARGET_MACHINE_TYPE_COMPATIBILITY)) && pe->FileHeader.NumberOfSections > 0 && pe->FileHeader.NumberOfSections <= MAX_SECTIONS && IN_SET(pe->OptionalHeader.Magic, OPTHDR32_MAGIC, OPTHDR64_MAGIC); @@ -160,6 +166,49 @@ static void locate_sections( } } +static UINT32 get_compatibility_entry_address(const struct DosFileHeader *dos, const struct PeFileHeader *pe) { + UINTN addr = 0, size = 0; + static const CHAR8 *sections[] = { (CHAR8 *) ".compat", NULL }; + + /* The kernel may provide alternative PE entry points for different PE architectures. This allows + * booting a 64bit kernel on 32bit EFI that is otherwise running on a 64bit CPU. The locations of any + * such compat entry points are located in a special PE section. */ + + locate_sections((const struct PeSectionHeader *) ((const UINT8 *) dos + section_table_offset(dos, pe)), + pe->FileHeader.NumberOfSections, + sections, + &addr, + NULL, + &size); + + if (size == 0) + return 0; + + typedef struct { + UINT8 type; + UINT8 size; + UINT16 machine_type; + UINT32 entry_point; + } _packed_ LinuxPeCompat1; + + while (size >= sizeof(LinuxPeCompat1) && addr % __alignof__(LinuxPeCompat1) == 0) { + LinuxPeCompat1 *compat = (LinuxPeCompat1 *) ((UINT8 *) dos + addr); + + if (compat->type == 0 || compat->size == 0 || compat->size > size) + break; + + if (compat->type == 1 && + compat->size >= sizeof(LinuxPeCompat1) && + compat->machine_type == TARGET_MACHINE_TYPE) + return compat->entry_point; + + addr += compat->size; + size -= compat->size; + } + + return 0; +} + EFI_STATUS pe_alignment_info( const void *base, UINT32 *ret_entry_point_address, @@ -171,20 +220,30 @@ EFI_STATUS pe_alignment_info( assert(base); assert(ret_entry_point_address); - assert(ret_size_of_image); - assert(ret_section_alignment); dos = (const struct DosFileHeader *) base; if (!verify_dos(dos)) return EFI_LOAD_ERROR; pe = (const struct PeFileHeader*) ((const UINT8 *)base + dos->ExeHeader); - if (!verify_pe(pe)) + if (!verify_pe(pe, /* allow_compatibility= */ TRUE)) return EFI_LOAD_ERROR; - *ret_entry_point_address = pe->OptionalHeader.AddressOfEntryPoint; - *ret_size_of_image = pe->OptionalHeader.SizeOfImage; - *ret_section_alignment = pe->OptionalHeader.SectionAlignment; + UINT32 entry_address = pe->OptionalHeader.AddressOfEntryPoint; + + /* Look for a compat entry point. */ + if (pe->FileHeader.Machine != TARGET_MACHINE_TYPE) { + entry_address = get_compatibility_entry_address(dos, pe); + if (entry_address == 0) + /* Image type not supported and no compat entry found. */ + return EFI_UNSUPPORTED; + } + + *ret_entry_point_address = entry_address; + if (ret_size_of_image) + *ret_size_of_image = pe->OptionalHeader.SizeOfImage; + if (ret_section_alignment) + *ret_section_alignment = pe->OptionalHeader.SectionAlignment; return EFI_SUCCESS; } @@ -207,7 +266,7 @@ EFI_STATUS pe_memory_locate_sections( return EFI_LOAD_ERROR; pe = (const struct PeFileHeader*)&base[dos->ExeHeader]; - if (!verify_pe(pe)) + if (!verify_pe(pe, /* allow_compatibility= */ FALSE)) return EFI_LOAD_ERROR; offset = section_table_offset(dos, pe); @@ -255,7 +314,7 @@ EFI_STATUS pe_file_locate_sections( err = handle->Read(handle, &len, &pe); if (EFI_ERROR(err)) return err; - if (len != sizeof(pe) || !verify_pe(&pe)) + if (len != sizeof(pe) || !verify_pe(&pe, /* allow_compatibility= */ FALSE)) return EFI_LOAD_ERROR; section_table_len = pe.FileHeader.NumberOfSections * sizeof(struct PeSectionHeader);