LCOV - code coverage report
Current view: top level - random-seed - random-seed.c (source / functions) Hit Total Coverage
Test: systemd_full.info Lines: 0 149 0.0 %
Date: 2019-08-23 13:36:53 Functions: 0 3 0.0 %
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 0 194 0.0 %

           Branch data     Line data    Source code
       1                 :            : /* SPDX-License-Identifier: LGPL-2.1+ */
       2                 :            : 
       3                 :            : #include <errno.h>
       4                 :            : #include <fcntl.h>
       5                 :            : #include <linux/random.h>
       6                 :            : #include <string.h>
       7                 :            : #include <sys/ioctl.h>
       8                 :            : #if USE_SYS_RANDOM_H
       9                 :            : #  include <sys/random.h>
      10                 :            : #endif
      11                 :            : #include <sys/stat.h>
      12                 :            : #include <sys/xattr.h>
      13                 :            : #include <unistd.h>
      14                 :            : 
      15                 :            : #include "sd-id128.h"
      16                 :            : 
      17                 :            : #include "alloc-util.h"
      18                 :            : #include "fd-util.h"
      19                 :            : #include "fs-util.h"
      20                 :            : #include "io-util.h"
      21                 :            : #include "log.h"
      22                 :            : #include "main-func.h"
      23                 :            : #include "missing.h"
      24                 :            : #include "mkdir.h"
      25                 :            : #include "parse-util.h"
      26                 :            : #include "random-util.h"
      27                 :            : #include "string-util.h"
      28                 :            : #include "util.h"
      29                 :            : #include "xattr-util.h"
      30                 :            : 
      31                 :            : typedef enum CreditEntropy {
      32                 :            :         CREDIT_ENTROPY_NO_WAY,
      33                 :            :         CREDIT_ENTROPY_YES_PLEASE,
      34                 :            :         CREDIT_ENTROPY_YES_FORCED,
      35                 :            : } CreditEntropy;
      36                 :            : 
      37                 :          0 : static CreditEntropy may_credit(int seed_fd) {
      38                 :          0 :         _cleanup_free_ char *creditable = NULL;
      39                 :            :         const char *e;
      40                 :            :         int r;
      41                 :            : 
      42         [ #  # ]:          0 :         assert(seed_fd >= 0);
      43                 :            : 
      44                 :          0 :         e = getenv("SYSTEMD_RANDOM_SEED_CREDIT");
      45         [ #  # ]:          0 :         if (!e) {
      46         [ #  # ]:          0 :                 log_debug("$SYSTEMD_RANDOM_SEED_CREDIT is not set, not crediting entropy.");
      47                 :          0 :                 return CREDIT_ENTROPY_NO_WAY;
      48                 :            :         }
      49         [ #  # ]:          0 :         if (streq(e, "force")) {
      50         [ #  # ]:          0 :                 log_debug("$SYSTEMD_RANDOM_SEED_CREDIT is set to 'force', crediting entropy.");
      51                 :          0 :                 return CREDIT_ENTROPY_YES_FORCED;
      52                 :            :         }
      53                 :            : 
      54                 :          0 :         r = parse_boolean(e);
      55         [ #  # ]:          0 :         if (r <= 0) {
      56         [ #  # ]:          0 :                 if (r < 0)
      57         [ #  # ]:          0 :                         log_warning_errno(r, "Failed to parse $SYSTEMD_RANDOM_SEED_CREDIT, not crediting entropy: %m");
      58                 :            :                 else
      59         [ #  # ]:          0 :                         log_debug("Crediting entropy is turned off via $SYSTEMD_RANDOM_SEED_CREDIT, not crediting entropy.");
      60                 :            : 
      61                 :          0 :                 return CREDIT_ENTROPY_NO_WAY;
      62                 :            :         }
      63                 :            : 
      64                 :            :         /* Determine if the file is marked as creditable */
      65                 :          0 :         r = fgetxattr_malloc(seed_fd, "user.random-seed-creditable", &creditable);
      66         [ #  # ]:          0 :         if (r < 0) {
      67   [ #  #  #  # ]:          0 :                 if (IN_SET(r, -ENODATA, -ENOSYS, -EOPNOTSUPP))
      68         [ #  # ]:          0 :                         log_debug_errno(r, "Seed file is not marked as creditable, not crediting.");
      69                 :            :                 else
      70         [ #  # ]:          0 :                         log_warning_errno(r, "Failed to read extended attribute, ignoring: %m");
      71                 :            : 
      72                 :          0 :                 return CREDIT_ENTROPY_NO_WAY;
      73                 :            :         }
      74                 :            : 
      75                 :          0 :         r = parse_boolean(creditable);
      76         [ #  # ]:          0 :         if (r <= 0) {
      77         [ #  # ]:          0 :                 if (r < 0)
      78         [ #  # ]:          0 :                         log_warning_errno(r, "Failed to parse user.random-seed-creditable extended attribute, ignoring: %s", creditable);
      79                 :            :                 else
      80         [ #  # ]:          0 :                         log_debug("Seed file is marked as not creditable, not crediting.");
      81                 :            : 
      82                 :          0 :                 return CREDIT_ENTROPY_NO_WAY;
      83                 :            :         }
      84                 :            : 
      85                 :            :         /* Don't credit the random seed if we are in first-boot mode, because we are supposed to start from
      86                 :            :          * scratch. This is a safety precaution for cases where we people ship "golden" images with empty
      87                 :            :          * /etc but populated /var that contains a random seed. */
      88         [ #  # ]:          0 :         if (access("/run/systemd/first-boot", F_OK) < 0) {
      89                 :            : 
      90         [ #  # ]:          0 :                 if (errno != ENOENT) {
      91         [ #  # ]:          0 :                         log_warning_errno(errno, "Failed to check whether we are in first-boot mode, not crediting entropy: %m");
      92                 :          0 :                         return CREDIT_ENTROPY_NO_WAY;
      93                 :            :                 }
      94                 :            : 
      95                 :            :                 /* If ENOENT all is good, we are not in first-boot mode. */
      96                 :            :         } else {
      97         [ #  # ]:          0 :                 log_debug("Not crediting entropy, since booted in first-boot mode.");
      98                 :          0 :                 return CREDIT_ENTROPY_NO_WAY;
      99                 :            :         }
     100                 :            : 
     101                 :          0 :         return CREDIT_ENTROPY_YES_PLEASE;
     102                 :            : }
     103                 :            : 
     104                 :          0 : static int run(int argc, char *argv[]) {
     105                 :          0 :         _cleanup_close_ int seed_fd = -1, random_fd = -1;
     106                 :            :         bool read_seed_file, write_seed_file, synchronous;
     107                 :          0 :         _cleanup_free_ void* buf = NULL;
     108                 :            :         size_t buf_size;
     109                 :            :         struct stat st;
     110                 :            :         ssize_t k;
     111                 :            :         int r;
     112                 :            : 
     113                 :          0 :         log_setup_service();
     114                 :            : 
     115         [ #  # ]:          0 :         if (argc != 2)
     116         [ #  # ]:          0 :                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
     117                 :            :                                        "This program requires one argument.");
     118                 :            : 
     119                 :          0 :         umask(0022);
     120                 :            : 
     121                 :          0 :         buf_size = random_pool_size();
     122                 :            : 
     123                 :          0 :         r = mkdir_parents(RANDOM_SEED, 0755);
     124         [ #  # ]:          0 :         if (r < 0)
     125         [ #  # ]:          0 :                 return log_error_errno(r, "Failed to create directory " RANDOM_SEED_DIR ": %m");
     126                 :            : 
     127                 :            :         /* When we load the seed we read it and write it to the device and then immediately update the saved seed with
     128                 :            :          * new data, to make sure the next boot gets seeded differently. */
     129                 :            : 
     130         [ #  # ]:          0 :         if (streq(argv[1], "load")) {
     131                 :            : 
     132                 :          0 :                 seed_fd = open(RANDOM_SEED, O_RDWR|O_CLOEXEC|O_NOCTTY|O_CREAT, 0600);
     133         [ #  # ]:          0 :                 if (seed_fd < 0) {
     134                 :          0 :                         int open_rw_error = -errno;
     135                 :            : 
     136                 :          0 :                         write_seed_file = false;
     137                 :            : 
     138                 :          0 :                         seed_fd = open(RANDOM_SEED, O_RDONLY|O_CLOEXEC|O_NOCTTY);
     139         [ #  # ]:          0 :                         if (seed_fd < 0) {
     140                 :          0 :                                 bool missing = errno == ENOENT;
     141                 :            : 
     142   [ #  #  #  # ]:          0 :                                 log_full_errno(missing ? LOG_DEBUG : LOG_ERR,
     143                 :            :                                                open_rw_error, "Failed to open " RANDOM_SEED " for writing: %m");
     144   [ #  #  #  # ]:          0 :                                 r = log_full_errno(missing ? LOG_DEBUG : LOG_ERR,
     145                 :            :                                                    errno, "Failed to open " RANDOM_SEED " for reading: %m");
     146         [ #  # ]:          0 :                                 return missing ? 0 : r;
     147                 :            :                         }
     148                 :            :                 } else
     149                 :          0 :                         write_seed_file = true;
     150                 :            : 
     151                 :          0 :                 random_fd = open("/dev/urandom", O_RDWR|O_CLOEXEC|O_NOCTTY, 0600);
     152         [ #  # ]:          0 :                 if (random_fd < 0)
     153         [ #  # ]:          0 :                         return log_error_errno(errno, "Failed to open /dev/urandom: %m");
     154                 :            : 
     155                 :          0 :                 read_seed_file = true;
     156                 :          0 :                 synchronous = true; /* make this invocation a synchronous barrier for random pool initialization */
     157                 :            : 
     158         [ #  # ]:          0 :         } else if (streq(argv[1], "save")) {
     159                 :            : 
     160                 :          0 :                 random_fd = open("/dev/urandom", O_RDONLY|O_CLOEXEC|O_NOCTTY);
     161         [ #  # ]:          0 :                 if (random_fd < 0)
     162         [ #  # ]:          0 :                         return log_error_errno(errno, "Failed to open /dev/urandom: %m");
     163                 :            : 
     164                 :          0 :                 seed_fd = open(RANDOM_SEED, O_WRONLY|O_CLOEXEC|O_NOCTTY|O_CREAT, 0600);
     165         [ #  # ]:          0 :                 if (seed_fd < 0)
     166         [ #  # ]:          0 :                         return log_error_errno(errno, "Failed to open " RANDOM_SEED ": %m");
     167                 :            : 
     168                 :          0 :                 read_seed_file = false;
     169                 :          0 :                 write_seed_file = true;
     170                 :          0 :                 synchronous = false;
     171                 :            :         } else
     172         [ #  # ]:          0 :                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
     173                 :            :                                        "Unknown verb '%s'.", argv[1]);
     174                 :            : 
     175         [ #  # ]:          0 :         if (fstat(seed_fd, &st) < 0)
     176         [ #  # ]:          0 :                 return log_error_errno(errno, "Failed to stat() seed file " RANDOM_SEED ": %m");
     177                 :            : 
     178                 :            :         /* If the seed file is larger than what we expect, then honour the existing size and save/restore as much as it says */
     179         [ #  # ]:          0 :         if ((uint64_t) st.st_size > buf_size)
     180                 :          0 :                 buf_size = MIN(st.st_size, RANDOM_POOL_SIZE_MAX);
     181                 :            : 
     182                 :          0 :         buf = malloc(buf_size);
     183         [ #  # ]:          0 :         if (!buf)
     184                 :          0 :                 return log_oom();
     185                 :            : 
     186         [ #  # ]:          0 :         if (read_seed_file) {
     187                 :            :                 sd_id128_t mid;
     188                 :            : 
     189                 :            :                 /* First, let's write the machine ID into /dev/urandom, not crediting entropy. Why? As an
     190                 :            :                  * extra protection against "golden images" that are put together sloppily, i.e. images which
     191                 :            :                  * are duplicated on multiple systems but where the random seed file is not properly
     192                 :            :                  * reset. Frequently the machine ID is properly reset on those systems however (simply
     193                 :            :                  * because it's easier to notice, if it isn't due to address clashes and so on, while random
     194                 :            :                  * seed equivalence is generally not noticed easily), hence let's simply write the machined
     195                 :            :                  * ID into the random pool too. */
     196                 :          0 :                 r = sd_id128_get_machine(&mid);
     197         [ #  # ]:          0 :                 if (r < 0)
     198         [ #  # ]:          0 :                         log_debug_errno(r, "Failed to get machine ID, ignoring: %m");
     199                 :            :                 else {
     200                 :          0 :                         r = loop_write(random_fd, &mid, sizeof(mid), false);
     201         [ #  # ]:          0 :                         if (r < 0)
     202         [ #  # ]:          0 :                                 log_debug_errno(r, "Failed to write machine ID to /dev/urandom, ignoring: %m");
     203                 :            :                 }
     204                 :            : 
     205                 :          0 :                 k = loop_read(seed_fd, buf, buf_size, false);
     206         [ #  # ]:          0 :                 if (k < 0)
     207         [ #  # ]:          0 :                         log_error_errno(k, "Failed to read seed from " RANDOM_SEED ": %m");
     208         [ #  # ]:          0 :                 else if (k == 0)
     209         [ #  # ]:          0 :                         log_debug("Seed file " RANDOM_SEED " not yet initialized, proceeding.");
     210                 :            :                 else {
     211                 :            :                         CreditEntropy lets_credit;
     212                 :            : 
     213                 :          0 :                         (void) lseek(seed_fd, 0, SEEK_SET);
     214                 :            : 
     215                 :          0 :                         lets_credit = may_credit(seed_fd);
     216                 :            : 
     217                 :            :                         /* Before we credit or use the entropy, let's make sure to securely drop the
     218                 :            :                          * creditable xattr from the file, so that we never credit the same random seed
     219                 :            :                          * again. Note that further down we'll write a new seed again, and likely mark it as
     220                 :            :                          * credible again, hence this is just paranoia to close the short time window between
     221                 :            :                          * the time we upload the random seed into the kernel and download the new one from
     222                 :            :                          * it. */
     223                 :            : 
     224         [ #  # ]:          0 :                         if (fremovexattr(seed_fd, "user.random-seed-creditable") < 0) {
     225   [ #  #  #  # ]:          0 :                                 if (!IN_SET(errno, ENODATA, ENOSYS, EOPNOTSUPP))
     226         [ #  # ]:          0 :                                         log_warning_errno(errno, "Failed to remove extended attribute, ignoring: %m");
     227                 :            : 
     228                 :            :                                 /* Otherwise, there was no creditable flag set, which is OK. */
     229                 :            :                         } else {
     230                 :          0 :                                 r = fsync_full(seed_fd);
     231         [ #  # ]:          0 :                                 if (r < 0) {
     232         [ #  # ]:          0 :                                         log_warning_errno(r, "Failed to synchronize seed to disk, not crediting entropy: %m");
     233                 :            : 
     234         [ #  # ]:          0 :                                         if (lets_credit == CREDIT_ENTROPY_YES_PLEASE)
     235                 :          0 :                                                 lets_credit = CREDIT_ENTROPY_NO_WAY;
     236                 :            :                                 }
     237                 :            :                         }
     238                 :            : 
     239   [ #  #  #  # ]:          0 :                         if (IN_SET(lets_credit, CREDIT_ENTROPY_YES_PLEASE, CREDIT_ENTROPY_YES_FORCED)) {
     240         [ #  # ]:          0 :                                 _cleanup_free_ struct rand_pool_info *info = NULL;
     241                 :            : 
     242                 :          0 :                                 info = malloc(offsetof(struct rand_pool_info, buf) + k);
     243         [ #  # ]:          0 :                                 if (!info)
     244                 :          0 :                                         return log_oom();
     245                 :            : 
     246                 :          0 :                                 info->entropy_count = k * 8;
     247                 :          0 :                                 info->buf_size = k;
     248                 :          0 :                                 memcpy(info->buf, buf, k);
     249                 :            : 
     250         [ #  # ]:          0 :                                 if (ioctl(random_fd, RNDADDENTROPY, info) < 0)
     251         [ #  # ]:          0 :                                         return log_warning_errno(errno, "Failed to credit entropy, ignoring: %m");
     252                 :            :                         } else {
     253                 :          0 :                                 r = loop_write(random_fd, buf, (size_t) k, false);
     254         [ #  # ]:          0 :                                 if (r < 0)
     255         [ #  # ]:          0 :                                         log_error_errno(r, "Failed to write seed to /dev/urandom: %m");
     256                 :            :                         }
     257                 :            :                 }
     258                 :            :         }
     259                 :            : 
     260         [ #  # ]:          0 :         if (write_seed_file) {
     261                 :          0 :                 bool getrandom_worked = false;
     262                 :            : 
     263                 :            :                 /* This is just a safety measure. Given that we are root and most likely created the file
     264                 :            :                  * ourselves the mode and owner should be correct anyway. */
     265                 :          0 :                 r = fchmod_and_chown(seed_fd, 0600, 0, 0);
     266         [ #  # ]:          0 :                 if (r < 0)
     267         [ #  # ]:          0 :                         return log_error_errno(r, "Failed to adjust seed file ownership and access mode.");
     268                 :            : 
     269                 :            :                 /* Let's make this whole job asynchronous, i.e. let's make ourselves a barrier for
     270                 :            :                  * proper initialization of the random pool. */
     271                 :          0 :                 k = getrandom(buf, buf_size, GRND_NONBLOCK);
     272   [ #  #  #  #  :          0 :                 if (k < 0 && errno == EAGAIN && synchronous) {
                   #  # ]
     273         [ #  # ]:          0 :                         log_notice("Kernel entropy pool is not initialized yet, waiting until it is.");
     274                 :          0 :                         k = getrandom(buf, buf_size, 0); /* retry synchronously */
     275                 :            :                 }
     276         [ #  # ]:          0 :                 if (k < 0)
     277         [ #  # ]:          0 :                         log_debug_errno(errno, "Failed to read random data with getrandom(), falling back to /dev/urandom: %m");
     278         [ #  # ]:          0 :                 else if ((size_t) k < buf_size)
     279         [ #  # ]:          0 :                         log_debug("Short read from getrandom(), falling back to /dev/urandom: %m");
     280                 :            :                 else
     281                 :          0 :                         getrandom_worked = true;
     282                 :            : 
     283         [ #  # ]:          0 :                 if (!getrandom_worked) {
     284                 :            :                         /* Retry with classic /dev/urandom */
     285                 :          0 :                         k = loop_read(random_fd, buf, buf_size, false);
     286         [ #  # ]:          0 :                         if (k < 0)
     287         [ #  # ]:          0 :                                 return log_error_errno(k, "Failed to read new seed from /dev/urandom: %m");
     288         [ #  # ]:          0 :                         if (k == 0)
     289         [ #  # ]:          0 :                                 return log_error_errno(SYNTHETIC_ERRNO(EIO),
     290                 :            :                                                        "Got EOF while reading from /dev/urandom.");
     291                 :            :                 }
     292                 :            : 
     293                 :          0 :                 r = loop_write(seed_fd, buf, (size_t) k, false);
     294         [ #  # ]:          0 :                 if (r < 0)
     295         [ #  # ]:          0 :                         return log_error_errno(r, "Failed to write new random seed file: %m");
     296                 :            : 
     297         [ #  # ]:          0 :                 if (ftruncate(seed_fd, k) < 0)
     298         [ #  # ]:          0 :                         return log_error_errno(r, "Failed to truncate random seed file: %m");
     299                 :            : 
     300                 :          0 :                 r = fsync_full(seed_fd);
     301         [ #  # ]:          0 :                 if (r < 0)
     302         [ #  # ]:          0 :                         return log_error_errno(r, "Failed to synchronize seed file: %m");
     303                 :            : 
     304                 :            :                 /* If we got this random seed data from getrandom() the data is suitable for crediting
     305                 :            :                  * entropy later on. Let's keep that in mind by setting an extended attribute. on the file */
     306         [ #  # ]:          0 :                 if (getrandom_worked)
     307         [ #  # ]:          0 :                         if (fsetxattr(seed_fd, "user.random-seed-creditable", "1", 1, 0) < 0)
     308   [ #  #  #  #  :          0 :                                 log_full_errno(IN_SET(errno, ENOSYS, EOPNOTSUPP) ? LOG_DEBUG : LOG_WARNING, errno,
                   #  # ]
     309                 :            :                                                "Failed to mark seed file as creditable, ignoring: %m");
     310                 :            :         }
     311                 :            : 
     312                 :          0 :         return 0;
     313                 :            : }
     314                 :            : 
     315                 :          0 : DEFINE_MAIN_FUNCTION(run);

Generated by: LCOV version 1.14