diff options
Diffstat (limited to 'util-linux/seedrng.c')
-rw-r--r-- | util-linux/seedrng.c | 308 |
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 | |||
65 | static char *seed_dir, *lock_file, *creditable_seed, *non_creditable_seed; | ||
66 | |||
67 | enum 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 | |||
76 | static 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 | |||
96 | static 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 | |||
129 | static 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 | |||
155 | static 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 | |||
206 | int seedrng_main(int argc, char *argv[]) MAIN_EXTERNALLY_VISIBLE; | ||
207 | int 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 | } | ||
302 | out: | ||
303 | if (fd >= 0) | ||
304 | close(fd); | ||
305 | if (lock >= 0) | ||
306 | close(lock); | ||
307 | return program_ret; | ||
308 | } | ||