Opportunistically read entropy from the OS in FIPS mode.
Even if RDRAND works, still mix in /dev/urandom or
getrandom(GRND_NONBLOCK) in the likely case that the entropy pool has
been initialized.
Change-Id: Ia61fc6eb07e90ae725a1781311c0ecc2fdabca87
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/37664
Commit-Queue: David Benjamin <davidben@google.com>
Reviewed-by: Adam Langley <agl@google.com>
diff --git a/crypto/fipsmodule/rand/internal.h b/crypto/fipsmodule/rand/internal.h
index ad75823..c7ed74d 100644
--- a/crypto/fipsmodule/rand/internal.h
+++ b/crypto/fipsmodule/rand/internal.h
@@ -26,6 +26,11 @@
#endif
+#if !defined(OPENSSL_WINDOWS) && !defined(OPENSSL_FUCHSIA) && \
+ !defined(BORINGSSL_UNSAFE_DETERMINISTIC_MODE) && !defined(OPENSSL_TRUSTY)
+#define OPENSSL_URANDOM
+#endif
+
// RAND_bytes_with_additional_data samples from the RNG after mixing 32 bytes
// from |user_additional_data| in.
void RAND_bytes_with_additional_data(uint8_t *out, size_t out_len,
@@ -35,6 +40,14 @@
// system.
void CRYPTO_sysrand(uint8_t *buf, size_t len);
+#if defined(OPENSSL_URANDOM) && defined(BORINGSSL_FIPS)
+// CRYPTO_sysrand_if_available fills |len| bytes at |buf| with entropy from the
+// operating system, if the entropy pool is initialized. If it is uninitialized,
+// it will not block and will instead fill |buf| with all zeros or early
+// /dev/urandom output.
+void CRYPTO_sysrand_if_available(uint8_t *buf, size_t len);
+#endif
+
// rand_fork_unsafe_buffering_enabled returns whether fork-unsafe buffering has
// been enabled via |RAND_enable_fork_unsafe_buffering|.
int rand_fork_unsafe_buffering_enabled(void);
diff --git a/crypto/fipsmodule/rand/rand.c b/crypto/fipsmodule/rand/rand.c
index a8ef458..60e92c5 100644
--- a/crypto/fipsmodule/rand/rand.c
+++ b/crypto/fipsmodule/rand/rand.c
@@ -179,7 +179,8 @@
#define FIPS_OVERREAD 10
uint8_t entropy[CTR_DRBG_ENTROPY_LEN * FIPS_OVERREAD];
- if (!hwrand(entropy, sizeof(entropy))) {
+ int used_hwrand = hwrand(entropy, sizeof(entropy));
+ if (!used_hwrand) {
CRYPTO_sysrand(entropy, sizeof(entropy));
}
@@ -210,6 +211,17 @@
seed[j] ^= entropy[CTR_DRBG_ENTROPY_LEN * i + j];
}
}
+
+#if defined(OPENSSL_URANDOM)
+ // If we used RDRAND, also opportunistically read from the system. This avoids
+ // solely relying on the hardware once the entropy pool has been initialized.
+ if (used_hwrand) {
+ CRYPTO_sysrand_if_available(entropy, CTR_DRBG_ENTROPY_LEN);
+ for (size_t i = 0; i < CTR_DRBG_ENTROPY_LEN; i++) {
+ seed[i] ^= entropy[i];
+ }
+ }
+#endif
}
#else
diff --git a/crypto/fipsmodule/rand/urandom.c b/crypto/fipsmodule/rand/urandom.c
index f63857f..23413ff 100644
--- a/crypto/fipsmodule/rand/urandom.c
+++ b/crypto/fipsmodule/rand/urandom.c
@@ -18,8 +18,7 @@
#include <openssl/rand.h>
-#if !defined(OPENSSL_WINDOWS) && !defined(OPENSSL_FUCHSIA) && \
- !defined(BORINGSSL_UNSAFE_DETERMINISTIC_MODE) && !defined(OPENSSL_TRUSTY)
+#if defined(OPENSSL_URANDOM)
#include <assert.h>
#include <errno.h>
@@ -133,6 +132,12 @@
// urandom_fd is a file descriptor to /dev/urandom. It's protected by |once|.
DEFINE_BSS_GET(int, urandom_fd)
+#if defined(USE_NR_getrandom)
+// getrandom_ready is one if |getrandom| had been initialized by the time
+// |init_once| was called and zero otherwise.
+DEFINE_BSS_GET(int, getrandom_ready)
+#endif
+
DEFINE_STATIC_ONCE(rand_once)
// init_once initializes the state of this module to values previously
@@ -145,42 +150,29 @@
CRYPTO_STATIC_MUTEX_unlock_read(rand_lock_bss_get());
#if defined(USE_NR_getrandom)
+ int have_getrandom;
uint8_t dummy;
ssize_t getrandom_ret =
boringssl_getrandom(&dummy, sizeof(dummy), GRND_NONBLOCK);
-
- if (getrandom_ret == -1 && errno == EAGAIN) {
- // Attempt to get the path of the current process to aid in debugging when
- // something blocks.
- const char *current_process = "<unknown>";
-#if defined(OPENSSL_HAS_GETAUXVAL)
- const unsigned long getauxval_ret = getauxval(AT_EXECFN);
- if (getauxval_ret != 0) {
- current_process = (const char *)getauxval_ret;
- }
-#endif
-
- fprintf(stderr,
- "%s: getrandom indicates that the entropy pool has not been "
- "initialized. Rather than continue with poor entropy, this process "
- "will block until entropy is available.\n",
- current_process);
-
- getrandom_ret =
- boringssl_getrandom(&dummy, sizeof(dummy), 0 /* no flags */);
- }
-
if (getrandom_ret == 1) {
- *urandom_fd_bss_get() = kHaveGetrandom;
- return;
- }
-
- // Ignore ENOSYS and fallthrough to using /dev/urandom, below. Otherwise it's
- // a fatal error.
- if (getrandom_ret != -1 || errno != ENOSYS) {
+ *getrandom_ready_bss_get() = 1;
+ have_getrandom = 1;
+ } else if (getrandom_ret == -1 && errno == EAGAIN) {
+ // We have getrandom, but the entropy pool has not been initialized yet.
+ have_getrandom = 1;
+ } else if (getrandom_ret == -1 && errno == ENOSYS) {
+ // Fallthrough to using /dev/urandom, below.
+ have_getrandom = 0;
+ } else {
+ // Other errors are fatal.
perror("getrandom");
abort();
}
+
+ if (have_getrandom) {
+ *urandom_fd_bss_get() = kHaveGetrandom;
+ return;
+ }
#endif // USE_NR_getrandom
// Android FIPS builds must support getrandom.
@@ -214,6 +206,71 @@
}
}
+ int flags = fcntl(fd, F_GETFD);
+ if (flags == -1) {
+ // Native Client doesn't implement |fcntl|.
+ if (errno != ENOSYS) {
+ perror("failed to get flags from urandom fd");
+ abort();
+ }
+ } else {
+ flags |= FD_CLOEXEC;
+ if (fcntl(fd, F_SETFD, flags) == -1) {
+ perror("failed to set FD_CLOEXEC on urandom fd");
+ abort();
+ }
+ }
+ *urandom_fd_bss_get() = fd;
+}
+
+DEFINE_STATIC_ONCE(wait_for_entropy_once)
+
+static void wait_for_entropy(void) {
+ int fd = *urandom_fd_bss_get();
+ if (fd == kHaveGetrandom) {
+#if defined(USE_NR_getrandom)
+ if (*getrandom_ready_bss_get()) {
+ // The entropy pool was already initialized in |init_once|.
+ return;
+ }
+
+ uint8_t dummy;
+ ssize_t getrandom_ret =
+ boringssl_getrandom(&dummy, sizeof(dummy), GRND_NONBLOCK);
+ if (getrandom_ret == -1 && errno == EAGAIN) {
+ // Attempt to get the path of the current process to aid in debugging when
+ // something blocks.
+ const char *current_process = "<unknown>";
+#if defined(OPENSSL_HAS_GETAUXVAL)
+ const unsigned long getauxval_ret = getauxval(AT_EXECFN);
+ if (getauxval_ret != 0) {
+ current_process = (const char *)getauxval_ret;
+ }
+#endif
+
+ fprintf(
+ stderr,
+ "%s: getrandom indicates that the entropy pool has not been "
+ "initialized. Rather than continue with poor entropy, this process "
+ "will block until entropy is available.\n",
+ current_process);
+
+ getrandom_ret =
+ boringssl_getrandom(&dummy, sizeof(dummy), 0 /* no flags */);
+ }
+
+ if (getrandom_ret == 1) {
+ return;
+ }
+
+ perror("getrandom");
+ abort();
+#else
+ fprintf(stderr, "urandom fd corrupt.\n");
+ abort();
+#endif // USE_NR_getrandom
+ }
+
#if defined(BORINGSSL_FIPS)
// In FIPS mode we ensure that the kernel has sufficient entropy before
// continuing. This is automatically handled by getrandom, which requires
@@ -235,23 +292,7 @@
usleep(250000);
}
-#endif
-
- int flags = fcntl(fd, F_GETFD);
- if (flags == -1) {
- // Native Client doesn't implement |fcntl|.
- if (errno != ENOSYS) {
- perror("failed to get flags from urandom fd");
- abort();
- }
- } else {
- flags |= FD_CLOEXEC;
- if (fcntl(fd, F_SETFD, flags) == -1) {
- perror("failed to set FD_CLOEXEC on urandom fd");
- abort();
- }
- }
- *urandom_fd_bss_get() = fd;
+#endif // BORINGSSL_FIPS
}
void RAND_set_urandom_fd(int fd) {
@@ -289,14 +330,28 @@
}
// fill_with_entropy writes |len| bytes of entropy into |out|. It returns one
-// on success and zero on error.
-static char fill_with_entropy(uint8_t *out, size_t len) {
+// on success and zero on error. If |block| is one, this function will block
+// until the entropy pool is initialized. Otherwise, this function may fail,
+// setting |errno| to |EAGAIN| if the entropy pool has not yet been initialized.
+static int fill_with_entropy(uint8_t *out, size_t len, int block) {
+ if (len == 0) {
+ return 1;
+ }
+
+ CRYPTO_once(rand_once_bss_get(), init_once);
+ if (block) {
+ CRYPTO_once(wait_for_entropy_once_bss_get(), wait_for_entropy);
+ }
+
+ // Clear |errno| so it has defined value if |read| or |getrandom|
+ // "successfully" returns zero.
+ errno = 0;
while (len > 0) {
ssize_t r;
if (*urandom_fd_bss_get() == kHaveGetrandom) {
#if defined(USE_NR_getrandom)
- r = boringssl_getrandom(out, len, 0 /* no flags */);
+ r = boringssl_getrandom(out, len, block ? 0 : GRND_NONBLOCK);
#else // USE_NR_getrandom
fprintf(stderr, "urandom fd corrupt.\n");
abort();
@@ -319,13 +374,7 @@
// CRYPTO_sysrand puts |requested| random bytes into |out|.
void CRYPTO_sysrand(uint8_t *out, size_t requested) {
- if (requested == 0) {
- return;
- }
-
- CRYPTO_once(rand_once_bss_get(), init_once);
-
- if (!fill_with_entropy(out, requested)) {
+ if (!fill_with_entropy(out, requested, /*block=*/1)) {
perror("entropy fill failed");
abort();
}
@@ -337,5 +386,17 @@
#endif
}
-#endif /* !OPENSSL_WINDOWS && !defined(OPENSSL_FUCHSIA) && \
- !BORINGSSL_UNSAFE_DETERMINISTIC_MODE && !OPENSSL_TRUSTY */
+#if defined(BORINGSSL_FIPS)
+void CRYPTO_sysrand_if_available(uint8_t *out, size_t requested) {
+ // Return all zeros if |fill_with_entropy| fails.
+ OPENSSL_memset(out, 0, requested);
+
+ if (!fill_with_entropy(out, requested, /*block=*/0) &&
+ errno != EAGAIN) {
+ perror("opportunistic entropy fill failed");
+ abort();
+ }
+}
+#endif // BORINGSSL_FIPS
+
+#endif // OPENSSL_URANDOM