From ccb97eff2f2419d7f840f5fd62e35d25d09587a2 Mon Sep 17 00:00:00 2001 From: torben-hansen <50673096+torben-hansen@users.noreply.github.com> Date: Fri, 11 Oct 2024 16:16:19 -0700 Subject: [PATCH] Integrate ube for rand engine (#1897) This PR integrates protection of the thread-local state into the new randomness generation implementation. rand_ensure_ctr_drbg_uniquness() is the function that determines whether a randomization of the thread-local state is necessary. rand_ensure_ctr_drbg_uniquness() is called inline in the core randomness generation code path (in RAND_bytes_core() and invoked on every entry. The only mechanism currently implemented that can force a randomization is the UBE mechanism implemented in bc7aeff. Note that if UBE is "unavailable" then a randomization is forced every time. --- crypto/fipsmodule/rand/new_rand.c | 87 +++++++++++++++++++--- crypto/fipsmodule/rand/new_rand_internal.h | 3 + crypto/fipsmodule/rand/new_rand_test.cc | 46 +++++++++++- crypto/ube/ube.c | 2 +- 4 files changed, 126 insertions(+), 12 deletions(-) diff --git a/crypto/fipsmodule/rand/new_rand.c b/crypto/fipsmodule/rand/new_rand.c index 184b7b7f6d..7e4e38e8ad 100644 --- a/crypto/fipsmodule/rand/new_rand.c +++ b/crypto/fipsmodule/rand/new_rand.c @@ -9,6 +9,7 @@ #include "new_rand_internal.h" #include "internal.h" #include "../../internal.h" +#include "../../ube/internal.h" #include "new_rand_prefix.h" #include "entropy/internal.h" @@ -22,6 +23,13 @@ struct rand_thread_local_state { // since it was last (re)seeded. Must be bounded by |kReseedInterval|. uint64_t generate_calls_since_seed; + // reseed_calls_since_initialization is the number of reseed calls made of + // |drbg| since its initialization. + uint64_t reseed_calls_since_initialization; + + // generation_number caches the UBE generation number. + uint64_t generation_number; + // Entropy source. UBE volatile state. const struct entropy_source *entropy_source; }; @@ -44,18 +52,48 @@ static void rand_thread_local_state_free(void *state_in) { OPENSSL_free(state); } -// TODO -// Ensure snapsafe is in valid state -static int rand_ensure_valid_state(void) { +// rand_ensure_valid_state determines whether |state| is in a valid state. The +// reasons are documented with inline comments in the function. +// +// Returns 1 if |state| is in a valid state and 0 otherwise. +static int rand_ensure_valid_state(struct rand_thread_local_state *state) { + + // We do not allow the UBE generation number to change while executing AWS-LC + // randomness generation code e.g. while |RAND_bytes| executes. One way to hit + // this error is if snapshotting the address space while executing + // |RAND_bytes| and while snapsafe is active. + uint64_t current_generation_number = 0; + if (CRYPTO_get_ube_generation_number(¤t_generation_number) == 1 && + current_generation_number != state->generation_number) { + return 0; + } + return 1; } -// TODO -// For UBE. -static int rand_ensure_ctr_drbg_uniquness(struct rand_thread_local_state *state, - size_t out_len) { +// rand_ensure_ctr_drbg_uniqueness computes whether |state| must be randomized +// to ensure uniqueness. +// +// Note: If |rand_ensure_ctr_drbg_uniqueness| returns 1 it does not necessarily +// imply that an UBE occurred. It can also mean that no UBE detection is +// supported or that UBE detection failed. In these cases, |state| must also be +// randomized to ensure uniqueness. Any special future cases can be handled in +// this function. +// +// Return 1 if |state| must be randomized. 0 otherwise. +static int rand_ensure_ctr_drbg_uniqueness(struct rand_thread_local_state *state) { + + uint64_t current_generation_number = 0; + if (CRYPTO_get_ube_generation_number(¤t_generation_number) != 1) { + return 1; + } - return 1; + if (current_generation_number != state->generation_number) { + state->generation_number = current_generation_number; + return 1; + } + + return 0; } // rand_maybe_get_ctr_drbg_pred_resistance maybe fills |pred_resistance| with @@ -136,6 +174,7 @@ static void rand_ctr_drbg_reseed(struct rand_thread_local_state *state) { abort(); } + state->reseed_calls_since_initialization++; state->generate_calls_since_seed = 0; OPENSSL_cleanse(seed, CTR_DRBG_ENTROPY_LEN); @@ -167,7 +206,9 @@ static void rand_state_initialize(struct rand_thread_local_state *state) { abort(); } + state->reseed_calls_since_initialization = 0; state->generate_calls_since_seed = 0; + state->generation_number = 0; OPENSSL_cleanse(seed, CTR_DRBG_ENTROPY_LEN); OPENSSL_cleanse(personalization_string, CTR_DRBG_ENTROPY_LEN); @@ -191,7 +232,7 @@ static void RAND_bytes_core( GUARD_PTR_ABORT(out); // Ensure the CTR-DRBG state is unique. - if (rand_ensure_ctr_drbg_uniquness(state, out_len) != 1) { + if (rand_ensure_ctr_drbg_uniqueness(state) == 1) { rand_ctr_drbg_reseed(state); } @@ -252,7 +293,7 @@ static void RAND_bytes_core( OPENSSL_cleanse(pred_resistance, RAND_PRED_RESISTANCE_LEN); - if (rand_ensure_valid_state() != 1) { + if (rand_ensure_valid_state(state) != 1) { abort(); } } @@ -316,3 +357,29 @@ int NR_PREFIX(RAND_priv_bytes)(uint8_t *out, size_t out_len) { int NR_PREFIX(RAND_pseudo_bytes)(uint8_t *out, size_t out_len) { return NR_PREFIX(RAND_bytes)(out, out_len); } + +// Returns the number of generate calls made on the thread-local state since +// last seed/reseed. Returns 0 if thread-local state has not been initialized. +uint64_t get_thread_generate_calls_since_seed(void) { + + struct rand_thread_local_state *state = + CRYPTO_get_thread_local(OPENSSL_THREAD_LOCAL_PRIVATE_RAND); + if (state == NULL) { + return 0; + } + + return state->generate_calls_since_seed; +} + +// Returns the number of reseed calls made on the thread-local state since +// initialization. Returns 0 if thread-local state has not been initialized. +uint64_t get_thread_reseed_calls_since_initialization(void) { + + struct rand_thread_local_state *state = + CRYPTO_get_thread_local(OPENSSL_THREAD_LOCAL_PRIVATE_RAND); + if (state == NULL) { + return 0; + } + + return state->reseed_calls_since_initialization; +} diff --git a/crypto/fipsmodule/rand/new_rand_internal.h b/crypto/fipsmodule/rand/new_rand_internal.h index 2ff4594b88..e0d1ebb6d4 100644 --- a/crypto/fipsmodule/rand/new_rand_internal.h +++ b/crypto/fipsmodule/rand/new_rand_internal.h @@ -29,6 +29,9 @@ OPENSSL_EXPORT int NR_PREFIX(RAND_pseudo_bytes)(uint8_t *out, size_t out_len); OPENSSL_EXPORT int RAND_bytes_with_user_prediction_resistance(uint8_t *out, size_t out_len, const uint8_t user_pred_resistance[RAND_PRED_RESISTANCE_LEN]); +OPENSSL_EXPORT uint64_t get_thread_generate_calls_since_seed(void); +OPENSSL_EXPORT uint64_t get_thread_reseed_calls_since_initialization(void); + #if defined(__cplusplus) } // extern C #endif diff --git a/crypto/fipsmodule/rand/new_rand_test.cc b/crypto/fipsmodule/rand/new_rand_test.cc index 919560a71c..336c7b19a7 100644 --- a/crypto/fipsmodule/rand/new_rand_test.cc +++ b/crypto/fipsmodule/rand/new_rand_test.cc @@ -6,7 +6,7 @@ #include #include "new_rand_internal.h" - +#include "../../ube/internal.h" // TODO // Remove when promoting to default @@ -39,5 +39,49 @@ TEST(NewRand, Basic) { } } +static void MockedUbeDetection(std::function set_detection_method_gn) { + + const size_t request_size_one_generate = 10; + const size_t request_size_two_generate = CTR_DRBG_MAX_GENERATE_LENGTH + 1; + uint64_t current_reseed_calls = 0; + uint8_t randomness[MAX_REQUEST_SIZE] = {0}; + + // Make sure things are initialized and at default values. Cache + // current_reseed_calls last in case RAND_bytes() invokes a reseed. + set_detection_method_gn(1); + ASSERT_TRUE(RAND_bytes(randomness, request_size_one_generate)); + current_reseed_calls = get_thread_reseed_calls_since_initialization(); + + // Bump fork generation number and expect one reseed. In addition, expect one + // generate call since request size is less than CTR_DRBG_MAX_GENERATE_LENGTH. + set_detection_method_gn(2); + ASSERT_TRUE(RAND_bytes(randomness, request_size_one_generate)); + ASSERT_EQ(get_thread_reseed_calls_since_initialization(), current_reseed_calls + 1ULL); + ASSERT_EQ(get_thread_generate_calls_since_seed(), 1ULL); + + // Bump fork generation number again and expect one reseed. In addition, + // expect two generate call since request size is higher than + // CTR_DRBG_MAX_GENERATE_LENGTH. + set_detection_method_gn(3); + ASSERT_TRUE(RAND_bytes(randomness, request_size_two_generate)); + ASSERT_EQ(get_thread_reseed_calls_since_initialization(), current_reseed_calls + 2ULL); + ASSERT_EQ(get_thread_generate_calls_since_seed(), 2ULL); +} + +TEST(NewRand, UbeDetectionForkMocked) { + MockedUbeDetection( + [](uint64_t gn) { + set_fork_generation_number_FOR_TESTING(gn); + } + ); +} + +TEST(NewRand, UbeDetectionSnapsafeMocked) { + MockedUbeDetection( + [](uint64_t gn) { + set_snapsafe_generation_number_FOR_TESTING(static_cast(gn)); + } + ); +} #endif diff --git a/crypto/ube/ube.c b/crypto/ube/ube.c index c16470acfa..94045cae44 100644 --- a/crypto/ube/ube.c +++ b/crypto/ube/ube.c @@ -209,7 +209,7 @@ int CRYPTO_get_ube_generation_number(uint64_t *current_generation_number) { // synchronize an update to the UBE generation number. To avoid redundant // reseeds, we must ensure the generation number is only incremented once for // all UBE's that might have happened. Therefore, first take a write lock but - // before mutation the state, check for an UBE again. Checking again ensures + // before mutating the state, check for an UBE again. Checking again ensures // that only one thread increments the UBE generation number, because the // cached detection method generation numbers have been updated by the thread // that had the first entry.