]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
random-seed: rework systemd-random-seed.service substantially
authorLennart Poettering <lennart@poettering.net>
Mon, 22 Jul 2019 11:51:30 +0000 (13:51 +0200)
committerLennart Poettering <lennart@poettering.net>
Thu, 25 Jul 2019 16:30:06 +0000 (18:30 +0200)
This makes two major changes to the way systemd-random-seed operates:

1. We now optionally credit entropy if this is configured (via an env
var). Previously we never would do that, with this change we still don't
by default, but it's possible to enable this if people acknowledge that
they shouldn't replicate an image with a contained random seed to
multiple systems. Note that in this patch crediting entropy is a boolean
thing (unlike in previous attempts such as #1062), where only a relative
amount of bits was credited. The simpler scheme implemented here should
be OK though as the random seeds saved to disk are now written only with
data from the kernel's entropy pool retrieved after the pool is fully
initialized. Specifically:

2. This makes systemd-random-seed.service a synchronization point for
kernel entropy pool initialization. It was already used like this, for
example by systemd-cryptsetup-generator's /dev/urandom passphrase
handling, with this change it explicitly operates like that (at least
systems which provide getrandom(), where we can support this). This
means services that rely on an initialized random pool should now place
After=systemd-random-seed.service and everything should be fine. Note
that with this change sysinit.target (and thus early boot) is NOT
systematically delayed until the entropy pool is initialized, i.e.
regular services need to add explicit ordering deps on this service if
they require an initialized random pool.

Fixes: #4271
Replaces: #10621 #4513

src/random-seed/random-seed.c
units/systemd-random-seed.service.in

index dc148ebc86a3acffcbecc9675cdbc3e7e9e66f19..cbc97455842ee09afbd57ee279c88f8579ce47d7 100644 (file)
@@ -2,8 +2,14 @@
 
 #include <errno.h>
 #include <fcntl.h>
+#include <linux/random.h>
 #include <string.h>
+#include <sys/ioctl.h>
+#if USE_SYS_RANDOM_H
+#  include <sys/random.h>
+#endif
 #include <sys/stat.h>
+#include <sys/xattr.h>
 #include <unistd.h>
 
 #include "sd-id128.h"
 #include "io-util.h"
 #include "log.h"
 #include "main-func.h"
+#include "missing.h"
 #include "mkdir.h"
+#include "parse-util.h"
 #include "random-util.h"
 #include "string-util.h"
 #include "util.h"
+#include "xattr-util.h"
+
+typedef enum CreditEntropy {
+        CREDIT_ENTROPY_NO_WAY,
+        CREDIT_ENTROPY_YES_PLEASE,
+        CREDIT_ENTROPY_YES_FORCED,
+} CreditEntropy;
+
+static CreditEntropy may_credit(int seed_fd) {
+        _cleanup_free_ char *creditable = NULL;
+        const char *e;
+        int r;
+
+        assert(seed_fd >= 0);
+
+        e = getenv("SYSTEMD_RANDOM_SEED_CREDIT");
+        if (!e) {
+                log_debug("$SYSTEMD_RANDOM_SEED_CREDIT is not set, not crediting entropy.");
+                return CREDIT_ENTROPY_NO_WAY;
+        }
+        if (streq(e, "force")) {
+                log_debug("$SYSTEMD_RANDOM_SEED_CREDIT is set to 'force', crediting entropy.");
+                return CREDIT_ENTROPY_YES_FORCED;
+        }
+
+        r = parse_boolean(e);
+        if (r <= 0) {
+                if (r < 0)
+                        log_warning_errno(r, "Failed to parse $SYSTEMD_RANDOM_SEED_CREDIT, not crediting entropy: %m");
+                else
+                        log_debug("Crediting entropy is turned off via $SYSTEMD_RANDOM_SEED_CREDIT, not crediting entropy.");
+
+                return CREDIT_ENTROPY_NO_WAY;
+        }
+
+        /* Determine if the file is marked as creditable */
+        r = fgetxattr_malloc(seed_fd, "user.random-seed-creditable", &creditable);
+        if (r < 0) {
+                if (IN_SET(r, -ENODATA, -ENOSYS, -EOPNOTSUPP))
+                        log_debug_errno(r, "Seed file is not marked as creditable, not crediting.");
+                else
+                        log_warning_errno(r, "Failed to read extended attribute, ignoring: %m");
+
+                return CREDIT_ENTROPY_NO_WAY;
+        }
+
+        r = parse_boolean(creditable);
+        if (r <= 0) {
+                if (r < 0)
+                        log_warning_errno(r, "Failed to parse user.random-seed-creditable extended attribute, ignoring: %s", creditable);
+                else
+                        log_debug("Seed file is marked as not creditable, not crediting.");
+
+                return CREDIT_ENTROPY_NO_WAY;
+        }
+
+        /* Don't credit the random seed if we are in first-boot mode, because we are supposed to start from
+         * scratch. This is a safety precaution for cases where we people ship "golden" images with empty
+         * /etc but populated /var that contains a random seed. */
+        if (access("/run/systemd/first-boot", F_OK) < 0) {
+
+                if (errno != ENOENT) {
+                        log_warning_errno(errno, "Failed to check whether we are in first-boot mode, not crediting entropy: %m");
+                        return CREDIT_ENTROPY_NO_WAY;
+                }
+
+                /* If ENOENT all is good, we are not in first-boot mode. */
+        } else {
+                log_debug("Not crediting entropy, since booted in first-boot mode.");
+                return CREDIT_ENTROPY_NO_WAY;
+        }
+
+        return CREDIT_ENTROPY_YES_PLEASE;
+}
 
 static int run(int argc, char *argv[]) {
         _cleanup_close_ int seed_fd = -1, random_fd = -1;
-        bool read_seed_file, write_seed_file;
+        bool read_seed_file, write_seed_file, synchronous;
         _cleanup_free_ void* buf = NULL;
         size_t buf_size;
         struct stat st;
@@ -71,6 +153,7 @@ static int run(int argc, char *argv[]) {
                         return log_error_errno(errno, "Failed to open /dev/urandom: %m");
 
                 read_seed_file = true;
+                synchronous = true; /* make this invocation a synchronous barrier for random pool initialization */
 
         } else if (streq(argv[1], "save")) {
 
@@ -84,7 +167,7 @@ static int run(int argc, char *argv[]) {
 
                 read_seed_file = false;
                 write_seed_file = true;
-
+                synchronous = false;
         } else
                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
                                        "Unknown verb '%s'.", argv[1]);
@@ -102,57 +185,131 @@ static int run(int argc, char *argv[]) {
 
         if (read_seed_file) {
                 sd_id128_t mid;
-                int z;
+
+                /* First, let's write the machine ID into /dev/urandom, not crediting entropy. Why? As an
+                 * extra protection against "golden images" that are put together sloppily, i.e. images which
+                 * are duplicated on multiple systems but where the random seed file is not properly
+                 * reset. Frequently the machine ID is properly reset on those systems however (simply
+                 * because it's easier to notice, if it isn't due to address clashes and so on, while random
+                 * seed equivalence is generally not noticed easily), hence let's simply write the machined
+                 * ID into the random pool too. */
+                r = sd_id128_get_machine(&mid);
+                if (r < 0)
+                        log_debug_errno(r, "Failed to get machine ID, ignoring: %m");
+                else {
+                        r = loop_write(random_fd, &mid, sizeof(mid), false);
+                        if (r < 0)
+                                log_debug_errno(r, "Failed to write machine ID to /dev/urandom, ignoring: %m");
+                }
 
                 k = loop_read(seed_fd, buf, buf_size, false);
                 if (k < 0)
-                        r = log_error_errno(k, "Failed to read seed from " RANDOM_SEED ": %m");
-                else if (k == 0) {
-                        r = 0;
+                        log_error_errno(k, "Failed to read seed from " RANDOM_SEED ": %m");
+                else if (k == 0)
                         log_debug("Seed file " RANDOM_SEED " not yet initialized, proceeding.");
-                } else {
+                else {
+                        CreditEntropy lets_credit;
+
                         (void) lseek(seed_fd, 0, SEEK_SET);
 
-                        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");
-                }
+                        lets_credit = may_credit(seed_fd);
 
-                /* Let's also write the machine ID into the random seed. Why? As an extra protection against "golden
-                 * images" that are put together sloppily, i.e. images which are duplicated on multiple systems but
-                 * where the random seed file is not properly reset. Frequently the machine ID is properly reset on
-                 * those systems however (simply because it's easier to notice, if it isn't due to address clashes and
-                 * so on, while random seed equivalence is generally not noticed easily), hence let's simply write the
-                 * machined ID into the random pool too. */
-                z = sd_id128_get_machine(&mid);
-                if (z < 0)
-                        log_debug_errno(z, "Failed to get machine ID, ignoring: %m");
-                else {
-                        z = loop_write(random_fd, &mid, sizeof(mid), false);
-                        if (z < 0)
-                                log_debug_errno(z, "Failed to write machine ID to /dev/urandom, ignoring: %m");
+                        /* Before we credit or use the entropy, let's make sure to securely drop the
+                         * creditable xattr from the file, so that we never credit the same random seed
+                         * again. Note that further down we'll write a new seed again, and likely mark it as
+                         * credible again, hence this is just paranoia to close the short time window between
+                         * the time we upload the random seed into the kernel and download the new one from
+                         * it. */
+
+                        if (fremovexattr(seed_fd, "user.random-seed-creditable") < 0) {
+                                if (!IN_SET(errno, ENODATA, ENOSYS, EOPNOTSUPP))
+                                        log_warning_errno(errno, "Failed to remove extended attribute, ignoring: %m");
+
+                                /* Otherwise, there was no creditable flag set, which is OK. */
+                        } else {
+                                r = fsync_full(seed_fd);
+                                if (r < 0) {
+                                        log_warning_errno(r, "Failed to synchronize seed to disk, not crediting entropy: %m");
+
+                                        if (lets_credit == CREDIT_ENTROPY_YES_PLEASE)
+                                                lets_credit = CREDIT_ENTROPY_NO_WAY;
+                                }
+                        }
+
+                        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");
+                        }
                 }
         }
 
         if (write_seed_file) {
-                /* This is just a safety measure. Given that we are root and
-                 * most likely created the file ourselves the mode and owner
-                 * should be correct anyway. */
-                (void) fchmod_and_chown(seed_fd, 0600, 0, 0);
+                bool getrandom_worked = false;
 
-                k = loop_read(random_fd, buf, buf_size, false);
+                /* This is just a safety measure. Given that we are root and most likely created the file
+                 * ourselves the mode and owner should be correct anyway. */
+                r = fchmod_and_chown(seed_fd, 0600, 0, 0);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to adjust seed file ownership and access mode.");
+
+                /* Let's make this whole job asynchronous, i.e. let's make ourselves a barrier for
+                 * proper initialization of the random pool. */
+                k = getrandom(buf, buf_size, GRND_NONBLOCK);
+                if (k < 0 && errno == EAGAIN && synchronous) {
+                        log_notice("Kernel entropy pool is not initialized yet, waiting until it is.");
+                        k = getrandom(buf, buf_size, 0); /* retry synchronously */
+                }
                 if (k < 0)
-                        return log_error_errno(k, "Failed to read new seed from /dev/urandom: %m");
-                if (k == 0)
-                        return log_error_errno(SYNTHETIC_ERRNO(EIO),
-                                               "Got EOF while reading from /dev/urandom.");
+                        log_debug_errno(errno, "Failed to read random data with getrandom(), falling back to /dev/urandom: %m");
+                else if ((size_t) k < buf_size)
+                        log_debug("Short read from getrandom(), falling back to /dev/urandom: %m");
+                else
+                        getrandom_worked = true;
+
+                if (!getrandom_worked) {
+                        /* Retry with classic /dev/urandom */
+                        k = loop_read(random_fd, buf, buf_size, false);
+                        if (k < 0)
+                                return log_error_errno(k, "Failed to read new seed from /dev/urandom: %m");
+                        if (k == 0)
+                                return log_error_errno(SYNTHETIC_ERRNO(EIO),
+                                                       "Got EOF while reading from /dev/urandom.");
+                }
 
                 r = loop_write(seed_fd, buf, (size_t) k, false);
                 if (r < 0)
                         return log_error_errno(r, "Failed to write new random seed file: %m");
+
+                if (ftruncate(seed_fd, k) < 0)
+                        return log_error_errno(r, "Failed to truncate random seed file: %m");
+
+                r = fsync_full(seed_fd);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to synchronize seed file: %m");
+
+                /* If we got this random seed data from getrandom() the data is suitable for crediting
+                 * 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,
+                                               "Failed to mark seed file as creditable, ignoring: %m");
         }
 
-        return r;
+        return 0;
 }
 
 DEFINE_MAIN_FUNCTION(run);
index 8903ee896cd19d3087a250af1d21da3a737eb547..5a490420e908f1dafb9400bfe2ecffabeb1e6a25 100644 (file)
@@ -22,4 +22,9 @@ Type=oneshot
 RemainAfterExit=yes
 ExecStart=@rootlibexecdir@/systemd-random-seed load
 ExecStop=@rootlibexecdir@/systemd-random-seed save
-TimeoutSec=30s
+
+# This service waits until the kernel's entropy pool is initialized, and may be
+# used as ordering barrier for service that require an initialized entropy
+# pool. Since initialization can take a while on entropy-starved systems, let's
+# increase the time-out substantially here.
+TimeoutSec=10min