diff --git a/tests/unit/s2n_ktls_io_test.c b/tests/unit/s2n_ktls_io_test.c index bb0249d9b52..8a651bc94a2 100644 --- a/tests/unit/s2n_ktls_io_test.c +++ b/tests/unit/s2n_ktls_io_test.c @@ -1139,6 +1139,50 @@ int main(int argc, char **argv) uint8_t *read = s2n_stuffer_raw_read(&conn->in, small_frag_len); EXPECT_BYTEARRAY_EQUAL(read, test_data, small_frag_len); }; + + /* Test: Receive drains conn->in before calling recvmsg again */ + { + 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; + + /* Write half the test data into conn->in */ + const size_t offset = sizeof(test_data) / 2; + EXPECT_SUCCESS(s2n_stuffer_write_bytes(&conn->in, test_data, offset)); + + /* Write the other half into a new record */ + size_t written = 0; + struct iovec offset_iovec = { + .iov_base = test_data + offset, + .iov_len = sizeof(test_data) - offset, + }; + EXPECT_OK(s2n_ktls_sendmsg(ctx, TLS_ALERT, &offset_iovec, 1, &blocked, &written)); + EXPECT_EQUAL(written, offset_iovec.iov_len); + + uint8_t record_type = 0; + uint8_t *read = NULL; + + /* Verify that our first read returns conn->in, not the new record */ + EXPECT_SUCCESS(s2n_ktls_read_full_record(conn, &record_type)); + EXPECT_EQUAL(record_type, TLS_APPLICATION_DATA); + EXPECT_EQUAL(s2n_stuffer_data_available(&conn->in), offset); + read = s2n_stuffer_raw_read(&conn->in, offset); + EXPECT_BYTEARRAY_EQUAL(read, test_data, offset); + + EXPECT_SUCCESS(s2n_stuffer_wipe(&conn->in)); + + /* Verify a second read returns the new record */ + EXPECT_SUCCESS(s2n_ktls_read_full_record(conn, &record_type)); + EXPECT_EQUAL(record_type, TLS_ALERT); + EXPECT_EQUAL(s2n_stuffer_data_available(&conn->in), offset_iovec.iov_len); + read = s2n_stuffer_raw_read(&conn->in, offset_iovec.iov_len); + EXPECT_BYTEARRAY_EQUAL(read, offset_iovec.iov_base, offset_iovec.iov_len); + }; }; END_TEST(); diff --git a/tests/unit/s2n_recv_test.c b/tests/unit/s2n_recv_test.c index c25f4f11925..5b52d8ec54a 100644 --- a/tests/unit/s2n_recv_test.c +++ b/tests/unit/s2n_recv_test.c @@ -14,8 +14,11 @@ */ #include "api/s2n.h" +#include "api/unstable/renegotiate.h" #include "s2n_test.h" +#include "testlib/s2n_ktls_test_utils.h" #include "testlib/s2n_testlib.h" +#include "utils/s2n_random.h" bool s2n_custom_recv_fn_called = false; @@ -30,6 +33,22 @@ int s2n_expect_concurrent_error_recv_fn(void *io_context, uint8_t *buf, uint32_t return result; } +static ssize_t s2n_test_ktls_recvmsg_cb(void *io_context, struct msghdr *msg) +{ + POSIX_ENSURE_REF(io_context); + return *(ssize_t *) io_context; +} + +static int s2n_test_reneg_req_cb(struct s2n_connection *conn, void *context, + s2n_renegotiate_response *response) +{ + POSIX_ENSURE_REF(context); + size_t *count = (size_t *) context; + (*count)++; + *response = S2N_RENEGOTIATE_IGNORE; + return S2N_SUCCESS; +} + int main(int argc, char **argv) { BEGIN_TEST(); @@ -340,7 +359,300 @@ int main(int argc, char **argv) EXPECT_EQUAL(blocked, S2N_NOT_BLOCKED); EXPECT_SUCCESS(s2n_shutdown(client_conn, &blocked)); EXPECT_EQUAL(blocked, S2N_NOT_BLOCKED); - } + }; + + /* Test with ktls */ + { + uint8_t test_data[100] = { 0 }; + struct s2n_blob test_data_blob = { 0 }; + EXPECT_SUCCESS(s2n_blob_init(&test_data_blob, test_data, sizeof(test_data))); + EXPECT_OK(s2n_get_public_random_data(&test_data_blob)); + + const struct iovec test_iovec = { + .iov_base = test_data, + .iov_len = sizeof(test_data), + }; + + s2n_blocked_status blocked = S2N_NOT_BLOCKED; + + /* Test: receive all requested application data */ + { + DEFER_CLEANUP(struct s2n_connection *conn = s2n_connection_new(S2N_CLIENT), + s2n_connection_ptr_free); + EXPECT_NOT_NULL(conn); + EXPECT_OK(s2n_ktls_configure_connection(conn, S2N_KTLS_MODE_RECV)); + + 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_APPLICATION_DATA, + &test_iovec, 1, &blocked, &written)); + EXPECT_EQUAL(written, sizeof(test_data)); + + uint8_t output[sizeof(test_data)] = { 0 }; + int read = s2n_recv(conn, output, sizeof(output), &blocked); + EXPECT_EQUAL(read, written); + EXPECT_EQUAL(blocked, S2N_NOT_BLOCKED); + EXPECT_BYTEARRAY_EQUAL(output, test_data, read); + }; + + /* Test: receive partial application data */ + { + const size_t partial_size = sizeof(test_data) / 2; + struct iovec partial_iovec = test_iovec; + partial_iovec.iov_len = partial_size; + + DEFER_CLEANUP(struct s2n_connection *conn = s2n_connection_new(S2N_CLIENT), + s2n_connection_ptr_free); + EXPECT_NOT_NULL(conn); + EXPECT_OK(s2n_ktls_configure_connection(conn, S2N_KTLS_MODE_RECV)); + + 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_APPLICATION_DATA, + &partial_iovec, 1, &blocked, &written)); + EXPECT_EQUAL(written, partial_size); + + uint8_t output[sizeof(test_data)] = { 0 }; + int read = s2n_recv(conn, output, sizeof(output), &blocked); + EXPECT_EQUAL(read, written); + EXPECT_EQUAL(blocked, S2N_NOT_BLOCKED); + EXPECT_BYTEARRAY_EQUAL(output, test_data, read); + }; + + /* Test: drain buffered application data */ + { + const size_t partial_size = sizeof(test_data) / 2; + + DEFER_CLEANUP(struct s2n_connection *conn = s2n_connection_new(S2N_CLIENT), + s2n_connection_ptr_free); + EXPECT_NOT_NULL(conn); + EXPECT_OK(s2n_ktls_configure_connection(conn, S2N_KTLS_MODE_RECV)); + + 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_APPLICATION_DATA, + &test_iovec, 1, &blocked, &written)); + EXPECT_EQUAL(written, sizeof(test_data)); + + /* The first read doesn't read all the available data */ + uint8_t output[sizeof(test_data)] = { 0 }; + int read = s2n_recv(conn, output, partial_size, &blocked); + EXPECT_EQUAL(read, partial_size); + EXPECT_EQUAL(blocked, S2N_BLOCKED_ON_READ); + EXPECT_BYTEARRAY_EQUAL(output, test_data, partial_size); + EXPECT_EQUAL(ctx->recvmsg_invoked_count, 1); + + /* The second read drains the remaining data */ + const size_t remaining = sizeof(test_data) - partial_size; + read = s2n_recv(conn, output + read, remaining, &blocked); + EXPECT_EQUAL(read, remaining); + EXPECT_EQUAL(blocked, S2N_NOT_BLOCKED); + EXPECT_BYTEARRAY_EQUAL(output, test_data, sizeof(test_data)); + EXPECT_EQUAL(ctx->recvmsg_invoked_count, 1); + }; + + /* Test: receive blocks */ + { + DEFER_CLEANUP(struct s2n_connection *conn = s2n_connection_new(S2N_CLIENT), + s2n_connection_ptr_free); + EXPECT_NOT_NULL(conn); + EXPECT_OK(s2n_ktls_configure_connection(conn, S2N_KTLS_MODE_RECV)); + + 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)); + + uint8_t output[sizeof(test_data)] = { 0 }; + int read = s2n_recv(conn, output, sizeof(output), &blocked); + EXPECT_FAILURE_WITH_ERRNO(read, S2N_ERR_IO_BLOCKED); + EXPECT_EQUAL(blocked, S2N_BLOCKED_ON_READ); + }; + + /* Test: receive indicates end-of-data */ + { + DEFER_CLEANUP(struct s2n_connection *conn = s2n_connection_new(S2N_CLIENT), + s2n_connection_ptr_free); + EXPECT_NOT_NULL(conn); + EXPECT_OK(s2n_ktls_configure_connection(conn, S2N_KTLS_MODE_RECV)); + + ssize_t ret_val = 0; + EXPECT_OK(s2n_ktls_set_recvmsg_cb(conn, s2n_test_ktls_recvmsg_cb, &ret_val)); + + uint8_t output[10] = { 0 }; + int read = s2n_recv(conn, output, sizeof(output), &blocked); + EXPECT_FAILURE_WITH_ERRNO(read, S2N_ERR_CLOSED); + EXPECT_EQUAL(blocked, S2N_BLOCKED_ON_READ); + + /* Error fatal but not blinded */ + EXPECT_TRUE(s2n_connection_check_io_status(conn, S2N_IO_CLOSED)); + EXPECT_EQUAL(s2n_connection_get_delay(conn), 0); + }; + + /* Test: receive alert */ + { + DEFER_CLEANUP(struct s2n_connection *conn = s2n_connection_new(S2N_CLIENT), + s2n_connection_ptr_free); + EXPECT_NOT_NULL(conn); + EXPECT_OK(s2n_ktls_configure_connection(conn, S2N_KTLS_MODE_RECV)); + + 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 output[sizeof(test_data)] = { 0 }; + int read = s2n_recv(conn, output, sizeof(output), &blocked); + EXPECT_FAILURE_WITH_ERRNO(read, S2N_ERR_ALERT); + EXPECT_EQUAL(blocked, S2N_BLOCKED_ON_READ); + + /* Error fatal but not blinded */ + EXPECT_TRUE(s2n_connection_check_io_status(conn, S2N_IO_CLOSED)); + EXPECT_EQUAL(s2n_connection_get_delay(conn), 0); + }; + + /* Test: receive handshake message */ + { + DEFER_CLEANUP(struct s2n_config *reneg_config = s2n_config_new(), + s2n_config_ptr_free); + EXPECT_NOT_NULL(reneg_config); + + size_t reneg_request_count = 0; + EXPECT_SUCCESS(s2n_config_set_renegotiate_request_cb(reneg_config, + s2n_test_reneg_req_cb, &reneg_request_count)); + + uint8_t hello_request[TLS_HANDSHAKE_HEADER_LENGTH] = { TLS_HELLO_REQUEST }; + const struct iovec hello_request_iovec = { + .iov_base = hello_request, + .iov_len = sizeof(hello_request), + }; + + DEFER_CLEANUP(struct s2n_connection *conn = s2n_connection_new(S2N_CLIENT), + s2n_connection_ptr_free); + EXPECT_NOT_NULL(conn); + EXPECT_SUCCESS(s2n_connection_set_config(conn, reneg_config)); + EXPECT_OK(s2n_ktls_configure_connection(conn, S2N_KTLS_MODE_RECV)); + conn->secure_renegotiation = true; + + 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; + + /* Send the handshake message */ + EXPECT_OK(s2n_ktls_sendmsg(ctx, TLS_HANDSHAKE, + &hello_request_iovec, 1, &blocked, &written)); + EXPECT_EQUAL(written, sizeof(hello_request)); + + /* Also send some application data */ + EXPECT_OK(s2n_ktls_sendmsg(ctx, TLS_APPLICATION_DATA, + &test_iovec, 1, &blocked, &written)); + EXPECT_EQUAL(written, sizeof(test_data)); + + /* Verify that we received the application data */ + uint8_t output[sizeof(test_data)] = { 0 }; + int read = s2n_recv(conn, output, sizeof(output), &blocked); + EXPECT_EQUAL(read, sizeof(test_data)); + EXPECT_EQUAL(blocked, S2N_NOT_BLOCKED); + EXPECT_BYTEARRAY_EQUAL(output, test_data, read); + + /* Verify that we received and processed the handshake message */ + EXPECT_EQUAL(reneg_request_count, 1); + }; + + /* Test: Multirecord mode */ + { + DEFER_CLEANUP(struct s2n_config *multi_config = s2n_config_new(), + s2n_config_ptr_free); + EXPECT_NOT_NULL(multi_config); + EXPECT_SUCCESS(s2n_config_set_recv_multi_record(multi_config, true)); + + /* Test: receive all requested application data */ + { + DEFER_CLEANUP(struct s2n_connection *conn = s2n_connection_new(S2N_CLIENT), + s2n_connection_ptr_free); + EXPECT_NOT_NULL(conn); + EXPECT_SUCCESS(s2n_connection_set_config(conn, multi_config)); + EXPECT_OK(s2n_ktls_configure_connection(conn, S2N_KTLS_MODE_RECV)); + + 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; + + /* Write a lot of very small records */ + struct iovec offset_iovec = { 0 }; + for (size_t offset = 0; offset < sizeof(test_data); offset++) { + offset_iovec.iov_base = test_data + offset; + offset_iovec.iov_len = 1; + + size_t written = 0; + EXPECT_OK(s2n_ktls_sendmsg(ctx, TLS_APPLICATION_DATA, + &offset_iovec, 1, &blocked, &written)); + EXPECT_EQUAL(written, 1); + } + + /* Receive all the data from the many small records */ + uint8_t output[sizeof(test_data)] = { 0 }; + int read = s2n_recv(conn, output, sizeof(output), &blocked); + EXPECT_EQUAL(read, sizeof(test_data)); + EXPECT_EQUAL(blocked, S2N_NOT_BLOCKED); + EXPECT_BYTEARRAY_EQUAL(output, test_data, sizeof(test_data)); + }; + + /* Test: receive partial application data */ + { + DEFER_CLEANUP(struct s2n_connection *conn = s2n_connection_new(S2N_CLIENT), + s2n_connection_ptr_free); + EXPECT_NOT_NULL(conn); + EXPECT_SUCCESS(s2n_connection_set_config(conn, multi_config)); + EXPECT_OK(s2n_ktls_configure_connection(conn, S2N_KTLS_MODE_RECV)); + + 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; + + /* Write a lot of very small records, but don't write the full + * expected test data size. */ + const size_t partial_size = sizeof(test_data) / 2; + struct iovec offset_iovec = { 0 }; + for (size_t offset = 0; offset < partial_size; offset++) { + offset_iovec.iov_base = test_data + offset; + offset_iovec.iov_len = 1; + + size_t written = 0; + EXPECT_OK(s2n_ktls_sendmsg(ctx, TLS_APPLICATION_DATA, + &offset_iovec, 1, &blocked, &written)); + EXPECT_EQUAL(written, 1); + } + + /* Receive the partial data */ + uint8_t output[sizeof(test_data)] = { 0 }; + int read = s2n_recv(conn, output, sizeof(output), &blocked); + EXPECT_EQUAL(read, partial_size); + EXPECT_EQUAL(blocked, S2N_NOT_BLOCKED); + EXPECT_BYTEARRAY_EQUAL(output, test_data, partial_size); + }; + }; + }; END_TEST(); } diff --git a/tests/unit/s2n_self_talk_ktls_test.c b/tests/unit/s2n_self_talk_ktls_test.c index 797446b4677..21cd1d655db 100644 --- a/tests/unit/s2n_self_talk_ktls_test.c +++ b/tests/unit/s2n_self_talk_ktls_test.c @@ -30,6 +30,22 @@ * https://stackoverflow.com/a/34042435 */ #define S2N_TEST_INADDR_LOOPBACK 0x7f000001 /* 127.0.0.1 */ +static S2N_RESULT s2n_setup_connections(struct s2n_connection *server, + struct s2n_connection *client, struct s2n_test_io_pair *io_pair) +{ + RESULT_GUARD_POSIX(s2n_connections_set_io_pair(client, server, io_pair)); + + /* The test negotiate method assumes non-blocking sockets */ + RESULT_GUARD_POSIX(s2n_fd_set_non_blocking(io_pair->server)); + RESULT_GUARD_POSIX(s2n_fd_set_non_blocking(io_pair->client)); + RESULT_GUARD_POSIX(s2n_negotiate_test_server_and_client(server, client)); + + /* Our IO methods are more predictable if they use blocking sockets. */ + RESULT_GUARD_POSIX(s2n_fd_set_blocking(io_pair->server)); + RESULT_GUARD_POSIX(s2n_fd_set_blocking(io_pair->client)); + return S2N_RESULT_OK; +} + /* Unlike our other self-talk tests, this test cannot use AF_UNIX / AF_LOCAL. * For a real self-talk test we need real kernel support for kTLS, and only * AF_INET sockets support kTLS. @@ -144,12 +160,7 @@ int main(int argc, char **argv) EXPECT_FALSE(ktls_expected); END_TEST(); } - 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)); + EXPECT_OK(s2n_setup_connections(server, client, &io_pair)); if (s2n_connection_ktls_enable_send(client) == S2N_SUCCESS) { EXPECT_SUCCESS(s2n_connection_ktls_enable_send(server)); @@ -186,12 +197,7 @@ int main(int argc, char **argv) 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)); + EXPECT_OK(s2n_setup_connections(server, client, &io_pair)); struct s2n_connection *conns[] = { [S2N_CLIENT] = client, @@ -203,10 +209,6 @@ int main(int argc, char **argv) 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_send */ for (size_t i = 0; i < 5; i++) { int written = s2n_send(writer, test_data, sizeof(test_data), &blocked); @@ -296,20 +298,17 @@ int main(int argc, char **argv) s2n_connection_ptr_free); EXPECT_NOT_NULL(client); EXPECT_SUCCESS(s2n_connection_set_config(client, config)); + EXPECT_SUCCESS(s2n_connection_set_blinding(client, S2N_SELF_SERVICE_BLINDING)); DEFER_CLEANUP(struct s2n_connection *server = s2n_connection_new(S2N_SERVER), s2n_connection_ptr_free); - EXPECT_NOT_NULL(client); + EXPECT_NOT_NULL(server); EXPECT_SUCCESS(s2n_connection_set_config(server, config)); + EXPECT_SUCCESS(s2n_connection_set_blinding(server, S2N_SELF_SERVICE_BLINDING)); 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)); + EXPECT_OK(s2n_setup_connections(server, client, &io_pair)); struct s2n_connection *conns[] = { [S2N_CLIENT] = client, @@ -321,19 +320,138 @@ int main(int argc, char **argv) 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 with only application data */ + for (size_t i = 0; i < 5; i++) { + int written = s2n_send(writer, test_data, sizeof(test_data), &blocked); + EXPECT_EQUAL(written, sizeof(test_data)); + + uint8_t buffer[sizeof(test_data)] = { 0 }; + int read = s2n_recv(reader, buffer, sizeof(buffer), &blocked); + EXPECT_EQUAL(read, sizeof(test_data)); + EXPECT_EQUAL(blocked, S2N_NOT_BLOCKED); + + EXPECT_BYTEARRAY_EQUAL(test_data, buffer, read); + } + + /* Test: s2n_recv with interleaved control messages */ + { + const uint8_t test_record_type = TLS_CHANGE_CIPHER_SPEC; + uint8_t control_record_data[] = "control record data"; + struct s2n_blob control_record = { 0 }; + EXPECT_SUCCESS(s2n_blob_init(&control_record, control_record_data, + sizeof(control_record_data))); + + for (size_t i = 0; i < 5; i++) { + EXPECT_OK(s2n_record_write(writer, test_record_type, &control_record)); + EXPECT_SUCCESS(s2n_flush(writer, &blocked)); + + int written = s2n_send(writer, test_data, sizeof(test_data), &blocked); + EXPECT_EQUAL(written, sizeof(test_data)); + + uint8_t buffer[sizeof(test_data)] = { 0 }; + int read = s2n_recv(reader, buffer, sizeof(buffer), &blocked); + EXPECT_EQUAL(read, sizeof(test_data)); + EXPECT_EQUAL(blocked, S2N_NOT_BLOCKED); + + EXPECT_BYTEARRAY_EQUAL(test_data, buffer, read); + } + }; - /* Test: s2n_recv not implemented yet */ + /* Test: s2n_recv with incorrectly encrypted application data + * + * This test closes the connection so should be the last test to use + * these connections. + */ { - uint8_t buffer[10] = { 0 }; - int received = s2n_recv(reader, buffer, sizeof(buffer), &blocked); - EXPECT_FAILURE_WITH_ERRNO(received, S2N_ERR_UNIMPLEMENTED); + /* Write a valid record of application data */ + EXPECT_OK(s2n_record_write(writer, TLS_APPLICATION_DATA, &test_data_blob)); + /* Wipe part of the encrypted record so that it is no longer valid */ + EXPECT_SUCCESS(s2n_stuffer_wipe_n(&writer->out, 10)); + EXPECT_SUCCESS(s2n_stuffer_skip_write(&writer->out, 10)); + /* Actually send the modified record */ + EXPECT_SUCCESS(s2n_flush(writer, &blocked)); + + uint8_t buffer[sizeof(test_data)] = { 0 }; + int read = s2n_recv(reader, buffer, sizeof(buffer), &blocked); + EXPECT_FAILURE_WITH_ERRNO(read, S2N_ERR_IO); + EXPECT_EQUAL(blocked, S2N_BLOCKED_ON_READ); + + /* This error is fatal and blinded */ + EXPECT_TRUE(s2n_connection_check_io_status(reader, S2N_IO_CLOSED)); + EXPECT_TRUE(s2n_connection_get_delay(reader) > 0); + EXPECT_TRUE(s2n_connection_get_delay(reader) < UINT64_MAX); + }; + }; + + /* Test: s2n_shutdown + * + * There are three ways to trigger the read side of a TLS connection to close: + * 1. Receive an alert while calling s2n_recv + * 2. Receive an alert while calling s2n_shutdown + * 3. Receive "end of data" while calling s2n_recv (but this is an error) + * + * We need a fresh socket pair to test each scenario. Reusing sockets isn't + * currently possible because we currently can't disable / reset ktls. + */ + for (size_t mode_i = 0; mode_i < s2n_array_len(modes); mode_i++) { + if (!ktls_recv_supported) { + break; } - /* Test: s2n_shutdown */ + 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)); + EXPECT_SUCCESS(s2n_connection_set_blinding(client, S2N_SELF_SERVICE_BLINDING)); + + DEFER_CLEANUP(struct s2n_connection *server = s2n_connection_new(S2N_SERVER), + s2n_connection_ptr_free); + EXPECT_NOT_NULL(server); + EXPECT_SUCCESS(s2n_connection_set_config(server, config)); + EXPECT_SUCCESS(s2n_connection_set_blinding(server, S2N_SELF_SERVICE_BLINDING)); + + 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)]; + + s2n_blocked_status blocked = S2N_NOT_BLOCKED; + + /* Test: Receive an alert while calling s2n_recv */ { + DEFER_CLEANUP(struct s2n_test_io_pair io_pair = { 0 }, s2n_io_pair_close); + EXPECT_OK(s2n_new_inet_socket_pair(&io_pair)); + EXPECT_OK(s2n_setup_connections(server, client, &io_pair)); + EXPECT_SUCCESS(s2n_connection_ktls_enable_recv(reader)); + + EXPECT_SUCCESS(s2n_shutdown_send(writer, &blocked)); + + uint8_t buffer[10] = { 0 }; + int read = s2n_recv(reader, buffer, sizeof(buffer), &blocked); + EXPECT_EQUAL(read, 0); + EXPECT_EQUAL(blocked, S2N_NOT_BLOCKED); + EXPECT_TRUE(s2n_atomic_flag_test(&reader->close_notify_received)); + EXPECT_FALSE(s2n_connection_check_io_status(reader, S2N_IO_READABLE)); + + EXPECT_SUCCESS(s2n_shutdown(reader, &blocked)); + EXPECT_EQUAL(blocked, S2N_NOT_BLOCKED); + EXPECT_TRUE(s2n_connection_check_io_status(reader, S2N_IO_CLOSED)); + }; + + EXPECT_SUCCESS(s2n_connection_wipe(server)); + EXPECT_SUCCESS(s2n_connection_wipe(client)); + + /* Test: Receive an alert while calling s2n_shutdown */ + { + DEFER_CLEANUP(struct s2n_test_io_pair io_pair = { 0 }, s2n_io_pair_close); + EXPECT_OK(s2n_new_inet_socket_pair(&io_pair)); + EXPECT_OK(s2n_setup_connections(server, client, &io_pair)); + EXPECT_SUCCESS(s2n_connection_ktls_enable_recv(reader)); + /* 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)); @@ -353,6 +471,28 @@ int main(int argc, char **argv) EXPECT_EQUAL(blocked, S2N_NOT_BLOCKED); EXPECT_TRUE(s2n_connection_check_io_status(reader, S2N_IO_CLOSED)); }; + + EXPECT_SUCCESS(s2n_connection_wipe(server)); + EXPECT_SUCCESS(s2n_connection_wipe(client)); + + /* Test: Receive "end of data" while calling s2n_recv */ + { + DEFER_CLEANUP(struct s2n_test_io_pair io_pair = { 0 }, s2n_io_pair_close); + EXPECT_OK(s2n_new_inet_socket_pair(&io_pair)); + EXPECT_OK(s2n_setup_connections(server, client, &io_pair)); + EXPECT_SUCCESS(s2n_connection_ktls_enable_recv(reader)); + + EXPECT_SUCCESS(s2n_io_pair_close_one_end(&io_pair, writer->mode)); + + uint8_t buffer[10] = { 0 }; + int read = s2n_recv(reader, buffer, sizeof(buffer), &blocked); + EXPECT_FAILURE_WITH_ERRNO(read, S2N_ERR_CLOSED); + EXPECT_EQUAL(blocked, S2N_BLOCKED_ON_READ); + + /* Error fatal but not blinded */ + EXPECT_TRUE(s2n_connection_check_io_status(reader, S2N_IO_CLOSED)); + EXPECT_EQUAL(s2n_connection_get_delay(reader), 0); + }; }; END_TEST(); diff --git a/tls/s2n_connection.c b/tls/s2n_connection.c index ac56df34cea..a5a3f98dc18 100644 --- a/tls/s2n_connection.c +++ b/tls/s2n_connection.c @@ -1160,6 +1160,7 @@ S2N_CLEANUP_RESULT s2n_connection_apply_error_blinding(struct s2n_connection **c * * We may want to someday add an explicit error type for these errors. */ + case S2N_ERR_CLOSED: case S2N_ERR_CANCELLED: case S2N_ERR_CIPHER_NOT_SUPPORTED: case S2N_ERR_PROTOCOL_VERSION_UNSUPPORTED: diff --git a/tls/s2n_ktls_io.c b/tls/s2n_ktls_io.c index 6dd337bd8c0..0b235f04e81 100644 --- a/tls/s2n_ktls_io.c +++ b/tls/s2n_ktls_io.c @@ -457,12 +457,14 @@ 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. + /* If any unread data remains in conn->in, it must be application data that + * couldn't be returned due to the size of the application's provided buffer. */ + if (s2n_stuffer_data_available(&conn->in)) { + *record_type = TLS_APPLICATION_DATA; + return S2N_SUCCESS; + } + POSIX_GUARD(s2n_stuffer_resize_if_empty(&conn->in, S2N_DEFAULT_FRAGMENT_LENGTH)); struct s2n_stuffer record_stuffer = conn->in; diff --git a/tls/s2n_recv.c b/tls/s2n_recv.c index 3e7c7fb24b9..4571789a881 100644 --- a/tls/s2n_recv.c +++ b/tls/s2n_recv.c @@ -152,7 +152,6 @@ 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;