aboutsummaryrefslogtreecommitdiff
path: root/util-linux/seedrng.c
diff options
context:
space:
mode:
Diffstat (limited to 'util-linux/seedrng.c')
-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}