Simplify random number selection

We currently have a convoluted and complex selection of which random
numbers to use. We can simplify this down to two functions that cover
all of our use cases:

1) Randomness for crypto: this one needs to wait until the RNG is
   initialized. So it uses getrandom(0). If that's not available, it
   polls on /dev/random, and then reads from /dev/urandom. This function
   returns whether or not it was successful, as before.

2) Randomness for other things: this one uses getrandom(GRND_INSECURE).
   If it's not available it uses getrandom(GRND_NONBLOCK). And if that
   would block, then it falls back to /dev/urandom. And if /dev/urandom
   isn't available, it uses the fallback code. It never fails and
   doesn't return a value.

These two cases match all the uses of randomness inside of systemd.

I would prefer to make both of these return void, and get rid of the
fallback code, and simply assert in the incredibly unlikely case that
/dev/urandom doesn't exist. But Luca disagrees, so this commit attempts
to instead keep case (1) returning a return value, which all the callers
already check, and fix the fallback code in (2) to be less bad than
before.

For the less bad fallback code for (2), we now use auxval and some
timestamps, together with various counters representing the invocation,
hash it all together and provide the output. Provided that AT_RANDOM is
secure, this construction is probably okay too, though notably it
doesn't have any forward secrecy. Fortunately, it's only used by
random_bytes() and not by crypto_random_bytes().
This commit is contained in:
Jason A. Donenfeld
2022-05-31 09:20:52 +02:00
committed by Zbigniew Jędrzejewski-Szmek
parent fada4bdcaa
commit 87cb1ab676
20 changed files with 149 additions and 211 deletions
+122 -163
View File
@@ -30,183 +30,142 @@
#include "missing_syscall.h"
#include "parse-util.h"
#include "random-util.h"
#include "siphash24.h"
#include "sha256.h"
#include "time-util.h"
static bool srand_called = false;
int genuine_random_bytes(void *p, size_t n, RandomFlags flags) {
static int have_syscall = -1;
_cleanup_close_ int fd = -1;
/* Gathers some high-quality randomness from the kernel. This call won't block, unless the RANDOM_BLOCK
* flag is set. If it doesn't block, it will still always return some data from the kernel, regardless
* of whether the random pool is fully initialized or not. When creating cryptographic key material you
* should always use RANDOM_BLOCK. */
if (n == 0)
return 0;
/* Use the getrandom() syscall unless we know we don't have it. */
if (have_syscall != 0 && !HAS_FEATURE_MEMORY_SANITIZER) {
for (;;) {
ssize_t l = getrandom(p, n, FLAGS_SET(flags, RANDOM_BLOCK) ? 0 : GRND_INSECURE);
if (l > 0) {
have_syscall = true;
if ((size_t) l == n)
return 0; /* Yay, success! */
/* We didn't get enough data, so try again */
assert((size_t) l < n);
p = (uint8_t*) p + l;
n -= l;
continue;
} else if (l == 0) {
have_syscall = true;
return -EIO;
} else if (ERRNO_IS_NOT_SUPPORTED(errno)) {
/* We lack the syscall, continue with reading from /dev/urandom. */
have_syscall = false;
break;
} else if (errno == EINVAL) {
/* If we previously passed GRND_INSECURE, and this flag isn't known, then
* we're likely running an old kernel which has getrandom() but not
* GRND_INSECURE. In this case, fall back to /dev/urandom. */
if (!FLAGS_SET(flags, RANDOM_BLOCK))
break;
return -errno;
} else
return -errno;
}
}
fd = open("/dev/urandom", O_RDONLY|O_CLOEXEC|O_NOCTTY);
if (fd < 0)
return errno == ENOENT ? -ENOSYS : -errno;
return loop_read_exact(fd, p, n, true);
}
static void clear_srand_initialization(void) {
srand_called = false;
}
void initialize_srand(void) {
static bool pthread_atfork_registered = false;
unsigned x;
if (srand_called)
return;
/* This is a "best effort" kind of thing, but has no real security value.
* So, this should only be used by random_bytes(), which is not meant for
* crypto. This could be made better, but we're *not* trying to roll a
* userspace prng here, or even have forward secrecy, but rather just do
* the shortest thing that is at least better than libc rand(). */
static void fallback_random_bytes(void *p, size_t n) {
static thread_local uint64_t fallback_counter = 0;
struct {
char label[32];
uint64_t call_id, block_id;
usec_t stamp_mono, stamp_real;
pid_t pid, tid;
uint8_t auxval[16];
} state = {
/* Arbitrary domain separation to prevent other usage of AT_RANDOM from clashing. */
.label = "systemd fallback random bytes v1",
.call_id = fallback_counter++,
.stamp_mono = now(CLOCK_MONOTONIC),
.stamp_real = now(CLOCK_REALTIME),
.pid = getpid(),
.tid = gettid()
};
#if HAVE_SYS_AUXV_H
/* The kernel provides us with 16 bytes of entropy in auxv, so let's try to make use of that to seed
* the pseudo-random generator. It's better than nothing... But let's first hash it to make it harder
* to recover the original value by watching any pseudo-random bits we generate. After all the
* AT_RANDOM data might be used by other stuff too (in particular: ASLR), and we probably shouldn't
* leak the seed for that. */
const void *auxv = ULONG_TO_PTR(getauxval(AT_RANDOM));
if (auxv) {
static const uint8_t auxval_hash_key[16] = {
0x92, 0x6e, 0xfe, 0x1b, 0xcf, 0x00, 0x52, 0x9c, 0xcc, 0x42, 0xcf, 0xdc, 0x94, 0x1f, 0x81, 0x0f
};
x = (unsigned) siphash24(auxv, 16, auxval_hash_key);
} else
#endif
x = 0;
x ^= (unsigned) now(CLOCK_REALTIME);
x ^= (unsigned) gettid();
srand(x);
srand_called = true;
if (!pthread_atfork_registered) {
(void) pthread_atfork(NULL, NULL, clear_srand_initialization);
pthread_atfork_registered = true;
}
}
/* INT_MAX gives us only 31 bits, so use 24 out of that. */
#if RAND_MAX >= INT_MAX
assert_cc(RAND_MAX >= 16777215);
# define RAND_STEP 3
#else
/* SHORT_INT_MAX or lower gives at most 15 bits, we just use 8 out of that. */
assert_cc(RAND_MAX >= 255);
# define RAND_STEP 1
memcpy(state.auxval, ULONG_TO_PTR(getauxval(AT_RANDOM)), sizeof(state.auxval));
#endif
void pseudo_random_bytes(void *p, size_t n) {
uint8_t *q;
while (n > 0) {
struct sha256_ctx ctx;
/* This returns pseudo-random data using libc's rand() function. You probably never want to call this
* directly, because why would you use this if you can get better stuff cheaply? Use random_bytes()
* instead, see below: it will fall back to this function if there's nothing better to get, but only
* then. */
initialize_srand();
for (q = p; q < (uint8_t*) p + n; q += RAND_STEP) {
unsigned rr;
rr = (unsigned) rand();
#if RAND_STEP >= 3
if ((size_t) (q - (uint8_t*) p + 2) < n)
q[2] = rr >> 16;
#endif
#if RAND_STEP >= 2
if ((size_t) (q - (uint8_t*) p + 1) < n)
q[1] = rr >> 8;
#endif
q[0] = rr;
sha256_init_ctx(&ctx);
sha256_process_bytes(&state, sizeof(state), &ctx);
if (n < SHA256_DIGEST_SIZE) {
uint8_t partial[SHA256_DIGEST_SIZE];
sha256_finish_ctx(&ctx, partial);
memcpy(p, partial, n);
break;
}
sha256_finish_ctx(&ctx, p);
p = (uint8_t *) p + SHA256_DIGEST_SIZE;
n -= SHA256_DIGEST_SIZE;
++state.block_id;
}
}
void random_bytes(void *p, size_t n) {
static bool have_getrandom = true, have_grndinsecure = true;
_cleanup_close_ int fd = -1;
/* This returns high quality randomness if we can get it cheaply. If we can't because for some reason
* it is not available we'll try some crappy fallbacks.
*
* What this function will do:
*
* • Use getrandom(GRND_INSECURE) or /dev/urandom, to return high-quality random values if
* they are cheaply available, or less high-quality random values if they are not.
*
* • This function will return pseudo-random data, generated via libc rand() if nothing
* better is available.
*
* • This function will work fine in early boot
*
* • This function will always succeed
*
* What this function won't do:
*
* • This function will never fail: it will give you randomness no matter what. It might not
* be high quality, but it will return some, possibly generated via libc's rand() call.
*
* • This function will never block: if the only way to get good randomness is a blocking,
* synchronous getrandom() we'll instead provide you with pseudo-random data.
*
* This function is hence great for things like seeding hash tables, generating random numeric UNIX
* user IDs (that are checked for collisions before use) and such.
*
* This function is hence not useful for generating UUIDs or cryptographic key material.
*/
if (genuine_random_bytes(p, n, 0) >= 0)
if (n == 0)
return;
/* If for some reason some user made /dev/urandom unavailable to us, or the kernel has no entropy, use a PRNG instead. */
pseudo_random_bytes(p, n);
for (;;) {
ssize_t l;
if (!have_getrandom)
break;
l = getrandom(p, n, have_grndinsecure ? GRND_INSECURE : GRND_NONBLOCK);
if (l > 0) {
if ((size_t) l == n)
return; /* Done reading, success. */
p = (uint8_t *) p + l;
n -= l;
continue; /* Interrupted by a signal; keep going. */
} else if (l == 0)
break; /* Weird, so fallback to /dev/urandom. */
else if (ERRNO_IS_NOT_SUPPORTED(errno)) {
have_getrandom = false;
break; /* No syscall, so fallback to /dev/urandom. */
} else if (errno == EINVAL && have_grndinsecure) {
have_grndinsecure = false;
continue; /* No GRND_INSECURE; fallback to GRND_NONBLOCK. */
} else if (errno == EAGAIN && !have_grndinsecure)
break; /* Will block, but no GRND_INSECURE, so fallback to /dev/urandom. */
break; /* Unexpected, so just give up and fallback to /dev/urandom. */
}
fd = open("/dev/urandom", O_RDONLY|O_CLOEXEC|O_NOCTTY);
if (fd >= 0 && loop_read_exact(fd, p, n, false) == 0)
return;
/* This is a terrible fallback. Oh well. */
fallback_random_bytes(p, n);
}
int crypto_random_bytes(void *p, size_t n) {
static bool have_getrandom = true, seen_initialized = false;
_cleanup_close_ int fd = -1;
if (n == 0)
return 0;
for (;;) {
ssize_t l;
if (!have_getrandom)
break;
l = getrandom(p, n, 0);
if (l > 0) {
if ((size_t) l == n)
return 0; /* Done reading, success. */
p = (uint8_t *) p + l;
n -= l;
continue; /* Interrupted by a signal; keep going. */
} else if (l == 0)
return -EIO; /* Weird, should never happen. */
else if (ERRNO_IS_NOT_SUPPORTED(errno)) {
have_getrandom = false;
break; /* No syscall, so fallback to /dev/urandom. */
}
return -errno;
}
if (!seen_initialized) {
_cleanup_close_ int ready_fd = -1;
int r;
ready_fd = open("/dev/random", O_RDONLY|O_CLOEXEC|O_NOCTTY);
if (ready_fd < 0)
return -errno;
r = fd_wait_for_event(ready_fd, POLLIN, USEC_INFINITY);
if (r < 0)
return r;
seen_initialized = true;
}
fd = open("/dev/urandom", O_RDONLY|O_CLOEXEC|O_NOCTTY);
if (fd < 0)
return -errno;
return loop_read_exact(fd, p, n, false);
}
size_t random_pool_size(void) {
+2 -9
View File
@@ -5,15 +5,8 @@
#include <stddef.h>
#include <stdint.h>
typedef enum RandomFlags {
RANDOM_BLOCK = 1 << 0, /* Rather block than return crap randomness (only if the kernel supports that) */
} RandomFlags;
int genuine_random_bytes(void *p, size_t n, RandomFlags flags); /* returns "genuine" randomness, optionally filled up with pseudo random, if not enough is available */
void pseudo_random_bytes(void *p, size_t n); /* returns only pseudo-randommess (but possibly seeded from something better) */
void random_bytes(void *p, size_t n); /* returns genuine randomness if cheaply available, and pseudo randomness if not. */
void initialize_srand(void);
void random_bytes(void *p, size_t n); /* Returns random bytes suitable for most uses, but may be insecure sometimes. */
int crypto_random_bytes(void *p, size_t n); /* Returns secure random bytes after waiting for the RNG to initialize. */
static inline uint64_t random_u64(void) {
uint64_t u;
+1 -1
View File
@@ -83,7 +83,7 @@ int make_recovery_key(char **ret) {
if (!key)
return -ENOMEM;
r = genuine_random_bytes(key, RECOVERY_KEY_MODHEX_RAW_LENGTH, RANDOM_BLOCK);
r = crypto_random_bytes(key, RECOVERY_KEY_MODHEX_RAW_LENGTH);
if (r < 0)
return r;