Skip to content

Commit

Permalink
Snapsafe-type uniqueness breaking event detection (#1640)
Browse files Browse the repository at this point in the history
  • Loading branch information
justsmth authored Jun 20, 2024
1 parent 6b26139 commit f31fd31
Show file tree
Hide file tree
Showing 16 changed files with 434 additions and 7 deletions.
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
33 changes: 31 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 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
// 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);

// 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));
} 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 @@ -538,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 All @@ -551,7 +580,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
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;
}

close(fd_sgn);

return 1;
}
#endif
59 changes: 59 additions & 0 deletions crypto/fipsmodule/rand/snapsafe_detect.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// 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 (default is `/dev/sysgenid`) but we are
// is unable to initialize its use. Otherwise, it returns 1.
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);

#if defined(OPENSSL_LINUX) && defined(AWSLC_SNAPSAFE_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_SNAPSAFE_DETECT */
Loading

0 comments on commit f31fd31

Please sign in to comment.