busybox/miscutils/seedrng.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0 OR MIT
   2/*
   3 * Copyright (C) 2022 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
   4 *
   5 * SeedRNG is a simple program made for seeding the Linux kernel random number
   6 * generator from seed files. It is is useful in light of the fact that the
   7 * Linux kernel RNG cannot be initialized from shell scripts, and new seeds
   8 * cannot be safely generated from boot time shell scripts either. It should
   9 * be run once at init time and once at shutdown time. It can be run at other
  10 * times on a timer as well. Whenever it is run, it writes existing seed files
  11 * into the RNG pool, and then creates a new seed file. If the RNG is
  12 * initialized at the time of creating a new seed file, then that new seed file
  13 * is marked as "creditable", which means it can be used to initialize the RNG.
  14 * Otherwise, it is marked as "non-creditable", in which case it is still used
  15 * to seed the RNG's pool, but will not initialize the RNG. In order to ensure
  16 * that entropy only ever stays the same or increases from one seed file to the
  17 * next, old seed values are hashed together with new seed values when writing
  18 * new seed files.
  19 *
  20 * This is based on code from <https://git.zx2c4.com/seedrng/about/>.
  21 */
  22//config:config SEEDRNG
  23//config:       bool "seedrng (1.3 kb)"
  24//config:       default y
  25//config:       help
  26//config:       Seed the kernel RNG from seed files, meant to be called
  27//config:       once during startup, once during shutdown, and optionally
  28//config:       at some periodic interval in between.
  29
  30//applet:IF_SEEDRNG(APPLET(seedrng, BB_DIR_USR_SBIN, BB_SUID_DROP))
  31
  32//kbuild:lib-$(CONFIG_SEEDRNG) += seedrng.o
  33
  34//usage:#define seedrng_trivial_usage
  35//usage:        "[-d DIR] [-n]"
  36//usage:#define seedrng_full_usage "\n\n"
  37//usage:        "Seed the kernel RNG from seed files"
  38//usage:        "\n"
  39//usage:        "\n     -d DIR  Use seed files in DIR (default: /var/lib/seedrng)"
  40//usage:        "\n     -n      Do not credit randomness, even if creditable"
  41
  42#include "libbb.h"
  43
  44#include <linux/random.h>
  45#include <sys/random.h>
  46#include <sys/file.h>
  47
  48#ifndef GRND_INSECURE
  49#define GRND_INSECURE 0x0004 /* Apparently some headers don't ship with this yet. */
  50#endif
  51
  52#define DEFAULT_SEED_DIR         "/var/lib/seedrng"
  53#define CREDITABLE_SEED_NAME     "seed.credit"
  54#define NON_CREDITABLE_SEED_NAME "seed.no-credit"
  55
  56enum {
  57        MIN_SEED_LEN = SHA256_OUTSIZE,
  58        /* kernels < 5.18 could return short reads from getrandom()
  59         * if signal is pending and length is > 256.
  60         * Let's limit our reads to 256 bytes.
  61         */
  62        MAX_SEED_LEN = 256,
  63};
  64
  65static size_t determine_optimal_seed_len(void)
  66{
  67        char poolsize_str[12];
  68        unsigned poolsize;
  69        int n;
  70
  71        n = open_read_close("/proc/sys/kernel/random/poolsize", poolsize_str, sizeof(poolsize_str) - 1);
  72        if (n < 0) {
  73                bb_perror_msg("can't determine pool size, assuming %u bits", MIN_SEED_LEN * 8);
  74                return MIN_SEED_LEN;
  75        }
  76        poolsize_str[n] = '\0';
  77        poolsize = (bb_strtou(poolsize_str, NULL, 10) + 7) / 8;
  78        return MAX(MIN(poolsize, MAX_SEED_LEN), MIN_SEED_LEN);
  79}
  80
  81static bool read_new_seed(uint8_t *seed, size_t len)
  82{
  83        bool is_creditable;
  84        ssize_t ret;
  85
  86        ret = getrandom(seed, len, GRND_NONBLOCK);
  87        if (ret == (ssize_t)len) {
  88                return true;
  89        }
  90        if (ret < 0 && errno == ENOSYS) {
  91                int fd = xopen("/dev/random", O_RDONLY);
  92                struct pollfd random_fd;
  93                random_fd.fd = fd;
  94                random_fd.events = POLLIN;
  95                is_creditable = poll(&random_fd, 1, 0) == 1;
  96//This is racy. is_creditable can be set to true here, but other process
  97//can consume "good" random data from /dev/urandom before we do it below.
  98                close(fd);
  99        } else {
 100                if (getrandom(seed, len, GRND_INSECURE) == (ssize_t)len)
 101                        return false;
 102                is_creditable = false;
 103        }
 104
 105        /* Either getrandom() is not implemented, or
 106         * getrandom(GRND_INSECURE) did not give us LEN bytes.
 107         * Fallback to reading /dev/urandom.
 108         */
 109        errno = 0;
 110        if (open_read_close("/dev/urandom", seed, len) != (ssize_t)len)
 111                bb_perror_msg_and_die("can't read '%s'", "/dev/urandom");
 112        return is_creditable;
 113}
 114
 115static void seed_from_file_if_exists(const char *filename, int dfd, bool credit, sha256_ctx_t *hash)
 116{
 117        struct {
 118                int entropy_count;
 119                int buf_size;
 120                uint8_t buf[MAX_SEED_LEN];
 121        } req;
 122        ssize_t seed_len;
 123
 124        seed_len = open_read_close(filename, req.buf, sizeof(req.buf));
 125        if (seed_len < 0) {
 126                if (errno != ENOENT)
 127                        bb_perror_msg_and_die("can't read '%s'", filename);
 128                return;
 129        }
 130        xunlink(filename);
 131        if (seed_len != 0) {
 132                int fd;
 133
 134                /* We are going to use this data to seed the RNG:
 135                 * we believe it to genuinely containing entropy.
 136                 * If this just-unlinked file survives
 137                 * (if machine crashes before deletion is recorded on disk)
 138                 * and we reuse it after reboot, this assumption
 139                 * would be violated, and RNG may end up generating
 140                 * the same data. fsync the directory
 141                 * to make sure file is gone:
 142                 */
 143                if (fsync(dfd) != 0)
 144                        bb_simple_perror_msg_and_die("I/O error");
 145
 146//Length is not random, and taking its address spills variable to stack
 147//              sha256_hash(hash, &seed_len, sizeof(seed_len));
 148                sha256_hash(hash, req.buf, seed_len);
 149
 150                req.buf_size = seed_len;
 151                seed_len *= 8;
 152                req.entropy_count = credit ? seed_len : 0;
 153                printf("Seeding %u bits %s crediting\n",
 154                                (unsigned)seed_len, credit ? "and" : "without");
 155                fd = xopen("/dev/urandom", O_RDONLY);
 156                xioctl(fd, RNDADDENTROPY, &req);
 157                if (ENABLE_FEATURE_CLEAN_UP)
 158                        close(fd);
 159        }
 160}
 161
 162int seedrng_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
 163int seedrng_main(int argc UNUSED_PARAM, char **argv)
 164{
 165        const char *seed_dir;
 166        int fd, dfd;
 167        int i;
 168        unsigned opts;
 169        uint8_t new_seed[MAX_SEED_LEN];
 170        size_t new_seed_len;
 171        bool new_seed_creditable;
 172        struct timespec timestamp[2];
 173        sha256_ctx_t hash;
 174
 175        enum {
 176                OPT_n = (1 << 0), /* must be 1 */
 177                OPT_d = (1 << 1),
 178        };
 179#if ENABLE_LONG_OPTS
 180        static const char longopts[] ALIGN1 =
 181                "skip-credit\0" No_argument       "n"
 182                "seed-dir\0"    Required_argument "d"
 183                ;
 184#endif
 185
 186        seed_dir = DEFAULT_SEED_DIR;
 187        opts = getopt32long(argv, "nd:", longopts, &seed_dir);
 188        umask(0077);
 189        if (getuid() != 0)
 190                bb_simple_error_msg_and_die(bb_msg_you_must_be_root);
 191
 192        if (mkdir(seed_dir, 0700) < 0 && errno != EEXIST)
 193                bb_perror_msg_and_die("can't create directory '%s'", seed_dir);
 194        dfd = xopen(seed_dir, O_DIRECTORY | O_RDONLY);
 195        xfchdir(dfd);
 196        /* Concurrent runs of this tool might feed the same data to RNG twice.
 197         * Avoid concurrent runs by taking a blocking lock on the directory.
 198         * Not checking for errors. Looking at manpage,
 199         * ENOLCK "The kernel ran out of memory for allocating lock records"
 200         * seems to be the only one which is possible - and if that happens,
 201         * machine is OOMing (much worse problem than inability to lock...).
 202         * Also, typically configured Linux machines do not fail GFP_KERNEL
 203         * allocations (they trigger memory reclaim instead).
 204         */
 205        flock(dfd, LOCK_EX); /* blocks while another instance runs */
 206
 207        sha256_begin(&hash);
 208//Hashing in a constant string doesn't add any entropy
 209//      sha256_hash(&hash, "SeedRNG v1 Old+New Prefix", 25);
 210        clock_gettime(CLOCK_REALTIME, &timestamp[0]);
 211        clock_gettime(CLOCK_BOOTTIME, &timestamp[1]);
 212        sha256_hash(&hash, timestamp, sizeof(timestamp));
 213
 214        for (i = 0; i <= 1; i++) {
 215                seed_from_file_if_exists(
 216                        i == 0 ? NON_CREDITABLE_SEED_NAME : CREDITABLE_SEED_NAME,
 217                        dfd,
 218                        /*credit?*/ (opts ^ OPT_n) & i, /* 0, then 1 unless -n */
 219                        &hash);
 220        }
 221
 222        new_seed_len = determine_optimal_seed_len();
 223        new_seed_creditable = read_new_seed(new_seed, new_seed_len);
 224//Length is not random, and taking its address spills variable to stack
 225//      sha256_hash(&hash, &new_seed_len, sizeof(new_seed_len));
 226        sha256_hash(&hash, new_seed, new_seed_len);
 227        sha256_end(&hash, new_seed + new_seed_len - SHA256_OUTSIZE);
 228
 229        printf("Saving %u bits of %screditable seed for next boot\n",
 230                (unsigned)new_seed_len * 8, new_seed_creditable ? "" : "non-");
 231        fd = xopen3(NON_CREDITABLE_SEED_NAME, O_WRONLY | O_CREAT | O_TRUNC, 0400);
 232        xwrite(fd, new_seed, new_seed_len);
 233        if (new_seed_creditable) {
 234                /* More paranoia when we create a file which we believe contains
 235                 * genuine entropy: make sure disk is not full, quota isn't exceeded, etc:
 236                 */
 237                if (fsync(fd) < 0)
 238                        bb_perror_msg_and_die("can't write '%s'", NON_CREDITABLE_SEED_NAME);
 239                xrename(NON_CREDITABLE_SEED_NAME, CREDITABLE_SEED_NAME);
 240        }
 241        return EXIT_SUCCESS;
 242}
 243