diff --git a/crypto/CMakeLists.txt b/crypto/CMakeLists.txt index fddb18a326..6bf9f9e7bd 100644 --- a/crypto/CMakeLists.txt +++ b/crypto/CMakeLists.txt @@ -502,6 +502,7 @@ add_library( trust_token/pmbtoken.c trust_token/trust_token.c trust_token/voprf.c + ube/ube.c x509/a_digest.c x509/a_sign.c x509/a_verify.c @@ -777,6 +778,11 @@ if(BUILD_TESTING) curve25519/spake25519_test.cc curve25519/x25519_test.cc ecdh_extra/ecdh_test.cc + decrepit/blowfish/blowfish_test.cc + decrepit/cast/cast_test.cc + decrepit/cfb/cfb_test.cc + decrepit/evp/evp_test.cc + decrepit/ripemd/ripemd_test.cc dh_extra/dh_test.cc digest_extra/digest_test.cc dilithium/p_dilithium_test.cc @@ -834,14 +840,10 @@ if(BUILD_TESTING) test/file_test_gtest.cc thread_test.cc trust_token/trust_token_test.cc + ube/ube_test.cc x509/tab_test.cc x509/x509_test.cc x509/x509_time_test.cc - decrepit/blowfish/blowfish_test.cc - decrepit/cast/cast_test.cc - decrepit/cfb/cfb_test.cc - decrepit/evp/evp_test.cc - decrepit/ripemd/ripemd_test.cc $ ) diff --git a/crypto/internal.h b/crypto/internal.h index 7c618ab77b..d9ca4c9269 100644 --- a/crypto/internal.h +++ b/crypto/internal.h @@ -720,6 +720,7 @@ typedef enum { AWSLC_THREAD_LOCAL_FIPS_SERVICE_INDICATOR_STATE, OPENSSL_THREAD_LOCAL_TEST, OPENSSL_THREAD_LOCAL_PRIVATE_RAND, + OPENSSL_THREAD_LOCAL_UBE, NUM_OPENSSL_THREAD_LOCALS, // Must be last entry in enum list } thread_local_data_t; diff --git a/crypto/ube/internal.h b/crypto/ube/internal.h new file mode 100644 index 0000000000..578f777d4e --- /dev/null +++ b/crypto/ube/internal.h @@ -0,0 +1,44 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 OR ISC + +#ifndef OPENSSL_HEADER_CRYPTO_UBE_INTERNAL_H +#define OPENSSL_HEADER_CRYPTO_UBE_INTERNAL_H + +#if defined(__cplusplus) +extern "C" { +#endif + +#include + +// get_ube_generation_number returns the uniqueness-breaking event (UBE) +// generation number for the address space in |current_generation_number|. The +// UBE generation number is a non-zero, strictly-monotonic counter with the +// following property: if queried in an address space and then subsequently +// queried, after an UBE occurred, a greater value will be observed. +// +// This function should be used to protect volatile memory. First cache a UBE +// generation number associating it to the volatile memory at +// creation/initialization time. Before using the volatile memory check whether +// the generation number has changed. Note, however, that detection methods rely +// on technology that is unique to a platform. Hence, support for UBE detection +// also depends on the platform AWS-LC is executed on. +// +// The parameter |current_generation_number| must be synchronized by the caller. +// +// Returns 1 on success and 0 if not supported. 0 means that UBE detection is +// not supported and any volatile state must randomize before usage. +// In case of an error or if UBE detection is unavailable, all subsequent +// entries will immediately return. +OPENSSL_EXPORT int CRYPTO_get_ube_generation_number(uint64_t *current_generation_number); + +// TODO +// Temporary overrides. Replace with something better. Used atm to test +// implementation during development. +OPENSSL_EXPORT void set_fork_generation_number_FOR_TESTING(uint64_t fork_gn); +OPENSSL_EXPORT void set_snapsafe_generation_number_FOR_TESTING(uint32_t snapsafe_gn); + +#if defined(__cplusplus) +} // extern C +#endif + +#endif // OPENSSL_HEADER_CRYPTO_UBE_INTERNAL_H diff --git a/crypto/ube/ube.c b/crypto/ube/ube.c new file mode 100644 index 0000000000..c16470acfa --- /dev/null +++ b/crypto/ube/ube.c @@ -0,0 +1,237 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 OR ISC + +#include + +#include "internal.h" +#include "../internal.h" + +// What is a uniqueness-breaking event (UBE)? +// AWS-LC manages state that must be unique on each usage. For example, the +// CTR-DRBG state must be unique on each usage, otherwise the generation +// function will produce duplicated values. We name such state "unique state" +// or "unique memory" if referring directly to memory. Before using a unique +// state, we must make sure to randomize it to preserve its uniqueness. +// +// When/how to randomize state depends on the context. For example, the CTR-DRBG +// state, that is managed by AWS-LC, will randomize itself before usage during +// normal operation. But there are events where this assumption is violated. We +// call such events "uniqueness breaking events" (UBE). Forking an address space +// is an example of an UBE. +// +// By detecting UBE's we can ensure that unique state is properly randomized. +// AWS-LC is currently able to detect two different type of UBE's that would +// violate usage requirements of a unique state. The two UBE types are: +// Forking and VM resuming. Note, however, that detection methods rely on +// technology that is unique to a platform. Hence, support for UBE detection +// also depends on the platform AWS-LC is executed on. +// +// This file implements and manages the general machinery to detect an UBE. This +// should be used by the rest of the code-base to implement proper randomization +// of unique states before usage. + +static CRYPTO_once_t ube_state_initialize_once = CRYPTO_ONCE_INIT; +static CRYPTO_once_t ube_detection_unavailable_once = CRYPTO_ONCE_INIT; +static struct CRYPTO_STATIC_MUTEX ube_lock = CRYPTO_STATIC_MUTEX_INIT; +static uint8_t ube_detection_unavailable = 0; + + +// TODO +// Temporary overrides to test detection code flow. +static uint64_t testing_fork_generation_number = 0; +void set_fork_generation_number_FOR_TESTING(uint64_t fork_gn) { + testing_fork_generation_number = fork_gn; +} +static uint32_t testing_snapsafe_generation_number = 0; +void set_snapsafe_generation_number_FOR_TESTING(uint32_t snapsafe_gn) { + testing_snapsafe_generation_number = snapsafe_gn; +} + +// TODO +// These two functions currently mocks the backend implementations of the fork +// detection method and snapsafe detection method. They will be reimplemented in +// a future change. +// +// Align signatures of these functions when integration real ones. +static int CRYPTO_get_snapsafe_generation_number_mocked(uint32_t *gn) { + *gn = 1; + if (testing_snapsafe_generation_number != 0) { + *gn = testing_snapsafe_generation_number; + } + return 1; +} +static uint64_t CRYPTO_get_fork_generation_number_mocked(void) { + if (testing_fork_generation_number != 0) { + return testing_fork_generation_number; + } + return 1; +} + + +// The UBE generation number is shared for the entire address space. One could +// implement a per-thread UBE generation number. However, this could be +// inefficient for global unique states if care is not taken. Because every +// thread only have visibility on their own per-thread generation number, we +// could trigger a new randomization of the unique state per-thread. While a +// single randomization of the global unique state should be sufficient. +struct ube_state { + uint64_t generation_number; + uint64_t cached_fork_gn; + uint32_t cached_snapsafe_gn; +}; +static struct ube_state ube_global_state = { 0, 0, 0 }; + +// Convenience object that makes it easier to extend the number of detection +// methods without having to modify function signatures. +struct detection_gn { +#define NUMBER_OF_DETECTION_GENERATION_NUMBERS 2 + uint64_t current_fork_gn; + uint32_t current_snapsafe_gn; +}; + +// set_ube_detection_unavailable_once is the single mutation point of +// |ube_detection_unavailable|. Sets the variable to 1 (true). +static void set_ube_detection_unavailable_once(void) { + ube_detection_unavailable = 1; +} + +// ube_failed is a convenience function to synchronize mutation of +// |ube_detection_unavailable|. In practice, synchronization is not strictly +// needed because currently the mutation is only ever assigning 1 to the +// variable. +static void ube_failed(void) { + CRYPTO_once(&ube_detection_unavailable_once, set_ube_detection_unavailable_once); +} + +// ube_state_initialize initializes the global state |ube_global_state|. +static void ube_state_initialize(void) { + + ube_global_state.generation_number = 0; + ube_global_state.cached_fork_gn = CRYPTO_get_fork_generation_number_mocked(); + int ret_snapsafe_gn = + CRYPTO_get_snapsafe_generation_number_mocked(&(ube_global_state.cached_snapsafe_gn)); + + if (ube_global_state.cached_fork_gn == 0 || + ret_snapsafe_gn == 0) { + ube_failed(); + } +} + +// ube_update_state updates the global state |ube_global_state| with the +// generation numbers loaded into |current_detection_gn| and increments the UBE +// generation number. +static void ube_update_state(struct detection_gn *current_detection_gn) { + + ube_global_state.generation_number += 1; + + // Make sure we cache all new generation numbers. Otherwise, we might detect + // a fork UBE but, in fact, both a fork and snapsafe UBE occurred. Then next + // time we enter, a redundant reseed will be emitted. + ube_global_state.cached_fork_gn = current_detection_gn->current_fork_gn; + ube_global_state.cached_snapsafe_gn = current_detection_gn->current_snapsafe_gn; +} + +// ube_get_detection_generation_numbers loads the current detection generation +// numbers into |current_detection_gn|. +// +// Returns 1 on success and 0 otherwise. The 0 return value means that a +// detection method we expected to be available, is in fact not. +static int ube_get_detection_generation_numbers( + struct detection_gn *current_detection_gn) { + + // An attempt to prevent a situation where new detection methods are added but + // we forget to load them. + OPENSSL_STATIC_ASSERT(NUMBER_OF_DETECTION_GENERATION_NUMBERS == 2, + not_handling_the_exact_number_of_detection_generation_numbers); + + current_detection_gn->current_fork_gn = CRYPTO_get_fork_generation_number_mocked(); + int ret_snapsafe_gn = CRYPTO_get_snapsafe_generation_number_mocked( + &(current_detection_gn->current_snapsafe_gn)); + + if (current_detection_gn->current_fork_gn == 0 || + ret_snapsafe_gn == 0) { + return 0; + } + + return 1; +} + +// ube_is_detected computes whether a UBE has been detected or not by comparing +// the cached detection generation numbers with the current detection generation +// numbers. The current generation numbers must be loaded into +// |current_detection_gn| before calling this function. +// +// Returns 1 if UBE has been detected and 0 if no UBE has been detected. +static int ube_is_detected(struct detection_gn *current_detection_gn) { + + if (ube_global_state.cached_fork_gn != current_detection_gn->current_fork_gn || + ube_global_state.cached_snapsafe_gn != current_detection_gn->current_snapsafe_gn) { + return 1; + } + return 0; +} + +int CRYPTO_get_ube_generation_number(uint64_t *current_generation_number) { + + GUARD_PTR(current_generation_number); + + CRYPTO_once(&ube_state_initialize_once, ube_state_initialize); + + // If something failed at an earlier point short-circuit immediately. This + // saves work in case the UBE detection is not supported. The check below + // must be done after attempting to initialize the UBE state. Because + // initialization might fail and we can short-circuit here. + if (ube_detection_unavailable == 1) { + return 0; + } + + struct detection_gn current_detection_gn = { 0, 0 }; + + // First read generation numbers for each supported detection method. We do + // not mutate |ube_global_state|. So, a read lock is sufficient at this point. + // Each individual detection method will have their own concurrency controls + // if needed. + + if (ube_get_detection_generation_numbers(¤t_detection_gn) != 1) { + ube_failed(); + return 0; + } + CRYPTO_STATIC_MUTEX_lock_read(&ube_lock); + if (ube_is_detected(¤t_detection_gn) == 0) { + // No UBE detected, so just grab UBE generation number from the state. + *current_generation_number = ube_global_state.generation_number; + CRYPTO_STATIC_MUTEX_unlock_read(&ube_lock); + return 1; + } + CRYPTO_STATIC_MUTEX_unlock_read(&ube_lock); + + // Reaching this point means that an UBE has been detected. We must now + // 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 + // 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. + + CRYPTO_STATIC_MUTEX_lock_write(&ube_lock); + if (ube_get_detection_generation_numbers(¤t_detection_gn) != 1) { + ube_failed(); + CRYPTO_STATIC_MUTEX_unlock_write(&ube_lock); + return 0; + } + if (ube_is_detected(¤t_detection_gn) == 0) { + // Another thread already updated the global state. Just load the UBE + // generation number instead. + *current_generation_number = ube_global_state.generation_number; + CRYPTO_STATIC_MUTEX_unlock_write(&ube_lock); + return 1; + } + + // Okay, we are really the first to update the state after detecting an UBE. + ube_update_state(¤t_detection_gn); + *current_generation_number = ube_global_state.generation_number; + CRYPTO_STATIC_MUTEX_unlock_write(&ube_lock); + + return 1; +} diff --git a/crypto/ube/ube_test.cc b/crypto/ube/ube_test.cc new file mode 100644 index 0000000000..2dc5f8217b --- /dev/null +++ b/crypto/ube/ube_test.cc @@ -0,0 +1,111 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 OR ISC + +#include + +#include "internal.h" + +TEST(Ube, BasicTests) { + uint64_t generation_number = 0; + ASSERT_TRUE(CRYPTO_get_ube_generation_number(&generation_number)); + + // Check stability. + uint64_t current_generation_number = generation_number + 1; + ASSERT_TRUE(CRYPTO_get_ube_generation_number(¤t_generation_number)); + ASSERT_EQ(current_generation_number, generation_number); + + // Check stability again. + current_generation_number = generation_number + 2; + ASSERT_TRUE(CRYPTO_get_ube_generation_number(¤t_generation_number)); + ASSERT_EQ(current_generation_number, generation_number); +} + +TEST(Ube, MockedMethodTests) { + uint64_t generation_number = 0; + uint64_t cached_generation_number = 0; + if (CRYPTO_get_ube_generation_number(&generation_number) == 0) { + // In this case, UBE detection is disabled, so just return + // successfully. The short-circuit feature means we can't mock detection + // methods. + return; + } + + // The fork generation number is initially 1. Use 10 because it's larger... + // Configuring specific values must change later on, since we might have tests + // running concurrently. + set_fork_generation_number_FOR_TESTING(10); + + // Generation number should have incremented once. + cached_generation_number = generation_number; + generation_number = 0; + ASSERT_TRUE(CRYPTO_get_ube_generation_number(&generation_number)); + ASSERT_EQ(generation_number, cached_generation_number + 1); + + // Should be stable again. + cached_generation_number = generation_number; + generation_number = 0; + ASSERT_TRUE(CRYPTO_get_ube_generation_number(&generation_number)); + ASSERT_EQ(generation_number, cached_generation_number); + + // Mock another process fork. We used 10 before. Hence, 11 should work. + set_fork_generation_number_FOR_TESTING(11); + + // Generation number should have incremented once. + cached_generation_number = generation_number; + generation_number = 0; + ASSERT_TRUE(CRYPTO_get_ube_generation_number(&generation_number)); + ASSERT_EQ(generation_number, cached_generation_number + 1); + + // Should be stable again. + cached_generation_number = generation_number; + generation_number = 0; + ASSERT_TRUE(CRYPTO_get_ube_generation_number(&generation_number)); + ASSERT_EQ(generation_number, cached_generation_number); + + // The snapsafe generation number is initially 1. Again use 10. + set_snapsafe_generation_number_FOR_TESTING(10); + + // Generation number should have incremented once. + cached_generation_number = generation_number; + generation_number = 0; + ASSERT_TRUE(CRYPTO_get_ube_generation_number(&generation_number)); + ASSERT_EQ(generation_number, cached_generation_number + 1); + + // Should be stable again. + cached_generation_number = generation_number; + generation_number = 0; + ASSERT_TRUE(CRYPTO_get_ube_generation_number(&generation_number)); + ASSERT_EQ(generation_number, cached_generation_number); + + // Mock another snapsafe event. We used 10 before. Hence, 11 should work. + set_snapsafe_generation_number_FOR_TESTING(11); + + // Generation number should have incremented once. + cached_generation_number = generation_number; + generation_number = 0; + ASSERT_TRUE(CRYPTO_get_ube_generation_number(&generation_number)); + ASSERT_EQ(generation_number, cached_generation_number + 1); + + // Should be stable again. + cached_generation_number = generation_number; + generation_number = 0; + ASSERT_TRUE(CRYPTO_get_ube_generation_number(&generation_number)); + ASSERT_EQ(generation_number, cached_generation_number); + + // Now try to increment both fork and snapsafe generation numbers. We expect + // to see one increment in the ube generation number and then stability. + set_snapsafe_generation_number_FOR_TESTING(20); + set_fork_generation_number_FOR_TESTING(20); + + // Check that ube generation number incremented once. + cached_generation_number = generation_number; + generation_number = 0; + ASSERT_TRUE(CRYPTO_get_ube_generation_number(&generation_number)); + ASSERT_EQ(generation_number, cached_generation_number + 1); + + // And that it's now stable. + cached_generation_number = generation_number; + generation_number = 0; + ASSERT_TRUE(CRYPTO_get_ube_generation_number(&generation_number)); + ASSERT_EQ(generation_number, cached_generation_number); +}