diff options
Diffstat (limited to 'src/crypto/rand')
-rw-r--r-- | src/crypto/rand/CMakeLists.txt | 2 | ||||
-rw-r--r-- | src/crypto/rand/asm/rdrand-x86_64.pl | 52 | ||||
-rw-r--r-- | src/crypto/rand/hwrand.c | 45 | ||||
-rw-r--r-- | src/crypto/rand/internal.h | 11 | ||||
-rw-r--r-- | src/crypto/rand/rand.c | 28 | ||||
-rw-r--r-- | src/crypto/rand/urandom.c | 292 |
6 files changed, 236 insertions, 194 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/asm/rdrand-x86_64.pl b/src/crypto/rand/asm/rdrand-x86_64.pl index a917611..c32a55c 100644 --- a/src/crypto/rand/asm/rdrand-x86_64.pl +++ b/src/crypto/rand/asm/rdrand-x86_64.pl @@ -1,5 +1,19 @@ #!/usr/bin/env perl +# Copyright (c) 2015, Google Inc. +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +# SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION +# OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ + $flavour = shift; $output = shift; if ($flavour =~ /\./) { $output = $flavour; undef $flavour; } @@ -14,11 +28,47 @@ open OUT,"| \"$^X\" $xlate $flavour $output"; print<<___; .text +# CRYPTO_rdrand writes eight bytes of random data from the hardware RNG to +# |out|. It returns one on success or zero on hardware failure. +# int CRYPTO_rdrand(uint8_t out[8]); .globl CRYPTO_rdrand .type CRYPTO_rdrand,\@function,1 .align 16 CRYPTO_rdrand: - .byte 0x48, 0x0f, 0xc7, 0xf0 + xorq %rax, %rax + # This is rdrand %rcx. It sets rcx to a random value and sets the carry + # flag on success. + .byte 0x48, 0x0f, 0xc7, 0xf1 + # An add-with-carry of zero effectively sets %rax to the carry flag. + adcq %rax, %rax + movq %rcx, 0(%rdi) + retq + +# CRYPTO_rdrand_multiple8_buf fills |len| bytes at |buf| with random data from +# the hardware RNG. The |len| argument must be a multiple of eight. It returns +# one on success and zero on hardware failure. +# int CRYPTO_rdrand_multiple8_buf(uint8_t *buf, size_t len); +.globl CRYPTO_rdrand_multiple8_buf +.type CRYPTO_rdrand_multiple8_buf,\@function,2 +.align 16 +CRYPTO_rdrand_multiple8_buf: + test %rsi, %rsi + jz .Lout + movq \$8, %rdx +.Lloop: + # This is rdrand %rcx. It sets rcx to a random value and sets the carry + # flag on success. + .byte 0x48, 0x0f, 0xc7, 0xf1 + jnc .Lerr + movq %rcx, 0(%rdi) + addq %rdx, %rdi + subq %rdx, %rsi + jnz .Lloop +.Lout: + movq \$1, %rax + retq +.Lerr: + xorq %rax, %rax retq ___ diff --git a/src/crypto/rand/hwrand.c b/src/crypto/rand/hwrand.c index 73d3de7..f0bbccd 100644 --- a/src/crypto/rand/hwrand.c +++ b/src/crypto/rand/hwrand.c @@ -14,43 +14,52 @@ #include <openssl/rand.h> -#include <stdlib.h> +#include <assert.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) { +/* 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; } -/* CRYPTO_rdrand is defined in asm/rdrand-x86_64.pl */ -extern uint64_t CRYPTO_rdrand(void); +int CRYPTO_hwrand(uint8_t *buf, size_t len) { + if (!have_rdrand()) { + return 0; + } -void CRYPTO_hwrand(uint8_t *buf, size_t len) { - while (len >= 8) { - uint64_t rand = CRYPTO_rdrand(); - memcpy(buf, &rand, sizeof(rand)); - len -= sizeof(rand); - buf += sizeof(rand); + const size_t len_multiple8 = len & ~7; + if (!CRYPTO_rdrand_multiple8_buf(buf, len_multiple8)) { + return 0; } + len -= len_multiple8; + + if (len != 0) { + assert(len < 8); - if (len > 0) { - uint64_t rand = CRYPTO_rdrand(); - memcpy(buf, &rand, len); + uint8_t rand_buf[8]; + if (!CRYPTO_rdrand(rand_buf)) { + return 0; + } + memcpy(buf + len_multiple8, rand_buf, len); } + + return 1; } #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 1cca7f3..f35abbb 100644 --- a/src/crypto/rand/internal.h +++ b/src/crypto/rand/internal.h @@ -24,13 +24,10 @@ 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. */ -void CRYPTO_hwrand(uint8_t *buf, size_t len); +/* 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); #if defined(__cplusplus) diff --git a/src/crypto/rand/rand.c b/src/crypto/rand/rand.c index a647b6a..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,16 +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()) { + 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); @@ -108,8 +105,6 @@ int RAND_bytes(uint8_t *buf, size_t len) { state->partial_block_used = sizeof(state->partial_block); } - CRYPTO_hwrand(buf, len); - if (len >= sizeof(state->partial_block)) { size_t remaining = len; while (remaining > 0) { @@ -163,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; } @@ -170,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 */ |