chore: checkpoint before Python removal

This commit is contained in:
2026-03-26 22:33:59 +00:00
parent 683cec9307
commit e568ddf82a
29972 changed files with 11269302 additions and 2 deletions

View File

@@ -0,0 +1,289 @@
// Copyright (c) 2020, Google Inc.
// SPDX-License-Identifier: ISC
#include <openssl/target.h>
// Methods to detect fork events aren't generally portable over our supported
// platforms. Fork detection is therefore an opt-in. Capture the opt-in logic
// below that categorizes a platform targets as either having
// 1) fork detection support,
// 2) forking doesn't exist, and
// 3) no fork detection support.
// For (1) we implement sufficient methods for detecting fork events. For (2),
// since forking is not a semantic that exists for the platform, we do not need
// to do anything. Except fake that fork detection is supported. For (3), we
// can't do anything. In this case randomness generation falls back to
// randomizing the state per-request.
#if defined(OPENSSL_LINUX)
#define AWSLC_FORK_UBE_DETECTION_SUPPORTED
#if !defined(_GNU_SOURCE)
#define _GNU_SOURCE // Needed for madvise() and MAP_ANONYMOUS.
#endif
#elif defined(OPENSSL_FREEBSD) || defined(OPENSSL_OPENBSD) || defined(OPENSSL_NETBSD)
#define AWSLC_FORK_UBE_DETECTION_SUPPORTED
// FreeBSD requires POSIX compatibility off for its syscalls
// (enables __BSD_VISIBLE). Without the below line, <sys/mman.h> cannot be
// imported (it requires __BSD_VISIBLE).
#undef _POSIX_C_SOURCE
#elif defined(OPENSSL_WINDOWS) || defined(OPENSSL_TRUSTY)
#define AWSLC_PLATFORM_DOES_NOT_FORK
#endif
#include "fork_ube_detect.h"
#include "../internal.h"
static struct CRYPTO_STATIC_MUTEX ignore_testing_lock = CRYPTO_STATIC_MUTEX_INIT;
static int ignore_wipeonfork = 0;
static int ignore_inheritzero = 0;
#if defined(AWSLC_FORK_UBE_DETECTION_SUPPORTED)
#include <openssl/base.h>
#include <openssl/type_check.h>
#include "../internal.h"
#include <stdlib.h>
static CRYPTO_once_t fork_detect_ube_once = CRYPTO_ONCE_INIT;
static struct CRYPTO_STATIC_MUTEX fork_detect_ube_lock = CRYPTO_STATIC_MUTEX_INIT;
// This value (pointed to) 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 fgn = 0;
static int ignore_all_fork_ube_detection(void) {
CRYPTO_STATIC_MUTEX_lock_read(&ignore_testing_lock);
if (ignore_wipeonfork == 1 &&
ignore_inheritzero == 1) {
CRYPTO_STATIC_MUTEX_unlock_read(&ignore_testing_lock);
return 1;
}
CRYPTO_STATIC_MUTEX_unlock_read(&ignore_testing_lock);
return 0;
}
#if defined(OPENSSL_LINUX)
#include <sys/mman.h>
#include <unistd.h>
#if defined(MADV_WIPEONFORK)
OPENSSL_STATIC_ASSERT(MADV_WIPEONFORK == 18, MADV_WIPEONFORK_is_not_18)
#else
#define MADV_WIPEONFORK 18
#endif
static int init_fork_detect_wipeonfork(void *addr, long page_size) {
GUARD_PTR(addr);
// 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
// know whether MADV_WIPEONFORK actually took effect. Therefore try an invalid
// call to check that the implementation of |madvise| is actually rejecting
// unknown |advice| values.
if (madvise(addr, (size_t)page_size, -1) == 0 ||
madvise(addr, (size_t)page_size, MADV_WIPEONFORK) != 0) {
return 0;
}
return 1;
}
#else // defined(OPENSSL_LINUX)
static int init_fork_detect_wipeonfork(void *addr, long page_size) {
return 0;
}
#endif // defined(OPENSSL_LINUX)
#if defined(OPENSSL_FREEBSD) || defined(OPENSSL_OPENBSD) || defined(OPENSSL_NETBSD)
#include <sys/mman.h>
#include <unistd.h>
// Sometimes (for example, on FreeBSD) MAP_INHERIT_ZERO is called INHERIT_ZERO
#if !defined(MAP_INHERIT_ZERO) && defined(INHERIT_ZERO)
#define MAP_INHERIT_ZERO INHERIT_ZERO
#endif
static int init_fork_detect_inheritzero(void *addr, long page_size) {
GUARD_PTR(addr);
if (minherit(addr, page_size, MAP_INHERIT_ZERO) != 0) {
return 0;
}
return 1;
}
#else // defined(OPENSSL_FREEBSD) || defined(OPENSSL_OPENBSD)
static int init_fork_detect_inheritzero(void *addr, long page_size) {
return 0;
}
#endif // defined(OPENSSL_FREEBSD) || defined(OPENSSL_OPENBSD)
// We assume that a method in this function is sufficient to detect fork events.
// Returns 1 if a method is sufficient for fork detection successfully
// initializes. Otherwise returns 0.
static int init_fork_detect_methods_sufficient(void *addr, long page_size) {
// No sufficient method found.
int ret = 0;
CRYPTO_STATIC_MUTEX_lock_read(&ignore_testing_lock);
if (ignore_wipeonfork != 1 &&
init_fork_detect_wipeonfork(addr, page_size) == 1) {
ret = 1;
goto out;
}
if (ignore_inheritzero != 1 &&
init_fork_detect_inheritzero(addr, page_size) == 1) {
ret = 1;
goto out;
}
out:
CRYPTO_STATIC_MUTEX_unlock_read(&ignore_testing_lock);
return ret;
}
// Best-effort attempt to initialize fork detection methods that provides
// defense-in-depth. These methods should not be relied on for fork detection.
// If initialization fails for one of these methods, just ignore it.
static void init_fork_detect_methods_best_effort(void *addr, long page_size) {
// No methods yet. pthread_at_fork() would be a "best-effort" choice. It can
// detect fork events through e.g. fork(). But miss fork events through
// clone().
}
static void init_fork_detect(void) {
void *addr = MAP_FAILED;
long page_size = 0;
if (ignore_all_fork_ube_detection() == 1) {
return;
}
page_size = sysconf(_SC_PAGESIZE);
if (page_size <= 0) {
return;
}
addr = mmap(NULL, (size_t)page_size, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (addr == MAP_FAILED) {
return;
}
// First attempt to initialize a method that is sufficient to detect fork
// events. If we can't initialize one such method, return without setting
// |fork_detect_addr|. This means that fork detection is not available.
if (init_fork_detect_methods_sufficient(addr, page_size) != 1) {
// No reason to verify return value of munmap() since we can't use that
// information for anything anyway.
munmap(addr, (size_t) page_size);
addr = NULL;
return;
}
// Next, try to initialize any defense-in-depth fork detection methods that
// might be available. Fail-open.
init_fork_detect_methods_best_effort(addr, page_size);
*((volatile char *) addr) = 1;
fork_detect_addr = addr;
fgn = 1;
}
uint64_t CRYPTO_get_fork_ube_generation(void) {
// In a single-threaded process, there are obviously no races because there's
// only a single mutator in the address space.
//
// In a multi-threaded environment, |CRYPTO_once| ensures that the flag byte
// is initialised atomically, even if multiple threads enter this function
// concurrently.
//
// In the limit, the kernel may clear e.g. WIPEONFORK pages while a
// multi-threaded process is running. (For example, because a VM was cloned.)
// Therefore a lock is used below to synchronize the potentially multiple
// threads that may concurrently observe the cleared flag.
//
// One cannot convert this to thread-local values to avoid locking. See e.g.
// https://github.com/aws/s2n-tls/issues/3107.
CRYPTO_once(&fork_detect_ube_once, init_fork_detect);
volatile char *const flag_ptr = fork_detect_addr;
if (flag_ptr == NULL) {
// Our kernel does not support fork detection.
return 0;
}
struct CRYPTO_STATIC_MUTEX *const lock = &fork_detect_ube_lock;
CRYPTO_STATIC_MUTEX_lock_read(lock);
uint64_t current_fgn = fgn;
if (*flag_ptr) {
CRYPTO_STATIC_MUTEX_unlock_read(lock);
return current_fgn;
}
CRYPTO_STATIC_MUTEX_unlock_read(lock);
CRYPTO_STATIC_MUTEX_lock_write(lock);
current_fgn = fgn;
if (*flag_ptr == 0) {
// A fork has occurred.
*flag_ptr = 1;
current_fgn++;
if (current_fgn == 0) {
current_fgn = 1;
}
fgn = current_fgn;
}
CRYPTO_STATIC_MUTEX_unlock_write(lock);
return current_fgn;
}
#elif defined(AWSLC_PLATFORM_DOES_NOT_FORK)
// These platforms are guaranteed not to fork, and therefore do not require
// fork detection support. Returning a constant non zero value makes BoringSSL
// assume address space duplication is not a concern and adding entropy to
// every RAND_bytes call is not needed.
uint64_t CRYPTO_get_fork_ube_generation(void) { return 0xc0ffee; }
#else
// These platforms may fork, but we do not have a mitigation mechanism in
// place. Returning a constant zero value makes BoringSSL assume that address
// space duplication could have occured on any call entropy must be added to
// every RAND_bytes call.
uint64_t CRYPTO_get_fork_ube_generation(void) { return 0; }
#endif // defined(AWSLC_FORK_UBE_DETECTION_SUPPORTED)
void CRYPTO_fork_detect_ignore_wipeonfork_FOR_TESTING(void) {
CRYPTO_STATIC_MUTEX_lock_write(&ignore_testing_lock);
ignore_wipeonfork = 1;
CRYPTO_STATIC_MUTEX_unlock_write(&ignore_testing_lock);
}
void CRYPTO_fork_detect_ignore_inheritzero_FOR_TESTING(void) {
CRYPTO_STATIC_MUTEX_lock_write(&ignore_testing_lock);
ignore_inheritzero = 1;
CRYPTO_STATIC_MUTEX_unlock_write(&ignore_testing_lock);
}

View File

@@ -0,0 +1,45 @@
// Copyright (c) 2020, Google Inc.
// SPDX-License-Identifier: ISC
#ifndef OPENSSL_HEADER_CRYPTO_FORK_UBE_DETECT_H
#define OPENSSL_HEADER_CRYPTO_FORK_UBE_DETECT_H
#include <openssl/base.h>
#include <stdint.h>
#if defined(__cplusplus)
extern "C" {
#endif
// CRYPTO_get_fork_ube_generation returns the fork generation number for the
// current process, or zero if not supported on the platform. The fork
// generation number is a non-zero, strictly-monotonic counter with the property
// that, if queried in an address space and then again in a subsequently forked
// copy, the forked address space will observe a greater value.
//
// This function may be used to clear cached values across a fork. When
// initializing a cache, record the fork generation. Before using the cache,
// check if the fork generation has changed. If so, drop the cache and update
// the save fork generation. Note this logic transparently handles platforms
// which always return zero.
//
// This is not reliably supported on all platforms which implement |fork|, so it
// should only be used as a hardening measure.
OPENSSL_EXPORT uint64_t CRYPTO_get_fork_ube_generation(void);
// CRYPTO_fork_detect_ignore_wipeonfork_FOR_TESTING is an internal detail
// used for testing purposes.
OPENSSL_EXPORT void CRYPTO_fork_detect_ignore_wipeonfork_FOR_TESTING(void);
// CRYPTO_fork_detect_ignore_inheritzero_FOR_TESTING is an internal detail
// used for testing purposes.
OPENSSL_EXPORT void CRYPTO_fork_detect_ignore_inheritzero_FOR_TESTING(void);
#if defined(__cplusplus)
} // extern C
#endif
#endif // OPENSSL_HEADER_CRYPTO_FORK_UBE_DETECT_H

View File

@@ -0,0 +1,160 @@
// Copyright (c) 2020, Google Inc.
// SPDX-License-Identifier: ISC
#include <openssl/base.h>
// TSAN cannot cope with this test and complains that "starting new threads
// after multi-threaded fork is not supported".
#if defined(OPENSSL_LINUX) && !defined(OPENSSL_TSAN)
#include <errno.h>
#ifndef __STDC_FORMAT_MACROS
#define __STDC_FORMAT_MACROS
#endif
#include <inttypes.h>
#include <stdio.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
#include <functional>
#if defined(OPENSSL_THREADS)
#include <thread>
#include <vector>
#endif
#include <gtest/gtest.h>
#include "fork_ube_detect.h"
#include "../test/test_util.h"
static pid_t WaitpidEINTR(pid_t pid, int *out_status, int options) {
pid_t ret;
do {
ret = waitpid(pid, out_status, options);
} while (ret < 0 && errno == EINTR);
return ret;
}
// The *InChild functions run inside a child process and must report errors via
// |stderr| and |_exit| rather than GTest.
static void CheckGenerationInChild(const char *name, uint64_t expected) {
uint64_t generation = CRYPTO_get_fork_ube_generation();
if (generation != expected) {
fprintf(stderr, "%s generation (#1) was %" PRIu64 ", wanted %" PRIu64 ".\n",
name, generation, expected);
_exit(1);
}
// The generation should be stable.
generation = CRYPTO_get_fork_ube_generation();
if (generation != expected) {
fprintf(stderr, "%s generation (#2) was %" PRIu64 ", wanted %" PRIu64 ".\n",
name, generation, expected);
_exit(1);
}
}
// ForkInChild forks a child which runs |f|. If the child exits unsuccessfully,
// this function will also exit unsuccessfully.
static void ForkInChild(std::function<void()> f) {
fflush(stderr); // Avoid duplicating any buffered output.
const pid_t pid = fork();
if (pid < 0) {
perror("fork");
_exit(1);
} else if (pid == 0) {
f();
_exit(0);
}
// Wait for the child and pass its exit code up.
int status;
if (WaitpidEINTR(pid, &status, 0) < 0) {
perror("waitpid");
_exit(1);
}
if (!WIFEXITED(status)) {
fprintf(stderr, "Child did not exit cleanly.\n");
_exit(1);
}
if (WEXITSTATUS(status) != 0) {
// Pass the failure up.
_exit(WEXITSTATUS(status));
}
}
TEST(ForkDetect, Test) {
maybeDisableSomeForkUbeDetectMechanisms();
const uint64_t start = CRYPTO_get_fork_ube_generation();
if (start == 0) {
fprintf(stderr, "Fork detection not supported. Skipping test.\n");
return;
}
// The fork generation should be stable.
EXPECT_EQ(start, CRYPTO_get_fork_ube_generation());
fflush(stderr);
const pid_t child = fork();
if (child == 0) {
// Fork grandchildren before observing the fork generation. The
// grandchildren will observe |start| + 1.
for (int i = 0; i < 2; i++) {
ForkInChild([&] { CheckGenerationInChild("Grandchild", start + 1); });
}
// Now the child also observes |start| + 1. This is fine because it has
// already diverged from the grandchild at this point.
CheckGenerationInChild("Child", start + 1);
// Forked grandchildren will now observe |start| + 2.
for (int i = 0; i < 2; i++) {
ForkInChild([&] { CheckGenerationInChild("Grandchild", start + 2); });
}
#if defined(OPENSSL_THREADS)
// The fork generation logic itself must be thread-safe. We test this in a
// child process to capture the actual fork detection. This segment is meant
// to be tested in TSan.
ForkInChild([&] {
std::vector<std::thread> threads(4);
for (int i = 0; i < 2; i++) {
for (auto &t : threads) {
t = std::thread(
[&] { CheckGenerationInChild("Grandchild thread", start + 2); });
}
for (auto &t : threads) {
t.join();
}
}
});
#endif // OPENSSL_THREADS
// The child still observes |start| + 1.
CheckGenerationInChild("Child", start + 1);
_exit(0);
}
ASSERT_GT(child, 0) << "Error in fork: " << strerror(errno);
int status;
ASSERT_EQ(child, WaitpidEINTR(child, &status, 0))
<< "Error in waitpid: " << strerror(errno);
ASSERT_TRUE(WIFEXITED(status));
EXPECT_EQ(0, WEXITSTATUS(status)) << "Error in child process";
// We still observe |start|.
EXPECT_EQ(start, CRYPTO_get_fork_ube_generation());
}
#endif // OPENSSL_LINUX && !OPENSSL_TSAN

View File

@@ -0,0 +1,64 @@
// 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 <openssl/base.h>
// 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 unique memory. First cache a UBE
// generation number associating it to the unique memory at
// creation/initialization time. Before using the unique 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 unique 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);
// set_fork_ube_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_ube_generation_number_FOR_TESTING(uint64_t fork_gn);
// set_vm_ube_generation_number_FOR_TESTING sets the vm_ube generation
// number to the value |vm_ube_gn|. This value will be the vm_ube generation
// value used by the UBE logic, overriding the generation number from the real
// vm_ube detection.
// |allow_mocked_ube_detection_FOR_TESTING| must have been invoked (once
// per-process) to allow mocking the vm_ube generation number.
OPENSSL_EXPORT void set_vm_ube_generation_number_FOR_TESTING(uint32_t vm_ube_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
#endif // OPENSSL_HEADER_CRYPTO_UBE_INTERNAL_H

View File

@@ -0,0 +1,288 @@
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 OR ISC
#include <openssl/base.h>
#include "fork_ube_detect.h"
#include "vm_ube_detect.h"
#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.
//
// ATTENTION: Before attempting to re-write this logic and add more mechanisms
// review P269907867 carefully.
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;
// Locking for testing-specific code. Don't use |ube_lock| to avoid any
// potential for deadlocks.
static struct CRYPTO_STATIC_MUTEX ube_testing_lock = CRYPTO_STATIC_MUTEX_INIT;
static uint8_t ube_detection_unavailable = 0;
static uint8_t allow_mocked_detection = 0;
static uint64_t override_fork_generation_number = 0;
void set_fork_ube_generation_number_FOR_TESTING(uint64_t fork_gn) {
CRYPTO_STATIC_MUTEX_lock_write(&ube_testing_lock);
override_fork_generation_number = fork_gn;
CRYPTO_STATIC_MUTEX_unlock_write(&ube_testing_lock);
}
static uint32_t override_vm_ube_generation_number = 0;
void set_vm_ube_generation_number_FOR_TESTING(uint32_t vm_ube_gn) {
CRYPTO_STATIC_MUTEX_lock_write(&ube_testing_lock);
override_vm_ube_generation_number = vm_ube_gn;
CRYPTO_STATIC_MUTEX_unlock_write(&ube_testing_lock);
}
static int get_vm_ube_generation_number(uint32_t *gn) {
if (allow_mocked_detection == 1) {
CRYPTO_STATIC_MUTEX_lock_read(&ube_testing_lock);
*gn = override_vm_ube_generation_number;
CRYPTO_STATIC_MUTEX_unlock_read(&ube_testing_lock);
return 1;
}
return CRYPTO_get_vm_ube_generation(gn);
}
static int get_fork_generation_number(uint64_t *gn) {
if (allow_mocked_detection == 1) {
CRYPTO_STATIC_MUTEX_lock_read(&ube_testing_lock);
*gn = override_fork_generation_number;
CRYPTO_STATIC_MUTEX_unlock_read(&ube_testing_lock);
return 1;
}
uint64_t fork_gn = CRYPTO_get_fork_ube_generation();
if (fork_gn == 0) {
return 0;
}
*gn = fork_gn;
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_vm_ube_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_vm_ube_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;
int ret_fork_gn = get_fork_generation_number(
&(ube_global_state.cached_fork_gn));
int ret_vm_ube_gn = get_vm_ube_generation_number(
&(ube_global_state.cached_vm_ube_gn));
if (ret_fork_gn == 0 || ret_vm_ube_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 vm_ube 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_vm_ube_gn = current_detection_gn->current_vm_ube_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);
int ret_detect_gn = get_fork_generation_number(
&(current_detection_gn->current_fork_gn));
int ret_vm_ube_gn = get_vm_ube_generation_number(
&(current_detection_gn->current_vm_ube_gn));
if (ret_detect_gn == 0 || ret_vm_ube_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.
//
// ATTENTION: Before attempting to re-write this logic and add more mechanisms
// review P269907867 carefully.
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_vm_ube_gn != current_detection_gn->current_vm_ube_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.
//
// |ube_detection_unavailable| and |allow_mocked_detection| are both global
// variables that can be mutated in multiple threads. They are uint8_t typed
// though and we assume read and write to them is atomic. This path is hot.
// The assumption avoids us having to acquire (up to) two read locks that
// would probably never be contended anyway. That would not block other
// threads, but cost cycles regardless.
if (ube_detection_unavailable == 1 &&
allow_mocked_detection != 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(&current_detection_gn) != 1) {
ube_failed();
return 0;
}
CRYPTO_STATIC_MUTEX_lock_read(&ube_lock);
if (ube_is_detected(&current_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 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.
CRYPTO_STATIC_MUTEX_lock_write(&ube_lock);
if (ube_get_detection_generation_numbers(&current_detection_gn) != 1) {
ube_failed();
CRYPTO_STATIC_MUTEX_unlock_write(&ube_lock);
return 0;
}
if (ube_is_detected(&current_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(&current_detection_gn);
*current_generation_number = ube_global_state.generation_number;
CRYPTO_STATIC_MUTEX_unlock_write(&ube_lock);
return 1;
}
// Synchronize writing to |allow_mocked_detection|. But only to more easily
// reason about ordering. They are supposed to only be used in testing code
// and called, initially, from a single-threaded process at initialization time.
// We generally don't care about any contention that might happen.
void allow_mocked_ube_detection_FOR_TESTING(void) {
CRYPTO_STATIC_MUTEX_lock_write(&ube_testing_lock);
allow_mocked_detection = 1;
CRYPTO_STATIC_MUTEX_unlock_write(&ube_testing_lock);
}
void disable_mocked_ube_detection_FOR_TESTING(void) {
CRYPTO_STATIC_MUTEX_lock_write(&ube_testing_lock);
allow_mocked_detection = 0;
CRYPTO_STATIC_MUTEX_unlock_write(&ube_testing_lock);
set_fork_ube_generation_number_FOR_TESTING(0);
set_vm_ube_generation_number_FOR_TESTING(0);
}

View File

@@ -0,0 +1,168 @@
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 OR ISC
#include <gtest/gtest.h>
#include <openssl/rand.h>
#include "internal.h"
#include "../test/ube_test.h"
#include "../test/test_util.h"
class ubeGenerationNumberTest : public::testing::Test {
private:
UbeBase ube_base_;
protected:
void SetUp() override {
ube_base_.SetUp();
}
void TearDown() override {
ube_base_.TearDown();
}
bool UbeIsSupported() const {
return ube_base_.UbeIsSupported();
}
void allowMockedUbe() const {
return ube_base_.allowMockedUbe();
}
};
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.
uint64_t current_generation_number = generation_number + 1;
ASSERT_TRUE(CRYPTO_get_ube_generation_number(&current_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(&current_generation_number));
ASSERT_EQ(current_generation_number, generation_number);
}
static void MockedDetectionMethodTest(
std::function<void(uint32_t)> set_method_generation_number) {
uint64_t generation_number = 0;
uint64_t cached_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));
// 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 a UBE.
set_method_generation_number(mocked_generation_number + 1);
// UBE 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 UBE with higher increment.
set_method_generation_number(mocked_generation_number + 3);
// 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 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;
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);
}
TEST_F(ubeGenerationNumberTest, MockedDetectionMethodTests) {
allowMockedUbe();
MockedDetectionMethodTest(
[](uint32_t gn) {
set_fork_ube_generation_number_FOR_TESTING(static_cast<uint64_t>(gn));
}
);
MockedDetectionMethodTest(
[](uint32_t gn) {
set_vm_ube_generation_number_FOR_TESTING(gn);
}
);
MockedDetectionMethodTest(
[](uint32_t gn) {
set_fork_ube_generation_number_FOR_TESTING(static_cast<uint64_t>(gn));
set_vm_ube_generation_number_FOR_TESTING(gn);
}
);
MockedDetectionMethodTest(
[](uint32_t gn) {
set_fork_ube_generation_number_FOR_TESTING(static_cast<uint64_t>(gn));
set_vm_ube_generation_number_FOR_TESTING(gn + 1);
}
);
}
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));
}
}

