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