From: Lennart Poettering Date: Thu, 24 Aug 2023 13:40:41 +0000 (+0200) Subject: shared: add common implementation of PE parser X-Git-Tag: v255-rc1~620^2~3 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=0786b039693a782795fff107cd825eb7d1e84a8c;p=thirdparty%2Fsystemd.git shared: add common implementation of PE parser --- diff --git a/src/shared/meson.build b/src/shared/meson.build index 2c18ab43dd8..01e85b617f1 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -131,6 +131,7 @@ shared_sources = files( 'password-quality-util-passwdqc.c', 'password-quality-util-pwquality.c', 'pcre2-util.c', + 'pe-binary.c', 'pkcs11-util.c', 'pretty-print.c', 'ptyfwd.c', diff --git a/src/shared/pe-binary.c b/src/shared/pe-binary.c new file mode 100644 index 00000000000..6a6b8adb86f --- /dev/null +++ b/src/shared/pe-binary.c @@ -0,0 +1,238 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "alloc-util.h" +#include "log.h" +#include "pe-binary.h" +#include "string-util.h" + +bool pe_header_is_64bit(const PeHeader *h) { + assert(h); + + if (le16toh(h->optional.Magic) == UINT16_C(0x010B)) /* PE32 */ + return false; + + if (le16toh(h->optional.Magic) == UINT16_C(0x020B)) /* PE32+ */ + return true; + + assert_not_reached(); +} + +static size_t pe_header_size(const PeHeader *pe_header) { + assert(pe_header); + + return offsetof(PeHeader, optional) + le16toh(pe_header->pe.SizeOfOptionalHeader); +} + +const IMAGE_DATA_DIRECTORY *pe_header_get_data_directory( + const PeHeader *h, + size_t i) { + + assert(h); + + if (i >= le32toh(PE_HEADER_OPTIONAL_FIELD(h, NumberOfRvaAndSizes))) + return NULL; + + return PE_HEADER_OPTIONAL_FIELD(h, DataDirectory) + i; +} + +const IMAGE_SECTION_HEADER *pe_header_find_section( + const PeHeader *pe_header, + const IMAGE_SECTION_HEADER *sections, + const char *name) { + + size_t n; + + assert(pe_header); + assert(name); + assert(sections || le16toh(pe_header->pe.NumberOfSections) == 0); + + n = strlen(name); + if (n > sizeof(sections[0].Name)) /* Too long? */ + return NULL; + + FOREACH_ARRAY(section, sections, le16toh(pe_header->pe.NumberOfSections)) + if (memcmp(section->Name, name, n) == 0 && + memeqzero(section->Name + n, sizeof(section->Name) - n)) + return section; + + return NULL; +} + +int pe_load_headers( + int fd, + IMAGE_DOS_HEADER **ret_dos_header, + PeHeader **ret_pe_header) { + + _cleanup_free_ IMAGE_DOS_HEADER *dos_header = NULL; + _cleanup_free_ PeHeader *pe_header = NULL; + ssize_t n; + + assert(fd >= 0); + + dos_header = new(IMAGE_DOS_HEADER, 1); + if (!dos_header) + return log_oom_debug(); + + n = pread(fd, + dos_header, + sizeof(IMAGE_DOS_HEADER), + 0); + if (n < 0) + return log_debug_errno(errno, "Failed to read DOS header: %m"); + if ((size_t) n != sizeof(IMAGE_DOS_HEADER)) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Short read while reading MZ executable header."); + + if (le16toh(dos_header->e_magic) != UINT16_C(0x5A4D)) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "File lacks MZ executable header."); + + pe_header = new(PeHeader, 1); + if (!pe_header) + return log_oom_debug(); + + n = pread(fd, + pe_header, + offsetof(PeHeader, optional), + le32toh(dos_header->e_lfanew)); + if (n < 0) + return log_debug_errno(errno, "Failed to read PE executable header: %m"); + if ((size_t) n != offsetof(PeHeader, optional)) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Short read while reading PE executable header."); + + if (le32toh(pe_header->signature) != UINT32_C(0x00004550)) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "File lacks PE executable header."); + + if (le16toh(pe_header->pe.SizeOfOptionalHeader) < sizeof_field(PeHeader, optional.Magic)) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Optional header size too short for magic."); + + PeHeader *pe_header_tmp = realloc(pe_header, MAX(sizeof(PeHeader), pe_header_size(pe_header))); + if (!pe_header_tmp) + return log_oom_debug(); + pe_header = pe_header_tmp; + + n = pread(fd, + &pe_header->optional, + le16toh(pe_header->pe.SizeOfOptionalHeader), + le32toh(dos_header->e_lfanew) + offsetof(PeHeader, optional)); + if (n < 0) + return log_debug_errno(errno, "Failed to read PE executable optional header: %m"); + if ((size_t) n != le16toh(pe_header->pe.SizeOfOptionalHeader)) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Short read while reading PE executable optional header."); + + if (!IN_SET(le16toh(pe_header->optional.Magic), UINT16_C(0x010B), UINT16_C(0x020B))) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Optional header magic invalid."); + + if (pe_header_size(pe_header) != + PE_HEADER_OPTIONAL_FIELD_OFFSET(pe_header, DataDirectory) + + sizeof(IMAGE_DATA_DIRECTORY) * (uint64_t) le32toh(PE_HEADER_OPTIONAL_FIELD(pe_header, NumberOfRvaAndSizes))) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Optional header size mismatch."); + + if (ret_dos_header) + *ret_dos_header = TAKE_PTR(dos_header); + if (ret_pe_header) + *ret_pe_header = TAKE_PTR(pe_header); + + return 0; +} + +int pe_load_sections( + int fd, + const IMAGE_DOS_HEADER *dos_header, + const PeHeader *pe_header, + IMAGE_SECTION_HEADER **ret_sections) { + + _cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL; + size_t nos; + ssize_t n; + + assert(fd >= 0); + assert(dos_header); + assert(pe_header); + + nos = le16toh(pe_header->pe.NumberOfSections); + + sections = new(IMAGE_SECTION_HEADER, nos); + if (!sections) + return log_oom_debug(); + + n = pread(fd, + sections, + sizeof(IMAGE_SECTION_HEADER) * nos, + le32toh(dos_header->e_lfanew) + pe_header_size(pe_header)); + if (n < 0) + return log_debug_errno(errno, "Failed to read section table: %m"); + if ((size_t) n != sizeof(IMAGE_SECTION_HEADER) * nos) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Short read while reading section table."); + + if (ret_sections) + *ret_sections = TAKE_PTR(sections); + + return 0; +} + +int pe_read_section_data( + int fd, + const PeHeader *pe_header, + const IMAGE_SECTION_HEADER *sections, + const char *name, + size_t max_size, + void **ret, + size_t *ret_size) { + + const IMAGE_SECTION_HEADER *section; + _cleanup_free_ void *data = NULL; + size_t n; + ssize_t ss; + + assert(fd >= 0); + assert(pe_header); + assert(sections || pe_header->pe.NumberOfSections == 0); + assert(name); + + section = pe_header_find_section(pe_header, sections, name); + if (!section) + return -ENXIO; + + n = le32toh(section->VirtualSize); + if (n > MIN(max_size, (size_t) SSIZE_MAX)) + return -E2BIG; + + data = malloc(n+1); + if (!data) + return -ENOMEM; + + ss = pread(fd, data, n, le32toh(section->PointerToRawData)); + if (ss < 0) + return -errno; + if ((size_t) ss != n) + return -EIO; + + ((uint8_t*) data)[n] = 0; /* NUL terminate, no matter what */ + + if (ret_size) + *ret_size = n; + else { + /* Check that there are no embedded NUL bytes if the caller doesn't want to know the size + * (i.e. treats the blob as a string) */ + const char *nul; + + nul = memchr(data, 0, n); + if (nul && !memeqzero(nul, n - (nul - (const char*) data))) /* If there's a NUL it must only be NULs from there on */ + return -EBADMSG; + } + if (ret) + *ret = TAKE_PTR(data); + + return 0; +} + +bool pe_is_uki(const PeHeader *pe_header, const IMAGE_SECTION_HEADER *sections) { + assert(pe_header); + assert(sections || le16toh(pe_header->pe.NumberOfSections) == 0); + + return + pe_header_find_section(pe_header, sections, ".osrel") && + pe_header_find_section(pe_header, sections, ".linux") && + pe_header_find_section(pe_header, sections, ".initrd"); +} diff --git a/src/shared/pe-binary.h b/src/shared/pe-binary.h new file mode 100644 index 00000000000..758bb1de83e --- /dev/null +++ b/src/shared/pe-binary.h @@ -0,0 +1,142 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include + +#include "sparse-endian.h" + +/* When naming things we try to stay close to the official Windows APIs as per: + * → https://learn.microsoft.com/en-us/windows/win32/debug/pe-format */ + +typedef struct _packed_ _IMAGE_DOS_HEADER { + le16_t e_magic; + le16_t e_cblp; + le16_t e_cp; + le16_t e_crlc; + le16_t e_cparhdr; + le16_t e_minalloc; + le16_t e_maxalloc; + le16_t e_ss; + le16_t e_sp; + le16_t e_csum; + le16_t e_ip; + le16_t e_cs; + le16_t e_lfarlc; + le16_t e_ovno; + le16_t e_res[4]; + le16_t e_oemid; + le16_t e_oeminfo; + le16_t e_res2[10]; + le32_t e_lfanew; +} IMAGE_DOS_HEADER; + +typedef struct _packed_ _IMAGE_FILE_HEADER { + le16_t Machine; + le16_t NumberOfSections; + le32_t TimeDateStamp; + le32_t PointerToSymbolTable; + le32_t NumberOfSymbols; + le16_t SizeOfOptionalHeader; + le16_t Characteristics; +} IMAGE_FILE_HEADER; + +typedef struct _packed_ _IMAGE_DATA_DIRECTORY { + le32_t VirtualAddress; + le32_t Size; +} IMAGE_DATA_DIRECTORY; + +typedef struct _packed_ _IMAGE_OPTIONAL_HEADER { + /* Standard fields */ + le16_t Magic; + uint8_t MajorLinkerVersion; + uint8_t MinorLinkerVersion; + le32_t SizeOfCode; + le32_t SizeOfInitializedData; + le32_t SizeOfUninitializedData; + le32_t AddressOfEntryPoint; + le32_t BaseOfCode; + + /* Here the PE32 and PE32+ headers differ: PE32+ has one 64bit field, PE32+ has two 32bit fields */ + union { + struct { + le32_t BaseOfData; + le32_t pe32_ImageBase; + }; + le64_t pe32plus_ImageBase; + }; + + /* Additional fields */ + le32_t SectionAlignment; + le32_t FileAlignment; + le16_t MajorOperatingSystemVersion; + le16_t MinorOperatingSystemVersion; + le16_t MajorImageVersion; + le16_t MinorImageVersion; + le16_t MajorSubsystemVersion; + le16_t MinorSubsystemVersion; + le32_t Win32VersionValue; + le32_t SizeOfImage; + le32_t SizeOfHeaders; + le32_t CheckSum; + le16_t Subsystem; + le16_t DllCharacteristics; + + /* Here similar: on PE32+ some fields are 64bit that are 32bit on PE32. */ + union { + struct { + le32_t pe32_SizeOfStackReserve; + le32_t pe32_SizeOfStackCommit; + le32_t pe32_SizeOfHeapReserve; + le32_t pe32_SizeOfHeapCommit; + le32_t pe32_LoaderFlags; + le32_t pe32_NumberOfRvaAndSizes; + IMAGE_DATA_DIRECTORY pe32_DataDirectory[]; + }; + struct { + le64_t pe32plus_SizeOfStackReserve; + le64_t pe32plus_SizeOfStackCommit; + le64_t pe32plus_SizeOfHeapReserve; + le64_t pe32plus_SizeOfHeapCommit; + le32_t pe32plus_LoaderFlags; + le32_t pe32plus_NumberOfRvaAndSizes; + IMAGE_DATA_DIRECTORY pe32plus_DataDirectory[]; + }; + }; +} IMAGE_OPTIONAL_HEADER; + +typedef struct _packed_ PeHeader { + le32_t signature; + IMAGE_FILE_HEADER pe; + IMAGE_OPTIONAL_HEADER optional; +} PeHeader; + +typedef struct _packed_ _IMAGE_SECTION_HEADER { + uint8_t Name[8]; + le32_t VirtualSize; + le32_t VirtualAddress; + le32_t SizeOfRawData; + le32_t PointerToRawData; + le32_t PointerToRelocations; + le32_t PointerToLinenumbers; + le16_t NumberOfRelocations; + le16_t NumberOfLinenumbers; + le32_t Characteristics; +} IMAGE_SECTION_HEADER; + +bool pe_header_is_64bit(const PeHeader *h); + +#define PE_HEADER_OPTIONAL_FIELD(h, field) \ + (pe_header_is_64bit(h) ? (h)->optional.pe32plus_##field : (h)->optional.pe32_##field) + +#define PE_HEADER_OPTIONAL_FIELD_OFFSET(h, field) \ + (pe_header_is_64bit(h) ? offsetof(PeHeader, optional.pe32plus_##field) : offsetof(PeHeader, optional.pe32_##field)) + +const IMAGE_DATA_DIRECTORY *pe_header_get_data_directory(const PeHeader *h, size_t i); +const IMAGE_SECTION_HEADER *pe_header_find_section(const PeHeader *pe_header, const IMAGE_SECTION_HEADER *sections, const char *name); + +int pe_load_headers(int fd, IMAGE_DOS_HEADER **ret_dos_header, PeHeader **ret_pe_header); + +int pe_load_sections(int fd, const IMAGE_DOS_HEADER *dos_header, const PeHeader *pe_header, IMAGE_SECTION_HEADER **ret_sections); +int pe_read_section_data(int fd, const PeHeader *pe_header, const IMAGE_SECTION_HEADER *sections, const char *name, size_t max_size, void **ret, size_t *ret_size); + +bool pe_is_uki(const PeHeader *pe_header, const IMAGE_SECTION_HEADER *sections);