]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
shared: add common implementation of PE parser
authorLennart Poettering <lennart@poettering.net>
Thu, 24 Aug 2023 13:40:41 +0000 (15:40 +0200)
committerLennart Poettering <lennart@poettering.net>
Tue, 29 Aug 2023 09:25:52 +0000 (11:25 +0200)
src/shared/meson.build
src/shared/pe-binary.c [new file with mode: 0644]
src/shared/pe-binary.h [new file with mode: 0644]

index 2c18ab43dd8d89dc5bceda1c5dd8669ab13dacf0..01e85b617f15223cff76c81b70dbefb4d2b93d9b 100644 (file)
@@ -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 (file)
index 0000000..6a6b8ad
--- /dev/null
@@ -0,0 +1,238 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <unistd.h>
+
+#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 (file)
index 0000000..758bb1d
--- /dev/null
@@ -0,0 +1,142 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <sys/types.h>
+
+#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);