/* 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); 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"); }