diff options
Diffstat (limited to 'src/test/rng_test_helpers.c')
-rw-r--r-- | src/test/rng_test_helpers.c | 259 |
1 files changed, 259 insertions, 0 deletions
diff --git a/src/test/rng_test_helpers.c b/src/test/rng_test_helpers.c new file mode 100644 index 0000000000..b7d7cb0dfa --- /dev/null +++ b/src/test/rng_test_helpers.c @@ -0,0 +1,259 @@ +/* Copyright (c) 2018-2020, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file rng_test_helpers.c + * \brief Helpers for overriding PRNGs during unit tests. + * + * We define two PRNG overrides: a "reproducible PRNG" where the seed is + * chosen randomly but the stream can be replayed later on in case a bug is + * found, and a "deterministic PRNG" where the seed is fixed in the unit + * tests. + * + * Obviously, this code is testing-only. + */ + +#include "orconfig.h" +#include "core/or/or.h" + +#include "lib/crypt_ops/crypto_rand.h" +#include "ext/tinytest.h" + +#include "test/rng_test_helpers.h" + +#ifndef TOR_UNIT_TESTS +#error "No. Never link this code into Tor proper." +#endif + +/** + * True iff the RNG is currently replaced. Prevents double-replacement. + **/ +static bool rng_is_replaced = false; + +/** + * Mutex to protect deterministic prng. + * + * Note that if you actually _use_ the prng from two threads at the same time, + * the results will probably be nondeterministic anyway. + */ +static tor_mutex_t *rng_mutex = NULL; + +/** + * Cached old value for the thread prng. + **/ +static crypto_fast_rng_t *stored_fast_rng = NULL; + +/** replacement for crypto_strongest_rand that delegates to crypto_rand. */ +static void +mock_crypto_strongest_rand(uint8_t *out, size_t len) +{ + crypto_rand((char *)out, len); +} + +/* This is the seed of the deterministic randomness. */ +static uint8_t rng_seed[16]; +static crypto_xof_t *rng_xof = NULL; + +/** + * Print the seed for our PRNG to stdout. We use this when we're failed + * test that had a reproducible RNG set. + **/ +void +testing_dump_reproducible_rng_seed(void) +{ + printf("\n" + "Seed: %s\n", + hex_str((const char*)rng_seed, sizeof(rng_seed))); +} + +/** Produce deterministic randomness for the stochastic tests using the global + * rng_xof output. + * + * This function produces deterministic data over multiple calls iff it's + * called in the same call order with the same 'n' parameter. + * If not, outputs will deviate. */ +static void +crypto_rand_deterministic(char *out, size_t n) +{ + tor_assert(rng_xof); + tor_mutex_acquire(rng_mutex); + crypto_xof_squeeze_bytes(rng_xof, (uint8_t*)out, n); + tor_mutex_release(rng_mutex); +} + +/** + * Implementation helper: override our crypto_rand() PRNG with a given seed of + * length <b>seed_len</b>. Overlong seeds are truncated; short ones are + * padded. + **/ +static void +enable_deterministic_rng_impl(const uint8_t *seed, size_t seed_len) +{ + tor_assert(!rng_is_replaced); + tor_assert(crypto_rand == crypto_rand__real); + + memset(rng_seed, 0, sizeof(rng_seed)); + memcpy(rng_seed, seed, MIN(seed_len, sizeof(rng_seed))); + + rng_mutex = tor_mutex_new(); + + crypto_xof_free(rng_xof); + rng_xof = crypto_xof_new(); + crypto_xof_add_bytes(rng_xof, rng_seed, sizeof(rng_seed)); + MOCK(crypto_rand, crypto_rand_deterministic); + MOCK(crypto_strongest_rand_, mock_crypto_strongest_rand); + + uint8_t fast_rng_seed[CRYPTO_FAST_RNG_SEED_LEN]; + memset(fast_rng_seed, 0xff, sizeof(fast_rng_seed)); + memcpy(fast_rng_seed, rng_seed, MIN(sizeof(rng_seed), + sizeof(fast_rng_seed))); + crypto_fast_rng_t *fast_rng = crypto_fast_rng_new_from_seed(fast_rng_seed); + crypto_fast_rng_disable_reseed(fast_rng); + stored_fast_rng = crypto_replace_thread_fast_rng(fast_rng); + + rng_is_replaced = true; +} + +/** + * Replace our get_thread_fast_rng(), crypto_rand() and + * crypto_strongest_rand() prngs with a variant that generates all of its + * output deterministically from a randomly chosen seed. In the event of an + * error, you can log the seed later on with + * testing_dump_reproducible_rng_seed. + **/ +void +testing_enable_reproducible_rng(void) +{ + const char *provided_seed = getenv("TOR_TEST_RNG_SEED"); + if (provided_seed) { + size_t hexlen = strlen(provided_seed); + size_t seedlen = hexlen / 2; + uint8_t *seed = tor_malloc(hexlen / 2); + if (base16_decode((char*)seed, seedlen, provided_seed, hexlen) < 0) { + puts("Cannot decode value in TOR_TEST_RNG_SEED"); + exit(1); + } + enable_deterministic_rng_impl(seed, seedlen); + tor_free(seed); + } else { + uint8_t seed[16]; + crypto_rand((char*)seed, sizeof(seed)); + enable_deterministic_rng_impl(seed, sizeof(seed)); + } +} + +/** + * Replace our get_thread_fast_rng(), crypto_rand() and + * crypto_strongest_rand() prngs with a variant that generates all of its + * output deterministically from a fixed seed. This variant is mainly useful + * for cases when we don't want coverage to change between runs. + * + * USAGE NOTE: Test correctness SHOULD NOT depend on the specific output of + * this "rng". If you need a specific output, use + * testing_enable_prefilled_rng() instead. + **/ +void +testing_enable_deterministic_rng(void) +{ + static const uint8_t quotation[] = + "What will it be? A tree? A weed? " + "Each one is started from a seed."; // -- Mary Ann Hoberman + enable_deterministic_rng_impl(quotation, sizeof(quotation)); +} + +static uint8_t *prefilled_rng_buffer = NULL; +static size_t prefilled_rng_buflen; +static size_t prefilled_rng_idx; + +/** + * crypto_rand() replacement that returns canned data. + **/ +static void +crypto_rand_prefilled(char *out, size_t n) +{ + tor_mutex_acquire(rng_mutex); + while (n) { + size_t n_to_copy = MIN(prefilled_rng_buflen - prefilled_rng_idx, n); + memcpy(out, prefilled_rng_buffer + prefilled_rng_idx, n_to_copy); + out += n_to_copy; + n -= n_to_copy; + prefilled_rng_idx += n_to_copy; + + if (prefilled_rng_idx == prefilled_rng_buflen) { + prefilled_rng_idx = 0; + } + } + tor_mutex_release(rng_mutex); +} + +/** + * Replace our crypto_rand() and crypto_strongest_rand() prngs with a variant + * that yields output from a buffer. If it reaches the end of the buffer, it + * starts over. + * + * Note: the get_thread_fast_rng() prng is not replaced by this; we'll need + * more code to support that. + **/ +void +testing_enable_prefilled_rng(const void *buffer, size_t buflen) +{ + tor_assert(buflen > 0); + tor_assert(!rng_mutex); + rng_mutex = tor_mutex_new(); + + tor_mutex_acquire(rng_mutex); + + prefilled_rng_buffer = tor_memdup(buffer, buflen); + prefilled_rng_buflen = buflen; + prefilled_rng_idx = 0; + + tor_mutex_release(rng_mutex); + + MOCK(crypto_rand, crypto_rand_prefilled); + MOCK(crypto_strongest_rand_, mock_crypto_strongest_rand); +} + +/** + * Reset the position in the prefilled RNG buffer to the start. + */ +void +testing_prefilled_rng_reset(void) +{ + tor_mutex_acquire(rng_mutex); + prefilled_rng_idx = 0; + tor_mutex_release(rng_mutex); +} + +/** + * Undo the overrides for our PRNG. To be used at the end of testing. + * + * Note that this function should be safe to call even if the rng has not + * yet been replaced. + **/ +void +testing_disable_rng_override(void) +{ + crypto_xof_free(rng_xof); + tor_free(prefilled_rng_buffer); + UNMOCK(crypto_rand); + UNMOCK(crypto_strongest_rand_); + tor_mutex_free(rng_mutex); + + crypto_fast_rng_t *rng = crypto_replace_thread_fast_rng(stored_fast_rng); + crypto_fast_rng_free(rng); + + rng_is_replaced = false; +} + +/** + * As testing_disable_rng_override(), but dump the seed if the current + * test has failed. + */ +void +testing_disable_reproducible_rng(void) +{ + if (tinytest_cur_test_has_failed()) { + testing_dump_reproducible_rng_seed(); + } + testing_disable_rng_override(); +} |