diff --git a/crypto/CMakeLists.txt b/crypto/CMakeLists.txt index b8be55400d..9efccc4486 100644 --- a/crypto/CMakeLists.txt +++ b/crypto/CMakeLists.txt @@ -499,6 +499,8 @@ add_library( trust_token/trust_token.c trust_token/voprf.c ube/ube.c + ube/fork_detect.c + ube/snapsafe_detect.c x509/a_digest.c x509/a_sign.c x509/a_verify.c @@ -799,8 +801,6 @@ if(BUILD_TESTING) fipsmodule/pbkdf/pbkdf_test.cc fipsmodule/rand/ctrdrbg_test.cc fipsmodule/rand/cpu_jitter_test.cc - fipsmodule/rand/fork_detect_test.cc - fipsmodule/rand/snapsafe_detect_test.cc fipsmodule/rand/new_rand_test.cc fipsmodule/service_indicator/service_indicator_test.cc fipsmodule/sha/sha_test.cc @@ -837,6 +837,8 @@ if(BUILD_TESTING) thread_test.cc trust_token/trust_token_test.cc ube/ube_test.cc + ube/fork_detect_test.cc + ube/snapsafe_detect_test.cc x509/tab_test.cc x509/x509_test.cc x509/x509_time_test.cc diff --git a/crypto/fipsmodule/bcm.c b/crypto/fipsmodule/bcm.c index 9477b9061a..7e6696e083 100644 --- a/crypto/fipsmodule/bcm.c +++ b/crypto/fipsmodule/bcm.c @@ -140,9 +140,7 @@ #include "modes/polyval.c" #include "pbkdf/pbkdf.c" #include "rand/ctrdrbg.c" -#include "rand/fork_detect.c" #include "rand/rand.c" -#include "rand/snapsafe_detect.c" #include "rand/urandom.c" #include "rsa/blinding.c" #include "rsa/padding.c" diff --git a/crypto/fipsmodule/rand/new_rand_test.cc b/crypto/fipsmodule/rand/new_rand_test.cc index 90b835a046..7be67079ad 100644 --- a/crypto/fipsmodule/rand/new_rand_test.cc +++ b/crypto/fipsmodule/rand/new_rand_test.cc @@ -9,6 +9,9 @@ #include "new_rand_internal.h" #include "../../ube/internal.h" +#include "../../test/ube_test.h" +#include "../../test/test_util.h" + #include // TODO @@ -20,6 +23,8 @@ #define MAX_REQUEST_SIZE (CTR_DRBG_MAX_GENERATE_LENGTH * 2 + 1) +class newRandTest : public ubeTest {}; + static void randBasicTests(bool *returnFlag) { // Do not use stack arrays for these. For example, Alpine OS has too low // default thread stack size limit to accommodate. @@ -46,27 +51,11 @@ static void randBasicTests(bool *returnFlag) { *returnFlag = true; } -TEST(NewRand, Basic) { -#if defined(OPENSSL_THREADS) - constexpr size_t kNumThreads = 10; - bool myFlags[kNumThreads] = {false}; - std::thread myThreads[kNumThreads]; - - for (size_t i = 0; i < kNumThreads; i++) { - myThreads[i] = std::thread(randBasicTests, &myFlags[i]); - } - for (size_t i = 0; i < kNumThreads; i++) { - myThreads[i].join(); - ASSERT_TRUE(myFlags[i]) << "Thread " << i << " failed."; - } -#else - bool myFlag = false; - randBasicTests(&myFlag); - ASSERT_TRUE(myFlag); -#endif +TEST_F(newRandTest, Basic) { + ASSERT_TRUE(threadTest(10, randBasicTests)); } -TEST(NewRand, ReseedInterval) { +static void randReseedIntervalUbeIsSupportedTests(bool *returnFlag) { uint8_t *randomness = (uint8_t *) OPENSSL_zalloc(CTR_DRBG_MAX_GENERATE_LENGTH * 5 + 1); bssl::UniquePtr deleter(randomness); uint64_t reseed_calls_since_initialization = get_thread_reseed_calls_since_initialization(); @@ -103,6 +92,48 @@ TEST(NewRand, ReseedInterval) { ASSERT_TRUE(RAND_bytes(randomness, request_len_new_reseed)); ASSERT_EQ(get_thread_reseed_calls_since_initialization(), reseed_calls_since_initialization + 2); ASSERT_EQ(get_thread_generate_calls_since_seed(), 1ULL); + + *returnFlag = true; +} + +TEST_F(newRandTest, ReseedIntervalWhenUbeIsSupported) { + if (!UbeIsSupported()) { + GTEST_SKIP() << "UBE detection is not supported"; + } + ASSERT_TRUE(threadTest(10, randReseedIntervalUbeIsSupportedTests)); +} + +static void randReseedIntervalUbeNotSupportedTests(bool *returnFlag) { + uint8_t *randomness = (uint8_t *) OPENSSL_zalloc(CTR_DRBG_MAX_GENERATE_LENGTH); + bssl::UniquePtr deleter(randomness); + uint64_t generate_calls_since_seed = get_thread_generate_calls_since_seed(); + uint64_t reseed_calls_since_initialization = get_thread_reseed_calls_since_initialization(); + + if (kCtrDrbgReseedInterval - generate_calls_since_seed < 2) { + // Ensure the reseed interval doesn't conflict with logic below. + ASSERT_TRUE(RAND_bytes(randomness, 1)); + ASSERT_TRUE(RAND_bytes(randomness, 1)); + } + + // Each invocation of the randomness generation induce a reseed due to UBE + // detection not being supported. + ASSERT_TRUE(RAND_bytes(randomness, 1)); + ASSERT_EQ(get_thread_generate_calls_since_seed(), 1ULL); + ASSERT_EQ(get_thread_reseed_calls_since_initialization(), reseed_calls_since_initialization + 1); + + ASSERT_TRUE(RAND_bytes(randomness, 1)); + ASSERT_EQ(get_thread_generate_calls_since_seed(), 1ULL); + ASSERT_EQ(get_thread_reseed_calls_since_initialization(), reseed_calls_since_initialization + 2); + + *returnFlag = true; +} + +TEST_F(newRandTest, ReseedIntervalWhenUbeNotSupported) { + + if (UbeIsSupported()) { + GTEST_SKIP() << "UBE detection is supported"; + } + ASSERT_TRUE(threadTest(10, randReseedIntervalUbeNotSupportedTests)); } static void MockedUbeDetection(std::function set_detection_method_gn) { @@ -135,15 +166,16 @@ static void MockedUbeDetection(std::function set_detection_metho ASSERT_EQ(get_thread_generate_calls_since_seed(), 2ULL); } -TEST(NewRand, UbeDetectionForkMocked) { +TEST_F(newRandTest, UbeDetectionMocked) { + + allowMockedUbe(); + 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)); diff --git a/crypto/fipsmodule/rand/rand.c b/crypto/fipsmodule/rand/rand.c index 81d861e56f..ce900ccdd6 100644 --- a/crypto/fipsmodule/rand/rand.c +++ b/crypto/fipsmodule/rand/rand.c @@ -34,8 +34,8 @@ #include #include "internal.h" -#include "fork_detect.h" -#include "snapsafe_detect.h" +#include "../../ube/fork_detect.h" +#include "../../ube/snapsafe_detect.h" #include "../../internal.h" #include "../delocate.h" diff --git a/crypto/fipsmodule/rand/urandom_test.cc b/crypto/fipsmodule/rand/urandom_test.cc index 91a0cfc487..8c67b2a733 100644 --- a/crypto/fipsmodule/rand/urandom_test.cc +++ b/crypto/fipsmodule/rand/urandom_test.cc @@ -20,7 +20,7 @@ #include "getrandom_fillin.h" #include "internal.h" -#include "snapsafe_detect.h" +#include "../../ube/snapsafe_detect.h" #if defined(OPENSSL_X86_64) && !defined(BORINGSSL_SHARED_LIBRARY) && \ !defined(BORINGSSL_UNSAFE_DETERMINISTIC_MODE) && \ @@ -33,7 +33,7 @@ #include #include -#include "fork_detect.h" +#include "../../ube/fork_detect.h" #include "getrandom_fillin.h" #include @@ -610,7 +610,7 @@ int main(int argc, char **argv) { ::testing::InitGoogleTest(&argc, argv); if (getenv("BORINGSSL_IGNORE_MADV_WIPEONFORK")) { - CRYPTO_fork_detect_ignore_madv_wipeonfork_for_testing(); + CRYPTO_fork_detect_ignore_madv_wipeonfork_FOR_TESTING(); } return RUN_ALL_TESTS(); diff --git a/crypto/fipsmodule/rsa/rsa_impl.c b/crypto/fipsmodule/rsa/rsa_impl.c index e88f14c7af..1884b61a33 100644 --- a/crypto/fipsmodule/rsa/rsa_impl.c +++ b/crypto/fipsmodule/rsa/rsa_impl.c @@ -70,7 +70,7 @@ #include "../bn/internal.h" #include "../../internal.h" #include "../delocate.h" -#include "../rand/fork_detect.h" +#include "../../ube/fork_detect.h" static int ensure_fixed_copy(BIGNUM **out, const BIGNUM *in, int width) { if (*out != NULL) { diff --git a/crypto/internal.h b/crypto/internal.h index 261aa3bd41..e3a168f15f 100644 --- a/crypto/internal.h +++ b/crypto/internal.h @@ -115,7 +115,7 @@ #include #include -#include "fipsmodule/rand/snapsafe_detect.h" +#include "ube/snapsafe_detect.h" #include #include diff --git a/crypto/rand_extra/rand_test.cc b/crypto/rand_extra/rand_test.cc index 8c3246218d..45ee613931 100644 --- a/crypto/rand_extra/rand_test.cc +++ b/crypto/rand_extra/rand_test.cc @@ -20,7 +20,7 @@ #include -#include "../fipsmodule/rand/fork_detect.h" +#include "../ube/fork_detect.h" #include "../fipsmodule/rand/internal.h" #include "../test/abi_test.h" #include "../test/test_util.h" @@ -41,7 +41,7 @@ static void maybe_disable_some_fork_detect_mechanisms(void) { #if defined(OPENSSL_LINUX) if (getenv("BORINGSSL_IGNORE_MADV_WIPEONFORK")) { - CRYPTO_fork_detect_ignore_madv_wipeonfork_for_testing(); + CRYPTO_fork_detect_ignore_madv_wipeonfork_FOR_TESTING(); } #endif } diff --git a/crypto/test/test_util.cc b/crypto/test/test_util.cc index a0e923eb92..611ddd229f 100644 --- a/crypto/test/test_util.cc +++ b/crypto/test/test_util.cc @@ -14,7 +14,9 @@ #include "test_util.h" +#include #include +#include #include "../internal.h" #include "openssl/pem.h" @@ -156,3 +158,70 @@ void CustomDataFree(void *parent, void *ptr, CRYPTO_EX_DATA *ad, free(ptr); } +bool osIsAmazonLinux(void) { + bool res = false; +#if defined(OPENSSL_LINUX) + // Per https://docs.aws.amazon.com/linux/al2023/ug/naming-and-versioning.html. + std::ifstream amazonLinuxSpecificFile("/etc/amazon-linux-release-cpe"); + if (amazonLinuxSpecificFile.is_open()) { + // Definitely on Amazon Linux. + amazonLinuxSpecificFile.close(); + return true; + } + + // /etc/amazon-linux-release-cpe was introduced in AL2023. For earlier, parse + // and read /etc/system-release-cpe. + std::ifstream osRelease("/etc/system-release-cpe"); + if (!osRelease.is_open()) { + return false; + } + + std::string line; + while (std::getline(osRelease, line)) { + // AL2: + // $ cat /etc/system-release-cpe + // cpe:2.3:o:amazon:amazon_linux:2 + // + // AL2023: + // $ cat /etc/system-release-cpe + // cpe:2.3:o:amazon:amazon_linux:2023 + if (line.find("amazon") != std::string::npos) { + res = true; + } else if (line.find("amazon_linux") != std::string::npos) { + res = true; + } + } + osRelease.close(); +#endif + return res; +} + +bool threadTest(const size_t numberOfThreads, std::function testFunc) { + bool res = true; + +#if defined(OPENSSL_THREADS) + // char to be able to pass-as-reference. + std::vector retValueVec(numberOfThreads, 0); + std::vector threadVec; + + for (size_t i = 0; i < numberOfThreads; i++) { + threadVec.emplace_back(testFunc, reinterpret_cast(&retValueVec[i])); + } + + for (auto& thread : threadVec) { + thread.join(); + } + + for (size_t i = 0; i < numberOfThreads; i++) { + if (!static_cast(retValueVec[i])) { + fprintf(stderr, "Thread %lu failed\n", (long unsigned int) i); + res = false; + } + } + +#else + testFunc(&res); +#endif + + return res; +} diff --git a/crypto/test/test_util.h b/crypto/test/test_util.h index 4f53149602..637915c965 100644 --- a/crypto/test/test_util.h +++ b/crypto/test/test_util.h @@ -20,8 +20,10 @@ #include #include +#include #include #include +#include #include #include @@ -103,6 +105,16 @@ size_t createTempFILEpath(char buffer[PATH_MAX]); FILE* createRawTempFILE(); TempFILE createTempFILE(); +// Returns true if operating system is Amazon Linux and false otherwise. +// Determined at run-time and requires read-permissions to /etc. +bool osIsAmazonLinux(void); + +// Executes |testFunc| simultaneously in |numberThreads| number of threads. If +// OPENSSL_THREADS is not defined, executes |testFunc| a single time +// non-concurrently. +bool threadTest(const size_t numberOfThreads, + std::function testFunc); + // CustomData is for testing new structs that we add support for |ex_data|. typedef struct { int custom_data; diff --git a/crypto/test/ube_test.h b/crypto/test/ube_test.h new file mode 100644 index 0000000000..da04960eb3 --- /dev/null +++ b/crypto/test/ube_test.h @@ -0,0 +1,34 @@ + +#ifndef OPENSSL_HEADER_CRYPTO_TEST_UBE_TEST_H +#define OPENSSL_HEADER_CRYPTO_TEST_UBE_TEST_H + +#include + +#include "../ube/internal.h" + +class ubeTest : public::testing::Test { + public: + void SetUp() override { + uint64_t current_generation_number = 0; + if (CRYPTO_get_ube_generation_number(¤t_generation_number) == 1) { + ube_detection_supported_ = true; + } + } + + void TearDown() override { + disable_mocked_ube_detection_FOR_TESTING(); + } + + protected: + bool UbeIsSupported(void) const { + return ube_detection_supported_; + } + + void allowMockedUbe(void) const { + allow_mocked_ube_detection_FOR_TESTING(); + } + + bool ube_detection_supported_ = false; +}; + +#endif // OPENSSL_HEADER_CRYPTO_TEST_UBE_TEST_H diff --git a/crypto/fipsmodule/rand/fork_detect.c b/crypto/ube/fork_detect.c similarity index 74% rename from crypto/fipsmodule/rand/fork_detect.c rename to crypto/ube/fork_detect.c index e600a786d7..0bc7c92a62 100644 --- a/crypto/fipsmodule/rand/fork_detect.c +++ b/crypto/ube/fork_detect.c @@ -27,8 +27,7 @@ #include -#include "../delocate.h" -#include "../../internal.h" +#include "../internal.h" #if defined(MADV_WIPEONFORK) @@ -37,13 +36,33 @@ OPENSSL_STATIC_ASSERT(MADV_WIPEONFORK == 18, MADV_WIPEONFORK_is_not_18) #define MADV_WIPEONFORK 18 #endif -DEFINE_STATIC_ONCE(g_fork_detect_once) -DEFINE_STATIC_MUTEX(g_fork_detect_lock) -DEFINE_BSS_GET(volatile char *, g_fork_detect_addr) -DEFINE_BSS_GET(uint64_t, g_fork_generation) -DEFINE_BSS_GET(int, g_ignore_madv_wipeonfork) +static CRYPTO_once_t fork_detect_once = CRYPTO_ONCE_INIT; +static struct CRYPTO_STATIC_MUTEX fork_detect_lock = CRYPTO_STATIC_MUTEX_INIT; -static int init_fork_detect_madv_wipeonfork(void *addr, long page_size) { +// This pointer is |volatile| because the value pointed to may be changed by +// external forces (i.e. the kernel wiping the page) thus the compiler must not +// assume that it has exclusive access to it. +static volatile char *fork_detect_addr = NULL; +static uint64_t fork_generation = 0; +static int ignore_madv_wipeonfork = 0; + +static int init_fork_detect_madv_wipeonfork(void **addr_out) { + + void *addr = MAP_FAILED; + long page_size = 0; + + GUARD_PTR(addr_out); + + page_size = sysconf(_SC_PAGESIZE); + if (page_size <= 0) { + return 0; + } + + addr = mmap(NULL, (size_t)page_size, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (addr == MAP_FAILED) { + return 0; + } // Some versions of qemu (up to at least 5.0.0-rc4, see linux-user/syscall.c) // ignore |madvise| calls and just return zero (i.e. success). But we need to @@ -53,51 +72,31 @@ static int init_fork_detect_madv_wipeonfork(void *addr, long page_size) { if (madvise(addr, (size_t)page_size, -1) == 0 || madvise(addr, (size_t)page_size, MADV_WIPEONFORK) != 0) { // The mapping |addr| points to is unmapped by caller. + munmap(addr, (size_t)page_size); return 0; } + *addr_out = addr; return 1; } static void init_fork_detect(void) { - int res = 0; - void *addr = MAP_FAILED; - long page_size = 0; + void *addr = NULL; // Check whether we are completely ignoring fork detection. This is only done // during testing. - if (*g_ignore_madv_wipeonfork_bss_get() == 1) { - goto cleanup; - } - - page_size = sysconf(_SC_PAGESIZE); - if (page_size <= 0) { - goto cleanup; - } - - addr = mmap(NULL, (size_t)page_size, PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); - if (addr == MAP_FAILED) { - goto cleanup; + if (ignore_madv_wipeonfork == 1) { + return; } - - if (init_fork_detect_madv_wipeonfork(addr, page_size) == 0) { - goto cleanup; + if (init_fork_detect_madv_wipeonfork(&addr) != 1) { + return; } *((volatile char *) addr) = 1; - *g_fork_detect_addr_bss_get() = addr; - *g_fork_generation_bss_get() = 1; - - res = 1; - -cleanup: - if (res == 0 && addr != MAP_FAILED) { - munmap(addr, (size_t)page_size); - addr = NULL; - } + fork_detect_addr = addr; + fork_generation = 1; } uint64_t CRYPTO_get_fork_generation(void) { @@ -112,22 +111,18 @@ uint64_t CRYPTO_get_fork_generation(void) { // process is running. (For example, because a VM was cloned.) Therefore a // lock is used below to synchronise the potentially multiple threads that may // concurrently observe the cleared flag. + CRYPTO_once(&fork_detect_once, init_fork_detect); - CRYPTO_once(g_fork_detect_once_bss_get(), init_fork_detect); - // This pointer is |volatile| because the value pointed to may be changed by - // external forces (i.e. the kernel wiping the page) thus the compiler must - // not assume that it has exclusive access to it. - volatile char *const flag_ptr = *g_fork_detect_addr_bss_get(); + volatile char *const flag_ptr = fork_detect_addr; if (flag_ptr == NULL) { // Our kernel is too old to support |MADV_WIPEONFORK|. return 0; } - struct CRYPTO_STATIC_MUTEX *const lock = g_fork_detect_lock_bss_get(); - uint64_t *const generation_ptr = g_fork_generation_bss_get(); + struct CRYPTO_STATIC_MUTEX *const lock = &fork_detect_lock; CRYPTO_STATIC_MUTEX_lock_read(lock); - uint64_t current_generation = *generation_ptr; + uint64_t current_generation = fork_generation; if (*flag_ptr) { CRYPTO_STATIC_MUTEX_unlock_read(lock); return current_generation; @@ -135,7 +130,7 @@ uint64_t CRYPTO_get_fork_generation(void) { CRYPTO_STATIC_MUTEX_unlock_read(lock); CRYPTO_STATIC_MUTEX_lock_write(lock); - current_generation = *generation_ptr; + current_generation = fork_generation; if (*flag_ptr == 0) { // A fork has occurred. *flag_ptr = 1; @@ -144,15 +139,15 @@ uint64_t CRYPTO_get_fork_generation(void) { if (current_generation == 0) { current_generation = 1; } - *generation_ptr = current_generation; + fork_generation = current_generation; } CRYPTO_STATIC_MUTEX_unlock_write(lock); return current_generation; } -void CRYPTO_fork_detect_ignore_madv_wipeonfork_for_testing(void) { - *g_ignore_madv_wipeonfork_bss_get() = 1; +void CRYPTO_fork_detect_ignore_madv_wipeonfork_FOR_TESTING(void) { + ignore_madv_wipeonfork = 1; } #elif defined(OPENSSL_WINDOWS) || defined(OPENSSL_TRUSTY) diff --git a/crypto/fipsmodule/rand/fork_detect.h b/crypto/ube/fork_detect.h similarity index 93% rename from crypto/fipsmodule/rand/fork_detect.h rename to crypto/ube/fork_detect.h index 8518830cec..6774ba93c9 100644 --- a/crypto/fipsmodule/rand/fork_detect.h +++ b/crypto/ube/fork_detect.h @@ -38,9 +38,9 @@ extern "C" { // should only be used as a hardening measure. OPENSSL_EXPORT uint64_t CRYPTO_get_fork_generation(void); -// CRYPTO_fork_detect_ignore_madv_wipeonfork_for_testing is an internal detail +// CRYPTO_fork_detect_ignore_madv_wipeonfork_FOR_TESTING is an internal detail // used for testing purposes. -OPENSSL_EXPORT void CRYPTO_fork_detect_ignore_madv_wipeonfork_for_testing(void); +OPENSSL_EXPORT void CRYPTO_fork_detect_ignore_madv_wipeonfork_FOR_TESTING(void); #if defined(__cplusplus) } // extern C diff --git a/crypto/fipsmodule/rand/fork_detect_test.cc b/crypto/ube/fork_detect_test.cc similarity index 98% rename from crypto/fipsmodule/rand/fork_detect_test.cc rename to crypto/ube/fork_detect_test.cc index 23274acfd4..59c0334627 100644 --- a/crypto/fipsmodule/rand/fork_detect_test.cc +++ b/crypto/ube/fork_detect_test.cc @@ -103,7 +103,7 @@ static void ForkInChild(std::function f) { TEST(ForkDetect, Test) { if (getenv("BORINGSSL_IGNORE_MADV_WIPEONFORK")) { - CRYPTO_fork_detect_ignore_madv_wipeonfork_for_testing(); + CRYPTO_fork_detect_ignore_madv_wipeonfork_FOR_TESTING(); } const uint64_t start = CRYPTO_get_fork_generation(); diff --git a/crypto/ube/internal.h b/crypto/ube/internal.h index 578f777d4e..1e37c5b5b6 100644 --- a/crypto/ube/internal.h +++ b/crypto/ube/internal.h @@ -31,12 +31,32 @@ extern "C" { // 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. +// set_fork_generation_number_FOR_TESTING sets the fork generation number to the +// value |fork_gn|. This value will be the fork generation value used by the UBE +// logic, overriding the generation number from the real fork detection. +// |allow_mocked_ube_detection_FOR_TESTING| must have been invoked +// (once per-process) to allow mocking the fork generation number. OPENSSL_EXPORT void set_fork_generation_number_FOR_TESTING(uint64_t fork_gn); + +// set_snapsafe_generation_number_FOR_TESTING sets the snapsafe generation +// number to the value |snapsafe_gn|. This value will be the snapsafe generation +// value used by the UBE logic, overriding the generation number from the real +// snapsafe detection. +// |allow_mocked_ube_detection_FOR_TESTING| must have been invoked (once +// per-process) to allow mocking the snapsafe generation number. OPENSSL_EXPORT void set_snapsafe_generation_number_FOR_TESTING(uint32_t snapsafe_gn); +// allow_mocked_ube_detection_FOR_TESTING allows mocking UBE detection even +// though real detection is not available. This function must be called in +// test code when mocking the generation numbers, once per-process. Mocking is +// process-global i.e. all threads will read the same mocked value. +OPENSSL_EXPORT void allow_mocked_ube_detection_FOR_TESTING(void); + +// disable_mocked_ube_detection_FOR_TESTING disables mocking UBE detection. It +// also resets any mocked values to default values (0). This function should be +// invoked when exiting testing. +OPENSSL_EXPORT void disable_mocked_ube_detection_FOR_TESTING(void); + #if defined(__cplusplus) } // extern C #endif diff --git a/crypto/fipsmodule/rand/snapsafe_detect.c b/crypto/ube/snapsafe_detect.c similarity index 74% rename from crypto/fipsmodule/rand/snapsafe_detect.c rename to crypto/ube/snapsafe_detect.c index dee46c3aa1..bbfb6572c8 100644 --- a/crypto/fipsmodule/rand/snapsafe_detect.c +++ b/crypto/ube/snapsafe_detect.c @@ -10,16 +10,19 @@ #include #include #include -#include "../delocate.h" + +#include "../internal.h" // Snapsafety state #define SNAPSAFETY_STATE_FAILED_INITIALISE 0x00 #define SNAPSAFETY_STATE_SUCCESS_INITIALISE 0x01 #define SNAPSAFETY_STATE_NOT_SUPPORTED 0x02 -DEFINE_STATIC_ONCE(aws_snapsafe_init) -DEFINE_BSS_GET(volatile uint32_t *, sgc_addr) -DEFINE_BSS_GET(int, snapsafety_state) +static CRYPTO_once_t aws_snapsafe_init = CRYPTO_ONCE_INIT; + +static volatile uint32_t *sgn_addr = NULL; +static int snapsafety_state = 0; + // aws_snapsafe_check_kernel_support returns 1 if the special sysgenid device // file exists and 0 otherwise. @@ -33,20 +36,20 @@ static int aws_snapsafe_check_kernel_support(void) { } static void do_aws_snapsafe_init(void) { - *snapsafety_state_bss_get() = SNAPSAFETY_STATE_NOT_SUPPORTED; - *sgc_addr_bss_get() = NULL; + snapsafety_state = SNAPSAFETY_STATE_NOT_SUPPORTED; + sgn_addr = NULL; if (aws_snapsafe_check_kernel_support() != 1) { return; } - *snapsafety_state_bss_get() = SNAPSAFETY_STATE_FAILED_INITIALISE; + snapsafety_state = SNAPSAFETY_STATE_FAILED_INITIALISE; - int fd_sgc = open(CRYPTO_get_sysgenid_path(), O_RDONLY); - if (fd_sgc == -1) { + int fd_sgn = open(CRYPTO_get_sysgenid_path(), O_RDONLY); + if (fd_sgn == -1) { return; } - void *addr = mmap(NULL, sizeof(uint32_t), PROT_READ, MAP_SHARED, fd_sgc, 0); + void *addr = mmap(NULL, sizeof(uint32_t), PROT_READ, MAP_SHARED, fd_sgn, 0); // Can close file descriptor now per // https://man7.org/linux/man-pages/man2/mmap.2.html: "After the mmap() call @@ -54,31 +57,30 @@ static void do_aws_snapsafe_init(void) { // invalidating the mapping.". We have initialised snapsafety without errors // and this function is only executed once. Therefore, try to close file // descriptor but don't error if it fails. */ - close(fd_sgc); + close(fd_sgn); if (addr == MAP_FAILED) { return; } - // sgc_addr will now point at the mapped memory and any 4-byte read from + // sgn_addr will now point at the mapped memory and any 4-byte read from // this pointer will correspond to the sgn managed by the VMM. - *sgc_addr_bss_get() = addr; - *snapsafety_state_bss_get() = SNAPSAFETY_STATE_SUCCESS_INITIALISE; + sgn_addr = addr; + snapsafety_state = SNAPSAFETY_STATE_SUCCESS_INITIALISE; } static uint32_t aws_snapsafe_read_sgn(void) { - if (*snapsafety_state_bss_get() == SNAPSAFETY_STATE_SUCCESS_INITIALISE) { - return **sgc_addr_bss_get(); + if (snapsafety_state == SNAPSAFETY_STATE_SUCCESS_INITIALISE) { + return *sgn_addr; } return 0; } int CRYPTO_get_snapsafe_generation(uint32_t *snapsafe_generation_number) { - CRYPTO_once(aws_snapsafe_init_bss_get(), do_aws_snapsafe_init); + CRYPTO_once(&aws_snapsafe_init, do_aws_snapsafe_init); - int state = *snapsafety_state_bss_get(); - switch (state) { + switch (snapsafety_state) { case SNAPSAFETY_STATE_NOT_SUPPORTED: *snapsafe_generation_number = 0; return 1; @@ -95,9 +97,9 @@ int CRYPTO_get_snapsafe_generation(uint32_t *snapsafe_generation_number) { } int CRYPTO_get_snapsafe_active(void) { - CRYPTO_once(aws_snapsafe_init_bss_get(), do_aws_snapsafe_init); + CRYPTO_once(&aws_snapsafe_init, do_aws_snapsafe_init); - if (*snapsafety_state_bss_get() == SNAPSAFETY_STATE_SUCCESS_INITIALISE) { + if (snapsafety_state == SNAPSAFETY_STATE_SUCCESS_INITIALISE) { return 1; } @@ -105,9 +107,9 @@ int CRYPTO_get_snapsafe_active(void) { } int CRYPTO_get_snapsafe_supported(void) { - CRYPTO_once(aws_snapsafe_init_bss_get(), do_aws_snapsafe_init); + CRYPTO_once(&aws_snapsafe_init, do_aws_snapsafe_init); - if (*snapsafety_state_bss_get() == SNAPSAFETY_STATE_NOT_SUPPORTED) { + if (snapsafety_state == SNAPSAFETY_STATE_NOT_SUPPORTED) { return 0; } diff --git a/crypto/fipsmodule/rand/snapsafe_detect.h b/crypto/ube/snapsafe_detect.h similarity index 96% rename from crypto/fipsmodule/rand/snapsafe_detect.h rename to crypto/ube/snapsafe_detect.h index db89a87297..68d131878c 100644 --- a/crypto/fipsmodule/rand/snapsafe_detect.h +++ b/crypto/ube/snapsafe_detect.h @@ -34,7 +34,7 @@ OPENSSL_EXPORT int CRYPTO_get_snapsafe_generation( uint32_t *snapsafe_generation_number); // CRYPTO_get_snapsafe_active returns 1 if the file system presents the SysGenID -// interface and the libraruy has successfully initialized its use. Otherwise, +// interface and the library has successfully initialized its use. Otherwise, // it returns 0. OPENSSL_EXPORT int CRYPTO_get_snapsafe_active(void); diff --git a/crypto/fipsmodule/rand/snapsafe_detect_test.cc b/crypto/ube/snapsafe_detect_test.cc similarity index 100% rename from crypto/fipsmodule/rand/snapsafe_detect_test.cc rename to crypto/ube/snapsafe_detect_test.cc diff --git a/crypto/ube/ube.c b/crypto/ube/ube.c index 94045cae44..7b6e4a15c5 100644 --- a/crypto/ube/ube.c +++ b/crypto/ube/ube.c @@ -3,6 +3,9 @@ #include +#include "fork_detect.h" +#include "snapsafe_detect.h" + #include "internal.h" #include "../internal.h" @@ -34,36 +37,38 @@ 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; +static uint8_t allow_mocked_detection = 0; - -// TODO -// Temporary overrides to test detection code flow. -static uint64_t testing_fork_generation_number = 0; +static uint64_t override_fork_generation_number = 0; void set_fork_generation_number_FOR_TESTING(uint64_t fork_gn) { - testing_fork_generation_number = fork_gn; + override_fork_generation_number = fork_gn; } -static uint32_t testing_snapsafe_generation_number = 0; +static uint32_t override_snapsafe_generation_number = 0; void set_snapsafe_generation_number_FOR_TESTING(uint32_t snapsafe_gn) { - testing_snapsafe_generation_number = snapsafe_gn; + override_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; +static int get_snapsafe_generation_number(uint32_t *gn) { + if (allow_mocked_detection == 1) { + *gn = override_snapsafe_generation_number; + return 1; } - return 1; + + return CRYPTO_get_snapsafe_generation(gn); } -static uint64_t CRYPTO_get_fork_generation_number_mocked(void) { - if (testing_fork_generation_number != 0) { - return testing_fork_generation_number; + +static int get_fork_generation_number(uint64_t *gn) { + if (allow_mocked_detection == 1) { + *gn = override_fork_generation_number; + return 1; } + + uint64_t fork_gn = CRYPTO_get_fork_generation(); + if (fork_gn == 0) { + return 0; + } + + *gn = fork_gn; return 1; } @@ -107,12 +112,12 @@ static void ube_failed(void) { 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)); + int ret_fork_gn = get_fork_generation_number( + &(ube_global_state.cached_fork_gn)); + int ret_snapsafe_gn = get_snapsafe_generation_number( + &(ube_global_state.cached_snapsafe_gn)); - if (ube_global_state.cached_fork_gn == 0 || - ret_snapsafe_gn == 0) { + if (ret_fork_gn == 0 || ret_snapsafe_gn == 0) { ube_failed(); } } @@ -144,12 +149,12 @@ static int ube_get_detection_generation_numbers( 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( + int ret_detect_gn = get_fork_generation_number( + &(current_detection_gn->current_fork_gn)); + int ret_snapsafe_gn = get_snapsafe_generation_number( &(current_detection_gn->current_snapsafe_gn)); - if (current_detection_gn->current_fork_gn == 0 || - ret_snapsafe_gn == 0) { + if (ret_detect_gn == 0 || ret_snapsafe_gn == 0) { return 0; } @@ -181,7 +186,8 @@ int CRYPTO_get_ube_generation_number(uint64_t *current_generation_number) { // 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) { + if (ube_detection_unavailable == 1 && + allow_mocked_detection != 1) { return 0; } @@ -235,3 +241,13 @@ int CRYPTO_get_ube_generation_number(uint64_t *current_generation_number) { return 1; } + +void allow_mocked_ube_detection_FOR_TESTING(void) { + allow_mocked_detection = 1; +} + +void disable_mocked_ube_detection_FOR_TESTING(void) { + allow_mocked_detection = 0; + set_fork_generation_number_FOR_TESTING(0); + set_snapsafe_generation_number_FOR_TESTING(0); +} diff --git a/crypto/ube/ube_test.cc b/crypto/ube/ube_test.cc index 2dc5f8217b..df51c9922a 100644 --- a/crypto/ube/ube_test.cc +++ b/crypto/ube/ube_test.cc @@ -3,10 +3,23 @@ #include +#include + #include "internal.h" +#include "../test/ube_test.h" +#include "../test/test_util.h" + +class ubeGenerationNumberTest : public ubeTest {} ; -TEST(Ube, BasicTests) { +TEST_F(ubeGenerationNumberTest, BasicTests) { uint64_t generation_number = 0; + if (CRYPTO_get_ube_generation_number(&generation_number) == 0) { + // In this case, UBE detection is disabled, so just return + // successfully. This should be a persistent state; check that. + ASSERT_FALSE(CRYPTO_get_ube_generation_number(&generation_number)); + return; + } + ASSERT_TRUE(CRYPTO_get_ube_generation_number(&generation_number)); // Check stability. @@ -20,37 +33,39 @@ TEST(Ube, BasicTests) { ASSERT_EQ(current_generation_number, generation_number); } -TEST(Ube, MockedMethodTests) { +static void MockedDetectionMethodTest( + std::function set_method_generation_number) { + 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; + uint32_t mocked_generation_number = 0; + + uint8_t initial_mocked_generation_number[4] = {0}; + ASSERT_TRUE(RAND_bytes(initial_mocked_generation_number, 4)); + mocked_generation_number = + ((uint32_t)initial_mocked_generation_number[0] << 24) | + ((uint32_t)initial_mocked_generation_number[1] << 16) | + ((uint32_t)initial_mocked_generation_number[2] << 8) | + ((uint32_t)initial_mocked_generation_number[3]); + + // Testing that UBE generation number is incremented when: + // mocked_generation_number + 1 + // mocked_generation_number + 3 + // mocked_generation_number - 1 + // Set our starting point and get initial UBE generation number + set_method_generation_number(mocked_generation_number); ASSERT_TRUE(CRYPTO_get_ube_generation_number(&generation_number)); - ASSERT_EQ(generation_number, cached_generation_number + 1); - // Should be stable again. + // Should be 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); - // Mock another process fork. We used 10 before. Hence, 11 should work. - set_fork_generation_number_FOR_TESTING(11); + // Mock a UBE. + set_method_generation_number(mocked_generation_number + 1); - // Generation number should have incremented once. + // UBE generation number should have incremented once. cached_generation_number = generation_number; generation_number = 0; ASSERT_TRUE(CRYPTO_get_ube_generation_number(&generation_number)); @@ -62,8 +77,8 @@ TEST(Ube, MockedMethodTests) { 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); + // Mock another UBE with higher increment. + set_method_generation_number(mocked_generation_number + 3); // Generation number should have incremented once. cached_generation_number = generation_number; @@ -77,8 +92,8 @@ TEST(Ube, MockedMethodTests) { 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); + // Mock another UBE but with a strictly smaller value. + set_method_generation_number(mocked_generation_number - 1); // Generation number should have incremented once. cached_generation_number = generation_number; @@ -91,21 +106,43 @@ TEST(Ube, MockedMethodTests) { 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); +TEST_F(ubeGenerationNumberTest, MockedDetectionMethodTests) { + + allowMockedUbe(); + + MockedDetectionMethodTest( + [](uint32_t gn) { + set_fork_generation_number_FOR_TESTING(static_cast(gn)); + } + ); + + MockedDetectionMethodTest( + [](uint32_t gn) { + set_snapsafe_generation_number_FOR_TESTING(gn); + } + ); + + MockedDetectionMethodTest( + [](uint32_t gn) { + set_fork_generation_number_FOR_TESTING(static_cast(gn)); + set_snapsafe_generation_number_FOR_TESTING(gn); + } + ); + + MockedDetectionMethodTest( + [](uint32_t gn) { + set_fork_generation_number_FOR_TESTING(static_cast(gn)); + set_snapsafe_generation_number_FOR_TESTING(gn + 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); +TEST_F(ubeGenerationNumberTest, ExpectedSupportTests) { + uint64_t generation_number = 0; + // Operating systems where we expect UBE detection to be enabled. + if (osIsAmazonLinux()) { + ASSERT_TRUE(CRYPTO_get_ube_generation_number(&generation_number)); + } }