From 3335dc2d7591fa4e5ae28efa9a2ab35fd5ba1951 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 7 Nov 2018 18:27:57 +0100 Subject: [PATCH 1/8] =?UTF-8?q?random-util:=20rename=20acquire=5Frandom=5F?= =?UTF-8?q?bytes()=20=E2=86=92=20genuine=5Frandom=5Fbytes()?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It's more descriptive, since we also have a function random_bytes() which sounds very similar. Also rename pseudorandom_bytes() to pseudo_random_bytes(). This way the two functions are nicely systematic, one returning genuine random bytes and the other pseudo random ones. --- src/basic/random-util.c | 17 +++++++---------- src/basic/random-util.h | 7 ++++--- src/firstboot/firstboot.c | 2 +- src/libsystemd/sd-id128/sd-id128.c | 2 +- src/test/test-random-util.c | 14 +++++++------- 5 files changed, 20 insertions(+), 22 deletions(-) diff --git a/src/basic/random-util.c b/src/basic/random-util.c index 071a41f186..5e4ed2da9d 100644 --- a/src/basic/random-util.c +++ b/src/basic/random-util.c @@ -65,7 +65,7 @@ int rdrand64(uint64_t *ret) { #endif } -int acquire_random_bytes(void *p, size_t n, bool high_quality_required) { +int genuine_random_bytes(void *p, size_t n, bool high_quality_required) { static int have_syscall = -1; _cleanup_close_ int fd = -1; @@ -88,7 +88,7 @@ int acquire_random_bytes(void *p, size_t n, bool high_quality_required) { return 0; if (!high_quality_required) { /* Fill in the remaining bytes using pseudorandom values */ - pseudorandom_bytes((uint8_t*) p + r, n - r); + pseudo_random_bytes((uint8_t*) p + r, n - r); return 0; } @@ -124,7 +124,7 @@ int acquire_random_bytes(void *p, size_t n, bool high_quality_required) { memcpy(p, &u, k); /* We only get 64bit out of RDRAND, the rest let's fill up with pseudo-random crap. */ - pseudorandom_bytes((uint8_t*) p + k, n - k); + pseudo_random_bytes((uint8_t*) p + k, n - k); return 0; } } else @@ -180,7 +180,7 @@ void initialize_srand(void) { # define RAND_STEP 1 #endif -void pseudorandom_bytes(void *p, size_t n) { +void pseudo_random_bytes(void *p, size_t n) { uint8_t *q; initialize_srand(); @@ -203,13 +203,10 @@ void pseudorandom_bytes(void *p, size_t n) { } void random_bytes(void *p, size_t n) { - int r; - r = acquire_random_bytes(p, n, false); - if (r >= 0) + if (genuine_random_bytes(p, n, false) >= 0) return; - /* If some idiot made /dev/urandom unavailable to us, or the - * kernel has no entropy, use a PRNG instead. */ - return pseudorandom_bytes(p, n); + /* 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); } diff --git a/src/basic/random-util.h b/src/basic/random-util.h index affcc9ac1d..0429ea5e52 100644 --- a/src/basic/random-util.h +++ b/src/basic/random-util.h @@ -5,9 +5,10 @@ #include #include -int acquire_random_bytes(void *p, size_t n, bool high_quality_required); -void pseudorandom_bytes(void *p, size_t n); -void random_bytes(void *p, size_t n); +int genuine_random_bytes(void *p, size_t n, bool high_quality_required); /* returns "genuine" randomness, optionally filled upwith 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); static inline uint64_t random_u64(void) { diff --git a/src/firstboot/firstboot.c b/src/firstboot/firstboot.c index c5deb66edf..96260fab29 100644 --- a/src/firstboot/firstboot.c +++ b/src/firstboot/firstboot.c @@ -647,7 +647,7 @@ static int process_root_password(void) { if (!arg_root_password) return 0; - r = acquire_random_bytes(raw, 16, true); + r = genuine_random_bytes(raw, 16, true); if (r < 0) return log_error_errno(r, "Failed to get salt: %m"); diff --git a/src/libsystemd/sd-id128/sd-id128.c b/src/libsystemd/sd-id128/sd-id128.c index b7cca832d8..bba40a0537 100644 --- a/src/libsystemd/sd-id128/sd-id128.c +++ b/src/libsystemd/sd-id128/sd-id128.c @@ -272,7 +272,7 @@ _public_ int sd_id128_randomize(sd_id128_t *ret) { assert_return(ret, -EINVAL); - r = acquire_random_bytes(&t, sizeof t, true); + r = genuine_random_bytes(&t, sizeof t, true); if (r < 0) return r; diff --git a/src/test/test-random-util.c b/src/test/test-random-util.c index 9652a0af05..82902b7e2d 100644 --- a/src/test/test-random-util.c +++ b/src/test/test-random-util.c @@ -5,14 +5,14 @@ #include "log.h" #include "tests.h" -static void test_acquire_random_bytes(bool high_quality_required) { +static void test_genuine_random_bytes(bool high_quality_required) { uint8_t buf[16] = {}; unsigned i; log_info("/* %s */", __func__); for (i = 1; i < sizeof buf; i++) { - assert_se(acquire_random_bytes(buf, i, high_quality_required) == 0); + assert_se(genuine_random_bytes(buf, i, high_quality_required) == 0); if (i + 1 < sizeof buf) assert_se(buf[i] == 0); @@ -20,14 +20,14 @@ static void test_acquire_random_bytes(bool high_quality_required) { } } -static void test_pseudorandom_bytes(void) { +static void test_pseudo_random_bytes(void) { uint8_t buf[16] = {}; unsigned i; log_info("/* %s */", __func__); for (i = 1; i < sizeof buf; i++) { - pseudorandom_bytes(buf, i); + pseudo_random_bytes(buf, i); if (i + 1 < sizeof buf) assert_se(buf[i] == 0); @@ -54,10 +54,10 @@ static void test_rdrand64(void) { int main(int argc, char **argv) { test_setup_logging(LOG_DEBUG); - test_acquire_random_bytes(false); - test_acquire_random_bytes(true); + test_genuine_random_bytes(false); + test_genuine_random_bytes(true); - test_pseudorandom_bytes(); + test_pseudo_random_bytes(); test_rdrand64(); From d68ccc08416ce0117c59a7ddac97d443f8239d12 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 7 Nov 2018 18:37:13 +0100 Subject: [PATCH 2/8] random-util: handle if getrandom() returns 0 This should normally not happen, but given that the man page suggests something about this in the context of interruption, let's handle this and propagate an I/O error. --- src/basic/random-util.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/basic/random-util.c b/src/basic/random-util.c index 5e4ed2da9d..3989bb9dc6 100644 --- a/src/basic/random-util.c +++ b/src/basic/random-util.c @@ -93,6 +93,9 @@ int genuine_random_bytes(void *p, size_t n, bool high_quality_required) { } already_done = r; + } else if (r == 0) { + have_syscall = true; + return -EIO; } else if (errno == ENOSYS) /* We lack the syscall, continue with reading from /dev/urandom. */ have_syscall = false; From afff8f16ae5f5a5dfaa1db226f755c8953dab382 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 7 Nov 2018 18:52:41 +0100 Subject: [PATCH 3/8] random-util: fix indentation --- src/basic/random-util.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/basic/random-util.c b/src/basic/random-util.c index 3989bb9dc6..b6d1cfd1ca 100644 --- a/src/basic/random-util.c +++ b/src/basic/random-util.c @@ -97,8 +97,8 @@ int genuine_random_bytes(void *p, size_t n, bool high_quality_required) { have_syscall = true; return -EIO; } else if (errno == ENOSYS) - /* We lack the syscall, continue with reading from /dev/urandom. */ - have_syscall = false; + /* We lack the syscall, continue with reading from /dev/urandom. */ + have_syscall = false; else if (errno == EAGAIN) { /* The kernel has no entropy whatsoever. Let's remember to * use the syscall the next time again though. From 94d457e8d936555b3b1ae25592d0624746994166 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 7 Nov 2018 18:40:26 +0100 Subject: [PATCH 4/8] random-util: change high_quality_required bool parameter into a flags parameter No change in behaviour, just some refactoring. --- src/basic/random-util.c | 18 ++++++++---------- src/basic/random-util.h | 10 +++++++--- src/firstboot/firstboot.c | 2 +- src/libsystemd/sd-id128/sd-id128.c | 2 +- src/test/test-random-util.c | 8 ++++---- 5 files changed, 21 insertions(+), 19 deletions(-) diff --git a/src/basic/random-util.c b/src/basic/random-util.c index b6d1cfd1ca..d7543d388a 100644 --- a/src/basic/random-util.c +++ b/src/basic/random-util.c @@ -65,19 +65,17 @@ int rdrand64(uint64_t *ret) { #endif } -int genuine_random_bytes(void *p, size_t n, bool high_quality_required) { +int genuine_random_bytes(void *p, size_t n, RandomFlags flags) { static int have_syscall = -1; _cleanup_close_ int fd = -1; size_t already_done = 0; int r; - /* Gathers some randomness from the kernel. This call will never block. If - * high_quality_required, it will always return some data from the kernel, - * regardless of whether the random pool is fully initialized or not. - * Otherwise, it will return success if at least some random bytes were - * successfully acquired, and an error if the kernel has no entropy whatsover - * for us. */ + /* Gathers some randomness from the kernel. This call will never block. If RANDOM_EXTEND_WITH_PSEUDO is unset, + * it will always return some data from the kernel, regardless of whether the random pool is fully initialized + * or not. Otherwise, it will return success if at least some random bytes were successfully acquired, and an + * error if the kernel has no entropy whatsover for us. */ /* Use the getrandom() syscall unless we know we don't have it. */ if (have_syscall != 0 && !HAS_FEATURE_MEMORY_SANITIZER) { @@ -86,7 +84,7 @@ int genuine_random_bytes(void *p, size_t n, bool high_quality_required) { have_syscall = true; if ((size_t) r == n) return 0; - if (!high_quality_required) { + if (FLAGS_SET(flags, RANDOM_EXTEND_WITH_PSEUDO)) { /* Fill in the remaining bytes using pseudorandom values */ pseudo_random_bytes((uint8_t*) p + r, n - r); return 0; @@ -110,7 +108,7 @@ int genuine_random_bytes(void *p, size_t n, bool high_quality_required) { * a best-effort basis. */ have_syscall = true; - if (!high_quality_required) { + if (FLAGS_SET(flags, RANDOM_EXTEND_WITH_PSEUDO)) { uint64_t u; size_t k; @@ -207,7 +205,7 @@ void pseudo_random_bytes(void *p, size_t n) { void random_bytes(void *p, size_t n) { - if (genuine_random_bytes(p, n, false) >= 0) + if (genuine_random_bytes(p, n, RANDOM_EXTEND_WITH_PSEUDO) >= 0) return; /* If for some reason some user made /dev/urandom unavailable to us, or the kernel has no entropy, use a PRNG instead. */ diff --git a/src/basic/random-util.h b/src/basic/random-util.h index 0429ea5e52..6328f661d2 100644 --- a/src/basic/random-util.h +++ b/src/basic/random-util.h @@ -5,9 +5,13 @@ #include #include -int genuine_random_bytes(void *p, size_t n, bool high_quality_required); /* returns "genuine" randomness, optionally filled upwith 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. */ +typedef enum RandomFlags { + RANDOM_EXTEND_WITH_PSEUDO = 1 << 0, /* If we can't get enough genuine randomness, but some, fill up the rest with pseudo-randomness */ +} RandomFlags; + +int genuine_random_bytes(void *p, size_t n, RandomFlags flags); /* returns "genuine" randomness, optionally filled upwith 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); diff --git a/src/firstboot/firstboot.c b/src/firstboot/firstboot.c index 96260fab29..ee267dcd7f 100644 --- a/src/firstboot/firstboot.c +++ b/src/firstboot/firstboot.c @@ -647,7 +647,7 @@ static int process_root_password(void) { if (!arg_root_password) return 0; - r = genuine_random_bytes(raw, 16, true); + r = genuine_random_bytes(raw, 16, 0); if (r < 0) return log_error_errno(r, "Failed to get salt: %m"); diff --git a/src/libsystemd/sd-id128/sd-id128.c b/src/libsystemd/sd-id128/sd-id128.c index bba40a0537..218070010d 100644 --- a/src/libsystemd/sd-id128/sd-id128.c +++ b/src/libsystemd/sd-id128/sd-id128.c @@ -272,7 +272,7 @@ _public_ int sd_id128_randomize(sd_id128_t *ret) { assert_return(ret, -EINVAL); - r = genuine_random_bytes(&t, sizeof t, true); + r = genuine_random_bytes(&t, sizeof t, 0); if (r < 0) return r; diff --git a/src/test/test-random-util.c b/src/test/test-random-util.c index 82902b7e2d..adb2588f00 100644 --- a/src/test/test-random-util.c +++ b/src/test/test-random-util.c @@ -5,14 +5,14 @@ #include "log.h" #include "tests.h" -static void test_genuine_random_bytes(bool high_quality_required) { +static void test_genuine_random_bytes(RandomFlags flags) { uint8_t buf[16] = {}; unsigned i; log_info("/* %s */", __func__); for (i = 1; i < sizeof buf; i++) { - assert_se(genuine_random_bytes(buf, i, high_quality_required) == 0); + assert_se(genuine_random_bytes(buf, i, flags) == 0); if (i + 1 < sizeof buf) assert_se(buf[i] == 0); @@ -54,8 +54,8 @@ static void test_rdrand64(void) { int main(int argc, char **argv) { test_setup_logging(LOG_DEBUG); - test_genuine_random_bytes(false); - test_genuine_random_bytes(true); + test_genuine_random_bytes(RANDOM_EXTEND_WITH_PSEUDO); + test_genuine_random_bytes(0); test_pseudo_random_bytes(); From 68534345b8af31e8df3d45cf21a832a42af52996 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 7 Nov 2018 19:04:04 +0100 Subject: [PATCH 5/8] random-util: optionally enable blocking getrandom() behaviour When generating the salt for the firstboot password logic, let's use getrandom() blocking mode, and insist in the very best entropy. --- src/basic/random-util.c | 122 +++++++++++++++++++++--------------- src/basic/random-util.h | 1 + src/firstboot/firstboot.c | 3 +- src/test/test-random-util.c | 1 + 4 files changed, 75 insertions(+), 52 deletions(-) diff --git a/src/basic/random-util.c b/src/basic/random-util.c index d7543d388a..1c54b07fbb 100644 --- a/src/basic/random-util.c +++ b/src/basic/random-util.c @@ -69,74 +69,94 @@ int genuine_random_bytes(void *p, size_t n, RandomFlags flags) { static int have_syscall = -1; _cleanup_close_ int fd = -1; - size_t already_done = 0; int r; - /* Gathers some randomness from the kernel. This call will never block. If RANDOM_EXTEND_WITH_PSEUDO is unset, - * it will always return some data from the kernel, regardless of whether the random pool is fully initialized - * or not. Otherwise, it will return success if at least some random bytes were successfully acquired, and an - * error if the kernel has no entropy whatsover for us. */ + /* Gathers some randomness from the kernel. This call won't block, unless the RANDOM_BLOCK flag is set. If + * RANDOM_EXTEND_WITH_PSEUDO is unset, it will always return some data from the kernel, regardless of whether + * the random pool is fully initialized or not. Otherwise, it will return success if at least some random + * bytes were successfully acquired, and an error if the kernel has no entropy whatsover for us. */ /* Use the getrandom() syscall unless we know we don't have it. */ if (have_syscall != 0 && !HAS_FEATURE_MEMORY_SANITIZER) { - r = getrandom(p, n, GRND_NONBLOCK); - if (r > 0) { - have_syscall = true; - if ((size_t) r == n) - return 0; - if (FLAGS_SET(flags, RANDOM_EXTEND_WITH_PSEUDO)) { - /* Fill in the remaining bytes using pseudorandom values */ - pseudo_random_bytes((uint8_t*) p + r, n - r); - return 0; - } - already_done = r; - } else if (r == 0) { - have_syscall = true; - return -EIO; - } else if (errno == ENOSYS) - /* We lack the syscall, continue with reading from /dev/urandom. */ - have_syscall = false; - else if (errno == EAGAIN) { - /* The kernel has no entropy whatsoever. Let's remember to - * use the syscall the next time again though. - * - * If high_quality_required is false, return an error so that - * random_bytes() can produce some pseudorandom - * bytes. Otherwise, fall back to /dev/urandom, which we know - * is empty, but the kernel will produce some bytes for us on - * a best-effort basis. */ - have_syscall = true; + for (;;) { + r = getrandom(p, n, FLAGS_SET(flags, RANDOM_BLOCK) ? 0 : GRND_NONBLOCK); + if (r > 0) { + have_syscall = true; - if (FLAGS_SET(flags, RANDOM_EXTEND_WITH_PSEUDO)) { - uint64_t u; - size_t k; + if ((size_t) r == n) + return 0; /* Yay, success! */ - /* Try x86-64' RDRAND intrinsic if we have it. We only use it if high quality - * randomness is not required, as we don't trust it (who does?). Note that we only do a - * single iteration of RDRAND here, even though the Intel docs suggest calling this in - * a tight loop of 10 invocatins or so. That's because we don't really care about the - * quality here. */ + assert((size_t) r < n); + p = (uint8_t*) p + r; + n -= r; - if (rdrand64(&u) < 0) - return -ENODATA; + if (FLAGS_SET(flags, RANDOM_EXTEND_WITH_PSEUDO)) { + /* Fill in the remaining bytes using pseudo-random values */ + pseudo_random_bytes(p, n); + return 0; + } - k = MIN(n, sizeof(u)); - memcpy(p, &u, k); + /* Hmm, we didn't get enough good data but the caller insists on good data? Then try again */ + if (FLAGS_SET(flags, RANDOM_BLOCK)) + continue; - /* We only get 64bit out of RDRAND, the rest let's fill up with pseudo-random crap. */ - pseudo_random_bytes((uint8_t*) p + k, n - k); - return 0; - } - } else - return -errno; + /* Fill in the rest with /dev/urandom */ + break; + + } else if (r == 0) { + have_syscall = true; + return -EIO; + + } else if (errno == ENOSYS) { + /* We lack the syscall, continue with reading from /dev/urandom. */ + have_syscall = false; + break; + + } else if (errno == EAGAIN) { + /* The kernel has no entropy whatsoever. Let's remember to + * use the syscall the next time again though. + * + * If high_quality_required is false, return an error so that + * random_bytes() can produce some pseudorandom + * bytes. Otherwise, fall back to /dev/urandom, which we know + * is empty, but the kernel will produce some bytes for us on + * a best-effort basis. */ + have_syscall = true; + + if (FLAGS_SET(flags, RANDOM_EXTEND_WITH_PSEUDO)) { + uint64_t u; + size_t k; + + /* Try x86-64' RDRAND intrinsic if we have it. We only use it if high quality + * randomness is not required, as we don't trust it (who does?). Note that we only do a + * single iteration of RDRAND here, even though the Intel docs suggest calling this in + * a tight loop of 10 invocatins or so. That's because we don't really care about the + * quality here. */ + + if (rdrand64(&u) < 0) + return -ENODATA; + + k = MIN(n, sizeof(u)); + memcpy(p, &u, k); + + /* We only get 64bit out of RDRAND, the rest let's fill up with pseudo-random crap. */ + pseudo_random_bytes((uint8_t*) p + k, n - k); + return 0; + } + + /* Use /dev/urandom instead */ + break; + } 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, (uint8_t*) p + already_done, n - already_done, true); + return loop_read_exact(fd, p, n, true); } void initialize_srand(void) { diff --git a/src/basic/random-util.h b/src/basic/random-util.h index 6328f661d2..0813314a05 100644 --- a/src/basic/random-util.h +++ b/src/basic/random-util.h @@ -7,6 +7,7 @@ typedef enum RandomFlags { RANDOM_EXTEND_WITH_PSEUDO = 1 << 0, /* If we can't get enough genuine randomness, but some, fill up the rest with pseudo-randomness */ + RANDOM_BLOCK = 1 << 1, /* 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 upwith pseudo random, if not enough is available */ diff --git a/src/firstboot/firstboot.c b/src/firstboot/firstboot.c index ee267dcd7f..d8b5893f76 100644 --- a/src/firstboot/firstboot.c +++ b/src/firstboot/firstboot.c @@ -647,7 +647,8 @@ static int process_root_password(void) { if (!arg_root_password) return 0; - r = genuine_random_bytes(raw, 16, 0); + /* Insist on the best randomness by setting RANDOM_BLOCK, this is about keeping passwords secret after all. */ + r = genuine_random_bytes(raw, 16, RANDOM_BLOCK); if (r < 0) return log_error_errno(r, "Failed to get salt: %m"); diff --git a/src/test/test-random-util.c b/src/test/test-random-util.c index adb2588f00..79c65a148c 100644 --- a/src/test/test-random-util.c +++ b/src/test/test-random-util.c @@ -56,6 +56,7 @@ int main(int argc, char **argv) { test_genuine_random_bytes(RANDOM_EXTEND_WITH_PSEUDO); test_genuine_random_bytes(0); + test_genuine_random_bytes(RANDOM_BLOCK); test_pseudo_random_bytes(); From 776cf7461fa54445a40df17c40cfd024b6f09578 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 7 Nov 2018 19:14:47 +0100 Subject: [PATCH 6/8] random-util: if zero random bytes are requested we can always fulfill the request --- src/basic/random-util.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/basic/random-util.c b/src/basic/random-util.c index 1c54b07fbb..922b5a57b0 100644 --- a/src/basic/random-util.c +++ b/src/basic/random-util.c @@ -76,6 +76,9 @@ int genuine_random_bytes(void *p, size_t n, RandomFlags flags) { * the random pool is fully initialized or not. Otherwise, it will return success if at least some random * bytes were successfully acquired, and an error if the kernel has no entropy whatsover for us. */ + 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) { From 6fb6f13896b13e8a5afee30ebec110dd1c8f1b29 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 7 Nov 2018 19:31:39 +0100 Subject: [PATCH 7/8] random-util: introduce RANDOM_DONT_DRAIN Originally, the high_quality_required boolean argument controlled two things: whether to extend any random data we successfully read with pseudo-random data, and whether to return -ENODATA if we couldn't read any data at all. The boolean got replaced by RANDOM_EXTEND_WITH_PSEUDO, but this name doesn't really cover the second part nicely. Moreover hiding both changes of behaviour under a single flag is confusing. Hence, let's split this part off under a new flag, and use it from random_bytes(). --- src/basic/random-util.c | 23 ++++++++++++----------- src/basic/random-util.h | 1 + 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/basic/random-util.c b/src/basic/random-util.c index 922b5a57b0..6b08f72fc2 100644 --- a/src/basic/random-util.c +++ b/src/basic/random-util.c @@ -72,9 +72,9 @@ int genuine_random_bytes(void *p, size_t n, RandomFlags flags) { int r; /* Gathers some randomness from the kernel. This call won't block, unless the RANDOM_BLOCK flag is set. If - * RANDOM_EXTEND_WITH_PSEUDO is unset, it will always return some data from the kernel, regardless of whether - * the random pool is fully initialized or not. Otherwise, it will return success if at least some random - * bytes were successfully acquired, and an error if the kernel has no entropy whatsover for us. */ + * RANDOM_DONT_DRAIN is set, an error is returned if the random pool is not initialized. Otherwise it will + * always return some data from the kernel, regardless of whether the random pool is fully initialized or + * not. */ if (n == 0) return 0; @@ -117,16 +117,17 @@ int genuine_random_bytes(void *p, size_t n, RandomFlags flags) { break; } else if (errno == EAGAIN) { - /* The kernel has no entropy whatsoever. Let's remember to - * use the syscall the next time again though. + /* The kernel has no entropy whatsoever. Let's remember to use the syscall the next + * time again though. * - * If high_quality_required is false, return an error so that - * random_bytes() can produce some pseudorandom - * bytes. Otherwise, fall back to /dev/urandom, which we know - * is empty, but the kernel will produce some bytes for us on - * a best-effort basis. */ + * If RANDOM_DONT_DRAIN is set, return an error so that random_bytes() can produce some + * pseudo-random bytes instead. Otherwise, fall back to /dev/urandom, which we know is empty, + * but the kernel will produce some bytes for us on a best-effort basis. */ have_syscall = true; + if (FLAGS_SET(flags, RANDOM_DONT_DRAIN)) + return -ENODATA; + if (FLAGS_SET(flags, RANDOM_EXTEND_WITH_PSEUDO)) { uint64_t u; size_t k; @@ -228,7 +229,7 @@ void pseudo_random_bytes(void *p, size_t n) { void random_bytes(void *p, size_t n) { - if (genuine_random_bytes(p, n, RANDOM_EXTEND_WITH_PSEUDO) >= 0) + if (genuine_random_bytes(p, n, RANDOM_EXTEND_WITH_PSEUDO|RANDOM_DONT_DRAIN) >= 0) return; /* If for some reason some user made /dev/urandom unavailable to us, or the kernel has no entropy, use a PRNG instead. */ diff --git a/src/basic/random-util.h b/src/basic/random-util.h index 0813314a05..487f20e0ab 100644 --- a/src/basic/random-util.h +++ b/src/basic/random-util.h @@ -8,6 +8,7 @@ typedef enum RandomFlags { RANDOM_EXTEND_WITH_PSEUDO = 1 << 0, /* If we can't get enough genuine randomness, but some, fill up the rest with pseudo-randomness */ RANDOM_BLOCK = 1 << 1, /* Rather block than return crap randomness (only if the kernel supports that) */ + RANDOM_DONT_DRAIN = 1 << 2, /* If we can't get any randomness at all, return early with -EAGAIN */ } RandomFlags; int genuine_random_bytes(void *p, size_t n, RandomFlags flags); /* returns "genuine" randomness, optionally filled upwith pseudo random, if not enough is available */ From cc83d5197ca08d68fa78167b6a64e9f28da3cc96 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 7 Nov 2018 19:46:18 +0100 Subject: [PATCH 8/8] random-util: optionally allow randomness to be generated via RDRAND We only use this when we don't require the best randomness. The primary usecase for this is UUID generation, as this means we don't drain randomness from the kernel pool for them. Since UUIDs are usually not secrets RDRAND should be goot enough for them to avoid real-life collisions. --- src/basic/random-util.c | 75 +++++++++++++++++++----------- src/basic/random-util.h | 1 + src/libsystemd/sd-id128/sd-id128.c | 4 +- src/test/test-random-util.c | 1 + 4 files changed, 53 insertions(+), 28 deletions(-) diff --git a/src/basic/random-util.c b/src/basic/random-util.c index 6b08f72fc2..8eb6acfbbf 100644 --- a/src/basic/random-util.c +++ b/src/basic/random-util.c @@ -67,18 +67,52 @@ int rdrand64(uint64_t *ret) { int genuine_random_bytes(void *p, size_t n, RandomFlags flags) { static int have_syscall = -1; - _cleanup_close_ int fd = -1; + bool got_some = false; int r; - /* Gathers some randomness from the kernel. This call won't block, unless the RANDOM_BLOCK flag is set. If - * RANDOM_DONT_DRAIN is set, an error is returned if the random pool is not initialized. Otherwise it will - * always return some data from the kernel, regardless of whether the random pool is fully initialized or - * not. */ + /* Gathers some randomness from the kernel (or the CPU if the RANDOM_ALLOW_RDRAND flag is set). This call won't + * block, unless the RANDOM_BLOCK flag is set. If RANDOM_DONT_DRAIN is set, an error is returned if the random + * pool is not initialized. Otherwise it will always return some data from the kernel, regardless of whether + * the random pool is fully initialized or not. */ if (n == 0) return 0; + if (FLAGS_SET(flags, RANDOM_ALLOW_RDRAND)) + /* Try x86-64' RDRAND intrinsic if we have it. We only use it if high quality randomness is not + * required, as we don't trust it (who does?). Note that we only do a single iteration of RDRAND here, + * even though the Intel docs suggest calling this in a tight loop of 10 invocations or so. That's + * because we don't really care about the quality here. We generally prefer using RDRAND if the caller + * allows us too, since this way we won't drain the kernel randomness pool if we don't need it, as the + * pool's entropy is scarce. */ + for (;;) { + uint64_t u; + size_t m; + + if (rdrand64(&u) < 0) { + if (got_some && FLAGS_SET(flags, RANDOM_EXTEND_WITH_PSEUDO)) { + /* Fill in the remaining bytes using pseudo-random values */ + pseudo_random_bytes(p, n); + return 0; + } + + /* OK, this didn't work, let's go to getrandom() + /dev/urandom instead */ + break; + } + + m = MIN(sizeof(u), n); + memcpy(p, &u, m); + + p = (uint8_t*) p + m; + n -= m; + + if (n == 0) + return 0; /* Yay, success! */ + + got_some = true; + } + /* Use the getrandom() syscall unless we know we don't have it. */ if (have_syscall != 0 && !HAS_FEATURE_MEMORY_SANITIZER) { @@ -100,6 +134,8 @@ int genuine_random_bytes(void *p, size_t n, RandomFlags flags) { return 0; } + got_some = true; + /* Hmm, we didn't get enough good data but the caller insists on good data? Then try again */ if (FLAGS_SET(flags, RANDOM_BLOCK)) continue; @@ -125,30 +161,15 @@ int genuine_random_bytes(void *p, size_t n, RandomFlags flags) { * but the kernel will produce some bytes for us on a best-effort basis. */ have_syscall = true; - if (FLAGS_SET(flags, RANDOM_DONT_DRAIN)) - return -ENODATA; - - if (FLAGS_SET(flags, RANDOM_EXTEND_WITH_PSEUDO)) { - uint64_t u; - size_t k; - - /* Try x86-64' RDRAND intrinsic if we have it. We only use it if high quality - * randomness is not required, as we don't trust it (who does?). Note that we only do a - * single iteration of RDRAND here, even though the Intel docs suggest calling this in - * a tight loop of 10 invocatins or so. That's because we don't really care about the - * quality here. */ - - if (rdrand64(&u) < 0) - return -ENODATA; - - k = MIN(n, sizeof(u)); - memcpy(p, &u, k); - - /* We only get 64bit out of RDRAND, the rest let's fill up with pseudo-random crap. */ - pseudo_random_bytes((uint8_t*) p + k, n - k); + if (got_some && FLAGS_SET(flags, RANDOM_EXTEND_WITH_PSEUDO)) { + /* Fill in the remaining bytes using pseudorandom values */ + pseudo_random_bytes(p, n); return 0; } + if (FLAGS_SET(flags, RANDOM_DONT_DRAIN)) + return -ENODATA; + /* Use /dev/urandom instead */ break; } else @@ -229,7 +250,7 @@ void pseudo_random_bytes(void *p, size_t n) { void random_bytes(void *p, size_t n) { - if (genuine_random_bytes(p, n, RANDOM_EXTEND_WITH_PSEUDO|RANDOM_DONT_DRAIN) >= 0) + if (genuine_random_bytes(p, n, RANDOM_EXTEND_WITH_PSEUDO|RANDOM_DONT_DRAIN|RANDOM_ALLOW_RDRAND) >= 0) return; /* If for some reason some user made /dev/urandom unavailable to us, or the kernel has no entropy, use a PRNG instead. */ diff --git a/src/basic/random-util.h b/src/basic/random-util.h index 487f20e0ab..7a960cf9e7 100644 --- a/src/basic/random-util.h +++ b/src/basic/random-util.h @@ -9,6 +9,7 @@ typedef enum RandomFlags { RANDOM_EXTEND_WITH_PSEUDO = 1 << 0, /* If we can't get enough genuine randomness, but some, fill up the rest with pseudo-randomness */ RANDOM_BLOCK = 1 << 1, /* Rather block than return crap randomness (only if the kernel supports that) */ RANDOM_DONT_DRAIN = 1 << 2, /* If we can't get any randomness at all, return early with -EAGAIN */ + RANDOM_ALLOW_RDRAND = 1 << 3, /* Allow usage of the CPU RNG */ } RandomFlags; int genuine_random_bytes(void *p, size_t n, RandomFlags flags); /* returns "genuine" randomness, optionally filled upwith pseudo random, if not enough is available */ diff --git a/src/libsystemd/sd-id128/sd-id128.c b/src/libsystemd/sd-id128/sd-id128.c index 218070010d..3593a71c02 100644 --- a/src/libsystemd/sd-id128/sd-id128.c +++ b/src/libsystemd/sd-id128/sd-id128.c @@ -272,7 +272,9 @@ _public_ int sd_id128_randomize(sd_id128_t *ret) { assert_return(ret, -EINVAL); - r = genuine_random_bytes(&t, sizeof t, 0); + /* We allow usage if x86-64 RDRAND here. It might not be trusted enough for keeping secrets, but it should be + * fine for UUIDS. */ + r = genuine_random_bytes(&t, sizeof t, RANDOM_ALLOW_RDRAND); if (r < 0) return r; diff --git a/src/test/test-random-util.c b/src/test/test-random-util.c index 79c65a148c..a59c6374b2 100644 --- a/src/test/test-random-util.c +++ b/src/test/test-random-util.c @@ -57,6 +57,7 @@ int main(int argc, char **argv) { test_genuine_random_bytes(RANDOM_EXTEND_WITH_PSEUDO); test_genuine_random_bytes(0); test_genuine_random_bytes(RANDOM_BLOCK); + test_genuine_random_bytes(RANDOM_ALLOW_RDRAND); test_pseudo_random_bytes();