Skip to content

Commit

Permalink
Integrate Snapsafe /dev/sysgenid into rand
Browse files Browse the repository at this point in the history
  • Loading branch information
justsmth committed Jun 14, 2024
1 parent 98735a2 commit 0b2960b
Show file tree
Hide file tree
Showing 5 changed files with 267 additions and 8 deletions.
8 changes: 8 additions & 0 deletions BUILDING.md
Original file line number Diff line number Diff line change
Expand Up @@ -222,3 +222,11 @@ 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 (ube) detection
using SysGenID (https://lkml.org/lkml/2021/3/8/677). The SysGenID interface
is not yet finalised and has little support. Therefore, we only use this
as a hardening mechanism and fail open. This behaviour is similar to how
fork detection works in AWS-LC.
1 change: 1 addition & 0 deletions crypto/fipsmodule/bcm.c
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@
#include "pbkdf/pbkdf.c"
#include "rand/ctrdrbg.c"
#include "rand/fork_detect.c"
#include "rand/snapsafe_detect.c"
#include "rand/rand.c"
#include "rand/urandom.c"
#include "rsa/blinding.c"
Expand Down
35 changes: 27 additions & 8 deletions crypto/fipsmodule/rand/rand.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@
#include <openssl/mem.h>
#include <openssl/type_check.h>

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


// It's assumed that the operating system always has an unfailing source of
Expand Down Expand Up @@ -100,6 +100,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,
// entropy must be collected.
uint32_t snapsafe_generation_id;

#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 +386,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_id = 0;
int snapsafe_status = CRYPTO_get_snapsafe_generation(&snapsafe_generation_id);

// 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 +402,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 +461,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_id = snapsafe_generation_id;

#if defined(BORINGSSL_FIPS)
if (state != &stack_state) {
Expand All @@ -470,6 +481,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_id != snapsafe_generation_id ||
// 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,8 +518,16 @@ 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_id = snapsafe_generation_id;
OPENSSL_cleanse(seed, CTR_DRBG_ENTROPY_LEN);
OPENSSL_cleanse(add_data_for_reseed, CTR_DRBG_ENTROPY_LEN);

CRYPTO_get_snapsafe_generation(&snapsafe_generation_id);
if (snapsafe_generation_id != state->snapsafe_generation_id) {
// Unexpected change to snapsafe generation id.
// Snapshot/clone was crated while this operation was active.
abort();
}
} else {
#if defined(BORINGSSL_FIPS)
CRYPTO_STATIC_MUTEX_lock_read(state_clear_all_lock_bss_get());
Expand Down Expand Up @@ -551,12 +572,10 @@ 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) {
return RAND_bytes(buf, len);
}
int RAND_pseudo_bytes(uint8_t *buf, size_t len) { return RAND_bytes(buf, len); }

void RAND_get_system_entropy_for_custom_prng(uint8_t *buf, size_t len) {
if (len > 256) {
Expand Down
168 changes: 168 additions & 0 deletions crypto/fipsmodule/rand/snapsafe_detect.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 OR ISC

#include <openssl/crypto.h>

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

/* Test mode state */
#define SNAPSAFE_TEST_MODE_DISABLED 0x00
#define SNAPSAFE_TEST_MODE_ENABLED 0x01
DEFINE_STATIC_MUTEX(test_mode_mutex);
static volatile int test_mode_enabled = 0;
static volatile uint32_t test_mode_sgn = 0;
static volatile int test_mode_status = 1;

#if defined(OPENSSL_LINUX)
#include <fcntl.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/random.h>
#include <unistd.h>

/* Snapsafety state */
#define SNAPSAFETY_STATE_FAILED_INITIALISE 0x00
#define SNAPSAFETY_STATE_SUCCESS_INITIALISE 0x01
#define SNAPSAFETY_STATE_NOT_SUPPORTED 0x02

/* Sysgenid state */
/* These values can be found here: https://lkml.org/lkml/2021/3/8/677 */
#define SYSGENID_IOCTL 0xE4
#define SYSGENID_TRIGGER_GEN_UPDATE _IO(SYSGENID_IOCTL, 3)

static const char *sgc_file_path = "/dev/sysgenid";
static volatile uint32_t *sgc_addr = NULL;
DEFINE_STATIC_ONCE(aws_snapsafe_init)

static int snapsafety_state = SNAPSAFETY_STATE_FAILED_INITIALISE;

/* 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(sgc_file_path, F_OK) != 0) {
return 0;
}
return 1;
}

static void do_aws_snapsafe_init(void) {
if (aws_snapsafe_check_kernel_support() != 1) {
snapsafety_state = SNAPSAFETY_STATE_NOT_SUPPORTED;
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 = SNAPSAFETY_STATE_FAILED_INITIALISE;
return;
}

if (sgc_addr == NULL) {
int fd_sgc = open(sgc_file_path, O_RDONLY);
if (fd_sgc == -1) {
snapsafety_state = 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 = 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. */
sgc_addr = addr;

snapsafety_state = SNAPSAFETY_STATE_SUCCESS_INITIALISE;
}
}

static uint32_t aws_snapsafe_read_sgn(void) {
if (snapsafety_state == SNAPSAFETY_STATE_SUCCESS_INITIALISE) {
return *sgc_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_STATIC_MUTEX_lock_read(test_mode_mutex_bss_get());
if (test_mode_enabled == SNAPSAFE_TEST_MODE_ENABLED) {
*snapsafe_generation_number = test_mode_sgn;
return test_mode_status;
}
CRYPTO_STATIC_MUTEX_unlock_read(test_mode_mutex_bss_get());

if (snapsafety_state == SNAPSAFETY_STATE_FAILED_INITIALISE) {
*snapsafe_generation_number = 0;
return 0;
}

if (snapsafety_state != SNAPSAFETY_STATE_SUCCESS_INITIALISE) {
// Use 0 to indicate snapsafe is not active.
*snapsafe_generation_number = 0;
} else {
// Retrieve value from OS
*snapsafe_generation_number = aws_snapsafe_read_sgn();
}

return 1;
}

#else // !defined(OPENSSL_LINUX)

int CRYPTO_get_snapsafe_generation(uint32_t *snapsafe_generation_number) {
CRYPTO_STATIC_MUTEX_lock_read(test_mode_mutex_bss_get());
if (test_mode_enabled == SNAPSAFE_TEST_MODE_ENABLED) {
*snapsafe_generation_number = test_mode_sgn;
return test_mode_status;
}
CRYPTO_STATIC_MUTEX_unlock_read(test_mode_mutex_bss_get());

*snapsafe_generation_number = 0;
return 1;
}

#endif // defined(OPENSSL_LINUX) && !defined(FIPS)

OPENSSL_EXPORT void HAZMAT_snapsafe_testing_status(int status) {
CRYPTO_STATIC_MUTEX_lock_write(test_mode_mutex_bss_get());
test_mode_status = status;
CRYPTO_STATIC_MUTEX_unlock_write(test_mode_mutex_bss_get());
}

OPENSSL_EXPORT void HAZMAT_snapsafe_testing(int enable) {
CRYPTO_STATIC_MUTEX_lock_write(test_mode_mutex_bss_get());
if (enable == 0) {
test_mode_enabled = SNAPSAFE_TEST_MODE_DISABLED;
} else {
test_mode_enabled = SNAPSAFE_TEST_MODE_ENABLED;
}
CRYPTO_STATIC_MUTEX_unlock_write(test_mode_mutex_bss_get());
}

OPENSSL_EXPORT void HAZMAT_snapsafe_testing_value(uint32_t val) {
CRYPTO_STATIC_MUTEX_lock_write(test_mode_mutex_bss_get());
test_mode_sgn = val;
CRYPTO_STATIC_MUTEX_unlock_write(test_mode_mutex_bss_get());
}
63 changes: 63 additions & 0 deletions crypto/fipsmodule/rand/snapsafe_detect.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// 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_FILE_PATH AWSLC_SYSGENID_PATH
#else
// If no user-specified path is provided at compile-time, use a default
// path. Path taken from https://lkml.org/lkml/2021/3/8/677. This path
// might change in the future, becasue the SysGenID interface is not yet
// stable.
#define AWSLC_SYSGENID_FILE_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
// is unable to initialize its use. In all other cases, it returns 1.
OPENSSL_EXPORT int CRYPTO_get_snapsafe_generation(
uint32_t *snapsafe_generation_number);

// Below functions are strictly for testing only!

// HAZMAT_snapsafe_testing is an internal function used for testing snapsafe.
// Call |HAZMAT_sysgenid_testing| with a non-zero value to enable testing, otherwise
// testing will be disabled.
// Returns 1 if succesfull and 0 otherwise.
OPENSSL_EXPORT void HAZMAT_snapsafe_testing(int enable);

// HAZMAT_snapsafe_testing_status is an internal function used for testing snapsafe.
// The value |val| passed to this function will be used as the snapsafe status
// returned by |CRYPTO_get_snapsafe_generation| when testing has been enabled.
OPENSSL_EXPORT void HAZMAT_snapsafe_testing_status(int status);

// HAZMAT_snapsafe_testing_value is an internal function used for testing snapsafe.
// The value |val| passed to this function will be used as the snapsafe generation
// number when testing has been enabled.
OPENSSL_EXPORT void HAZMAT_snapsafe_testing_value(uint32_t val);

#ifdef __cplusplus
}
#endif

#endif /* HEADER_SNAPSAFE_DETECT */

0 comments on commit 0b2960b

Please sign in to comment.