summaryrefslogtreecommitdiff
path: root/src/random-seed.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/random-seed.c')
-rw-r--r--src/random-seed.c575
1 files changed, 575 insertions, 0 deletions
diff --git a/src/random-seed.c b/src/random-seed.c
new file mode 100644
index 0000000..0b7c5c4
--- /dev/null
+++ b/src/random-seed.c
@@ -0,0 +1,575 @@
+/* Copyright 2018 Alex Xu (aka Hello71, alxu)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdnoreturn.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/statfs.h>
+#include <sys/syscall.h>
+#include <unistd.h>
+
+#include "musl-libgen-c.h"
+#include "util.h"
+
+// musl forbids include/linux
+#define RNDADDENTROPY _IOW( 'R', 0x03, int [2] )
+
+/* 3 hours */
+#define DAEMONIZE_SLEEP_TIME (3*60*60)
+
+/* random pool is always the same size, so use a fixed size array */
+struct rand_pool_info_ {
+ int entropy_count;
+ int buf_size;
+ uint32_t buf[RAND_POOL_SIZE / sizeof(uint32_t)];
+};
+
+static const char MAGIC[] = "RANDOM SEED FILE VERSION 1\n";
+
+static sig_atomic_t sigint = 0;
+static sig_atomic_t sigterm = 0;
+
+static bool noperms = false;
+
+static inline void usage() {
+ puts("usage: random-seed MODE FILE");
+ puts("see random-seed(8) for more information.");
+}
+
+static char *get_machine_id() {
+#ifdef MACHINE_ID_PATH
+ FILE *machine_id_file = fopen(MACHINE_ID_PATH, "r");
+#else
+ const char *etc_machine_id = "/etc/machine-id";
+ const char *var_lib_dbus_machine_id = "/var/lib/dbus/machine-id";
+ FILE *machine_id_file = fopen(etc_machine_id, "r");
+ if (!machine_id_file) {
+ if (errno != ENOENT)
+ fprintf(stderr, "error opening %s: %s, trying %s\n",
+ etc_machine_id, strerror(errno), var_lib_dbus_machine_id);
+ machine_id_file = fopen(var_lib_dbus_machine_id, "r");
+ }
+#endif
+
+ if (!machine_id_file) {
+ perror("couldn't open any machine-id file, last error");
+ return NULL;
+ }
+
+ char *machine_id = NULL;
+ size_t machine_id_len = 0;
+ if (getdelim(&machine_id, &machine_id_len, '\0', machine_id_file) == -1) {
+ fputs("error reading machine id file\n", stderr);
+ free(machine_id);
+ return NULL;
+ }
+
+ return machine_id;
+}
+
+static bool get_machine_id_hash(const unsigned char salt[static SALT_LEN], unsigned char machine_id_digest[static HASH_LEN]) {
+ static char *c_machine_id;
+ if (!c_machine_id)
+ c_machine_id = get_machine_id();
+ if (!c_machine_id)
+ return false;
+
+ unsigned char c_machine_id_digest[HASH_LEN];
+ hash(salt, c_machine_id_digest, c_machine_id, strlen(c_machine_id));
+ memcpy(machine_id_digest, c_machine_id_digest, HASH_LEN);
+ return true;
+}
+
+static inline bool get_fs_id_hash(const unsigned char salt[static SALT_LEN], unsigned char fsid_digest[static HASH_LEN], int seed_fd) {
+ struct statfs statfs_buf;
+ if (fstatfs(seed_fd, &statfs_buf) == -1) {
+ fprintf(stderr, "error statfs seed file: %s, "
+ "disabling entropy credit\n", strerror(errno));
+ return false;
+ }
+ hash(salt, fsid_digest, &statfs_buf.f_fsid, sizeof(statfs_buf.f_fsid));
+ return true;
+}
+
+static bool run_seed_file_cmd(const char *cmd, const unsigned char *salt, FILE *seed_file) {
+ char *arg;
+ if (streq(cmd, "machine-id")) {
+ arg = strtok(NULL, " \t");
+ if (!arg) {
+ fputs("error parsing seed file: expected argument "
+ "to 'machine-id'\n", stderr);
+ return false;
+ }
+ unsigned char machine_id_hash[HASH_LEN];
+ if (!get_machine_id_hash(salt, machine_id_hash)) {
+ fputs("error getting machine id hash, disabling entropy credit\n",
+ stderr);
+ return false;
+ }
+
+ if (!hash_match(machine_id_hash, arg)) {
+ fputs("machine-id does not match, disabling entropy credit\n",
+ stderr);
+ return false;
+ }
+ return true;
+ }
+
+ if (streq(cmd, "fs-id")) {
+ arg = strtok(NULL, " \t");
+ if (!arg) {
+ fputs("error parsing seed file: expected argument to 'fs-id'\n", stderr);
+ return false;
+ }
+ unsigned char fs_id_hash[HASH_LEN];
+ if (!get_fs_id_hash(salt, fs_id_hash, fileno(seed_file))) {
+ fputs("error getting fs id hash, disabling entropy credit\n", stderr);
+ return false;
+ }
+
+ if (!hash_match(fs_id_hash, arg)) {
+ fputs("fs id does not match, disabling entropy credit\n", stderr);
+ return false;
+ }
+ return true;
+ }
+
+ fprintf(stderr, "error parsing seed file: unsupported command: %s\n", cmd);
+ return false;
+}
+
+static bool load(FILE *seed_file) {
+ bool credit_entropy = true;
+
+ struct rand_pool_info_ rpi = {
+ .entropy_count = RAND_POOL_SIZE * 8,
+ .buf_size = RAND_POOL_SIZE,
+ .buf = { 0 }
+ };
+
+ uint64_t linenum = 0;
+ char *line = NULL;
+ size_t n = 0;
+ bool done = false;
+
+ if (fread(&rpi.buf, 1, RAND_POOL_SIZE, seed_file) != RAND_POOL_SIZE) {
+ if (feof(seed_file)) {
+ fputs("premature EOF on seed file\n", stderr);
+ } else if (ferror(seed_file)) {
+ fputs("error reading from seed file\n", stderr);
+ }
+ // else: we got signalled, so return to main loop to quit or whatever
+ return false;
+ }
+
+ unsigned char *salt = (unsigned char *)rpi.buf;
+
+ while (1) {
+ errno = 0;
+ ssize_t line_length = getline(&line, &n, seed_file);
+ if (line_length == -1) {
+ if (errno) {
+ perror("error reading from seed file");
+ credit_entropy = false;
+ }
+ break;
+ }
+
+ linenum++;
+
+ if (linenum == 1) {
+ if (streq(line, MAGIC)) {
+ continue;
+ } else {
+ fputs("error parsing seed file: invalid magic\n", stderr);
+ credit_entropy = false;
+ break;
+ }
+ }
+
+ char *nul = memchr(line, '\0', (size_t)line_length);
+ if (nul) {
+ fprintf(stderr, "error parsing seed file: encountered NUL byte in commands line %zu char %zu\n", linenum, nul - line + 1);
+ credit_entropy = false;
+ break;
+ }
+
+ char *cmd = strtok(line, " \t\n");
+ if (!cmd)
+ continue;
+
+#ifdef DEBUG
+ fprintf(stderr, "executing command: %s\n", cmd);
+#endif
+
+ if (streq(cmd, "done")) {
+ done = true;
+ continue;
+ }
+
+ if (!run_seed_file_cmd(cmd, salt, seed_file)) {
+ credit_entropy = false;
+ break;
+ }
+ }
+
+ if (!linenum) {
+ fputs("seed file has no commands, assuming legacy format. disabling entropy credit\n", stderr);
+ credit_entropy = false;
+ }
+
+ if (credit_entropy && !done) {
+ fputs("missing done command, random seed file probably truncated. disabling entropy credit\n", stderr);
+ credit_entropy = false;
+ }
+
+ int random_fd = open("/dev/random", O_RDWR, 0);
+ if (random_fd == -1) {
+ perror("error opening /dev/random");
+ exit(1);
+ }
+
+ if (credit_entropy) {
+ if (ioctl(random_fd, RNDADDENTROPY, &rpi) == -1) {
+ perror("ioctl(RNDADDENTROPY)");
+ if (errno == EPERM) {
+ fputs("Continuing without crediting entropy.\n", stderr);
+ noperms = true;
+ }
+ credit_entropy = false;
+ }
+ }
+
+ if (!credit_entropy) {
+ if (write(random_fd, &rpi.buf, RAND_POOL_SIZE) != RAND_POOL_SIZE) {
+ fputs("error writing entropy to /dev/random\n", stderr);
+ exit(1);
+ }
+ }
+
+ return credit_entropy;
+}
+
+static bool get_rand_pool(unsigned char *buf) {
+ size_t rand_bytes_gotten = 0;
+
+ do {
+ long this_rand_bytes_gotten = random_get(buf + rand_bytes_gotten, RAND_POOL_SIZE - rand_bytes_gotten, 0);
+ if (this_rand_bytes_gotten == -1) {
+ switch (errno) {
+ case EAGAIN: continue;
+ case EINTR: return false;
+ default:
+ perror("getrandom");
+ exit(1);
+ }
+ } else {
+ rand_bytes_gotten += (size_t)this_rand_bytes_gotten;
+ }
+ } while (rand_bytes_gotten < RAND_POOL_SIZE);
+
+ return true;
+}
+
+/**
+ * Save entropy to disk.
+ *
+ * \param seed_path the seed file path
+ * \param random_buf the random buffer. if NULL, get our own entropy.
+ * \return true means saved successfully, false means received EINTR
+ */
+static bool save(const char *seed_path, unsigned char *random_buf) {
+ assert(seed_path);
+
+ bool rv = false;
+
+ unsigned char *random_ptr;
+ if (random_buf) {
+ random_ptr = random_buf;
+ } else {
+ random_ptr = alloca(RAND_POOL_SIZE);
+ if (!get_rand_pool(random_ptr))
+ return false;
+ }
+
+ unsigned char machine_id_digest[HASH_LEN];
+ if (!get_machine_id_hash(random_ptr, machine_id_digest)) {
+ fputs("cannot obtain machine id, aborting save\n", stderr);
+ return false;
+ }
+ char machine_id_hash[HASH_STR_LEN];
+ mem2hex(machine_id_hash, machine_id_digest, HASH_STR_LEN);
+ machine_id_hash[HASH_STR_LEN-1] = '\0';
+
+ int seed_dir_fd = -1;
+ int seed_fd = -1;
+ FILE *seed_file = NULL;
+
+ char *seed_path_tmp = strdup(seed_path);
+ const char *seed_dir, *seed_name;
+ char *seed_path_last_slash = strrchr(seed_path_tmp, '/');
+ if (seed_path_last_slash) {
+ *seed_path_last_slash = '\0';
+ seed_dir = seed_path_tmp;
+ seed_name = seed_path_last_slash + 1;
+ } else {
+ free(seed_path_tmp);
+ seed_path_tmp = NULL;
+ seed_dir = ".";
+ seed_name = seed_path;
+ }
+ char *seed_name_new = NULL;
+
+ if (asprintf(&seed_name_new, ".%s.new", seed_name) == -1) {
+ fputs("out of memory\n", stderr);
+ goto out;
+ }
+
+ seed_dir_fd = open(seed_dir, O_RDONLY | O_DIRECTORY);
+ if (seed_dir_fd == -1) {
+ perror("error opening seed directory");
+ goto out;
+ }
+
+ unsigned char fs_id_digest[HASH_LEN];
+ if (!get_fs_id_hash(random_ptr, fs_id_digest, seed_dir_fd)) {
+ fputs("cannot obtain machine id, aborting save\n", stderr);
+ goto out;
+ }
+ char fs_id_hash[HASH_LEN*2+1];
+ mem2hex(fs_id_hash, fs_id_digest, HASH_LEN);
+ fs_id_hash[sizeof(fs_id_hash)-1] = '\0';
+
+ seed_fd = openat(seed_dir_fd, seed_name_new, O_WRONLY | O_CREAT | O_TRUNC, 0600);
+ if (seed_fd == -1) {
+ perror("error opening new seed file");
+ goto err;
+ }
+ seed_file = fdopen(seed_fd, "w");
+ if (!seed_file) {
+ perror("error converting seed file fd to stream");
+ goto err;
+ }
+
+ if (fwrite(random_ptr, 1, RAND_POOL_SIZE, seed_file) != RAND_POOL_SIZE
+ || fputs(MAGIC, seed_file) == EOF
+ || fputs("machine-id ", seed_file) == EOF
+ || fputs(machine_id_hash, seed_file) == EOF
+ || fputs("\nfs-id ", seed_file) == EOF
+ || fputs(fs_id_hash, seed_file) == EOF
+ || fputs("\ndone\n", seed_file) == EOF) {
+ fputs("error writing new seed file\n", stderr);
+ goto err;
+ }
+
+ if (fflush(seed_file) == EOF) {
+ perror("error flushing new seed file");
+ goto err;
+ }
+ if (fsync(seed_fd) == -1) {
+ perror("error syncing new seed file");
+ goto err;
+ }
+ if (renameat(seed_dir_fd, seed_name_new, seed_dir_fd, seed_name) == -1) {
+ perror("error installing new seed file");
+ goto err;
+ }
+ if (fclose(seed_file) == EOF) {
+ perror("error closing new seed file");
+ goto err;
+ }
+ if (fsync(seed_dir_fd) == -1) {
+ perror("error syncing seed directory");
+ goto out;
+ }
+
+ rv = true;
+ goto out;
+
+err:
+ if (seed_file) {
+ fclose(seed_file);
+ if (unlinkat(seed_dir_fd, seed_name_new, 0) == -1) {
+ perror("error removing temporary seed file");
+ rv = false;
+ }
+ }
+
+out:
+ if (seed_dir_fd != -1) {
+ if (close(seed_dir_fd) == -1) {
+ perror("error closing seed directory");
+ rv = false;
+ }
+ }
+
+ free(seed_path_tmp);
+ free(seed_name_new);
+
+ return rv;
+}
+
+static void sighandler(int signum) {
+ switch (signum) {
+ case SIGHUP:
+ // do nothing; we just needed to interrupt sleep
+ break;
+ case SIGINT:
+ sigint = 1;
+ break;
+ case SIGTERM:
+ sigterm = 1;
+ break;
+ default:
+ abort();
+ }
+}
+
+noreturn static void run(const char *mode, const char *seed_path) {
+ FILE *seed_file;
+ int exit_status = 0;
+
+ if (streq(mode, "load")) {
+ bool refresh_seed = true;
+
+ if (streq(seed_path, "-")) {
+ fputs("warning: cannot refresh stdin seed\n", stderr);
+ seed_file = stdin;
+ refresh_seed = false;
+ } else {
+ seed_file = fopen(seed_path, "r");
+ }
+ if (!seed_file) {
+ perror("error opening seed file");
+ exit(1);
+ }
+ if (!load(seed_file)) {
+ if (noperms)
+ exit_status = 15;
+ else
+ exit_status = 1;
+ }
+ if (refresh_seed) {
+ unsigned char random_buf[RAND_POOL_SIZE];
+ unsigned char *random_ptr = random_buf;
+ switch (random_get(random_buf, RAND_POOL_SIZE, GRND_NONBLOCK)) {
+ case RAND_POOL_SIZE:
+ goto save;
+ case -1:
+ if (errno != EAGAIN) {
+ perror("getrandom");
+ exit(1);
+ }
+ }
+ if (daemon(0, 1) == -1) {
+ perror("error daemonizing, continuing without");
+ }
+ close(0);
+ close(1);
+ random_ptr = NULL;
+save: if (!save(seed_path, random_ptr))
+ exit_status = 1;
+ }
+ exit(exit_status);
+ } else if (streq(mode, "save")) {
+ exit(!save(seed_path, NULL));
+ } else if (streq(mode, "daemonize")) {
+ if (streq(seed_path, "-")) {
+ fputs("error: seed_path cannot be - for daemonize\n", stderr);
+ exit(2);
+ }
+ seed_file = fopen(seed_path, "r");
+ if (!seed_file) {
+ perror("error opening seed file");
+ exit(3);
+ }
+ if (!load(seed_file))
+ fputs("warning: failed to load initial entropy\n", stderr);
+
+ struct sigaction sa;
+ sa.sa_handler = sighandler;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = 0;
+ sigaction(SIGHUP, &sa, NULL);
+ sigaction(SIGINT, &sa, NULL);
+ sigaction(SIGTERM, &sa, NULL);
+
+ if (daemon(seed_path[0] != '/', 1) == -1) {
+ perror("error daemonizing");
+ exit(1);
+ }
+ close(0);
+ close(1);
+ // don't close stderr because we log there
+
+ while (true) {
+ if (sigint)
+ exit(exit_status);
+ if (!save(seed_path, NULL)) {
+ exit_status = 1;
+ fputs("an error occurred while saving, trying again later\n", stderr);
+ }
+ if (sigterm)
+ exit(exit_status);
+ sleep(DAEMONIZE_SLEEP_TIME);
+ }
+ } else if (streq(mode, "daemonise")) {
+ fputs("invalid mode (did you mean `daemonize'?)\n", stderr);
+ exit(2);
+ } else {
+ fputs("invalid mode, expected load, save, or daemonize\n", stderr);
+ exit(2);
+ }
+}
+
+int main(int argc, char *argv[]) {
+ char *mode, *seed_path;
+
+ switch (argc) {
+ case 2:
+ if (streq(argv[1], "-h") || streq(argv[1], "--help")) {
+ usage();
+ exit(0);
+ }
+ if (streq(argv[1], "-V") || streq(argv[1], "--version")) {
+ printf("random-seed %s\n", PACKAGE_VERSION);
+ exit(0);
+ }
+ mode = argv[1];
+ seed_path = DEFAULT_SEED_PATH;
+ break;
+ case 3:
+ mode = argv[1];
+ seed_path = argv[2];
+ break;
+ default:
+ fprintf(stderr, "error: invalid arguments\n");
+ usage();
+ exit(2);
+ }
+
+ umask(0);
+ run(mode, seed_path);
+}