]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
boot: synthesize a separate menu entry from each .profile section
authorLennart Poettering <lennart@poettering.net>
Fri, 28 Jun 2024 18:12:39 +0000 (20:12 +0200)
committerLennart Poettering <lennart@poettering.net>
Tue, 10 Sep 2024 12:38:11 +0000 (14:38 +0200)
This iterates through the .profile sections a UKI provides and uses it
to generate multiple menu entries from them, one for each .profile
section.

src/boot/efi/boot.c
src/fundamental/efivars-fundamental.h
src/fundamental/uki.h

index c788978eaa70be2e4e3ec67582ddc95d3147fdc5..8604c1e8d03c0703f8c141c63d782c826e49457a 100644 (file)
@@ -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);
@@ -1722,10 +1732,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;
 
@@ -2121,11 +2141,13 @@ static void boot_entry_add_type2(
         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,
         };
 
@@ -2141,124 +2163,193 @@ static void boot_entry_add_type2(
         if (err != EFI_SUCCESS)
                 return;
 
+        /* Load section table once */
         _cleanup_free_ PeSectionHeader *section_table = NULL;
         size_t n_section_table;
         err = pe_section_table_from_file(handle, &section_table, &n_section_table);
         if (err != EFI_SUCCESS)
                 return;
 
-        /* Look for .osrel and .cmdline sections in the .efi binary */
-        PeSectionVector sections[_SECTION_MAX] = {};
+        /* 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,
-                        sections);
-        if (!PE_SECTION_VECTOR_IS_SET(sections + SECTION_OSREL))
-                return;
+                        base_sections);
+
+        /* 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];
+
+                /* Start out with the base sections */
+                memcpy(sections, base_sections, sizeof(sections));
+
+                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;
 
-        _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)
-                return;
+                if (!PE_SECTION_VECTOR_IS_SET(sections + SECTION_OSREL))
+                        continue;
 
-        _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;
+                _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;
+
+                _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;
+
+                /* 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);
 
-        /* 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_VERSION")) {
+                                free(os_image_version);
+                                os_image_version = 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, "VERSION")) {
+                                free(os_version);
+                                os_version = xstr8_to_16(value);
 
-                } else if (streq8(key, "NAME")) {
-                        free(os_name);
-                        os_name = 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, "ID")) {
-                        free(os_id);
-                        os_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;
 
-                } else if (streq8(key, "IMAGE_VERSION")) {
-                        free(os_image_version);
-                        os_image_version = xstr8_to_16(value);
+                _cleanup_free_ char16_t *profile_id = NULL, *profile_title = NULL;
 
-                } else if (streq8(key, "VERSION")) {
-                        free(os_version);
-                        os_version = xstr8_to_16(value);
+                if (PE_SECTION_VECTOR_IS_SET(sections + SECTION_PROFILE)) {
+                        content = mfree(content);
 
-                } else if (streq8(key, "VERSION_ID")) {
-                        free(os_version_id);
-                        os_version_id = xstr8_to_16(value);
+                        /* Read any .profile data from the file, if we have it */
 
-                } else if (streq8(key, "BUILD_ID")) {
-                        free(os_build_id);
-                        os_build_id = xstr8_to_16(value);
+                        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);
+
+                _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);
 
-        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,
-        };
+                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("\\EFI\\Linux\\%ls", filename),
+                        .sort_key = xstrdup16(good_sort_key),
+                        .key = 'l',
+                        .tries_done = -1,
+                        .tries_left = -1,
+                        .profile = profile,
+                };
 
-        strtolower16(entry->id);
-        config_add_entry(config, entry);
-        boot_entry_parse_tries(entry, u"\\EFI\\Linux", filename, u".efi");
+                config_add_entry(config, entry);
+                boot_entry_parse_tries(entry, u"\\EFI\\Linux", filename, u".efi");
 
-        if (!PE_SECTION_VECTOR_IS_SET(sections + SECTION_CMDLINE))
-                return;
+                if (!PE_SECTION_VECTOR_IS_SET(sections + SECTION_CMDLINE))
+                        return;
 
-        content = mfree(content);
+                content = mfree(content);
 
-        /* read the embedded cmdline file */
-        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 = xstrn8_to_16(content, cmdline_len);
-                mangle_stub_cmdline(entry->options);
-                entry->options_implied = true;
+                /* 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;
+                }
         }
 }
 
@@ -2458,10 +2549,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);
@@ -2623,6 +2726,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);
index 10afe38c17689e3c8a9690bb3a2f0b6a7f975df2..e18d59ff604aeb27aa2b485b904ee962793b212c 100644 (file)
@@ -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)
index 653fd2e616684c568bc4c658853aafde7ede189b..2fcde690d91e95f65b0756b0ffdfbde2053751eb 100644 (file)
@@ -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