summaryrefslogtreecommitdiffstats
path: root/src/boot/efi/pe.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/boot/efi/pe.c338
1 files changed, 338 insertions, 0 deletions
diff --git a/src/boot/efi/pe.c b/src/boot/efi/pe.c
new file mode 100644
index 0000000..3d5da14
--- /dev/null
+++ b/src/boot/efi/pe.c
@@ -0,0 +1,338 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <efi.h>
+#include <efilib.h>
+
+#include "missing_efi.h"
+#include "pe.h"
+#include "util.h"
+
+#define DOS_FILE_MAGIC "MZ"
+#define PE_FILE_MAGIC "PE\0\0"
+#define MAX_SECTIONS 96
+
+#if defined(__i386__)
+# 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
+#elif defined(__aarch64__)
+# define TARGET_MACHINE_TYPE EFI_IMAGE_MACHINE_AARCH64
+#elif defined(__arm__)
+# define TARGET_MACHINE_TYPE EFI_IMAGE_MACHINE_ARMTHUMB_MIXED
+#elif defined(__riscv) && __riscv_xlen == 64
+# define TARGET_MACHINE_TYPE EFI_IMAGE_MACHINE_RISCV64
+#else
+# error Unknown EFI arch
+#endif
+
+#ifndef TARGET_MACHINE_TYPE_COMPATIBILITY
+# define TARGET_MACHINE_TYPE_COMPATIBILITY 0
+#endif
+
+typedef struct DosFileHeader {
+ uint8_t Magic[2];
+ uint16_t LastSize;
+ uint16_t nBlocks;
+ uint16_t nReloc;
+ uint16_t HdrSize;
+ uint16_t MinAlloc;
+ uint16_t MaxAlloc;
+ uint16_t ss;
+ uint16_t sp;
+ uint16_t Checksum;
+ uint16_t ip;
+ uint16_t cs;
+ uint16_t RelocPos;
+ uint16_t nOverlay;
+ uint16_t reserved[4];
+ uint16_t OEMId;
+ uint16_t OEMInfo;
+ uint16_t reserved2[10];
+ uint32_t ExeHeader;
+} _packed_ DosFileHeader;
+
+typedef struct CoffFileHeader {
+ uint16_t Machine;
+ uint16_t NumberOfSections;
+ uint32_t TimeDateStamp;
+ uint32_t PointerToSymbolTable;
+ uint32_t NumberOfSymbols;
+ uint16_t SizeOfOptionalHeader;
+ uint16_t Characteristics;
+} _packed_ CoffFileHeader;
+
+#define OPTHDR32_MAGIC 0x10B /* PE32 OptionalHeader */
+#define OPTHDR64_MAGIC 0x20B /* PE32+ OptionalHeader */
+
+typedef struct PeOptionalHeader {
+ uint16_t Magic;
+ uint8_t LinkerMajor;
+ uint8_t LinkerMinor;
+ uint32_t SizeOfCode;
+ uint32_t SizeOfInitializedData;
+ uint32_t SizeOfUninitializeData;
+ uint32_t AddressOfEntryPoint;
+ uint32_t BaseOfCode;
+ union {
+ struct { /* PE32 */
+ uint32_t BaseOfData;
+ uint32_t ImageBase32;
+ };
+ uint64_t ImageBase64; /* PE32+ */
+ };
+ uint32_t SectionAlignment;
+ uint32_t FileAlignment;
+ uint16_t MajorOperatingSystemVersion;
+ uint16_t MinorOperatingSystemVersion;
+ uint16_t MajorImageVersion;
+ uint16_t MinorImageVersion;
+ uint16_t MajorSubsystemVersion;
+ uint16_t MinorSubsystemVersion;
+ uint32_t Win32VersionValue;
+ uint32_t SizeOfImage;
+ uint32_t SizeOfHeaders;
+ uint32_t CheckSum;
+ uint16_t Subsystem;
+ uint16_t DllCharacteristics;
+ /* fields with different sizes for 32/64 omitted */
+} _packed_ PeOptionalHeader;
+
+typedef struct PeFileHeader {
+ uint8_t Magic[4];
+ CoffFileHeader FileHeader;
+ PeOptionalHeader OptionalHeader;
+} _packed_ PeFileHeader;
+
+typedef struct PeSectionHeader {
+ uint8_t Name[8];
+ uint32_t VirtualSize;
+ uint32_t VirtualAddress;
+ uint32_t SizeOfRawData;
+ uint32_t PointerToRawData;
+ uint32_t PointerToRelocations;
+ uint32_t PointerToLinenumbers;
+ uint16_t NumberOfRelocations;
+ uint16_t NumberOfLinenumbers;
+ uint32_t Characteristics;
+} _packed_ PeSectionHeader;
+
+static inline bool verify_dos(const DosFileHeader *dos) {
+ assert(dos);
+ return memcmp(dos->Magic, DOS_FILE_MAGIC, STRLEN(DOS_FILE_MAGIC)) == 0;
+}
+
+static inline bool verify_pe(const PeFileHeader *pe, bool allow_compatibility) {
+ assert(pe);
+ return memcmp(pe->Magic, PE_FILE_MAGIC, STRLEN(PE_FILE_MAGIC)) == 0 &&
+ (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);
+}
+
+static inline UINTN section_table_offset(const DosFileHeader *dos, const PeFileHeader *pe) {
+ assert(dos);
+ assert(pe);
+ return dos->ExeHeader + offsetof(PeFileHeader, OptionalHeader) + pe->FileHeader.SizeOfOptionalHeader;
+}
+
+static void locate_sections(
+ const PeSectionHeader section_table[],
+ UINTN n_table,
+ const char * const sections[],
+ UINTN *offsets,
+ UINTN *sizes,
+ bool in_memory) {
+
+ assert(section_table);
+ assert(sections);
+ assert(offsets);
+ assert(sizes);
+
+ size_t prev_section_addr = 0;
+
+ for (UINTN i = 0; i < n_table; i++) {
+ const PeSectionHeader *sect = section_table + i;
+
+ if (in_memory) {
+ if (prev_section_addr > sect->VirtualAddress)
+ log_error_stall(u"Overlapping PE sections detected. Boot may fail due to image memory corruption!");
+ prev_section_addr = sect->VirtualAddress + sect->VirtualSize;
+ }
+
+ for (UINTN j = 0; sections[j]; j++) {
+ if (memcmp(sect->Name, sections[j], strlen8(sections[j])) != 0)
+ continue;
+
+ offsets[j] = in_memory ? sect->VirtualAddress : sect->PointerToRawData;
+ sizes[j] = sect->VirtualSize;
+ }
+ }
+}
+
+static uint32_t get_compatibility_entry_address(const DosFileHeader *dos, const PeFileHeader *pe) {
+ UINTN addr = 0, size = 0;
+ static const char *sections[] = { ".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 PeSectionHeader *) ((const uint8_t *) dos + section_table_offset(dos, pe)),
+ pe->FileHeader.NumberOfSections,
+ sections,
+ &addr,
+ &size,
+ /*in_memory=*/true);
+
+ if (size == 0)
+ return 0;
+
+ typedef struct {
+ uint8_t type;
+ uint8_t size;
+ uint16_t machine_type;
+ uint32_t entry_point;
+ } _packed_ LinuxPeCompat1;
+
+ while (size >= sizeof(LinuxPeCompat1) && addr % __alignof__(LinuxPeCompat1) == 0) {
+ LinuxPeCompat1 *compat = (LinuxPeCompat1 *) ((uint8_t *) 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_kernel_info(const void *base, uint32_t *ret_compat_address) {
+ assert(base);
+ assert(ret_compat_address);
+
+ const DosFileHeader *dos = (const DosFileHeader *) base;
+ if (!verify_dos(dos))
+ return EFI_LOAD_ERROR;
+
+ const PeFileHeader *pe = (const PeFileHeader *) ((const uint8_t *) base + dos->ExeHeader);
+ if (!verify_pe(pe, /* allow_compatibility= */ true))
+ return EFI_LOAD_ERROR;
+
+ /* Support for LINUX_INITRD_MEDIA_GUID was added in kernel stub 1.0. */
+ if (pe->OptionalHeader.MajorImageVersion < 1)
+ return EFI_UNSUPPORTED;
+
+ if (pe->FileHeader.Machine == TARGET_MACHINE_TYPE) {
+ *ret_compat_address = 0;
+ return EFI_SUCCESS;
+ }
+
+ uint32_t compat_address = get_compatibility_entry_address(dos, pe);
+ if (compat_address == 0)
+ /* Image type not supported and no compat entry found. */
+ return EFI_UNSUPPORTED;
+
+ *ret_compat_address = compat_address;
+ return EFI_SUCCESS;
+}
+
+EFI_STATUS pe_memory_locate_sections(const void *base, const char * const sections[], UINTN *addrs, UINTN *sizes) {
+ const DosFileHeader *dos;
+ const PeFileHeader *pe;
+ UINTN offset;
+
+ assert(base);
+ assert(sections);
+ assert(addrs);
+ assert(sizes);
+
+ dos = (const DosFileHeader *) base;
+ if (!verify_dos(dos))
+ return EFI_LOAD_ERROR;
+
+ pe = (const PeFileHeader *) ((uint8_t *) base + dos->ExeHeader);
+ if (!verify_pe(pe, /* allow_compatibility= */ false))
+ return EFI_LOAD_ERROR;
+
+ offset = section_table_offset(dos, pe);
+ locate_sections((PeSectionHeader *) ((uint8_t *) base + offset),
+ pe->FileHeader.NumberOfSections,
+ sections,
+ addrs,
+ sizes,
+ /*in_memory=*/true);
+
+ return EFI_SUCCESS;
+}
+
+EFI_STATUS pe_file_locate_sections(
+ EFI_FILE *dir,
+ const char16_t *path,
+ const char * const sections[],
+ UINTN *offsets,
+ UINTN *sizes) {
+ _cleanup_free_ PeSectionHeader *section_table = NULL;
+ _cleanup_(file_closep) EFI_FILE *handle = NULL;
+ DosFileHeader dos;
+ PeFileHeader pe;
+ UINTN len, section_table_len;
+ EFI_STATUS err;
+
+ assert(dir);
+ assert(path);
+ assert(sections);
+ assert(offsets);
+ assert(sizes);
+
+ err = dir->Open(dir, &handle, (char16_t *) path, EFI_FILE_MODE_READ, 0ULL);
+ if (err != EFI_SUCCESS)
+ return err;
+
+ len = sizeof(dos);
+ err = handle->Read(handle, &len, &dos);
+ if (err != EFI_SUCCESS)
+ return err;
+ if (len != sizeof(dos) || !verify_dos(&dos))
+ return EFI_LOAD_ERROR;
+
+ err = handle->SetPosition(handle, dos.ExeHeader);
+ if (err != EFI_SUCCESS)
+ return err;
+
+ len = sizeof(pe);
+ err = handle->Read(handle, &len, &pe);
+ if (err != EFI_SUCCESS)
+ return err;
+ if (len != sizeof(pe) || !verify_pe(&pe, /* allow_compatibility= */ false))
+ return EFI_LOAD_ERROR;
+
+ section_table_len = pe.FileHeader.NumberOfSections * sizeof(PeSectionHeader);
+ section_table = xmalloc(section_table_len);
+ if (!section_table)
+ return EFI_OUT_OF_RESOURCES;
+
+ err = handle->SetPosition(handle, section_table_offset(&dos, &pe));
+ if (err != EFI_SUCCESS)
+ return err;
+
+ len = section_table_len;
+ err = handle->Read(handle, &len, section_table);
+ if (err != EFI_SUCCESS)
+ return err;
+ if (len != section_table_len)
+ return EFI_LOAD_ERROR;
+
+ locate_sections(section_table, pe.FileHeader.NumberOfSections,
+ sections, offsets, sizes, /*in_memory=*/false);
+
+ return EFI_SUCCESS;
+}