diff --git a/src/shared/bootspec.c b/src/shared/bootspec.c index 8d16b36be1..d23eab38df 100644 --- a/src/shared/bootspec.c +++ b/src/shared/bootspec.c @@ -15,7 +15,7 @@ #include "fileio.h" #include "find-esp.h" #include "path-util.h" -#include "pe-header.h" +#include "pe-binary.h" #include "pretty-print.h" #include "recurse-dir.h" #include "sort-util.h" @@ -757,92 +757,36 @@ static int find_sections( char **ret_osrelease, char **ret_cmdline) { - _cleanup_free_ struct PeSectionHeader *sections = NULL; - _cleanup_free_ char *osrelease = NULL, *cmdline = NULL; - ssize_t n; + _cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL; + _cleanup_free_ IMAGE_DOS_HEADER *dos_header = NULL; + _cleanup_free_ char *osrel = NULL, *cmdline = NULL; + _cleanup_free_ PeHeader *pe_header = NULL; + int r; - struct DosFileHeader dos; - n = pread(fd, &dos, sizeof(dos), 0); - if (n < 0) - return log_warning_errno(errno, "%s: Failed to read DOS header, ignoring: %m", path); - if (n != sizeof(dos)) - return log_warning_errno(SYNTHETIC_ERRNO(EIO), "%s: Short read while reading DOS header, ignoring.", path); + assert(fd >= 0); + assert(path); - if (dos.Magic[0] != 'M' || dos.Magic[1] != 'Z') - return log_warning_errno(SYNTHETIC_ERRNO(EBADMSG), "%s: DOS executable magic missing, ignoring.", path); + r = pe_load_headers(fd, &dos_header, &pe_header); + if (r < 0) + return log_warning_errno(r, "Failed to parse PE file '%s': %m", path); - uint64_t start = unaligned_read_le32(&dos.ExeHeader); + r = pe_load_sections(fd, dos_header, pe_header, §ions); + if (r < 0) + return log_warning_errno(r, "Failed to parse PE sections of '%s': %m", path); - struct PeHeader pe; - n = pread(fd, &pe, sizeof(pe), start); - if (n < 0) - return log_warning_errno(errno, "%s: Failed to read PE header, ignoring: %m", path); - if (n != sizeof(pe)) - return log_warning_errno(SYNTHETIC_ERRNO(EIO), "%s: Short read while reading PE header, ignoring.", path); + if (!pe_is_uki(pe_header, sections)) + return log_warning_errno(SYNTHETIC_ERRNO(EBADMSG), "Parsed PE file '%s' is not a UKI.", path); - if (pe.Magic[0] != 'P' || pe.Magic[1] != 'E' || pe.Magic[2] != 0 || pe.Magic[3] != 0) - return log_warning_errno(SYNTHETIC_ERRNO(EBADMSG), "%s: PE executable magic missing, ignoring.", path); + r = pe_read_section_data(fd, pe_header, sections, ".osrel", PE_SECTION_SIZE_MAX, (void**) &osrel, NULL); + if (r < 0) + return log_warning_errno(r, "Failed to read .osrel section of '%s': %m", path); - size_t n_sections = unaligned_read_le16(&pe.FileHeader.NumberOfSections); - if (n_sections > 96) - return log_warning_errno(SYNTHETIC_ERRNO(EBADMSG), "%s: PE header has too many sections, ignoring.", path); - - sections = new(struct PeSectionHeader, n_sections); - if (!sections) - return log_oom(); - - n = pread(fd, sections, - n_sections * sizeof(struct PeSectionHeader), - start + sizeof(pe) + unaligned_read_le16(&pe.FileHeader.SizeOfOptionalHeader)); - if (n < 0) - return log_warning_errno(errno, "%s: Failed to read section data, ignoring: %m", path); - if ((size_t) n != n_sections * sizeof(struct PeSectionHeader)) - return log_warning_errno(SYNTHETIC_ERRNO(EIO), "%s: Short read while reading sections, ignoring.", path); - - for (size_t i = 0; i < n_sections; i++) { - _cleanup_free_ char *k = NULL; - uint32_t offset, size; - char **b; - - if (strneq((char*) sections[i].Name, ".osrel", sizeof(sections[i].Name))) - b = &osrelease; - else if (strneq((char*) sections[i].Name, ".cmdline", sizeof(sections[i].Name))) - b = &cmdline; - else - continue; - - if (*b) - return log_warning_errno(SYNTHETIC_ERRNO(EBADMSG), "%s: Duplicate section %s, ignoring.", path, sections[i].Name); - - offset = unaligned_read_le32(§ions[i].PointerToRawData); - size = unaligned_read_le32(§ions[i].VirtualSize); - - if (size > PE_SECTION_SIZE_MAX) - return log_warning_errno(SYNTHETIC_ERRNO(EBADMSG), "%s: Section %s too large, ignoring.", path, sections[i].Name); - - k = new(char, size+1); - if (!k) - return log_oom(); - - n = pread(fd, k, size, offset); - if (n < 0) - return log_warning_errno(errno, "%s: Failed to read section payload, ignoring: %m", path); - if ((size_t) n != size) - return log_warning_errno(SYNTHETIC_ERRNO(EIO), "%s: Short read while reading section payload, ignoring:", path); - - /* Allow one trailing NUL byte, but nothing more. */ - if (size > 0 && memchr(k, 0, size - 1)) - return log_warning_errno(SYNTHETIC_ERRNO(EBADMSG), "%s: Section contains embedded NUL byte, ignoring.", path); - - k[size] = 0; - *b = TAKE_PTR(k); - } - - if (!osrelease) - return log_warning_errno(SYNTHETIC_ERRNO(EBADMSG), "%s: Image lacks .osrel section, ignoring.", path); + r = pe_read_section_data(fd, pe_header, sections, ".cmdline", PE_SECTION_SIZE_MAX, (void**) &cmdline, NULL); + if (r < 0 && r != -ENXIO) /* cmdline is optional */ + return log_warning_errno(r, "Failed to read .cmdline section of '%s': %m", path); if (ret_osrelease) - *ret_osrelease = TAKE_PTR(osrelease); + *ret_osrelease = TAKE_PTR(osrel); if (ret_cmdline) *ret_cmdline = TAKE_PTR(cmdline); diff --git a/src/shared/kernel-image.c b/src/shared/kernel-image.c index 3d2ec820d9..7dc9e01e92 100644 --- a/src/shared/kernel-image.c +++ b/src/shared/kernel-image.c @@ -6,19 +6,10 @@ #include "kernel-image.h" #include "os-util.h" #include "parse-util.h" -#include "pe-header.h" +#include "pe-binary.h" #include "string-table.h" -#define MAX_SECTIONS 96 - -static const uint8_t dos_file_magic[2] = "MZ"; -static const uint8_t pe_file_magic[4] = "PE\0\0"; - -static const uint8_t name_osrel[8] = ".osrel"; -static const uint8_t name_linux[8] = ".linux"; -static const uint8_t name_initrd[8] = ".initrd"; -static const uint8_t name_cmdline[8] = ".cmdline"; -static const uint8_t name_uname[8] = ".uname"; +#define PE_SECTION_READ_MAX (16U*1024U) static const char * const kernel_image_type_table[_KERNEL_IMAGE_TYPE_MAX] = { [KERNEL_IMAGE_TYPE_UNKNOWN] = "unknown", @@ -28,172 +19,44 @@ static const char * const kernel_image_type_table[_KERNEL_IMAGE_TYPE_MAX] = { DEFINE_STRING_TABLE_LOOKUP_TO_STRING(kernel_image_type, KernelImageType); -static int pe_sections(FILE *f, struct PeSectionHeader **ret, size_t *ret_n) { - _cleanup_free_ struct PeSectionHeader *sections = NULL; - struct DosFileHeader dos; - struct PeHeader pe; - size_t scount; - uint64_t soff, items; - - assert(f); - assert(ret); - assert(ret_n); - - items = fread(&dos, 1, sizeof(dos), f); - if (items < sizeof(dos.Magic)) - return log_error_errno(SYNTHETIC_ERRNO(EIO), "File is smaller than DOS magic (got %"PRIu64" of %zu bytes)", - items, sizeof(dos.Magic)); - if (memcmp(dos.Magic, dos_file_magic, sizeof(dos_file_magic)) != 0) - goto no_sections; - - if (items != sizeof(dos)) - return log_error_errno(SYNTHETIC_ERRNO(EIO), "File is smaller than DOS header (got %"PRIu64" of %zu bytes)", - items, sizeof(dos)); - - if (fseek(f, le32toh(dos.ExeHeader), SEEK_SET) < 0) - return log_error_errno(errno, "Failed to seek to PE header: %m"); - - items = fread(&pe, 1, sizeof(pe), f); - if (items != sizeof(pe)) - return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to read PE header."); - if (memcmp(pe.Magic, pe_file_magic, sizeof(pe_file_magic)) != 0) - goto no_sections; - - soff = le32toh(dos.ExeHeader) + sizeof(pe) + le16toh(pe.FileHeader.SizeOfOptionalHeader); - if (fseek(f, soff, SEEK_SET) < 0) - return log_error_errno(errno, "Failed to seek to PE section headers: %m"); - - scount = le16toh(pe.FileHeader.NumberOfSections); - if (scount > MAX_SECTIONS) - goto no_sections; - sections = new(struct PeSectionHeader, scount); - if (!sections) - return log_oom(); - items = fread(sections, sizeof(*sections), scount, f); - if (items != scount) - return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to read PE section header."); - - *ret = TAKE_PTR(sections); - *ret_n = scount; - return 0; - -no_sections: - *ret = NULL; - *ret_n = 0; - return 0; -} - -static bool find_pe_section( - struct PeSectionHeader *sections, - size_t scount, - const uint8_t *name, - size_t namelen, - size_t *ret) { - - assert(sections || scount == 0); - assert(name || namelen == 0); - - for (size_t s = 0; s < scount; s++) - if (memcmp_nn(sections[s].Name, sizeof(sections[s].Name), name, namelen) == 0) { - if (ret) - *ret = s; - return true; - } - - return false; -} - -static bool is_uki(struct PeSectionHeader *sections, size_t scount) { - assert(sections || scount == 0); - - return - find_pe_section(sections, scount, name_osrel, sizeof(name_osrel), NULL) && - find_pe_section(sections, scount, name_linux, sizeof(name_linux), NULL) && - find_pe_section(sections, scount, name_initrd, sizeof(name_initrd), NULL); -} - -static int read_pe_section( - FILE *f, - struct PeSectionHeader *sections, - size_t scount, - const uint8_t *name, - size_t name_len, - void **ret, - size_t *ret_n) { - - struct PeSectionHeader *section; - _cleanup_free_ void *data = NULL; - uint32_t size, bytes; - uint64_t soff; - size_t idx; - - assert(f); - assert(sections || scount == 0); - assert(ret); - - if (!find_pe_section(sections, scount, name, name_len, &idx)) { - *ret = NULL; - if (ret_n) - *ret_n = 0; - return 0; - } - - section = sections + idx; - soff = le32toh(section->PointerToRawData); - size = le32toh(section->VirtualSize); - - if (size > 16 * 1024) - return log_error_errno(SYNTHETIC_ERRNO(E2BIG), "PE section too big."); - - if (fseek(f, soff, SEEK_SET) < 0) - return log_error_errno(errno, "Failed to seek to PE section: %m"); - - data = malloc(size+1); - if (!data) - return log_oom(); - ((uint8_t*) data)[size] = 0; /* safety NUL byte */ - - bytes = fread(data, 1, size, f); - if (bytes != size) - return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to read PE section."); - - *ret = TAKE_PTR(data); - if (ret_n) - *ret_n = size; - return 1; -} - static int uki_read_pretty_name( - FILE *f, - struct PeSectionHeader *sections, - size_t scount, + int fd, + const PeHeader *pe_header, + const IMAGE_SECTION_HEADER *sections, char **ret) { _cleanup_free_ char *pname = NULL, *name = NULL; - _cleanup_fclose_ FILE *s = NULL; + _cleanup_fclose_ FILE *f = NULL; _cleanup_free_ void *osrel = NULL; - size_t osrel_size = 0; + size_t osrel_size; int r; - assert(f); - assert(sections || scount == 0); + assert(fd >= 0); + assert(pe_header); + assert(sections || le16toh(pe_header->pe.NumberOfSections) == 0); assert(ret); - r = read_pe_section(f, sections, scount, name_osrel, sizeof(name_osrel), &osrel, &osrel_size); - if (r < 0) - return r; - if (r == 0) { + r = pe_read_section_data( + fd, + pe_header, + sections, + ".osrel", + /* max_size=*/ PE_SECTION_READ_MAX, + &osrel, + &osrel_size); + if (r == -ENXIO) { /* Section not found */ *ret = NULL; return 0; } - s = fmemopen(osrel, osrel_size, "r"); - if (!s) + f = fmemopen(osrel, osrel_size, "r"); + if (!f) return log_error_errno(errno, "Failed to open embedded os-release file: %m"); - r = parse_env_file(s, NULL, - "PRETTY_NAME", &pname, - "NAME", &name); + r = parse_env_file( + f, NULL, + "PRETTY_NAME", &pname, + "NAME", &name); if (r < 0) return log_error_errno(r, "Failed to parse embedded os-release file: %m"); @@ -214,9 +77,9 @@ static int uki_read_pretty_name( } static int inspect_uki( - FILE *f, - struct PeSectionHeader *sections, - size_t scount, + int fd, + const PeHeader *pe_header, + const IMAGE_SECTION_HEADER *sections, char **ret_cmdline, char **ret_uname, char **ret_pretty_name) { @@ -224,23 +87,23 @@ static int inspect_uki( _cleanup_free_ char *cmdline = NULL, *uname = NULL, *pname = NULL; int r; - assert(f); - assert(sections || scount == 0); + assert(fd >= 0); + assert(sections || le16toh(pe_header->pe.NumberOfSections) == 0); if (ret_cmdline) { - r = read_pe_section(f, sections, scount, name_cmdline, sizeof(name_cmdline), (void**) &cmdline, NULL); - if (r < 0) + r = pe_read_section_data(fd, pe_header, sections, ".cmdline", PE_SECTION_READ_MAX, (void**) &cmdline, NULL); + if (r < 0 && r != -ENXIO) /* If the section doesn't exist, that's fine */ return r; } if (ret_uname) { - r = read_pe_section(f, sections, scount, name_uname, sizeof(name_uname), (void**) &uname, NULL); - if (r < 0) + r = pe_read_section_data(fd, pe_header, sections, ".uname", PE_SECTION_READ_MAX, (void**) &uname, NULL); + if (r < 0 && r != -ENXIO) /* If the section doesn't exist, that's fine */ return r; } if (ret_pretty_name) { - r = uki_read_pretty_name(f, sections, scount, &pname); + r = uki_read_pretty_name(fd, pe_header, sections, &pname); if (r < 0) return r; } @@ -263,33 +126,51 @@ int inspect_kernel( char **ret_uname, char **ret_pretty_name) { - _cleanup_fclose_ FILE *f = NULL; - _cleanup_free_ struct PeSectionHeader *sections = NULL; - size_t scount; - KernelImageType t; + _cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL; + _cleanup_free_ IMAGE_DOS_HEADER *dos_header = NULL; + KernelImageType t = KERNEL_IMAGE_TYPE_UNKNOWN; + _cleanup_free_ PeHeader *pe_header = NULL; + _cleanup_close_ int fd = -EBADF; int r; assert(dir_fd >= 0 || dir_fd == AT_FDCWD); assert(filename); - r = xfopenat(dir_fd, filename, "re", 0, &f); - if (r < 0) - return log_error_errno(r, "Failed to open kernel image file '%s': %m", filename); + fd = openat(dir_fd, filename, O_RDONLY|O_CLOEXEC); + if (fd < 0) + return log_error_errno(errno, "Failed to open kernel image file '%s': %m", filename); - r = pe_sections(f, §ions, &scount); + r = pe_load_headers(fd, &dos_header, &pe_header); + if (r == -EBADMSG) /* not a valid PE file */ + goto not_uki; if (r < 0) - return r; + return log_error_errno(r, "Failed to parse kernel image file '%s': %m", filename); - if (!sections) - t = KERNEL_IMAGE_TYPE_UNKNOWN; - else if (is_uki(sections, scount)) { - t = KERNEL_IMAGE_TYPE_UKI; - r = inspect_uki(f, sections, scount, ret_cmdline, ret_uname, ret_pretty_name); + r = pe_load_sections(fd, dos_header, pe_header, §ions); + if (r == -EBADMSG) /* not a valid PE file */ + goto not_uki; + if (r < 0) + return log_error_errno(r, "Failed to load PE sections from kernel image file '%s': %m", filename); + + if (pe_is_uki(pe_header, sections)) { + r = inspect_uki(fd, pe_header, sections, ret_cmdline, ret_uname, ret_pretty_name); if (r < 0) return r; + + t = KERNEL_IMAGE_TYPE_UKI; + goto done; } else t = KERNEL_IMAGE_TYPE_PE; +not_uki: + if (ret_cmdline) + *ret_cmdline = NULL; + if (ret_uname) + *ret_uname = NULL; + if (ret_pretty_name) + *ret_pretty_name = NULL; + +done: if (ret_type) *ret_type = t; diff --git a/src/shared/meson.build b/src/shared/meson.build index 2c18ab43dd..01e85b617f 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 0000000000..6a6b8adb86 --- /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 0000000000..758bb1de83 --- /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); diff --git a/src/shared/pe-header.h b/src/shared/pe-header.h deleted file mode 100644 index 4ddfce39e6..0000000000 --- a/src/shared/pe-header.h +++ /dev/null @@ -1,58 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ - -#pragma once - -#include - -#include "macro.h" -#include "sparse-endian.h" - -struct DosFileHeader { - uint8_t Magic[2]; - le16_t LastSize; - le16_t nBlocks; - le16_t nReloc; - le16_t HdrSize; - le16_t MinAlloc; - le16_t MaxAlloc; - le16_t ss; - le16_t sp; - le16_t Checksum; - le16_t ip; - le16_t cs; - le16_t RelocPos; - le16_t nOverlay; - le16_t reserved[4]; - le16_t OEMId; - le16_t OEMInfo; - le16_t reserved2[10]; - le32_t ExeHeader; -} _packed_; - -struct PeFileHeader { - le16_t Machine; - le16_t NumberOfSections; - le32_t TimeDateStamp; - le32_t PointerToSymbolTable; - le32_t NumberOfSymbols; - le16_t SizeOfOptionalHeader; - le16_t Characteristics; -} _packed_; - -struct PeHeader { - uint8_t Magic[4]; - struct PeFileHeader FileHeader; -} _packed_; - -struct PeSectionHeader { - 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; - } _packed_;