seedrng.c 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  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. //applet:IF_SEEDRNG(APPLET(seedrng, BB_DIR_USR_SBIN, BB_SUID_DROP))
  30. //kbuild:lib-$(CONFIG_SEEDRNG) += seedrng.o
  31. //usage:#define seedrng_trivial_usage
  32. //usage: "[-d DIR] [-n]"
  33. //usage:#define seedrng_full_usage "\n\n"
  34. //usage: "Seed the kernel RNG from seed files"
  35. //usage: "\n"
  36. //usage: "\n -d DIR Use seed files in DIR (default: /var/lib/seedrng)"
  37. //usage: "\n -n Do not credit randomness, even if creditable"
  38. #include "libbb.h"
  39. #include <linux/random.h>
  40. #include <sys/random.h>
  41. #include <sys/file.h>
  42. #ifndef GRND_INSECURE
  43. #define GRND_INSECURE 0x0004 /* Apparently some headers don't ship with this yet. */
  44. #endif
  45. #define DEFAULT_SEED_DIR "/var/lib/seedrng"
  46. #define CREDITABLE_SEED_NAME "seed.credit"
  47. #define NON_CREDITABLE_SEED_NAME "seed.no-credit"
  48. enum {
  49. MIN_SEED_LEN = SHA256_OUTSIZE,
  50. /* kernels < 5.18 could return short reads from getrandom()
  51. * if signal is pending and length is > 256.
  52. * Let's limit our reads to 256 bytes.
  53. */
  54. MAX_SEED_LEN = 256,
  55. };
  56. static size_t determine_optimal_seed_len(void)
  57. {
  58. char poolsize_str[12];
  59. unsigned poolsize;
  60. int n;
  61. n = open_read_close("/proc/sys/kernel/random/poolsize", poolsize_str, sizeof(poolsize_str) - 1);
  62. if (n < 0) {
  63. bb_perror_msg("can't determine pool size, assuming %u bits", MIN_SEED_LEN * 8);
  64. return MIN_SEED_LEN;
  65. }
  66. poolsize_str[n] = '\0';
  67. poolsize = (bb_strtou(poolsize_str, NULL, 10) + 7) / 8;
  68. return MAX(MIN(poolsize, MAX_SEED_LEN), MIN_SEED_LEN);
  69. }
  70. static bool read_new_seed(uint8_t *seed, size_t len)
  71. {
  72. bool is_creditable;
  73. ssize_t ret;
  74. ret = getrandom(seed, len, GRND_NONBLOCK);
  75. if (ret == (ssize_t)len) {
  76. return true;
  77. }
  78. if (ret < 0 && errno == ENOSYS) {
  79. int fd = xopen("/dev/random", O_RDONLY);
  80. struct pollfd random_fd;
  81. random_fd.fd = fd;
  82. random_fd.events = POLLIN;
  83. is_creditable = poll(&random_fd, 1, 0) == 1;
  84. //This is racy. is_creditable can be set to true here, but other process
  85. //can consume "good" random data from /dev/urandom before we do it below.
  86. close(fd);
  87. } else {
  88. if (getrandom(seed, len, GRND_INSECURE) == (ssize_t)len)
  89. return false;
  90. is_creditable = false;
  91. }
  92. /* Either getrandom() is not implemented, or
  93. * getrandom(GRND_INSECURE) did not give us LEN bytes.
  94. * Fallback to reading /dev/urandom.
  95. */
  96. errno = 0;
  97. if (open_read_close("/dev/urandom", seed, len) != (ssize_t)len)
  98. bb_perror_msg_and_die("can't read '%s'", "/dev/urandom");
  99. return is_creditable;
  100. }
  101. static void seed_from_file_if_exists(const char *filename, int dfd, bool credit, sha256_ctx_t *hash)
  102. {
  103. struct {
  104. int entropy_count;
  105. int buf_size;
  106. uint8_t buf[MAX_SEED_LEN];
  107. } req;
  108. ssize_t seed_len;
  109. seed_len = open_read_close(filename, req.buf, sizeof(req.buf));
  110. if (seed_len < 0) {
  111. if (errno != ENOENT)
  112. bb_perror_msg_and_die("can't read '%s'", filename);
  113. return;
  114. }
  115. xunlink(filename);
  116. if (seed_len != 0) {
  117. int fd;
  118. /* We are going to use this data to seed the RNG:
  119. * we believe it to genuinely containing entropy.
  120. * If this just-unlinked file survives
  121. * (if machine crashes before deletion is recorded on disk)
  122. * and we reuse it after reboot, this assumption
  123. * would be violated, and RNG may end up generating
  124. * the same data. fsync the directory
  125. * to make sure file is gone:
  126. */
  127. if (fsync(dfd) != 0)
  128. bb_simple_perror_msg_and_die("I/O error");
  129. //Length is not random, and taking its address spills variable to stack
  130. // sha256_hash(hash, &seed_len, sizeof(seed_len));
  131. sha256_hash(hash, req.buf, seed_len);
  132. req.buf_size = seed_len;
  133. seed_len *= 8;
  134. req.entropy_count = credit ? seed_len : 0;
  135. printf("Seeding %u bits %s crediting\n",
  136. (unsigned)seed_len, credit ? "and" : "without");
  137. fd = xopen("/dev/urandom", O_RDONLY);
  138. xioctl(fd, RNDADDENTROPY, &req);
  139. if (ENABLE_FEATURE_CLEAN_UP)
  140. close(fd);
  141. }
  142. }
  143. int seedrng_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
  144. int seedrng_main(int argc UNUSED_PARAM, char **argv)
  145. {
  146. const char *seed_dir;
  147. int fd, dfd;
  148. int i;
  149. unsigned opts;
  150. uint8_t new_seed[MAX_SEED_LEN];
  151. size_t new_seed_len;
  152. bool new_seed_creditable;
  153. struct timespec timestamp[2];
  154. sha256_ctx_t hash;
  155. enum {
  156. OPT_n = (1 << 0), /* must be 1 */
  157. OPT_d = (1 << 1),
  158. };
  159. #if ENABLE_LONG_OPTS
  160. static const char longopts[] ALIGN1 =
  161. "skip-credit\0" No_argument "n"
  162. "seed-dir\0" Required_argument "d"
  163. ;
  164. #endif
  165. seed_dir = DEFAULT_SEED_DIR;
  166. opts = getopt32long(argv, "nd:", longopts, &seed_dir);
  167. umask(0077);
  168. if (getuid() != 0)
  169. bb_simple_error_msg_and_die(bb_msg_you_must_be_root);
  170. if (mkdir(seed_dir, 0700) < 0 && errno != EEXIST)
  171. bb_perror_msg_and_die("can't create directory '%s'", seed_dir);
  172. dfd = xopen(seed_dir, O_DIRECTORY | O_RDONLY);
  173. xfchdir(dfd);
  174. /* Concurrent runs of this tool might feed the same data to RNG twice.
  175. * Avoid concurrent runs by taking a blocking lock on the directory.
  176. * Not checking for errors. Looking at manpage,
  177. * ENOLCK "The kernel ran out of memory for allocating lock records"
  178. * seems to be the only one which is possible - and if that happens,
  179. * machine is OOMing (much worse problem than inability to lock...).
  180. * Also, typically configured Linux machines do not fail GFP_KERNEL
  181. * allocations (they trigger memory reclaim instead).
  182. */
  183. flock(dfd, LOCK_EX); /* blocks while another instance runs */
  184. sha256_begin(&hash);
  185. //Hashing in a constant string doesn't add any entropy
  186. // sha256_hash(&hash, "SeedRNG v1 Old+New Prefix", 25);
  187. clock_gettime(CLOCK_REALTIME, &timestamp[0]);
  188. clock_gettime(CLOCK_BOOTTIME, &timestamp[1]);
  189. sha256_hash(&hash, timestamp, sizeof(timestamp));
  190. for (i = 0; i <= 1; i++) {
  191. seed_from_file_if_exists(
  192. i == 0 ? NON_CREDITABLE_SEED_NAME : CREDITABLE_SEED_NAME,
  193. dfd,
  194. /*credit?*/ (opts ^ OPT_n) & i, /* 0, then 1 unless -n */
  195. &hash);
  196. }
  197. new_seed_len = determine_optimal_seed_len();
  198. new_seed_creditable = read_new_seed(new_seed, new_seed_len);
  199. //Length is not random, and taking its address spills variable to stack
  200. // sha256_hash(&hash, &new_seed_len, sizeof(new_seed_len));
  201. sha256_hash(&hash, new_seed, new_seed_len);
  202. sha256_end(&hash, new_seed + new_seed_len - SHA256_OUTSIZE);
  203. printf("Saving %u bits of %screditable seed for next boot\n",
  204. (unsigned)new_seed_len * 8, new_seed_creditable ? "" : "non-");
  205. fd = xopen3(NON_CREDITABLE_SEED_NAME, O_WRONLY | O_CREAT | O_TRUNC, 0400);
  206. xwrite(fd, new_seed, new_seed_len);
  207. if (new_seed_creditable) {
  208. /* More paranoia when we create a file which we believe contains
  209. * genuine entropy: make sure disk is not full, quota isn't exceeded, etc:
  210. */
  211. if (fsync(fd) < 0)
  212. bb_perror_msg_and_die("can't write '%s'", NON_CREDITABLE_SEED_NAME);
  213. xrename(NON_CREDITABLE_SEED_NAME, CREDITABLE_SEED_NAME);
  214. }
  215. return EXIT_SUCCESS;
  216. }