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

Check fd status before using urandom #4352

Merged
merged 10 commits into from
Feb 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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
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
Loading