Added support for getrandom()/getentropy(), and a fix for the RDRAND bug on AMD CPU (family 22).

This commit is contained in:
Thomas Pornin 2019-05-15 18:16:00 +02:00
parent 924921d1be
commit e4edfb84ed
3 changed files with 128 additions and 8 deletions

View file

@ -109,9 +109,27 @@
#define BR_RDRAND 1 #define BR_RDRAND 1
*/ */
/*
* When BR_USE_GETENTROPY is enabled, the SSL engine will use the
* getentropy() function to obtain quality randomness for seeding its
* internal PRNG. On Linux and FreeBSD, getentropy() is implemented by
* the standard library with the system call getrandom(); on OpenBSD,
* getentropy() is the system call, and there is no getrandom() wrapper,
* hence the use of the getentropy() function for maximum portability.
*
* If the getentropy() call fails, and BR_USE_URANDOM is not explicitly
* disabled, then /dev/urandom will be used as a fallback mechanism. On
* FreeBSD and OpenBSD, this does not change much, since /dev/urandom
* will block if not enough entropy has been obtained since last boot.
* On Linux, /dev/urandom might not block, which can be troublesome in
* early boot stages, which is why getentropy() is preferred.
*
#define BR_USE_GETENTROPY 1
*/
/* /*
* When BR_USE_URANDOM is enabled, the SSL engine will use /dev/urandom * When BR_USE_URANDOM is enabled, the SSL engine will use /dev/urandom
* to automatically obtain quality randomness for seedings its internal * to automatically obtain quality randomness for seeding its internal
* PRNG. * PRNG.
* *
#define BR_USE_URANDOM 1 #define BR_USE_URANDOM 1
@ -120,7 +138,7 @@
/* /*
* When BR_USE_WIN32_RAND is enabled, the SSL engine will use the Win32 * When BR_USE_WIN32_RAND is enabled, the SSL engine will use the Win32
* (CryptoAPI) functions (CryptAcquireContext(), CryptGenRandom()...) to * (CryptoAPI) functions (CryptAcquireContext(), CryptGenRandom()...) to
* automatically obtain quality randomness for seedings its internal PRNG. * automatically obtain quality randomness for seeding its internal PRNG.
* *
* Note: if both BR_USE_URANDOM and BR_USE_WIN32_RAND are defined, the * Note: if both BR_USE_URANDOM and BR_USE_WIN32_RAND are defined, the
* former takes precedence. * former takes precedence.

View file

@ -309,9 +309,20 @@
* values are documented on: * values are documented on:
* https://sourceforge.net/p/predef/wiki/OperatingSystems/ * https://sourceforge.net/p/predef/wiki/OperatingSystems/
* *
* TODO: enrich the list of detected system. Also add detection for * Win32's CryptGenRandom() should be available on Windows systems.
* alternate system calls like getentropy(), which are usually *
* preferable when available. * /dev/urandom should work on all Unix-like systems (including macOS X).
*
* getentropy() is present on Linux (Glibc 2.25+), FreeBSD (12.0+) and
* OpenBSD (5.6+). For OpenBSD, there does not seem to be easy to use
* macros to test the minimum version, so we just assume that it is
* recent enough (last version without getentropy() has gone out of
* support in May 2015).
*
* Ideally we should use getentropy() on macOS (10.12+) too, but I don't
* know how to test the exact OS version with preprocessor macros.
*
* TODO: enrich the list of detected system.
*/ */
#ifndef BR_USE_URANDOM #ifndef BR_USE_URANDOM
@ -328,6 +339,15 @@
#endif #endif
#endif #endif
#ifndef BR_USE_GETENTROPY
#if (defined __linux__ \
&& (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 25))) \
|| (defined __FreeBSD__ && __FreeBSD__ >= 12) \
|| defined __OpenBSD__
#define BR_USE_GETENTROPY 1
#endif
#endif
#ifndef BR_USE_WIN32_RAND #ifndef BR_USE_WIN32_RAND
#if defined _WIN32 || defined _WIN64 #if defined _WIN32 || defined _WIN64
#define BR_USE_WIN32_RAND 1 #define BR_USE_WIN32_RAND 1

View file

