diff options
Diffstat (limited to 'src/crypto/rand/urandom.c')
-rw-r--r-- | src/crypto/rand/urandom.c | 292 |
1 files changed, 160 insertions, 132 deletions
diff --git a/src/crypto/rand/urandom.c b/src/crypto/rand/urandom.c index 1cc5260..788a979 100644 --- a/src/crypto/rand/urandom.c +++ b/src/crypto/rand/urandom.c @@ -30,126 +30,92 @@ /* This file implements a PRNG by reading from /dev/urandom, optionally with a - * buffer, which is unsafe across |fork|. */ - -#define BUF_SIZE 4096 - -/* rand_buffer contains unused, random bytes, some of which may have been - * consumed already. */ + * 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. */ struct rand_buffer { - size_t used; - uint8_t rand[BUF_SIZE]; + 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[]; }; -/* requested_lock is used to protect the |*_requested| variables. */ -static struct CRYPTO_STATIC_MUTEX requested_lock = CRYPTO_STATIC_MUTEX_INIT; +/* 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); -/* urandom_fd_requested is set by |RAND_set_urandom_fd|. It's protected by - * |requested_lock|. */ -static int urandom_fd_requested = -2; +static struct CRYPTO_STATIC_MUTEX global_lock = CRYPTO_STATIC_MUTEX_INIT; -/* urandom_fd is a file descriptor to /dev/urandom. It's protected by |once|. */ -static int urandom_fd = -2; +/* 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_buffering_requested is set by |RAND_enable_fork_unsafe_buffering|. - * It's protected by |requested_lock|. */ -static int urandom_buffering_requested = 0; +/* urandom_fd is a file descriptor to /dev/urandom. It's protected by + * |global_lock|. */ +static int urandom_fd = -2; /* urandom_buffering controls whether buffering is enabled (1) or not (0). This - * is protected by |once|. */ + * is protected by |global_lock|. */ static int urandom_buffering = 0; -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_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; } - 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; + urandom_fd = open("/dev/urandom", O_RDONLY); + return urandom_fd; } -void RAND_cleanup(void) {} +/* 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_set_urandom_fd(int fd) { - fd = dup(fd); - if (fd < 0) { - abort(); + CRYPTO_STATIC_MUTEX_lock_write(&global_lock); + while ((cur = list_head)) { + list_head = cur->next; + OPENSSL_free(cur); } - - 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. + if (urandom_fd >= 0) { + close(urandom_fd); } -} - -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; + urandom_fd = -2; + list_head = NULL; + CRYPTO_STATIC_MUTEX_unlock(&global_lock); } /* read_full reads exactly |len| bytes from |fd| into |out| and returns 1. In @@ -172,48 +138,110 @@ static char read_full(int fd, uint8_t *out, size_t len) { return 1; } -/* 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_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; + } - while (requested > remaining) { - memcpy(out, &buf->rand[buf->used], remaining); - buf->used += remaining; - out += remaining; - requested -= remaining; + CRYPTO_STATIC_MUTEX_lock_write(&global_lock); + fd = urandom_get_fd_locked(); - if (!read_full(urandom_fd, buf->rand, BUF_SIZE)) { + 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)) { abort(); - return; } - buf->used = 0; - remaining = BUF_SIZE; + return; } - memcpy(out, &buf->rand[buf->used], requested); - buf->used += requested; -} + pid = getpid(); + ppid = getppid(); -/* CRYPTO_sysrand puts |requested| random bytes into |out|. */ -void CRYPTO_sysrand(uint8_t *out, size_t requested) { - if (requested == 0) { - return; + 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); } - 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); + 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; + + if (buf->used < rand_bytes_per_buf) { + break; + } + + if (!read_full(fd, buf->rand, rand_bytes_per_buf)) { + OPENSSL_free(buf); + abort(); return; } - } - if (!read_full(urandom_fd, out, requested)) { - abort(); + 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); } #endif /* !OPENSSL_WINDOWS */ |