]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
bootctl: add new verb for initializing a random seed in the ESP
authorLennart Poettering <lennart@poettering.net>
Fri, 19 Jul 2019 12:51:43 +0000 (14:51 +0200)
committerLennart Poettering <lennart@poettering.net>
Thu, 25 Jul 2019 16:20:50 +0000 (18:20 +0200)
src/boot/bootctl.c

index 41a9920420016673768fa9d53fe229a573ddb68d..b36ad8063af96a81a90f02003a27999e42d2e268 100644 (file)
@@ -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 },