Skip to content

Commit

Permalink
Check fd status before using urandom (#4352)
Browse files Browse the repository at this point in the history
  • Loading branch information
goatgoose authored Feb 5, 2024
1 parent 33382e6 commit 827ec00
Show file tree
Hide file tree
Showing 4 changed files with 223 additions and 33 deletions.
36 changes: 36 additions & 0 deletions tests/unit/s2n_handshake_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include <fcntl.h>
#include <stdint.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>

#include "api/s2n.h"
Expand Down Expand Up @@ -434,6 +435,41 @@ int main(int argc, char **argv)
}
}

/* Ensure that a handshake can be performed after all file descriptors are closed */
{
/* A fork is created to ensure that closing file descriptors (like stdout) won't impact
* other tests.
*/
pid_t pid = fork();
if (pid == 0) {
long max_file_descriptors = sysconf(_SC_OPEN_MAX);
for (long fd = 0; fd < max_file_descriptors; fd++) {
EXPECT_TRUE(fd <= INT_MAX);
close((int) fd);
}

DEFER_CLEANUP(struct s2n_cert_chain_and_key *chain_and_key = NULL, s2n_cert_chain_and_key_ptr_free);
EXPECT_SUCCESS(s2n_test_cert_chain_and_key_new(&chain_and_key,
S2N_DEFAULT_TEST_CERT_CHAIN, S2N_DEFAULT_TEST_PRIVATE_KEY));
EXPECT_NOT_NULL(chain_and_key);

DEFER_CLEANUP(struct s2n_config *config = s2n_config_new(), s2n_config_ptr_free);
EXPECT_NOT_NULL(config);
EXPECT_SUCCESS(s2n_config_set_cipher_preferences(config, "default"));
EXPECT_SUCCESS(s2n_config_add_cert_chain_and_key_to_store(config, chain_and_key));
EXPECT_SUCCESS(s2n_config_set_verification_ca_location(config, S2N_DEFAULT_TEST_CERT_CHAIN, NULL));
EXPECT_SUCCESS(s2n_config_disable_x509_verification(config));

EXPECT_SUCCESS(test_cipher_preferences(config, config, chain_and_key, S2N_SIGNATURE_RSA));

exit(EXIT_SUCCESS);
}

int status = 0;
EXPECT_EQUAL(waitpid(pid, &status, 0), pid);
EXPECT_EQUAL(status, EXIT_SUCCESS);
}

END_TEST();
return 0;
}
62 changes: 62 additions & 0 deletions tests/unit/s2n_random_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@
#define NUMBER_OF_RANGE_FUNCTION_CALLS 200
#define MAX_REPEATED_OUTPUT 4

S2N_RESULT s2n_rand_device_validate(struct s2n_rand_device *device);
S2N_RESULT s2n_rand_get_urandom_for_test(struct s2n_rand_device **device);
S2N_RESULT s2n_rand_set_urandom_for_test();

