diff options
| author | Denys Vlasenko <vda.linux@googlemail.com> | 2022-05-02 14:53:14 +0200 |
|---|---|---|
| committer | Denys Vlasenko <vda.linux@googlemail.com> | 2022-05-02 14:53:14 +0200 |
| commit | 5ba56e8b95ea84dbd7c0f7adfb9bdb1740480904 (patch) | |
| tree | db46c43aaa0a7915ea10f4dcc7a0edd6d8ec97d7 /miscutils | |
| parent | 1a290f889c5103d867ba1e0715ae730b394a3a12 (diff) | |
| download | busybox-w32-5ba56e8b95ea84dbd7c0f7adfb9bdb1740480904.tar.gz busybox-w32-5ba56e8b95ea84dbd7c0f7adfb9bdb1740480904.tar.bz2 busybox-w32-5ba56e8b95ea84dbd7c0f7adfb9bdb1740480904.zip | |
seedrng: it's not a part of util-linux, move to miscutils
Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
Diffstat (limited to 'miscutils')
| -rw-r--r-- | miscutils/seedrng.c | 239 |
1 files changed, 239 insertions, 0 deletions
diff --git a/miscutils/seedrng.c b/miscutils/seedrng.c new file mode 100644 index 000000000..8c81835f6 --- /dev/null +++ b/miscutils/seedrng.c | |||
| @@ -0,0 +1,239 @@ | |||
| 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 | |||
| 56 | enum { | ||
| 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 | |||
| 65 | static 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 | |||
| 81 | static 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 | |||
| 115 | static 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 | * (e.g. if machine crashes _right now_) | ||
| 138 | * and we reuse it after reboot, this assumption | ||
| 139 | * would be violated. Fsync the directory to | ||
| 140 | * make sure file is gone: | ||
| 141 | */ | ||
| 142 | fsync(dfd); | ||
| 143 | |||
| 144 | //Length is not random, and taking its address spills variable to stack | ||
| 145 | // sha256_hash(hash, &seed_len, sizeof(seed_len)); | ||
| 146 | sha256_hash(hash, req.buf, seed_len); | ||
| 147 | |||
| 148 | req.buf_size = seed_len; | ||
| 149 | seed_len *= 8; | ||
| 150 | req.entropy_count = credit ? seed_len : 0; | ||
| 151 | printf("Seeding %u bits %s crediting\n", | ||
| 152 | (unsigned)seed_len, credit ? "and" : "without"); | ||
| 153 | fd = xopen("/dev/urandom", O_RDONLY); | ||
| 154 | xioctl(fd, RNDADDENTROPY, &req); | ||
| 155 | if (ENABLE_FEATURE_CLEAN_UP) | ||
| 156 | close(fd); | ||
| 157 | } | ||
| 158 | } | ||
| 159 | |||
| 160 | int seedrng_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; | ||
| 161 | int seedrng_main(int argc UNUSED_PARAM, char **argv) | ||
| 162 | { | ||
| 163 | const char *seed_dir; | ||
| 164 | int fd, dfd; | ||
| 165 | int i; | ||
| 166 | unsigned opts; | ||
| 167 | uint8_t new_seed[MAX_SEED_LEN]; | ||
| 168 | size_t new_seed_len; | ||
| 169 | bool new_seed_creditable; | ||
| 170 | struct timespec timestamp; | ||
| 171 | sha256_ctx_t hash; | ||
| 172 | |||
| 173 | enum { | ||
| 174 | OPT_n = (1 << 0), /* must be 1 */ | ||
| 175 | OPT_d = (1 << 1), | ||
| 176 | }; | ||
| 177 | #if ENABLE_LONG_OPTS | ||
| 178 | static const char longopts[] ALIGN1 = | ||
| 179 | "skip-credit\0" No_argument "n" | ||
| 180 | "seed-dir\0" Required_argument "d" | ||
| 181 | ; | ||
| 182 | #endif | ||
| 183 | |||
| 184 | seed_dir = DEFAULT_SEED_DIR; | ||
| 185 | opts = getopt32long(argv, "nd:", longopts, &seed_dir); | ||
| 186 | umask(0077); | ||
| 187 | if (getuid() != 0) | ||
| 188 | bb_simple_error_msg_and_die(bb_msg_you_must_be_root); | ||
| 189 | |||
| 190 | if (mkdir(seed_dir, 0700) < 0 && errno != EEXIST) | ||
| 191 | bb_perror_msg_and_die("can't create directory '%s'", seed_dir); | ||
| 192 | dfd = xopen(seed_dir, O_DIRECTORY | O_RDONLY); | ||
| 193 | xfchdir(dfd); | ||
| 194 | /* Concurrent runs of this tool might feed the same data to RNG twice. | ||
| 195 | * Avoid concurrent runs by taking a blocking lock on the directory. | ||
| 196 | * Not checking for errors. Looking at manpage, | ||
| 197 | * ENOLCK "The kernel ran out of memory for allocating lock records" | ||
| 198 | * seems to be the only one which is likely - and if that happens, | ||
| 199 | * machine is OOMing (much worse problem than inability to lock...). | ||
| 200 | * Also, typically configured Linux machines do not fail GFP_KERNEL | ||
| 201 | * allocations (they trigger memory reclaim instead). | ||
| 202 | */ | ||
| 203 | flock(dfd, LOCK_EX); /* would block while another copy runs */ | ||
| 204 | |||
| 205 | sha256_begin(&hash); | ||
| 206 | sha256_hash(&hash, "SeedRNG v1 Old+New Prefix", 25); | ||
| 207 | clock_gettime(CLOCK_REALTIME, ×tamp); | ||
| 208 | sha256_hash(&hash, ×tamp, sizeof(timestamp)); | ||
| 209 | clock_gettime(CLOCK_BOOTTIME, ×tamp); | ||
| 210 | sha256_hash(&hash, ×tamp, sizeof(timestamp)); | ||
| 211 | |||
| 212 | for (i = 0; i <= 1; i++) { | ||
| 213 | seed_from_file_if_exists(i == 0 ? NON_CREDITABLE_SEED_NAME : CREDITABLE_SEED_NAME, | ||
| 214 | dfd, | ||
| 215 | /* credit? */ (opts ^ OPT_n) & i, /* 0, then 1 unless -n */ | ||
| 216 | &hash); | ||
| 217 | } | ||
| 218 | |||
| 219 | new_seed_len = determine_optimal_seed_len(); | ||
| 220 | new_seed_creditable = read_new_seed(new_seed, new_seed_len); | ||
| 221 | //Length is not random, and taking its address spills variable to stack | ||
| 222 | // sha256_hash(&hash, &new_seed_len, sizeof(new_seed_len)); | ||
| 223 | sha256_hash(&hash, new_seed, new_seed_len); | ||
| 224 | sha256_end(&hash, new_seed + new_seed_len - SHA256_OUTSIZE); | ||
| 225 | |||
| 226 | printf("Saving %u bits of %screditable seed for next boot\n", | ||
| 227 | (unsigned)new_seed_len * 8, new_seed_creditable ? "" : "non-"); | ||
| 228 | fd = xopen3(NON_CREDITABLE_SEED_NAME, O_WRONLY | O_CREAT | O_TRUNC, 0400); | ||
| 229 | xwrite(fd, new_seed, new_seed_len); | ||
| 230 | if (new_seed_creditable) { | ||
| 231 | /* More paranoia when we create a file which we believe contains | ||
| 232 | * genuine entropy: make sure disk is not full, quota isn't exceeded, etc: | ||
| 233 | */ | ||
| 234 | if (fsync(fd) < 0) | ||
| 235 | bb_perror_msg_and_die("can't write '%s'", NON_CREDITABLE_SEED_NAME); | ||
| 236 | xrename(NON_CREDITABLE_SEED_NAME, CREDITABLE_SEED_NAME); | ||
| 237 | } | ||
| 238 | return EXIT_SUCCESS; | ||
| 239 | } | ||
