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

Merged
merged 17 commits into from
Jun 20, 2024
Merged
12 changes: 12 additions & 0 deletions BUILDING.md
Original file line number Diff line number Diff line change
Expand Up @@ -222,3 +222,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 @@ -91,6 +91,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 @@ -741,6 +741,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 @@ -129,6 +129,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
30 changes: 28 additions & 2 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 Down Expand Up @@ -100,6 +101,10 @@ struct rand_thread_state {
// fork-unsafe buffering was enabled.
int fork_unsafe_buffering;

// snapsafe_generation_id is non-zero when active. When the value changes,
justsmth marked this conversation as resolved.
Show resolved Hide resolved
// 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
// a process.
Expand Down Expand Up @@ -382,6 +387,9 @@ 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);
justsmth marked this conversation as resolved.
Show resolved Hide resolved

// 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
// don't reseed with it so, from the point of view of FIPS, this doesn't
Expand All @@ -395,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 || fork_unsafe_buffering) {
// 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));
justsmth marked this conversation as resolved.
Show resolved Hide resolved
} else if (!have_rdrand()) {
// No alternative so block for OS entropy.
Expand Down Expand Up @@ -451,6 +462,7 @@ 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 @@ -470,6 +482,8 @@ void RAND_bytes_with_additional_data(uint8_t *out, size_t out_len,
}

if (state->calls >= kReseedInterval ||
// 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
Expand Down Expand Up @@ -505,6 +519,7 @@ 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 @@ -539,6 +554,17 @@ void RAND_bytes_with_additional_data(uint8_t *out, size_t out_len,

OPENSSL_cleanse(additional_data, 32);

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();
}
}
#if defined(BORINGSSL_FIPS)
CRYPTO_STATIC_MUTEX_unlock_read(state_clear_all_lock_bss_get());
#endif
Expand All @@ -551,7 +577,7 @@ int RAND_bytes(uint8_t *out, size_t out_len) {
}

int RAND_priv_bytes(uint8_t *out, size_t out_len) {
return RAND_bytes(out, out_len);
return RAND_bytes(out, out_len);
}

int RAND_pseudo_bytes(uint8_t *buf, size_t len) {
Expand Down
142 changes: 142 additions & 0 deletions crypto/fipsmodule/rand/snapsafe_detect.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
// 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 */
justsmth marked this conversation as resolved.
Show resolved Hide resolved
#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) {
*sgc_addr_bss_get() = NULL;

if (aws_snapsafe_check_kernel_support() != 1) {
*snapsafety_state_bss_get() = SNAPSAFETY_STATE_NOT_SUPPORTED;
justsmth marked this conversation as resolved.
Show resolved Hide resolved
return;
}

/* While not totally obvious from man pages, mmap actually only allocates
* memory on page size boundaries. So just give it that hint. */
long page_size = sysconf(_SC_PAGESIZE);
if (page_size <= 0) {
*snapsafety_state_bss_get() = SNAPSAFETY_STATE_FAILED_INITIALISE;
justsmth marked this conversation as resolved.
Show resolved Hide resolved
return;
}

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

void *addr = mmap(NULL, (size_t)page_size, 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) {
*snapsafety_state_bss_get() = SNAPSAFETY_STATE_FAILED_INITIALISE;
return;
}

/* sgc_addr will now point at the mapped memory and any 4-byte read from
* this pointer will correspond to the sgn manager by the VMM. */
justsmth marked this conversation as resolved.
Show resolved Hide resolved
*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();
}
justsmth marked this conversation as resolved.
Show resolved Hide resolved

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;
}
52 changes: 52 additions & 0 deletions crypto/fipsmodule/rand/snapsafe_detect.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 OR ISC

#ifndef HEADER_SNAPSAFE_DETECT
#define HEADER_SNAPSAFE_DETECT

#include <openssl/base.h>

#ifdef __cplusplus
extern "C" {
#endif

#if !defined(AWSLC_SYSGENID_PATH)
#define AWSLC_SYSGENID_PATH "/dev/sysgenid"
#endif

// Snapsafe-type uniqueness breaking event (ube detection).
//
// CRYPTO_get_snapsafe_generation provides the snapsafe generation number for
// the current process. The snapsafe 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_snapsafe_generation| returns 0 only when the filesystem
// presents SysGenID interface (typically `/dev/sysgenid`) but the library
justsmth marked this conversation as resolved.
Show resolved Hide resolved
// is unable to initialize its use. Otherwise, it returns 1.
justsmth marked this conversation as resolved.
Show resolved Hide resolved
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,
// it returns 0.
OPENSSL_EXPORT int CRYPTO_get_snapsafe_active(void);

// CRYPTO_get_snapsafe_supported returns 1 if the file system presents the
// SysGenID interface. Otherwise, it returns 0.
OPENSSL_EXPORT int CRYPTO_get_snapsafe_supported(void);

// CRYPTO_get_sysgenid_path returns the path used for the SysGenId interface.
OPENSSL_EXPORT const char *CRYPTO_get_sysgenid_path(void);

#ifdef __cplusplus
}
#endif

#endif /* HEADER_SNAPSAFE_DETECT */
Loading
Loading