summaryrefslogtreecommitdiffstats
path: root/src/shared/pe-binary.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/shared/pe-binary.c241
1 files changed, 241 insertions, 0 deletions
diff --git a/src/shared/pe-binary.c b/src/shared/pe-binary.c
new file mode 100644
index 0000000..4c05323
--- /dev/null
+++ b/src/shared/pe-binary.c
@@ -0,0 +1,241 @@
+/* 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);
+
+ if (le16toh(pe_header->optional.Subsystem) != IMAGE_SUBSYSTEM_EFI_APPLICATION)
+ return false;
+
+ 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");
+}