aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJason A. Donenfeld <Jason@zx2c4.com>2022-04-04 18:21:51 +0200
committerBernhard Reutner-Fischer <rep.dot.nop@gmail.com>2022-04-20 15:20:29 +0200
commit4b407bacd4c1628782d24c3e044e43780bb057a4 (patch)
tree1218b1d3dbe540289677b2469fb4083219211dbe
parentfc7868602ecf0d761a9a877141add4a9b6918d02 (diff)
downloadbusybox-w32-4b407bacd4c1628782d24c3e044e43780bb057a4.tar.gz
busybox-w32-4b407bacd4c1628782d24c3e044e43780bb057a4.tar.bz2
busybox-w32-4b407bacd4c1628782d24c3e044e43780bb057a4.zip
seedrng: import SeedRNG utility for kernel RNG seed files
The RNG can't actually be seeded from a shell script, due to the reliance on ioctls and the fact that entropy written into the unprivileged /dev/urandom device is not immediately mixed in, making subsequent seed reads dangerous. For this reason, the seedrng project provides a basic "C script" meant to be copy and pasted into projects like Busybox and tweaked as needed: <https://git.zx2c4.com/seedrng/about/>. The SeedRNG construction has been part of systemd's seeder since January, and recently was added to Android, OpenRC, and Void's Runit, with more integrations on their way depending on context. Virtually every single Busybox-based distro I have seen seeds things in wrong, incomplete, or otherwise dangerous way. For example, fixing this issue in Buildroot requires first for Busybox to have this fix. This commit imports it into Busybox and wires up the basic config. The utility itself is tiny, and unlike the example code from the SeedRNG project, we can re-use libbb's existing hash functions, rather than having to ship a standalone BLAKE2s, which makes this even smaller. function old new delta seedrng_main - 1463 +1463 .rodata 107858 108665 +807 seed_from_file_if_exists - 697 +697 packed_usage 34414 34519 +105 static.longopts - 38 +38 static.seedrng_prefix - 26 +26 seed_dir - 8 +8 non_creditable_seed - 8 +8 lock_file - 8 +8 creditable_seed - 8 +8 applet_names 2747 2755 +8 applet_main 3192 3200 +8 ------------------------------------------------------------------------------ (add/remove: 9/0 grow/shrink: 4/0 up/down: 3184/0) Total: 3184 bytes text data bss dec hex filename 973776 4219 1816 979811 ef363 busybox_old 977035 4227 1848 983110 f0046 busybox_unstripped Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com> Signed-off-by: Bernhard Reutner-Fischer <rep.dot.nop@gmail.com>
-rw-r--r--util-linux/seedrng.c308
1 files changed, 308 insertions, 0 deletions
diff --git a/util-linux/seedrng.c b/util-linux/seedrng.c
new file mode 100644
index 000000000..2950acde0
--- /dev/null
+++ b/util-linux/seedrng.c
@@ -0,0 +1,308 @@
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 * This is based on code from <https://git.zx2c4.com/seedrng/about/>.
6 */
7
8//config:config SEEDRNG
9//config: bool "seedrng (3.8 kb)"
10//config: default y
11//config: help
12//config: Seed the kernel RNG from seed files, meant to be called
13//config: once during startup, once during shutdown, and optionally
14//config: at some periodic interval in between.
15
16//applet:IF_SEEDRNG(APPLET(seedrng, BB_DIR_USR_SBIN, BB_SUID_DROP))
17
18//kbuild:lib-$(CONFIG_SEEDRNG) += seedrng.o
19
20//usage:#define seedrng_trivial_usage
21//usage: "[-d SEED_DIRECTORY] [-l LOCK_FILE] [-n]"
22//usage:#define seedrng_full_usage "\n\n"
23//usage: "Seed the kernel RNG from seed files."
24//usage: "\n"
25//usage: "\n -d, --seed-dir DIR Use seed files from specified directory (default: /var/lib/seedrng)"
26//usage: "\n -l, --lock-file FILE Use file as exclusive lock (default: /var/run/seedrng.lock)"
27//usage: "\n -n, --skip-credit Skip crediting seeds, even if creditable"
28
29#include "libbb.h"
30
31#include <linux/random.h>
32#include <sys/random.h>
33#include <sys/ioctl.h>
34#include <sys/file.h>
35#include <sys/stat.h>
36#include <sys/types.h>
37#include <fcntl.h>
38#include <poll.h>
39#include <unistd.h>
40#include <time.h>
41#include <errno.h>
42#include <endian.h>
43#include <stdbool.h>
44#include <stdint.h>
45#include <string.h>
46#include <stdio.h>
47#include <stdlib.h>
48
49#ifndef GRND_INSECURE
50#define GRND_INSECURE 0x0004 /* Apparently some headers don't ship with this yet. */
51#endif
52
53#ifndef LOCALSTATEDIR
54#define LOCALSTATEDIR "/var/lib"
55#endif
56#ifndef RUNSTATEDIR
57#define RUNSTATEDIR "/var/run"
58#endif
59
60#define DEFAULT_SEED_DIR LOCALSTATEDIR "/seedrng"
61#define DEFAULT_LOCK_FILE RUNSTATEDIR "/seedrng.lock"
62#define CREDITABLE_SEED_NAME "seed.credit"
63#define NON_CREDITABLE_SEED_NAME "seed.no-credit"
64
65static char *seed_dir, *lock_file, *creditable_seed, *non_creditable_seed;
66
67enum seedrng_lengths {
68 MAX_SEED_LEN = 512,
69 MIN_SEED_LEN = SHA256_OUTSIZE
70};
71
72#ifndef DIV_ROUND_UP
73#define DIV_ROUND_UP(n, d) (((n) + (d) - 1) / (d))
74#endif
75
76static size_t determine_optimal_seed_len(void)
77{
78 size_t ret = 0;
79 char poolsize_str[11] = { 0 };
80 int fd = open("/proc/sys/kernel/random/poolsize", O_RDONLY);
81
82 if (fd < 0 || read(fd, poolsize_str, sizeof(poolsize_str) - 1) < 0) {
83 fprintf(stderr, "WARNING: Unable to determine pool size, falling back to %u bits: %s\n", MIN_SEED_LEN * 8, strerror(errno));
84 ret = MIN_SEED_LEN;
85 } else
86 ret = DIV_ROUND_UP(strtoul(poolsize_str, NULL, 10), 8);
87 if (fd >= 0)
88 close(fd);
89 if (ret < MIN_SEED_LEN)
90 ret = MIN_SEED_LEN;
91 else if (ret > MAX_SEED_LEN)
92 ret = MAX_SEED_LEN;
93 return ret;
94}
95
96static int read_new_seed(uint8_t *seed, size_t len, bool *is_creditable)
97{
98 ssize_t ret;
99 int urandom_fd;
100
101 *is_creditable = false;
102 ret = getrandom(seed, len, GRND_NONBLOCK);
103 if (ret == (ssize_t)len) {
104 *is_creditable = true;
105 return 0;
106 } else if (ret < 0 && errno == ENOSYS) {
107 struct pollfd random_fd = {
108 .fd = open("/dev/random", O_RDONLY),
109 .events = POLLIN
110 };
111 if (random_fd.fd < 0)
112 return -errno;
113 *is_creditable = poll(&random_fd, 1, 0) == 1;
114 close(random_fd.fd);
115 } else if (getrandom(seed, len, GRND_INSECURE) == (ssize_t)len)
116 return 0;
117 urandom_fd = open("/dev/urandom", O_RDONLY);
118 if (urandom_fd < 0)
119 return -errno;
120 ret = read(urandom_fd, seed, len);
121 if (ret == (ssize_t)len)
122 ret = 0;
123 else
124 ret = -errno ? -errno : -EIO;
125 close(urandom_fd);
126 return ret;
127}
128
129static int seed_rng(uint8_t *seed, size_t len, bool credit)
130{
131 struct {
132 int entropy_count;
133 int buf_size;
134 uint8_t buffer[MAX_SEED_LEN];
135 } req = {
136 .entropy_count = credit ? len * 8 : 0,
137 .buf_size = len
138 };
139 int random_fd, ret;
140
141 if (len > sizeof(req.buffer))
142 return -EFBIG;
143 memcpy(req.buffer, seed, len);
144
145 random_fd = open("/dev/random", O_RDWR);
146 if (random_fd < 0)
147 return -errno;
148 ret = ioctl(random_fd, RNDADDENTROPY, &req);
149 if (ret)
150 ret = -errno ? -errno : -EIO;
151 close(random_fd);
152 return ret;
153}
154
155static int seed_from_file_if_exists(const char *filename, bool credit, sha256_ctx_t *hash)
156{
157 uint8_t seed[MAX_SEED_LEN];
158 ssize_t seed_len;
159 int fd, dfd, ret = 0;
160
161 fd = open(filename, O_RDONLY);
162 if (fd < 0 && errno == ENOENT)
163 return 0;
164 else if (fd < 0) {
165 ret = -errno;
166 fprintf(stderr, "ERROR: Unable to open seed file: %s\n", strerror(errno));
167 return ret;
168 }
169 dfd = open(seed_dir, O_DIRECTORY | O_RDONLY);
170 if (dfd < 0) {
171 ret = -errno;
172 close(fd);
173 fprintf(stderr, "ERROR: Unable to open seed directory: %s\n", strerror(errno));
174 return ret;
175 }
176 seed_len = read(fd, seed, sizeof(seed));
177 if (seed_len < 0) {
178 ret = -errno;
179 fprintf(stderr, "ERROR: Unable to read seed file: %s\n", strerror(errno));
180 }
181 close(fd);
182 if (ret) {
183 close(dfd);
184 return ret;
185 }
186 if ((unlink(filename) < 0 || fsync(dfd) < 0) && seed_len) {
187 ret = -errno;
188 fprintf(stderr, "ERROR: Unable to remove seed after reading, so not seeding: %s\n", strerror(errno));
189 }
190 close(dfd);
191 if (ret)
192 return ret;
193 if (!seed_len)
194 return 0;
195
196 sha256_hash(hash, &seed_len, sizeof(seed_len));
197 sha256_hash(hash, seed, seed_len);
198
199 fprintf(stdout, "Seeding %zd bits %s crediting\n", seed_len * 8, credit ? "and" : "without");
200 ret = seed_rng(seed, seed_len, credit);
201 if (ret < 0)
202 fprintf(stderr, "ERROR: Unable to seed: %s\n", strerror(-ret));
203 return ret;
204}
205
206int seedrng_main(int argc, char *argv[]) MAIN_EXTERNALLY_VISIBLE;
207int seedrng_main(int argc UNUSED_PARAM, char *argv[])
208{
209 static const char seedrng_prefix[] = "SeedRNG v1 Old+New Prefix";
210 static const char seedrng_failure[] = "SeedRNG v1 No New Seed Failure";
211 int ret, fd = -1, lock, program_ret = 0;
212 uint8_t new_seed[MAX_SEED_LEN];
213 size_t new_seed_len;
214 bool new_seed_creditable;
215 bool skip_credit = false;
216 struct timespec realtime = { 0 }, boottime = { 0 };
217 sha256_ctx_t hash;
218
219 int opt;
220 enum {
221 OPT_d = (1 << 0),
222 OPT_l = (1 << 1),
223 OPT_n = (1 << 2)
224 };
225#if ENABLE_LONG_OPTS
226 static const char longopts[] ALIGN1 =
227 "seed-dir\0" Required_argument "d"
228 "lock-file\0" Required_argument "l"
229 "skip-credit\0" No_argument "n"
230 ;
231#endif
232
233 opt = getopt32long(argv, "d:l:n", longopts, &seed_dir, &lock_file);
234 if (!(opt & OPT_d) || !seed_dir)
235 seed_dir = xstrdup(DEFAULT_SEED_DIR);
236 if (!(opt & OPT_l) || !lock_file)
237 lock_file = xstrdup(DEFAULT_LOCK_FILE);
238 skip_credit = opt & OPT_n;
239 creditable_seed = xasprintf("%s/%s", seed_dir, CREDITABLE_SEED_NAME);
240 non_creditable_seed = xasprintf("%s/%s", seed_dir, NON_CREDITABLE_SEED_NAME);
241
242 umask(0077);
243 if (getuid()) {
244 fprintf(stderr, "ERROR: This program requires root\n");
245 return 1;
246 }
247
248 sha256_begin(&hash);
249 sha256_hash(&hash, seedrng_prefix, strlen(seedrng_prefix));
250 clock_gettime(CLOCK_REALTIME, &realtime);
251 clock_gettime(CLOCK_BOOTTIME, &boottime);
252 sha256_hash(&hash, &realtime, sizeof(realtime));
253 sha256_hash(&hash, &boottime, sizeof(boottime));
254
255 if (mkdir(seed_dir, 0700) < 0 && errno != EEXIST) {
256 fprintf(stderr, "ERROR: Unable to create \"%s\" directory: %s\n", seed_dir, strerror(errno));
257 return 1;
258 }
259
260 lock = open(lock_file, O_WRONLY | O_CREAT, 0000);
261 if (lock < 0 || flock(lock, LOCK_EX) < 0) {
262 fprintf(stderr, "ERROR: Unable to open lock file: %s\n", strerror(errno));
263 program_ret = 1;
264 goto out;
265 }
266
267 ret = seed_from_file_if_exists(non_creditable_seed, false, &hash);
268 if (ret < 0)
269 program_ret |= 1 << 1;
270 ret = seed_from_file_if_exists(creditable_seed, !skip_credit, &hash);
271 if (ret < 0)
272 program_ret |= 1 << 2;
273
274 new_seed_len = determine_optimal_seed_len();
275 ret = read_new_seed(new_seed, new_seed_len, &new_seed_creditable);
276 if (ret < 0) {
277 fprintf(stderr, "ERROR: Unable to read new seed: %s\n", strerror(-ret));
278 new_seed_len = SHA256_OUTSIZE;
279 strncpy((char *)new_seed, seedrng_failure, new_seed_len);
280 program_ret |= 1 << 3;
281 }
282 sha256_hash(&hash, &new_seed_len, sizeof(new_seed_len));
283 sha256_hash(&hash, new_seed, new_seed_len);
284 sha256_end(&hash, new_seed + new_seed_len - SHA256_OUTSIZE);
285
286 fprintf(stdout, "Saving %zu bits of %s seed for next boot\n", new_seed_len * 8, new_seed_creditable ? "creditable" : "non-creditable");
287 fd = open(non_creditable_seed, O_WRONLY | O_CREAT | O_TRUNC, 0400);
288 if (fd < 0) {
289 fprintf(stderr, "ERROR: Unable to open seed file for writing: %s\n", strerror(errno));
290 program_ret |= 1 << 4;
291 goto out;
292 }
293 if (write(fd, new_seed, new_seed_len) != (ssize_t)new_seed_len || fsync(fd) < 0) {
294 fprintf(stderr, "ERROR: Unable to write seed file: %s\n", strerror(errno));
295 program_ret |= 1 << 5;
296 goto out;
297 }
298 if (new_seed_creditable && rename(non_creditable_seed, creditable_seed) < 0) {
299 fprintf(stderr, "WARNING: Unable to make new seed creditable: %s\n", strerror(errno));
300 program_ret |= 1 << 6;
301 }
302out:
303 if (fd >= 0)
304 close(fd);
305 if (lock >= 0)
306 close(lock);
307 return program_ret;
308}