1 /* $NetBSD: rndctl.c,v 1.41 2023/04/11 13:17:32 riastradh Exp $ */
2
3 /*-
4 * Copyright (c) 1997 Michael Graff.
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * 3. Neither the name of the author nor the names of other contributors
16 * may be used to endorse or promote products derived from this software
17 * without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
20 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
21 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
22 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
23 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
24 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
26 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 * SUCH DAMAGE.
30 */
31
32 #include <sys/cdefs.h>
33 #ifndef lint
34 __RCSID("$NetBSD: rndctl.c,v 1.41 2023/04/11 13:17:32 riastradh Exp $");
35 #endif
36
37 #include <sys/param.h>
38 #include <sys/types.h>
39 #include <sys/endian.h>
40 #include <sys/ioctl.h>
41 #include <sys/rndio.h>
42 #include <sys/sha3.h>
43 #include <sys/sysctl.h>
44
45 #include <err.h>
46 #include <errno.h>
47 #include <fcntl.h>
48 #include <paths.h>
49 #include <sha1.h>
50 #include <stdio.h>
51 #include <stdlib.h>
52 #include <string.h>
53 #include <unistd.h>
54
55 typedef struct {
56 const char *a_name;
57 u_int32_t a_type;
58 } arg_t;
59
60 static const arg_t source_types[] = {
61 { "???", RND_TYPE_UNKNOWN },
62 { "disk", RND_TYPE_DISK },
63 { "net", RND_TYPE_NET },
64 { "tape", RND_TYPE_TAPE },
65 { "tty", RND_TYPE_TTY },
66 { "rng", RND_TYPE_RNG },
67 { "skew", RND_TYPE_SKEW },
68 { "env", RND_TYPE_ENV },
69 { "vm", RND_TYPE_VM },
70 { "power", RND_TYPE_POWER },
71 { NULL, 0 }
72 };
73
74 __dead static void usage(void);
75 static u_int32_t find_type(const char *name);
76 static const char *find_name(u_int32_t);
77 static void do_ioctl(rndctl_t *);
78 static char * strflags(uint32_t, u_int32_t);
79 static void do_list(int, u_int32_t, char *);
80 static void do_print_source(rndsource_est_t *);
81 static void do_print_source_verbose(rndsource_est_t *);
82 static void do_stats(void);
83
84 static int iflag;
85 static int vflag;
86
87 static void
usage(void)88 usage(void)
89 {
90
91 fprintf(stderr, "usage: %s [-CEce] [-d devname | -t devtype]\n",
92 getprogname());
93 fprintf(stderr, " %s [-lsv] [-d devname | -t devtype]\n",
94 getprogname());
95 fprintf(stderr, " %s [-i] -L save-file\n", getprogname());
96 fprintf(stderr, " %s -S save-file\n", getprogname());
97 exit(1);
98 }
99
100 static u_int32_t
find_type(const char * name)101 find_type(const char *name)
102 {
103 const arg_t *a;
104
105 a = source_types;
106
107 while (a->a_name != NULL) {
108 if (strcmp(a->a_name, name) == 0)
109 return (a->a_type);
110 a++;
111 }
112
113 errx(1, "device name %s unknown", name);
114 return (0);
115 }
116
117 static const char *
find_name(u_int32_t type)118 find_name(u_int32_t type)
119 {
120 const arg_t *a;
121
122 a = source_types;
123
124 while (a->a_name != NULL) {
125 if (type == a->a_type)
126 return (a->a_name);
127 a++;
128 }
129
130 warnx("device type %u unknown", type);
131 return ("???");
132 }
133
134 static int
update_seed(const char * filename,int fd_seed,const char * tmp,const void * extra,size_t nextra,uint32_t extraentropy)135 update_seed(const char *filename, int fd_seed, const char *tmp,
136 const void *extra, size_t nextra, uint32_t extraentropy)
137 {
138 uint32_t systementropy;
139 uint8_t buf[32];
140 SHAKE128_CTX shake128;
141 rndsave_t rs;
142 SHA1_CTX s;
143 ssize_t nread, nwrit;
144 int fd_random;
145
146 /* Paranoia: Avoid stack memory disclosure. */
147 memset(&rs, 0, sizeof rs);
148
149 /* Open /dev/urandom to read data from the system. */
150 if ((fd_random = open(_PATH_URANDOM, O_RDONLY)) == -1) {
151 warn("open /dev/urandom");
152 return -1;
153 }
154
155 /* Find how much entropy is in the pool. */
156 if (ioctl(fd_random, RNDGETENTCNT, &systementropy) == -1) {
157 warn("ioctl(RNDGETENTCNT)");
158 systementropy = 0;
159 }
160
161 /* Read some data from /dev/urandom. */
162 if ((size_t)(nread = read(fd_random, buf, sizeof buf)) != sizeof buf) {
163 if (nread == -1)
164 warn("read");
165 else
166 warnx("truncated read");
167 return -1;
168 }
169
170 /* Close /dev/urandom; we're done with it. */
171 if (close(fd_random) == -1)
172 warn("close");
173 fd_random = -1; /* paranoia */
174
175 /*
176 * Hash what we read together with the extra input to generate
177 * the seed data.
178 */
179 SHAKE128_Init(&shake128);
180 SHAKE128_Update(&shake128, buf, sizeof buf);
181 SHAKE128_Update(&shake128, extra, nextra);
182 SHAKE128_Final(rs.data, sizeof(rs.data), &shake128);
183 explicit_memset(&shake128, 0, sizeof shake128); /* paranoia */
184
185 /*
186 * Report an upper bound on the min-entropy of the seed data.
187 * We take the larger of the system entropy and the extra
188 * entropy -- the system state and the extra input may or may
189 * not be independent, so we can't add them -- and clamp to the
190 * size of the data.
191 */
192 systementropy = MIN(systementropy,
193 MIN(sizeof(buf), UINT32_MAX/NBBY)*NBBY);
194 extraentropy = MIN(extraentropy, MIN(nextra, UINT32_MAX/NBBY)*NBBY);
195 rs.entropy = MIN(MAX(systementropy, extraentropy),
196 MIN(sizeof(rs.data), UINT32_MAX/NBBY)*NBBY);
197
198 /*
199 * Compute the checksum on the 32-bit entropy count, followed
200 * by the seed data.
201 */
202 SHA1Init(&s);
203 SHA1Update(&s, (const uint8_t *)&rs.entropy, sizeof(rs.entropy));
204 SHA1Update(&s, rs.data, sizeof(rs.data));
205 SHA1Final(rs.digest, &s);
206 explicit_memset(&s, 0, sizeof s); /* paranoia */
207
208 /*
209 * Write it to a temporary file and sync it before we commit.
210 * This way either the old seed or the new seed is completely
211 * written in the expected location on disk even if the system
212 * crashes as long as the file system doesn't get corrupted too
213 * badly.
214 *
215 * If interrupted after this point and the temporary file is
216 * disclosed, no big deal -- either the pool was predictable to
217 * begin with in which case we're hosed either way, or we've
218 * just revealed some output which is not a problem.
219 */
220 if ((size_t)(nwrit = write(fd_seed, &rs, sizeof rs)) != sizeof rs) {
221 int error = errno;
222 if (unlink(tmp) == -1)
223 warn("unlink");
224 if (nwrit == -1)
225 warnc(error, "write");
226 else
227 warnx("truncated write");
228 return -1;
229 }
230 explicit_memset(&rs, 0, sizeof rs); /* paranoia */
231 if (fsync_range(fd_seed, FDATASYNC|FDISKSYNC, 0, 0) == -1) {
232 int error = errno;
233 if (unlink(tmp) == -1)
234 warn("unlink");
235 warnc(error, "fsync_range");
236 return -1;
237 }
238 if (close(fd_seed) == -1)
239 warn("close");
240
241 /* Rename it over the original file to commit. */
242 if (rename(tmp, filename) == -1) {
243 warn("rename");
244 return -1;
245 }
246
247 /* Success! */
248 return 0;
249 }
250
251 static void
do_save(const char * filename)252 do_save(const char *filename)
253 {
254 char tmp[PATH_MAX];
255 int fd_seed;
256
257 /* Consolidate any pending samples. */
258 if (sysctlbyname("kern.entropy.consolidate", NULL, NULL,
259 (const int[]){1}, sizeof(int)) == -1)
260 warn("consolidate entropy");
261
262 /* Format the temporary file name. */
263 if (snprintf(tmp, sizeof tmp, "%s.tmp", filename) >= PATH_MAX)
264 errx(1, "path too long");
265
266 /* Create a temporary seed file. */
267 if ((fd_seed = open(tmp, O_CREAT|O_TRUNC|O_WRONLY, 0600)) == -1)
268 err(1, "open seed file to save");
269
270 /* Update the seed. Abort on failure. */
271 if (update_seed(filename, fd_seed, tmp, NULL, 0, 0) == -1)
272 exit(1);
273 }
274
275 static void
do_load(const char * filename)276 do_load(const char *filename)
277 {
278 char tmp[PATH_MAX];
279 int fd_new, fd_old, fd_random;
280 rndsave_t rs;
281 rnddata_t rd;
282 ssize_t nread, nwrit;
283 SHA1_CTX s;
284 uint8_t digest[SHA1_DIGEST_LENGTH];
285 int ro = 0, fail = 0;
286 int error;
287
288 /*
289 * 1. Load the old seed.
290 * 2. Feed the old seed into the kernel.
291 * 3. Generate and write a new seed.
292 * 4. Erase the old seed if we can.
293 *
294 * We follow the procedure in
295 *
296 * Niels Ferguson, Bruce Schneier, and Tadayoshi Kohno,
297 * _Cryptography Engineering_, Wiley, 2010, Sec. 9.6.2
298 * `Update Seed File'.
299 *
300 * Additionally, we zero the seed's stored entropy estimate if
301 * it appears to be on a read-only medium.
302 */
303
304 /* Format the temporary file name. */
305 if (snprintf(tmp, sizeof tmp, "%s.tmp", filename) >= PATH_MAX)
306 errx(1, "path too long");
307
308 /* Create a new seed file or determine the medium is read-only. */
309 if ((fd_new = open(tmp, O_CREAT|O_TRUNC|O_WRONLY, 0600)) == -1) {
310 warn("update seed file");
311 ro = 1;
312 }
313
314 /*
315 * 1. Load the old seed.
316 */
317 if ((fd_old = open(filename, O_RDWR)) == -1) {
318 error = errno;
319 if ((error != EPERM && error != EROFS) ||
320 (fd_old = open(filename, O_RDONLY)) == -1)
321 err(1, "open seed file to load");
322 if (fd_new != -1)
323 warnc(error, "can't overwrite old seed file");
324 ro = 1;
325 }
326 if ((size_t)(nread = read(fd_old, &rs, sizeof rs)) != sizeof rs) {
327 if (nread == -1)
328 err(1, "read seed");
329 else
330 errx(1, "seed too short");
331 }
332
333 /* Verify its checksum. */
334 SHA1Init(&s);
335 SHA1Update(&s, (const uint8_t *)&rs.entropy, sizeof(rs.entropy));
336 SHA1Update(&s, rs.data, sizeof(rs.data));
337 SHA1Final(digest, &s);
338 if (!consttime_memequal(digest, rs.digest, sizeof(digest))) {
339 /*
340 * If the checksum doesn't match, doesn't hurt to feed
341 * the seed in anyway, but act as though it has zero
342 * entropy in case it was corrupted with predictable
343 * garbage.
344 */
345 warnx("bad checksum");
346 rs.entropy = 0;
347 }
348
349 /*
350 * If the entropy is insensibly large, try byte-swapping.
351 * Otherwise assume the file is corrupted and act as though it
352 * has zero entropy.
353 */
354 if (howmany(rs.entropy, NBBY) > sizeof(rs.data)) {
355 rs.entropy = bswap32(rs.entropy);
356 if (howmany(rs.entropy, NBBY) > sizeof(rs.data)) {
357 warnx("bad entropy estimate");
358 rs.entropy = 0;
359 }
360 }
361
362 /* If the medium can't be updated, zero the entropy estimate. */
363 if (ro)
364 rs.entropy = 0;
365
366 /* Fail later on if there's no entropy in the seed. */
367 if (rs.entropy == 0) {
368 warnx("no entropy in seed");
369 fail = 1;
370 }
371
372 /* If the user asked, zero the entropy estimate, but succeed. */
373 if (iflag)
374 rs.entropy = 0;
375
376 /*
377 * 2. Feed the old seed into the kernel.
378 *
379 * This also has the effect of consolidating pending samples,
380 * whether or not there are enough samples from sources deemed
381 * to have full entropy, so that the updated seed will
382 * incorporate them.
383 */
384 rd.len = MIN(sizeof(rd.data), sizeof(rs.data));
385 rd.entropy = rs.entropy;
386 memcpy(rd.data, rs.data, rd.len);
387 explicit_memset(&rs, 0, sizeof rs); /* paranoia */
388 if ((fd_random = open(_PATH_URANDOM, O_WRONLY)) == -1)
389 err(1, "open /dev/urandom");
390 if (ioctl(fd_random, RNDADDDATA, &rd) == -1)
391 err(1, "RNDADDDATA");
392 explicit_memset(&rd, 0, sizeof rd); /* paranoia */
393 if (close(fd_random) == -1)
394 warn("close /dev/urandom");
395 fd_random = -1; /* paranoia */
396
397 /*
398 * 3. Generate and write a new seed.
399 */
400 if (fd_new == -1 ||
401 update_seed(filename, fd_new, tmp, rs.data, sizeof(rs.data),
402 rs.entropy) == -1)
403 fail = 1;
404
405 /*
406 * 4. Erase the old seed.
407 *
408 * Only effective if we're on a fixed-address file system like
409 * ffs -- doesn't help to erase the data on lfs, but doesn't
410 * hurt either. No need to unlink because update_seed will
411 * have already renamed over it.
412 */
413 if (!ro) {
414 memset(&rs, 0, sizeof rs);
415 if ((size_t)(nwrit = pwrite(fd_old, &rs, sizeof rs, 0)) !=
416 sizeof rs) {
417 if (nwrit == -1)
418 err(1, "overwrite old seed");
419 else
420 errx(1, "truncated overwrite");
421 }
422 if (fsync_range(fd_old, FDATASYNC|FDISKSYNC, 0, 0) == -1)
423 err(1, "fsync_range");
424 }
425
426 /* Fail noisily if anything went wrong. */
427 if (fail)
428 exit(1);
429 }
430
431 static void
do_ioctl(rndctl_t * rctl)432 do_ioctl(rndctl_t *rctl)
433 {
434 int fd;
435 int res;
436
437 fd = open(_PATH_URANDOM, O_RDONLY, 0644);
438 if (fd < 0)
439 err(1, "open");
440
441 res = ioctl(fd, RNDCTL, rctl);
442 if (res < 0)
443 err(1, "ioctl(RNDCTL)");
444
445 close(fd);
446 }
447
448 static char *
strflags(uint32_t totalbits,u_int32_t fl)449 strflags(uint32_t totalbits, u_int32_t fl)
450 {
451 static char str[512];
452
453 str[0] = '\0';
454 if (totalbits > 0 && (fl & RND_FLAG_NO_ESTIMATE) == 0)
455 strlcat(str, "estimate, ", sizeof(str));
456
457 if ((fl & RND_FLAG_NO_COLLECT) == 0)
458 strlcat(str, "collect, ", sizeof(str));
459
460 if (fl & RND_FLAG_COLLECT_VALUE)
461 strlcat(str, "v, ", sizeof(str));
462 if (fl & RND_FLAG_COLLECT_TIME)
463 strlcat(str, "t, ", sizeof(str));
464
465 if (str[strlen(str) - 2] == ',')
466 str[strlen(str) - 2] = '\0';
467
468 return (str);
469 }
470
471 #define HEADER "Source Estimated bits Samples Type Flags\n"
472
473 static void
do_print_source(rndsource_est_t * source)474 do_print_source(rndsource_est_t *source)
475 {
476 printf("%-16s ", source->rt.name);
477 printf("%10" PRIu32 " ", source->rt.total);
478 printf("%10" PRIu32 " ", source->dt_samples + source->dv_samples);
479 printf("%-6s ", find_name(source->rt.type));
480 printf("%s\n", strflags(source->rt.total, source->rt.flags));
481 }
482
483 static void
do_print_source_verbose(rndsource_est_t * source)484 do_print_source_verbose(rndsource_est_t *source)
485 {
486 printf("\tDt samples = %d\n", source->dt_samples);
487 printf("\tDt bits = %d\n", source->dt_total);
488 printf("\tDv samples = %d\n", source->dv_samples);
489 printf("\tDv bits = %d\n", source->dv_total);
490 }
491
492 static void
do_list(int all,u_int32_t type,char * name)493 do_list(int all, u_int32_t type, char *name)
494 {
495 rndstat_est_t rstat;
496 rndstat_est_name_t rstat_name;
497 int fd;
498 int res;
499 uint32_t i;
500 u_int32_t start;
501
502 fd = open(_PATH_URANDOM, O_RDONLY, 0644);
503 if (fd < 0)
504 err(1, "open");
505
506 if (!all && type == 0xff) {
507 strncpy(rstat_name.name, name, sizeof(rstat_name.name));
508 res = ioctl(fd, RNDGETESTNAME, &rstat_name);
509 if (res < 0)
510 err(1, "ioctl(RNDGETESTNAME)");
511 printf(HEADER);
512 do_print_source(&rstat_name.source);
513 if (vflag)
514 do_print_source_verbose(&rstat_name.source);
515 close(fd);
516 return;
517 }
518
519 /*
520 * Run through all the devices present in the system, and either
521 * print out ones that match, or print out all of them.
522 */
523 printf(HEADER);
524 start = 0;
525 for (;;) {
526 rstat.count = RND_MAXSTATCOUNT;
527 rstat.start = start;
528 res = ioctl(fd, RNDGETESTNUM, &rstat);
529 if (res < 0)
530 err(1, "ioctl(RNDGETESTNUM)");
531
532 if (rstat.count == 0)
533 break;
534
535 for (i = 0; i < rstat.count; i++) {
536 if (all || type == rstat.source[i].rt.type) {
537 do_print_source(&rstat.source[i]);
538 if (vflag)
539 do_print_source_verbose(&rstat.source[i]);
540 }
541 }
542 start += rstat.count;
543 }
544
545 close(fd);
546 }
547
548 static void
do_stats(void)549 do_stats(void)
550 {
551 rndpoolstat_t rs;
552 int fd;
553
554 fd = open(_PATH_URANDOM, O_RDONLY, 0644);
555 if (fd < 0)
556 err(1, "open");
557
558 if (ioctl(fd, RNDGETPOOLSTAT, &rs) < 0)
559 err(1, "ioctl(RNDGETPOOLSTAT)");
560
561 printf("\t%9u bits currently stored in pool (max %u)\n",
562 rs.curentropy, rs.maxentropy);
563
564 close(fd);
565 }
566
567 int
main(int argc,char ** argv)568 main(int argc, char **argv)
569 {
570 rndctl_t rctl;
571 int ch, cmd, lflag, mflag, sflag;
572 u_int32_t type;
573 char name[16] = "";
574 const char *filename = NULL;
575
576 if (SHA3_Selftest() != 0)
577 errx(1, "SHA-3 self-test failed");
578
579 rctl.mask = 0;
580 rctl.flags = 0;
581
582 cmd = 0;
583 lflag = 0;
584 mflag = 0;
585 sflag = 0;
586 type = 0xff;
587
588 while ((ch = getopt(argc, argv, "CES:L:celit:d:sv")) != -1) {
589 switch (ch) {
590 case 'C':
591 rctl.flags |= RND_FLAG_NO_COLLECT;
592 rctl.mask |= RND_FLAG_NO_COLLECT;
593 mflag++;
594 break;
595 case 'E':
596 rctl.flags |= RND_FLAG_NO_ESTIMATE;
597 rctl.mask |= RND_FLAG_NO_ESTIMATE;
598 mflag++;
599 break;
600 case 'L':
601 if (cmd != 0)
602 usage();
603 cmd = 'L';
604 filename = optarg;
605 break;
606 case 'S':
607 if (cmd != 0)
608 usage();
609 cmd = 'S';
610 filename = optarg;
611 break;
612 case 'c':
613 rctl.flags &= ~RND_FLAG_NO_COLLECT;
614 rctl.mask |= RND_FLAG_NO_COLLECT;
615 mflag++;
616 break;
617 case 'e':
618 rctl.flags &= ~RND_FLAG_NO_ESTIMATE;
619 rctl.mask |= RND_FLAG_NO_ESTIMATE;
620 mflag++;
621 break;
622 case 'i':
623 iflag = 1;
624 break;
625 case 'l':
626 lflag++;
627 break;
628 case 't':
629 if (cmd != 0)
630 usage();
631 cmd = 't';
632
633 type = find_type(optarg);
634 break;
635 case 'd':
636 if (cmd != 0)
637 usage();
638 cmd = 'd';
639
640 type = 0xff;
641 strlcpy(name, optarg, sizeof(name));
642 break;
643 case 's':
644 sflag++;
645 break;
646 case 'v':
647 vflag++;
648 break;
649 case '?':
650 default:
651 usage();
652 }
653 }
654 argc -= optind;
655 argv += optind;
656
657 /*
658 * No leftover non-option arguments.
659 */
660 if (argc > 0)
661 usage();
662
663 /*
664 * -i makes sense only with -L.
665 */
666 if (iflag && cmd != 'L')
667 usage();
668
669 /*
670 * Save.
671 */
672 if (cmd == 'S') {
673 do_save(filename);
674 exit(0);
675 }
676
677 /*
678 * Load.
679 */
680 if (cmd == 'L') {
681 do_load(filename);
682 exit(0);
683 }
684
685 /*
686 * Cannot list and modify at the same time.
687 */
688 if ((lflag != 0 || sflag != 0) && mflag != 0)
689 usage();
690
691 /*
692 * Bomb out on no-ops.
693 */
694 if (lflag == 0 && mflag == 0 && sflag == 0)
695 usage();
696
697 /*
698 * Modify request.
699 */
700 if (mflag != 0) {
701 rctl.type = type;
702 strncpy(rctl.name, name, sizeof(rctl.name));
703 do_ioctl(&rctl);
704
705 exit(0);
706 }
707
708 /*
709 * List sources.
710 */
711 if (lflag != 0)
712 do_list(cmd == 0, type, name);
713
714 if (sflag != 0)
715 do_stats();
716
717 exit(0);
718 }
719