View File

@@ -0,0 +1,157 @@
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 OR ISC
#include <openssl/crypto.h>
#include "vm_ube_detect.h"
#if defined(OPENSSL_LINUX)
#include <fcntl.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#include "../internal.h"
// VM UBE state
#define VM_UBE_STATE_FAILED_INITIALISE 0x00
#define VM_UBE_STATE_SUCCESS_INITIALISE 0x01
#define VM_UBE_STATE_NOT_SUPPORTED 0x02
static CRYPTO_once_t vm_ube_init = CRYPTO_ONCE_INIT;
static int vm_ube_state = 0;
// SysGenID generation number pointer
static volatile uint32_t *sgn_addr = NULL;
static void do_sysgenid_init(void) {
vm_ube_state = VM_UBE_STATE_NOT_SUPPORTED;
sgn_addr = NULL;
struct stat buff;
if (stat(CRYPTO_get_sysgenid_path(), &buff) != 0) {
return;
}
vm_ube_state = VM_UBE_STATE_FAILED_INITIALISE;
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_sgn, 0);
// Can close file descriptor now per
// https://man7.org/linux/man-pages/man2/mmap.2.html: "After the mmap() call
// has returned, the file descriptor, fd, can be closed immediately without
// invalidating the mapping.". We have initialised vm_ube_state without errors
// and this function is only executed once. Therefore, try to close file
// descriptor but don't error if it fails. */
close(fd_sgn);
if (addr == MAP_FAILED) {
return;
}
// 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.
sgn_addr = addr;
vm_ube_state = VM_UBE_STATE_SUCCESS_INITIALISE;
}
static uint32_t vm_ube_read_sysgenid_gn(void) {
if (vm_ube_state == VM_UBE_STATE_SUCCESS_INITIALISE) {
return *sgn_addr;
}
return 0;
}
int CRYPTO_get_vm_ube_generation(uint32_t *vm_ube_generation_number) {
CRYPTO_once(&vm_ube_init, do_sysgenid_init);
switch (vm_ube_state) {
case VM_UBE_STATE_NOT_SUPPORTED:
*vm_ube_generation_number = 0;
return 1;
case VM_UBE_STATE_SUCCESS_INITIALISE:
*vm_ube_generation_number = vm_ube_read_sysgenid_gn();
return 1;
case VM_UBE_STATE_FAILED_INITIALISE:
*vm_ube_generation_number = 0;
return 0;
default:
// No other state should be possible.
abort();
}
}
int CRYPTO_get_vm_ube_active(void) {
CRYPTO_once(&vm_ube_init, do_sysgenid_init);
if (vm_ube_state == VM_UBE_STATE_SUCCESS_INITIALISE) {
return 1;
}
return 0;
}
int CRYPTO_get_vm_ube_supported(void) {
CRYPTO_once(&vm_ube_init, do_sysgenid_init);
if (vm_ube_state == VM_UBE_STATE_NOT_SUPPORTED) {
return 0;
}
return 1;
}
#else // !defined(OPENSSL_LINUX)
int CRYPTO_get_vm_ube_generation(uint32_t *vm_ube_generation_number) {
*vm_ube_generation_number = 0;
return 1;
}
int CRYPTO_get_vm_ube_active(void) { return 0; }
int CRYPTO_get_vm_ube_supported(void) { return 0; }
#endif // defined(OPENSSL_LINUX)
const char* CRYPTO_get_sysgenid_path(void) {
return AWSLC_SYSGENID_PATH;
}
#if defined(OPENSSL_LINUX) && defined(AWSLC_VM_UBE_TESTING)
int HAZMAT_init_sysgenid_file(void) {
int fd_sgn = open(CRYPTO_get_sysgenid_path(), O_CREAT | O_RDWR,
S_IRWXU | S_IRGRP | S_IROTH);
if (fd_sgn == -1) {
return 0;
}
// If the file is empty, populate it. Otherwise, no change.
if (0 == lseek(fd_sgn, 0, SEEK_END)) {
if (0 != lseek(fd_sgn, 0, SEEK_SET)) {
close(fd_sgn);
return 0;
}
uint32_t value = 0;
if (0 >= write(fd_sgn, &value, sizeof(uint32_t))) {
close(fd_sgn);
return 0;
}
if (0 != fsync(fd_sgn)) {
close(fd_sgn);
return 0;
}
}
close(fd_sgn);
return 1;
}
#endif

