1
0
mirror of https://github.com/systemd/systemd.git synced 2025-03-31 14:50:15 +03:00

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-24 15:09:20 +02:00 committed by Zbigniew Jędrzejewski-Szmek
parent fada4bdcaa
commit 87cb1ab676
20 changed files with 149 additions and 211 deletions

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) {

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;

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;

View File

@ -1805,7 +1805,7 @@ static int install_random_seed(const char *esp) {
if (!buffer)
return log_oom();
r = genuine_random_bytes(buffer, sz, RANDOM_BLOCK);
r = crypto_random_bytes(buffer, sz);
if (r < 0)
return log_error_errno(r, "Failed to acquire random seed: %m");
@ -1892,7 +1892,7 @@ static int install_random_seed(const char *esp) {
log_debug("Existing system token size (%zu) does not match our expectations (%zu), replacing.", token_size, sz);
}
r = genuine_random_bytes(buffer, sz, RANDOM_BLOCK);
r = crypto_random_bytes(buffer, sz);
if (r < 0)
return log_error_errno(r, "Failed to acquire random seed: %m");

View File

@ -50,7 +50,7 @@ int enroll_pkcs11(
if (!decrypted_key)
return log_oom();
r = genuine_random_bytes(decrypted_key, decrypted_key_size, RANDOM_BLOCK);
r = crypto_random_bytes(decrypted_key, decrypted_key_size);
if (r < 0)
return log_error_errno(r, "Failed to generate random key: %m");

View File

@ -184,7 +184,7 @@ int identity_add_pkcs11_key_data(JsonVariant **v, const char *uri) {
if (!decrypted_key)
return log_oom();
r = genuine_random_bytes(decrypted_key, decrypted_key_size, RANDOM_BLOCK);
r = crypto_random_bytes(decrypted_key, decrypted_key_size);
if (r < 0)
return log_error_errno(r, "Failed to generate random key: %m");

View File

@ -409,7 +409,7 @@ static int fscrypt_slot_set(
const EVP_CIPHER *cc;
size_t encrypted_size;
r = genuine_random_bytes(salt, sizeof(salt), RANDOM_BLOCK);
r = crypto_random_bytes(salt, sizeof(salt));
if (r < 0)
return log_error_errno(r, "Failed to generate salt: %m");
@ -540,7 +540,7 @@ int home_create_fscrypt(
if (!volume_key)
return log_oom();
r = genuine_random_bytes(volume_key, volume_key_size, RANDOM_BLOCK);
r = crypto_random_bytes(volume_key, volume_key_size);
if (r < 0)
return log_error_errno(r, "Failed to acquire volume key: %m");

View File

@ -949,7 +949,7 @@ static int format_luks_token_text(
if (!iv)
return log_oom();
r = genuine_random_bytes(iv, iv_size, RANDOM_BLOCK);
r = crypto_random_bytes(iv, iv_size);
if (r < 0)
return log_error_errno(r, "Failed to generate IV: %m");
}
@ -1738,7 +1738,7 @@ static int luks_format(
if (!volume_key)
return log_oom();
r = genuine_random_bytes(volume_key, volume_key_size, RANDOM_BLOCK);
r = crypto_random_bytes(volume_key, volume_key_size);
if (r < 0)
return log_error_errno(r, "Failed to generate volume key: %m");

View File

@ -1909,7 +1909,7 @@ static int setup_keys(void) {
state = alloca_safe(state_size);
log_info("Generating seed...");
r = genuine_random_bytes(seed, seed_size, RANDOM_BLOCK);
r = crypto_random_bytes(seed, seed_size);
if (r < 0)
return log_error_errno(r, "Failed to acquire random seed: %m");

View File

@ -272,13 +272,10 @@ _public_ int sd_id128_get_invocation(sd_id128_t *ret) {
_public_ int sd_id128_randomize(sd_id128_t *ret) {
sd_id128_t t;
int r;
assert_return(ret, -EINVAL);
r = genuine_random_bytes(&t, sizeof(t), 0);
if (r < 0)
return r;
random_bytes(&t, sizeof(t));
/* Turn this into a valid v4 UUID, to be nice. Note that we
* only guarantee this for newly generated UUIDs, not for

View File

@ -2594,7 +2594,7 @@ static int partition_encrypt(
if (!volume_key)
return log_oom();
r = genuine_random_bytes(volume_key, volume_key_size, RANDOM_BLOCK);
r = crypto_random_bytes(volume_key, volume_key_size);
if (r < 0)
return log_error_errno(r, "Failed to generate volume key: %m");

View File

@ -158,7 +158,7 @@ static int make_credential_host_secret(
.machine_id = machine_id,
};
r = genuine_random_bytes(buf.data, sizeof(buf.data), RANDOM_BLOCK);
r = crypto_random_bytes(buf.data, sizeof(buf.data));
if (r < 0)
goto finish;
@ -642,7 +642,7 @@ int encrypt_credential_and_warn(
if (!iv)
return log_oom();
r = genuine_random_bytes(iv, ivsz, RANDOM_BLOCK);
r = crypto_random_bytes(iv, ivsz);
if (r < 0)
return log_error_errno(r, "Failed to acquired randomized IV: %m");
}

View File

@ -76,7 +76,7 @@ int make_salt(char **ret) {
log_debug("Generating fallback salt for hash prefix: $6$");
/* Insist on the best randomness by setting RANDOM_BLOCK, this is about keeping passwords secret after all. */
r = genuine_random_bytes(raw, sizeof(raw), RANDOM_BLOCK);
r = crypto_random_bytes(raw, sizeof(raw));
if (r < 0)
return r;

View File

@ -596,7 +596,7 @@ int fido2_generate_hmac_hash(
if (!salt)
return log_oom();
r = genuine_random_bytes(salt, FIDO2_SALT_SIZE, RANDOM_BLOCK);
r = crypto_random_bytes(salt, FIDO2_SALT_SIZE);
if (r < 0)
return log_error_errno(r, "Failed to generate salt: %m");

View File

@ -930,7 +930,7 @@ int tpm2_seal(
log_debug("Generating secret key data.");
r = genuine_random_bytes(hmac_sensitive.sensitive.data.buffer, hmac_sensitive.sensitive.data.size, RANDOM_BLOCK);
r = crypto_random_bytes(hmac_sensitive.sensitive.data.buffer, hmac_sensitive.sensitive.data.size);
if (r < 0) {
log_error_errno(r, "Failed to generate secret key: %m");
goto finish;

View File

@ -145,7 +145,7 @@ TEST(auto_erase_memory) {
* end of the allocation, since malloc() enforces alignment */
assert_se(p2 = new(uint8_t, 4703));
assert_se(genuine_random_bytes(p1, 4703, RANDOM_BLOCK) == 0);
assert_se(crypto_random_bytes(p1, 4703) == 0);
/* before we exit the scope, do something with this data, so that the compiler won't optimize this away */
memcpy(p2, p1, 4703);

View File

@ -25,7 +25,7 @@ static void test_v6(FirewallContext *ctx) {
assert_se(in_addr_from_string(AF_INET6, "1c3::c01d", &u2) >= 0);
prefixlen = random_u64_range(128 + 1 - 8) + 8;
pseudo_random_bytes(&u3, sizeof(u3));
random_bytes(&u3, sizeof(u3));
assert_se(fw_add_masquerade(&ctx, true, AF_INET6, &u1, 128) >= 0);
assert_se(fw_add_masquerade(&ctx, false, AF_INET6, &u1, 128) >= 0);

View File

@ -9,13 +9,11 @@
#include "terminal-util.h"
#include "tests.h"
static void test_genuine_random_bytes_one(RandomFlags flags) {
TEST(random_bytes) {
uint8_t buf[16] = {};
log_info("/* %s(%d) */", __func__, flags);
for (size_t i = 1; i < sizeof buf; i++) {
assert_se(genuine_random_bytes(buf, i, flags) == 0);
random_bytes(buf, i);
if (i + 1 < sizeof buf)
assert_se(buf[i] == 0);
@ -23,16 +21,11 @@ static void test_genuine_random_bytes_one(RandomFlags flags) {
}
}
TEST(genuine_random_bytes) {
test_genuine_random_bytes_one(0);
test_genuine_random_bytes_one(RANDOM_BLOCK);
}
TEST(pseudo_random_bytes) {
TEST(crypto_random_bytes) {
uint8_t buf[16] = {};
for (size_t i = 1; i < sizeof buf; i++) {
pseudo_random_bytes(buf, i);
assert_se(crypto_random_bytes(buf, i) == 0);
if (i + 1 < sizeof buf)
assert_se(buf[i] == 0);

View File

@ -623,10 +623,7 @@ static int link_generate_new_hw_addr(Link *link, struct hw_addr_data *ret) {
/* We require genuine randomness here, since we want to make sure we won't collide with other
* systems booting up at the very same time. */
for (;;) {
r = genuine_random_bytes(p, len, 0);
if (r < 0)
return log_link_warning_errno(link, r, "Failed to acquire random data to generate MAC address: %m");
random_bytes(p, len);
if (hw_addr_is_valid(link, &hw_addr))
break;
}

View File

@ -789,7 +789,6 @@ int scsi_get_serial(struct scsi_id_device *dev_scsi, const char *devname,
int retval;
memzero(dev_scsi->serial, len);
initialize_srand();
for (cnt = 20; cnt > 0; cnt--) {
struct timespec duration;
@ -797,7 +796,7 @@ int scsi_get_serial(struct scsi_id_device *dev_scsi, const char *devname,
if (fd >= 0 || errno != EBUSY)
break;
duration.tv_sec = 0;
duration.tv_nsec = (200 * 1000 * 1000) + (rand() % 100 * 1000 * 1000);
duration.tv_nsec = (200 * 1000 * 1000) + (random_u32() % 100 * 1000 * 1000);
nanosleep(&duration, NULL);
}
if (fd < 0)