blob: d82a942aa53aa92470d4b109776c2b6524f4ae59 [file] [log] [blame]
Jason A. Donenfeld4b407ba2022-04-04 18:21:51 +02001// 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
Jason A. Donenfeld45385782022-04-20 15:22:55 +02009//config: bool "seedrng (2.6 kb)"
Jason A. Donenfeld4b407ba2022-04-04 18:21:51 +020010//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
Jason A. Donenfeld45385782022-04-20 15:22:55 +020053#if ENABLE_PID_FILE_PATH
54#define PID_FILE_PATH CONFIG_PID_FILE_PATH
55#else
56#define PID_FILE_PATH "/var/run"
Jason A. Donenfeld4b407ba2022-04-04 18:21:51 +020057#endif
58
Jason A. Donenfeld45385782022-04-20 15:22:55 +020059#define DEFAULT_SEED_DIR "/var/lib/seedrng"
60#define DEFAULT_LOCK_FILE PID_FILE_PATH "/seedrng.lock"
Jason A. Donenfeld4b407ba2022-04-04 18:21:51 +020061#define CREDITABLE_SEED_NAME "seed.credit"
62#define NON_CREDITABLE_SEED_NAME "seed.no-credit"
63
64static char *seed_dir, *lock_file, *creditable_seed, *non_creditable_seed;
65
66enum seedrng_lengths {
Jason A. Donenfeld45385782022-04-20 15:22:55 +020067 MIN_SEED_LEN = SHA256_OUTSIZE,
68 MAX_SEED_LEN = 512
Jason A. Donenfeld4b407ba2022-04-04 18:21:51 +020069};
70
Jason A. Donenfeld4b407ba2022-04-04 18:21:51 +020071static size_t determine_optimal_seed_len(void)
72{
Jason A. Donenfeld4b407ba2022-04-04 18:21:51 +020073 char poolsize_str[11] = { 0 };
Jason A. Donenfeld4b407ba2022-04-04 18:21:51 +020074
Jason A. Donenfeld45385782022-04-20 15:22:55 +020075 if (open_read_close("/proc/sys/kernel/random/poolsize", poolsize_str, sizeof(poolsize_str) - 1) < 0) {
76 bb_perror_msg("unable to determine pool size, falling back to %u bits", MIN_SEED_LEN * 8);
77 return MIN_SEED_LEN;
78 }
79 return MAX(MIN((bb_strtoul(poolsize_str, NULL, 10) + 7) / 8, MAX_SEED_LEN), MIN_SEED_LEN);
Jason A. Donenfeld4b407ba2022-04-04 18:21:51 +020080}
81
82static int read_new_seed(uint8_t *seed, size_t len, bool *is_creditable)
83{
84 ssize_t ret;
Jason A. Donenfeld4b407ba2022-04-04 18:21:51 +020085
86 *is_creditable = false;
87 ret = getrandom(seed, len, GRND_NONBLOCK);
88 if (ret == (ssize_t)len) {
89 *is_creditable = true;
90 return 0;
91 } else if (ret < 0 && errno == ENOSYS) {
92 struct pollfd random_fd = {
93 .fd = open("/dev/random", O_RDONLY),
94 .events = POLLIN
95 };
96 if (random_fd.fd < 0)
Jason A. Donenfeld45385782022-04-20 15:22:55 +020097 return -1;
98 *is_creditable = safe_poll(&random_fd, 1, 0) == 1;
Jason A. Donenfeld4b407ba2022-04-04 18:21:51 +020099 close(random_fd.fd);
100 } else if (getrandom(seed, len, GRND_INSECURE) == (ssize_t)len)
101 return 0;
Jason A. Donenfeld45385782022-04-20 15:22:55 +0200102 if (open_read_close("/dev/urandom", seed, len) == (ssize_t)len)
103 return 0;
104 if (!errno)
105 errno = EIO;
106 return -1;
Jason A. Donenfeld4b407ba2022-04-04 18:21:51 +0200107}
108
109static int seed_rng(uint8_t *seed, size_t len, bool credit)
110{
111 struct {
112 int entropy_count;
113 int buf_size;
114 uint8_t buffer[MAX_SEED_LEN];
115 } req = {
116 .entropy_count = credit ? len * 8 : 0,
117 .buf_size = len
118 };
119 int random_fd, ret;
120
Jason A. Donenfeld45385782022-04-20 15:22:55 +0200121 if (len > sizeof(req.buffer)) {
122 errno = EFBIG;
123 return -1;
124 }
Jason A. Donenfeld4b407ba2022-04-04 18:21:51 +0200125 memcpy(req.buffer, seed, len);
126
127 random_fd = open("/dev/random", O_RDWR);
128 if (random_fd < 0)
Jason A. Donenfeld45385782022-04-20 15:22:55 +0200129 return -1;
Jason A. Donenfeld4b407ba2022-04-04 18:21:51 +0200130 ret = ioctl(random_fd, RNDADDENTROPY, &req);
131 if (ret)
132 ret = -errno ? -errno : -EIO;
133 close(random_fd);
Jason A. Donenfeld45385782022-04-20 15:22:55 +0200134 errno = -ret;
135 return ret ? -1 : 0;
Jason A. Donenfeld4b407ba2022-04-04 18:21:51 +0200136}
137
138static int seed_from_file_if_exists(const char *filename, bool credit, sha256_ctx_t *hash)
139{
140 uint8_t seed[MAX_SEED_LEN];
141 ssize_t seed_len;
Jason A. Donenfeld45385782022-04-20 15:22:55 +0200142 int dfd = -1, ret = 0;
Jason A. Donenfeld4b407ba2022-04-04 18:21:51 +0200143
Jason A. Donenfeld4b407ba2022-04-04 18:21:51 +0200144 dfd = open(seed_dir, O_DIRECTORY | O_RDONLY);
145 if (dfd < 0) {
146 ret = -errno;
Jason A. Donenfeld45385782022-04-20 15:22:55 +0200147 bb_simple_perror_msg("unable to open seed directory");
148 goto out;
Jason A. Donenfeld4b407ba2022-04-04 18:21:51 +0200149 }
Jason A. Donenfeld45385782022-04-20 15:22:55 +0200150 seed_len = open_read_close(filename, seed, sizeof(seed));
Jason A. Donenfeld4b407ba2022-04-04 18:21:51 +0200151 if (seed_len < 0) {
Jason A. Donenfeld45385782022-04-20 15:22:55 +0200152 if (errno != ENOENT) {
153 ret = -errno;
154 bb_simple_perror_msg("unable to read seed file");
155 }
156 goto out;
Jason A. Donenfeld4b407ba2022-04-04 18:21:51 +0200157 }
158 if ((unlink(filename) < 0 || fsync(dfd) < 0) && seed_len) {
159 ret = -errno;
Jason A. Donenfeld45385782022-04-20 15:22:55 +0200160 bb_simple_perror_msg("unable to remove seed after reading, so not seeding");
161 goto out;
Jason A. Donenfeld4b407ba2022-04-04 18:21:51 +0200162 }
Jason A. Donenfeld4b407ba2022-04-04 18:21:51 +0200163 if (!seed_len)
Jason A. Donenfeld45385782022-04-20 15:22:55 +0200164 goto out;
Jason A. Donenfeld4b407ba2022-04-04 18:21:51 +0200165
166 sha256_hash(hash, &seed_len, sizeof(seed_len));
167 sha256_hash(hash, seed, seed_len);
168
Jason A. Donenfeld45385782022-04-20 15:22:55 +0200169 printf("Seeding %zd bits %s crediting\n", seed_len * 8, credit ? "and" : "without");
Jason A. Donenfeld4b407ba2022-04-04 18:21:51 +0200170 ret = seed_rng(seed, seed_len, credit);
171 if (ret < 0)
Jason A. Donenfeld45385782022-04-20 15:22:55 +0200172 bb_simple_perror_msg("unable to seed");
173out:
174 if (dfd >= 0)
175 close(dfd);
176 errno = -ret;
177 return ret ? -1 : 0;
Jason A. Donenfeld4b407ba2022-04-04 18:21:51 +0200178}
179
180int seedrng_main(int argc, char *argv[]) MAIN_EXTERNALLY_VISIBLE;
181int seedrng_main(int argc UNUSED_PARAM, char *argv[])
182{
183 static const char seedrng_prefix[] = "SeedRNG v1 Old+New Prefix";
184 static const char seedrng_failure[] = "SeedRNG v1 No New Seed Failure";
185 int ret, fd = -1, lock, program_ret = 0;
186 uint8_t new_seed[MAX_SEED_LEN];
187 size_t new_seed_len;
188 bool new_seed_creditable;
189 bool skip_credit = false;
190 struct timespec realtime = { 0 }, boottime = { 0 };
191 sha256_ctx_t hash;
192
193 int opt;
194 enum {
195 OPT_d = (1 << 0),
196 OPT_l = (1 << 1),
197 OPT_n = (1 << 2)
198 };
199#if ENABLE_LONG_OPTS
200 static const char longopts[] ALIGN1 =
201 "seed-dir\0" Required_argument "d"
202 "lock-file\0" Required_argument "l"
203 "skip-credit\0" No_argument "n"
204 ;
205#endif
206
207 opt = getopt32long(argv, "d:l:n", longopts, &seed_dir, &lock_file);
208 if (!(opt & OPT_d) || !seed_dir)
209 seed_dir = xstrdup(DEFAULT_SEED_DIR);
210 if (!(opt & OPT_l) || !lock_file)
211 lock_file = xstrdup(DEFAULT_LOCK_FILE);
212 skip_credit = opt & OPT_n;
Jason A. Donenfeld45385782022-04-20 15:22:55 +0200213 creditable_seed = concat_path_file(seed_dir, CREDITABLE_SEED_NAME);
214 non_creditable_seed = concat_path_file(seed_dir, NON_CREDITABLE_SEED_NAME);
Jason A. Donenfeld4b407ba2022-04-04 18:21:51 +0200215
216 umask(0077);
Jason A. Donenfeld45385782022-04-20 15:22:55 +0200217 if (getuid())
218 bb_simple_error_msg_and_die("this program requires root");
Jason A. Donenfeld4b407ba2022-04-04 18:21:51 +0200219
220 sha256_begin(&hash);
221 sha256_hash(&hash, seedrng_prefix, strlen(seedrng_prefix));
222 clock_gettime(CLOCK_REALTIME, &realtime);
223 clock_gettime(CLOCK_BOOTTIME, &boottime);
224 sha256_hash(&hash, &realtime, sizeof(realtime));
225 sha256_hash(&hash, &boottime, sizeof(boottime));
226
Jason A. Donenfeld45385782022-04-20 15:22:55 +0200227 if (mkdir(seed_dir, 0700) < 0 && errno != EEXIST)
228 bb_simple_perror_msg_and_die("unable to create seed directory");
Jason A. Donenfeld4b407ba2022-04-04 18:21:51 +0200229
230 lock = open(lock_file, O_WRONLY | O_CREAT, 0000);
231 if (lock < 0 || flock(lock, LOCK_EX) < 0) {
Jason A. Donenfeld45385782022-04-20 15:22:55 +0200232 bb_simple_perror_msg("unable to open lock file");
Jason A. Donenfeld4b407ba2022-04-04 18:21:51 +0200233 program_ret = 1;
234 goto out;
235 }
236
237 ret = seed_from_file_if_exists(non_creditable_seed, false, &hash);
238 if (ret < 0)
239 program_ret |= 1 << 1;
240 ret = seed_from_file_if_exists(creditable_seed, !skip_credit, &hash);
241 if (ret < 0)
242 program_ret |= 1 << 2;
243
244 new_seed_len = determine_optimal_seed_len();
245 ret = read_new_seed(new_seed, new_seed_len, &new_seed_creditable);
246 if (ret < 0) {
Jason A. Donenfeld45385782022-04-20 15:22:55 +0200247 bb_simple_perror_msg("unable to read new seed");
Jason A. Donenfeld4b407ba2022-04-04 18:21:51 +0200248 new_seed_len = SHA256_OUTSIZE;
249 strncpy((char *)new_seed, seedrng_failure, new_seed_len);
250 program_ret |= 1 << 3;
251 }
252 sha256_hash(&hash, &new_seed_len, sizeof(new_seed_len));
253 sha256_hash(&hash, new_seed, new_seed_len);
254 sha256_end(&hash, new_seed + new_seed_len - SHA256_OUTSIZE);
255
Jason A. Donenfeld45385782022-04-20 15:22:55 +0200256 printf("Saving %zu bits of %s seed for next boot\n", new_seed_len * 8, new_seed_creditable ? "creditable" : "non-creditable");
Jason A. Donenfeld4b407ba2022-04-04 18:21:51 +0200257 fd = open(non_creditable_seed, O_WRONLY | O_CREAT | O_TRUNC, 0400);
258 if (fd < 0) {
Jason A. Donenfeld45385782022-04-20 15:22:55 +0200259 bb_simple_perror_msg("unable to open seed file for writing");
Jason A. Donenfeld4b407ba2022-04-04 18:21:51 +0200260 program_ret |= 1 << 4;
261 goto out;
262 }
263 if (write(fd, new_seed, new_seed_len) != (ssize_t)new_seed_len || fsync(fd) < 0) {
Jason A. Donenfeld45385782022-04-20 15:22:55 +0200264 bb_simple_perror_msg("unable to write seed file");
Jason A. Donenfeld4b407ba2022-04-04 18:21:51 +0200265 program_ret |= 1 << 5;
266 goto out;
267 }
268 if (new_seed_creditable && rename(non_creditable_seed, creditable_seed) < 0) {
Jason A. Donenfeld45385782022-04-20 15:22:55 +0200269 bb_simple_perror_msg("unable to make new seed creditable");
Jason A. Donenfeld4b407ba2022-04-04 18:21:51 +0200270 program_ret |= 1 << 6;
271 }
272out:
273 if (fd >= 0)
274 close(fd);
275 if (lock >= 0)
276 close(lock);
277 return program_ret;
278}