diff options
author | Lennart Poettering <lennart@poettering.net> | 2019-07-19 14:51:43 +0200 |
---|---|---|
committer | Lennart Poettering <lennart@poettering.net> | 2019-07-25 18:20:50 +0200 |
commit | e44c3229f22459b189c1e79cb01fdb156672eb93 (patch) | |
tree | 2a795fa14968dc9446600cd263b891a639142adc /src/boot/bootctl.c | |
parent | 3e155eba4363ce3f7953e5b69db526ab47bf165d (diff) | |
download | systemd-e44c3229f22459b189c1e79cb01fdb156672eb93.tar.gz |
bootctl: add new verb for initializing a random seed in the ESP
Diffstat (limited to 'src/boot/bootctl.c')
-rw-r--r-- | src/boot/bootctl.c | 303 |
1 files changed, 253 insertions, 50 deletions
diff --git a/src/boot/bootctl.c b/src/boot/bootctl.c index 41a9920420..b36ad8063a 100644 --- a/src/boot/bootctl.c +++ b/src/boot/bootctl.c @@ -25,6 +25,7 @@ #include "copy.h" #include "dirent-util.h" #include "efivars.h" +#include "env-util.h" #include "escape.h" #include "fd-util.h" #include "fileio.h" @@ -34,6 +35,7 @@ #include "pager.h" #include "parse-util.h" #include "pretty-print.h" +#include "random-util.h" #include "rm-rf.h" #include "stat-util.h" #include "stdio-util.h" @@ -580,21 +582,29 @@ static int mkdir_one(const char *prefix, const char *suffix) { } static const char *const esp_subdirs[] = { + /* The directories to place in the ESP */ "EFI", "EFI/systemd", "EFI/BOOT", "loader", - /* Note that "/loader/entries" is not listed here, since it should be placed in $BOOT, which might - * not necessarily be the ESP */ NULL }; -static int create_esp_subdirs(const char *esp_path) { +static const char *const dollar_boot_subdirs[] = { + /* The directories to place in the XBOOTLDR partition or the ESP, depending what exists */ + "loader", + "loader/entries", /* Type #1 entries */ + "EFI", + "EFI/Linux", /* Type #2 entries */ + NULL +}; + +static int create_subdirs(const char *root, const char * const *subdirs) { const char *const *i; int r; - STRV_FOREACH(i, esp_subdirs) { - r = mkdir_one(esp_path, *i); + STRV_FOREACH(i, subdirs) { + r = mkdir_one(root, *i); if (r < 0) return r; } @@ -865,19 +875,27 @@ static int rmdir_one(const char *prefix, const char *suffix) { return 0; } -static int remove_esp_subdirs(const char *esp_path) { - size_t i; - int r = 0; +static int remove_subdirs(const char *root, const char *const *subdirs) { + int r, q; - for (i = ELEMENTSOF(esp_subdirs)-1; i > 0; i--) { - int q; + /* We use recursion here to destroy the directories in reverse order. Which should be safe given how + * short the array is. */ - q = rmdir_one(esp_path, esp_subdirs[i-1]); - if (q < 0 && r >= 0) - r = q; - } + if (!subdirs[0]) /* A the end of the list */ + return 0; - return r; + r = remove_subdirs(root, subdirs + 1); + q = rmdir_one(root, subdirs[0]); + + return r < 0 ? r : q; +} + +static int remove_machine_id_directory(const char *root, sd_id128_t machine_id) { + char buf[SD_ID128_STRING_MAX]; + + assert(root); + + return rmdir_one(root, sd_id128_to_string(machine_id, buf)); } static int remove_binaries(const char *esp_path) { @@ -894,26 +912,22 @@ static int remove_binaries(const char *esp_path) { return r; } -static int remove_loader_config(const char *esp_path) { +static int remove_file(const char *root, const char *file) { const char *p; - assert(esp_path); + assert(root); + assert(file); - p = prefix_roota(esp_path, "/loader/loader.conf"); + p = prefix_roota(root, file); if (unlink(p) < 0) { - log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_ERR, errno, "Failed to unlink file \"%s\": %m", p); - if (errno != ENOENT) - return -errno; - } else - log_info("Removed \"%s\".", p); - - return 0; -} + log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_ERR, errno, + "Failed to unlink file \"%s\": %m", p); -static int remove_entries_directory(const char *dollar_boot_path) { - assert(dollar_boot_path); + return errno == ENOENT ? 0 : -errno; + } - return rmdir_one(dollar_boot_path, "/loader/entries"); + log_info("Removed \"%s\".", p); + return 1; } static int remove_variables(sd_id128_t uuid, const char *path, bool in_order) { @@ -937,6 +951,35 @@ static int remove_variables(sd_id128_t uuid, const char *path, bool in_order) { return 0; } +static int remove_loader_variables(void) { + const char *p; + int r = 0; + + /* Remove all persistent loader variables we define */ + + FOREACH_STRING(p, + "LoaderConfigTimeout", + "LoaderConfigTimeoutOneShot", + "LoaderEntryDefault", + "LoaderEntryOneShot", + "LoaderSystemToken") { + + int q; + + q = efi_set_variable(EFI_VENDOR_LOADER, p, NULL, 0); + if (q == -ENOENT) + continue; + if (q < 0) { + log_warning_errno(q, "Failed to remove %s variable: %m", p); + if (r >= 0) + r = q; + } else + log_info("Removed EFI variable %s.", p); + } + + return r; +} + static int install_loader_config(const char *esp_path, sd_id128_t machine_id) { char machine_string[SD_ID128_STRING_MAX]; _cleanup_(unlink_and_freep) char *t = NULL; @@ -976,21 +1019,12 @@ static int install_loader_config(const char *esp_path, sd_id128_t machine_id) { return 1; } -static int install_entries_directories(const char *dollar_boot_path, sd_id128_t machine_id) { - int r; +static int install_machine_id_directory(const char *root, sd_id128_t machine_id) { char buf[SD_ID128_STRING_MAX]; - assert(dollar_boot_path); - - /* Both /loader/entries and the entry directories themselves should be located on the same - * partition. Also create the parent directory for entry directories, so that kernel-install - * knows where to put them. */ - - r = mkdir_one(dollar_boot_path, "loader/entries"); - if (r < 0) - return r; + assert(root); - return mkdir_one(dollar_boot_path, sd_id128_to_string(machine_id, buf)); + return mkdir_one(root, sd_id128_to_string(machine_id, buf)); } static int help(int argc, char *argv[], void *userdata) { @@ -1016,6 +1050,7 @@ static int help(int argc, char *argv[], void *userdata) { " install Install systemd-boot to the ESP and EFI variables\n" " update Update systemd-boot in the ESP and EFI variables\n" " remove Remove systemd-boot from the ESP and EFI variables\n" + " random-seed Initialize random seed in ESP and EFI variables\n" "\nBoot Loader Entries Commands:\n" " list List boot loader entries\n" " set-default ID Set default boot loader entry\n" @@ -1286,6 +1321,122 @@ static int verb_list(int argc, char *argv[], void *userdata) { return 0; } +static int install_random_seed(const char *esp) { + _cleanup_(unlink_and_freep) char *tmp = NULL; + _cleanup_free_ void *buffer = NULL; + _cleanup_free_ char *path = NULL; + _cleanup_close_ int fd = -1; + size_t sz, token_size; + ssize_t n; + int r; + + assert(esp); + + path = path_join(esp, "/loader/random-seed"); + if (!path) + return log_oom(); + + sz = random_pool_size(); + + buffer = malloc(sz); + if (!buffer) + return log_oom(); + + r = genuine_random_bytes(buffer, sz, RANDOM_BLOCK); + if (r < 0) + return log_error_errno(r, "Faile to acquire random seed: %m"); + + r = tempfn_random(path, "bootctl", &tmp); + if (r < 0) + return log_oom(); + + fd = open(tmp, O_CREAT|O_EXCL|O_NOFOLLOW|O_NOCTTY|O_WRONLY|O_CLOEXEC, 0600); + if (fd < 0) { + tmp = mfree(tmp); + return log_error_errno(fd, "Failed to open random seed file for writing: %m"); + } + + n = write(fd, buffer, sz); + if (n < 0) + return log_error_errno(errno, "Failed to write random seed file: %m"); + if ((size_t) n != sz) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Short write while writing random seed file."); + + if (rename(tmp, path) < 0) + return log_error_errno(r, "Failed to move random seed file into place: %m"); + + tmp = mfree(tmp); + + log_info("Successfully written random seed file %s with %zu bytes.", path, sz); + + if (!arg_touch_variables) + return 0; + + if (!is_efi_boot()) { + log_notice("Not booted with EFI, skipping EFI variable setup."); + return 0; + } + + r = getenv_bool("SYSTEMD_WRITE_SYSTEM_TOKEN"); + if (r < 0) { + if (r != -ENXIO) + log_warning_errno(r, "Failed to parse $SYSTEMD_WRITE_SYSTEM_TOKEN, ignoring."); + + if (detect_vm() > 0) { + /* Let's not write a system token if we detect we are running in a VM + * environment. Why? Our default security model for the random seed uses the system + * token as a mechanism to ensure we are not vulnerable to golden master sloppiness + * issues, i.e. that people initialize the random seed file, then copy the image to + * many systems and end up with the same random seed in each that is assumed to be + * valid but in reality is the same for all machines. By storing a system token in + * the EFI variable space we can make sure that even though the random seeds on disk + * are all the same they will be different on each system under the assumption that + * the EFI variable space is maintained separate from the random seed storage. That + * is generally the case on physical systems, as the ESP is stored on persistant + * storage, and the EFI variables in NVRAM. However in virtualized environments this + * is generally not true: the EFI variable set is typically stored along with the + * disk image itself. For example, using the OVMF EFI firmware the EFI variables are + * stored in a file in the ESP itself. */ + + log_notice("Not installing system token, since we are running in a virtualized environment."); + return 0; + } + } else if (r == 0) { + log_notice("Not writing system token, because $SYSTEMD_WRITE_SYSTEM_TOKEN is set to false."); + return 0; + } + + r = efi_get_variable(EFI_VENDOR_LOADER, "LoaderSystemToken", NULL, NULL, &token_size); + if (r < 0) { + if (r != -ENOENT) + return log_error_errno(r, "Failed to test system token validity: %m"); + } else { + if (token_size >= sz) { + /* Let's avoid writes if we can, and initialize this only once. */ + log_debug("System token already written, not updating."); + return 0; + } + + log_debug("Existing system token size (%zu) does not match our expectations (%zu), replacing.", token_size, sz); + } + + r = genuine_random_bytes(buffer, sz, RANDOM_BLOCK); + if (r < 0) + return log_error_errno(r, "Failed to acquire random seed: %m"); + + /* Let's write this variable with an umask in effect, so that unprivileged users can't see the token + * and possibly get identification information or too much insight into the kernel's entropy pool + * state. */ + RUN_WITH_UMASK(0077) { + r = efi_set_variable(EFI_VENDOR_LOADER, "LoaderSystemToken", buffer, sz); + if (r < 0) + return log_error_errno(r, "Failed to set LoaderSystemToken EFI variable: %m"); + } + + log_info("Successfully initialized system token in EFI variable with %zu bytes.", sz); + return 0; +} + static int sync_everything(void) { int ret = 0, k; @@ -1331,7 +1482,11 @@ static int verb_install(int argc, char *argv[], void *userdata) { /* Don't create any of these directories when we are just updating. When we update * we'll drop-in our files (unless there are newer ones already), but we won't create * the directories for them in the first place. */ - r = create_esp_subdirs(arg_esp_path); + r = create_subdirs(arg_esp_path, esp_subdirs); + if (r < 0) + return r; + + r = create_subdirs(arg_dollar_boot_path(), dollar_boot_subdirs); if (r < 0) return r; } @@ -1345,7 +1500,11 @@ static int verb_install(int argc, char *argv[], void *userdata) { if (r < 0) return r; - r = install_entries_directories(arg_dollar_boot_path(), machine_id); + r = install_machine_id_directory(arg_dollar_boot_path(), machine_id); + if (r < 0) + return r; + + r = install_random_seed(arg_esp_path); if (r < 0) return r; } @@ -1363,7 +1522,7 @@ static int verb_install(int argc, char *argv[], void *userdata) { } static int verb_remove(int argc, char *argv[], void *userdata) { - sd_id128_t uuid = SD_ID128_NULL; + sd_id128_t uuid = SD_ID128_NULL, machine_id; int r, q; r = acquire_esp(false, NULL, NULL, NULL, &uuid); @@ -1374,28 +1533,56 @@ static int verb_remove(int argc, char *argv[], void *userdata) { if (r < 0) return r; + r = sd_id128_get_machine(&machine_id); + if (r < 0) + return log_error_errno(r, "Failed to get machine id: %m"); + r = remove_binaries(arg_esp_path); - q = remove_loader_config(arg_esp_path); + q = remove_file(arg_esp_path, "/loader/loader.conf"); if (q < 0 && r >= 0) r = q; - q = remove_entries_directory(arg_dollar_boot_path()); + q = remove_file(arg_esp_path, "/loader/random-seed"); if (q < 0 && r >= 0) r = q; - q = remove_esp_subdirs(arg_esp_path); + q = remove_subdirs(arg_esp_path, esp_subdirs); if (q < 0 && r >= 0) r = q; - (void) sync_everything(); + q = remove_subdirs(arg_esp_path, dollar_boot_subdirs); + if (q < 0 && r >= 0) + r = q; - if (arg_touch_variables) { - q = remove_variables(uuid, "/EFI/systemd/systemd-boot" EFI_MACHINE_TYPE_NAME ".efi", true); + q = remove_machine_id_directory(arg_esp_path, machine_id); + if (q < 0 && r >= 0) + r = 1; + + if (arg_xbootldr_path) { + /* Remove the latter two also in the XBOOTLDR partition if it exists */ + q = remove_subdirs(arg_xbootldr_path, dollar_boot_subdirs); + if (q < 0 && r >= 0) + r = q; + + q = remove_machine_id_directory(arg_xbootldr_path, machine_id); if (q < 0 && r >= 0) r = q; } + (void) sync_everything(); + + if (!arg_touch_variables) + return r; + + q = remove_variables(uuid, "/EFI/systemd/systemd-boot" EFI_MACHINE_TYPE_NAME ".efi", true); + if (q < 0 && r >= 0) + r = q; + + q = remove_loader_variables(); + if (q < 0 && r >= 0) + r = q; + return r; } @@ -1447,6 +1634,21 @@ static int verb_set_default(int argc, char *argv[], void *userdata) { return 0; } +static int verb_random_seed(int argc, char *argv[], void *userdata) { + int r; + + r = acquire_esp(false, NULL, NULL, NULL, NULL); + if (r < 0) + return r; + + r = install_random_seed(arg_esp_path); + if (r < 0) + return r; + + (void) sync_everything(); + return 0; +} + static int bootctl_main(int argc, char *argv[]) { static const Verb verbs[] = { { "help", VERB_ANY, VERB_ANY, 0, help }, @@ -1454,6 +1656,7 @@ static int bootctl_main(int argc, char *argv[]) { { "install", VERB_ANY, 1, 0, verb_install }, { "update", VERB_ANY, 1, 0, verb_install }, { "remove", VERB_ANY, 1, 0, verb_remove }, + { "random-seed", VERB_ANY, 1, 0, verb_random_seed }, { "list", VERB_ANY, 1, 0, verb_list }, { "set-default", 2, 2, 0, verb_set_default }, { "set-oneshot", 2, 2, 0, verb_set_default }, |