diff options
Diffstat (limited to 'src/shared/pe-binary.c')
-rw-r--r-- | src/shared/pe-binary.c | 241 |
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"); +} |