[tor-commits] [tor/master] Add support for getrandom() and getentropy() when available

nickm at torproject.org nickm at torproject.org
Tue Dec 8 17:36:21 UTC 2015


commit 353c71516e80f41dbb97da00ddfc528ec5ce3c40
Author: Yawning Angel <yawning at schwanenlied.me>
Date:   Thu Nov 26 15:21:50 2015 +0000

    Add support for getrandom() and getentropy() when available
    
    Implements feature #13696.
---
 changes/feature13696 |    3 ++
 configure.ac         |    2 +
 src/common/crypto.c  |  146 +++++++++++++++++++++++++++++++++++++++++++++-----
 src/common/sandbox.c |    4 ++
 4 files changed, 142 insertions(+), 13 deletions(-)

diff --git a/changes/feature13696 b/changes/feature13696
new file mode 100644
index 0000000..21c2188
--- /dev/null
+++ b/changes/feature13696
@@ -0,0 +1,3 @@
+  o Minor features (security, cryptography):
+    - Use modern system calls to generate strong entropy on platforms that
+      provide them. Closes ticket 13696.
diff --git a/configure.ac b/configure.ac
index 0530d77..b14361f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -384,6 +384,7 @@ AC_CHECK_FUNCS(
         flock \
         ftime \
         getaddrinfo \
+        getentropy \
         getifaddrs \
         getpass \
         getrlimit \
@@ -951,6 +952,7 @@ AC_CHECK_HEADERS(
         sys/select.h \
         sys/socket.h \
 	sys/statvfs.h \
+        sys/syscall.h \
 	sys/sysctl.h \
         sys/syslimits.h \
         sys/time.h \
diff --git a/src/common/crypto.c b/src/common/crypto.c
index baef755..5d8b45e 100644
--- a/src/common/crypto.c
+++ b/src/common/crypto.c
@@ -43,6 +43,7 @@
 #include <ctype.h>
 #endif
 #ifdef HAVE_UNISTD_H
+#define _GNU_SOURCE
 #include <unistd.h>
 #endif
 #ifdef HAVE_FCNTL_H
@@ -51,6 +52,9 @@
 #ifdef HAVE_SYS_FCNTL_H
 #include <sys/fcntl.h>
 #endif
+#ifdef HAVE_SYS_SYSCALL_H
+#include <sys/syscall.h>
+#endif
 
 #include "torlog.h"
 #include "aes.h"
@@ -68,6 +72,9 @@
 /** Longest recognized */
 #define MAX_DNS_LABEL_SIZE 63
 
+/** Largest strong entropy request */
+#define MAX_STRONGEST_RAND_SIZE 256
+
 /** Macro: is k a valid RSA public or private key? */
 #define PUBLIC_KEY_OK(k) ((k) && (k)->key && (k)->key->n)
 /** Macro: is k a valid RSA private key? */
@@ -2344,23 +2351,18 @@ crypto_seed_weak_rng(tor_weak_rng_t *rng)
 }
 
 /** Try to get <b>out_len</b> bytes of the strongest entropy we can generate,
- * storing it into <b>out</b>. Return -1 on success, 0 on failure.
+ * via system calls, storing it into <b>out</b>. Return -1 on success, 0 on
+ * failure.  A maximum request size of 256 bytes is imposed.
  */
-int
-crypto_strongest_rand(uint8_t *out, size_t out_len)
+static int
+crypto_strongest_rand_syscall(uint8_t *out, size_t out_len)
 {
-#ifdef _WIN32
+  tor_assert(out_len <= MAX_STRONGEST_RAND_SIZE);
+
+#if defined(_WIN32)
   static int provider_set = 0;
   static HCRYPTPROV provider;
-#else
-  static const char *filenames[] = {
-    "/dev/srandom", "/dev/urandom", "/dev/random", NULL
-  };
-  int fd, i;
-  size_t n;
-#endif
 
-#ifdef _WIN32
   if (!provider_set) {
     if (!CryptAcquireContext(&provider, NULL, NULL, PROV_RSA_FULL,
                              CRYPT_VERIFYCONTEXT)) {
@@ -2375,7 +2377,79 @@ crypto_strongest_rand(uint8_t *out, size_t out_len)
   }
 
   return 0;
+#elif defined(__linux__) && defined(SYS_getrandom)
+  static int getrandom_works = 1; /* Be optimitic about our chances... */
+
+  /* getrandom() isn't as straight foward as getentropy(), and has
+   * no glibc wrapper.
+   *
+   * As far as I can tell from getrandom(2) and the source code, the
+   * requests we issue will always succeed (though it will block on the
+   * call if /dev/urandom isn't seeded yet), since we are NOT specifying
+   * GRND_NONBLOCK and the request is <= 256 bytes.
+   *
+   * The manpage is unclear on what happens if a signal interrupts the call
+   * while the request is blocked due to lack of entropy....
+   *
+   * We optimistically assume that getrandom() is available and functional
+   * because it is the way of the future, and 2 branch mispredicts pale in
+   * comparision to the overheads involved with failing to open
+   * /dev/srandom followed by opening and reading from /dev/urandom.
+   */
+  if (PREDICT_LIKELY(getrandom_works)) {
+    int ret;
+    do {
+      /* A flag of '0' here means to read from '/dev/urandom', and to
+       * block if insufficient entropy is available to service the
+       * request.
+       */
+      ret = syscall(SYS_getrandom, out, out_len, 0);
+    } while (ret == -1 && ((errno == EINTR) ||(errno == EAGAIN)));
+
+    if (PREDICT_UNLIKELY(ret == -1)) {
+      tor_assert(errno != EAGAIN);
+      tor_assert(errno != EINTR);
+
+      /* Probably ENOSYS. */
+      log_warn(LD_CRYPTO, "Can't get entropy from getrandom().");
+      getrandom_works = 0; /* Don't bother trying again. */
+      return -1;
+    }
+
+    tor_assert(ret == (int)out_len);
+    return 0;
+  }
+
+  return -1; /* getrandom() previously failed unexpectedly. */
+#elif defined(HAVE_GETENTROPY)
+  /* getentropy() is what Linux's getrandom() wants to be when it grows up.
+   * the only gotcha is that requests are limited to 256 bytes.
+   */
+  return getentropy(out, out_len);
+#endif
+
+  /* This platform doesn't have a supported syscall based random. */
+  return -1;
+}
+
+/** Try to get <b>out_len</b> bytes of the strongest entropy we can generate,
+ * via the per-platform fallback mechanism, storing it into <b>out</b>.
+ * Return -1 on success, 0 on failure.  A maximum request size of 256 bytes
+ * is imposed.
+ */
+static int
+crypto_strongest_rand_fallback(uint8_t *out, size_t out_len)
+{
+#ifdef _WIN32
+  /* Windows exclusively uses crypto_strongest_rand_syscall(). */
+  return -1;
 #else
+  static const char *filenames[] = {
+    "/dev/srandom", "/dev/urandom", "/dev/random", NULL
+  };
+  int fd, i;
+  size_t n;
+
   for (i = 0; filenames[i]; ++i) {
     log_debug(LD_FS, "Opening %s for entropy", filenames[i]);
     fd = open(sandbox_intern_string(filenames[i]), O_RDONLY, 0);
@@ -2393,11 +2467,57 @@ crypto_strongest_rand(uint8_t *out, size_t out_len)
     return 0;
   }
 
-  log_warn(LD_CRYPTO, "Cannot get strong entropy: no entropy source found.");
   return -1;
 #endif
 }
 
+/** Try to get <b>out_len</b> bytes of the strongest entropy we can generate,
+ * storing it into <b>out</b>. Return -1 on success, 0 on failure.  A maximum
+ * request size of 256 bytes is imposed.
+ */
+int
+crypto_strongest_rand(uint8_t *out, size_t out_len)
+{
+  static const size_t sanity_min_size = 16;
+  static const int max_attempts = 3;
+  tor_assert(out_len <= MAX_STRONGEST_RAND_SIZE);
+
+  /* For buffers >= 16 bytes (128 bits), we sanity check the output by
+   * zero filling the buffer and ensuring that it actually was at least
+   * partially modified.
+   *
+   * Checking that any individual byte is non-zero seems like it would
+   * fail too often (p = out_len * 1/256) for comfort, but this is an
+   * "adjust according to taste" sort of check.
+   */
+  memwipe(out, 0, out_len);
+  for (int i = 0; i < max_attempts; i++) {
+    /* Try to use the syscall/OS favored mechanism to get strong entropy. */
+    if (crypto_strongest_rand_syscall(out, out_len) != 0) {
+      /* Try to use the less-favored mechanism to get strong entropy. */
+      if (crypto_strongest_rand_fallback(out, out_len) != 0) {
+        /* Welp, we tried.  Hopefully the calling code terminates the process
+         * since we're basically boned without good entropy.
+         */
+        log_warn(LD_CRYPTO,
+                 "Cannot get strong entropy: no entropy source found.");
+        return -1;
+      }
+    }
+
+    if ((out_len < sanity_min_size) || !tor_mem_is_zero((char*)out, out_len))
+      return 0;
+  }
+
+  /* We tried max_attempts times to fill a buffer >= 128 bits long,
+   * and each time it returned all '0's.  Either the system entropy
+   * source is busted, or the user should go out and buy a ticket to
+   * every lottery on the planet.
+   */
+  log_warn(LD_CRYPTO, "Strong OS entropy returned all zero buffer.");
+  return -1;
+}
+
 /** Seed OpenSSL's random number generator with bytes from the operating
  * system.  Return 0 on success, -1 on failure.
  */
diff --git a/src/common/sandbox.c b/src/common/sandbox.c
index b995762..8198858 100644
--- a/src/common/sandbox.c
+++ b/src/common/sandbox.c
@@ -199,6 +199,10 @@ static int filter_nopar_gen[] = {
     SCMP_SYS(stat64),
 #endif
 
+#ifdef __NR_getrandom
+    SCMP_SYS(getrandom),
+#endif
+
     /*
      * These socket syscalls are not required on x86_64 and not supported with
      * some libseccomp versions (eg: 1.0.1)





More information about the tor-commits mailing list