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