From: Lennart Poettering Date: Thu, 24 Aug 2023 13:41:32 +0000 (+0200) Subject: shared: port kernel-image.c over to new common PE parser X-Git-Tag: v255-rc1~620^2~1 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=5c425807bfdd232495e3878a7157f22c3db3d03e;p=thirdparty%2Fsystemd.git shared: port kernel-image.c over to new common PE parser --- diff --git a/src/shared/kernel-image.c b/src/shared/kernel-image.c index 3d2ec820d96..7dc9e01e92f 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); + 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_load_headers(fd, &dos_header, &pe_header); + if (r == -EBADMSG) /* not a valid PE file */ + goto not_uki; if (r < 0) - return log_error_errno(r, "Failed to open kernel image file '%s': %m", filename); + return log_error_errno(r, "Failed to parse kernel image file '%s': %m", filename); - r = pe_sections(f, §ions, &scount); + 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 r; + return log_error_errno(r, "Failed to load PE sections from 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); + 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;