Branch data Line data Source code
1 : : /* SPDX-License-Identifier: LGPL-2.1+ */
2 : :
3 : : #include <errno.h>
4 : : #include <fcntl.h>
5 : : #include <linux/random.h>
6 : : #include <string.h>
7 : : #include <sys/ioctl.h>
8 : : #if USE_SYS_RANDOM_H
9 : : # include <sys/random.h>
10 : : #endif
11 : : #include <sys/stat.h>
12 : : #include <sys/xattr.h>
13 : : #include <unistd.h>
14 : :
15 : : #include "sd-id128.h"
16 : :
17 : : #include "alloc-util.h"
18 : : #include "fd-util.h"
19 : : #include "fs-util.h"
20 : : #include "io-util.h"
21 : : #include "log.h"
22 : : #include "main-func.h"
23 : : #include "missing.h"
24 : : #include "mkdir.h"
25 : : #include "parse-util.h"
26 : : #include "random-util.h"
27 : : #include "string-util.h"
28 : : #include "util.h"
29 : : #include "xattr-util.h"
30 : :
31 : : typedef enum CreditEntropy {
32 : : CREDIT_ENTROPY_NO_WAY,
33 : : CREDIT_ENTROPY_YES_PLEASE,
34 : : CREDIT_ENTROPY_YES_FORCED,
35 : : } CreditEntropy;
36 : :
37 : 0 : static CreditEntropy may_credit(int seed_fd) {
38 : 0 : _cleanup_free_ char *creditable = NULL;
39 : : const char *e;
40 : : int r;
41 : :
42 [ # # ]: 0 : assert(seed_fd >= 0);
43 : :
44 : 0 : e = getenv("SYSTEMD_RANDOM_SEED_CREDIT");
45 [ # # ]: 0 : if (!e) {
46 [ # # ]: 0 : log_debug("$SYSTEMD_RANDOM_SEED_CREDIT is not set, not crediting entropy.");
47 : 0 : return CREDIT_ENTROPY_NO_WAY;
48 : : }
49 [ # # ]: 0 : if (streq(e, "force")) {
50 [ # # ]: 0 : log_debug("$SYSTEMD_RANDOM_SEED_CREDIT is set to 'force', crediting entropy.");
51 : 0 : return CREDIT_ENTROPY_YES_FORCED;
52 : : }
53 : :
54 : 0 : r = parse_boolean(e);
55 [ # # ]: 0 : if (r <= 0) {
56 [ # # ]: 0 : if (r < 0)
57 [ # # ]: 0 : log_warning_errno(r, "Failed to parse $SYSTEMD_RANDOM_SEED_CREDIT, not crediting entropy: %m");
58 : : else
59 [ # # ]: 0 : log_debug("Crediting entropy is turned off via $SYSTEMD_RANDOM_SEED_CREDIT, not crediting entropy.");
60 : :
61 : 0 : return CREDIT_ENTROPY_NO_WAY;
62 : : }
63 : :
64 : : /* Determine if the file is marked as creditable */
65 : 0 : r = fgetxattr_malloc(seed_fd, "user.random-seed-creditable", &creditable);
66 [ # # ]: 0 : if (r < 0) {
67 [ # # # # ]: 0 : if (IN_SET(r, -ENODATA, -ENOSYS, -EOPNOTSUPP))
68 [ # # ]: 0 : log_debug_errno(r, "Seed file is not marked as creditable, not crediting.");
69 : : else
70 [ # # ]: 0 : log_warning_errno(r, "Failed to read extended attribute, ignoring: %m");
71 : :
72 : 0 : return CREDIT_ENTROPY_NO_WAY;
73 : : }
74 : :
75 : 0 : r = parse_boolean(creditable);
76 [ # # ]: 0 : if (r <= 0) {
77 [ # # ]: 0 : if (r < 0)
78 [ # # ]: 0 : log_warning_errno(r, "Failed to parse user.random-seed-creditable extended attribute, ignoring: %s", creditable);
79 : : else
80 [ # # ]: 0 : log_debug("Seed file is marked as not creditable, not crediting.");
81 : :
82 : 0 : return CREDIT_ENTROPY_NO_WAY;
83 : : }
84 : :
85 : : /* Don't credit the random seed if we are in first-boot mode, because we are supposed to start from
86 : : * scratch. This is a safety precaution for cases where we people ship "golden" images with empty
87 : : * /etc but populated /var that contains a random seed. */
88 [ # # ]: 0 : if (access("/run/systemd/first-boot", F_OK) < 0) {
89 : :
90 [ # # ]: 0 : if (errno != ENOENT) {
91 [ # # ]: 0 : log_warning_errno(errno, "Failed to check whether we are in first-boot mode, not crediting entropy: %m");
92 : 0 : return CREDIT_ENTROPY_NO_WAY;
93 : : }
94 : :
95 : : /* If ENOENT all is good, we are not in first-boot mode. */
96 : : } else {
97 [ # # ]: 0 : log_debug("Not crediting entropy, since booted in first-boot mode.");
98 : 0 : return CREDIT_ENTROPY_NO_WAY;
99 : : }
100 : :
101 : 0 : return CREDIT_ENTROPY_YES_PLEASE;
102 : : }
103 : :
104 : 0 : static int run(int argc, char *argv[]) {
105 : 0 : _cleanup_close_ int seed_fd = -1, random_fd = -1;
106 : : bool read_seed_file, write_seed_file, synchronous;
107 : 0 : _cleanup_free_ void* buf = NULL;
108 : : size_t buf_size;
109 : : struct stat st;
110 : : ssize_t k;
111 : : int r;
112 : :
113 : 0 : log_setup_service();
114 : :
115 [ # # ]: 0 : if (argc != 2)
116 [ # # ]: 0 : return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
117 : : "This program requires one argument.");
118 : :
119 : 0 : umask(0022);
120 : :
121 : 0 : buf_size = random_pool_size();
122 : :
123 : 0 : r = mkdir_parents(RANDOM_SEED, 0755);
124 [ # # ]: 0 : if (r < 0)
125 [ # # ]: 0 : return log_error_errno(r, "Failed to create directory " RANDOM_SEED_DIR ": %m");
126 : :
127 : : /* When we load the seed we read it and write it to the device and then immediately update the saved seed with
128 : : * new data, to make sure the next boot gets seeded differently. */
129 : :
130 [ # # ]: 0 : if (streq(argv[1], "load")) {
131 : :
132 : 0 : seed_fd = open(RANDOM_SEED, O_RDWR|O_CLOEXEC|O_NOCTTY|O_CREAT, 0600);
133 [ # # ]: 0 : if (seed_fd < 0) {
134 : 0 : int open_rw_error = -errno;
135 : :
136 : 0 : write_seed_file = false;
137 : :
138 : 0 : seed_fd = open(RANDOM_SEED, O_RDONLY|O_CLOEXEC|O_NOCTTY);
139 [ # # ]: 0 : if (seed_fd < 0) {
140 : 0 : bool missing = errno == ENOENT;
141 : :
142 [ # # # # ]: 0 : log_full_errno(missing ? LOG_DEBUG : LOG_ERR,
143 : : open_rw_error, "Failed to open " RANDOM_SEED " for writing: %m");
144 [ # # # # ]: 0 : r = log_full_errno(missing ? LOG_DEBUG : LOG_ERR,
145 : : errno, "Failed to open " RANDOM_SEED " for reading: %m");
146 [ # # ]: 0 : return missing ? 0 : r;
147 : : }
148 : : } else
149 : 0 : write_seed_file = true;
150 : :
151 : 0 : random_fd = open("/dev/urandom", O_RDWR|O_CLOEXEC|O_NOCTTY, 0600);
152 [ # # ]: 0 : if (random_fd < 0)
153 [ # # ]: 0 : return log_error_errno(errno, "Failed to open /dev/urandom: %m");
154 : :
155 : 0 : read_seed_file = true;
156 : 0 : synchronous = true; /* make this invocation a synchronous barrier for random pool initialization */
157 : :
158 [ # # ]: 0 : } else if (streq(argv[1], "save")) {
159 : :
160 : 0 : random_fd = open("/dev/urandom", O_RDONLY|O_CLOEXEC|O_NOCTTY);
161 [ # # ]: 0 : if (random_fd < 0)
162 [ # # ]: 0 : return log_error_errno(errno, "Failed to open /dev/urandom: %m");
163 : :
164 : 0 : seed_fd = open(RANDOM_SEED, O_WRONLY|O_CLOEXEC|O_NOCTTY|O_CREAT, 0600);
165 [ # # ]: 0 : if (seed_fd < 0)
166 [ # # ]: 0 : return log_error_errno(errno, "Failed to open " RANDOM_SEED ": %m");
167 : :
168 : 0 : read_seed_file = false;
169 : 0 : write_seed_file = true;
170 : 0 : synchronous = false;
171 : : } else
172 [ # # ]: 0 : return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
173 : : "Unknown verb '%s'.", argv[1]);
174 : :
175 [ # # ]: 0 : if (fstat(seed_fd, &st) < 0)
176 [ # # ]: 0 : return log_error_errno(errno, "Failed to stat() seed file " RANDOM_SEED ": %m");
177 : :
178 : : /* If the seed file is larger than what we expect, then honour the existing size and save/restore as much as it says */
179 [ # # ]: 0 : if ((uint64_t) st.st_size > buf_size)
180 : 0 : buf_size = MIN(st.st_size, RANDOM_POOL_SIZE_MAX);
181 : :
182 : 0 : buf = malloc(buf_size);
183 [ # # ]: 0 : if (!buf)
184 : 0 : return log_oom();
185 : :
186 [ # # ]: 0 : if (read_seed_file) {
187 : : sd_id128_t mid;
188 : :
189 : : /* First, let's write the machine ID into /dev/urandom, not crediting entropy. Why? As an
190 : : * extra protection against "golden images" that are put together sloppily, i.e. images which
191 : : * are duplicated on multiple systems but where the random seed file is not properly
192 : : * reset. Frequently the machine ID is properly reset on those systems however (simply
193 : : * because it's easier to notice, if it isn't due to address clashes and so on, while random
194 : : * seed equivalence is generally not noticed easily), hence let's simply write the machined
195 : : * ID into the random pool too. */
196 : 0 : r = sd_id128_get_machine(&mid);
197 [ # # ]: 0 : if (r < 0)
198 [ # # ]: 0 : log_debug_errno(r, "Failed to get machine ID, ignoring: %m");
199 : : else {
200 : 0 : r = loop_write(random_fd, &mid, sizeof(mid), false);
201 [ # # ]: 0 : if (r < 0)
202 [ # # ]: 0 : log_debug_errno(r, "Failed to write machine ID to /dev/urandom, ignoring: %m");
203 : : }
204 : :
205 : 0 : k = loop_read(seed_fd, buf, buf_size, false);
206 [ # # ]: 0 : if (k < 0)
207 [ # # ]: 0 : log_error_errno(k, "Failed to read seed from " RANDOM_SEED ": %m");
208 [ # # ]: 0 : else if (k == 0)
209 [ # # ]: 0 : log_debug("Seed file " RANDOM_SEED " not yet initialized, proceeding.");
210 : : else {
211 : : CreditEntropy lets_credit;
212 : :
213 : 0 : (void) lseek(seed_fd, 0, SEEK_SET);
214 : :
215 : 0 : lets_credit = may_credit(seed_fd);
216 : :
217 : : /* Before we credit or use the entropy, let's make sure to securely drop the
218 : : * creditable xattr from the file, so that we never credit the same random seed
219 : : * again. Note that further down we'll write a new seed again, and likely mark it as
220 : : * credible again, hence this is just paranoia to close the short time window between
221 : : * the time we upload the random seed into the kernel and download the new one from
222 : : * it. */
223 : :
224 [ # # ]: 0 : if (fremovexattr(seed_fd, "user.random-seed-creditable") < 0) {
225 [ # # # # ]: 0 : if (!IN_SET(errno, ENODATA, ENOSYS, EOPNOTSUPP))
226 [ # # ]: 0 : log_warning_errno(errno, "Failed to remove extended attribute, ignoring: %m");
227 : :
228 : : /* Otherwise, there was no creditable flag set, which is OK. */
229 : : } else {
230 : 0 : r = fsync_full(seed_fd);
231 [ # # ]: 0 : if (r < 0) {
232 [ # # ]: 0 : log_warning_errno(r, "Failed to synchronize seed to disk, not crediting entropy: %m");
233 : :
234 [ # # ]: 0 : if (lets_credit == CREDIT_ENTROPY_YES_PLEASE)
235 : 0 : lets_credit = CREDIT_ENTROPY_NO_WAY;
236 : : }
237 : : }
238 : :
239 [ # # # # ]: 0 : if (IN_SET(lets_credit, CREDIT_ENTROPY_YES_PLEASE, CREDIT_ENTROPY_YES_FORCED)) {
240 [ # # ]: 0 : _cleanup_free_ struct rand_pool_info *info = NULL;
241 : :
242 : 0 : info = malloc(offsetof(struct rand_pool_info, buf) + k);
243 [ # # ]: 0 : if (!info)
244 : 0 : return log_oom();
245 : :
246 : 0 : info->entropy_count = k * 8;
247 : 0 : info->buf_size = k;
248 : 0 : memcpy(info->buf, buf, k);
249 : :
250 [ # # ]: 0 : if (ioctl(random_fd, RNDADDENTROPY, info) < 0)
251 [ # # ]: 0 : return log_warning_errno(errno, "Failed to credit entropy, ignoring: %m");
252 : : } else {
253 : 0 : r = loop_write(random_fd, buf, (size_t) k, false);
254 [ # # ]: 0 : if (r < 0)
255 [ # # ]: 0 : log_error_errno(r, "Failed to write seed to /dev/urandom: %m");
256 : : }
257 : : }
258 : : }
259 : :
260 [ # # ]: 0 : if (write_seed_file) {
261 : 0 : bool getrandom_worked = false;
262 : :
263 : : /* This is just a safety measure. Given that we are root and most likely created the file
264 : : * ourselves the mode and owner should be correct anyway. */
265 : 0 : r = fchmod_and_chown(seed_fd, 0600, 0, 0);
266 [ # # ]: 0 : if (r < 0)
267 [ # # ]: 0 : return log_error_errno(r, "Failed to adjust seed file ownership and access mode.");
268 : :
269 : : /* Let's make this whole job asynchronous, i.e. let's make ourselves a barrier for
270 : : * proper initialization of the random pool. */
271 : 0 : k = getrandom(buf, buf_size, GRND_NONBLOCK);
272 [ # # # # : 0 : if (k < 0 && errno == EAGAIN && synchronous) {
# # ]
273 [ # # ]: 0 : log_notice("Kernel entropy pool is not initialized yet, waiting until it is.");
274 : 0 : k = getrandom(buf, buf_size, 0); /* retry synchronously */
275 : : }
276 [ # # ]: 0 : if (k < 0)
277 [ # # ]: 0 : log_debug_errno(errno, "Failed to read random data with getrandom(), falling back to /dev/urandom: %m");
278 [ # # ]: 0 : else if ((size_t) k < buf_size)
279 [ # # ]: 0 : log_debug("Short read from getrandom(), falling back to /dev/urandom: %m");
280 : : else
281 : 0 : getrandom_worked = true;
282 : :
283 [ # # ]: 0 : if (!getrandom_worked) {
284 : : /* Retry with classic /dev/urandom */
285 : 0 : k = loop_read(random_fd, buf, buf_size, false);
286 [ # # ]: 0 : if (k < 0)
287 [ # # ]: 0 : return log_error_errno(k, "Failed to read new seed from /dev/urandom: %m");
288 [ # # ]: 0 : if (k == 0)
289 [ # # ]: 0 : return log_error_errno(SYNTHETIC_ERRNO(EIO),
290 : : "Got EOF while reading from /dev/urandom.");
291 : : }
292 : :
293 : 0 : r = loop_write(seed_fd, buf, (size_t) k, false);
294 [ # # ]: 0 : if (r < 0)
295 [ # # ]: 0 : return log_error_errno(r, "Failed to write new random seed file: %m");
296 : :
297 [ # # ]: 0 : if (ftruncate(seed_fd, k) < 0)
298 [ # # ]: 0 : return log_error_errno(r, "Failed to truncate random seed file: %m");
299 : :
300 : 0 : r = fsync_full(seed_fd);
301 [ # # ]: 0 : if (r < 0)
302 [ # # ]: 0 : return log_error_errno(r, "Failed to synchronize seed file: %m");
303 : :
304 : : /* If we got this random seed data from getrandom() the data is suitable for crediting
305 : : * entropy later on. Let's keep that in mind by setting an extended attribute. on the file */
306 [ # # ]: 0 : if (getrandom_worked)
307 [ # # ]: 0 : if (fsetxattr(seed_fd, "user.random-seed-creditable", "1", 1, 0) < 0)
308 [ # # # # : 0 : log_full_errno(IN_SET(errno, ENOSYS, EOPNOTSUPP) ? LOG_DEBUG : LOG_WARNING, errno,
# # ]
309 : : "Failed to mark seed file as creditable, ignoring: %m");
310 : : }
311 : :
312 : 0 : return 0;
313 : : }
314 : :
315 : 0 : DEFINE_MAIN_FUNCTION(run);
|