diff --git a/src/boot/efi/boot.c b/src/boot/efi/boot.c index 097dceb958..c143801019 100644 --- a/src/boot/efi/boot.c +++ b/src/boot/efi/boot.c @@ -24,6 +24,7 @@ #include "shim.h" #include "ticks.h" #include "tpm2-pcr.h" +#include "uki.h" #include "util.h" #include "version.h" #include "vmm.h" @@ -52,7 +53,8 @@ typedef enum LoaderType { } LoaderType; typedef struct { - char16_t *id; /* The unique identifier for this entry (typically the filename of the file defining the entry) */ + char16_t *id; /* The unique identifier for this entry (typically the filename of the file defining the entry, possibly suffixed with a profile id) */ + char16_t *id_without_profile; /* same, but without any profile id suffixed */ char16_t *title_show; /* The string to actually display (this is made unique before showing) */ char16_t *title; /* The raw (human readable) title string of the entry (not necessarily unique) */ char16_t *sort_key; /* The string to use as primary sort key, usually ID= from os-release, possibly suffixed */ @@ -72,6 +74,7 @@ typedef struct { char16_t *path; char16_t *current_name; char16_t *next_name; + unsigned profile; } BootEntry; typedef struct { @@ -584,7 +587,11 @@ static void print_status(Config *config, char16_t *loaded_image_path) { (void) device_path_to_str(dp, &dp_str); printf(" boot entry: %zu/%zu\n", i + 1, config->n_entries); - printf(" id: %ls\n", entry->id); + printf(" id: %ls", entry->id); + if (entry->id_without_profile && !streq(entry->id_without_profile, entry->id)) + printf(" (without profile: %ls)\n", entry->id_without_profile); + else + printf("\n"); if (entry->title) printf(" title: %ls\n", entry->title); if (entry->title_show && !streq16(entry->title, entry->title_show)) @@ -605,6 +612,8 @@ static void print_status(Config *config, char16_t *loaded_image_path) { printf(" devicetree: %ls\n", entry->devicetree); if (entry->options) printf(" options: %ls\n", entry->options); + if (entry->profile > 0) + printf(" profile: %u\n", entry->profile); printf(" internal call: %ls\n", yes_no(!!entry->call)); printf("counting boots: %ls\n", yes_no(entry->tries_left >= 0)); @@ -1182,6 +1191,7 @@ static BootEntry* boot_entry_free(BootEntry *entry) { return NULL; free(entry->id); + free(entry->id_without_profile); free(entry->title_show); free(entry->title); free(entry->sort_key); @@ -1651,7 +1661,9 @@ static void config_load_type1_entries( /* Adds Boot Loader Type #1 entries (i.e. /loader/entries/….conf) */ - err = open_directory(root_dir, u"\\loader\\entries", &entries_dir); + const uint16_t dropin_path[] = u"\\loader\\entries"; + + err = open_directory(root_dir, dropin_path, &entries_dir); if (err != EFI_SUCCESS) return; @@ -1680,7 +1692,7 @@ static void config_load_type1_entries( if (err != EFI_SUCCESS) continue; - boot_entry_add_type1(config, device, root_dir, u"\\loader\\entries", f->FileName, content, loaded_image_path); + boot_entry_add_type1(config, device, root_dir, dropin_path, f->FileName, content, loaded_image_path); } } @@ -1722,10 +1734,20 @@ static int boot_entry_compare(const BootEntry *a, const BootEntry *b) { /* Now order by ID. The version is likely part of the ID, thus note that this will generatelly put * the newer versions earlier. Specifying a sort key explicitly is preferable, because it gives an * explicit sort order. */ - 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; + /* Let's sort profiles by their profile */ + if (a->id_without_profile && b->id_without_profile) { + /* Note: the strverscmp_improved() call above checked for us that we are looking at the very + * same id, hence at this point we only need to compare profile numbers, since we know they + * belong to the same UKI. */ + r = CMP(a->profile, b->profile); + if (r != 0) + return r; + } + if (a->tries_left < 0 || b->tries_left < 0) return 0; @@ -1872,23 +1894,40 @@ static void generate_boot_entry_titles(Config *config) { } static bool is_sd_boot(EFI_FILE *root_dir, const char16_t *loader_path) { - static const char * const sections[] = { + static const char * const section_names[] = { ".sdmagic", NULL }; _cleanup_free_ char *content = NULL; - PeSectionVector vector = {}; EFI_STATUS err; size_t read; assert(root_dir); assert(loader_path); - err = pe_file_locate_sections(root_dir, loader_path, sections, &vector); - if (err != EFI_SUCCESS || vector.size != sizeof(SD_MAGIC)) + _cleanup_(file_closep) EFI_FILE *handle = NULL; + err = root_dir->Open(root_dir, &handle, (char16_t *) loader_path, EFI_FILE_MODE_READ, 0ULL); + if (err != EFI_SUCCESS) return false; - err = file_read(root_dir, loader_path, vector.file_offset, vector.size, &content, &read); + _cleanup_free_ PeSectionHeader *section_table = NULL; + size_t n_section_table; + err = pe_section_table_from_file(handle, §ion_table, &n_section_table); + if (err != EFI_SUCCESS) + return false; + + PeSectionVector vector = {}; + pe_locate_profile_sections( + section_table, + n_section_table, + section_names, + /* profile= */ UINT_MAX, + /* validate_base= */ 0, + &vector); + if (vector.size != sizeof(SD_MAGIC)) + return false; + + err = file_handle_read(handle, vector.file_offset, vector.size, &content, &read); if (err != EFI_SUCCESS || vector.size != read) return false; @@ -2099,16 +2138,19 @@ static void boot_entry_add_type2( Config *config, EFI_HANDLE *device, EFI_FILE *dir, + const uint16_t *path, const uint16_t *filename) { enum { SECTION_CMDLINE, SECTION_OSREL, + SECTION_PROFILE, _SECTION_MAX, }; static const char * const section_names[_SECTION_MAX + 1] = { [SECTION_CMDLINE] = ".cmdline", [SECTION_OSREL] = ".osrel", + [SECTION_PROFILE] = ".profile", NULL, }; @@ -2117,114 +2159,201 @@ static void boot_entry_add_type2( assert(config); assert(device); assert(dir); + assert(path); assert(filename); - /* Look for .osrel and .cmdline sections in the .efi binary */ - PeSectionVector sections[_SECTION_MAX] = {}; - err = pe_file_locate_sections(dir, filename, section_names, sections); - if (err != EFI_SUCCESS || !PE_SECTION_VECTOR_IS_SET(sections + SECTION_OSREL)) - return; - - _cleanup_free_ char *content = NULL; - err = file_read(dir, - filename, - sections[SECTION_OSREL].file_offset, - sections[SECTION_OSREL].size, - &content, - /* ret_size= */ NULL); + _cleanup_(file_closep) EFI_FILE *handle = NULL; + err = dir->Open(dir, &handle, (char16_t *) filename, EFI_FILE_MODE_READ, 0ULL); if (err != EFI_SUCCESS) return; - _cleanup_free_ char16_t *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; - char *line, *key, *value; - size_t pos = 0; + /* Load section table once */ + _cleanup_free_ PeSectionHeader *section_table = NULL; + size_t n_section_table; + err = pe_section_table_from_file(handle, §ion_table, &n_section_table); + if (err != EFI_SUCCESS) + return; - /* read properties from the embedded os-release file */ - while ((line = line_get_key_value(content, "=", &pos, &key, &value))) - if (streq8(key, "PRETTY_NAME")) { - free(os_pretty_name); - os_pretty_name = xstr8_to_16(value); + /* Find base profile */ + PeSectionVector base_sections[_SECTION_MAX] = {}; + pe_locate_profile_sections( + section_table, + n_section_table, + section_names, + /* profile= */ UINT_MAX, + /* validate_base= */ 0, + base_sections); - } else if (streq8(key, "IMAGE_ID")) { - free(os_image_id); - os_image_id = xstr8_to_16(value); + /* and now iterate through possible profiles, and create a menu item for each profile we find */ + for (unsigned profile = 0; profile < UNIFIED_PROFILES_MAX; profile ++) { + PeSectionVector sections[_SECTION_MAX]; - } else if (streq8(key, "NAME")) { - free(os_name); - os_name = xstr8_to_16(value); + /* Start out with the base sections */ + memcpy(sections, base_sections, sizeof(sections)); - } else if (streq8(key, "ID")) { - free(os_id); - os_id = xstr8_to_16(value); + err = pe_locate_profile_sections( + section_table, + n_section_table, + section_names, + profile, + /* validate_base= */ 0, + sections); + if (err != EFI_SUCCESS && profile > 0) /* It's fine if there's no .profile for the first + profile */ + break; - } else if (streq8(key, "IMAGE_VERSION")) { - free(os_image_version); - os_image_version = xstr8_to_16(value); + if (!PE_SECTION_VECTOR_IS_SET(sections + SECTION_OSREL)) + continue; - } else if (streq8(key, "VERSION")) { - free(os_version); - os_version = xstr8_to_16(value); + _cleanup_free_ char *content = NULL; + err = file_handle_read( + handle, + sections[SECTION_OSREL].file_offset, + sections[SECTION_OSREL].size, + &content, + /* ret_size= */ NULL); + if (err != EFI_SUCCESS) + continue; - } else if (streq8(key, "VERSION_ID")) { - free(os_version_id); - os_version_id = xstr8_to_16(value); + _cleanup_free_ char16_t *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; + char *line, *key, *value; + size_t pos = 0; - } else if (streq8(key, "BUILD_ID")) { - free(os_build_id); - os_build_id = xstr8_to_16(value); + /* read properties from the embedded os-release file */ + while ((line = line_get_key_value(content, "=", &pos, &key, &value))) + if (streq8(key, "PRETTY_NAME")) { + free(os_pretty_name); + os_pretty_name = xstr8_to_16(value); + + } else if (streq8(key, "IMAGE_ID")) { + free(os_image_id); + os_image_id = xstr8_to_16(value); + + } else if (streq8(key, "NAME")) { + free(os_name); + os_name = xstr8_to_16(value); + + } else if (streq8(key, "ID")) { + free(os_id); + os_id = xstr8_to_16(value); + + } else if (streq8(key, "IMAGE_VERSION")) { + free(os_image_version); + os_image_version = xstr8_to_16(value); + + } else if (streq8(key, "VERSION")) { + free(os_version); + os_version = xstr8_to_16(value); + + } else if (streq8(key, "VERSION_ID")) { + free(os_version_id); + os_version_id = xstr8_to_16(value); + + } else if (streq8(key, "BUILD_ID")) { + free(os_build_id); + os_build_id = xstr8_to_16(value); + } + + const char16_t *good_name, *good_version, *good_sort_key; + if (!bootspec_pick_name_version_sort_key( + os_pretty_name, + os_image_id, + os_name, + os_id, + os_image_version, + os_version, + os_version_id, + os_build_id, + &good_name, + &good_version, + &good_sort_key)) + continue; + + _cleanup_free_ char16_t *profile_id = NULL, *profile_title = NULL; + + if (PE_SECTION_VECTOR_IS_SET(sections + SECTION_PROFILE)) { + content = mfree(content); + + /* Read any .profile data from the file, if we have it */ + + err = file_handle_read( + handle, + sections[SECTION_PROFILE].file_offset, + sections[SECTION_PROFILE].size, + &content, + /* ret_size= */ NULL); + if (err != EFI_SUCCESS) + continue; + + /* read properties from the embedded os-release file */ + pos = 0; + while ((line = line_get_key_value(content, "=", &pos, &key, &value))) + if (streq8(key, "ID")) { + free(profile_id); + profile_id = xstr8_to_16(value); + } else if (streq8(key, "TITLE")) { + free(profile_title); + profile_title = xstr8_to_16(value); + } } - const char16_t *good_name, *good_version, *good_sort_key; - if (!bootspec_pick_name_version_sort_key( - os_pretty_name, - os_image_id, - os_name, - os_id, - os_image_version, - os_version, - os_version_id, - os_build_id, - &good_name, - &good_version, - &good_sort_key)) - return; + _cleanup_free_ char16_t *id = NULL; + if (profile > 0) { + if (profile_id) + id = xasprintf("%ls@%ls", filename, profile_id); + else + id = xasprintf("%ls@%u", filename, profile); + } else + id = xstrdup16(filename); - BootEntry *entry = xnew(BootEntry, 1); - *entry = (BootEntry) { - .id = xstrdup16(filename), - .type = LOADER_UNIFIED_LINUX, - .title = xstrdup16(good_name), - .version = xstrdup16(good_version), - .device = device, - .loader = xasprintf("\\EFI\\Linux\\%ls", filename), - .sort_key = xstrdup16(good_sort_key), - .key = 'l', - .tries_done = -1, - .tries_left = -1, - }; + _cleanup_free_ char16_t *title = NULL; + if (profile_title) + title = xasprintf("%ls (%ls)", good_name, profile_title); + else if (profile > 0) { + if (profile_id) + title = xasprintf("%ls (%ls)", good_name, profile_id); + else + title = xasprintf("%ls (Profile #%u)", good_name, profile + 1); + } else + title = xstrdup16(good_name); - strtolower16(entry->id); - config_add_entry(config, entry); - boot_entry_parse_tries(entry, u"\\EFI\\Linux", filename, u".efi"); + BootEntry *entry = xnew(BootEntry, 1); + *entry = (BootEntry) { + .id = strtolower16(TAKE_PTR(id)), + .id_without_profile = profile > 0 ? strtolower16(xstrdup16(filename)) : NULL, + .type = LOADER_UNIFIED_LINUX, + .title = TAKE_PTR(title), + .version = xstrdup16(good_version), + .device = device, + .loader = xasprintf("%ls\\%ls", path, filename), + .sort_key = xstrdup16(good_sort_key), + .key = 'l', + .tries_done = -1, + .tries_left = -1, + .profile = profile, + }; - if (!PE_SECTION_VECTOR_IS_SET(sections + SECTION_CMDLINE)) - return; + config_add_entry(config, entry); + boot_entry_parse_tries(entry, path, filename, u".efi"); - content = mfree(content); + if (!PE_SECTION_VECTOR_IS_SET(sections + SECTION_CMDLINE)) + return; - /* read the embedded cmdline file */ - size_t cmdline_len; - err = file_read(dir, - filename, - sections[SECTION_CMDLINE].file_offset, - sections[SECTION_CMDLINE].size, - &content, - &cmdline_len); - if (err == EFI_SUCCESS) { - entry->options = xstrn8_to_16(content, cmdline_len); - mangle_stub_cmdline(entry->options); - entry->options_implied = true; + content = mfree(content); + + /* Read the embedded cmdline file for display purposes */ + size_t cmdline_len; + err = file_handle_read( + handle, + sections[SECTION_CMDLINE].file_offset, + sections[SECTION_CMDLINE].size, + &content, + &cmdline_len); + if (err == EFI_SUCCESS) { + entry->options = mangle_stub_cmdline(xstrn8_to_16(content, cmdline_len)); + entry->options_implied = true; + } } } @@ -2244,7 +2373,9 @@ static void config_load_type2_entries( assert(device); assert(root_dir); - err = open_directory(root_dir, u"\\EFI\\Linux", &linux_dir); + const uint16_t dropin_path[] = u"\\EFI\\Linux"; + + err = open_directory(root_dir, dropin_path, &linux_dir); if (err != EFI_SUCCESS) return; @@ -2262,7 +2393,7 @@ static void config_load_type2_entries( if (startswith_no_case(f->FileName, u"auto-")) continue; - boot_entry_add_type2(config, device, linux_dir, f->FileName); + boot_entry_add_type2(config, device, linux_dir, dropin_path, f->FileName); } } @@ -2424,10 +2555,22 @@ static EFI_STATUS image_start( const char *extra = smbios_find_oem_string("io.systemd.boot.kernel-cmdline-extra"); if (extra) { _cleanup_free_ char16_t *tmp = TAKE_PTR(options), *extra16 = xstr8_to_16(extra); - options = xasprintf("%ls %ls", tmp, extra16); + if (isempty(tmp)) + options = TAKE_PTR(extra16); + else + options = xasprintf("%ls %ls", tmp, extra16); } } + /* Prefix profile if it's non-zero */ + if (entry->profile > 0) { + _cleanup_free_ char16_t *tmp = TAKE_PTR(options); + if (isempty(tmp)) + options = xasprintf("@%u", entry->profile); + else + options = xasprintf("@%u %ls", entry->profile, tmp); + } + if (options) { loaded_image->LoadOptions = options; loaded_image->LoadOptionsSize = strsize16(options); @@ -2589,6 +2732,7 @@ static void export_loader_variables( EFI_LOADER_FEATURE_SECUREBOOT_ENROLL | EFI_LOADER_FEATURE_RETAIN_SHIM | EFI_LOADER_FEATURE_MENU_DISABLE | + EFI_LOADER_FEATURE_MULTI_PROFILE_UKI | 0; assert(loaded_image); diff --git a/src/boot/efi/pe.c b/src/boot/efi/pe.c index 7eb8c4fa20..7cae3e7131 100644 --- a/src/boot/efi/pe.c +++ b/src/boot/efi/pe.c @@ -410,40 +410,6 @@ EFI_STATUS pe_section_table_from_file( return EFI_SUCCESS; } -EFI_STATUS pe_file_locate_sections( - EFI_FILE *dir, - const char16_t *path, - const char* const section_names[], - PeSectionVector sections[]) { - - _cleanup_free_ PeSectionHeader *section_table = NULL; - _cleanup_(file_closep) EFI_FILE *handle = NULL; - size_t n_section_table; - EFI_STATUS err; - - assert(dir); - assert(path); - assert(section_names); - assert(sections); - - err = dir->Open(dir, &handle, (char16_t *) path, EFI_FILE_MODE_READ, 0ULL); - if (err != EFI_SUCCESS) - return err; - - err = pe_section_table_from_file(handle, §ion_table, &n_section_table); - if (err != EFI_SUCCESS) - return err; - - pe_locate_sections( - section_table, - n_section_table, - section_names, - /* validate_base= */ 0, /* don't validate base */ - sections); - - return EFI_SUCCESS; -} - static const PeSectionHeader* pe_section_table_find_profile_start( const PeSectionHeader *section_table, size_t n_section_table, diff --git a/src/boot/efi/pe.h b/src/boot/efi/pe.h index 31efbebbeb..b9838579cf 100644 --- a/src/boot/efi/pe.h +++ b/src/boot/efi/pe.h @@ -52,10 +52,4 @@ EFI_STATUS pe_memory_locate_sections( const char *const section_names[], PeSectionVector sections[]); -EFI_STATUS pe_file_locate_sections( - EFI_FILE *dir, - const char16_t *path, - const char *const section_names[], - PeSectionVector sections[]); - EFI_STATUS pe_kernel_info(const void *base, uint32_t *ret_compat_address, size_t *ret_size_in_memory); diff --git a/src/fundamental/efivars-fundamental.h b/src/fundamental/efivars-fundamental.h index 10afe38c17..e18d59ff60 100644 --- a/src/fundamental/efivars-fundamental.h +++ b/src/fundamental/efivars-fundamental.h @@ -23,6 +23,7 @@ #define EFI_LOADER_FEATURE_SECUREBOOT_ENROLL (UINT64_C(1) << 11) #define EFI_LOADER_FEATURE_RETAIN_SHIM (UINT64_C(1) << 12) #define EFI_LOADER_FEATURE_MENU_DISABLE (UINT64_C(1) << 13) +#define EFI_LOADER_FEATURE_MULTI_PROFILE_UKI (UINT64_C(1) << 14) /* Features of the stub, i.e. systemd-stub */ #define EFI_STUB_FEATURE_REPORT_BOOT_PARTITION (UINT64_C(1) << 0) diff --git a/src/fundamental/uki.h b/src/fundamental/uki.h index 653fd2e616..2fcde690d9 100644 --- a/src/fundamental/uki.h +++ b/src/fundamental/uki.h @@ -28,3 +28,6 @@ static inline bool unified_section_measure(UnifiedSection section) { * the measurement, and hence shouldn't be input to it. */ return section >= 0 && section < _UNIFIED_SECTION_MAX && section != UNIFIED_SECTION_PCRSIG; } + +/* Max number of profiles per UKI */ +#define UNIFIED_PROFILES_MAX 256U