From a8e912f01ba85bcb6e42e395d39301f81671b845 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 5 Jul 2024 10:05:59 +0200 Subject: [PATCH 1/6] pe-binary: add helper pe_is_addon() for detecting whether we are looking at PE EFI add-on --- src/shared/pe-binary.c | 14 ++++++++++++++ src/shared/pe-binary.h | 1 + 2 files changed, 15 insertions(+) diff --git a/src/shared/pe-binary.c b/src/shared/pe-binary.c index 997e0e49a6..a3aceb909e 100644 --- a/src/shared/pe-binary.c +++ b/src/shared/pe-binary.c @@ -240,3 +240,17 @@ bool pe_is_uki(const PeHeader *pe_header, const IMAGE_SECTION_HEADER *sections) pe_header_find_section(pe_header, sections, ".osrel") && pe_header_find_section(pe_header, sections, ".linux"); } + +bool pe_is_addon(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; + + /* Add-ons do not have a Linux kernel, but do have either .cmdline or .dtb (currently) */ + return !pe_header_find_section(pe_header, sections, ".linux") && + (pe_header_find_section(pe_header, sections, ".cmdline") || + pe_header_find_section(pe_header, sections, ".dtb") || + pe_header_find_section(pe_header, sections, ".ucode")); +} diff --git a/src/shared/pe-binary.h b/src/shared/pe-binary.h index 2ef44d7e49..f0979711c8 100644 --- a/src/shared/pe-binary.h +++ b/src/shared/pe-binary.h @@ -142,3 +142,4 @@ int pe_load_sections(int fd, const IMAGE_DOS_HEADER *dos_header, const PeHeader 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); +bool pe_is_addon(const PeHeader *pe_header, const IMAGE_SECTION_HEADER *sections); From f3c1d7fea124c5a68534ffe14bab2996ffe84732 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 5 Jul 2024 10:15:56 +0200 Subject: [PATCH 2/6] pe-binary: split pe_read_section_data() into two This renames pe_read_section_data() to pe_read_section_data_by_name() and makes pe_read_section_data() a bit more low-level: it takes a header table entry directly, instead of searching it first by name. --- src/shared/bootspec.c | 4 +-- src/shared/kernel-image.c | 6 ++--- src/shared/pe-binary.c | 52 +++++++++++++++++++++++---------------- src/shared/pe-binary.h | 3 ++- 4 files changed, 38 insertions(+), 27 deletions(-) diff --git a/src/shared/bootspec.c b/src/shared/bootspec.c index 29338ac7c4..b902fe5480 100644 --- a/src/shared/bootspec.c +++ b/src/shared/bootspec.c @@ -790,7 +790,7 @@ static int find_cmdline_section( if (!ret_cmdline) return 0; - r = pe_read_section_data(fd, pe_header, sections, ".cmdline", PE_SECTION_SIZE_MAX, (void**) &cmdline, NULL); + r = pe_read_section_data_by_name(fd, pe_header, sections, ".cmdline", PE_SECTION_SIZE_MAX, (void**) &cmdline, NULL); if (r == -ENXIO) { /* cmdline is optional */ *ret_cmdline = NULL; return 0; @@ -824,7 +824,7 @@ static int find_osrel_section( if (!ret_osrelease) return 0; - r = pe_read_section_data(fd, pe_header, sections, ".osrel", PE_SECTION_SIZE_MAX, (void**) ret_osrelease, NULL); + r = pe_read_section_data_by_name(fd, pe_header, sections, ".osrel", PE_SECTION_SIZE_MAX, (void**) ret_osrelease, NULL); if (r < 0) return log_error_errno(r, "Failed to read .osrel section of '%s': %m", path); diff --git a/src/shared/kernel-image.c b/src/shared/kernel-image.c index 7dc9e01e92..4230d012be 100644 --- a/src/shared/kernel-image.c +++ b/src/shared/kernel-image.c @@ -36,7 +36,7 @@ static int uki_read_pretty_name( assert(sections || le16toh(pe_header->pe.NumberOfSections) == 0); assert(ret); - r = pe_read_section_data( + r = pe_read_section_data_by_name( fd, pe_header, sections, @@ -91,13 +91,13 @@ static int inspect_uki( assert(sections || le16toh(pe_header->pe.NumberOfSections) == 0); if (ret_cmdline) { - r = pe_read_section_data(fd, pe_header, sections, ".cmdline", PE_SECTION_READ_MAX, (void**) &cmdline, NULL); + r = pe_read_section_data_by_name(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 = pe_read_section_data(fd, pe_header, sections, ".uname", PE_SECTION_READ_MAX, (void**) &uname, NULL); + r = pe_read_section_data_by_name(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; } diff --git a/src/shared/pe-binary.c b/src/shared/pe-binary.c index a3aceb909e..448ace7571 100644 --- a/src/shared/pe-binary.c +++ b/src/shared/pe-binary.c @@ -173,43 +173,28 @@ int pe_load_sections( int pe_read_section_data( int fd, - const PeHeader *pe_header, - const IMAGE_SECTION_HEADER *sections, - const char *name, + const IMAGE_SECTION_HEADER *section, 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); + assert(section); - section = pe_header_find_section(pe_header, sections, name); - if (!section) - return -ENXIO; - - n = le32toh(section->VirtualSize); + size_t n = le32toh(section->VirtualSize); if (n > MIN(max_size, (size_t) SSIZE_MAX)) return -E2BIG; - data = malloc(n+1); + _cleanup_free_ void *data = malloc(n+1); if (!data) return -ENOMEM; - ss = pread(fd, data, n, le32toh(section->PointerToRawData)); + ssize_t 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 { @@ -221,12 +206,37 @@ int pe_read_section_data( 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) + if (ret) { + ((uint8_t*) data)[n] = 0; /* NUL terminate, no matter what */ *ret = TAKE_PTR(data); + } return 0; } +int pe_read_section_data_by_name( + 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; + + 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; + + return pe_read_section_data(fd, section, max_size, ret, ret_size); +} + bool pe_is_uki(const PeHeader *pe_header, const IMAGE_SECTION_HEADER *sections) { assert(pe_header); assert(sections || le16toh(pe_header->pe.NumberOfSections) == 0); diff --git a/src/shared/pe-binary.h b/src/shared/pe-binary.h index f0979711c8..08cc6a75ad 100644 --- a/src/shared/pe-binary.h +++ b/src/shared/pe-binary.h @@ -139,7 +139,8 @@ const IMAGE_SECTION_HEADER *pe_header_find_section(const PeHeader *pe_header, co 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); +int pe_read_section_data(int fd, const IMAGE_SECTION_HEADER *section, size_t max_size, void **ret, size_t *ret_size); +int pe_read_section_data_by_name(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); bool pe_is_addon(const PeHeader *pe_header, const IMAGE_SECTION_HEADER *sections); From e6c49f7f11cbee0fde345c60b610cd0d8b0d3e1e Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 5 Jul 2024 10:31:20 +0200 Subject: [PATCH 3/6] pe-binary: split pe_header_find_section() in two This splits out the core part into a new function pe_section_table_find(). pe_header_find_section() takes a PeHeader as input, while pe_section_table_find() just takes the section table and its size. --- src/shared/pe-binary.c | 19 ++++++++++++++----- src/shared/pe-binary.h | 1 + 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/shared/pe-binary.c b/src/shared/pe-binary.c index 448ace7571..bfeaf3f1e1 100644 --- a/src/shared/pe-binary.c +++ b/src/shared/pe-binary.c @@ -37,22 +37,21 @@ const IMAGE_DATA_DIRECTORY *pe_header_get_data_directory( return PE_HEADER_OPTIONAL_FIELD(h, DataDirectory) + i; } -const IMAGE_SECTION_HEADER *pe_header_find_section( - const PeHeader *pe_header, +const IMAGE_SECTION_HEADER *pe_section_table_find( const IMAGE_SECTION_HEADER *sections, + size_t n_sections, const char *name) { size_t n; - assert(pe_header); assert(name); - assert(sections || le16toh(pe_header->pe.NumberOfSections) == 0); + assert(sections || n_sections == 0); n = strlen(name); if (n > sizeof(sections[0].Name)) /* Too long? */ return NULL; - FOREACH_ARRAY(section, sections, le16toh(pe_header->pe.NumberOfSections)) + FOREACH_ARRAY(section, sections, n_sections) if (memcmp(section->Name, name, n) == 0 && memeqzero(section->Name + n, sizeof(section->Name) - n)) return section; @@ -60,6 +59,16 @@ const IMAGE_SECTION_HEADER *pe_header_find_section( return NULL; } +const IMAGE_SECTION_HEADER* pe_header_find_section( + const PeHeader *pe_header, + const IMAGE_SECTION_HEADER *sections, + const char *name) { + + assert(pe_header); + + return pe_section_table_find(sections, le16toh(pe_header->pe.NumberOfSections), name); +} + int pe_load_headers( int fd, IMAGE_DOS_HEADER **ret_dos_header, diff --git a/src/shared/pe-binary.h b/src/shared/pe-binary.h index 08cc6a75ad..20f839e9a0 100644 --- a/src/shared/pe-binary.h +++ b/src/shared/pe-binary.h @@ -135,6 +135,7 @@ bool pe_header_is_64bit(const PeHeader *h); 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); +const IMAGE_SECTION_HEADER *pe_section_table_find(const IMAGE_SECTION_HEADER *sections, size_t n_sections, const char *name); int pe_load_headers(int fd, IMAGE_DOS_HEADER **ret_dos_header, PeHeader **ret_pe_header); From 9de565dd5d8dc768c1e9e2edb1eb5461d75505ef Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 5 Jul 2024 16:15:35 +0200 Subject: [PATCH 4/6] pe-binary: add pe_is_native() for checking if PE is native --- src/fundamental/uki.h | 22 ++++++++++++++++++++++ src/shared/pe-binary.c | 11 +++++++++++ src/shared/pe-binary.h | 2 ++ 3 files changed, 35 insertions(+) diff --git a/src/fundamental/uki.h b/src/fundamental/uki.h index 2fcde690d9..e7c59100e1 100644 --- a/src/fundamental/uki.h +++ b/src/fundamental/uki.h @@ -31,3 +31,25 @@ static inline bool unified_section_measure(UnifiedSection section) { /* Max number of profiles per UKI */ #define UNIFIED_PROFILES_MAX 256U + +/* The native PE machine type, if known, for a full list see: + * https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#machine-types */ +#ifndef _IMAGE_FILE_MACHINE_NATIVE +# if defined(__x86_64__) +# define _IMAGE_FILE_MACHINE_NATIVE UINT16_C(0x8664) +# elif defined(__i386__) +# define _IMAGE_FILE_MACHINE_NATIVE UINT16_C(0x014c) +# elif defined(__ia64__) +# define _IMAGE_FILE_MACHINE_NATIVE UINT16_C(0x0200) +# elif defined(__aarch64__) +# define _IMAGE_FILE_MACHINE_NATIVE UINT16_C(0xaa64) +# elif defined(__arm__) +# define _IMAGE_FILE_MACHINE_NATIVE UINT16_C(0x01c0) +# elif defined(__riscv) +# if __SIZEOF_POINTER__ == 4 +# define _IMAGE_FILE_MACHINE_NATIVE UINT16_C(0x5032) +# elif __SIZEOF_POINTER__ == 8 +# define _IMAGE_FILE_MACHINE_NATIVE UINT16_C(0x5064) +# endif +# endif +#endif diff --git a/src/shared/pe-binary.c b/src/shared/pe-binary.c index bfeaf3f1e1..3893ffaf97 100644 --- a/src/shared/pe-binary.c +++ b/src/shared/pe-binary.c @@ -6,6 +6,7 @@ #include "log.h" #include "pe-binary.h" #include "string-util.h" +#include "uki.h" bool pe_header_is_64bit(const PeHeader *h) { assert(h); @@ -273,3 +274,13 @@ bool pe_is_addon(const PeHeader *pe_header, const IMAGE_SECTION_HEADER *sections pe_header_find_section(pe_header, sections, ".dtb") || pe_header_find_section(pe_header, sections, ".ucode")); } + +bool pe_is_native(const PeHeader *pe_header) { + assert(pe_header); + +#ifdef _IMAGE_FILE_MACHINE_NATIVE + return le16toh(pe_header->pe.Machine) == _IMAGE_FILE_MACHINE_NATIVE; +#else + return false; +#endif +} diff --git a/src/shared/pe-binary.h b/src/shared/pe-binary.h index 20f839e9a0..f508989185 100644 --- a/src/shared/pe-binary.h +++ b/src/shared/pe-binary.h @@ -145,3 +145,5 @@ int pe_read_section_data_by_name(int fd, const PeHeader *pe_header, const IMAGE_ bool pe_is_uki(const PeHeader *pe_header, const IMAGE_SECTION_HEADER *sections); bool pe_is_addon(const PeHeader *pe_header, const IMAGE_SECTION_HEADER *sections); + +bool pe_is_native(const PeHeader *pe_header); From 59b3df9bae5743195363ff5931d33cc951cc29f3 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 5 Jul 2024 12:11:48 +0200 Subject: [PATCH 5/6] bootspec: process multi-profile UKIs --- src/shared/bootspec.c | 350 ++++++++++++++++++++++++++++++------------ src/shared/bootspec.h | 2 + 2 files changed, 252 insertions(+), 100 deletions(-) diff --git a/src/shared/bootspec.c b/src/shared/bootspec.c index b902fe5480..a74204f119 100644 --- a/src/shared/bootspec.c +++ b/src/shared/bootspec.c @@ -23,6 +23,7 @@ #include "string-table.h" #include "strv.h" #include "terminal-util.h" +#include "uki.h" #include "unaligned.h" static const char* const boot_entry_type_table[_BOOT_ENTRY_TYPE_MAX] = { @@ -48,6 +49,7 @@ static void boot_entry_free(BootEntry *entry) { free(entry->id); free(entry->id_old); + free(entry->id_without_profile); free(entry->path); free(entry->root); free(entry->title); @@ -529,10 +531,18 @@ static int boot_entry_compare(const BootEntry *a, const BootEntry *b) { return r; } - r = -strverscmp_improved(a->id, b->id); + r = -strverscmp_improved(a->id_without_profile ?: a->id, b->id_without_profile ?: b->id); if (r != 0) return r; + if (a->id_without_profile && b->id_without_profile) { + /* The strverscmp_improved() call above already established that we are talking about the + * same image here, hence order by profile, if there is one */ + r = CMP(a->profile, b->profile); + if (r != 0) + return r; + } + if (a->tries_left != UINT_MAX || b->tries_left != UINT_MAX) return 0; @@ -636,28 +646,30 @@ static int boot_entries_find_type1( static int boot_entry_load_unified( const char *root, const char *path, - const char *osrelease, - const char *cmdline, + unsigned profile, + const char *osrelease_text, + const char *profile_text, + const char *cmdline_text, BootEntry *ret) { _cleanup_free_ char *fname = NULL, *os_pretty_name = NULL, *os_image_id = NULL, *os_name = NULL, *os_id = NULL, *os_image_version = NULL, *os_version = NULL, *os_version_id = NULL, *os_build_id = NULL; - _cleanup_(boot_entry_free) BootEntry tmp = BOOT_ENTRY_INIT(BOOT_ENTRY_UNIFIED); const char *k, *good_name, *good_version, *good_sort_key; _cleanup_fclose_ FILE *f = NULL; int r; assert(root); assert(path); - assert(osrelease); + assert(osrelease_text); + assert(ret); k = path_startswith(path, root); if (!k) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Path is not below root: %s", path); - f = fmemopen_unlocked((void*) osrelease, strlen(osrelease), "r"); + f = fmemopen_unlocked((void*) osrelease_text, strlen(osrelease_text), "r"); if (!f) - return log_error_errno(errno, "Failed to open os-release buffer: %m"); + return log_oom(); r = parse_env_file(f, "os-release", "PRETTY_NAME", &os_pretty_name, @@ -685,10 +697,28 @@ static int boot_entry_load_unified( &good_sort_key)) return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Missing fields in os-release data from unified kernel image %s, refusing.", path); + _cleanup_free_ char *profile_id = NULL, *profile_title = NULL; + if (profile_text) { + fclose(f); + + f = fmemopen_unlocked((void*) profile_text, strlen(profile_text), "r"); + if (!f) + return log_oom(); + + r = parse_env_file( + f, "profile", + "ID", &profile_id, + "TITLE", &profile_title); + if (r < 0) + return log_error_errno(r, "Failed to parse profile data from unified kernel image '%s': %m", path); + } + r = path_extract_filename(path, &fname); if (r < 0) return log_error_errno(r, "Failed to extract file name from '%s': %m", path); + _cleanup_(boot_entry_free) BootEntry tmp = BOOT_ENTRY_INIT(BOOT_ENTRY_UNIFIED); + r = boot_filename_extract_tries(fname, &tmp.id, &tmp.tries_left, &tmp.tries_done); if (r < 0) return r; @@ -696,6 +726,19 @@ static int boot_entry_load_unified( if (!efi_loader_entry_name_valid(tmp.id)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid loader entry name: %s", tmp.id); + tmp.profile = profile; + + if (profile_id || profile > 0) { + tmp.id_without_profile = TAKE_PTR(tmp.id); + + if (profile_id) + tmp.id = strjoin(tmp.id_without_profile, "@", profile_id); + else + (void) asprintf(&tmp.id, "%s@%u", tmp.id_without_profile, profile); + if (!tmp.id) + return log_oom(); + } + if (os_id && os_version_id) { tmp.id_old = strjoin(os_id, "-", os_version_id); if (!tmp.id_old) @@ -714,13 +757,18 @@ static int boot_entry_load_unified( if (!tmp.kernel) return log_oom(); - tmp.options = strv_new(skip_leading_chars(cmdline, WHITESPACE)); + tmp.options = strv_new(cmdline_text); if (!tmp.options) return log_oom(); - delete_trailing_chars(tmp.options[0], WHITESPACE); - - tmp.title = strdup(good_name); + if (profile_title) + tmp.title = strjoin(good_name, " (", profile_title, ")"); + else if (profile_id) + tmp.title = strjoin(good_name, " (", profile_id, ")"); + else if (profile > 0) + (void) asprintf(&tmp.title, "%s (@%u)", good_name, profile); + else + tmp.title = strdup(good_name); if (!tmp.title) return log_oom(); @@ -740,11 +788,7 @@ static int boot_entry_load_unified( return 0; } -/* Maximum PE section we are willing to load (Note that sections we are not interested in may be larger, but - * the ones we do care about and we are willing to load into memory have this size limit.) */ -#define PE_SECTION_SIZE_MAX (4U*1024U*1024U) - -static int find_sections( +static int pe_load_headers_and_sections( int fd, const char *path, IMAGE_SECTION_HEADER **ret_sections, @@ -774,92 +818,174 @@ static int find_sections( return 0; } -static int find_cmdline_section( - int fd, - const char *path, - IMAGE_SECTION_HEADER *sections, - PeHeader *pe_header, - char **ret_cmdline) { +static const IMAGE_SECTION_HEADER* pe_find_profile_section_table( + const PeHeader *pe_header, + const IMAGE_SECTION_HEADER *sections, + unsigned profile, + size_t *ret_n_sections) { - int r; - char *cmdline = NULL, *t = NULL; - _cleanup_free_ char *word = NULL; + assert(pe_header); - assert(path); + /* Looks for the part of the section table that defines the specified profile. If 'profile' is + * specified as UINT_MAX this will look for the base profile. */ - if (!ret_cmdline) + if (le16toh(pe_header->pe.NumberOfSections) == 0) + return NULL; + + assert(sections); + + const IMAGE_SECTION_HEADER + *p = sections, + *e = sections + le16toh(pe_header->pe.NumberOfSections), + *start = profile == UINT_MAX ? sections : NULL, + *end; + unsigned current_profile = UINT_MAX; + + for (;;) { + p = pe_section_table_find(p, e - p, ".profile"); + if (!p) { + end = e; + break; + } + if (current_profile == profile) { + end = p; + break; + } + + if (current_profile == UINT_MAX) + current_profile = 0; + else + current_profile++; + + if (current_profile == profile) + start = p; + + p++; /* Continue scanning after the .profile entry we just found */ + } + + if (!start) + return NULL; + + if (ret_n_sections) + *ret_n_sections = end - start; + + return start; +} + +static int trim_cmdline(char **cmdline) { + assert(cmdline); + + /* Strips leading and trailing whitespace from command line */ + + if (!*cmdline) return 0; - r = pe_read_section_data_by_name(fd, pe_header, sections, ".cmdline", PE_SECTION_SIZE_MAX, (void**) &cmdline, NULL); - if (r == -ENXIO) { /* cmdline is optional */ - *ret_cmdline = NULL; + const char *skipped = skip_leading_chars(*cmdline, WHITESPACE); + + if (isempty(skipped)) { + *cmdline = mfree(*cmdline); return 0; } - if (r < 0) - return log_warning_errno(r, "Failed to read .cmdline section of '%s': %m", path); - word = strdup(cmdline); - if (!word) - return log_oom(); + if (skipped != *cmdline) { + _cleanup_free_ char *c = strdup(skipped); + if (!c) + return -ENOMEM; - /* Quick test to check if there is actual content in the addon cmdline */ - t = delete_chars(word, NULL); - if (isempty(t)) - *ret_cmdline = NULL; - else - *ret_cmdline = TAKE_PTR(cmdline); + free_and_replace(*cmdline, c); + } - return 0; + delete_trailing_chars(*cmdline, WHITESPACE); + return 1; } -static int find_osrel_section( - int fd, - const char *path, - IMAGE_SECTION_HEADER *sections, - PeHeader *pe_header, - char **ret_osrelease) { +/* Maximum PE section we are willing to load (Note that sections we are not interested in may be larger, but + * the ones we do care about and we are willing to load into memory have this size limit.) */ +#define PE_SECTION_SIZE_MAX (4U*1024U*1024U) - int r; - - if (!ret_osrelease) - return 0; - - r = pe_read_section_data_by_name(fd, pe_header, sections, ".osrel", PE_SECTION_SIZE_MAX, (void**) ret_osrelease, NULL); - if (r < 0) - return log_error_errno(r, "Failed to read .osrel section of '%s': %m", path); - - return 0; -} - -static int find_uki_sections( +static int pe_find_uki_sections( int fd, const char *path, + unsigned profile, char **ret_osrelease, + char **ret_profile, char **ret_cmdline) { + _cleanup_free_ char *osrelease_text = NULL, *profile_text = NULL, *cmdline_text = NULL; _cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL; _cleanup_free_ PeHeader *pe_header = NULL; int r; - r = find_sections(fd, path, §ions, &pe_header); + assert(fd >= 0); + assert(path); + assert(profile != UINT_MAX); + assert(ret_osrelease); + assert(ret_profile); + assert(ret_cmdline); + + r = pe_load_headers_and_sections(fd, path, §ions, &pe_header); if (r < 0) return r; if (!pe_is_uki(pe_header, sections)) return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Parsed PE file '%s' is not a UKI.", path); - r = find_osrel_section(fd, path, sections, pe_header, ret_osrelease); - if (r < 0) - return r; + /* Find part of the section table for this profile */ + size_t n_psections = 0; + const IMAGE_SECTION_HEADER *psections = pe_find_profile_section_table(pe_header, sections, profile, &n_psections); + if (!psections && profile != 0) /* Profile not found? (Profile @0 needs no explicit .profile!) */ + goto nothing; - r = find_cmdline_section(fd, path, sections, pe_header, ret_cmdline); - if (r < 0) - return r; + /* Find base profile part of section table */ + size_t n_bsections; + const IMAGE_SECTION_HEADER *bsections = ASSERT_PTR(pe_find_profile_section_table(pe_header, sections, UINT_MAX, &n_bsections)); + struct { + const char *name; + char **data; + } table[] = { + { ".osrel", &osrelease_text }, + { ".profile", &profile_text }, + { ".cmdline", &cmdline_text }, + }; + + FOREACH_ELEMENT(t, table) { + const IMAGE_SECTION_HEADER *found; + + /* First look in the profile part of the section table, and if we don't find anything there, look into the base part */ + found = pe_section_table_find(psections, n_psections, t->name); + if (!found) { + found = pe_section_table_find(bsections, n_bsections, t->name); + if (!found) + continue; + } + + /* Permit "masking" of sections in the base profile */ + if (found->VirtualSize == 0) + continue; + + r = pe_read_section_data(fd, found, PE_SECTION_SIZE_MAX, (void**) t->data, /* ret_data= */ NULL); + if (r < 0) + return log_error_errno(r, "Failed to load contents of section '%s': %m", t->name); + } + + if (!osrelease_text) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Unified kernel image lacks .osrel data for profile @%u, refusing.", profile); + + if (trim_cmdline(&cmdline_text) < 0) + return log_oom(); + + *ret_osrelease = TAKE_PTR(osrelease_text); + *ret_profile = TAKE_PTR(profile_text); + *ret_cmdline = TAKE_PTR(cmdline_text); + return 1; + +nothing: + *ret_osrelease = *ret_profile = *ret_cmdline = NULL; return 0; } -static int find_addon_sections( +static int pe_find_addon_sections( int fd, const char *path, char **ret_cmdline) { @@ -868,19 +994,36 @@ static int find_addon_sections( _cleanup_free_ PeHeader *pe_header = NULL; int r; - r = find_sections(fd, path, §ions, &pe_header); + assert(fd >= 0); + assert(path); + + r = pe_load_headers_and_sections(fd, path, §ions, &pe_header); if (r < 0) return r; - r = find_cmdline_section(fd, path, sections, pe_header, ret_cmdline); - /* If addon cmdline is empty or contains just separators, - * don't bother tracking it. - * Don't check r because it cannot return <0 if cmdline is empty, - * as cmdline is always optional. */ - if (!ret_cmdline) - return log_warning_errno(SYNTHETIC_ERRNO(ENOENT), "Addon %s contains empty cmdline and will be therefore ignored.", path); + if (!pe_is_addon(pe_header, sections)) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Parse PE file '%s' is not an add-on.", path); - return r; + /* Define early, before the goto below */ + _cleanup_free_ char *cmdline_text = NULL; + + const IMAGE_SECTION_HEADER *found = pe_section_table_find(sections, le16toh(pe_header->pe.NumberOfSections), ".cmdline"); + if (!found) + goto nothing; + + r = pe_read_section_data(fd, found, PE_SECTION_SIZE_MAX, (void**) &cmdline_text, /* ret_size= */ NULL); + if (r < 0) + return log_error_errno(r, "Failed to load contents of section '.cmdline': %m"); + + if (trim_cmdline(&cmdline_text) < 0) + return log_oom(); + + *ret_cmdline = TAKE_PTR(cmdline_text); + return 1; + +nothing: + *ret_cmdline = NULL; + return 0; } static int insert_boot_entry_addon( @@ -959,7 +1102,7 @@ static int boot_entries_find_unified_addons( if (!j) return log_oom(); - if (find_addon_sections(fd, j, &cmdline) < 0) + if (pe_find_addon_sections(fd, j, &cmdline) <= 0) continue; location = strdup(j); @@ -1032,19 +1175,13 @@ static int boot_entries_find_unified( return log_error_errno(r, "Failed to open '%s/%s': %m", root, dir); FOREACH_DIRENT(de, d, return log_error_errno(errno, "Failed to read %s: %m", full)) { - _cleanup_free_ char *j = NULL, *osrelease = NULL, *cmdline = NULL; - _cleanup_close_ int fd = -EBADF; - if (!dirent_is_file(de)) continue; if (!endswith_no_case(de->d_name, ".efi")) continue; - if (!GREEDY_REALLOC0(config->entries, config->n_entries + 1)) - return log_oom(); - - fd = openat(dirfd(d), de->d_name, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOFOLLOW|O_NOCTTY); + _cleanup_close_ int fd = openat(dirfd(d), de->d_name, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOFOLLOW|O_NOCTTY); if (fd < 0) { log_warning_errno(errno, "Failed to open %s/%s, ignoring: %m", full, de->d_name); continue; @@ -1056,23 +1193,30 @@ static int boot_entries_find_unified( if (r == 0) /* inode already seen or otherwise not relevant */ continue; - j = path_join(full, de->d_name); + _cleanup_free_ char *j = path_join(full, de->d_name); if (!j) return log_oom(); - if (find_uki_sections(fd, j, &osrelease, &cmdline) < 0) - continue; + for (unsigned p = 0; p < UNIFIED_PROFILES_MAX; p++) { + _cleanup_free_ char *osrelease = NULL, *profile = NULL, *cmdline = NULL; - r = boot_entry_load_unified(root, j, osrelease, cmdline, config->entries + config->n_entries); - if (r < 0) - continue; + r = pe_find_uki_sections(fd, j, p, &osrelease, &profile, &cmdline); + if (r == 0) /* this profile does not exist, we are done */ + break; + if (r < 0) + continue; - /* look for .efi.extra.d */ - r = boot_entries_find_unified_local_addons(config, dirfd(d), de->d_name, full, config->entries + config->n_entries); - if (r < 0) - continue; + if (!GREEDY_REALLOC0(config->entries, config->n_entries + 2)) + return log_oom(); - config->n_entries++; + if (boot_entry_load_unified(root, j, p, osrelease, profile, cmdline, config->entries + config->n_entries) < 0) + continue; + + config->n_entries++; + + /* look for .efi.extra.d */ + (void) boot_entries_find_unified_local_addons(config, dirfd(d), de->d_name, full, config->entries + config->n_entries); + } } return 0; @@ -1648,8 +1792,14 @@ int show_boot_entry( putchar('\n'); - if (e->id) - printf(" id: %s\n", e->id); + if (e->id) { + printf(" id: %s", e->id); + + if (e->id_without_profile && !streq_ptr(e->id, e->id_without_profile)) + printf(" (without profile: %s)\n", e->id_without_profile); + else + putchar('\n'); + } if (e->path) { _cleanup_free_ char *text = NULL, *link = NULL; @@ -1673,7 +1823,7 @@ int show_boot_entry( if (e->tries_done != UINT_MAX) printf("; %u done\n", e->tries_done); else - printf("\n"); + putchar('\n'); } if (e->sort_key) diff --git a/src/shared/bootspec.h b/src/shared/bootspec.h index 0be254e106..58c676fbec 100644 --- a/src/shared/bootspec.h +++ b/src/shared/bootspec.h @@ -38,6 +38,7 @@ typedef struct BootEntry { bool reported_by_loader; char *id; /* This is the file basename (including extension!) */ char *id_old; /* Old-style ID, for deduplication purposes. */ + char *id_without_profile; /* id without profile suffixed */ char *path; /* This is the full path to the drop-in file */ char *root; /* The root path in which the drop-in was found, i.e. to which 'kernel', 'efi' and 'initrd' are relative */ char *title; @@ -55,6 +56,7 @@ typedef struct BootEntry { char **device_tree_overlay; unsigned tries_left; unsigned tries_done; + unsigned profile; } BootEntry; #define BOOT_ENTRY_INIT(t) \ From 6a92a793ac858f08f29fe345986171dc9e93aa46 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 5 Jul 2024 16:17:27 +0200 Subject: [PATCH 6/6] bootspec: automatically filter non-native UKIs and add-ons when enumerating --- src/shared/bootspec.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/shared/bootspec.c b/src/shared/bootspec.c index a74204f119..50a07400fe 100644 --- a/src/shared/bootspec.c +++ b/src/shared/bootspec.c @@ -930,6 +930,9 @@ static int pe_find_uki_sections( if (!pe_is_uki(pe_header, sections)) return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Parsed PE file '%s' is not a UKI.", path); + if (!pe_is_native(pe_header)) /* Don't process non-native UKIs */ + goto nothing; + /* Find part of the section table for this profile */ size_t n_psections = 0; const IMAGE_SECTION_HEADER *psections = pe_find_profile_section_table(pe_header, sections, profile, &n_psections); @@ -1004,9 +1007,12 @@ static int pe_find_addon_sections( if (!pe_is_addon(pe_header, sections)) return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Parse PE file '%s' is not an add-on.", path); - /* Define early, before the goto below */ + /* Define early, before the gotos below */ _cleanup_free_ char *cmdline_text = NULL; + if (!pe_is_native(pe_header)) + goto nothing; + const IMAGE_SECTION_HEADER *found = pe_section_table_find(sections, le16toh(pe_header->pe.NumberOfSections), ".cmdline"); if (!found) goto nothing;