urandom-seed: go back to seeding with shell script temporarily

This reverts commit 2edc017a6e.

We shouldn't be using a shell script here, but the SeedRNG integration
into OpenWRT requires a bit more thought. Etienne raised some important
points immediately after this was merged and planned to send some follow
up commits, but became busy with other things. The points he raised are
important enough that we should actually back this out until it's ready
to go, and then merge it as a cohesive unit. So let's revert this for
now, and come back to it later on.

Cc: Etienne Champetier <champetier.etienne@gmail.com>
Cc: Petr Štetiar <ynezz@true.cz>
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
This commit is contained in:
Jason A. Donenfeld 2022-04-12 00:53:49 +02:00 committed by Petr Štetiar
parent 9a22943eb2
commit a001630a1a
5 changed files with 35 additions and 442 deletions

View File

@ -9,6 +9,7 @@ include $(INCLUDE_DIR)/package.mk
define Package/urandom-seed
SECTION:=base
CATEGORY:=Base system
DEPENDS:=+getrandom
TITLE:=/etc/urandom.seed handling for OpenWrt
URL:=https://openwrt.org/
endef
@ -18,15 +19,11 @@ define Build/Prepare
endef
define Build/Compile/Default
$(TARGET_CC) $(TARGET_CFLAGS) $(TARGET_CPPFLAGS) $(TARGET_LDFLAGS) \
-std=gnu99 -o $(PKG_BUILD_DIR)/seedrng seedrng.c
endef
Build/Compile = $(Build/Compile/Default)
define Package/urandom-seed/install
$(CP) ./files/* $(1)/
$(INSTALL_DIR) $(1)/sbin
$(CP) $(PKG_BUILD_DIR)/seedrng $(1)/sbin/
endef
$(eval $(call BuildPackage,urandom-seed))

View File

@ -5,7 +5,7 @@ USE_PROCD=1
start_service() {
procd_open_instance "urandom_seed"
procd_set_param command "/sbin/seedrng"
procd_set_param command "/sbin/urandom_seed"
procd_set_param stdout 1
procd_set_param stderr 1
procd_close_instance

View File

@ -2,11 +2,21 @@ log_urandom_seed() {
echo "urandom-seed: $1" > /dev/kmsg
}
_do_urandom_seed() {
[ -f "$1" ] || { log_urandom_seed "Seed file not found ($1)"; return; }
[ -O "$1" -a -G "$1" -a ! -x "$1" ] || { log_urandom_seed "Wrong owner / permissions for $1"; return; }
log_urandom_seed "Seeding with $1"
cat "$1" > /dev/urandom
}
do_urandom_seed() {
[ -c /dev/urandom ] || { log_urandom_seed "Something is wrong with /dev/urandom"; return; }
seedrng 2>&1 | while read -r line; do
log_urandom_seed "$line"
done
_do_urandom_seed "/etc/urandom.seed"
SEED="$(uci -q get system.@system[0].urandom_seed)"
[ "${SEED:0:1}" = "/" -a "$SEED" != "/etc/urandom.seed" ] && _do_urandom_seed "$SEED"
}
boot_hook_add preinit_main do_urandom_seed

View File

@ -0,0 +1,20 @@
#!/bin/sh
set -e
trap '[ "$?" -eq 0 ] || echo "An error occured" >&2' EXIT
save() {
touch "$1.tmp"
chown root:root "$1.tmp"
chmod 600 "$1.tmp"
getrandom 512 > "$1.tmp"
mv "$1.tmp" "$1"
echo "Seed saved ($1)"
}
SEED="$(uci -q get system.@system[0].urandom_seed || true)"
[ "${SEED:0:1}" = "/" ] && save "$SEED"
SEED=/etc/urandom.seed
[ ! -f $SEED ] && save "$SEED"
true

View File

@ -1,434 +0,0 @@
// SPDX-License-Identifier: (GPL-2.0 OR MIT OR Apache-2.0)
/*
* Copyright (C) 2022 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
*/
#include <linux/random.h>
#include <sys/random.h>
#include <sys/ioctl.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <poll.h>
#include <unistd.h>
#include <time.h>
#include <errno.h>
#include <endian.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#define SEED_DIR "/etc/seedrng"
#define CREDITABLE_SEED SEED_DIR "/seed.credit"
#define NON_CREDITABLE_SEED SEED_DIR "/seed.no-credit"
#define LOCK_FILE "/tmp/run/seedrng.lock"
enum blake2s_lengths {
BLAKE2S_BLOCK_LEN = 64,
BLAKE2S_HASH_LEN = 32,
BLAKE2S_KEY_LEN = 32
};
enum seedrng_lengths {
MAX_SEED_LEN = 512,
MIN_SEED_LEN = BLAKE2S_HASH_LEN
};
struct blake2s_state {
uint32_t h[8];
uint32_t t[2];
uint32_t f[2];
uint8_t buf[BLAKE2S_BLOCK_LEN];
unsigned int buflen;
unsigned int outlen;
};
#define le32_to_cpup(a) le32toh(*(a))
#define cpu_to_le32(a) htole32(a)
#ifndef ARRAY_SIZE
#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
#endif
#ifndef DIV_ROUND_UP
#define DIV_ROUND_UP(n, d) (((n) + (d) - 1) / (d))
#endif
static inline void cpu_to_le32_array(uint32_t *buf, unsigned int words)
{
while (words--) {
*buf = cpu_to_le32(*buf);
++buf;
}
}
static inline void le32_to_cpu_array(uint32_t *buf, unsigned int words)
{
while (words--) {
*buf = le32_to_cpup(buf);
++buf;
}
}
static inline uint32_t ror32(uint32_t word, unsigned int shift)
{
return (word >> (shift & 31)) | (word << ((-shift) & 31));
}
static const uint32_t blake2s_iv[8] = {
0x6A09E667UL, 0xBB67AE85UL, 0x3C6EF372UL, 0xA54FF53AUL,
0x510E527FUL, 0x9B05688CUL, 0x1F83D9ABUL, 0x5BE0CD19UL
};
static const uint8_t blake2s_sigma[10][16] = {
{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 },
{ 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 },
{ 11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4 },
{ 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8 },
{ 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13 },
{ 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9 },
{ 12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11 },
{ 13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10 },
{ 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5 },
{ 10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0 },
};
static void blake2s_set_lastblock(struct blake2s_state *state)
{
state->f[0] = -1;
}
static void blake2s_increment_counter(struct blake2s_state *state, const uint32_t inc)
{
state->t[0] += inc;
state->t[1] += (state->t[0] < inc);
}
static void blake2s_init_param(struct blake2s_state *state, const uint32_t param)
{
int i;
memset(state, 0, sizeof(*state));
for (i = 0; i < 8; ++i)
state->h[i] = blake2s_iv[i];
state->h[0] ^= param;
}
static void blake2s_init(struct blake2s_state *state, const size_t outlen)
{
blake2s_init_param(state, 0x01010000 | outlen);
state->outlen = outlen;
}
static void blake2s_compress(struct blake2s_state *state, const uint8_t *block, size_t nblocks, const uint32_t inc)
{
uint32_t m[16];
uint32_t v[16];
int i;
while (nblocks > 0) {
blake2s_increment_counter(state, inc);
memcpy(m, block, BLAKE2S_BLOCK_LEN);
le32_to_cpu_array(m, ARRAY_SIZE(m));
memcpy(v, state->h, 32);
v[ 8] = blake2s_iv[0];
v[ 9] = blake2s_iv[1];
v[10] = blake2s_iv[2];
v[11] = blake2s_iv[3];
v[12] = blake2s_iv[4] ^ state->t[0];
v[13] = blake2s_iv[5] ^ state->t[1];
v[14] = blake2s_iv[6] ^ state->f[0];
v[15] = blake2s_iv[7] ^ state->f[1];
#define G(r, i, a, b, c, d) do { \
a += b + m[blake2s_sigma[r][2 * i + 0]]; \
d = ror32(d ^ a, 16); \
c += d; \
b = ror32(b ^ c, 12); \
a += b + m[blake2s_sigma[r][2 * i + 1]]; \
d = ror32(d ^ a, 8); \
c += d; \
b = ror32(b ^ c, 7); \
} while (0)
#define ROUND(r) do { \
G(r, 0, v[0], v[ 4], v[ 8], v[12]); \
G(r, 1, v[1], v[ 5], v[ 9], v[13]); \
G(r, 2, v[2], v[ 6], v[10], v[14]); \
G(r, 3, v[3], v[ 7], v[11], v[15]); \
G(r, 4, v[0], v[ 5], v[10], v[15]); \
G(r, 5, v[1], v[ 6], v[11], v[12]); \
G(r, 6, v[2], v[ 7], v[ 8], v[13]); \
G(r, 7, v[3], v[ 4], v[ 9], v[14]); \
} while (0)
ROUND(0);
ROUND(1);
ROUND(2);
ROUND(3);
ROUND(4);
ROUND(5);
ROUND(6);
ROUND(7);
ROUND(8);
ROUND(9);
#undef G
#undef ROUND
for (i = 0; i < 8; ++i)
state->h[i] ^= v[i] ^ v[i + 8];
block += BLAKE2S_BLOCK_LEN;
--nblocks;
}
}
static void blake2s_update(struct blake2s_state *state, const void *inp, size_t inlen)
{
const size_t fill = BLAKE2S_BLOCK_LEN - state->buflen;
const uint8_t *in = inp;
if (!inlen)
return;
if (inlen > fill) {
memcpy(state->buf + state->buflen, in, fill);
blake2s_compress(state, state->buf, 1, BLAKE2S_BLOCK_LEN);
state->buflen = 0;
in += fill;
inlen -= fill;
}
if (inlen > BLAKE2S_BLOCK_LEN) {
const size_t nblocks = DIV_ROUND_UP(inlen, BLAKE2S_BLOCK_LEN);
blake2s_compress(state, in, nblocks - 1, BLAKE2S_BLOCK_LEN);
in += BLAKE2S_BLOCK_LEN * (nblocks - 1);
inlen -= BLAKE2S_BLOCK_LEN * (nblocks - 1);
}
memcpy(state->buf + state->buflen, in, inlen);
state->buflen += inlen;
}
static void blake2s_final(struct blake2s_state *state, uint8_t *out)
{
blake2s_set_lastblock(state);
memset(state->buf + state->buflen, 0, BLAKE2S_BLOCK_LEN - state->buflen);
blake2s_compress(state, state->buf, 1, state->buflen);
cpu_to_le32_array(state->h, ARRAY_SIZE(state->h));
memcpy(out, state->h, state->outlen);
}
static size_t determine_optimal_seed_len(void)
{
size_t ret = 0;
char poolsize_str[11] = { 0 };
int fd = open("/proc/sys/kernel/random/poolsize", O_RDONLY);
if (fd < 0 || read(fd, poolsize_str, sizeof(poolsize_str) - 1) < 0) {
fprintf(stderr, "WARNING: Unable to determine pool size, falling back to %u bits: %s\n", MIN_SEED_LEN * 8, strerror(errno));
ret = MIN_SEED_LEN;
} else
ret = DIV_ROUND_UP(strtoul(poolsize_str, NULL, 10), 8);
if (fd >= 0)
close(fd);
if (ret < MIN_SEED_LEN)
ret = MIN_SEED_LEN;
else if (ret > MAX_SEED_LEN)
ret = MAX_SEED_LEN;
return ret;
}
static int read_new_seed(uint8_t *seed, size_t len, bool *is_creditable)
{
ssize_t ret;
int urandom_fd;
*is_creditable = false;
ret = getrandom(seed, len, GRND_NONBLOCK);
if (ret == (ssize_t)len) {
*is_creditable = true;
return 0;
} else if (ret < 0 && errno == ENOSYS) {
struct pollfd random_fd = {
.fd = open("/dev/random", O_RDONLY),
.events = POLLIN
};
if (random_fd.fd < 0)
return -errno;
*is_creditable = poll(&random_fd, 1, 0) == 1;
close(random_fd.fd);
} else if (getrandom(seed, len, GRND_INSECURE) == (ssize_t)len)
return 0;
urandom_fd = open("/dev/urandom", O_RDONLY);
if (urandom_fd < 0)
return -errno;
ret = read(urandom_fd, seed, len);
if (ret == (ssize_t)len)
ret = 0;
else
ret = -errno ? -errno : -EIO;
close(urandom_fd);
return ret;
}
static int seed_rng(uint8_t *seed, size_t len, bool credit)
{
struct {
int entropy_count;
int buf_size;
uint8_t buffer[MAX_SEED_LEN];
} req = {
.entropy_count = credit ? len * 8 : 0,
.buf_size = len
};
int random_fd, ret;
if (len > sizeof(req.buffer))
return -EFBIG;
memcpy(req.buffer, seed, len);
random_fd = open("/dev/random", O_RDWR);
if (random_fd < 0)
return -errno;
ret = ioctl(random_fd, RNDADDENTROPY, &req);
if (ret)
ret = -errno ? -errno : -EIO;
close(random_fd);
return ret;
}
static int seed_from_file_if_exists(const char *filename, bool credit, struct blake2s_state *hash)
{
uint8_t seed[MAX_SEED_LEN];
ssize_t seed_len;
int fd, dfd, ret = 0;
fd = open(filename, O_RDONLY);
if (fd < 0 && errno == ENOENT)
return 0;
else if (fd < 0) {
ret = -errno;
fprintf(stderr, "ERROR: Unable to open seed file: %s\n", strerror(errno));
return ret;
}
dfd = open(SEED_DIR, O_DIRECTORY | O_RDONLY);
if (dfd < 0) {
ret = -errno;
close(fd);
fprintf(stderr, "ERROR: Unable to open seed directory: %s\n", strerror(errno));
return ret;
}
seed_len = read(fd, seed, sizeof(seed));
if (seed_len < 0) {
ret = -errno;
fprintf(stderr, "ERROR: Unable to read seed file: %s\n", strerror(errno));
}
close(fd);
if (ret) {
close(dfd);
return ret;
}
if ((unlink(filename) < 0 || fsync(dfd) < 0) && seed_len) {
ret = -errno;
fprintf(stderr, "ERROR: Unable to remove seed after reading, so not seeding: %s\n", strerror(errno));
}
close(dfd);
if (ret)
return ret;
if (!seed_len)
return 0;
blake2s_update(hash, &seed_len, sizeof(seed_len));
blake2s_update(hash, seed, seed_len);
fprintf(stdout, "Seeding %zd bits %s crediting\n", seed_len * 8, credit ? "and" : "without");
ret = seed_rng(seed, seed_len, credit);
if (ret < 0)
fprintf(stderr, "ERROR: Unable to seed: %s\n", strerror(-ret));
return ret;
}
static bool skip_credit(void)
{
const char *skip = getenv("SEEDRNG_SKIP_CREDIT");
return skip && (!strcmp(skip, "1") || !strcasecmp(skip, "true") ||
!strcasecmp(skip, "yes") || !strcasecmp(skip, "y"));
}
int main(int argc __attribute__((unused)), char *argv[] __attribute__((unused)))
{
static const char seedrng_prefix[] = "SeedRNG v1 Old+New Prefix";
static const char seedrng_failure[] = "SeedRNG v1 No New Seed Failure";
int ret, fd = -1, lock, program_ret = 0;
uint8_t new_seed[MAX_SEED_LEN];
size_t new_seed_len;
bool new_seed_creditable;
struct timespec realtime = { 0 }, boottime = { 0 };
struct blake2s_state hash;
umask(0077);
if (getuid()) {
fprintf(stderr, "ERROR: This program requires root\n");
return 1;
}
blake2s_init(&hash, BLAKE2S_HASH_LEN);
blake2s_update(&hash, seedrng_prefix, strlen(seedrng_prefix));
clock_gettime(CLOCK_REALTIME, &realtime);
clock_gettime(CLOCK_BOOTTIME, &boottime);
blake2s_update(&hash, &realtime, sizeof(realtime));
blake2s_update(&hash, &boottime, sizeof(boottime));
if (mkdir(SEED_DIR, 0700) < 0 && errno != EEXIST) {
fprintf(stderr, "ERROR: Unable to create \"%s\" directory: %s\n", SEED_DIR, strerror(errno));
return 1;
}
lock = open(LOCK_FILE, O_WRONLY | O_CREAT, 0000);
if (lock < 0 || flock(lock, LOCK_EX) < 0) {
fprintf(stderr, "ERROR: Unable to open lock file: %s\n", strerror(errno));
program_ret = 1;
goto out;
}
ret = seed_from_file_if_exists(NON_CREDITABLE_SEED, false, &hash);
if (ret < 0)
program_ret |= 1 << 1;
ret = seed_from_file_if_exists(CREDITABLE_SEED, !skip_credit(), &hash);
if (ret < 0)
program_ret |= 1 << 2;
new_seed_len = determine_optimal_seed_len();
ret = read_new_seed(new_seed, new_seed_len, &new_seed_creditable);
if (ret < 0) {
fprintf(stderr, "ERROR: Unable to read new seed: %s\n", strerror(-ret));
new_seed_len = BLAKE2S_HASH_LEN;
strncpy((char *)new_seed, seedrng_failure, new_seed_len);
program_ret |= 1 << 3;
}
blake2s_update(&hash, &new_seed_len, sizeof(new_seed_len));
blake2s_update(&hash, new_seed, new_seed_len);
blake2s_final(&hash, new_seed + new_seed_len - BLAKE2S_HASH_LEN);
fprintf(stdout, "Saving %zu bits of %s seed for next boot\n", new_seed_len * 8, new_seed_creditable ? "creditable" : "non-creditable");
fd = open(NON_CREDITABLE_SEED, O_WRONLY | O_CREAT | O_TRUNC, 0400);
if (fd < 0) {
fprintf(stderr, "ERROR: Unable to open seed file for writing: %s\n", strerror(errno));
program_ret |= 1 << 4;
goto out;
}
if (write(fd, new_seed, new_seed_len) != (ssize_t)new_seed_len || fsync(fd) < 0) {
fprintf(stderr, "ERROR: Unable to write seed file: %s\n", strerror(errno));
program_ret |= 1 << 5;
goto out;
}
if (new_seed_creditable && rename(NON_CREDITABLE_SEED, CREDITABLE_SEED) < 0) {
fprintf(stderr, "WARNING: Unable to make new seed creditable: %s\n", strerror(errno));
program_ret |= 1 << 6;
}
out:
if (fd >= 0)
close(fd);
if (lock >= 0)
close(lock);
return program_ret;
}