commit 155fa2dbdb78be0cbf73c9b200a295740584f24e Author: teor (Tim Wilson-Brown) teor2345@gmail.com Date: Thu Nov 26 21:25:31 2015 +1100
Add unit tests that check for common RNG failure modes
Check that crypto_rand doesn't return all zeroes, identical values, or incrementing values (OpenSSL's rand_predictable feature). --- changes/rand-failure-modes | 5 +++ src/test/test_crypto.c | 105 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+)
diff --git a/changes/rand-failure-modes b/changes/rand-failure-modes new file mode 100644 index 0000000..cc6ef47 --- /dev/null +++ b/changes/rand-failure-modes @@ -0,0 +1,5 @@ + o Minor features (unit tests, random number generation): + - Add unit tests that check for common RNG failure modes, such as + returning all zeroes, identical values, or incrementing values + (OpenSSL's rand_predictable feature). + Patch by "teor". diff --git a/src/test/test_crypto.c b/src/test/test_crypto.c index dbaec61..08347b2 100644 --- a/src/test/test_crypto.c +++ b/src/test/test_crypto.c @@ -1803,6 +1803,110 @@ test_crypto_siphash(void *arg) ; }
+/* We want the likelihood that the random buffer exhibits any regular pattern + * to be far less than the memory bit error rate in the int return value. + * Using 2048 bits provides a failure rate of 1/(3 * 10^616), and we call + * 3 functions, leading to an overall error rate of 1/10^616. + * This is comparable with the 1/10^603 failure rate of test_crypto_rng_range. + */ +#define FAILURE_MODE_BUFFER_SIZE (2048/8) + +/** Check crypto_rand for a failure mode where it does nothing to the buffer, + * or it sets the buffer to all zeroes. Return 0 when the check passes, + * or -1 when it fails. */ +static int +crypto_rand_check_failure_mode_zero(void) +{ + char buf[FAILURE_MODE_BUFFER_SIZE]; + + memset(buf, 0, FAILURE_MODE_BUFFER_SIZE); + crypto_rand(buf, FAILURE_MODE_BUFFER_SIZE); + + for (size_t i = 0; i < FAILURE_MODE_BUFFER_SIZE; i++) { + if (buf[i] != 0) { + return 0; + } + } + + return -1; +} + +/** Check crypto_rand for a failure mode where every int64_t in the buffer is + * the same. Return 0 when the check passes, or -1 when it fails. */ +static int +crypto_rand_check_failure_mode_identical(void) +{ + /* just in case the buffer size isn't a multiple of sizeof(int64_t) */ +#define FAILURE_MODE_BUFFER_SIZE_I64 \ + (FAILURE_MODE_BUFFER_SIZE/SIZEOF_INT64_T) +#define FAILURE_MODE_BUFFER_SIZE_I64_BYTES \ + (FAILURE_MODE_BUFFER_SIZE_I64*SIZEOF_INT64_T) + +#if FAILURE_MODE_BUFFER_SIZE_I64 < 2 +#error FAILURE_MODE_BUFFER_SIZE needs to be at least 2*SIZEOF_INT64_T +#endif + + int64_t buf[FAILURE_MODE_BUFFER_SIZE_I64]; + + memset(buf, 0, FAILURE_MODE_BUFFER_SIZE_I64_BYTES); + crypto_rand((char *)buf, FAILURE_MODE_BUFFER_SIZE_I64_BYTES); + + for (size_t i = 1; i < FAILURE_MODE_BUFFER_SIZE_I64; i++) { + if (buf[i] != buf[i-1]) { + return 0; + } + } + + return -1; +} + +/** Check crypto_rand for a failure mode where it increments the "random" + * value by 1 for every byte in the buffer. (This is OpenSSL's PREDICT mode.) + * Return 0 when the check passes, or -1 when it fails. */ +static int +crypto_rand_check_failure_mode_predict(void) +{ + unsigned char buf[FAILURE_MODE_BUFFER_SIZE]; + + memset(buf, 0, FAILURE_MODE_BUFFER_SIZE); + crypto_rand((char *)buf, FAILURE_MODE_BUFFER_SIZE); + + for (size_t i = 1; i < FAILURE_MODE_BUFFER_SIZE; i++) { + /* check if the last byte was incremented by 1, including integer + * wrapping */ + if (buf[i] - buf[i-1] != 1 && buf[i-1] - buf[i] != 255) { + return 0; + } + } + + return -1; +} + +#undef FAILURE_MODE_BUFFER_SIZE + +static void +test_crypto_failure_modes(void *arg) +{ + int rv = 0; + (void)arg; + + rv = crypto_early_init(); + tt_assert(rv == 0); + + /* Check random works */ + rv = crypto_rand_check_failure_mode_zero(); + tt_assert(rv == 0); + + rv = crypto_rand_check_failure_mode_identical(); + tt_assert(rv == 0); + + rv = crypto_rand_check_failure_mode_predict(); + tt_assert(rv == 0); + + done: + ; +} + #define CRYPTO_LEGACY(name) \ { #name, test_crypto_ ## name , 0, NULL, NULL }
@@ -1841,6 +1945,7 @@ struct testcase_t crypto_tests[] = { { "ed25519_fuzz_donna", test_crypto_ed25519_fuzz_donna, TT_FORK, NULL, NULL }, { "siphash", test_crypto_siphash, 0, NULL, NULL }, + { "failure_modes", test_crypto_failure_modes, TT_FORK, NULL, NULL }, END_OF_TESTCASES };