[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