]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
shared: port kernel-image.c over to new common PE parser
authorLennart Poettering <lennart@poettering.net>
Thu, 24 Aug 2023 13:41:32 +0000 (15:41 +0200)
committerLennart Poettering <lennart@poettering.net>
Tue, 29 Aug 2023 09:25:52 +0000 (11:25 +0200)
src/shared/kernel-image.c

index 3d2ec820d96efa3b1c03204453a09fbed4c2124d..7dc9e01e92f82e6337d0f32d869bf01123a4705e 100644 (file)
@@ -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, &sections, &scount);
+        r = pe_load_sections(fd, dos_header, pe_header, &sections);
+        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;