struct random_test_case {
const char *test_case_label;
int (*test_case_cb)(struct random_test_case *test_case);
Expand Down Expand Up @@ -788,6 +792,63 @@ static int s2n_random_rand_bytes_after_cleanup_cb(struct random_test_case *test_
return S2N_SUCCESS;
}

static int s2n_random_invalid_urandom_fd_cb(struct random_test_case *test_case)
{
EXPECT_SUCCESS(s2n_disable_atexit());

struct s2n_rand_device *dev_urandom = NULL;
EXPECT_OK(s2n_rand_get_urandom_for_test(&dev_urandom));
EXPECT_NOT_NULL(dev_urandom);

for (size_t test = 0; test <= 1; test++) {
EXPECT_EQUAL(dev_urandom->fd, -1);

/* Validation should fail before initialization. */
EXPECT_ERROR(s2n_rand_device_validate(dev_urandom));

EXPECT_SUCCESS(s2n_init());

/* Validation should succeed after initialization. */
EXPECT_OK(s2n_rand_device_validate(dev_urandom));

/* Override the mix callback with urandom, in case support for rdrand is detected and enabled. */
EXPECT_OK(s2n_rand_set_urandom_for_test());

EXPECT_TRUE(dev_urandom->fd > STDERR_FILENO);
if (test == 0) {
/* Close the file descriptor. */
EXPECT_EQUAL(close(dev_urandom->fd), 0);
} else {
/* Make the file descriptor invalid by pointing it to STDERR. */
dev_urandom->fd = STDERR_FILENO;
}

/* Validation should fail when the file descriptor is invalid. */
EXPECT_ERROR(s2n_rand_device_validate(dev_urandom));

s2n_stack_blob(rand_data, 16, 16);
EXPECT_OK(s2n_get_public_random_data(&rand_data));

uint64_t public_bytes_used = 0;
EXPECT_OK(s2n_get_public_random_bytes_used(&public_bytes_used));

if (s2n_is_in_fips_mode()) {
/* The urandom implementation should not be in use when s2n-tls is in FIPS mode. */
EXPECT_EQUAL(public_bytes_used, 0);
} else {
/* When the urandom implementation is used, the file descriptor is re-opened and
* validation should succeed.
*/
EXPECT_OK(s2n_rand_device_validate(dev_urandom));
EXPECT_TRUE(public_bytes_used > 0);
}

EXPECT_SUCCESS(s2n_cleanup());
}

return S2N_SUCCESS;
}

struct random_test_case random_test_cases[] = {
{ "Random API.", s2n_random_test_case_default_cb, CLONE_TEST_DETERMINE_AT_RUNTIME, EXIT_SUCCESS },
{ "Random API without prediction resistance.", s2n_random_test_case_without_pr_cb, CLONE_TEST_DETERMINE_AT_RUNTIME, EXIT_SUCCESS },
Expand All @@ -800,6 +861,7 @@ struct random_test_case random_test_cases[] = {
*/
{ "Test failure.", s2n_random_test_case_failure_cb, CLONE_TEST_DETERMINE_AT_RUNTIME, 1 },
{ "Test libcrypto's RAND engine is reset correctly after manual s2n_cleanup()", s2n_random_rand_bytes_after_cleanup_cb, CLONE_TEST_DETERMINE_AT_RUNTIME, EXIT_SUCCESS },
{ "Test getting entropy with an invalid file descriptor", s2n_random_invalid_urandom_fd_cb, CLONE_TEST_DETERMINE_AT_RUNTIME, EXIT_SUCCESS },
};

int main(int argc, char **argv)
Expand Down
149 changes: 116 additions & 33 deletions utils/s2n_random.c
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
#include "crypto/s2n_drbg.h"
#include "crypto/s2n_fips.h"
#include "error/s2n_errno.h"
#include "s2n_io.h"
#include "stuffer/s2n_stuffer.h"
#include "utils/s2n_fork_detection.h"
#include "utils/s2n_init.h"
Expand All @@ -75,8 +76,6 @@
#include "utils/s2n_result.h"
#include "utils/s2n_safety.h"

#define ENTROPY_SOURCE "/dev/urandom"

#if defined(O_CLOEXEC)
#define ENTROPY_FLAGS O_RDONLY | O_CLOEXEC
#else
Expand All @@ -92,7 +91,10 @@
/* Placeholder value for an uninitialized entropy file descriptor */
#define UNINITIALIZED_ENTROPY_FD -1

static int entropy_fd = UNINITIALIZED_ENTROPY_FD;
static struct s2n_rand_device s2n_dev_urandom = {
.source = "/dev/urandom",
.fd = UNINITIALIZED_ENTROPY_FD,
};

struct s2n_rand_state {
uint64_t cached_fork_generation_number;
Expand All @@ -115,15 +117,23 @@ static __thread struct s2n_rand_state s2n_per_thread_rand_state = {
.drbgs_initialized = false
};

static int s2n_rand_init_impl(void);
static int s2n_rand_cleanup_impl(void);
static int s2n_rand_urandom_impl(void *ptr, uint32_t size);
static int s2n_rand_rdrand_impl(void *ptr, uint32_t size);
static int s2n_rand_init_cb_impl(void);
static int s2n_rand_cleanup_cb_impl(void);
static int s2n_rand_get_entropy_from_urandom(void *ptr, uint32_t size);
static int s2n_rand_get_entropy_from_rdrand(void *ptr, uint32_t size);

static s2n_rand_init_callback s2n_rand_init_cb = s2n_rand_init_cb_impl;
static s2n_rand_cleanup_callback s2n_rand_cleanup_cb = s2n_rand_cleanup_cb_impl;
static s2n_rand_seed_callback s2n_rand_seed_cb = s2n_rand_get_entropy_from_urandom;
static s2n_rand_mix_callback s2n_rand_mix_cb = s2n_rand_get_entropy_from_urandom;

static s2n_rand_init_callback s2n_rand_init_cb = s2n_rand_init_impl;
static s2n_rand_cleanup_callback s2n_rand_cleanup_cb = s2n_rand_cleanup_impl;
static s2n_rand_seed_callback s2n_rand_seed_cb = s2n_rand_urandom_impl;
static s2n_rand_mix_callback s2n_rand_mix_cb = s2n_rand_urandom_impl;
static int s2n_rand_entropy_fd_close_ptr(int *fd)
{
if (fd && *fd != UNINITIALIZED_ENTROPY_FD) {
close(*fd);
}
return S2N_SUCCESS;
}

/* non-static for SAW proof */
bool s2n_cpu_supports_rdrand()
Expand Down Expand Up @@ -332,9 +342,76 @@ S2N_RESULT s2n_get_private_random_bytes_used(uint64_t *bytes_used)
return S2N_RESULT_OK;
}

static int s2n_rand_urandom_impl(void *ptr, uint32_t size)
S2N_RESULT s2n_rand_get_urandom_for_test(struct s2n_rand_device **device)
{
RESULT_ENSURE_REF(device);
RESULT_ENSURE(s2n_in_unit_test(), S2N_ERR_NOT_IN_UNIT_TEST);
*device = &s2n_dev_urandom;
return S2N_RESULT_OK;
}

static S2N_RESULT s2n_rand_device_open(struct s2n_rand_device *device)
{
RESULT_ENSURE_REF(device);
RESULT_ENSURE_REF(device->source);

DEFER_CLEANUP(int fd = -1, s2n_rand_entropy_fd_close_ptr);
S2N_IO_RETRY_EINTR(fd, open(device->source, ENTROPY_FLAGS));
RESULT_ENSURE(fd >= 0, S2N_ERR_OPEN_RANDOM);

struct stat st = { 0 };
RESULT_ENSURE(fstat(fd, &st) == 0, S2N_ERR_OPEN_RANDOM);
device->dev = st.st_dev;
device->ino = st.st_ino;
device->mode = st.st_mode;
device->rdev = st.st_rdev;

device->fd = fd;

/* Disable closing the file descriptor with defer cleanup */
fd = UNINITIALIZED_ENTROPY_FD;

return S2N_RESULT_OK;
}

S2N_RESULT s2n_rand_device_validate(struct s2n_rand_device *device)
{
RESULT_ENSURE_REF(device);
RESULT_ENSURE_NE(device->fd, UNINITIALIZED_ENTROPY_FD);

/* Ensure that the random device is still valid by comparing it to the current file descriptor
* status. From:
* https://github.com/openssl/openssl/blob/260d97229c467d17934ca3e2e0455b1b5c0994a6/providers/implementations/rands/seeding/rand_unix.c#L513
*/
struct stat st = { 0 };
RESULT_ENSURE(fstat(device->fd, &st) == 0, S2N_ERR_OPEN_RANDOM);
RESULT_ENSURE_EQ(device->dev, st.st_dev);
RESULT_ENSURE_EQ(device->ino, st.st_ino);
RESULT_ENSURE_EQ(device->rdev, st.st_rdev);

/* Ensure that the mode is the same (equal to 0 when xor'd), but don't check the permission bits. */
mode_t permission_mask = ~(S_IRWXU | S_IRWXG | S_IRWXO);
RESULT_ENSURE_EQ((device->mode ^ st.st_mode) & permission_mask, 0);

return S2N_RESULT_OK;
}

static int s2n_rand_get_entropy_from_urandom(void *ptr, uint32_t size)
{
POSIX_ENSURE(entropy_fd != UNINITIALIZED_ENTROPY_FD, S2N_ERR_NOT_INITIALIZED);
POSIX_ENSURE_REF(ptr);
POSIX_ENSURE(s2n_dev_urandom.fd != UNINITIALIZED_ENTROPY_FD, S2N_ERR_NOT_INITIALIZED);

/* It's possible that the file descriptor pointing to /dev/urandom was closed or changed from
* when it was last opened. Ensure that the file descriptor is still valid, and if it isn't,
* re-open it before getting entropy.
*
* If the file descriptor is invalid and the process doesn't have access to /dev/urandom (as is
* the case within a chroot tree), an error is raised here before attempting to indefinitely
* read.
*/
if (s2n_result_is_error(s2n_rand_device_validate(&s2n_dev_urandom))) {
POSIX_GUARD_RESULT(s2n_rand_device_open(&s2n_dev_urandom));
}

uint8_t *data = ptr;
uint32_t n = size;
Expand All @@ -343,7 +420,7 @@ static int s2n_rand_urandom_impl(void *ptr, uint32_t size)

while (n) {
errno = 0;
int r = read(entropy_fd, data, n);
int r = read(s2n_dev_urandom.fd, data, n);
if (r <= 0) {
/*
* A non-blocking read() on /dev/urandom should "never" fail,
Expand Down Expand Up @@ -450,19 +527,16 @@ RAND_METHOD s2n_openssl_rand_method = {
};
#endif

static int s2n_rand_init_impl(void)
static int s2n_rand_init_cb_impl(void)
{
OPEN:
entropy_fd = open(ENTROPY_SOURCE, ENTROPY_FLAGS);
if (entropy_fd == -1) {
if (errno == EINTR) {
goto OPEN;
}
POSIX_BAIL(S2N_ERR_OPEN_RANDOM);
}
/* Currently, s2n-tls may mix in entropy from urandom into every generation of random data. The
* file descriptor is opened on initialization for better performance reading from urandom, and
* to ensure that urandom is accessible from within a chroot tree.
*/
POSIX_GUARD_RESULT(s2n_rand_device_open(&s2n_dev_urandom));

if (s2n_cpu_supports_rdrand()) {
s2n_rand_mix_cb = s2n_rand_rdrand_impl;
s2n_rand_mix_cb = s2n_rand_get_entropy_from_rdrand;
}

return S2N_SUCCESS;
Expand Down Expand Up @@ -502,12 +576,14 @@ S2N_RESULT s2n_rand_init(void)
return S2N_RESULT_OK;
}

static int s2n_rand_cleanup_impl(void)
static int s2n_rand_cleanup_cb_impl(void)
{
POSIX_ENSURE(entropy_fd != UNINITIALIZED_ENTROPY_FD, S2N_ERR_NOT_INITIALIZED);
POSIX_ENSURE(s2n_dev_urandom.fd != UNINITIALIZED_ENTROPY_FD, S2N_ERR_NOT_INITIALIZED);

POSIX_GUARD(close(entropy_fd));
entropy_fd = UNINITIALIZED_ENTROPY_FD;
if (s2n_result_is_ok(s2n_rand_device_validate(&s2n_dev_urandom))) {
POSIX_GUARD(close(s2n_dev_urandom.fd));
}
s2n_dev_urandom.fd = UNINITIALIZED_ENTROPY_FD;

return S2N_SUCCESS;
}
Expand All @@ -530,10 +606,10 @@ S2N_RESULT s2n_rand_cleanup(void)
}
#endif

s2n_rand_init_cb = s2n_rand_init_impl;
s2n_rand_cleanup_cb = s2n_rand_cleanup_impl;
s2n_rand_seed_cb = s2n_rand_urandom_impl;
s2n_rand_mix_cb = s2n_rand_urandom_impl;
s2n_rand_init_cb = s2n_rand_init_cb_impl;
s2n_rand_cleanup_cb = s2n_rand_cleanup_cb_impl;
s2n_rand_seed_cb = s2n_rand_get_entropy_from_urandom;
s2n_rand_mix_cb = s2n_rand_get_entropy_from_urandom;

return S2N_RESULT_OK;
}
Expand Down Expand Up @@ -571,11 +647,18 @@ S2N_RESULT s2n_set_private_drbg_for_test(struct s2n_drbg drbg)
return S2N_RESULT_OK;
}

S2N_RESULT s2n_rand_set_urandom_for_test()
{
RESULT_ENSURE(s2n_in_unit_test(), S2N_ERR_NOT_IN_UNIT_TEST);
s2n_rand_mix_cb = s2n_rand_get_entropy_from_urandom;
return S2N_RESULT_OK;
}

/*
* volatile is important to prevent the compiler from
* re-ordering or optimizing the use of RDRAND.
*/
static int s2n_rand_rdrand_impl(void *data, uint32_t size)
static int s2n_rand_get_entropy_from_rdrand(void *data, uint32_t size)
{
#if defined(__x86_64__) || defined(__i386__)
struct s2n_blob out = { 0 };
Expand Down
9 changes: 9 additions & 0 deletions utils/s2n_random.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,15 @@
#include "utils/s2n_blob.h"
#include "utils/s2n_result.h"

struct s2n_rand_device {
const char *source;
int fd;
dev_t dev;
ino_t ino;
mode_t mode;
dev_t rdev;
};

S2N_RESULT s2n_rand_init(void);
S2N_RESULT s2n_rand_cleanup(void);
S2N_RESULT s2n_get_seed_entropy(struct s2n_blob *blob);
Expand Down

0 comments on commit 827ec00

Please sign in to comment.