Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Snapsafe-type uniqueness breaking event detection (#1640) #1648

Merged
merged 2 commits into from
Jun 20, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions BUILDING.md
Original file line number Diff line number Diff line change
Expand Up @@ -229,3 +229,15 @@ range of unit tests, as well as running valgrind and SDE tests. Building without
produces a new target, `run_minimal_tests` in place of `run_tests`.

More information on this can be found in [INCORPORATING.md](/INCORPORATING.md).

# Snapsafe Detection

AWS-LC supports Snapsafe-type uniqueness breaking event detection
on Linux using SysGenID (https://lkml.org/lkml/2021/3/8/677). This mechanism
is used for security hardening. If a SysGenID interface is not found, then the
mechanism is ignored.

## Snapsafe Prerequisites

Snapshots taken on active hosts can potentially be unsafe to use.
See "Snapshot Safety Prerequisites" here: https://lkml.org/lkml/2021/3/8/677
7 changes: 7 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,13 @@ install(DIRECTORY include/openssl
PATTERN boringssl_prefix_symbols_nasm.inc EXCLUDE
)

if (TEST_SYSGENID_PATH)
message(STATUS "Setting AWSLC_SNAPSAFE_TESTING=1")
add_definitions(-DAWSLC_SNAPSAFE_TESTING=1)
message(STATUS "Setting AWSLC_SYSGENID_PATH=${TEST_SYSGENID_PATH}")
add_definitions(-DAWSLC_SYSGENID_PATH=\"${TEST_SYSGENID_PATH}\")
endif()

if(ANDROID)
# Android-NDK CMake files reconfigure the path and so Perl won't be found.
# However, ninja will still find them in $PATH if we just name them.
Expand Down
1 change: 1 addition & 0 deletions crypto/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -719,6 +719,7 @@ if(BUILD_TESTING)
fipsmodule/rand/ctrdrbg_test.cc
fipsmodule/rand/cpu_jitter_test.cc
fipsmodule/rand/fork_detect_test.cc
fipsmodule/rand/snapsafe_detect_test.cc
fipsmodule/service_indicator/service_indicator_test.cc
fipsmodule/sha/sha_test.cc
fipsmodule/sha/sha3_test.cc
Expand Down
1 change: 1 addition & 0 deletions crypto/fipsmodule/bcm.c
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@
#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"
Expand Down
65 changes: 57 additions & 8 deletions crypto/fipsmodule/rand/rand.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@

#include "internal.h"
#include "fork_detect.h"
#include "snapsafe_detect.h"
#include "../../internal.h"
#include "../delocate.h"

Expand All @@ -54,14 +55,14 @@
// This might be a bit of a leap of faith, esp on Windows, but there's nothing
// that we can do about it.)

// When in FIPS mode we use the CPU Jitter entropy source to seed our DRBG.
// When in FIPS mode we use the CPU Jitter entropy source to seed our DRBG.
// This entropy source is very slow and can incur a cost anywhere between 10-60ms
// depending on configuration and CPU. Increasing to 2^24 will amortize the
// penalty over more requests. This is the same value used in OpenSSL 3.0
// depending on configuration and CPU. Increasing to 2^24 will amortize the
// penalty over more requests. This is the same value used in OpenSSL 3.0
// and meets the requirements defined in SP 800-90B for a max reseed of interval (2^48)
//
// CPU Jitter: https://www.chronox.de/jent/doc/CPU-Jitter-NPTRNG.html
//
//
// kReseedInterval is the number of generate calls made to CTR-DRBG before
// reseeding.

Expand Down Expand Up @@ -96,6 +97,13 @@ struct rand_thread_state {
// calls is the number of generate calls made on |drbg| since it was last
// (re)seeded. This is bound by |kReseedInterval|.
unsigned calls;
// fork_unsafe_buffering is non-zero iff, when |drbg| was last (re)seeded,
// fork-unsafe buffering was enabled.
int fork_unsafe_buffering;

// snapsafe_generation is non-zero when active. When the value changes,
// the drbg state must be reseeded.
uint32_t snapsafe_generation;

#if defined(BORINGSSL_FIPS)
// next and prev form a NULL-terminated, double-linked list of all states in
Expand Down Expand Up @@ -317,6 +325,12 @@ static int rdrand(uint8_t *buf, size_t len) {
#if defined(BORINGSSL_FIPS)

#if defined(FIPS_ENTROPY_SOURCE_PASSIVE)

// Currently, we assume that the length of externally loaded entropy has the
// same length as the seed used in the ctr-drbg.
OPENSSL_STATIC_ASSERT(CTR_DRBG_ENTROPY_LEN == PASSIVE_ENTROPY_LOAD_LENGTH,
passive_entropy_load_length_different_from_ctr_drbg_seed_length)

void RAND_load_entropy(uint8_t out_entropy[CTR_DRBG_ENTROPY_LEN],
uint8_t entropy[PASSIVE_ENTROPY_LOAD_LENGTH]) {
OPENSSL_memcpy(out_entropy, entropy, CTR_DRBG_ENTROPY_LEN);
Expand Down Expand Up @@ -355,8 +369,9 @@ static void rand_get_seed(struct rand_thread_state *state,
static void rand_get_seed(struct rand_thread_state *state,
uint8_t seed[CTR_DRBG_ENTROPY_LEN],
int *out_want_additional_input) {
// If not in FIPS mode, we don't overread from the system entropy source and
// we don't depend only on the hardware RDRAND.
// If not in FIPS mode, we use the system entropy source.
// We don't source the entropy directly from the CPU.
// Therefore, |*out_want_additonal_input| is set to zero.
CRYPTO_sysrand_for_seed(seed, CTR_DRBG_ENTROPY_LEN);
*out_want_additional_input = 0;
}
Expand All @@ -370,6 +385,10 @@ void RAND_bytes_with_additional_data(uint8_t *out, size_t out_len,
}

const uint64_t fork_generation = CRYPTO_get_fork_generation();
const int fork_unsafe_buffering = rand_fork_unsafe_buffering_enabled();

uint32_t snapsafe_generation = 0;
int snapsafe_status = CRYPTO_get_snapsafe_generation(&snapsafe_generation);

// Additional data is mixed into every CTR-DRBG call to protect, as best we
// can, against forks & VM clones. We do not over-read this information and
Expand All @@ -384,7 +403,10 @@ void RAND_bytes_with_additional_data(uint8_t *out, size_t out_len,
// entropy is used. This can be expensive (one read per |RAND_bytes| call)
// and so is disabled when we have fork detection, or if the application has
// promised not to fork.
if (fork_generation != 0 || rand_fork_unsafe_buffering_enabled()) {
// snapsafe_status is only 0 when the kernel has snapsafe support, but it
// failed to initialize. Otherwise, snapsafe_status is 1.
if ((snapsafe_status != 0 && fork_generation != 0) ||
fork_unsafe_buffering) {
OPENSSL_memset(additional_data, 0, sizeof(additional_data));
} else if (!have_rdrand()) {
// No alternative so block for OS entropy.
Expand Down Expand Up @@ -439,6 +461,8 @@ void RAND_bytes_with_additional_data(uint8_t *out, size_t out_len,
}
state->calls = 0;
state->fork_generation = fork_generation;
state->fork_unsafe_buffering = fork_unsafe_buffering;
state->snapsafe_generation = snapsafe_generation;

#if defined(BORINGSSL_FIPS)
if (state != &stack_state) {
Expand All @@ -458,7 +482,16 @@ void RAND_bytes_with_additional_data(uint8_t *out, size_t out_len,
}

if (state->calls >= kReseedInterval ||
state->fork_generation != fork_generation) {
// If we've been cloned since |state| was last seeded, reseed.
state->snapsafe_generation != snapsafe_generation ||
// If we've forked since |state| was last seeded, reseed.
state->fork_generation != fork_generation ||
// If |state| was seeded from a state with different fork-safety
// preferences, reseed. Suppose |state| was fork-safe, then forked into
// two children, but each of the children never fork and disable fork
// safety. The children must reseed to avoid working from the same PRNG
// state.
state->fork_unsafe_buffering != fork_unsafe_buffering) {
uint8_t seed[CTR_DRBG_ENTROPY_LEN];
int want_additional_input;
rand_get_seed(state, seed, &want_additional_input);
Expand All @@ -485,6 +518,8 @@ void RAND_bytes_with_additional_data(uint8_t *out, size_t out_len,
}
state->calls = 0;
state->fork_generation = fork_generation;
state->fork_unsafe_buffering = fork_unsafe_buffering;
state->snapsafe_generation = snapsafe_generation;
OPENSSL_cleanse(seed, CTR_DRBG_ENTROPY_LEN);
OPENSSL_cleanse(add_data_for_reseed, CTR_DRBG_ENTROPY_LEN);
} else {
Expand Down Expand Up @@ -518,6 +553,20 @@ void RAND_bytes_with_additional_data(uint8_t *out, size_t out_len,
}

OPENSSL_cleanse(additional_data, 32);
#if !defined(AWSLC_SNAPSAFE_TESTING)
// SysGenId tests might be running parallel to this, causing changes to sgn.
if (1 == CRYPTO_get_snapsafe_generation(&snapsafe_generation)) {
if (snapsafe_generation != state->snapsafe_generation) {
// Unexpected change to snapsafe generation.
// A change in the snapsafe generation between the beginning of this
// funtion and here indicates that a snapshot was taken (and is now being
// used) while this function was executing. This is an invalid snapshot
// and is not safe for use. Please ensure all processing is completed
// prior to collecting a snapshot.
abort();
}
}
#endif

#if defined(BORINGSSL_FIPS)
CRYPTO_STATIC_MUTEX_unlock_read(state_clear_all_lock_bss_get());
Expand Down
158 changes: 158 additions & 0 deletions crypto/fipsmodule/rand/snapsafe_detect.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 OR ISC

#include <openssl/crypto.h>

#include "snapsafe_detect.h"

#if defined(OPENSSL_LINUX)
#include <fcntl.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <unistd.h>
#include "../delocate.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)

// aws_snapsafe_check_kernel_support returns 1 if the special sysgenid device
// file exists and 0 otherwise.
static int aws_snapsafe_check_kernel_support(void) {
// This file-exist method is generally brittle. But for our purpose, this
// should be more than fine.
if (access(CRYPTO_get_sysgenid_path(), F_OK) != 0) {
return 0;
}
return 1;
}

static void do_aws_snapsafe_init(void) {
*snapsafety_state_bss_get() = SNAPSAFETY_STATE_NOT_SUPPORTED;
*sgc_addr_bss_get() = NULL;

if (aws_snapsafe_check_kernel_support() != 1) {
return;
}
*snapsafety_state_bss_get() = SNAPSAFETY_STATE_FAILED_INITIALISE;

int fd_sgc = open(CRYPTO_get_sysgenid_path(), O_RDONLY);
if (fd_sgc == -1) {
return;
}

void *addr = mmap(NULL, sizeof(uint32_t), PROT_READ, MAP_SHARED, fd_sgc, 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 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);

if (addr == MAP_FAILED) {
return;
}

// sgc_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;
}

static uint32_t aws_snapsafe_read_sgn(void) {
if (*snapsafety_state_bss_get() == SNAPSAFETY_STATE_SUCCESS_INITIALISE) {
return **sgc_addr_bss_get();
}

return 0;
}

int CRYPTO_get_snapsafe_generation(uint32_t *snapsafe_generation_number) {
CRYPTO_once(aws_snapsafe_init_bss_get(), do_aws_snapsafe_init);

int state = *snapsafety_state_bss_get();
switch (state) {
case SNAPSAFETY_STATE_NOT_SUPPORTED:
*snapsafe_generation_number = 0;
return 1;
case SNAPSAFETY_STATE_SUCCESS_INITIALISE:
*snapsafe_generation_number = aws_snapsafe_read_sgn();
return 1;
case SNAPSAFETY_STATE_FAILED_INITIALISE:
*snapsafe_generation_number = 0;
return 0;
default:
// No other state should be possible.
abort();
}
}

int CRYPTO_get_snapsafe_active(void) {
CRYPTO_once(aws_snapsafe_init_bss_get(), do_aws_snapsafe_init);

if (*snapsafety_state_bss_get() == SNAPSAFETY_STATE_SUCCESS_INITIALISE) {
return 1;
}

return 0;
}

int CRYPTO_get_snapsafe_supported(void) {
CRYPTO_once(aws_snapsafe_init_bss_get(), do_aws_snapsafe_init);

if (*snapsafety_state_bss_get() == SNAPSAFETY_STATE_NOT_SUPPORTED) {
return 0;
}

return 1;
}

#else // !defined(OPENSSL_LINUX)

int CRYPTO_get_snapsafe_generation(uint32_t *snapsafe_generation_number) {
*snapsafe_generation_number = 0;
return 1;
}

int CRYPTO_get_snapsafe_active(void) { return 0; }

int CRYPTO_get_snapsafe_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_SNAPSAFE_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 (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)) {
return 0;
justsmth marked this conversation as resolved.
Show resolved Hide resolved
}

close(fd_sgn);

return 1;
}
#endif
Loading
Loading