summaryrefslogtreecommitdiff
path: root/src/basic/random-util.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/basic/random-util.c')
-rw-r--r--src/basic/random-util.c209
1 files changed, 160 insertions, 49 deletions
diff --git a/src/basic/random-util.c b/src/basic/random-util.c
index 91481559db..f7decf60b6 100644
--- a/src/basic/random-util.c
+++ b/src/basic/random-util.c
@@ -1,9 +1,12 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
+#if defined(__i386__) || defined(__x86_64__)
+#include <cpuid.h>
+#endif
+
#include <elf.h>
#include <errno.h>
#include <fcntl.h>
-#include <linux/random.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
@@ -26,67 +29,175 @@
#include "random-util.h"
#include "time-util.h"
-int acquire_random_bytes(void *p, size_t n, bool high_quality_required) {
- static int have_syscall = -1;
+#if HAS_FEATURE_MEMORY_SANITIZER
+#include <sanitizer/msan_interface.h>
+#endif
+
+int rdrand(unsigned long *ret) {
+
+#if defined(__i386__) || defined(__x86_64__)
+ static int have_rdrand = -1;
+ unsigned char err;
+
+ if (have_rdrand < 0) {
+ uint32_t eax, ebx, ecx, edx;
+
+ /* Check if RDRAND is supported by the CPU */
+ if (__get_cpuid(1, &eax, &ebx, &ecx, &edx) == 0) {
+ have_rdrand = false;
+ return -EOPNOTSUPP;
+ }
+
+ have_rdrand = !!(ecx & (1U << 30));
+ }
+
+ if (have_rdrand == 0)
+ return -EOPNOTSUPP;
+ asm volatile("rdrand %0;"
+ "setc %1"
+ : "=r" (*ret),
+ "=qm" (err));
+
+#if HAS_FEATURE_MEMORY_SANITIZER
+ __msan_unpoison(&err, sizeof(err));
+#endif
+
+ if (!err)
+ return -EAGAIN;
+
+ return 0;
+#else
+ return -EOPNOTSUPP;
+#endif
+}
+
+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;
+ bool got_some = false;
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 (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 (;;) {
+ unsigned long u;
+ size_t m;
+
+ if (rdrand(&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) {
- r = getrandom(p, n, GRND_NONBLOCK);
- if (r > 0) {
- have_syscall = true;
- if ((size_t) r == n)
- return 0;
- if (!high_quality_required) {
- /* Fill in the remaining bytes using pseudorandom values */
- pseudorandom_bytes((uint8_t*) p + r, n - r);
- return 0;
- }
- already_done = r;
- } 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;
-
- if (!high_quality_required)
- return -ENODATA;
- } else
- return -errno;
+ for (;;) {
+ r = getrandom(p, n, FLAGS_SET(flags, RANDOM_BLOCK) ? 0 : GRND_NONBLOCK);
+ if (r > 0) {
+ have_syscall = true;
+
+ if ((size_t) r == n)
+ return 0; /* Yay, success! */
+
+ assert((size_t) r < n);
+ p = (uint8_t*) p + r;
+ n -= r;
+
+ if (FLAGS_SET(flags, RANDOM_EXTEND_WITH_PSEUDO)) {
+ /* Fill in the remaining bytes using pseudo-random values */
+ pseudo_random_bytes(p, n);
+ 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;
+
+ /* 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 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 (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
+ 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) {
static bool srand_called = false;
unsigned x;
#if HAVE_SYS_AUXV_H
- void *auxv;
+ const void *auxv;
#endif
+ unsigned long k;
if (srand_called)
return;
@@ -96,7 +207,7 @@ void initialize_srand(void) {
* try to make use of that to seed the pseudo-random generator. It's
* better than nothing... */
- auxv = (void*) getauxval(AT_RANDOM);
+ auxv = (const void*) getauxval(AT_RANDOM);
if (auxv) {
assert_cc(sizeof(x) <= 16);
memcpy(&x, auxv, sizeof(x));
@@ -107,6 +218,9 @@ void initialize_srand(void) {
x ^= (unsigned) now(CLOCK_REALTIME);
x ^= (unsigned) gettid();
+ if (rdrand(&k) >= 0)
+ x ^= (unsigned) k;
+
srand(x);
srand_called = true;
}
@@ -119,7 +233,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();
@@ -142,13 +256,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, RANDOM_EXTEND_WITH_PSEUDO|RANDOM_DONT_DRAIN|RANDOM_ALLOW_RDRAND) >= 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);
}