seedrng.c 8.4 KB

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