bootctl: add new verb for initializing a random seed in the ESP

This commit is contained in:
Lennart Poettering 2019-07-19 14:51:43 +02:00
parent 3e155eba43
commit e44c3229f2

View 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);
log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_ERR, errno,
"Failed to unlink file \"%s\": %m", p);
return 0;
}
return errno == ENOENT ? 0 : -errno;
}
static int remove_entries_directory(const char *dollar_boot_path) {
assert(dollar_boot_path);
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);
assert(root);
/* 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;
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 },