diff --git a/TODO b/TODO index bb49b7a799..c837694d71 100644 --- a/TODO +++ b/TODO @@ -20,12 +20,6 @@ Features: * add --copy-from and --copy-to command to systemd-dissect which copies stuff in and out of a disk image -* add systemd.random_seed= on the kernel cmdline, taking some hex or base64 - encoded data. During earliest boot, credit it to entropy. This is not useful - for general purpose systems, but certainly for testing environments in VMs - and such, as it allows us to boot up instantly with fully initialized entropy - pool even if RNG pass-thru is not available. - * Support ProtectProc= or so, using: https://patchwork.kernel.org/cover/11310197/ * if /usr/bin/swapoff fails due to OOM, log a friendly explanatory message about it diff --git a/docs/RANDOM_SEEDS.md b/docs/RANDOM_SEEDS.md index c1735b1ac9..e4b4a7a9cb 100644 --- a/docs/RANDOM_SEEDS.md +++ b/docs/RANDOM_SEEDS.md @@ -257,7 +257,16 @@ boot, in order to ensure the entropy pool is filled up quickly. file. If done, `systemd-boot` will use the random seed file even if no system token is found in EFI variables. -With the three mechanisms described above it should be possible to provide +4. A kernel command line option `systemd.random_seed=` may be used to pass in a + base64 encoded seed to initialize the kernel's entropy pool from during + early service manager initialization. This option is only safe in testing + environments, as the random seed passed this way is accessible to + unprivileged programs via `/proc/cmdline`. Using this option outside of + testing environments is a security problem since cryptographic key material + derived from the entropy pool initialized with a seed accessible to + unprivileged programs should not be considered secret. + +With the four mechanisms described above it should be possible to provide early-boot entropy in most cases. Specifically: 1. On EFI systems, `systemd-boot`'s random seed logic should make sure good @@ -267,7 +276,8 @@ early-boot entropy in most cases. Specifically: 2. On virtualized systems, the early `virtio-rng` hookup should ensure entropy is available early on — as long as the VM environment provides virtualized RNG devices, which they really should all do in 2019. Complain to your - hosting provider if they don't. + hosting provider if they don't. For VMs used in testing environments, + `systemd.random_seed=` may be used as an alternative to a virtualized RNG. 3. On Intel/AMD systems systemd's own reliance on the kernel entropy pool is minimal (as RDRAND is used on those for UUID generation). This only works if @@ -286,8 +296,9 @@ This primarily leaves two kind of systems in the cold: boot. Alternatively, consider implementing a solution similar to systemd-boot's random seed concept in your platform's boot loader. -2. Virtualized environments that lack both virtio-rng and RDRAND. Tough - luck. Talk to your hosting provider, and ask them to fix this. +2. Virtualized environments that lack both virtio-rng and RDRAND, outside of + test environments. Tough luck. Talk to your hosting provider, and ask them + to fix this. 3. Also note: if you deploy an image without any random seed and/or without installing any 'system token' in an EFI variable, as described above, this @@ -410,6 +421,10 @@ This primarily leaves two kind of systems in the cold: information to possibly gain too much information about the current state of the kernel's entropy pool. + That said, we actually do implement this with the `systemd.random_seed=` + kernel command line option. Don't use this outside of testing environments, + however, for the aforementioned reasons. + 12. *Why doesn't `systemd-boot` rewrite the 'system token' too each time when updating the random seed file stored in the ESP?* diff --git a/man/kernel-command-line.xml b/man/kernel-command-line.xml index 9627f7e14b..b67639c92e 100644 --- a/man/kernel-command-line.xml +++ b/man/kernel-command-line.xml @@ -468,8 +468,32 @@ systemd.clock-usec= Takes a decimal, numeric timestamp in µs since January 1st 1970, 00:00am, to set the - system clock to. The system time is set to the specified timestamp early during - boot. It is not propagated to the hardware clock (RTC). + system clock to. The system time is set to the specified timestamp early during boot. It is not + propagated to the hardware clock (RTC). + + + + systemd.random-seed= + + Takes a base64 encoded random seed value to credit with full entropy to the kernel's + random pool during early service manager initialization. This option is useful in testing + environments where delays due to random pool initialization in entropy starved virtual machines shall + be avoided. + + Note that if this option is used the seed is accessible to unprivileged programs from + /proc/cmdline. This option is hence a security risk when used outside of test + systems, since the (possibly) only seed used for initialization of the kernel's entropy pool might be + easily acquired by unprivileged programs. + + It is recommended to pass 512 bytes of randomized data (as that matches the Linux kernel pool + size), which may be generated with a command like the following: + + dd if=/dev/urandom bs=512 count=1 status=none | base64 -w 0 + + Again: do not use this option outside of testing environments, it's a security risk elsewhere, + as secret key material derived from the entropy pool can possibly be reconstructed by unprivileged + programs. + diff --git a/src/basic/random-util.c b/src/basic/random-util.c index 73cc7272db..4a30c4d359 100644 --- a/src/basic/random-util.c +++ b/src/basic/random-util.c @@ -7,11 +7,13 @@ #include #include #include +#include #include #include #include #include #include +#include #include #if HAVE_SYS_AUXV_H @@ -438,3 +440,36 @@ size_t random_pool_size(void) { /* Use the minimum as default, if we can't retrieve the correct value */ return RANDOM_POOL_SIZE_MIN; } + +int random_write_entropy(int fd, const void *seed, size_t size, bool credit) { + int r; + + assert(fd >= 0); + assert(seed && size > 0); + + if (credit) { + _cleanup_free_ struct rand_pool_info *info = NULL; + + /* The kernel API only accepts "int" as entropy count (which is in bits), let's avoid any + * chance for confusion here. */ + if (size > INT_MAX / 8) + return -EOVERFLOW; + + info = malloc(offsetof(struct rand_pool_info, buf) + size); + if (!info) + return -ENOMEM; + + info->entropy_count = size * 8; + info->buf_size = size; + memcpy(info->buf, seed, size); + + if (ioctl(fd, RNDADDENTROPY, info) < 0) + return -errno; + } else { + r = loop_write(fd, seed, size, false); + if (r < 0) + return r; + } + + return 0; +} diff --git a/src/basic/random-util.h b/src/basic/random-util.h index d8e067d96e..7824ffaceb 100644 --- a/src/basic/random-util.h +++ b/src/basic/random-util.h @@ -38,3 +38,5 @@ int rdrand(unsigned long *ret); #define RANDOM_POOL_SIZE_MAX (10U*1024U*1024U) size_t random_pool_size(void); + +int random_write_entropy(int fd, const void *seed, size_t size, bool credit); diff --git a/src/core/efi-random.c b/src/core/efi-random.c index c4d25d68e4..b6609e63e5 100644 --- a/src/core/efi-random.c +++ b/src/core/efi-random.c @@ -1,8 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1+ */ #include -#include -#include #include #include "alloc-util.h" @@ -11,6 +9,7 @@ #include "efivars.h" #include "fd-util.h" #include "fs-util.h" +#include "random-util.h" #include "strv.h" /* If a random seed was passed by the boot loader in the LoaderRandomSeed EFI variable, let's credit it to @@ -43,7 +42,6 @@ static void lock_down_efi_variables(void) { } int efi_take_random_seed(void) { - _cleanup_free_ struct rand_pool_info *info = NULL; _cleanup_free_ void *value = NULL; _cleanup_close_ int random_fd = -1; size_t size; @@ -79,11 +77,6 @@ int efi_take_random_seed(void) { if (size == 0) return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), "Random seed passed from boot loader has zero size? Ignoring."); - /* The kernel API only accepts "int" as entropy count (which is in bits), let's avoid any chance for - * confusion here. */ - if (size > INT_MAX / 8) - size = INT_MAX / 8; - random_fd = open("/dev/urandom", O_WRONLY|O_CLOEXEC|O_NOCTTY); if (random_fd < 0) return log_warning_errno(errno, "Failed to open /dev/urandom for writing, ignoring: %m"); @@ -94,15 +87,8 @@ int efi_take_random_seed(void) { if (r < 0) return log_warning_errno(r, "Unable to mark EFI random seed as used, not using it: %m"); - info = malloc(offsetof(struct rand_pool_info, buf) + size); - if (!info) - return log_oom(); - - info->entropy_count = size * 8; - info->buf_size = size; - memcpy(info->buf, value, size); - - if (ioctl(random_fd, RNDADDENTROPY, info) < 0) + r = random_write_entropy(random_fd, value, size, true); + if (r < 0) return log_warning_errno(errno, "Failed to credit entropy, ignoring: %m"); log_info("Successfully credited entropy passed from boot loader."); diff --git a/src/core/main.c b/src/core/main.c index a2ff71fa71..2154c8eb12 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -42,6 +42,7 @@ #include "fileio.h" #include "format-util.h" #include "fs-util.h" +#include "hexdecoct.h" #include "hostname-setup.h" #include "ima-setup.h" #include "killall.h" @@ -60,6 +61,7 @@ #include "pretty-print.h" #include "proc-cmdline.h" #include "process-util.h" +#include "random-util.h" #include "raw-clone.h" #include "rlimit-util.h" #if HAVE_SECCOMP @@ -100,8 +102,8 @@ static enum { static const char *arg_bus_introspect = NULL; -/* Those variables are initialized to 0 automatically, so we avoid uninitialized memory access. - * Real defaults are assigned in reset_arguments() below. */ +/* Those variables are initialized to 0 automatically, so we avoid uninitialized memory access. Real + * defaults are assigned in reset_arguments() below. */ static char *arg_default_unit; static bool arg_system; static bool arg_dump_core; @@ -149,6 +151,8 @@ static OOMPolicy arg_default_oom_policy; static CPUSet arg_cpu_affinity; static NUMAPolicy arg_numa_policy; static usec_t arg_clock_usec; +static void *arg_random_seed; +static size_t arg_random_seed_size; /* A copy of the original environment block */ static char **saved_env = NULL; @@ -503,6 +507,21 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat if (r < 0) log_warning_errno(r, "Failed to parse systemd.clock_usec= argument, ignoring: %s", value); + } else if (proc_cmdline_key_streq(key, "systemd.random_seed")) { + void *p; + size_t sz; + + if (proc_cmdline_value_missing(key, value)) + return 0; + + r = unbase64mem(value, (size_t) -1, &p, &sz); + if (r < 0) + log_warning_errno(r, "Failed to parse systemd.random_seed= argument, ignoring: %s", value); + + free(arg_random_seed); + arg_random_seed = sz > 0 ? p : mfree(p); + arg_random_seed_size = sz; + } else if (streq(key, "quiet") && !value) { if (arg_show_status == _SHOW_STATUS_INVALID) @@ -1574,6 +1593,9 @@ static void apply_clock_update(void) { if (arg_clock_usec == 0) return; + if (getpid_cached() != 1) + return; + if (clock_settime(CLOCK_REALTIME, timespec_store(&ts, arg_clock_usec)) < 0) log_error_errno(errno, "Failed to set system clock to time specified on kernel command line: %m"); else { @@ -1584,6 +1606,40 @@ static void apply_clock_update(void) { } } +static void cmdline_take_random_seed(void) { + _cleanup_close_ int random_fd = -1; + size_t suggested; + int r; + + if (arg_random_seed_size == 0) + return; + + if (getpid_cached() != 1) + return; + + assert(arg_random_seed); + suggested = random_pool_size(); + + if (arg_random_seed_size < suggested) + log_warning("Random seed specified on kernel command line has size %zu, but %zu bytes required to fill entropy pool.", + arg_random_seed_size, suggested); + + random_fd = open("/dev/urandom", O_WRONLY|O_CLOEXEC|O_NOCTTY); + if (random_fd < 0) { + log_warning_errno(errno, "Failed to open /dev/urandom for writing, ignoring: %m"); + return; + } + + r = random_write_entropy(random_fd, arg_random_seed, arg_random_seed_size, true); + if (r < 0) { + log_warning_errno(r, "Failed to credit entropy specified on kernel command line, ignoring: %m"); + return; + } + + log_notice("Successfully credited entropy passed on kernel command line.\n" + "Note that the seed provided this way is accessible to unprivileged programs. This functionality should not be used outside of testing environments."); +} + static void initialize_coredump(bool skip_setup) { #if ENABLE_COREDUMP if (getpid_cached() != 1) @@ -2261,6 +2317,9 @@ static void reset_arguments(void) { cpu_set_reset(&arg_cpu_affinity); numa_policy_reset(&arg_numa_policy); + + arg_random_seed = mfree(arg_random_seed); + arg_random_seed_size = 0; } static int parse_configuration(const struct rlimit *saved_rlimit_nofile, @@ -2580,8 +2639,7 @@ int main(int argc, char *argv[]) { /* For later on, see above... */ log_set_target(LOG_TARGET_JOURNAL); - /* clear the kernel timestamp, - * because we are in a container */ + /* clear the kernel timestamp, because we are in a container */ kernel_timestamp = DUAL_TIMESTAMP_NULL; } @@ -2600,8 +2658,7 @@ int main(int argc, char *argv[]) { log_set_target(LOG_TARGET_AUTO); log_open(); - /* clear the kernel timestamp, - * because we are not PID 1 */ + /* clear the kernel timestamp, because we are not PID 1 */ kernel_timestamp = DUAL_TIMESTAMP_NULL; if (mac_selinux_init() < 0) { @@ -2620,8 +2677,7 @@ int main(int argc, char *argv[]) { log_warning_errno(r, "Failed to redirect standard streams to /dev/null, ignoring: %m"); } - /* Mount /proc, /sys and friends, so that /proc/cmdline and - * /proc/$PID/fd is available. */ + /* Mount /proc, /sys and friends, so that /proc/cmdline and /proc/$PID/fd is available. */ if (getpid_cached() == 1) { /* Load the kernel modules early. */ @@ -2694,8 +2750,13 @@ int main(int argc, char *argv[]) { assert_se(chdir("/") == 0); if (arg_action == ACTION_RUN) { - /* Apply the systemd.clock_usec= kernel command line switch */ - apply_clock_update(); + if (!skip_setup) { + /* Apply the systemd.clock_usec= kernel command line switch */ + apply_clock_update(); + + /* Apply random seed from kernel command line */ + cmdline_take_random_seed(); + } /* A core pattern might have been specified via the cmdline. */ initialize_core_pattern(skip_setup); diff --git a/src/random-seed/random-seed.c b/src/random-seed/random-seed.c index 596bff98f1..63ad977514 100644 --- a/src/random-seed/random-seed.c +++ b/src/random-seed/random-seed.c @@ -236,24 +236,10 @@ static int run(int argc, char *argv[]) { } } - if (IN_SET(lets_credit, CREDIT_ENTROPY_YES_PLEASE, CREDIT_ENTROPY_YES_FORCED)) { - _cleanup_free_ struct rand_pool_info *info = NULL; - - info = malloc(offsetof(struct rand_pool_info, buf) + k); - if (!info) - return log_oom(); - - info->entropy_count = k * 8; - info->buf_size = k; - memcpy(info->buf, buf, k); - - if (ioctl(random_fd, RNDADDENTROPY, info) < 0) - return log_warning_errno(errno, "Failed to credit entropy, ignoring: %m"); - } else { - r = loop_write(random_fd, buf, (size_t) k, false); - if (r < 0) - log_error_errno(r, "Failed to write seed to /dev/urandom: %m"); - } + r = random_write_entropy(random_fd, buf, k, + IN_SET(lets_credit, CREDIT_ENTROPY_YES_PLEASE, CREDIT_ENTROPY_YES_FORCED)); + if (r < 0) + log_error_errno(r, "Failed to write seed to /dev/urandom: %m"); } } @@ -305,7 +291,7 @@ static int run(int argc, char *argv[]) { * entropy later on. Let's keep that in mind by setting an extended attribute. on the file */ if (getrandom_worked) if (fsetxattr(seed_fd, "user.random-seed-creditable", "1", 1, 0) < 0) - log_full_errno(IN_SET(errno, ENOSYS, EOPNOTSUPP) ? LOG_DEBUG : LOG_WARNING, errno, + log_full_errno(ERRNO_IS_NOT_SUPPORTED(errno) ? LOG_DEBUG : LOG_WARNING, errno, "Failed to mark seed file as creditable, ignoring: %m"); }