Merge pull request #16142 from poettering/random-seed-cmdline

pid1: add support for allowing to pass in random seed via kernel cmdline
This commit is contained in:
Zbigniew Jędrzejewski-Szmek 2020-06-26 22:42:51 +02:00 committed by GitHub
commit 0e31a6c2ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 161 additions and 58 deletions

6
TODO
View File

@ -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

View File

@ -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?*

View File

@ -468,8 +468,32 @@
<term><varname>systemd.clock-usec=</varname></term>
<listitem><para>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).</para></listitem>
system clock to. The system time is set to the specified timestamp early during boot. It is not
propagated to the hardware clock (RTC).</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>systemd.random-seed=</varname></term>
<listitem><para>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.</para>
<para>Note that if this option is used the seed is accessible to unprivileged programs from
<filename>/proc/cmdline</filename>. 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.</para>
<para>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:</para>
<programlisting>dd if=/dev/urandom bs=512 count=1 status=none | base64 -w 0</programlisting>
<para>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.</para>
</listitem>
</varlistentry>
<varlistentry>

View File

@ -7,11 +7,13 @@
#include <elf.h>
#include <errno.h>
#include <fcntl.h>
#include <linux/random.h>
#include <pthread.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#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;
}

View File

@ -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);

View File

@ -1,8 +1,6 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include <fcntl.h>
#include <linux/random.h>
#include <sys/ioctl.h>
#include <unistd.h>
#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.");

View File

@ -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);

View File

@ -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");
}