diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-12 03:50:40 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-12 03:50:40 +0000 |
commit | fc53809803cd2bc2434e312b19a18fa36776da12 (patch) | |
tree | b4b43bd6538f51965ce32856e9c053d0f90919c8 /src/boot | |
parent | Adding upstream version 255.5. (diff) | |
download | systemd-fc53809803cd2bc2434e312b19a18fa36776da12.tar.xz systemd-fc53809803cd2bc2434e312b19a18fa36776da12.zip |
Adding upstream version 256.upstream/256
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/boot')
31 files changed, 740 insertions, 319 deletions
diff --git a/src/boot/bless-boot-generator.c b/src/boot/bless-boot-generator.c index 38b2c3a..cf645e2 100644 --- a/src/boot/bless-boot-generator.c +++ b/src/boot/bless-boot-generator.c @@ -7,7 +7,6 @@ #include "generator.h" #include "initrd-util.h" #include "log.h" -#include "mkdir.h" #include "special.h" #include "string-util.h" #include "virt.h" @@ -17,6 +16,7 @@ * boot as "good" if we manage to boot up far enough. */ static int run(const char *dest, const char *dest_early, const char *dest_late) { + assert(dest_early); if (in_initrd()) { log_debug("Skipping generator, running in the initrd."); @@ -34,7 +34,6 @@ static int run(const char *dest, const char *dest_early, const char *dest_late) } if (access(EFIVAR_PATH(EFI_LOADER_VARIABLE(LoaderBootCountPath)), F_OK) < 0) { - if (errno == ENOENT) { log_debug_errno(errno, "Skipping generator, not booted with boot counting in effect."); return 0; @@ -45,12 +44,7 @@ static int run(const char *dest, const char *dest_early, const char *dest_late) /* We pull this in from basic.target so that it ends up in all "regular" boot ups, but not in * rescue.target or even emergency.target. */ - const char *p = strjoina(dest_early, "/" SPECIAL_BASIC_TARGET ".wants/systemd-bless-boot.service"); - (void) mkdir_parents(p, 0755); - if (symlink(SYSTEM_DATA_UNIT_DIR "/systemd-bless-boot.service", p) < 0) - return log_error_errno(errno, "Failed to create symlink '%s': %m", p); - - return 0; + return generator_add_symlink(dest_early, SPECIAL_BASIC_TARGET, "wants", "systemd-bless-boot.service"); } DEFINE_MAIN_GENERATOR_FUNCTION(run); diff --git a/src/boot/bless-boot.c b/src/boot/bless-boot.c index 0c0b4f2..f86d102 100644 --- a/src/boot/bless-boot.c +++ b/src/boot/bless-boot.c @@ -316,13 +316,6 @@ static int make_bad(const char *prefix, uint64_t done, const char *suffix, char return 0; } -static const char *skip_slash(const char *path) { - assert(path); - assert(path[0] == '/'); - - return path + 1; -} - static int verb_status(int argc, char *argv[], void *userdata) { _cleanup_free_ char *path = NULL, *prefix = NULL, *suffix = NULL, *good = NULL, *bad = NULL; uint64_t left, done; @@ -370,14 +363,14 @@ static int verb_status(int argc, char *argv[], void *userdata) { return log_error_errno(errno, "Failed to open $BOOT partition '%s': %m", *p); } - if (faccessat(fd, skip_slash(path), F_OK, 0) >= 0) { + if (faccessat(fd, skip_leading_slash(path), F_OK, 0) >= 0) { puts("indeterminate"); return 0; } if (errno != ENOENT) return log_error_errno(errno, "Failed to check if '%s' exists: %m", path); - if (faccessat(fd, skip_slash(good), F_OK, 0) >= 0) { + if (faccessat(fd, skip_leading_slash(good), F_OK, 0) >= 0) { puts("good"); return 0; } @@ -385,7 +378,7 @@ static int verb_status(int argc, char *argv[], void *userdata) { if (errno != ENOENT) return log_error_errno(errno, "Failed to check if '%s' exists: %m", good); - if (faccessat(fd, skip_slash(bad), F_OK, 0) >= 0) { + if (faccessat(fd, skip_leading_slash(bad), F_OK, 0) >= 0) { puts("bad"); return 0; } @@ -395,7 +388,7 @@ static int verb_status(int argc, char *argv[], void *userdata) { /* We didn't find any of the three? If so, let's try the next directory, before we give up. */ } - return log_error_errno(SYNTHETIC_ERRNO(EBUSY), "Couldn't determine boot state: %m"); + return log_error_errno(SYNTHETIC_ERRNO(EBUSY), "Couldn't determine boot state."); } static int verb_set(int argc, char *argv[], void *userdata) { @@ -445,17 +438,17 @@ static int verb_set(int argc, char *argv[], void *userdata) { if (fd < 0) return log_error_errno(errno, "Failed to open $BOOT partition '%s': %m", *p); - r = rename_noreplace(fd, skip_slash(source1), fd, skip_slash(target)); + r = rename_noreplace(fd, skip_leading_slash(source1), fd, skip_leading_slash(target)); if (r == -EEXIST) goto exists; if (r == -ENOENT) { - r = rename_noreplace(fd, skip_slash(source2), fd, skip_slash(target)); + r = rename_noreplace(fd, skip_leading_slash(source2), fd, skip_leading_slash(target)); if (r == -EEXIST) goto exists; if (r == -ENOENT) { - if (faccessat(fd, skip_slash(target), F_OK, 0) >= 0) /* Hmm, if we can't find either source file, maybe the destination already exists? */ + if (faccessat(fd, skip_leading_slash(target), F_OK, 0) >= 0) /* Hmm, if we can't find either source file, maybe the destination already exists? */ goto exists; if (errno != ENOENT) @@ -474,9 +467,9 @@ static int verb_set(int argc, char *argv[], void *userdata) { log_debug("Successfully renamed '%s' to '%s'.", source1, target); /* First, fsync() the directory these files are located in */ - r = fsync_parent_at(fd, skip_slash(target)); + r = fsync_parent_at(fd, skip_leading_slash(target)); if (r < 0) - log_debug_errno(errno, "Failed to synchronize image directory, ignoring: %m"); + log_debug_errno(r, "Failed to synchronize image directory, ignoring: %m"); /* Secondly, syncfs() the whole file system these files are located in */ if (syncfs(fd) < 0) @@ -486,8 +479,7 @@ static int verb_set(int argc, char *argv[], void *userdata) { return 0; } - log_error_errno(SYNTHETIC_ERRNO(EBUSY), "Can't find boot counter source file for '%s': %m", target); - return 1; + return log_error_errno(SYNTHETIC_ERRNO(EBUSY), "Can't find boot counter source file for '%s'.", target); exists: log_debug("Operation already executed before, not doing anything."); @@ -506,8 +498,7 @@ static int run(int argc, char *argv[]) { int r; - log_parse_environment(); - log_open(); + log_setup(); r = parse_argv(argc, argv); if (r <= 0) diff --git a/src/boot/boot-check-no-failures.c b/src/boot/boot-check-no-failures.c index 4ff91cb..56c63b7 100644 --- a/src/boot/boot-check-no-failures.c +++ b/src/boot/boot-check-no-failures.c @@ -79,8 +79,7 @@ static int run(int argc, char *argv[]) { uint32_t n; int r; - log_parse_environment(); - log_open(); + log_setup(); r = parse_argv(argc, argv); if (r <= 0) diff --git a/src/boot/bootctl-install.c b/src/boot/bootctl-install.c index bacbbb2..dc46d30 100644 --- a/src/boot/bootctl-install.c +++ b/src/boot/bootctl-install.c @@ -14,6 +14,7 @@ #include "fs-util.h" #include "glyph-util.h" #include "id128-util.h" +#include "kernel-config.h" #include "os-util.h" #include "path-util.h" #include "rm-rf.h" @@ -81,22 +82,22 @@ static int load_etc_machine_info(void) { return 0; } -static int load_etc_kernel_install_conf(void) { - _cleanup_free_ char *layout = NULL, *p = NULL; +static int load_kernel_install_layout(void) { + _cleanup_free_ char *layout = NULL; int r; - p = path_join(arg_root, etc_kernel(), "install.conf"); - if (!p) - return log_oom(); - - r = parse_env_file(NULL, p, "layout", &layout); - if (r == -ENOENT) - return 0; - if (r < 0) - return log_error_errno(r, "Failed to parse %s: %m", p); + r = load_kernel_install_conf(arg_root, + getenv("KERNEL_INSTALL_CONF_ROOT"), + /* ret_machine_id= */ NULL, + /* ret_boot_root= */ NULL, + &layout, + /* ret_initrd_generator= */ NULL, + /* ret_uki_generator= */ NULL); + if (r <= 0) + return r; if (!isempty(layout)) { - log_debug("layout=%s is specified in %s.", layout, p); + log_debug("layout=%s is specified in config.", layout); free_and_replace(arg_install_layout, layout); } @@ -120,7 +121,7 @@ static int settle_make_entry_directory(void) { if (r < 0) return r; - r = load_etc_kernel_install_conf(); + r = load_kernel_install_layout(); if (r < 0) return r; @@ -318,6 +319,46 @@ static int create_subdirs(const char *root, const char * const *subdirs) { return 0; } +static int update_efi_boot_binaries(const char *esp_path, const char *source_path) { + _cleanup_closedir_ DIR *d = NULL; + _cleanup_free_ char *p = NULL; + int r, ret = 0; + + r = chase_and_opendir("/EFI/BOOT", esp_path, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, &p, &d); + if (r == -ENOENT) + return 0; + if (r < 0) + return log_error_errno(r, "Failed to open directory \"%s/EFI/BOOT\": %m", esp_path); + + FOREACH_DIRENT(de, d, break) { + _cleanup_close_ int fd = -EBADF; + _cleanup_free_ char *v = NULL; + + if (!endswith_no_case(de->d_name, ".efi")) + continue; + + fd = openat(dirfd(d), de->d_name, O_RDONLY|O_CLOEXEC); + if (fd < 0) + return log_error_errno(errno, "Failed to open \"%s/%s\" for reading: %m", p, de->d_name); + + r = get_file_version(fd, &v); + if (r == -ESRCH) + continue; /* No version information */ + if (r < 0) + return r; + if (startswith(v, "systemd-boot ")) { + _cleanup_free_ char *dest_path = NULL; + + dest_path = path_join(p, de->d_name); + if (!dest_path) + return log_oom(); + + RET_GATHER(ret, copy_file_with_version_check(source_path, dest_path, /* force = */ false)); + } + } + + return ret; +} static int copy_one_file(const char *esp_path, const char *name, bool force) { char *root = IN_SET(arg_install_source, ARG_INSTALL_SOURCE_AUTO, ARG_INSTALL_SOURCE_IMAGE) ? arg_root : NULL; @@ -371,9 +412,12 @@ static int copy_one_file(const char *esp_path, const char *name, bool force) { if (r < 0) return log_error_errno(r, "Failed to resolve path %s under directory %s: %m", v, esp_path); - r = copy_file_with_version_check(source_path, default_dest_path, force); - if (r < 0 && ret == 0) - ret = r; + RET_GATHER(ret, copy_file_with_version_check(source_path, default_dest_path, force)); + + /* If we were installed under any other name in /EFI/BOOT, make sure we update those binaries + * as well. */ + if (!force) + RET_GATHER(ret, update_efi_boot_binaries(esp_path, source_path)); } return ret; @@ -514,7 +558,7 @@ static int install_entry_token(void) { if (!arg_make_entry_directory && arg_entry_token_type == BOOT_ENTRY_TOKEN_MACHINE_ID) return 0; - p = path_join(arg_root, etc_kernel(), "entry-token"); + p = path_join(arg_root, getenv("KERNEL_INSTALL_CONF_ROOT") ?: "/etc/kernel/", "entry-token"); if (!p) return log_oom(); @@ -845,9 +889,6 @@ static int remove_boot_efi(const char *esp_path) { if (!endswith_no_case(de->d_name, ".efi")) continue; - if (!startswith_no_case(de->d_name, "boot")) - continue; - fd = openat(dirfd(d), de->d_name, O_RDONLY|O_CLOEXEC); if (fd < 0) return log_error_errno(errno, "Failed to open \"%s/%s\" for reading: %m", p, de->d_name); @@ -978,7 +1019,7 @@ static int remove_loader_variables(void) { EFI_LOADER_VARIABLE(LoaderEntryDefault), EFI_LOADER_VARIABLE(LoaderEntryLastBooted), EFI_LOADER_VARIABLE(LoaderEntryOneShot), - EFI_LOADER_VARIABLE(LoaderSystemToken)){ + EFI_LOADER_VARIABLE(LoaderSystemToken)) { int q; diff --git a/src/boot/bootctl-reboot-to-firmware.c b/src/boot/bootctl-reboot-to-firmware.c index 91f2597..cdb04f8 100644 --- a/src/boot/bootctl-reboot-to-firmware.c +++ b/src/boot/bootctl-reboot-to-firmware.c @@ -2,6 +2,7 @@ #include "bootctl-reboot-to-firmware.h" #include "efi-api.h" +#include "errno-util.h" #include "parse-util.h" int verb_reboot_to_firmware(int argc, char *argv[], void *userdata) { @@ -17,7 +18,7 @@ int verb_reboot_to_firmware(int argc, char *argv[], void *userdata) { puts("supported"); return 1; /* recognizable error #1 */ } - if (r == -EOPNOTSUPP) { + if (ERRNO_IS_NEG_NOT_SUPPORTED(r)) { puts("not supported"); return 2; /* recognizable error #2 */ } @@ -36,3 +37,39 @@ int verb_reboot_to_firmware(int argc, char *argv[], void *userdata) { return 0; } } + +int vl_method_set_reboot_to_firmware(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) { + static const JsonDispatch dispatch_table[] = { + { "state", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, 0, 0 }, + {} + }; + bool b; + int r; + + r = varlink_dispatch(link, parameters, dispatch_table, &b); + if (r != 0) + return r; + + r = efi_set_reboot_to_firmware(b); + if (ERRNO_IS_NEG_NOT_SUPPORTED(r)) + return varlink_error(link, "io.systemd.BootControl.RebootToFirmwareNotSupported", NULL); + if (r < 0) + return r; + + return varlink_reply(link, NULL); +} + +int vl_method_get_reboot_to_firmware(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) { + int r; + + if (json_variant_elements(parameters) > 0) + return varlink_error_invalid_parameter(link, parameters); + + r = efi_get_reboot_to_firmware(); + if (ERRNO_IS_NEG_NOT_SUPPORTED(r)) + return varlink_error(link, "io.systemd.BootControl.RebootToFirmwareNotSupported", NULL); + if (r < 0) + return r; + + return varlink_replyb(link, JSON_BUILD_OBJECT(JSON_BUILD_PAIR_BOOLEAN("state", r))); +} diff --git a/src/boot/bootctl-reboot-to-firmware.h b/src/boot/bootctl-reboot-to-firmware.h index 0ca4b2c..fb8a248 100644 --- a/src/boot/bootctl-reboot-to-firmware.h +++ b/src/boot/bootctl-reboot-to-firmware.h @@ -1,3 +1,8 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "varlink.h" + int verb_reboot_to_firmware(int argc, char *argv[], void *userdata); + +int vl_method_set_reboot_to_firmware(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata); +int vl_method_get_reboot_to_firmware(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata); diff --git a/src/boot/bootctl-set-efivar.c b/src/boot/bootctl-set-efivar.c index cb2ed0d..10867b2 100644 --- a/src/boot/bootctl-set-efivar.c +++ b/src/boot/bootctl-set-efivar.c @@ -38,7 +38,7 @@ static int parse_timeout(const char *arg1, char16_t **ret_timeout, size_t *ret_t (void) efi_loader_get_features(&loader_features); if (!(loader_features & EFI_LOADER_FEATURE_MENU_DISABLE)) { if (!arg_graceful) - return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Loader does not support 'menu-disabled': %m"); + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Loader does not support 'menu-disabled'."); log_warning("Loader does not support 'menu-disabled', setting anyway."); } diff --git a/src/boot/bootctl-status.c b/src/boot/bootctl-status.c index d171512..224a1bf 100644 --- a/src/boot/bootctl-status.c +++ b/src/boot/bootctl-status.c @@ -92,6 +92,7 @@ static int status_entries( r = show_boot_entry( boot_config_default_entry(config), + &config->global_addons, /* show_as_default= */ false, /* show_as_selected= */ false, /* show_discovered= */ false); @@ -187,7 +188,6 @@ static int status_variables(void) { static int enumerate_binaries( const char *esp_path, const char *path, - const char *prefix, char **previous, bool *is_first) { @@ -213,9 +213,6 @@ static int enumerate_binaries( if (!endswith_no_case(de->d_name, ".efi")) continue; - if (prefix && !startswith_no_case(de->d_name, prefix)) - continue; - filename = path_join(p, de->d_name); if (!filename) return log_oom(); @@ -272,11 +269,11 @@ static int status_binaries(const char *esp_path, sd_id128_t partition) { printf(" (/dev/disk/by-partuuid/" SD_ID128_UUID_FORMAT_STR ")", SD_ID128_FORMAT_VAL(partition)); printf("\n"); - r = enumerate_binaries(esp_path, "EFI/systemd", NULL, &last, &is_first); + r = enumerate_binaries(esp_path, "EFI/systemd", &last, &is_first); if (r < 0) goto fail; - k = enumerate_binaries(esp_path, "EFI/BOOT", "boot", &last, &is_first); + k = enumerate_binaries(esp_path, "EFI/BOOT", &last, &is_first); if (k < 0) { r = k; goto fail; @@ -321,7 +318,13 @@ int verb_status(int argc, char *argv[], void *userdata) { dev_t esp_devid = 0, xbootldr_devid = 0; int r, k; - r = acquire_esp(/* unprivileged_mode= */ -1, /* graceful= */ false, NULL, NULL, NULL, &esp_uuid, &esp_devid); + r = acquire_esp(/* unprivileged_mode= */ -1, + /* graceful= */ false, + /* ret_part= */ NULL, + /* ret_pstart= */ NULL, + /* ret_psize= */ NULL, + &esp_uuid, + &esp_devid); if (arg_print_esp_path) { if (r == -EACCES) /* If we couldn't acquire the ESP path, log about access errors (which is the only * error the find_esp_and_warn() won't log on its own) */ @@ -333,7 +336,10 @@ int verb_status(int argc, char *argv[], void *userdata) { return 0; } - r = acquire_xbootldr(/* unprivileged_mode= */ -1, &xbootldr_uuid, &xbootldr_devid); + r = acquire_xbootldr( + /* unprivileged_mode= */ -1, + &xbootldr_uuid, + &xbootldr_devid); if (arg_print_dollar_boot_path) { if (r == -EACCES) return log_error_errno(r, "Failed to determine XBOOTLDR partition: %m"); @@ -342,7 +348,7 @@ int verb_status(int argc, char *argv[], void *userdata) { const char *path = arg_dollar_boot_path(); if (!path) - return log_error_errno(SYNTHETIC_ERRNO(EACCES), "Failed to determine XBOOTLDR location: %m"); + return log_error_errno(SYNTHETIC_ERRNO(EACCES), "Failed to determine XBOOTLDR location."); puts(path); return 0; @@ -377,14 +383,15 @@ int verb_status(int argc, char *argv[], void *userdata) { uint64_t flag; const char *name; } stub_flags[] = { - { EFI_STUB_FEATURE_REPORT_BOOT_PARTITION, "Stub sets ESP information" }, - { EFI_STUB_FEATURE_PICK_UP_CREDENTIALS, "Picks up credentials from boot partition" }, - { EFI_STUB_FEATURE_PICK_UP_SYSEXTS, "Picks up system extension images from boot partition" }, - { EFI_STUB_FEATURE_THREE_PCRS, "Measures kernel+command line+sysexts" }, - { EFI_STUB_FEATURE_RANDOM_SEED, "Support for passing random seed to OS" }, - { EFI_STUB_FEATURE_CMDLINE_ADDONS, "Pick up .cmdline from addons" }, - { EFI_STUB_FEATURE_CMDLINE_SMBIOS, "Pick up .cmdline from SMBIOS Type 11" }, - { EFI_STUB_FEATURE_DEVICETREE_ADDONS, "Pick up .dtb from addons" }, + { EFI_STUB_FEATURE_REPORT_BOOT_PARTITION, "Stub sets ESP information" }, + { EFI_STUB_FEATURE_PICK_UP_CREDENTIALS, "Picks up credentials from boot partition" }, + { EFI_STUB_FEATURE_PICK_UP_SYSEXTS, "Picks up system extension images from boot partition" }, + { EFI_STUB_FEATURE_PICK_UP_CONFEXTS, "Picks up configuration extension images from boot partition" }, + { EFI_STUB_FEATURE_THREE_PCRS, "Measures kernel+command line+sysexts" }, + { EFI_STUB_FEATURE_RANDOM_SEED, "Support for passing random seed to OS" }, + { EFI_STUB_FEATURE_CMDLINE_ADDONS, "Pick up .cmdline from addons" }, + { EFI_STUB_FEATURE_CMDLINE_SMBIOS, "Pick up .cmdline from SMBIOS Type 11" }, + { EFI_STUB_FEATURE_DEVICETREE_ADDONS, "Pick up .dtb from addons" }, }; _cleanup_free_ char *fw_type = NULL, *fw_info = NULL, *loader = NULL, *loader_path = NULL, *stub = NULL; sd_id128_t loader_part_uuid = SD_ID128_NULL; @@ -827,3 +834,58 @@ int verb_list(int argc, char *argv[], void *userdata) { int verb_unlink(int argc, char *argv[], void *userdata) { return verb_list(argc, argv, userdata); } + +int vl_method_list_boot_entries(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) { + _cleanup_(boot_config_free) BootConfig config = BOOT_CONFIG_NULL; + dev_t esp_devid = 0, xbootldr_devid = 0; + int r; + + assert(link); + + if (json_variant_elements(parameters) > 0) + return varlink_error_invalid_parameter(link, parameters); + + r = acquire_esp(/* unprivileged_mode= */ false, + /* graceful= */ false, + /* ret_part= */ NULL, + /* ret_pstart= */ NULL, + /* ret_psize= */ NULL, + /* ret_uuid=*/ NULL, + &esp_devid); + if (r == -EACCES) /* We really need the ESP path for this call, hence also log about access errors */ + return log_error_errno(r, "Failed to determine ESP location: %m"); + if (r < 0) + return r; + + r = acquire_xbootldr( + /* unprivileged_mode= */ false, + /* ret_uuid= */ NULL, + &xbootldr_devid); + if (r == -EACCES) + return log_error_errno(r, "Failed to determine XBOOTLDR partition: %m"); + if (r < 0) + return r; + + r = boot_config_load_and_select(&config, arg_esp_path, esp_devid, arg_xbootldr_path, xbootldr_devid); + if (r < 0) + return r; + + _cleanup_(json_variant_unrefp) JsonVariant *previous = NULL; + for (size_t i = 0; i < config.n_entries; i++) { + if (previous) { + r = varlink_notifyb(link, JSON_BUILD_OBJECT( + JSON_BUILD_PAIR_VARIANT("entry", previous))); + if (r < 0) + return r; + + previous = json_variant_unref(previous); + } + + r = boot_entry_to_json(&config, i, &previous); + if (r < 0) + return r; + } + + return varlink_replyb(link, JSON_BUILD_OBJECT( + JSON_BUILD_PAIR_CONDITION(previous, "entry", JSON_BUILD_VARIANT(previous)))); +} diff --git a/src/boot/bootctl-status.h b/src/boot/bootctl-status.h index f7998a3..6fd4365 100644 --- a/src/boot/bootctl-status.h +++ b/src/boot/bootctl-status.h @@ -1,5 +1,9 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "varlink.h" + int verb_status(int argc, char *argv[], void *userdata); int verb_list(int argc, char *argv[], void *userdata); int verb_unlink(int argc, char *argv[], void *userdata); + +int vl_method_list_boot_entries(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata); diff --git a/src/boot/bootctl-util.c b/src/boot/bootctl-util.c index 3cab875..448e868 100644 --- a/src/boot/bootctl-util.c +++ b/src/boot/bootctl-util.c @@ -119,7 +119,7 @@ int settle_entry_token(void) { r = boot_entry_token_ensure( arg_root, - etc_kernel(), + getenv("KERNEL_INSTALL_CONF_ROOT"), arg_machine_id, /* machine_id_is_random = */ false, &arg_entry_token_type, diff --git a/src/boot/bootctl-util.h b/src/boot/bootctl-util.h index 147455e..6f2c163 100644 --- a/src/boot/bootctl-util.h +++ b/src/boot/bootctl-util.h @@ -8,7 +8,3 @@ const char *get_efi_arch(void); int get_file_version(int fd, char **ret); int settle_entry_token(void); - -static inline const char* etc_kernel(void) { - return getenv("KERNEL_INSTALL_CONF_ROOT") ?: "/etc/kernel/"; -} diff --git a/src/boot/bootctl.c b/src/boot/bootctl.c index 4614ca1..b883159 100644 --- a/src/boot/bootctl.c +++ b/src/boot/bootctl.c @@ -22,6 +22,8 @@ #include "parse-argument.h" #include "pretty-print.h" #include "utf8.h" +#include "varlink.h" +#include "varlink-io.systemd.BootControl.h" #include "verbs.h" #include "virt.h" @@ -53,6 +55,7 @@ InstallSource arg_install_source = ARG_INSTALL_SOURCE_AUTO; char *arg_efi_boot_option_description = NULL; bool arg_dry_run = false; ImagePolicy *arg_image_policy = NULL; +bool arg_varlink = false; STATIC_DESTRUCTOR_REGISTER(arg_esp_path, freep); STATIC_DESTRUCTOR_REGISTER(arg_xbootldr_path, freep); @@ -324,7 +327,7 @@ static int parse_argv(int argc, char *argv[]) { break; case 'R': - arg_print_root_device ++; + arg_print_root_device++; break; case ARG_NO_VARIABLES: @@ -418,6 +421,14 @@ static int parse_argv(int argc, char *argv[]) { if (arg_dry_run && argv[optind] && !STR_IN_SET(argv[optind], "unlink", "cleanup")) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--dry is only supported with --unlink or --cleanup"); + r = varlink_invocation(VARLINK_ALLOW_ACCEPT); + if (r < 0) + return log_error_errno(r, "Failed to check if invoked in Varlink mode: %m"); + if (r > 0) { + arg_varlink = true; + arg_pager_flags |= PAGER_DISABLE; + } + return 1; } @@ -462,6 +473,34 @@ static int run(int argc, char *argv[]) { if (r <= 0) return r; + if (arg_varlink) { + _cleanup_(varlink_server_unrefp) VarlinkServer *varlink_server = NULL; + + /* Invocation as Varlink service */ + + r = varlink_server_new(&varlink_server, VARLINK_SERVER_ROOT_ONLY); + if (r < 0) + return log_error_errno(r, "Failed to allocate Varlink server: %m"); + + r = varlink_server_add_interface(varlink_server, &vl_interface_io_systemd_BootControl); + if (r < 0) + return log_error_errno(r, "Failed to add Varlink interface: %m"); + + r = varlink_server_bind_method_many( + varlink_server, + "io.systemd.BootControl.ListBootEntries", vl_method_list_boot_entries, + "io.systemd.BootControl.SetRebootToFirmware", vl_method_set_reboot_to_firmware, + "io.systemd.BootControl.GetRebootToFirmware", vl_method_get_reboot_to_firmware); + if (r < 0) + return log_error_errno(r, "Failed to bind Varlink methods: %m"); + + r = varlink_server_loop_auto(varlink_server); + if (r < 0) + return log_error_errno(r, "Failed to run Varlink event loop: %m"); + + return EXIT_SUCCESS; + } + if (arg_print_root_device > 0) { _cleanup_free_ char *path = NULL; dev_t devno; @@ -498,7 +537,8 @@ static int run(int argc, char *argv[]) { arg_image, arg_image_policy, DISSECT_IMAGE_GENERIC_ROOT | - DISSECT_IMAGE_RELAX_VAR_CHECK, + DISSECT_IMAGE_RELAX_VAR_CHECK | + DISSECT_IMAGE_ALLOW_USERSPACE_VERITY, &mounted_dir, /* ret_dir_fd= */ NULL, &loop_device); diff --git a/src/boot/bootctl.h b/src/boot/bootctl.h index e395b33..25cb516 100644 --- a/src/boot/bootctl.h +++ b/src/boot/bootctl.h @@ -36,6 +36,7 @@ extern InstallSource arg_install_source; extern char *arg_efi_boot_option_description; extern bool arg_dry_run; extern ImagePolicy *arg_image_policy; +extern bool arg_varlink; static inline const char *arg_dollar_boot_path(void) { /* $BOOT shall be the XBOOTLDR partition if it exists, and otherwise the ESP */ diff --git a/src/boot/efi/UEFI_SECURITY.md b/src/boot/efi/UEFI_SECURITY.md index 9f750d8..55e66db 100644 --- a/src/boot/efi/UEFI_SECURITY.md +++ b/src/boot/efi/UEFI_SECURITY.md @@ -9,28 +9,28 @@ stub's role is that of the fundamental entrypoint to kernel execution from UEFI modern Linux boot protocol. `systemd-stub` on the other hand loads various resources, including the kernel image, via the EFI LoadImage/StartImage protocol (although it does support the legacy Linux boot protocol, as a fallback for older kernels on x86). The purpose of `systemd-stub` is to provide additional features and -functionality for either or both `systemd-boot` and `systemd` (userspace). +functionality for `systemd-boot` and `systemd` (in userspace). ## Fundamental Security Design Goals -The fundamental security design goals for these components are separation of security policy logic from the -rest of the functionality, achieved by offloading security-critical tasks to the firmware or earlier stages -of the boot process (e.g.: `Shim`). +The fundamental security design goal for these components is the separation of security policy logic from the +rest of the functionality. This is achieved by offloading security-critical tasks to the firmware or earlier stages +of the boot process (in particular `Shim`). -When SecureBoot is enabled, these components are designed to avoid executing, loading or using +When SecureBoot is enabled, these components are designed to avoid loading, executing, or using unauthenticated payloads that could compromise the boot process, with special care taken for anything that could affect the system before `ExitBootServices()` has been called. For example, when additional resources are loaded, if running with SecureBoot enabled, they will be validated before use. The only exceptions are -the bootloader's own textual configuration files, and parsing metadata out of images for displaying purposes +the bootloader's own textual configuration files, and metadata parsed out of kernel images for display purposes only. There are no build time or runtime configuration options that can be set to weaken the security model of these components when SecureBoot is enabled. The role of `systemd-boot` is to discover next stage components in the ESP (and XBOOTLDR if present), via -filesystem enumeration or explicit configuration files, and present a menu to the user, to choose the next -step. This auto discovery mechanism is described in details in the [BLS (Boot Loader +filesystem enumeration or explicit configuration files, and present a menu to the user to choose the next +step. This auto discovery mechanism is described in detail in the [BLS (Boot Loader Specification)](https://uapi-group.org/specifications/specs/boot_loader_specification/). The role of `systemd-stub` is to load and measure in the TPM the post-bootloader stages, such as the kernel, -initrd and kernel command line, and implement optional features such as augmenting the initrd with +initrd, and kernel command line, and implement optional features such as augmenting the initrd with additional content such as configuration or optional services. [Unified Kernel Images](https://uapi-group.org/specifications/specs/unified_kernel_image/) embed `systemd-stub`, a kernel and other optional components as sections in a PE signed binary, that can thus be executed in UEFI @@ -42,19 +42,19 @@ the image, given that the payload kernel was already authenticated and verified SecureBoot authentication is re-enabled immediately after the kernel image has been loaded. Various EFI variables, under the vendor UUID `4a67b082-0a4c-41cf-b6c7-440b29bb8c4f`, are set and read by -these components, to pass metadata and configuration between different stages of the boot process, as +these components. This is used to pass metadata and configuration between different stages of the boot process, as defined in the [Boot Loader Interface](https://systemd.io/BOOT_LOADER_INTERFACE/). ## Dependencies -Neither of these components implements cryptographic primitives, cryptographic checks or drivers. File +Neither of these components implements cryptographic primitives, cryptographic checks, or drivers. File access to the ESP is implemented solely via the appropriate UEFI file protocols. Verification of next stage -payloads is implementend solely via the appropriate UEFI image load protocols, which means authenticode +payloads is implementend solely via the appropriate UEFI image load protocols, which means `authenticode` signature checks are again done by the firmware or `Shim`. As a consequence, no external security-critical -libraries (such as OpenSSL or gnu-efi) are used, linked or embedded. +libraries (such as OpenSSL or gnu-efi) are linked, embedded, or used. ## Additional Resources BLS Type #1 entries allow the user to load two types of additional resources that can affect the system -before `ExitBootServices()` has been called, kernel command line arguments and Devicetree blobs, that are +before `ExitBootServices()` has been called — kernel command line arguments and Devicetree blobs — that are not validated before use, as they do not carry signatures. For this reason, when SecureBoot is enabled, loading these resources is automatically disabled. There is no override for this security mechanism, neither at build time nor at runtime. Note that initrds are also not verified in BLS Type #1 configurations, for @@ -62,8 +62,9 @@ compatibility with how SecureBoot has been traditionally handled on Linux-based only load them after `ExitBootServices()` has been called. Another mechanism is supported by `systemd-boot` and `systemd-stub` to add additional payloads to the boot -process: `addons`. Addons are PE signed binaries that can carry kernel command line arguments or Devicetree -blobs (more might be added in the future). In contrast to the user-specified additions in the Type #1 case +process: "addons". Addons are PE signed binaries that can carry kernel command line arguments or Devicetree +blobs (more payload types might be added in the future). +In contrast to the user-specified additions in the Type #1 case described above, these addons are loaded through the UEFI image loading protocol, and thus are subject to signature validation, and will be rejected if not signed or if the signature is invalid, following the standard SecureBoot model. They are also measured in the TPM. @@ -72,22 +73,22 @@ standard SecureBoot model. They are also measured in the TPM. firmware's capabilities. These are again PE signed binaries and will be verified using the appropriate UEFI protocol. -A random seed will be loaded and passed to the kernel for early-boot entropy pool filling if found in the -ESP. It is mixed with various other sources of entropy available in the UEFI environment, such as the RNG +A random seed will be loaded and passed to the kernel for early-boot entropy if found in the ESP. +It is mixed with various other sources of entropy available in the UEFI environment, such as the RNG protocol, the boot counter and the clock. Moreover, the seed is updated before the kernel is invoked, as well as after the kernel is invoked (from userspace), with a new seed derived from the Linux kernel entropy pool. When operating as a virtual machine payload, the loaded payloads can be customized via `SMBIOS Type 11 -Strings`, if the hypervisor specifies them. This is automatically disabled if running inside a confidential -computing VM. +Strings`. Those settings are specified by the hypervisor and trusted. +They are automatically disabled if running inside a confidential computing VM. ## Certificates Enrollment -When SecureBoot is supported but in `setup` mode, `systemd-boot` can enroll user certificates if a set of -`PK`, `KEK` and `db` certificates is found in the ESP, after which SecureBoot is enabled and a firmware -reset is performed. When running on bare metal, the certificate(s) will be shown to the user on the console, -and manual confirmation will be asked before proceeding. When running as a virtual machine payload, -enrollment is fully automated, without user interaction, unless disabled via a configuration file in the +When SecureBoot is supported, but in `setup` mode, `systemd-boot` can enroll user certificates if a set of +`PK`, `KEK` and `db` certificates is found in the ESP. Afterwards, SecureBoot is enabled and a firmware +reset is performed. When running on bare metal, the certificates will be shown to the user on the console, +and manual confirmation is required before proceeding. When running as a virtual machine payload, +enrollment is fully automated without user interaction, unless disabled via a configuration file in the ESP. The configuration file can also be used to disable enrollment completely. ## Compiler Hardening @@ -111,10 +112,10 @@ allow customizations of the metadata included in the section, that can be used b The `systemd` project will participate in the coordinated `SBAT` disclosure and metadata revision process as deemed necessary, in coordination with the Shim Review group. -The upstream project name used to be unified (`systemd`) for both components, but since version v255 has +The upstream project name used to be unified (`systemd`) for both components, but since version 255 has been split into separate `systemd-boot` and `systemd-stub` project names, so that each component can be -revisioned independently. Most of the code tend to be shared between these two components, but there is no -complete overlap, so it is possible for a vulnerability to affect only one component but not the other. +revisioned independently. Most of the code tends to be shared between these two components, but the +overlap is not complete, so a future vulnerability may affect only one of the components. ## Known Vulnerabilities There is currently one known (and fixed) security vulnerability affecting `systemd-boot` on arm64 and diff --git a/src/boot/efi/boot.c b/src/boot/efi/boot.c index a3d5607..79de121 100644 --- a/src/boot/efi/boot.c +++ b/src/boot/efi/boot.c @@ -741,20 +741,25 @@ static bool menu_run( lines = xnew(char16_t *, config->n_entries + 1); for (size_t i = 0; i < config->n_entries; i++) { - size_t j, padding; + size_t width = line_width - MIN(strlen16(config->entries[i]->title_show), line_width); + size_t padding = width / 2; + bool odd = width % 2; - lines[i] = xnew(char16_t, line_width + 1); - padding = (line_width - MIN(strlen16(config->entries[i]->title_show), line_width)) / 2; + /* Make sure there is space for => */ + padding = MAX((size_t) 2, padding); - for (j = 0; j < padding; j++) - lines[i][j] = ' '; + size_t print_width = MIN( + strlen16(config->entries[i]->title_show), + line_width - padding * 2); - for (size_t k = 0; config->entries[i]->title_show[k] != '\0' && j < line_width; j++, k++) - lines[i][j] = config->entries[i]->title_show[k]; + assert((padding + 1) <= INT_MAX); + assert(print_width <= INT_MAX); - for (; j < line_width; j++) - lines[i][j] = ' '; - lines[i][line_width] = '\0'; + lines[i] = xasprintf( + "%*ls%.*ls%*ls", + (int) padding, u"", + (int) print_width, config->entries[i]->title_show, + odd ? (int) (padding + 1) : (int) padding, u""); } lines[config->n_entries] = NULL; @@ -2288,7 +2293,7 @@ static EFI_STATUS initrd_prepare( continue; size_t new_size, read_size = info->FileSize; - if (__builtin_add_overflow(size, read_size, &new_size)) + if (!ADD_SAFE(&new_size, size, read_size)) return EFI_OUT_OF_RESOURCES; initrd = xrealloc(initrd, size, new_size); @@ -2369,7 +2374,16 @@ static EFI_STATUS image_start( /* If we had to append an initrd= entry to the command line, we have to pass it, and measure it. * Otherwise, only pass/measure it if it is not implicit anyway (i.e. embedded into the UKI or * so). */ - char16_t *options = options_initrd ?: entry->options_implied ? NULL : entry->options; + _cleanup_free_ char16_t *options = xstrdup16(options_initrd ?: entry->options_implied ? NULL : entry->options); + + if (entry->type == LOADER_LINUX && !is_confidential_vm()) { + 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 (options) { loaded_image->LoadOptions = options; loaded_image->LoadOptionsSize = strsize16(options); @@ -2466,7 +2480,7 @@ static EFI_STATUS secure_boot_discover_keys(Config *config, EFI_FILE *root_dir) EFI_STATUS err; _cleanup_(file_closep) EFI_FILE *keys_basedir = NULL; - if (secure_boot_mode() != SECURE_BOOT_SETUP) + if (!IN_SET(secure_boot_mode(), SECURE_BOOT_SETUP, SECURE_BOOT_AUDIT)) return EFI_SUCCESS; /* the lack of a 'keys' directory is not fatal and is silently ignored */ diff --git a/src/boot/efi/cpio.c b/src/boot/efi/cpio.c index c4f803c..bd1118a 100644 --- a/src/boot/efi/cpio.c +++ b/src/boot/efi/cpio.c @@ -305,6 +305,7 @@ EFI_STATUS pack_cpio( EFI_LOADED_IMAGE_PROTOCOL *loaded_image, const char16_t *dropin_dir, const char16_t *match_suffix, + const char16_t *exclude_suffix, const char *target_dir_prefix, uint32_t dir_mode, uint32_t access_mode, @@ -367,6 +368,8 @@ EFI_STATUS pack_cpio( continue; if (match_suffix && !endswith_no_case(dirent->FileName, match_suffix)) continue; + if (exclude_suffix && endswith_no_case(dirent->FileName, exclude_suffix)) + continue; if (!is_ascii(dirent->FileName)) continue; if (strlen16(dirent->FileName) > 255) /* Max filename size on Linux */ diff --git a/src/boot/efi/cpio.h b/src/boot/efi/cpio.h index 26851e3..9d14fa1 100644 --- a/src/boot/efi/cpio.h +++ b/src/boot/efi/cpio.h @@ -8,6 +8,7 @@ EFI_STATUS pack_cpio( EFI_LOADED_IMAGE_PROTOCOL *loaded_image, const char16_t *dropin_dir, const char16_t *match_suffix, + const char16_t *exclude_suffix, const char *target_dir_prefix, uint32_t dir_mode, uint32_t access_mode, diff --git a/src/boot/efi/efi-string.c b/src/boot/efi/efi-string.c index 4144c0d..3bdb802 100644 --- a/src/boot/efi/efi-string.c +++ b/src/boot/efi/efi-string.c @@ -372,9 +372,9 @@ bool efi_fnmatch(const char16_t *pattern, const char16_t *haystack) { \ uint64_t u = 0; \ while (*s >= '0' && *s <= '9') { \ - if (__builtin_mul_overflow(u, 10, &u)) \ + if (!MUL_ASSIGN_SAFE(&u, 10)) \ return false; \ - if (__builtin_add_overflow(u, *s - '0', &u)) \ + if (!INC_SAFE(&u, *s - '0')) \ return false; \ s++; \ } \ @@ -593,13 +593,13 @@ typedef struct { static void grow_buf(FormatContext *ctx, size_t need) { assert(ctx); - assert_se(!__builtin_add_overflow(ctx->n, need, &need)); + assert_se(INC_SAFE(&need, ctx->n)); if (need < ctx->n_buf) return; /* Greedily allocate if we can. */ - if (__builtin_mul_overflow(need, 2, &ctx->n_buf)) + if (!MUL_SAFE(&ctx->n_buf, need, 2)) ctx->n_buf = need; /* We cannot use realloc here as ctx->buf may be ctx->stack_buf, which we cannot free. */ diff --git a/src/boot/efi/efi.h b/src/boot/efi/efi.h index fbc5d10..e8217c1 100644 --- a/src/boot/efi/efi.h +++ b/src/boot/efi/efi.h @@ -140,6 +140,8 @@ typedef struct { GUID_DEF(0x8be4df61, 0x93ca, 0x11d2, 0xaa, 0x0d, 0x00, 0xe0, 0x98, 0x03, 0x2b, 0x8c) #define EFI_IMAGE_SECURITY_DATABASE_GUID \ GUID_DEF(0xd719b2cb, 0x3d3a, 0x4596, 0xa3, 0xbc, 0xda, 0xd0, 0x0e, 0x67, 0x65, 0x6f) +#define EFI_CUSTOM_MODE_ENABLE_GUID \ + GUID_DEF(0xc076ec0c, 0x7028, 0x4399, 0xa0, 0x72, 0x71, 0xee, 0x5c, 0x44, 0x8b, 0x9f) #define EVT_TIMER 0x80000000U #define EVT_RUNTIME 0x40000000U diff --git a/src/boot/efi/measure.c b/src/boot/efi/measure.c index 01c97c8..08a2ecd 100644 --- a/src/boot/efi/measure.c +++ b/src/boot/efi/measure.c @@ -5,43 +5,11 @@ #include "macro-fundamental.h" #include "measure.h" #include "memory-util-fundamental.h" +#include "proto/cc-measurement.h" #include "proto/tcg.h" #include "tpm2-pcr.h" #include "util.h" -static EFI_STATUS tpm1_measure_to_pcr_and_event_log( - const EFI_TCG_PROTOCOL *tcg, - uint32_t pcrindex, - EFI_PHYSICAL_ADDRESS buffer, - size_t buffer_size, - const char16_t *description) { - - _cleanup_free_ TCG_PCR_EVENT *tcg_event = NULL; - EFI_PHYSICAL_ADDRESS event_log_last; - uint32_t event_number = 1; - size_t desc_len; - - assert(tcg); - assert(description); - - desc_len = strsize16(description); - tcg_event = xmalloc(offsetof(TCG_PCR_EVENT, Event) + desc_len); - *tcg_event = (TCG_PCR_EVENT) { - .EventSize = desc_len, - .PCRIndex = pcrindex, - .EventType = EV_IPL, - }; - memcpy(tcg_event->Event, description, desc_len); - - return tcg->HashLogExtendEvent( - (EFI_TCG_PROTOCOL *) tcg, - buffer, buffer_size, - TCG_ALG_SHA, - tcg_event, - &event_number, - &event_log_last); -} - static EFI_STATUS tpm2_measure_to_pcr_and_tagged_event_log( EFI_TCG2_PROTOCOL *tcg, uint32_t pcrindex, @@ -123,35 +91,67 @@ static EFI_STATUS tpm2_measure_to_pcr_and_event_log( tcg_event); } -static EFI_TCG_PROTOCOL *tcg1_interface_check(void) { - EFI_PHYSICAL_ADDRESS event_log_location, event_log_last_entry; - EFI_TCG_BOOT_SERVICE_CAPABILITY capability = { +static EFI_STATUS cc_measure_to_mr_and_event_log( + EFI_CC_MEASUREMENT_PROTOCOL *cc, + uint32_t pcrindex, + EFI_PHYSICAL_ADDRESS buffer, + uint64_t buffer_size, + const char16_t *description) { + + _cleanup_free_ EFI_CC_EVENT *event = NULL; + uint32_t mr; + EFI_STATUS err; + size_t desc_len; + + assert(cc); + assert(description); + + /* MapPcrToMrIndex service provides callers information on + * how the TPM PCR registers are mapped to the CC measurement + * registers (MR) in the vendor implementation. */ + err = cc->MapPcrToMrIndex(cc, pcrindex, &mr); + if (err != EFI_SUCCESS) + return EFI_NOT_FOUND; + + desc_len = strsize16(description); + event = xmalloc(offsetof(EFI_CC_EVENT, Event) + desc_len); + *event = (EFI_CC_EVENT) { + .Size = offsetof(EFI_CC_EVENT, Event) + desc_len, + .Header.HeaderSize = sizeof(EFI_CC_EVENT_HEADER), + .Header.HeaderVersion = EFI_CC_EVENT_HEADER_VERSION, + .Header.MrIndex = mr, + .Header.EventType = EV_IPL, + }; + + memcpy(event->Event, description, desc_len); + + return cc->HashLogExtendEvent( + cc, + 0, + buffer, + buffer_size, + event); +} + +static EFI_CC_MEASUREMENT_PROTOCOL *cc_interface_check(void) { + EFI_CC_BOOT_SERVICE_CAPABILITY capability = { .Size = sizeof(capability), }; EFI_STATUS err; - uint32_t features; - EFI_TCG_PROTOCOL *tcg; + EFI_CC_MEASUREMENT_PROTOCOL *cc; - err = BS->LocateProtocol(MAKE_GUID_PTR(EFI_TCG_PROTOCOL), NULL, (void **) &tcg); + err = BS->LocateProtocol(MAKE_GUID_PTR(EFI_CC_MEASUREMENT_PROTOCOL), NULL, (void **) &cc); if (err != EFI_SUCCESS) return NULL; - err = tcg->StatusCheck( - tcg, - &capability, - &features, - &event_log_location, - &event_log_last_entry); + err = cc->GetCapability(cc, &capability); if (err != EFI_SUCCESS) return NULL; - if (capability.TPMDeactivatedFlag) + if (!(capability.SupportedEventLogs & EFI_CC_EVENT_LOG_FORMAT_TCG_2)) return NULL; - if (!capability.TPMPresentFlag) - return NULL; - - return tcg; + return cc; } static EFI_TCG2_PROTOCOL *tcg2_interface_check(void) { @@ -184,12 +184,42 @@ static EFI_TCG2_PROTOCOL *tcg2_interface_check(void) { } bool tpm_present(void) { - return tcg2_interface_check() || tcg1_interface_check(); + return tcg2_interface_check(); } -EFI_STATUS tpm_log_event(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, size_t buffer_size, const char16_t *description, bool *ret_measured) { +static EFI_STATUS tcg2_log_event(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, size_t buffer_size, const char16_t *description, bool *ret_measured) { EFI_TCG2_PROTOCOL *tpm2; + EFI_STATUS err = EFI_SUCCESS; + + assert(ret_measured); + + tpm2 = tcg2_interface_check(); + if (tpm2) + err = tpm2_measure_to_pcr_and_event_log(tpm2, pcrindex, buffer, buffer_size, description); + + *ret_measured = tpm2 && (err == EFI_SUCCESS); + + return err; +} + +static EFI_STATUS cc_log_event(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, size_t buffer_size, const char16_t *description, bool *ret_measured) { + EFI_CC_MEASUREMENT_PROTOCOL *cc; + EFI_STATUS err = EFI_SUCCESS; + + assert(ret_measured); + + cc = cc_interface_check(); + if (cc) + err = cc_measure_to_mr_and_event_log(cc, pcrindex, buffer, buffer_size, description); + + *ret_measured = cc && (err == EFI_SUCCESS); + + return err; +} + +EFI_STATUS tpm_log_event(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, size_t buffer_size, const char16_t *description, bool *ret_measured) { EFI_STATUS err; + bool tpm_ret_measured, cc_ret_measured; assert(description || pcrindex == UINT32_MAX); @@ -203,27 +233,15 @@ EFI_STATUS tpm_log_event(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, size_t return EFI_SUCCESS; } - tpm2 = tcg2_interface_check(); - if (tpm2) - err = tpm2_measure_to_pcr_and_event_log(tpm2, pcrindex, buffer, buffer_size, description); - else { - EFI_TCG_PROTOCOL *tpm1; - - tpm1 = tcg1_interface_check(); - if (tpm1) - err = tpm1_measure_to_pcr_and_event_log(tpm1, pcrindex, buffer, buffer_size, description); - else { - /* No active TPM found, so don't return an error */ - - if (ret_measured) - *ret_measured = false; + /* Measure into both CC and TPM if both are available to avoid a problem like CVE-2021-42299 */ + err = cc_log_event(pcrindex, buffer, buffer_size, description, &cc_ret_measured); + if (err != EFI_SUCCESS) + return err; - return EFI_SUCCESS; - } - } + err = tcg2_log_event(pcrindex, buffer, buffer_size, description, &tpm_ret_measured); if (err == EFI_SUCCESS && ret_measured) - *ret_measured = true; + *ret_measured = tpm_ret_measured || cc_ret_measured; return err; } diff --git a/src/boot/efi/meson.build b/src/boot/efi/meson.build index 43727ef..7a60b0e 100644 --- a/src/boot/efi/meson.build +++ b/src/boot/efi/meson.build @@ -28,7 +28,7 @@ efi_fuzz_template = fuzz_template + efitest_base executables += [ efi_test_template + { 'sources' : files('test-bcd.c'), - 'dependencies' : libzstd, + 'dependencies' : libzstd_cflags, 'conditions' : ['ENABLE_BOOTLOADER', 'HAVE_ZSTD'], }, efi_test_template + { @@ -67,7 +67,8 @@ if meson.is_cross_build() and get_option('sbat-distro') == 'auto' warning('Auto detection of SBAT information not supported when cross-building, disabling SBAT.') elif get_option('sbat-distro') != '' efi_conf.set_quoted('SBAT_PROJECT', meson.project_name()) - efi_conf.set_quoted('PROJECT_VERSION', meson.project_version()) + efi_conf.set_quoted('PROJECT_VERSION', meson.project_version().split('~')[0]) + efi_conf.set_quoted('VERSION_TAG', version_tag) efi_conf.set('PROJECT_URL', conf.get('PROJECT_URL')) if get_option('sbat-distro-generation') < 1 error('SBAT Distro Generation must be a positive integer') @@ -209,11 +210,13 @@ if cc.get_id() == 'clang' endif efi_arch_c_args = { - 'aarch64' : ['-mgeneral-regs-only'], - 'arm' : ['-mgeneral-regs-only'], + 'aarch64' : ['-mgeneral-regs-only'], + 'arm' : ['-mgeneral-regs-only'], + # Until -mgeneral-regs-only is supported in LoongArch, use the following option instead: + 'loongarch64' : ['-mno-lsx', '-mno-lasx'], # Pass -m64/32 explicitly to make building on x32 work. - 'x86_64' : ['-m64', '-march=x86-64', '-mno-red-zone', '-mgeneral-regs-only'], - 'x86' : ['-m32', '-march=i686', '-mgeneral-regs-only', '-malign-double'], + 'x86_64' : ['-m64', '-march=x86-64', '-mno-red-zone', '-mgeneral-regs-only'], + 'x86' : ['-m32', '-march=i686', '-mgeneral-regs-only', '-malign-double'], } efi_arch_c_ld_args = { # libgcc is not compiled with -fshort-wchar, but it does not use it anyways, @@ -385,7 +388,7 @@ foreach efi_elf_binary : efi_elf_binaries install_tag : 'systemd-boot', command : [ elf2efi_py, - '--version-major=' + meson.project_version(), + '--version-major=' + meson.project_version().split('~')[0], '--version-minor=0', '--efi-major=1', '--efi-minor=1', diff --git a/src/boot/efi/part-discovery.c b/src/boot/efi/part-discovery.c index f5b1573..e66e2da 100644 --- a/src/boot/efi/part-discovery.c +++ b/src/boot/efi/part-discovery.c @@ -232,7 +232,7 @@ static EFI_STATUS find_device(const EFI_GUID *type, EFI_HANDLE *device, EFI_DEVI } /* Patch in the data we found */ - *ret_device_path = device_path_replace_node(partition_path, part_node, (EFI_DEVICE_PATH *) &hd); + *ret_device_path = device_path_replace_node(partition_path, part_node, &hd.Header); return EFI_SUCCESS; } diff --git a/src/boot/efi/proto/cc-measurement.h b/src/boot/efi/proto/cc-measurement.h new file mode 100644 index 0000000..9335ecf --- /dev/null +++ b/src/boot/efi/proto/cc-measurement.h @@ -0,0 +1,67 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "efi.h" + +/* The UEFI specification defines the interface between the confidential virtual guest OS and + * virtual firmware as EFI_CC_MEASUREMENT_PROTOCOL. The measurements are captured in the CC eventlog + * that follows the TCG2 format. TPM PCR registers are mapped to vendor specific measurement registers + * and the mapping can be queried using MapPcrToMrIndex service as part of the protocol. + * + * The "Confidential Computing" section in the UEFI specification covers the details. */ + +#define EFI_CC_MEASUREMENT_PROTOCOL_GUID \ + GUID_DEF(0x96751a3d, 0x72f4, 0x41a6, 0xa7, 0x94, 0xed, 0x5d, 0x0e, 0x67, 0xae, 0x6b) + +#define EFI_CC_EVENT_HEADER_VERSION 1 +#define EFI_CC_EVENT_LOG_FORMAT_TCG_2 0x00000002 + +typedef struct { + uint8_t Type; + uint8_t SubType; +} EFI_CC_TYPE; + +typedef struct { + uint8_t Major; + uint8_t Minor; +} EFI_CC_VERSION; + +typedef struct { + uint8_t Size; + EFI_CC_VERSION StructureVersion; + EFI_CC_VERSION ProtocolVersion; + uint32_t HashAlgorithmBitmap; + uint32_t SupportedEventLogs; + EFI_CC_TYPE CcType; +} EFI_CC_BOOT_SERVICE_CAPABILITY; + +typedef struct { + uint32_t HeaderSize; + uint16_t HeaderVersion; + uint32_t MrIndex; + uint32_t EventType; +} _packed_ EFI_CC_EVENT_HEADER; + +typedef struct { + uint32_t Size; + EFI_CC_EVENT_HEADER Header; + uint8_t Event[]; +} _packed_ EFI_CC_EVENT; + +typedef struct EFI_CC_MEASUREMENT_PROTOCOL EFI_CC_MEASUREMENT_PROTOCOL; +struct EFI_CC_MEASUREMENT_PROTOCOL { + EFI_STATUS (EFIAPI *GetCapability)( + EFI_CC_MEASUREMENT_PROTOCOL *This, + EFI_CC_BOOT_SERVICE_CAPABILITY *ProtocolCapability); + void *GetEventLog; + EFI_STATUS (EFIAPI *HashLogExtendEvent)( + EFI_CC_MEASUREMENT_PROTOCOL *This, + uint64_t Flags, + EFI_PHYSICAL_ADDRESS DataToHash, + uint64_t DataToHashLen, + EFI_CC_EVENT *EfiCcEvent); + EFI_STATUS (EFIAPI *MapPcrToMrIndex)( + EFI_CC_MEASUREMENT_PROTOCOL *This, + uint32_t PcrIndex, + uint32_t *MrIndex); +}; diff --git a/src/boot/efi/proto/tcg.h b/src/boot/efi/proto/tcg.h index b4b8296..e243bf8 100644 --- a/src/boot/efi/proto/tcg.h +++ b/src/boot/efi/proto/tcg.h @@ -3,12 +3,9 @@ #include "efi.h" -#define EFI_TCG_PROTOCOL_GUID \ - GUID_DEF(0xf541796d, 0xa62e, 0x4954, 0xa7, 0x75, 0x95, 0x84, 0xf6, 0x1b, 0x9c, 0xdd) #define EFI_TCG2_PROTOCOL_GUID \ GUID_DEF(0x607f766c, 0x7455, 0x42be, 0x93, 0x0b, 0xe4, 0xd7, 0x6d, 0xb2, 0x72, 0x0f) -#define TCG_ALG_SHA 0x4 #define EFI_TCG2_EVENT_HEADER_VERSION 1 #define EV_IPL 13 #define EV_EVENT_TAG UINT32_C(6) @@ -49,16 +46,6 @@ typedef struct { } EFI_TCG2_BOOT_SERVICE_CAPABILITY; typedef struct { - uint32_t PCRIndex; - uint32_t EventType; - struct { - uint8_t Digest[20]; - } Digest; - uint32_t EventSize; - uint8_t Event[]; -} _packed_ TCG_PCR_EVENT; - -typedef struct { uint32_t HeaderSize; uint16_t HeaderVersion; uint32_t PCRIndex; @@ -77,27 +64,6 @@ typedef struct { uint8_t Event[]; } _packed_ EFI_TCG2_TAGGED_EVENT; -typedef struct EFI_TCG_PROTOCOL EFI_TCG_PROTOCOL; -struct EFI_TCG_PROTOCOL { - EFI_STATUS (EFIAPI *StatusCheck)( - EFI_TCG_PROTOCOL *This, - EFI_TCG_BOOT_SERVICE_CAPABILITY *ProtocolCapability, - uint32_t *TCGFeatureFlags, - EFI_PHYSICAL_ADDRESS *EventLogLocation, - EFI_PHYSICAL_ADDRESS *EventLogLastEntry); - void *HashAll; - void *LogEvent; - void *PassThroughToTpm; - EFI_STATUS (EFIAPI *HashLogExtendEvent)( - EFI_TCG_PROTOCOL *This, - EFI_PHYSICAL_ADDRESS HashData, - uint64_t HashDataLen, - uint32_t AlgorithmId, - TCG_PCR_EVENT *TCGLogData, - uint32_t *EventNumber, - EFI_PHYSICAL_ADDRESS *EventLogLastEntry); -}; - typedef struct EFI_TCG2_PROTOCOL EFI_TCG2_PROTOCOL; struct EFI_TCG2_PROTOCOL { EFI_STATUS (EFIAPI *GetCapability)( diff --git a/src/boot/efi/random-seed.c b/src/boot/efi/random-seed.c index 8147e54..03f3ebd 100644 --- a/src/boot/efi/random-seed.c +++ b/src/boot/efi/random-seed.c @@ -4,7 +4,7 @@ #include "proto/rng.h" #include "random-seed.h" #include "secure-boot.h" -#include "sha256.h" +#include "sha256-fundamental.h" #include "util.h" #define RANDOM_MAX_SIZE_MIN (32U) diff --git a/src/boot/efi/secure-boot.c b/src/boot/efi/secure-boot.c index f76d2f4..2400324 100644 --- a/src/boot/efi/secure-boot.c +++ b/src/boot/efi/secure-boot.c @@ -32,10 +32,46 @@ SecureBootMode secure_boot_mode(void) { return decode_secure_boot_mode(secure, audit, deployed, setup); } +/* + * Custom mode allows the secure boot certificate databases db, dbx, KEK, and PK to be changed without the variable + * updates being signed. When enrolling certificates to an unconfigured system (no PK present yet) writing + * db, dbx and KEK updates without signature works fine even in standard mode. Writing PK updates without + * signature requires custom mode in any case. + * + * Enabling custom mode works only if a user is physically present. Note that OVMF has a dummy + * implementation for the user presence check (there is no useful way to implement a presence check for a + * virtual machine). + * + * FYI: Your firmware setup utility might offers the option to enroll certificates from *.crt files + * (DER-encoded x509 certificates) on the ESP; that uses custom mode too. Your firmware setup might also + * offer the option to switch the system into custom mode for the next boot. + */ +static bool custom_mode_enabled(void) { + bool enabled = false; + + (void) efivar_get_boolean_u8(MAKE_GUID_PTR(EFI_CUSTOM_MODE_ENABLE), + u"CustomMode", &enabled); + return enabled; +} + +static EFI_STATUS set_custom_mode(bool enable) { + static char16_t name[] = u"CustomMode"; + static uint32_t attr = + EFI_VARIABLE_NON_VOLATILE | + EFI_VARIABLE_BOOTSERVICE_ACCESS; + uint8_t mode = enable + ? 1 /* CUSTOM_SECURE_BOOT_MODE */ + : 0; /* STANDARD_SECURE_BOOT_MODE */ + + return RT->SetVariable(name, MAKE_GUID_PTR(EFI_CUSTOM_MODE_ENABLE), + attr, sizeof(mode), &mode); +} + EFI_STATUS secure_boot_enroll_at(EFI_FILE *root_dir, const char16_t *path, bool force) { assert(root_dir); assert(path); + bool need_custom_mode = false; EFI_STATUS err; clear_screen(COLOR_NORMAL); @@ -88,21 +124,47 @@ EFI_STATUS secure_boot_enroll_at(EFI_FILE *root_dir, const char16_t *path, bool const char16_t *name; const char16_t *filename; const EFI_GUID vendor; + bool required; char *buffer; size_t size; } sb_vars[] = { - { u"db", u"db.auth", EFI_IMAGE_SECURITY_DATABASE_GUID, NULL, 0 }, - { u"KEK", u"KEK.auth", EFI_GLOBAL_VARIABLE, NULL, 0 }, - { u"PK", u"PK.auth", EFI_GLOBAL_VARIABLE, NULL, 0 }, + { u"db", u"db.auth", EFI_IMAGE_SECURITY_DATABASE_GUID, true }, + { u"dbx", u"dbx.auth", EFI_IMAGE_SECURITY_DATABASE_GUID, false }, + { u"KEK", u"KEK.auth", EFI_GLOBAL_VARIABLE, true }, + { u"PK", u"PK.auth", EFI_GLOBAL_VARIABLE, true }, }; /* Make sure all keys files exist before we start enrolling them by loading them from the disk first. */ for (size_t i = 0; i < ELEMENTSOF(sb_vars); i++) { err = file_read(dir, sb_vars[i].filename, 0, 0, &sb_vars[i].buffer, &sb_vars[i].size); - if (err != EFI_SUCCESS) { + if (err != EFI_SUCCESS && sb_vars[i].required) { log_error_status(err, "Failed reading file %ls\\%ls: %m", path, sb_vars[i].filename); goto out_deallocate; } + if (streq16(sb_vars[i].name, u"PK") && sb_vars[i].size > 20) { + assert(sb_vars[i].buffer); + /* + * The buffer should be EFI_TIME (16 bytes), followed by + * EFI_VARIABLE_AUTHENTICATION_2 header. First header field is the size. If the + * size covers only the header itself (8 bytes) plus the signature type guid (16 + * bytes), leaving no space for an actual signature, we can conclude that no + * signature is present. + */ + uint32_t *sigsize = (uint32_t*)(sb_vars[i].buffer + 16); + if (*sigsize <= 24) { + printf("PK is not signed (need custom mode).\n"); + need_custom_mode = true; + } + } + } + + if (need_custom_mode && !custom_mode_enabled()) { + err = set_custom_mode(/* enable */ true); + if (err != EFI_SUCCESS) { + log_error_status(err, "Failed to enable custom mode: %m"); + goto out_deallocate; + } + printf("Custom mode enabled.\n"); } for (size_t i = 0; i < ELEMENTSOF(sb_vars); i++) { @@ -112,6 +174,9 @@ EFI_STATUS secure_boot_enroll_at(EFI_FILE *root_dir, const char16_t *path, bool EFI_VARIABLE_RUNTIME_ACCESS | EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS; + if (!sb_vars[i].buffer) + continue; + err = efivar_set_raw(&sb_vars[i].vendor, sb_vars[i].name, sb_vars[i].buffer, sb_vars[i].size, sb_vars_opts); if (err != EFI_SUCCESS) { log_error_status(err, "Failed to write %ls secure boot variable: %m", sb_vars[i].name); diff --git a/src/boot/efi/stub.c b/src/boot/efi/stub.c index 0d9df7e..9aa605b 100644 --- a/src/boot/efi/stub.c +++ b/src/boot/efi/stub.c @@ -26,28 +26,27 @@ DECLARE_NOALLOC_SECTION(".sdmagic", "#### LoaderInfo: systemd-stub " GIT_VERSION DECLARE_SBAT(SBAT_STUB_SECTION_TEXT); -static EFI_STATUS combine_initrd( - EFI_PHYSICAL_ADDRESS initrd_base, size_t initrd_size, - const void * const extra_initrds[], const size_t extra_initrd_sizes[], size_t n_extra_initrds, +/* Combine initrds by concatenation in memory */ +static EFI_STATUS combine_initrds( + const void * const initrds[], const size_t initrd_sizes[], size_t n_initrds, Pages *ret_initr_pages, size_t *ret_initrd_size) { - size_t n; + size_t n = 0; assert(ret_initr_pages); assert(ret_initrd_size); - /* Combines four initrds into one, by simple concatenation in memory */ - - n = ALIGN4(initrd_size); /* main initrd might not be padded yet */ - - for (size_t i = 0; i < n_extra_initrds; i++) { - if (!extra_initrds[i]) + for (size_t i = 0; i < n_initrds; i++) { + if (!initrds[i]) continue; - if (n > SIZE_MAX - extra_initrd_sizes[i]) + /* some initrds (the ones from UKI sections) need padding, + * pad all to be safe */ + size_t initrd_size = ALIGN4(initrd_sizes[i]); + if (n > SIZE_MAX - initrd_size) return EFI_OUT_OF_RESOURCES; - n += extra_initrd_sizes[i]; + n += initrd_size; } _cleanup_pages_ Pages pages = xmalloc_pages( @@ -56,27 +55,21 @@ static EFI_STATUS combine_initrd( EFI_SIZE_TO_PAGES(n), UINT32_MAX /* Below 4G boundary. */); uint8_t *p = PHYSICAL_ADDRESS_TO_POINTER(pages.addr); - if (initrd_base != 0) { + for (size_t i = 0; i < n_initrds; i++) { + if (!initrds[i]) + continue; + size_t pad; - /* Order matters, the real initrd must come first, since it might include microcode updates - * which the kernel only looks for in the first cpio archive */ - p = mempcpy(p, PHYSICAL_ADDRESS_TO_POINTER(initrd_base), initrd_size); + p = mempcpy(p, initrds[i], initrd_sizes[i]); - pad = ALIGN4(initrd_size) - initrd_size; + pad = ALIGN4(initrd_sizes[i]) - initrd_sizes[i]; if (pad > 0) { memzero(p, pad); p += pad; } } - for (size_t i = 0; i < n_extra_initrds; i++) { - if (!extra_initrds[i]) - continue; - - p = mempcpy(p, extra_initrds[i], extra_initrd_sizes[i]); - } - assert(PHYSICAL_ADDRESS_TO_POINTER(pages.addr + n) == p); *ret_initr_pages = pages; @@ -91,6 +84,7 @@ static void export_variables(EFI_LOADED_IMAGE_PROTOCOL *loaded_image) { EFI_STUB_FEATURE_REPORT_BOOT_PARTITION | /* We set LoaderDevicePartUUID */ EFI_STUB_FEATURE_PICK_UP_CREDENTIALS | /* We pick up credentials from the boot partition */ EFI_STUB_FEATURE_PICK_UP_SYSEXTS | /* We pick up system extensions from the boot partition */ + EFI_STUB_FEATURE_PICK_UP_CONFEXTS | /* We pick up configuration extensions from the boot partition */ EFI_STUB_FEATURE_THREE_PCRS | /* We can measure kernel image, parameters and sysext */ EFI_STUB_FEATURE_RANDOM_SEED | /* We pass a random seed to the kernel */ EFI_STUB_FEATURE_CMDLINE_ADDONS | /* We pick up .cmdline addons */ @@ -497,20 +491,20 @@ static EFI_STATUS load_addons( } static EFI_STATUS run(EFI_HANDLE image) { - _cleanup_free_ void *credential_initrd = NULL, *global_credential_initrd = NULL, *sysext_initrd = NULL, *pcrsig_initrd = NULL, *pcrpkey_initrd = NULL; - size_t credential_initrd_size = 0, global_credential_initrd_size = 0, sysext_initrd_size = 0, pcrsig_initrd_size = 0, pcrpkey_initrd_size = 0; + _cleanup_free_ void *credential_initrd = NULL, *global_credential_initrd = NULL, *sysext_initrd = NULL, *confext_initrd = NULL, *pcrsig_initrd = NULL, *pcrpkey_initrd = NULL; + size_t credential_initrd_size = 0, global_credential_initrd_size = 0, sysext_initrd_size = 0, confext_initrd_size = 0, pcrsig_initrd_size = 0, pcrpkey_initrd_size = 0; void **dt_bases_addons_global = NULL, **dt_bases_addons_uki = NULL; char16_t **dt_filenames_addons_global = NULL, **dt_filenames_addons_uki = NULL; _cleanup_free_ size_t *dt_sizes_addons_global = NULL, *dt_sizes_addons_uki = NULL; - size_t linux_size, initrd_size, dt_size, n_dts_addons_global = 0, n_dts_addons_uki = 0; - EFI_PHYSICAL_ADDRESS linux_base, initrd_base, dt_base; + size_t linux_size, initrd_size, ucode_size, dt_size, n_dts_addons_global = 0, n_dts_addons_uki = 0; + EFI_PHYSICAL_ADDRESS linux_base, initrd_base, ucode_base, dt_base; _cleanup_(devicetree_cleanup) struct devicetree_state dt_state = {}; EFI_LOADED_IMAGE_PROTOCOL *loaded_image; size_t addrs[_UNIFIED_SECTION_MAX] = {}, szs[_UNIFIED_SECTION_MAX] = {}; _cleanup_free_ char16_t *cmdline = NULL, *cmdline_addons_global = NULL, *cmdline_addons_uki = NULL; int sections_measured = -1, parameters_measured = -1; _cleanup_free_ char *uname = NULL; - bool sysext_measured = false, m; + bool sysext_measured = false, confext_measured = false, m; uint64_t loader_features = 0; EFI_STATUS err; @@ -660,8 +654,9 @@ static EFI_STATUS run(EFI_HANDLE image) { export_variables(loaded_image); if (pack_cpio(loaded_image, - NULL, + /* dropin_dir= */ NULL, u".cred", + /* exclude_suffix= */ NULL, ".extra/credentials", /* dir_mode= */ 0500, /* access_mode= */ 0400, @@ -675,6 +670,7 @@ static EFI_STATUS run(EFI_HANDLE image) { if (pack_cpio(loaded_image, u"\\loader\\credentials", u".cred", + /* exclude_suffix= */ NULL, ".extra/global_credentials", /* dir_mode= */ 0500, /* access_mode= */ 0400, @@ -686,8 +682,9 @@ static EFI_STATUS run(EFI_HANDLE image) { parameters_measured = parameters_measured < 0 ? m : (parameters_measured && m); if (pack_cpio(loaded_image, - NULL, - u".raw", + /* dropin_dir= */ NULL, + u".raw", /* ideally we'd pick up only *.sysext.raw here, but for compat we pick up *.raw instead … */ + u".confext.raw", /* … but then exclude *.confext.raw again */ ".extra/sysext", /* dir_mode= */ 0555, /* access_mode= */ 0444, @@ -698,6 +695,20 @@ static EFI_STATUS run(EFI_HANDLE image) { &m) == EFI_SUCCESS) sysext_measured = m; + if (pack_cpio(loaded_image, + /* dropin_dir= */ NULL, + u".confext.raw", + /* exclude_suffix= */ NULL, + ".extra/confext", + /* dir_mode= */ 0555, + /* access_mode= */ 0444, + /* tpm_pcr= */ TPM2_PCR_KERNEL_CONFIG, + u"Configuration extension initrd", + &confext_initrd, + &confext_initrd_size, + &m) == EFI_SUCCESS) + confext_measured = m; + dt_size = szs[UNIFIED_SECTION_DTB]; dt_base = dt_size != 0 ? POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + addrs[UNIFIED_SECTION_DTB] : 0; @@ -728,6 +739,8 @@ static EFI_STATUS run(EFI_HANDLE image) { (void) efivar_set_uint_string(MAKE_GUID_PTR(LOADER), u"StubPcrKernelParameters", TPM2_PCR_KERNEL_CONFIG, 0); if (sysext_measured) (void) efivar_set_uint_string(MAKE_GUID_PTR(LOADER), u"StubPcrInitRDSysExts", TPM2_PCR_SYSEXTS, 0); + if (confext_measured) + (void) efivar_set_uint_string(MAKE_GUID_PTR(LOADER), u"StubPcrInitRDConfExts", TPM2_PCR_KERNEL_CONFIG, 0); /* If the PCR signature was embedded in the PE image, then let's wrap it in a cpio and also pass it * to the kernel, so that it can be read from /.extra/tpm2-pcr-signature.json. Note that this section @@ -772,26 +785,36 @@ static EFI_STATUS run(EFI_HANDLE image) { initrd_size = szs[UNIFIED_SECTION_INITRD]; initrd_base = initrd_size != 0 ? POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + addrs[UNIFIED_SECTION_INITRD] : 0; + ucode_size = szs[UNIFIED_SECTION_UCODE]; + ucode_base = ucode_size != 0 ? POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + addrs[UNIFIED_SECTION_UCODE] : 0; + _cleanup_pages_ Pages initrd_pages = {}; - if (credential_initrd || global_credential_initrd || sysext_initrd || pcrsig_initrd || pcrpkey_initrd) { - /* If we have generated initrds dynamically, let's combine them with the built-in initrd. */ - err = combine_initrd( - initrd_base, initrd_size, + if (ucode_base || credential_initrd || global_credential_initrd || sysext_initrd || confext_initrd || pcrsig_initrd || pcrpkey_initrd) { + /* If we have generated initrds dynamically or there is a microcode initrd, combine them with the built-in initrd. */ + err = combine_initrds( (const void*const[]) { + /* Microcode must always be first as kernel only scans uncompressed cpios + * and later initrds might be compressed. */ + PHYSICAL_ADDRESS_TO_POINTER(ucode_base), + PHYSICAL_ADDRESS_TO_POINTER(initrd_base), credential_initrd, global_credential_initrd, sysext_initrd, + confext_initrd, pcrsig_initrd, pcrpkey_initrd, }, (const size_t[]) { + ucode_size, + initrd_size, credential_initrd_size, global_credential_initrd_size, sysext_initrd_size, + confext_initrd_size, pcrsig_initrd_size, pcrpkey_initrd_size, }, - 5, + 8, &initrd_pages, &initrd_size); if (err != EFI_SUCCESS) return err; @@ -802,6 +825,7 @@ static EFI_STATUS run(EFI_HANDLE image) { credential_initrd = mfree(credential_initrd); global_credential_initrd = mfree(global_credential_initrd); sysext_initrd = mfree(sysext_initrd); + confext_initrd = mfree(confext_initrd); pcrsig_initrd = mfree(pcrsig_initrd); pcrpkey_initrd = mfree(pcrpkey_initrd); } diff --git a/src/boot/efi/util.c b/src/boot/efi/util.c index e56ccfd..b5c8c63 100644 --- a/src/boot/efi/util.c +++ b/src/boot/efi/util.c @@ -303,7 +303,7 @@ EFI_STATUS chunked_read(EFI_FILE *file, size_t *size, void *buf) { * Some broken firmwares cannot handle large file reads and will instead return * an error. As a workaround, read such files in small chunks. * Note that we cannot just try reading the whole file first on such firmware as - * that will permanently break the handle even if it is re-opened. + * that will permanently break the handle even if it is reopened. * * https://github.com/systemd/systemd/issues/25911 */ diff --git a/src/boot/efi/util.h b/src/boot/efi/util.h index 0306e32..ceac07c 100644 --- a/src/boot/efi/util.h +++ b/src/boot/efi/util.h @@ -33,7 +33,7 @@ void *xmalloc(size_t size); _malloc_ _alloc_(1, 2) _returns_nonnull_ _warn_unused_result_ static inline void *xmalloc_multiply(size_t n, size_t size) { - assert_se(!__builtin_mul_overflow(size, n, &size)); + assert_se(MUL_ASSIGN_SAFE(&size, n)); return xmalloc(size); } @@ -84,7 +84,7 @@ static inline Pages xmalloc_pages( EFI_STATUS efivar_set(const EFI_GUID *vendor, const char16_t *name, const char16_t *value, uint32_t flags); EFI_STATUS efivar_set_raw(const EFI_GUID *vendor, const char16_t *name, const void *buf, size_t size, uint32_t flags); EFI_STATUS efivar_set_uint_string(const EFI_GUID *vendor, const char16_t *name, size_t i, uint32_t flags); -EFI_STATUS efivar_set_uint32_le(const EFI_GUID *vendor, const char16_t *NAME, uint32_t value, uint32_t flags); +EFI_STATUS efivar_set_uint32_le(const EFI_GUID *vendor, const char16_t *name, uint32_t value, uint32_t flags); EFI_STATUS efivar_set_uint64_le(const EFI_GUID *vendor, const char16_t *name, uint64_t value, uint32_t flags); void efivar_set_time_usec(const EFI_GUID *vendor, const char16_t *name, uint64_t usec); diff --git a/src/boot/efi/vmm.h b/src/boot/efi/vmm.h index df48af3..1d1037b 100644 --- a/src/boot/efi/vmm.h +++ b/src/boot/efi/vmm.h @@ -4,7 +4,7 @@ #include "efi.h" bool is_direct_boot(EFI_HANDLE device); -EFI_STATUS vmm_open(EFI_HANDLE *ret_qemu_dev, EFI_FILE **ret_qemu_dir); +EFI_STATUS vmm_open(EFI_HANDLE *ret_vmm_dev, EFI_FILE **ret_vmm_dir); bool in_hypervisor(void); diff --git a/src/boot/measure.c b/src/boot/measure.c index 5c5071e..a6d27a7 100644 --- a/src/boot/measure.c +++ b/src/boot/measure.c @@ -30,7 +30,10 @@ static char *arg_sections[_UNIFIED_SECTION_MAX] = {}; static char **arg_banks = NULL; static char *arg_tpm2_device = NULL; static char *arg_private_key = NULL; +static KeySourceType arg_private_key_source_type = OPENSSL_KEY_SOURCE_FILE; +static char *arg_private_key_source = NULL; static char *arg_public_key = NULL; +static char *arg_certificate = NULL; static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_PRETTY_AUTO|JSON_FORMAT_COLOR_AUTO|JSON_FORMAT_OFF; static PagerFlags arg_pager_flags = 0; static bool arg_current = false; @@ -40,7 +43,9 @@ static char *arg_append = NULL; STATIC_DESTRUCTOR_REGISTER(arg_banks, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep); STATIC_DESTRUCTOR_REGISTER(arg_private_key, freep); +STATIC_DESTRUCTOR_REGISTER(arg_private_key_source, freep); STATIC_DESTRUCTOR_REGISTER(arg_public_key, freep); +STATIC_DESTRUCTOR_REGISTER(arg_certificate, freep); STATIC_DESTRUCTOR_REGISTER(arg_phase, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_append, freep); @@ -74,7 +79,11 @@ static int help(int argc, char *argv[], void *userdata) { " --bank=DIGEST Select TPM bank (SHA1, SHA256, SHA384, SHA512)\n" " --tpm2-device=PATH Use specified TPM2 device\n" " --private-key=KEY Private key (PEM) to sign with\n" + " --private-key-source=file|provider:PROVIDER|engine:ENGINE\n" + " Specify how to use KEY for --private-key=. Allows\n" + " an OpenSSL engine/provider to be used for signing\n" " --public-key=KEY Public key (PEM) to validate against\n" + " --certificate=PATH PEM certificate to use when signing with a URI\n" " --json=MODE Output as JSON\n" " -j Same as --json=pretty on tty, --json=short otherwise\n" " --append=PATH Load specified JSON signature, and append new signature to it\n" @@ -83,6 +92,7 @@ static int help(int argc, char *argv[], void *userdata) { " --osrel=PATH Path to os-release file %7$s .osrel\n" " --cmdline=PATH Path to file with kernel command line %7$s .cmdline\n" " --initrd=PATH Path to initrd image file %7$s .initrd\n" + " --ucode=PATH Path to microcode image file %7$s .ucode\n" " --splash=PATH Path to splash bitmap file %7$s .splash\n" " --dtb=PATH Path to Devicetree file %7$s .dtb\n" " --uname=PATH Path to 'uname -r' file %7$s .uname\n" @@ -124,6 +134,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_OSREL, ARG_CMDLINE, ARG_INITRD, + ARG_UCODE, ARG_SPLASH, ARG_DTB, ARG_UNAME, @@ -133,7 +144,9 @@ static int parse_argv(int argc, char *argv[]) { ARG_PCRPKEY = _ARG_SECTION_LAST, ARG_BANK, ARG_PRIVATE_KEY, + ARG_PRIVATE_KEY_SOURCE, ARG_PUBLIC_KEY, + ARG_CERTIFICATE, ARG_TPM2_DEVICE, ARG_JSON, ARG_PHASE, @@ -141,26 +154,29 @@ static int parse_argv(int argc, char *argv[]) { }; static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "version", no_argument, NULL, ARG_VERSION }, - { "linux", required_argument, NULL, ARG_LINUX }, - { "osrel", required_argument, NULL, ARG_OSREL }, - { "cmdline", required_argument, NULL, ARG_CMDLINE }, - { "initrd", required_argument, NULL, ARG_INITRD }, - { "splash", required_argument, NULL, ARG_SPLASH }, - { "dtb", required_argument, NULL, ARG_DTB }, - { "uname", required_argument, NULL, ARG_UNAME }, - { "sbat", required_argument, NULL, ARG_SBAT }, - { "pcrpkey", required_argument, NULL, ARG_PCRPKEY }, - { "current", no_argument, NULL, 'c' }, - { "bank", required_argument, NULL, ARG_BANK }, - { "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE }, - { "private-key", required_argument, NULL, ARG_PRIVATE_KEY }, - { "public-key", required_argument, NULL, ARG_PUBLIC_KEY }, - { "json", required_argument, NULL, ARG_JSON }, - { "phase", required_argument, NULL, ARG_PHASE }, - { "append", required_argument, NULL, ARG_APPEND }, + { "help", no_argument, NULL, 'h' }, + { "no-pager", no_argument, NULL, ARG_NO_PAGER }, + { "version", no_argument, NULL, ARG_VERSION }, + { "linux", required_argument, NULL, ARG_LINUX }, + { "osrel", required_argument, NULL, ARG_OSREL }, + { "cmdline", required_argument, NULL, ARG_CMDLINE }, + { "initrd", required_argument, NULL, ARG_INITRD }, + { "ucode", required_argument, NULL, ARG_UCODE }, + { "splash", required_argument, NULL, ARG_SPLASH }, + { "dtb", required_argument, NULL, ARG_DTB }, + { "uname", required_argument, NULL, ARG_UNAME }, + { "sbat", required_argument, NULL, ARG_SBAT }, + { "pcrpkey", required_argument, NULL, ARG_PCRPKEY }, + { "current", no_argument, NULL, 'c' }, + { "bank", required_argument, NULL, ARG_BANK }, + { "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE }, + { "private-key", required_argument, NULL, ARG_PRIVATE_KEY }, + { "private-key-source", required_argument, NULL, ARG_PRIVATE_KEY_SOURCE }, + { "public-key", required_argument, NULL, ARG_PUBLIC_KEY }, + { "certificate", required_argument, NULL, ARG_CERTIFICATE }, + { "json", required_argument, NULL, ARG_JSON }, + { "phase", required_argument, NULL, ARG_PHASE }, + { "append", required_argument, NULL, ARG_APPEND }, {} }; @@ -213,7 +229,17 @@ static int parse_argv(int argc, char *argv[]) { } case ARG_PRIVATE_KEY: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_private_key); + r = free_and_strdup_warn(&arg_private_key, optarg); + if (r < 0) + return r; + + break; + + case ARG_PRIVATE_KEY_SOURCE: + r = parse_openssl_key_source_argument( + optarg, + &arg_private_key_source, + &arg_private_key_source_type); if (r < 0) return r; @@ -226,6 +252,13 @@ static int parse_argv(int argc, char *argv[]) { break; + case ARG_CERTIFICATE: + r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_certificate); + if (r < 0) + return r; + + break; + case ARG_TPM2_DEVICE: { _cleanup_free_ char *device = NULL; @@ -281,6 +314,12 @@ static int parse_argv(int argc, char *argv[]) { assert_not_reached(); } + if (arg_public_key && arg_certificate) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Both --public-key= and --certificate= specified, refusing."); + + if (arg_private_key_source && !arg_certificate) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "When using --private-key-source=, --certificate= must be specified."); + if (strv_isempty(arg_banks)) { /* If no banks are specifically selected, pick all known banks */ arg_banks = strv_new("SHA1", "SHA256", "SHA384", "SHA512"); @@ -300,12 +339,11 @@ static int parse_argv(int argc, char *argv[]) { if (strv_isempty(arg_phase)) { /* If no phases are specifically selected, pick everything from the beginning of the initrd * to the beginning of shutdown. */ - if (strv_extend_strv(&arg_phase, - STRV_MAKE("enter-initrd", - "enter-initrd:leave-initrd", - "enter-initrd:leave-initrd:sysinit", - "enter-initrd:leave-initrd:sysinit:ready"), - /* filter_duplicates= */ false) < 0) + if (strv_extend_many(&arg_phase, + "enter-initrd", + "enter-initrd:leave-initrd", + "enter-initrd:leave-initrd:sysinit", + "enter-initrd:leave-initrd:sysinit:ready") < 0) return log_oom(); } else { strv_sort(arg_phase); @@ -419,7 +457,7 @@ static int measure_kernel(PcrState *pcr_states, size_t n) { if (r < 0) return log_error_errno(r, "Failed to read '%s': %m", p); - r = unhexmem(strstrip(s), SIZE_MAX, &v, &sz); + r = unhexmem(strstrip(s), &v, &sz); if (r < 0) return log_error_errno(r, "Failed to decode PCR value '%s': %m", s); @@ -732,7 +770,7 @@ static int verb_sign(int argc, char *argv[], void *userdata) { _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; _cleanup_(pcr_state_free_all) PcrState *pcr_states = NULL; _cleanup_(EVP_PKEY_freep) EVP_PKEY *privkey = NULL, *pubkey = NULL; - _cleanup_fclose_ FILE *privkeyf = NULL; + _cleanup_(X509_freep) X509 *certificate = NULL; size_t n; int r; @@ -760,13 +798,57 @@ static int verb_sign(int argc, char *argv[], void *userdata) { /* When signing we only support JSON output */ arg_json_format_flags &= ~JSON_FORMAT_OFF; - privkeyf = fopen(arg_private_key, "re"); - if (!privkeyf) - return log_error_errno(errno, "Failed to open private key file '%s': %m", arg_private_key); + /* This must be done before openssl_load_key_from_token() otherwise it will get stuck */ + if (arg_certificate) { + _cleanup_(BIO_freep) BIO *cb = NULL; + _cleanup_free_ char *crt = NULL; - privkey = PEM_read_PrivateKey(privkeyf, NULL, NULL, NULL); - if (!privkey) - return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to parse private key '%s'.", arg_private_key); + r = read_full_file_full( + AT_FDCWD, arg_certificate, UINT64_MAX, SIZE_MAX, + READ_FULL_FILE_CONNECT_SOCKET, + /* bind_name= */ NULL, + &crt, &n); + if (r < 0) + return log_error_errno(r, "Failed to read certificate file '%s': %m", arg_certificate); + + cb = BIO_new_mem_buf(crt, n); + if (!cb) + return log_oom(); + + certificate = PEM_read_bio_X509(cb, NULL, NULL, NULL); + if (!certificate) + return log_error_errno( + SYNTHETIC_ERRNO(EBADMSG), + "Failed to parse X.509 certificate: %s", + ERR_error_string(ERR_get_error(), NULL)); + } + + if (arg_private_key_source_type == OPENSSL_KEY_SOURCE_FILE) { + _cleanup_fclose_ FILE *privkeyf = NULL; + _cleanup_free_ char *resolved_pkey = NULL; + + r = parse_path_argument(arg_private_key, /* suppress_root= */ false, &resolved_pkey); + if (r < 0) + return log_error_errno(r, "Failed to parse private key path %s: %m", arg_private_key); + + privkeyf = fopen(resolved_pkey, "re"); + if (!privkeyf) + return log_error_errno(errno, "Failed to open private key file '%s': %m", resolved_pkey); + + privkey = PEM_read_PrivateKey(privkeyf, NULL, NULL, NULL); + if (!privkey) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to parse private key '%s'.", resolved_pkey); + } else if (arg_private_key_source && + IN_SET(arg_private_key_source_type, OPENSSL_KEY_SOURCE_ENGINE, OPENSSL_KEY_SOURCE_PROVIDER)) { + r = openssl_load_key_from_token( + arg_private_key_source_type, arg_private_key_source, arg_private_key, &privkey); + if (r < 0) + return log_error_errno( + r, + "Failed to load key '%s' from OpenSSL key source %s: %m", + arg_private_key, + arg_private_key_source); + } if (arg_public_key) { _cleanup_fclose_ FILE *pubkeyf = NULL; @@ -778,6 +860,13 @@ static int verb_sign(int argc, char *argv[], void *userdata) { pubkey = PEM_read_PUBKEY(pubkeyf, NULL, NULL, NULL); if (!pubkey) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to parse public key '%s'.", arg_public_key); + } else if (certificate) { + pubkey = X509_get_pubkey(certificate); + if (!pubkey) + return log_error_errno( + SYNTHETIC_ERRNO(EIO), + "Failed to extract public key from certificate %s.", + arg_certificate); } else { _cleanup_(memstream_done) MemStream m = {}; FILE *tf; @@ -995,7 +1084,7 @@ static int verb_status(int argc, char *argv[], void *userdata) { if (r < 0) return log_error_errno(r, "Failed to read '%s': %m", p); - r = unhexmem(strstrip(s), SIZE_MAX, &h, &l); + r = unhexmem(strstrip(s), &h, &l); if (r < 0) return log_error_errno(r, "Failed to decode PCR value '%s': %m", s); @@ -1071,9 +1160,7 @@ static int measure_main(int argc, char *argv[]) { static int run(int argc, char *argv[]) { int r; - log_show_color(true); - log_parse_environment(); - log_open(); + log_setup(); r = parse_argv(argc, argv); if (r <= 0) |