From 87f0c9e543578d98f00a54165bbdb2a43a4dac80 Mon Sep 17 00:00:00 2001 From: Lindsay Stewart Date: Fri, 15 Sep 2023 05:20:06 -0700 Subject: [PATCH] ktls: recv alerts (#4199) --- tests/unit/s2n_ktls_io_test.c | 85 +++++++++++++++++ tests/unit/s2n_self_talk_ktls_test.c | 100 ++++++++++++++++++-- tests/unit/s2n_shutdown_test.c | 135 +++++++++++++++++++++++++++ tls/s2n_ktls.h | 1 + tls/s2n_ktls_io.c | 33 +++++++ tls/s2n_recv.c | 6 ++ 6 files changed, 354 insertions(+), 6 deletions(-) diff --git a/tests/unit/s2n_ktls_io_test.c b/tests/unit/s2n_ktls_io_test.c index f58d89c361e..bb0249d9b52 100644 --- a/tests/unit/s2n_ktls_io_test.c +++ b/tests/unit/s2n_ktls_io_test.c @@ -18,6 +18,7 @@ #include "testlib/s2n_mem_testlib.h" #include "testlib/s2n_testlib.h" #include "tls/s2n_ktls.h" +#include "tls/s2n_tls.h" #include "utils/s2n_random.h" #define S2N_TEST_TO_SEND 10 @@ -1056,5 +1057,89 @@ int main(int argc, char **argv) }; }; + /* Test: s2n_ktls_read_full_record */ + { + const struct iovec test_iovec = { + .iov_base = test_data, + .iov_len = sizeof(test_data), + }; + s2n_blocked_status blocked = S2N_NOT_BLOCKED; + + const size_t max_frag_len = S2N_DEFAULT_FRAGMENT_LENGTH; + /* Our test assumptions are wrong if this isn't true */ + EXPECT_TRUE(max_frag_len < sizeof(test_data)); + + /* Safety */ + { + DEFER_CLEANUP(struct s2n_connection *conn = s2n_connection_new(S2N_SERVER), + s2n_connection_ptr_free); + EXPECT_NOT_NULL(conn); + uint8_t record_type = 0; + EXPECT_FAILURE_WITH_ERRNO(s2n_ktls_read_full_record(NULL, &record_type), + S2N_ERR_NULL); + EXPECT_FAILURE_WITH_ERRNO(s2n_ktls_read_full_record(conn, NULL), + S2N_ERR_NULL); + }; + + /* Test: Basic read succeeds */ + { + DEFER_CLEANUP(struct s2n_connection *conn = s2n_connection_new(S2N_CLIENT), + s2n_connection_ptr_free); + EXPECT_NOT_NULL(conn); + + DEFER_CLEANUP(struct s2n_test_ktls_io_stuffer_pair pair = { 0 }, + s2n_ktls_io_stuffer_pair_free); + EXPECT_OK(s2n_test_init_ktls_io_stuffer(conn, conn, &pair)); + struct s2n_test_ktls_io_stuffer *ctx = &pair.client_in; + + size_t written = 0; + EXPECT_OK(s2n_ktls_sendmsg(ctx, TLS_ALERT, &test_iovec, 1, &blocked, &written)); + EXPECT_EQUAL(written, sizeof(test_data)); + + uint8_t record_type = 0; + EXPECT_SUCCESS(s2n_ktls_read_full_record(conn, &record_type)); + EXPECT_EQUAL(record_type, TLS_ALERT); + + EXPECT_EQUAL(conn->in.blob.allocated, max_frag_len); + EXPECT_EQUAL(s2n_stuffer_data_available(&conn->in), max_frag_len); + uint8_t *read = s2n_stuffer_raw_read(&conn->in, max_frag_len); + EXPECT_BYTEARRAY_EQUAL(read, test_data, max_frag_len); + }; + + /* Test: Receive does not completely fill the output buffer */ + { + const size_t small_frag_len = 10; + EXPECT_TRUE(small_frag_len < max_frag_len); + EXPECT_TRUE(small_frag_len < sizeof(test_data)); + struct iovec small_test_iovec = test_iovec; + small_test_iovec.iov_len = small_frag_len; + + DEFER_CLEANUP(struct s2n_connection *conn = s2n_connection_new(S2N_CLIENT), + s2n_connection_ptr_free); + EXPECT_NOT_NULL(conn); + + DEFER_CLEANUP(struct s2n_test_ktls_io_stuffer_pair pair = { 0 }, + s2n_ktls_io_stuffer_pair_free); + EXPECT_OK(s2n_test_init_ktls_io_stuffer(conn, conn, &pair)); + struct s2n_test_ktls_io_stuffer *ctx = &pair.client_in; + + size_t written = 0; + EXPECT_OK(s2n_ktls_sendmsg(ctx, TLS_ALERT, &small_test_iovec, 1, &blocked, &written)); + EXPECT_EQUAL(written, small_frag_len); + + uint8_t record_type = 0; + EXPECT_SUCCESS(s2n_ktls_read_full_record(conn, &record_type)); + EXPECT_EQUAL(record_type, TLS_ALERT); + + /* Verify that conn->in reflects the correct size of the "record" + * read and doesn't just assume the maximum read size. + */ + EXPECT_EQUAL(conn->in.blob.allocated, max_frag_len); + EXPECT_EQUAL(s2n_stuffer_data_available(&conn->in), small_frag_len); + uint8_t *read = s2n_stuffer_raw_read(&conn->in, small_frag_len); + EXPECT_BYTEARRAY_EQUAL(read, test_data, small_frag_len); + }; + }; + END_TEST(); } diff --git a/tests/unit/s2n_self_talk_ktls_test.c b/tests/unit/s2n_self_talk_ktls_test.c index 45743ccb182..797446b4677 100644 --- a/tests/unit/s2n_self_talk_ktls_test.c +++ b/tests/unit/s2n_self_talk_ktls_test.c @@ -23,6 +23,7 @@ #include "s2n_test.h" #include "testlib/s2n_testlib.h" #include "tls/s2n_ktls.h" +#include "tls/s2n_tls.h" #include "utils/s2n_random.h" /* There are issues with MacOS and FreeBSD so we define the constant ourselves. @@ -116,6 +117,12 @@ int main(int argc, char **argv) EXPECT_SUCCESS(s2n_config_set_unsafe_for_testing(config)); EXPECT_SUCCESS(s2n_config_set_cipher_preferences(config, "default")); + /* Even if we detected ktls support at compile time, enabling ktls + * can fail at runtime if the system is not properly configured. + */ + bool ktls_send_supported = true; + bool ktls_recv_supported = true; + /* Test enabling ktls for sending */ { DEFER_CLEANUP(struct s2n_connection *client = s2n_connection_new(S2N_CLIENT), @@ -144,18 +151,27 @@ int main(int argc, char **argv) EXPECT_SUCCESS(s2n_fd_set_non_blocking(io_pair.client)); EXPECT_SUCCESS(s2n_negotiate_test_server_and_client(server, client)); - if (s2n_connection_ktls_enable_send(client) != S2N_SUCCESS) { - /* Even if we detected ktls support at compile time, enabling ktls - * can fail at runtime if the system is not properly configured. - */ + if (s2n_connection_ktls_enable_send(client) == S2N_SUCCESS) { + EXPECT_SUCCESS(s2n_connection_ktls_enable_send(server)); + } else { EXPECT_FALSE(ktls_expected); - END_TEST(); + ktls_send_supported = false; + } + + if (s2n_connection_ktls_enable_recv(client) == S2N_SUCCESS) { + EXPECT_SUCCESS(s2n_connection_ktls_enable_recv(server)); + } else { + EXPECT_FALSE(ktls_expected); + ktls_recv_supported = false; } - EXPECT_SUCCESS(s2n_connection_ktls_enable_send(server)); }; /* Test sending with ktls */ for (size_t mode_i = 0; mode_i < s2n_array_len(modes); mode_i++) { + if (!ktls_send_supported) { + break; + } + const s2n_mode mode = modes[mode_i]; DEFER_CLEANUP(struct s2n_connection *client = s2n_connection_new(S2N_CLIENT), @@ -264,6 +280,78 @@ int main(int argc, char **argv) EXPECT_SUCCESS(s2n_shutdown(reader, &blocked)); EXPECT_EQUAL(blocked, S2N_NOT_BLOCKED); + EXPECT_TRUE(s2n_connection_check_io_status(reader, S2N_IO_CLOSED)); + }; + }; + + /* Test receiving with ktls */ + for (size_t mode_i = 0; mode_i < s2n_array_len(modes); mode_i++) { + if (!ktls_recv_supported) { + break; + } + + const s2n_mode mode = modes[mode_i]; + + DEFER_CLEANUP(struct s2n_connection *client = s2n_connection_new(S2N_CLIENT), + s2n_connection_ptr_free); + EXPECT_NOT_NULL(client); + EXPECT_SUCCESS(s2n_connection_set_config(client, config)); + + DEFER_CLEANUP(struct s2n_connection *server = s2n_connection_new(S2N_SERVER), + s2n_connection_ptr_free); + EXPECT_NOT_NULL(client); + EXPECT_SUCCESS(s2n_connection_set_config(server, config)); + + DEFER_CLEANUP(struct s2n_test_io_pair io_pair = { 0 }, s2n_io_pair_close); + EXPECT_OK(s2n_new_inet_socket_pair(&io_pair)); + EXPECT_SUCCESS(s2n_connections_set_io_pair(client, server, &io_pair)); + + /* The test negotiate method assumes non-blocking sockets */ + EXPECT_SUCCESS(s2n_fd_set_non_blocking(io_pair.server)); + EXPECT_SUCCESS(s2n_fd_set_non_blocking(io_pair.client)); + EXPECT_SUCCESS(s2n_negotiate_test_server_and_client(server, client)); + + struct s2n_connection *conns[] = { + [S2N_CLIENT] = client, + [S2N_SERVER] = server, + }; + struct s2n_connection *reader = conns[mode]; + struct s2n_connection *writer = conns[S2N_PEER_MODE(mode)]; + EXPECT_SUCCESS(s2n_connection_ktls_enable_recv(reader)); + + s2n_blocked_status blocked = S2N_NOT_BLOCKED; + + /* Our IO methods are more predictable if they use blocking sockets. */ + EXPECT_SUCCESS(s2n_fd_set_blocking(io_pair.server)); + EXPECT_SUCCESS(s2n_fd_set_blocking(io_pair.client)); + + /* Test: s2n_recv not implemented yet */ + { + uint8_t buffer[10] = { 0 }; + int received = s2n_recv(reader, buffer, sizeof(buffer), &blocked); + EXPECT_FAILURE_WITH_ERRNO(received, S2N_ERR_UNIMPLEMENTED); + } + + /* Test: s2n_shutdown */ + { + /* Send some application data for the reader to skip */ + for (size_t i = 0; i < 3; i++) { + EXPECT_SUCCESS(s2n_send(writer, test_data, 10, &blocked)); + } + + /* Send the close_notify */ + EXPECT_SUCCESS(s2n_shutdown_send(writer, &blocked)); + EXPECT_EQUAL(blocked, S2N_NOT_BLOCKED); + + /* Verify that the reader skips the application data and successfully + * receives the close_notify. + * + * The close_notify was sent after the application data, so if the + * close_notify was received, then the application data was also received. + */ + EXPECT_SUCCESS(s2n_shutdown(reader, &blocked)); + EXPECT_EQUAL(blocked, S2N_NOT_BLOCKED); + EXPECT_TRUE(s2n_connection_check_io_status(reader, S2N_IO_CLOSED)); }; }; diff --git a/tests/unit/s2n_shutdown_test.c b/tests/unit/s2n_shutdown_test.c index f5e16a37897..8575942f13d 100644 --- a/tests/unit/s2n_shutdown_test.c +++ b/tests/unit/s2n_shutdown_test.c @@ -704,5 +704,140 @@ int main(int argc, char **argv) }; }; + /* Test: ktls enabled */ + { + /* Test: Successfully shutdown */ + { + DEFER_CLEANUP(struct s2n_connection *client = s2n_connection_new(S2N_CLIENT), + s2n_connection_ptr_free); + EXPECT_NOT_NULL(client); + EXPECT_OK(s2n_ktls_configure_connection(client, S2N_KTLS_MODE_SEND)); + EXPECT_OK(s2n_ktls_configure_connection(client, S2N_KTLS_MODE_RECV)); + EXPECT_OK(s2n_skip_handshake(client)); + + DEFER_CLEANUP(struct s2n_connection *server = s2n_connection_new(S2N_SERVER), + s2n_connection_ptr_free); + EXPECT_NOT_NULL(server); + EXPECT_OK(s2n_ktls_configure_connection(server, S2N_KTLS_MODE_SEND)); + EXPECT_OK(s2n_ktls_configure_connection(server, S2N_KTLS_MODE_RECV)); + EXPECT_OK(s2n_skip_handshake(server)); + + DEFER_CLEANUP(struct s2n_test_ktls_io_stuffer_pair io_pair = { 0 }, + s2n_ktls_io_stuffer_pair_free); + EXPECT_OK(s2n_test_init_ktls_io_stuffer(server, client, &io_pair)); + + s2n_blocked_status blocked = S2N_NOT_BLOCKED; + EXPECT_SUCCESS(s2n_shutdown_send(client, &blocked)); + EXPECT_TRUE(client->alert_sent); + + EXPECT_SUCCESS(s2n_shutdown(server, &blocked)); + EXPECT_TRUE(server->alert_sent); + EXPECT_TRUE(s2n_connection_check_io_status(server, S2N_IO_CLOSED)); + }; + + /* Test: Successfully shutdown after blocking */ + { + DEFER_CLEANUP(struct s2n_connection *client = s2n_connection_new(S2N_CLIENT), + s2n_connection_ptr_free); + EXPECT_NOT_NULL(client); + EXPECT_OK(s2n_ktls_configure_connection(client, S2N_KTLS_MODE_SEND)); + EXPECT_OK(s2n_ktls_configure_connection(client, S2N_KTLS_MODE_RECV)); + EXPECT_OK(s2n_skip_handshake(client)); + + DEFER_CLEANUP(struct s2n_connection *server = s2n_connection_new(S2N_SERVER), + s2n_connection_ptr_free); + EXPECT_NOT_NULL(server); + EXPECT_OK(s2n_ktls_configure_connection(server, S2N_KTLS_MODE_SEND)); + EXPECT_OK(s2n_ktls_configure_connection(server, S2N_KTLS_MODE_RECV)); + EXPECT_OK(s2n_skip_handshake(server)); + + DEFER_CLEANUP(struct s2n_test_ktls_io_stuffer_pair io_pair = { 0 }, + s2n_ktls_io_stuffer_pair_free); + EXPECT_OK(s2n_test_init_ktls_io_stuffer(server, client, &io_pair)); + + /* Setup the client->server stuffer to not fit the entire close_notify */ + EXPECT_SUCCESS(s2n_stuffer_free(&io_pair.server_in.data_buffer)); + EXPECT_SUCCESS(s2n_stuffer_alloc(&io_pair.server_in.data_buffer, 1)); + + s2n_blocked_status blocked = S2N_NOT_BLOCKED; + EXPECT_FAILURE_WITH_ERRNO(s2n_shutdown(client, &blocked), S2N_ERR_IO_BLOCKED); + EXPECT_EQUAL(blocked, S2N_BLOCKED_ON_WRITE); + EXPECT_FALSE(s2n_connection_check_io_status(client, S2N_IO_WRITABLE)); + EXPECT_TRUE(s2n_connection_check_io_status(client, S2N_IO_READABLE)); + + EXPECT_FAILURE_WITH_ERRNO(s2n_shutdown(server, &blocked), S2N_ERR_IO_BLOCKED); + EXPECT_EQUAL(blocked, S2N_BLOCKED_ON_READ); + EXPECT_FALSE(s2n_connection_check_io_status(server, S2N_IO_WRITABLE)); + EXPECT_TRUE(s2n_connection_check_io_status(server, S2N_IO_READABLE)); + + /* Reuse the client->server stuffer for the remaining close_notify */ + EXPECT_SUCCESS(s2n_stuffer_wipe(&io_pair.server_in.data_buffer)); + + EXPECT_SUCCESS(s2n_shutdown(client, &blocked)); + EXPECT_SUCCESS(s2n_shutdown(server, &blocked)); + }; + + /* Test: Skip application data when waiting for close_notify */ + { + DEFER_CLEANUP(struct s2n_connection *client = s2n_connection_new(S2N_CLIENT), + s2n_connection_ptr_free); + EXPECT_NOT_NULL(client); + EXPECT_OK(s2n_ktls_configure_connection(client, S2N_KTLS_MODE_SEND)); + EXPECT_OK(s2n_ktls_configure_connection(client, S2N_KTLS_MODE_RECV)); + EXPECT_OK(s2n_skip_handshake(client)); + + DEFER_CLEANUP(struct s2n_connection *server = s2n_connection_new(S2N_SERVER), + s2n_connection_ptr_free); + EXPECT_NOT_NULL(server); + EXPECT_OK(s2n_ktls_configure_connection(server, S2N_KTLS_MODE_SEND)); + EXPECT_OK(s2n_ktls_configure_connection(server, S2N_KTLS_MODE_RECV)); + EXPECT_OK(s2n_skip_handshake(server)); + + DEFER_CLEANUP(struct s2n_test_ktls_io_stuffer_pair io_pair = { 0 }, + s2n_ktls_io_stuffer_pair_free); + EXPECT_OK(s2n_test_init_ktls_io_stuffer(server, client, &io_pair)); + + /* Send some application data for shutdown to skip */ + uint8_t app_data[] = "hello world"; + size_t app_data_size = sizeof(app_data); + s2n_blocked_status blocked = S2N_NOT_BLOCKED; + size_t app_data_count = 5; + for (size_t i = 0; i < app_data_count; i++) { + EXPECT_SUCCESS(s2n_send(client, app_data, app_data_size, &blocked)); + EXPECT_SUCCESS(s2n_send(server, app_data, app_data_size, &blocked)); + } + EXPECT_OK(s2n_test_validate_ancillary(&io_pair.client_in, TLS_APPLICATION_DATA, app_data_size)); + EXPECT_OK(s2n_test_validate_ancillary(&io_pair.server_in, TLS_APPLICATION_DATA, app_data_size)); + EXPECT_OK(s2n_test_records_in_ancillary(&io_pair.client_in, app_data_count)); + EXPECT_OK(s2n_test_records_in_ancillary(&io_pair.server_in, app_data_count)); + + /* Client's first shutdown blocks on reading the close_notify, + * but successfully writes the close_notify and skips all the app data.*/ + EXPECT_FAILURE_WITH_ERRNO(s2n_shutdown(client, &blocked), S2N_ERR_IO_BLOCKED); + EXPECT_EQUAL(blocked, S2N_BLOCKED_ON_READ); + EXPECT_FALSE(s2n_connection_check_io_status(client, S2N_IO_WRITABLE)); + EXPECT_TRUE(s2n_connection_check_io_status(client, S2N_IO_READABLE)); + EXPECT_TRUE(client->alert_sent); + EXPECT_OK(s2n_test_records_in_ancillary(&io_pair.client_in, 0)); + EXPECT_OK(s2n_test_records_in_ancillary(&io_pair.server_in, app_data_count + 1)); + + /* Server's first shutdown successfully skips all the app data + * and receives the close_notify */ + EXPECT_SUCCESS(s2n_shutdown(server, &blocked)); + EXPECT_EQUAL(blocked, S2N_NOT_BLOCKED); + EXPECT_TRUE(s2n_connection_check_io_status(server, S2N_IO_CLOSED)); + EXPECT_TRUE(server->alert_sent); + EXPECT_OK(s2n_test_records_in_ancillary(&io_pair.client_in, 1)); + EXPECT_OK(s2n_test_records_in_ancillary(&io_pair.server_in, 0)); + + /* Client's second shutdown successfully receives the close_notify */ + EXPECT_SUCCESS(s2n_shutdown(client, &blocked)); + EXPECT_EQUAL(blocked, S2N_NOT_BLOCKED); + EXPECT_TRUE(s2n_connection_check_io_status(client, S2N_IO_CLOSED)); + EXPECT_OK(s2n_test_records_in_ancillary(&io_pair.client_in, 0)); + EXPECT_OK(s2n_test_records_in_ancillary(&io_pair.server_in, 0)); + }; + }; + END_TEST(); } diff --git a/tls/s2n_ktls.h b/tls/s2n_ktls.h index c4b94fe9efe..ca3861ee3c2 100644 --- a/tls/s2n_ktls.h +++ b/tls/s2n_ktls.h @@ -49,6 +49,7 @@ ssize_t s2n_ktls_sendv_with_offset(struct s2n_connection *conn, const struct iov ssize_t count, ssize_t offs, s2n_blocked_status *blocked); int s2n_ktls_record_writev(struct s2n_connection *conn, uint8_t content_type, const struct iovec *in, int in_count, size_t offs, size_t to_write); +int s2n_ktls_read_full_record(struct s2n_connection *conn, uint8_t *record_type); /* These functions will be part of the public API. */ int s2n_connection_ktls_enable_send(struct s2n_connection *conn); diff --git a/tls/s2n_ktls_io.c b/tls/s2n_ktls_io.c index 04e210932aa..6dd337bd8c0 100644 --- a/tls/s2n_ktls_io.c +++ b/tls/s2n_ktls_io.c @@ -31,6 +31,7 @@ #include "error/s2n_errno.h" #include "tls/s2n_ktls.h" +#include "tls/s2n_tls.h" #include "utils/s2n_result.h" #include "utils/s2n_safety.h" #include "utils/s2n_socket.h" @@ -450,3 +451,35 @@ int s2n_sendfile(struct s2n_connection *conn, int in_fd, off_t offset, size_t co *blocked = S2N_NOT_BLOCKED; return S2N_SUCCESS; } + +int s2n_ktls_read_full_record(struct s2n_connection *conn, uint8_t *record_type) +{ + POSIX_ENSURE_REF(conn); + POSIX_ENSURE_REF(record_type); + + /* This method copies data into conn->in, so is intended for control messages + * rather than application data. However in some cases-- such as when attempting + * to read the close_notify alert during s2n_shutdown-- it may encounter application + * data. Set a reasonable conn->in size to avoid an excessive number of calls + * to recvmsg when reading a larger record. + */ + POSIX_GUARD(s2n_stuffer_resize_if_empty(&conn->in, S2N_DEFAULT_FRAGMENT_LENGTH)); + + struct s2n_stuffer record_stuffer = conn->in; + size_t len = s2n_stuffer_space_remaining(&record_stuffer); + uint8_t *buf = s2n_stuffer_raw_write(&record_stuffer, len); + POSIX_ENSURE_REF(buf); + + s2n_blocked_status blocked = S2N_NOT_BLOCKED; + size_t bytes_read = 0; + + /* Since recvmsg is responsible for decrypting the record in ktls, + * we apply blinding to the recvmsg call. + */ + s2n_result result = s2n_ktls_recvmsg(conn->recv_io_context, record_type, + buf, len, &blocked, &bytes_read); + WITH_ERROR_BLINDING(conn, POSIX_GUARD_RESULT(result)); + + POSIX_GUARD(s2n_stuffer_skip_write(&conn->in, bytes_read)); + return S2N_SUCCESS; +} diff --git a/tls/s2n_recv.c b/tls/s2n_recv.c index 713ec3ae6a8..3e7c7fb24b9 100644 --- a/tls/s2n_recv.c +++ b/tls/s2n_recv.c @@ -26,6 +26,7 @@ #include "tls/s2n_alerts.h" #include "tls/s2n_connection.h" #include "tls/s2n_handshake.h" +#include "tls/s2n_ktls.h" #include "tls/s2n_post_handshake.h" #include "tls/s2n_record.h" #include "tls/s2n_resume.h" @@ -60,6 +61,10 @@ int s2n_read_full_record(struct s2n_connection *conn, uint8_t *record_type, int { *isSSLv2 = 0; + if (conn->ktls_recv_enabled) { + return s2n_ktls_read_full_record(conn, record_type); + } + /* If the record has already been decrypted, then leave it alone */ if (conn->in_status == PLAINTEXT) { /* Only application data packets count as plaintext */ @@ -147,6 +152,7 @@ ssize_t s2n_recv_impl(struct s2n_connection *conn, void *buf, ssize_t size_signe POSIX_ENSURE(!s2n_connection_is_quic_enabled(conn), S2N_ERR_UNSUPPORTED_WITH_QUIC); POSIX_GUARD_RESULT(s2n_early_data_validate_recv(conn)); + POSIX_ENSURE(!conn->ktls_recv_enabled, S2N_ERR_UNIMPLEMENTED); while (size && s2n_connection_check_io_status(conn, S2N_IO_READABLE)) { int isSSLv2 = 0;