Clear PRNG states in FIPS mode.

FIPS requires that the CTR-DRBG state be zeroed on process exit, however
destructors for thread-local data aren't called when the process exits.

This change maintains a linked-list of thread-local state which is
walked on exit to zero each thread's PRNG state. Any concurrently
running threads block until the process finishes exiting.

Change-Id: Ie5dc18e1bb2941a569d8b309411cf20c9bdf52ef
Reviewed-on: https://boringssl-review.googlesource.com/16764
Reviewed-by: David Benjamin <davidben@google.com>
Commit-Queue: David Benjamin <davidben@google.com>
CQ-Verified: CQ bot account: commit-bot@chromium.org <commit-bot@chromium.org>
This commit is contained in:
Adam Langley 2017-05-31 14:05:20 -07:00 committed by CQ bot account: commit-bot@chromium.org
parent d79bc9d397
commit 0ffc795efb
2 changed files with 99 additions and 4 deletions

View File

@ -18,12 +18,17 @@
#include <limits.h>
#include <string.h>
#if defined(BORINGSSL_FIPS)
#include <unistd.h>
#endif
#include <openssl/chacha.h>
#include <openssl/cpu.h>
#include <openssl/mem.h>
#include "internal.h"
#include "../../internal.h"
#include "../delocate.h"
/* It's assumed that the operating system always has an unfailing source of
@ -55,22 +60,66 @@ struct rand_thread_state {
/* calls is the number of generate calls made on |drbg| since it was last
* (re)seeded. This is bound by |kReseedInterval|. */
unsigned calls;
/* last_block contains the previous block from |CRYPTO_sysrand|. */
uint8_t last_block[CRNGT_BLOCK_SIZE];
/* last_block_valid is non-zero iff |last_block| contains data from
* |CRYPTO_sysrand|. */
int last_block_valid;
#if defined(BORINGSSL_FIPS)
/* last_block contains the previous block from |CRYPTO_sysrand|. */
uint8_t last_block[CRNGT_BLOCK_SIZE];
/* next and prev form a NULL-terminated, double-linked list of all states in
* a process. */
struct rand_thread_state *next, *prev;
#endif
};
#if defined(BORINGSSL_FIPS)
/* thread_states_list is the head of a linked-list of all |rand_thread_state|
* objects in the process, one per thread. This is needed because FIPS requires
* that they be zeroed on process exit, but thread-local destructors aren't
* called when the whole process is exiting. */
DEFINE_BSS_GET(struct rand_thread_state *, thread_states_list);
DEFINE_STATIC_MUTEX(thread_states_list_lock);
static void rand_thread_state_clear_all(void) __attribute__((destructor));
static void rand_thread_state_clear_all(void) {
CRYPTO_STATIC_MUTEX_lock_write(thread_states_list_lock_bss_get());
for (struct rand_thread_state *cur = *thread_states_list_bss_get();
cur != NULL; cur = cur->next) {
CTR_DRBG_clear(&cur->drbg);
}
/* |thread_states_list_lock is deliberately left locked so that any threads
* that are still running will hang if they try to call |RAND_bytes|. */
}
#endif
/* rand_thread_state_free frees a |rand_thread_state|. This is called when a
* thread exits. */
static void rand_thread_state_free(void *state_in) {
struct rand_thread_state *state = state_in;
if (state_in == NULL) {
return;
}
struct rand_thread_state *state = state_in;
#if defined(BORINGSSL_FIPS)
CRYPTO_STATIC_MUTEX_lock_write(thread_states_list_lock_bss_get());
if (state->prev != NULL) {
state->prev->next = state->next;
} else {
*thread_states_list_bss_get() = state->next;
}
if (state->next != NULL) {
state->next->prev = state->prev;
}
CRYPTO_STATIC_MUTEX_unlock_write(thread_states_list_lock_bss_get());
CTR_DRBG_clear(&state->drbg);
#endif
OPENSSL_free(state);
}
@ -202,8 +251,26 @@ void RAND_bytes_with_additional_data(uint8_t *out, size_t out_len,
abort();
}
state->calls = 0;
#if defined(BORINGSSL_FIPS)
if (state != &stack_state) {
CRYPTO_STATIC_MUTEX_lock_write(thread_states_list_lock_bss_get());
struct rand_thread_state **states_list = thread_states_list_bss_get();
state->next = *states_list;
if (state->next != NULL) {
state->next->prev = state;
}
state->prev = NULL;
*states_list = state;
CRYPTO_STATIC_MUTEX_unlock_write(thread_states_list_lock_bss_get());
}
#endif
}
#if defined(BORINGSSL_FIPS)
CRYPTO_STATIC_MUTEX_lock_read(thread_states_list_lock_bss_get());
#endif
if (state->calls >= kReseedInterval) {
uint8_t seed[CTR_DRBG_ENTROPY_LEN];
rand_get_seed(state, seed);
@ -256,6 +323,10 @@ void RAND_bytes_with_additional_data(uint8_t *out, size_t out_len,
CTR_DRBG_clear(&state->drbg);
}
#if defined(BORINGSSL_FIPS)
CRYPTO_STATIC_MUTEX_unlock_read(thread_states_list_lock_bss_get());
#endif
return;
}

View File

@ -15,6 +15,7 @@
#include "internal.h"
#include <openssl/crypto.h>
#include <openssl/rand.h>
#include <stdio.h>
@ -232,9 +233,32 @@ static int test_thread_local(void) {
return 1;
}
static void rand_state_test_thread(void) {
uint8_t buf[1];
RAND_bytes(buf, sizeof(buf));
}
static int test_rand_state(void) {
/* In FIPS mode, rand.c maintains a linked-list of thread-local data because
* we're required to clear it on process exit. This test exercises removing a
* value from that list. */
uint8_t buf[1];
RAND_bytes(buf, sizeof(buf));
thread_t thread;
if (!run_thread(&thread, rand_state_test_thread) ||
!wait_for_thread(thread)) {
fprintf(stderr, "thread failed.\n");
return 0;
}
return 1;
}
int main(int argc, char **argv) {
if (!test_once() ||
!test_thread_local()) {
!test_thread_local() ||
!test_rand_state()) {
return 1;
}