blob: c946ce2b0ace12781757f4dfb2efc94327ba6d4c [file] [log] [blame]
/* 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 size_t 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[],
size_t n_table,
const char * const sections[],
size_t *offsets,
size_t *sizes,
bool in_memory) {
assert(section_table);
assert(sections);
assert(offsets);
assert(sizes);
size_t prev_section_addr = 0;
for (size_t i = 0; i < n_table; i++) {
const PeSectionHeader *sect = section_table + i;
if (in_memory) {
if (prev_section_addr > sect->VirtualAddress)
log_error("Overlapping PE sections detected. Boot may fail due to image memory corruption!");
prev_section_addr = sect->VirtualAddress + sect->VirtualSize;
}
for (size_t 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) {
size_t 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[], size_t *addrs, size_t *sizes) {
const DosFileHeader *dos;
const PeFileHeader *pe;
size_t 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[],
size_t *offsets,
size_t *sizes) {
_cleanup_free_ PeSectionHeader *section_table = NULL;
_cleanup_(file_closep) EFI_FILE *handle = NULL;
DosFileHeader dos;
PeFileHeader pe;
size_t 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;
}