Added support for getrandom()/getentropy(), and a fix for the RDRAND bug on AMD CPU...
authorThomas Pornin <pornin@bolet.org>
Wed, 15 May 2019 16:16:00 +0000 (18:16 +0200)
committerThomas Pornin <pornin@bolet.org>
Wed, 15 May 2019 16:16:00 +0000 (18:16 +0200)
src/config.h
src/inner.h
src/rand/sysrng.c

index 8ea4d8a..94627a2 100644 (file)
 #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
- * to automatically obtain quality randomness for seedings its internal
+ * to automatically obtain quality randomness for seeding its internal
  * PRNG.
  *
 #define BR_USE_URANDOM   1
 /*
  * When BR_USE_WIN32_RAND is enabled, the SSL engine will use the Win32
  * (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
  * former takes precedence.
index 986220f..07e1d0a 100644 (file)
  * values are documented on:
  *    https://sourceforge.net/p/predef/wiki/OperatingSystems/
  *
- * TODO: enrich the list of detected system. Also add detection for
- * alternate system calls like getentropy(), which are usually
- * preferable when available.
+ * Win32's CryptGenRandom() should be available on Windows systems.
+ *
+ * /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
 #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
 #if defined _WIN32 || defined _WIN64
 #define BR_USE_WIN32_RAND   1
index 5ddbcbe..5a92114 100644 (file)
 #define BR_ENABLE_INTRINSICS   1
 #include "inner.h"
 
+#if BR_USE_GETENTROPY
+#include <unistd.h>
+#endif
+
 #if BR_USE_URANDOM
 #include <sys/types.h>
 #include <unistd.h>
@@ -38,6 +42,9 @@
 #pragma comment(lib, "advapi32")
 #endif
 
+/*
+ * Seeder that uses the RDRAND opcodes (on x86 CPU).
+ */
 #if BR_RDRAND
 BR_TARGETS_X86_UP
 BR_TARGET("rdrnd")
@@ -57,9 +64,24 @@ seeder_rdrand(const br_prng_class **ctx)
                 *
                 * Intel recommends trying at least 10 times in case of
                 * 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 ++) {
-                       if (_rdrand32_step(&x)) {
+                       if (_rdrand32_step(&x) && x != 0 && x != (uint32_t)-1) {
                                goto next_word;
                        }
                }
@@ -80,9 +102,11 @@ rdrand_supported(void)
         */
        return br_cpuid(0, 0, 0x40000000, 0);
 }
-
 #endif
 
+/*
+ * Seeder that uses /dev/urandom (on Unix-like systems).
+ */
 #if BR_USE_URANDOM
 static int
 seeder_urandom(const br_prng_class **ctx)
@@ -116,6 +140,32 @@ seeder_urandom(const br_prng_class **ctx)
 }
 #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
 static int
 seeder_win32(const br_prng_class **ctx)
@@ -139,6 +189,29 @@ seeder_win32(const br_prng_class **ctx)
 }
 #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 */
 br_prng_seeder
 br_prng_seeder_system(const char **name)
@@ -148,10 +221,19 @@ br_prng_seeder_system(const char **name)
                if (name != NULL) {
                        *name = "rdrand";
                }
+#if BR_USE_GETENTROPY || BR_USE_URANDOM || BR_USE_WIN32_RAND
+               return &seeder_rdrand_with_fallback;
+#else
                return &seeder_rdrand;
+#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) {
                *name = "urandom";
        }