summaryrefslogtreecommitdiff
path: root/src/boot/bootctl.c
diff options
context:
space:
mode:
authorLennart Poettering <lennart@poettering.net>2019-07-19 14:51:43 +0200
committerLennart Poettering <lennart@poettering.net>2019-07-25 18:20:50 +0200
commite44c3229f22459b189c1e79cb01fdb156672eb93 (patch)
tree2a795fa14968dc9446600cd263b891a639142adc /src/boot/bootctl.c
parent3e155eba4363ce3f7953e5b69db526ab47bf165d (diff)
downloadsystemd-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.c303
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 },