diff options
Diffstat (limited to 'src/crypto/rand')
-rw-r--r-- | src/crypto/rand/CMakeLists.txt | 2 | ||||
-rw-r--r-- | src/crypto/rand/hwrand.c | 21 | ||||
-rw-r--r-- | src/crypto/rand/internal.h | 10 | ||||
-rw-r--r-- | src/crypto/rand/rand.c | 27 | ||||
-rw-r--r-- | src/crypto/rand/urandom.c | 292 |
5 files changed, 168 insertions, 184 deletions
diff --git a/src/crypto/rand/CMakeLists.txt b/src/crypto/rand/CMakeLists.txt index 374d8f1..35d5290 100644 --- a/src/crypto/rand/CMakeLists.txt +++ b/src/crypto/rand/CMakeLists.txt @@ -1,4 +1,4 @@ -include_directories(. .. ../../include) +include_directories(../../include) if (${ARCH} STREQUAL "x86_64") set( diff --git a/src/crypto/rand/hwrand.c b/src/crypto/rand/hwrand.c index 5f81f09..f0bbccd 100644 --- a/src/crypto/rand/hwrand.c +++ b/src/crypto/rand/hwrand.c @@ -15,23 +15,28 @@ #include <openssl/rand.h> #include <assert.h> -#include <stdlib.h> #include <string.h> #include <openssl/cpu.h> +#include "internal.h" -#if defined(OPENSSL_X86_64) && !defined(OPENSSL_NO_ASM) -int CRYPTO_have_hwrand(void) { - return (OPENSSL_ia32cap_P[1] & (1u << 30)) != 0; -} +#if defined(OPENSSL_X86_64) && !defined(OPENSSL_NO_ASM) /* These functions are defined in asm/rdrand-x86_64.pl */ extern int CRYPTO_rdrand(uint8_t out[8]); extern int CRYPTO_rdrand_multiple8_buf(uint8_t *buf, size_t len); +static int have_rdrand(void) { + return (OPENSSL_ia32cap_P[1] & (1u << 30)) != 0; +} + int CRYPTO_hwrand(uint8_t *buf, size_t len) { + if (!have_rdrand()) { + return 0; + } + const size_t len_multiple8 = len & ~7; if (!CRYPTO_rdrand_multiple8_buf(buf, len_multiple8)) { return 0; @@ -53,12 +58,8 @@ int CRYPTO_hwrand(uint8_t *buf, size_t len) { #else -int CRYPTO_have_hwrand(void) { +int CRYPTO_hwrand(uint8_t *buf, size_t len) { return 0; } -void CRYPTO_hwrand(uint8_t *buf, size_t len) { - abort(); -} - #endif diff --git a/src/crypto/rand/internal.h b/src/crypto/rand/internal.h index 5e6ea11..f35abbb 100644 --- a/src/crypto/rand/internal.h +++ b/src/crypto/rand/internal.h @@ -24,13 +24,9 @@ extern "C" { * system. */ void CRYPTO_sysrand(uint8_t *buf, size_t len); -/* CRYPTO_have_hwrand returns one iff |CRYPTO_hwrand| can be called to generate - * hardware entropy. */ -int CRYPTO_have_hwrand(void); - -/* CRYPTO_hwrand fills |len| bytes at |buf| with entropy from the hardware. - * This function can only be called if |CRYPTO_have_hwrand| returns one. - * It returns one on success or zero on hardware failure. */ +/* CRYPTO_hwrand fills |len| bytes at |buf| with entropy from the hardware. It + * returns one on success or zero on hardware failure or if hardware support is + * unavailable. */ int CRYPTO_hwrand(uint8_t *buf, size_t len); diff --git a/src/crypto/rand/rand.c b/src/crypto/rand/rand.c index a96ac48..e76a120 100644 --- a/src/crypto/rand/rand.c +++ b/src/crypto/rand/rand.c @@ -17,6 +17,7 @@ #include <limits.h> #include <string.h> +#include <openssl/chacha.h> #include <openssl/mem.h> #include "internal.h" @@ -69,17 +70,12 @@ static void rand_thread_state_free(void *state) { OPENSSL_free(state); } -extern void CRYPTO_chacha_20(uint8_t *out, const uint8_t *in, size_t in_len, - const uint8_t key[32], const uint8_t nonce[8], - size_t counter); - int RAND_bytes(uint8_t *buf, size_t len) { if (len == 0) { return 1; } - if (!CRYPTO_have_hwrand() || - !CRYPTO_hwrand(buf, len)) { + if (!CRYPTO_hwrand(buf, len)) { /* Without a hardware RNG to save us from address-space duplication, the OS * entropy is used directly. */ CRYPTO_sysrand(buf, len); @@ -162,6 +158,10 @@ int RAND_load_file(const char *path, long num) { void RAND_add(const void *buf, int num, double entropy) {} +int RAND_egd(const char *path) { + return 255; +} + int RAND_poll(void) { return 1; } @@ -169,3 +169,18 @@ int RAND_poll(void) { int RAND_status(void) { return 1; } + +static const struct rand_meth_st kSSLeayMethod = { + RAND_seed, + RAND_bytes, + RAND_cleanup, + RAND_add, + RAND_pseudo_bytes, + RAND_status, +}; + +RAND_METHOD *RAND_SSLeay(void) { + return (RAND_METHOD*) &kSSLeayMethod; +} + +void RAND_set_rand_method(const RAND_METHOD *method) {} diff --git a/src/crypto/rand/urandom.c b/src/crypto/rand/urandom.c index 788a979..1cc5260 100644 --- a/src/crypto/rand/urandom.c +++ b/src/crypto/rand/urandom.c @@ -30,92 +30,126 @@ /* This file implements a PRNG by reading from /dev/urandom, optionally with a - * fork-safe buffer. - * - * If buffering is enabled then it maintains a global, linked list of buffers. - * Threads which need random bytes grab a buffer from the list under a lock and - * copy out the bytes that they need. In the rare case that the buffer is - * empty, it's refilled from /dev/urandom outside of the lock. - * - * Large requests are always serviced from /dev/urandom directly. - * - * Each buffer contains the PID of the process that created it and it's tested - * against the current PID each time. Thus processes that fork will discard all - * the buffers filled by the parent process. There are two problems with this: - * - * 1) glibc maintains a cache of the current PID+PPID and, if this cache isn't - * correctly invalidated, the getpid() will continue to believe that - * it's the old process. Glibc depends on the glibc wrappers for fork, - * vfork and clone being used in order to invalidate the getpid() cache. - * - * 2) If a process forks, dies and then its child forks, it's possible that - * the third process will end up with the same PID as the original process. - * If the second process never used any random values then this will mean - * that the third process has stale, cached values and won't notice. - */ - -/* BUF_SIZE is intended to be a 4K allocation with malloc overhead. struct - * rand_buffer also fits in this space and the remainder is entropy. */ -#define BUF_SIZE (4096 - 16) - -/* rand_buffer contains unused, random bytes. These structures form a linked - * list via the |next| pointer, which is NULL in the final element. */ + * buffer, which is unsafe across |fork|. */ + +#define BUF_SIZE 4096 + +/* rand_buffer contains unused, random bytes, some of which may have been + * consumed already. */ struct rand_buffer { - size_t used; /* used contains the number of bytes of |rand| that have - been consumed. */ - struct rand_buffer *next; - pid_t pid; /* pid contains the pid at the time that the buffer was - created so that data is not duplicated after a fork. */ - pid_t ppid; /* ppid contains the parent pid in order to try and reduce - the possibility of duplicated PID confusing the - detection of a fork. */ - uint8_t rand[]; + size_t used; + uint8_t rand[BUF_SIZE]; }; -/* rand_bytes_per_buf is the number of actual entropy bytes in a buffer. */ -static const size_t rand_bytes_per_buf = BUF_SIZE - sizeof(struct rand_buffer); - -static struct CRYPTO_STATIC_MUTEX global_lock = CRYPTO_STATIC_MUTEX_INIT; +/* requested_lock is used to protect the |*_requested| variables. */ +static struct CRYPTO_STATIC_MUTEX requested_lock = CRYPTO_STATIC_MUTEX_INIT; -/* list_head is the start of a global, linked-list of rand_buffer objects. It's - * protected by |global_lock|. */ -static struct rand_buffer *list_head; +/* urandom_fd_requested is set by |RAND_set_urandom_fd|. It's protected by + * |requested_lock|. */ +static int urandom_fd_requested = -2; -/* urandom_fd is a file descriptor to /dev/urandom. It's protected by - * |global_lock|. */ +/* urandom_fd is a file descriptor to /dev/urandom. It's protected by |once|. */ static int urandom_fd = -2; +/* urandom_buffering_requested is set by |RAND_enable_fork_unsafe_buffering|. + * It's protected by |requested_lock|. */ +static int urandom_buffering_requested = 0; + /* urandom_buffering controls whether buffering is enabled (1) or not (0). This - * is protected by |global_lock|. */ + * is protected by |once|. */ static int urandom_buffering = 0; -/* urandom_get_fd_locked returns a file descriptor to /dev/urandom. The caller - * of this function must hold |global_lock|. */ -static int urandom_get_fd_locked(void) { - if (urandom_fd != -2) { - return urandom_fd; +static CRYPTO_once_t once = CRYPTO_ONCE_INIT; + +/* init_once initializes the state of this module to values previously + * requested. This is the only function that modifies |urandom_fd| and + * |urandom_buffering|, whose values may be read safely after calling the + * once. */ +static void init_once(void) { + CRYPTO_STATIC_MUTEX_lock_read(&requested_lock); + urandom_buffering = urandom_buffering_requested; + int fd = urandom_fd_requested; + CRYPTO_STATIC_MUTEX_unlock(&requested_lock); + + if (fd == -2) { + do { + fd = open("/dev/urandom", O_RDONLY); + } while (fd == -1 && errno == EINTR); } - urandom_fd = open("/dev/urandom", O_RDONLY); - return urandom_fd; + if (fd < 0) { + abort(); + } + + int flags = fcntl(fd, F_GETFD); + if (flags == -1) { + abort(); + } + flags |= FD_CLOEXEC; + if (fcntl(fd, F_SETFD, flags) == -1) { + abort(); + } + urandom_fd = fd; } -/* RAND_cleanup frees all buffers, closes any cached file descriptor - * and resets the global state. */ -void RAND_cleanup(void) { - struct rand_buffer *cur; +void RAND_cleanup(void) {} - CRYPTO_STATIC_MUTEX_lock_write(&global_lock); - while ((cur = list_head)) { - list_head = cur->next; - OPENSSL_free(cur); +void RAND_set_urandom_fd(int fd) { + fd = dup(fd); + if (fd < 0) { + abort(); } - if (urandom_fd >= 0) { - close(urandom_fd); + + CRYPTO_STATIC_MUTEX_lock_write(&requested_lock); + urandom_fd_requested = fd; + CRYPTO_STATIC_MUTEX_unlock(&requested_lock); + + CRYPTO_once(&once, init_once); + if (urandom_fd != fd) { + abort(); // Already initialized. } - urandom_fd = -2; - list_head = NULL; - CRYPTO_STATIC_MUTEX_unlock(&global_lock); +} + +void RAND_enable_fork_unsafe_buffering(int fd) { + if (fd >= 0) { + fd = dup(fd); + if (fd < 0) { + abort(); + } + } else { + fd = -2; + } + + CRYPTO_STATIC_MUTEX_lock_write(&requested_lock); + urandom_buffering_requested = 1; + urandom_fd_requested = fd; + CRYPTO_STATIC_MUTEX_unlock(&requested_lock); + + CRYPTO_once(&once, init_once); + if (urandom_buffering != 1 || (fd >= 0 && urandom_fd != fd)) { + abort(); // Already initialized. + } +} + +static struct rand_buffer *get_thread_local_buffer(void) { + struct rand_buffer *buf = + CRYPTO_get_thread_local(OPENSSL_THREAD_LOCAL_URANDOM_BUF); + if (buf != NULL) { + return buf; + } + + buf = OPENSSL_malloc(sizeof(struct rand_buffer)); + if (buf == NULL) { + return NULL; + } + buf->used = BUF_SIZE; /* To trigger a |read_full| on first use. */ + if (!CRYPTO_set_thread_local(OPENSSL_THREAD_LOCAL_URANDOM_BUF, buf, + OPENSSL_free)) { + OPENSSL_free(buf); + return NULL; + } + + return buf; } /* read_full reads exactly |len| bytes from |fd| into |out| and returns 1. In @@ -138,110 +172,48 @@ static char read_full(int fd, uint8_t *out, size_t len) { return 1; } -/* CRYPTO_sysrand puts |num| random bytes into |out|. */ -void CRYPTO_sysrand(uint8_t *out, size_t requested) { - int fd; - struct rand_buffer *buf; - size_t todo; - pid_t pid, ppid; - - if (requested == 0) { - return; - } +/* read_from_buffer reads |requested| random bytes from the buffer into |out|, + * refilling it if necessary to satisfy the request. */ +static void read_from_buffer(struct rand_buffer *buf, + uint8_t *out, size_t requested) { + size_t remaining = BUF_SIZE - buf->used; - CRYPTO_STATIC_MUTEX_lock_write(&global_lock); - fd = urandom_get_fd_locked(); + while (requested > remaining) { + memcpy(out, &buf->rand[buf->used], remaining); + buf->used += remaining; + out += remaining; + requested -= remaining; - if (fd < 0) { - CRYPTO_STATIC_MUTEX_unlock(&global_lock); - abort(); - return; - } - - /* If buffering is not enabled, or if the request is large, then the - * result comes directly from urandom. */ - if (!urandom_buffering || requested > BUF_SIZE / 2) { - CRYPTO_STATIC_MUTEX_unlock(&global_lock); - if (!read_full(fd, out, requested)) { + if (!read_full(urandom_fd, buf->rand, BUF_SIZE)) { abort(); - } - return; - } - - pid = getpid(); - ppid = getppid(); - - for (;;) { - buf = list_head; - if (buf && buf->pid == pid && buf->ppid == ppid && - rand_bytes_per_buf - buf->used >= requested) { - memcpy(out, &buf->rand[buf->used], requested); - buf->used += requested; - CRYPTO_STATIC_MUTEX_unlock(&global_lock); return; } - - /* If we don't immediately have enough entropy with the correct - * PID, remove the buffer from the list in order to gain - * exclusive access and unlock. */ - if (buf) { - list_head = buf->next; - } - CRYPTO_STATIC_MUTEX_unlock(&global_lock); - - if (!buf) { - buf = (struct rand_buffer *)OPENSSL_malloc(BUF_SIZE); - if (!buf) { - abort(); - return; - } - /* The buffer doesn't contain any random bytes yet - * so we mark it as fully used so that it will be - * filled below. */ - buf->used = rand_bytes_per_buf; - buf->next = NULL; - buf->pid = pid; - buf->ppid = ppid; - } - - if (buf->pid == pid && buf->ppid == ppid) { - break; - } - - /* We have forked and so cannot use these bytes as they - * may have been used in another process. */ - OPENSSL_free(buf); - CRYPTO_STATIC_MUTEX_lock_write(&global_lock); + buf->used = 0; + remaining = BUF_SIZE; } - while (requested > 0) { - todo = rand_bytes_per_buf - buf->used; - if (todo > requested) { - todo = requested; - } - memcpy(out, &buf->rand[buf->used], todo); - requested -= todo; - out += todo; - buf->used += todo; + memcpy(out, &buf->rand[buf->used], requested); + buf->used += requested; +} - if (buf->used < rand_bytes_per_buf) { - break; - } +/* CRYPTO_sysrand puts |requested| random bytes into |out|. */ +void CRYPTO_sysrand(uint8_t *out, size_t requested) { + if (requested == 0) { + return; + } - if (!read_full(fd, buf->rand, rand_bytes_per_buf)) { - OPENSSL_free(buf); - abort(); + CRYPTO_once(&once, init_once); + if (urandom_buffering && requested < BUF_SIZE) { + struct rand_buffer *buf = get_thread_local_buffer(); + if (buf != NULL) { + read_from_buffer(buf, out, requested); return; } - - buf->used = 0; } - CRYPTO_STATIC_MUTEX_lock_write(&global_lock); - assert(list_head != buf); - buf->next = list_head; - list_head = buf; - CRYPTO_STATIC_MUTEX_unlock(&global_lock); + if (!read_full(urandom_fd, out, requested)) { + abort(); + } } #endif /* !OPENSSL_WINDOWS */ |