View File

@@ -0,0 +1,59 @@
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 OR ISC
#ifndef HEADER_VM_UBE_DETECT
#define HEADER_VM_UBE_DETECT
#include <openssl/base.h>
#ifdef __cplusplus
extern "C" {
#endif
#if !defined(AWSLC_SYSGENID_PATH)
#define AWSLC_SYSGENID_PATH "/dev/sysgenid"
#endif
// VM UBE-type uniqueness breaking event (ube detection).
//
// CRYPTO_get_vm_ube_generation provides the VM UBE generation number for
// the current process. The VM UBE generation number is a non-zero,
// strictly-monotonic counter with the property that, if queried in an address
// space and then again in a subsequently resumed snapshot/VM, the resumed
// address space will observe a greater value.
//
// We use SysGenID to detect resumed snapshot/VM events. See
// https://lkml.org/lkml/2021/3/8/677 for details about how SysGenID works.
// We make light use of the SysGenId capabilities and only use the following
// supported functions on the device: |open| and |mmap|.
//
// |CRYPTO_get_vm_ube_generation| returns 0 only when the filesystem
// presents SysGenID interface (default is `/dev/sysgenid`) but we are
// is unable to initialize its use. Otherwise, it returns 1.
OPENSSL_EXPORT int CRYPTO_get_vm_ube_generation(
uint32_t *vm_ube_generation_number);
// CRYPTO_get_vm_ube_active returns 1 if the file system presents the SysGenID
// interface and the library has successfully initialized its use. Otherwise,
// it returns 0.
OPENSSL_EXPORT int CRYPTO_get_vm_ube_active(void);
// CRYPTO_get_vm_ube_supported returns 1 if the file system presents the
// SysGenID interface. Otherwise, it returns 0.
OPENSSL_EXPORT int CRYPTO_get_vm_ube_supported(void);
// CRYPTO_get_sysgenid_path returns the path used for the SysGenId interface.
OPENSSL_EXPORT const char *CRYPTO_get_sysgenid_path(void);
#if defined(OPENSSL_LINUX) && defined(AWSLC_VM_UBE_TESTING)
// HAZMAT_init_sysgenid_file should only be used for testing. It creates and
// initializes the sysgenid path indicated by AWSLC_SYSGENID_PATH.
// On success, it returns 1. Otherwise, returns 0.
OPENSSL_EXPORT int HAZMAT_init_sysgenid_file(void);
#endif
#ifdef __cplusplus
}
#endif
#endif /* HEADER_VM_UBE_DETECT */

