Skip to content

Commit

Permalink
Integrate ube for rand engine (#1897)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
torben-hansen authored Oct 11, 2024
1 parent a11fc48 commit ccb97ef
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 12 deletions.
87 changes: 77 additions & 10 deletions crypto/fipsmodule/rand/new_rand.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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;
};
Expand All @@ -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(&current_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(&current_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
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand All @@ -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);
}

Expand Down Expand Up @@ -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();
}
}
Expand Down Expand Up @@ -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;
}
3 changes: 3 additions & 0 deletions crypto/fipsmodule/rand/new_rand_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
46 changes: 45 additions & 1 deletion crypto/fipsmodule/rand/new_rand_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
#include <openssl/ctrdrbg.h>

#include "new_rand_internal.h"

#include "../../ube/internal.h"

// TODO
// Remove when promoting to default
Expand Down Expand Up @@ -39,5 +39,49 @@ TEST(NewRand, Basic) {
}
}

static void MockedUbeDetection(std::function<void(uint64_t)> 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<uint32_t>(gn));
}
);
}

#endif
2 changes: 1 addition & 1 deletion crypto/ube/ube.c
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down

0 comments on commit ccb97ef

Please sign in to comment.