@ -25,6 +25,10 @@
#define BR_ENABLE_INTRINSICS 1 #define BR_ENABLE_INTRINSICS 1
#include "inner.h" #include "inner.h"
#if BR_USE_GETENTROPY
#include <unistd.h>
#endif
#if BR_USE_URANDOM #if BR_USE_URANDOM
#include <sys/types.h> #include <sys/types.h>
#include <unistd.h> #include <unistd.h>
@ -38,6 +42,9 @@
#pragma comment(lib, "advapi32") #pragma comment(lib, "advapi32")
#endif #endif
/*
* Seeder that uses the RDRAND opcodes (on x86 CPU).
*/
#if BR_RDRAND #if BR_RDRAND
BR_TARGETS_X86_UP BR_TARGETS_X86_UP
BR_TARGET("rdrnd") BR_TARGET("rdrnd")
@ -57,9 +64,24 @@ seeder_rdrand(const br_prng_class **ctx)
* *
* Intel recommends trying at least 10 times in case of * Intel recommends trying at least 10 times in case of
* failure. * failure.
*
* AMD bug: there are reports that some AMD processors
* have a bug that makes them fail silently after a
* suspend/resume cycle, in which case RDRAND will report
* a success but always return 0xFFFFFFFF.
* see: https://bugzilla.kernel.org/show_bug.cgi?id=85911
*
* As a mitigation, if the 32-bit value is 0 or -1, then
* it is considered a failure and tried again. This should
* reliably detect the buggy case, at least. This also
* implies that the selected seed values can never be
* 0x00000000 or 0xFFFFFFFF, which is not a problem since
* we are generating a seed for a PRNG, and we overdo it
* a bit (we generate 32 bytes of randomness, and 256 bits
* of entropy are really overkill).
*/ */
for (j = 0; j < 10; j ++) { for (j = 0; j < 10; j ++) {
if (_rdrand32_step(&x)) { if (_rdrand32_step(&x) && x != 0 && x != (uint32_t)-1) {
goto next_word; goto next_word;
} }
} }
@ -80,9 +102,11 @@ rdrand_supported(void)
*/ */
return br_cpuid(0, 0, 0x40000000, 0); return br_cpuid(0, 0, 0x40000000, 0);
} }
#endif #endif
/*
* Seeder that uses /dev/urandom (on Unix-like systems).
*/
#if BR_USE_URANDOM #if BR_USE_URANDOM
static int static int
seeder_urandom(const br_prng_class **ctx) seeder_urandom(const br_prng_class **ctx)
@ -116,6 +140,32 @@ seeder_urandom(const br_prng_class **ctx)
} }
#endif #endif
/*
* Seeder that uses getentropy() (backed by getrandom() on some systems,
* e.g. Linux). On failure, it will use the /dev/urandom seeder (if
* enabled).
*/
#if BR_USE_GETENTROPY
static int
seeder_getentropy(const br_prng_class **ctx)
{
unsigned char tmp[32];
if (getentropy(tmp, sizeof tmp) == 0) {
(*ctx)->update(ctx, tmp, sizeof tmp);
return 1;
}
#if BR_USE_URANDOM
return seeder_urandom(ctx);
#else
return 0;
#endif
}
#endif
/*
* Seeder that uses CryptGenRandom() (on Windows).
*/
#if BR_USE_WIN32_RAND #if BR_USE_WIN32_RAND
static int static int
seeder_win32(const br_prng_class **ctx) seeder_win32(const br_prng_class **ctx)
@ -139,6 +189,29 @@ seeder_win32(const br_prng_class **ctx)
} }
#endif #endif
/*
* An aggregate seeder that uses RDRAND, and falls back to an OS-provided
* source if RDRAND fails.
*/
#if BR_RDRAND && (BR_USE_GETENTROPY || BR_USE_URANDOM || BR_USE_WIN32_RAND)
static int
seeder_rdrand_with_fallback(const br_prng_class **ctx)
{
if (!seeder_rdrand(ctx)) {
#if BR_USE_GETENTROPY
return seeder_getentropy(ctx);
#elif BR_USE_URANDOM
return seeder_urandom(ctx);
#elif BR_USE_WIN32_RAND
return seeder_win32(ctx);
#else
#error "macro selection has gone wrong"
#endif
}
return 1;
}
#endif
/* see bearssl_rand.h */ /* see bearssl_rand.h */
br_prng_seeder br_prng_seeder
br_prng_seeder_system(const char **name) br_prng_seeder_system(const char **name)
@ -148,10 +221,19 @@ br_prng_seeder_system(const char **name)
if (name != NULL) { if (name != NULL) {
*name = "rdrand"; *name = "rdrand";
} }
#if BR_USE_GETENTROPY || BR_USE_URANDOM || BR_USE_WIN32_RAND
return &seeder_rdrand_with_fallback;
#else
return &seeder_rdrand; return &seeder_rdrand;
#endif
} }
#endif #endif
#if BR_USE_URANDOM #if BR_USE_GETENTROPY
if (name != NULL) {
*name = "getentropy";
}
return &seeder_getentropy;
#elif BR_USE_URANDOM
if (name != NULL) { if (name != NULL) {
*name = "urandom"; *name = "urandom";
} }