View File

@@ -0,0 +1,115 @@
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 OR ISC
#include <openssl/base.h>
#include <cstdint>
#include <gtest/gtest.h>
#include "vm_ube_detect.h"
#if defined(OPENSSL_LINUX) && defined(AWSLC_VM_UBE_TESTING)
#include <fcntl.h>
#include <cstring>
#include <sys/mman.h>
#define NUMBER_OF_TEST_VALUES 5
typedef struct sgn_test_s {
void *addr;
} sgn_test_s;
static int init_sgn_file(void** addr);
static int init_sgn_file(void** addr) {
*addr = nullptr;
// This file should've been created during test initialization
const int fd_sgn = open(CRYPTO_get_sysgenid_path(), O_RDWR);
if (fd_sgn == -1) {
return 0;
}
if (0 != lseek(fd_sgn, 0, SEEK_SET)) {
close(fd_sgn);
return 0;
}
void* my_addr = mmap(nullptr, sizeof(uint32_t), PROT_WRITE, MAP_SHARED, fd_sgn, 0);
if (my_addr == MAP_FAILED) {
close(fd_sgn);
return 0;
}
close(fd_sgn);
*addr = my_addr;
return 1;
}
static int init_sgn_test(sgn_test_s* sgn_test);
static int init_sgn_test(sgn_test_s* sgn_test) {
return init_sgn_file(&sgn_test->addr);
}
static int set_sgn(const sgn_test_s* sgn_test, uint32_t val);
static int set_sgn(const sgn_test_s* sgn_test, uint32_t val) {
memcpy(sgn_test->addr, &val, sizeof(uint32_t));
if(0 != msync(sgn_test->addr, sizeof(uint32_t), MS_SYNC)) {
return 0;
}
return 1;
}
TEST(VmUbeGenerationTest, DISABLED_SysGenIDretrievalTesting) {
sgn_test_s sgn_test;
ASSERT_TRUE(init_sgn_test(&sgn_test));
ASSERT_TRUE(set_sgn(&sgn_test, 0));
EXPECT_EQ(1, CRYPTO_get_vm_ube_supported());
EXPECT_EQ(1, CRYPTO_get_vm_ube_active());
uint32_t current_vm_ube_gen_num = 0;
ASSERT_TRUE(set_sgn(&sgn_test, 7));
ASSERT_TRUE(CRYPTO_get_vm_ube_generation(&current_vm_ube_gen_num));
ASSERT_EQ((uint32_t) 7, current_vm_ube_gen_num);
uint32_t test_sysgenid_values[NUMBER_OF_TEST_VALUES] = {
0x03, // 2^0 + 2
0x103, // 2^8 + 3
0x10004, // 2^16 + 4
0x1000005, // 2^24 + 5
0xFFFFFFFF // 2^32 - 1
};
for (size_t i = 0; i < NUMBER_OF_TEST_VALUES; i++) {
// Exercise all bytes of the 32-bit generation number.
uint32_t new_sysgenid_value_hint = test_sysgenid_values[i];
ASSERT_TRUE(set_sgn(&sgn_test, new_sysgenid_value_hint));
ASSERT_TRUE(CRYPTO_get_vm_ube_generation(&current_vm_ube_gen_num));
EXPECT_EQ(new_sysgenid_value_hint, current_vm_ube_gen_num);
}
}
#elif defined(OPENSSL_LINUX)
TEST(VmUbeGenerationTest, SysGenIDretrievalLinux) {
uint32_t current_vm_ube_gen_num = 0xffffffff;
ASSERT_TRUE(CRYPTO_get_vm_ube_generation(&current_vm_ube_gen_num));
if (CRYPTO_get_vm_ube_supported()) {
ASSERT_TRUE(CRYPTO_get_vm_ube_active());
// If we're on a system where the SysGenId is available, we won't
// know what sgn value to expect, but we assume it's not 0xffffffff
ASSERT_NE(0xffffffff, current_vm_ube_gen_num);
} else {
ASSERT_FALSE(CRYPTO_get_vm_ube_active());
ASSERT_EQ((uint32_t) 0, current_vm_ube_gen_num);
}
}
#else
TEST(VmUbeGenerationTest, SysGenIDretrievalNonLinux) {
ASSERT_FALSE(CRYPTO_get_vm_ube_supported());
ASSERT_FALSE(CRYPTO_get_vm_ube_active());
uint32_t current_vm_ube_gen_num = 0xffffffff;
ASSERT_TRUE(CRYPTO_get_vm_ube_generation(&current_vm_ube_gen_num));
ASSERT_EQ((uint32_t) 0, current_vm_ube_gen_num);
}
#endif // defined(OPENSSL_LINUX)