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 | } | ||