From 1f19c68d51c0d72e78b8f6c91cd93040a73e7adb Mon Sep 17 00:00:00 2001 From: toidiu Date: Tue, 12 Sep 2023 10:49:50 -0700 Subject: [PATCH 1/4] ci: run duvet when commits are merged into main branch (#4197) --- .github/workflows/ci_compliance.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci_compliance.yml b/.github/workflows/ci_compliance.yml index aa1a3e9b444..8c2e53c31b6 100644 --- a/.github/workflows/ci_compliance.yml +++ b/.github/workflows/ci_compliance.yml @@ -2,6 +2,8 @@ name: Compliance on: + push: + branches: [main] pull_request: branches: [main] merge_group: From f40c6b9bd5beb977f7cb3d2200d4c52a40b08518 Mon Sep 17 00:00:00 2001 From: Lindsay Stewart Date: Tue, 12 Sep 2023 16:37:41 -0700 Subject: [PATCH 2/4] ktls: self-talk tests for send (#4189) --- codebuild/spec/buildspec_ktls.yml | 3 + tests/testlib/s2n_io_testlib.c | 84 ++++++ tests/testlib/s2n_testlib.h | 12 + tests/unit/s2n_ktls_io_test.c | 76 ------ tests/unit/s2n_self_talk_inet_socket_test.c | 204 --------------- tests/unit/s2n_self_talk_ktls_test.c | 271 ++++++++++++++++++++ 6 files changed, 370 insertions(+), 280 deletions(-) create mode 100644 tests/testlib/s2n_io_testlib.c delete mode 100644 tests/unit/s2n_self_talk_inet_socket_test.c create mode 100644 tests/unit/s2n_self_talk_ktls_test.c diff --git a/codebuild/spec/buildspec_ktls.yml b/codebuild/spec/buildspec_ktls.yml index 5958b9c6711..ddece2a356e 100644 --- a/codebuild/spec/buildspec_ktls.yml +++ b/codebuild/spec/buildspec_ktls.yml @@ -1,5 +1,8 @@ --- version: 0.2 +env: + variables: + S2N_KTLS_TESTING_EXPECTED: true phases: install: commands: diff --git a/tests/testlib/s2n_io_testlib.c b/tests/testlib/s2n_io_testlib.c new file mode 100644 index 00000000000..cf5313412ee --- /dev/null +++ b/tests/testlib/s2n_io_testlib.c @@ -0,0 +1,84 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#include "testlib/s2n_testlib.h" + +S2N_CLEANUP_RESULT s2n_test_iovecs_free(struct s2n_test_iovecs *in) +{ + RESULT_ENSURE_REF(in); + for (size_t i = 0; i < in->iovecs_count; i++) { + RESULT_GUARD_POSIX(s2n_free_object((uint8_t **) &in->iovecs[i].iov_base, + in->iovecs[i].iov_len)); + } + RESULT_GUARD_POSIX(s2n_free_object((uint8_t **) &in->iovecs, + sizeof(struct iovec) * in->iovecs_count)); + return S2N_RESULT_OK; +} + +static S2N_RESULT s2n_test_split_data(struct s2n_test_iovecs *iovecs, struct s2n_blob *data) +{ + RESULT_ENSURE_REF(iovecs); + RESULT_ENSURE_REF(data); + + struct s2n_stuffer in = { 0 }; + RESULT_GUARD_POSIX(s2n_stuffer_init_written(&in, data)); + + for (size_t i = 0; i < iovecs->iovecs_count; i++) { + if (iovecs->iovecs[i].iov_len == 0) { + continue; + } + struct s2n_blob mem = { 0 }; + RESULT_GUARD_POSIX(s2n_alloc(&mem, iovecs->iovecs[i].iov_len)); + RESULT_GUARD_POSIX(s2n_stuffer_read(&in, &mem)); + iovecs->iovecs[i].iov_base = mem.data; + } + RESULT_ENSURE_EQ(s2n_stuffer_data_available(&in), 0); + return S2N_RESULT_OK; +} + +S2N_RESULT s2n_test_new_iovecs(struct s2n_test_iovecs *iovecs, + struct s2n_blob *data, const size_t *lens, size_t lens_count) +{ + RESULT_ENSURE_REF(iovecs); + RESULT_ENSURE_REF(data); + RESULT_ENSURE_REF(lens); + + size_t len_total = 0; + for (size_t i = 0; i < lens_count; i++) { + len_total += lens[i]; + } + RESULT_ENSURE_LTE(len_total, data->size); + + size_t iovecs_count = lens_count; + if (len_total < data->size) { + iovecs_count++; + } + + struct s2n_blob iovecs_mem = { 0 }; + RESULT_GUARD_POSIX(s2n_alloc(&iovecs_mem, sizeof(struct iovec) * iovecs_count)); + RESULT_GUARD_POSIX(s2n_blob_zero(&iovecs_mem)); + iovecs->iovecs = (struct iovec *) (void *) iovecs_mem.data; + iovecs->iovecs_count = iovecs_count; + + for (size_t i = 0; i < lens_count; i++) { + iovecs->iovecs[i].iov_len = lens[i]; + } + if (lens_count < iovecs_count) { + iovecs->iovecs[lens_count].iov_len = data->size - len_total; + } + + RESULT_GUARD(s2n_test_split_data(iovecs, data)); + return S2N_RESULT_OK; +} diff --git a/tests/testlib/s2n_testlib.h b/tests/testlib/s2n_testlib.h index 0a5aeeacfc4..e67aaff6a7c 100644 --- a/tests/testlib/s2n_testlib.h +++ b/tests/testlib/s2n_testlib.h @@ -208,6 +208,18 @@ int s2n_shutdown_test_server_and_client(struct s2n_connection *server_conn, stru S2N_RESULT s2n_negotiate_test_server_and_client_with_early_data(struct s2n_connection *server_conn, struct s2n_connection *client_conn, struct s2n_blob *early_data_to_send, struct s2n_blob *early_data_received); +/* Testing only with easily constructed contiguous data buffers could hide errors. + * We should use iovecs where every buffer is allocated separately. + * These test methods construct separate io buffers from one contiguous buffer. + */ +struct s2n_test_iovecs { + struct iovec *iovecs; + size_t iovecs_count; +}; +S2N_RESULT s2n_test_new_iovecs(struct s2n_test_iovecs *iovecs, + struct s2n_blob *data, const size_t *lens, size_t lens_count); +S2N_CLEANUP_RESULT s2n_test_iovecs_free(struct s2n_test_iovecs *in); + struct s2n_kem_kat_test_vector { const struct s2n_kem *kem; const char *kat_file; diff --git a/tests/unit/s2n_ktls_io_test.c b/tests/unit/s2n_ktls_io_test.c index 3f437d92e4d..65dc4c8eca4 100644 --- a/tests/unit/s2n_ktls_io_test.c +++ b/tests/unit/s2n_ktls_io_test.c @@ -72,82 +72,6 @@ ssize_t s2n_test_ktls_recvmsg_io_stuffer_and_ctrunc(void *io_context, struct msg return ret; } -struct s2n_test_iovecs { - struct iovec *iovecs; - size_t iovecs_count; -}; - -static S2N_CLEANUP_RESULT s2n_test_iovecs_free(struct s2n_test_iovecs *in) -{ - RESULT_ENSURE_REF(in); - for (size_t i = 0; i < in->iovecs_count; i++) { - RESULT_GUARD_POSIX(s2n_free_object((uint8_t **) &in->iovecs[i].iov_base, - in->iovecs[i].iov_len)); - } - RESULT_GUARD_POSIX(s2n_free_object((uint8_t **) &in->iovecs, - sizeof(struct iovec) * in->iovecs_count)); - return S2N_RESULT_OK; -} - -/* Testing only with contiguous data could hide errors. - * We should use iovecs where every buffer is allocated separately. - */ -static S2N_RESULT s2n_test_split_data(struct s2n_test_iovecs *iovecs, struct s2n_blob *data) -{ - RESULT_ENSURE_REF(iovecs); - RESULT_ENSURE_REF(data); - - struct s2n_stuffer in = { 0 }; - RESULT_GUARD_POSIX(s2n_stuffer_init_written(&in, data)); - - for (size_t i = 0; i < iovecs->iovecs_count; i++) { - if (iovecs->iovecs[i].iov_len == 0) { - continue; - } - struct s2n_blob mem = { 0 }; - RESULT_GUARD_POSIX(s2n_alloc(&mem, iovecs->iovecs[i].iov_len)); - RESULT_GUARD_POSIX(s2n_stuffer_read(&in, &mem)); - iovecs->iovecs[i].iov_base = mem.data; - } - RESULT_ENSURE_EQ(s2n_stuffer_data_available(&in), 0); - return S2N_RESULT_OK; -} - -static S2N_RESULT s2n_test_new_iovecs(struct s2n_test_iovecs *iovecs, - struct s2n_blob *data, const size_t *lens, size_t lens_count) -{ - RESULT_ENSURE_REF(iovecs); - RESULT_ENSURE_REF(data); - RESULT_ENSURE_REF(lens); - - size_t len_total = 0; - for (size_t i = 0; i < lens_count; i++) { - len_total += lens[i]; - } - RESULT_ENSURE_LTE(len_total, data->size); - - size_t iovecs_count = lens_count; - if (len_total < data->size) { - iovecs_count++; - } - - struct s2n_blob iovecs_mem = { 0 }; - RESULT_GUARD_POSIX(s2n_alloc(&iovecs_mem, sizeof(struct iovec) * iovecs_count)); - RESULT_GUARD_POSIX(s2n_blob_zero(&iovecs_mem)); - iovecs->iovecs = (struct iovec *) iovecs_mem.data; - iovecs->iovecs_count = iovecs_count; - - for (size_t i = 0; i < lens_count; i++) { - iovecs->iovecs[i].iov_len = lens[i]; - } - if (lens_count < iovecs_count) { - iovecs->iovecs[lens_count].iov_len = data->size - len_total; - } - - RESULT_GUARD(s2n_test_split_data(iovecs, data)); - return S2N_RESULT_OK; -} - int main(int argc, char **argv) { BEGIN_TEST(); diff --git a/tests/unit/s2n_self_talk_inet_socket_test.c b/tests/unit/s2n_self_talk_inet_socket_test.c deleted file mode 100644 index 4399d617790..00000000000 --- a/tests/unit/s2n_self_talk_inet_socket_test.c +++ /dev/null @@ -1,204 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -#include -#include -#include -#include - -#include "s2n_test.h" -#include "testlib/s2n_testlib.h" - -/* There are issues with MacOS and FreeBSD so we define the constant ourselves. - * https://stackoverflow.com/a/34042435 */ -#define S2N_TEST_INADDR_LOOPBACK 0x7f000001 /* 127.0.0.1 */ - -const char CHAR_A = 'a'; -const char CHAR_B = 'b'; - -/* A collection of callbacks run during inet socket self talk tests */ -struct self_talk_inet_socket_callbacks { - S2N_RESULT (*server_post_handshake_cb)(struct s2n_connection *conn); - S2N_RESULT (*client_post_handshake_cb)(struct s2n_connection *conn); -}; - -S2N_RESULT s2n_noop_inet_socket_cb(struct s2n_connection *conn) -{ - return S2N_RESULT_OK; -} - -static S2N_RESULT start_client(int fd, int read_pipe, const struct self_talk_inet_socket_callbacks *socket_cb) -{ - /* Setup connections */ - DEFER_CLEANUP(struct s2n_connection *client_conn = s2n_connection_new(S2N_CLIENT), - s2n_connection_ptr_free); - DEFER_CLEANUP(struct s2n_config *config = s2n_config_new(), s2n_config_ptr_free); - - DEFER_CLEANUP(struct s2n_cert_chain_and_key * chain_and_key, s2n_cert_chain_and_key_ptr_free); - RESULT_GUARD_POSIX(s2n_test_cert_chain_and_key_new(&chain_and_key, - S2N_DEFAULT_TEST_CERT_CHAIN, S2N_DEFAULT_TEST_PRIVATE_KEY)); - - /* Setup config */ - RESULT_GUARD_POSIX(s2n_connection_set_blinding(client_conn, S2N_SELF_SERVICE_BLINDING)); - RESULT_GUARD_POSIX(s2n_connection_set_fd(client_conn, fd)); - RESULT_GUARD_POSIX(s2n_config_set_cipher_preferences(config, "default")); - RESULT_GUARD_POSIX(s2n_config_set_unsafe_for_testing(config)); - RESULT_GUARD_POSIX(s2n_config_add_cert_chain_and_key_to_store(config, chain_and_key)); - RESULT_GUARD_POSIX(s2n_connection_set_config(client_conn, config)); - - /* Complete the handshake */ - s2n_blocked_status blocked = S2N_NOT_BLOCKED; - RESULT_GUARD_POSIX(s2n_negotiate(client_conn, &blocked)); - RESULT_ENSURE_EQ(client_conn->actual_protocol_version, S2N_TLS12); - - RESULT_GUARD(socket_cb->client_post_handshake_cb(client_conn)); - - char sync = 0; - char recv_buffer[1] = { 0 }; - - RESULT_GUARD_POSIX(read(read_pipe, &sync, 1)); - RESULT_GUARD_POSIX(s2n_recv(client_conn, recv_buffer, 1, &blocked)); - RESULT_ENSURE_EQ(memcmp(&CHAR_A, &recv_buffer[0], 1), 0); - - RESULT_GUARD_POSIX(read(read_pipe, &sync, 1)); - RESULT_GUARD_POSIX(s2n_recv(client_conn, recv_buffer, 1, &blocked)); - RESULT_ENSURE_EQ(memcmp(&CHAR_B, &recv_buffer[0], 1), 0); - - return S2N_RESULT_OK; -} - -static S2N_RESULT start_server(int fd, int write_pipe, const struct self_talk_inet_socket_callbacks *socket_cb) -{ - /* Setup connections */ - DEFER_CLEANUP(struct s2n_connection *server_conn = s2n_connection_new(S2N_SERVER), - s2n_connection_ptr_free); - DEFER_CLEANUP(struct s2n_config *config = s2n_config_new(), s2n_config_ptr_free); - - DEFER_CLEANUP(struct s2n_cert_chain_and_key * chain_and_key, s2n_cert_chain_and_key_ptr_free); - RESULT_GUARD_POSIX(s2n_test_cert_chain_and_key_new(&chain_and_key, - S2N_DEFAULT_TEST_CERT_CHAIN, S2N_DEFAULT_TEST_PRIVATE_KEY)); - - /* Setup config */ - RESULT_GUARD_POSIX(s2n_connection_set_blinding(server_conn, S2N_SELF_SERVICE_BLINDING)); - RESULT_ENSURE_EQ(s2n_connection_get_delay(server_conn), 0); - RESULT_GUARD_POSIX(s2n_connection_set_fd(server_conn, fd)); - RESULT_GUARD_POSIX(s2n_config_set_cipher_preferences(config, "default")); - RESULT_GUARD_POSIX(s2n_config_set_unsafe_for_testing(config)); - RESULT_GUARD_POSIX(s2n_config_add_cert_chain_and_key_to_store(config, chain_and_key)); - RESULT_GUARD_POSIX(s2n_connection_set_config(server_conn, config)); - - /* Complete the handshake */ - s2n_blocked_status blocked = S2N_NOT_BLOCKED; - RESULT_GUARD_POSIX(s2n_negotiate(server_conn, &blocked)); - RESULT_ENSURE_EQ(server_conn->actual_protocol_version, S2N_TLS12); - - RESULT_GUARD(socket_cb->server_post_handshake_cb(server_conn)); - - char sync = 0; - char send_buffer[1] = { 0 }; - - send_buffer[0] = CHAR_A; - RESULT_GUARD_POSIX(s2n_send(server_conn, send_buffer, 1, &blocked)); - RESULT_GUARD_POSIX(write(write_pipe, &sync, 1)); - - send_buffer[0] = CHAR_B; - RESULT_GUARD_POSIX(s2n_send(server_conn, send_buffer, 1, &blocked)); - RESULT_GUARD_POSIX(write(write_pipe, &sync, 1)); - - return S2N_RESULT_OK; -} - -static S2N_RESULT launch_test(const struct self_talk_inet_socket_callbacks *socket_cb) -{ - /* configure real socket */ - int listener = socket(AF_INET, SOCK_STREAM, 0); - RESULT_GUARD_POSIX(listener); - struct sockaddr_in saddr = { 0 }; - saddr.sin_family = AF_INET; - saddr.sin_addr.s_addr = htonl(S2N_TEST_INADDR_LOOPBACK); - saddr.sin_port = 0; - - /* listen on socket address */ - socklen_t addrlen = sizeof(saddr); - RESULT_GUARD_POSIX(bind(listener, (struct sockaddr *) &saddr, addrlen)); - RESULT_GUARD_POSIX(getsockname(listener, (struct sockaddr *) &saddr, &addrlen)); - - /* Used for synchronizing read and writes between client and server. - * - * The purpose of this mechanism is to prevent the client from reading - * before the server has sent it. The client blocks on reading a single - * char while the server writes a char after it has sent data and wishes - * to unblock the client. */ - int sync_pipe[2] = { 0 }; - RESULT_GUARD_POSIX(pipe(sync_pipe)); - - pid_t child = fork(); - RESULT_ENSURE(child >= 0, S2N_ERR_SAFETY); - int status = 0; - int fd = 0; - if (child) { - /* server */ - RESULT_GUARD_POSIX(listen(listener, 1)); - fd = accept(listener, NULL, NULL); - RESULT_GUARD_POSIX(fd); - - RESULT_GUARD_POSIX(close(sync_pipe[0])); - RESULT_GUARD(start_server(fd, sync_pipe[1], socket_cb)); - - RESULT_ENSURE_EQ(waitpid(-1, &status, 0), child); - RESULT_ENSURE_EQ(status, 0); - } else { - /* client */ - fd = socket(AF_INET, SOCK_STREAM, 0); - RESULT_GUARD_POSIX(fd); - - /* wait for server to start up */ - sleep(1); - RESULT_GUARD_POSIX(connect(fd, (struct sockaddr *) &saddr, addrlen)); - - RESULT_GUARD_POSIX(close(sync_pipe[1])); - RESULT_GUARD(start_client(fd, sync_pipe[0], socket_cb)); - exit(0); - } - - return S2N_RESULT_OK; -} - -/* This test is unique compared to our other self talk tests because it's testing - * AF_INET socket functionality. - * - * When enabled, kTLS offloads the TLS protocol to the socket. It's not possible to - * enable kTLS on an AF_UNIX socket, which is what our other self talk tests use. - * Instead we need to use AF_INET sockets to test kTLS functionality. The use of - * AF_INET sockets also drives the need to `fork` and create two processes (client and - * server) to establish a TLS connection. - */ -int main(int argc, char **argv) -{ - BEGIN_TEST(); - - /* SIGPIPE is received when a process tries to write to a socket which - * has been shutdown. Ignore it and handle it gracefully. */ - signal(SIGPIPE, SIG_IGN); - - /* A regular connection */ - const struct self_talk_inet_socket_callbacks noop_inet_cb = { - .server_post_handshake_cb = s2n_noop_inet_socket_cb, - .client_post_handshake_cb = s2n_noop_inet_socket_cb, - }; - EXPECT_OK(launch_test(&noop_inet_cb)); - - END_TEST(); -} diff --git a/tests/unit/s2n_self_talk_ktls_test.c b/tests/unit/s2n_self_talk_ktls_test.c new file mode 100644 index 00000000000..45743ccb182 --- /dev/null +++ b/tests/unit/s2n_self_talk_ktls_test.c @@ -0,0 +1,271 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#include +#include +#include +#include +#include +#include + +#include "s2n_test.h" +#include "testlib/s2n_testlib.h" +#include "tls/s2n_ktls.h" +#include "utils/s2n_random.h" + +/* There are issues with MacOS and FreeBSD so we define the constant ourselves. + * https://stackoverflow.com/a/34042435 */ +#define S2N_TEST_INADDR_LOOPBACK 0x7f000001 /* 127.0.0.1 */ + +/* 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. + */ +static S2N_RESULT s2n_new_inet_socket_pair(struct s2n_test_io_pair *io_pair) +{ + RESULT_ENSURE_REF(io_pair); + + int listener = socket(AF_INET, SOCK_STREAM, 0); + RESULT_ENSURE_GT(listener, 0); + + struct sockaddr_in saddr = { 0 }; + saddr.sin_family = AF_INET; + saddr.sin_addr.s_addr = htonl(S2N_TEST_INADDR_LOOPBACK); + saddr.sin_port = 0; + + socklen_t addrlen = sizeof(saddr); + RESULT_ENSURE_EQ(bind(listener, (struct sockaddr *) &saddr, addrlen), 0); + RESULT_ENSURE_EQ(getsockname(listener, (struct sockaddr *) &saddr, &addrlen), 0); + RESULT_ENSURE_EQ(listen(listener, 1), 0); + + io_pair->client = socket(AF_INET, SOCK_STREAM, 0); + RESULT_ENSURE_GT(io_pair->client, 0); + + fflush(stdout); + pid_t pid = fork(); + RESULT_ENSURE_GTE(pid, 0); + if (pid == 0) { + RESULT_ENSURE_EQ(connect(io_pair->client, (struct sockaddr *) &saddr, addrlen), 0); + ZERO_TO_DISABLE_DEFER_CLEANUP(io_pair); + exit(0); + } + io_pair->server = accept(listener, NULL, NULL); + RESULT_ENSURE_GT(io_pair->server, 0); + return S2N_RESULT_OK; +} + +int main(int argc, char **argv) +{ + BEGIN_TEST(); + + /* ktls is complicated to enable. We should ensure that it's actually enabled + * where we think we're testing it. + */ + bool ktls_expected = (getenv("S2N_KTLS_TESTING_EXPECTED") != NULL); + + if (!s2n_ktls_is_supported_on_platform() && !ktls_expected) { + END_TEST(); + } + + const s2n_mode modes[] = { S2N_CLIENT, S2N_SERVER }; + + 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)); + + 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)); + + DEFER_CLEANUP(struct s2n_test_iovecs test_iovecs = { 0 }, s2n_test_iovecs_free); + size_t test_iovecs_lens[20] = { 5, 6, 1, 10, 0 }; + EXPECT_OK(s2n_test_new_iovecs(&test_iovecs, &test_data_blob, test_iovecs_lens, + s2n_array_len(test_iovecs_lens))); + + const size_t test_offsets[] = { + 0, + test_iovecs_lens[0], + test_iovecs_lens[0] + 1, + test_iovecs_lens[0] + test_iovecs_lens[1], + sizeof(test_data) - 1, + sizeof(test_data), + }; + + uint8_t file_test_data[100] = { 0 }; + int file = open(argv[0], O_RDONLY); + EXPECT_TRUE(file > 0); + int file_read = pread(file, file_test_data, sizeof(file_test_data), 0); + EXPECT_EQUAL(file_read, sizeof(file_test_data)); + + DEFER_CLEANUP(struct s2n_config *config = s2n_config_new(), s2n_config_ptr_free); + EXPECT_SUCCESS(s2n_config_add_cert_chain_and_key_to_store(config, chain_and_key)); + EXPECT_SUCCESS(s2n_config_set_unsafe_for_testing(config)); + EXPECT_SUCCESS(s2n_config_set_cipher_preferences(config, "default")); + + /* Test enabling ktls for sending */ + { + 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); + if (s2n_result_is_error(s2n_new_inet_socket_pair(&io_pair))) { + /* We should be able to setup AF_INET sockets everywhere, but if + * we can't, don't block the build unless the build explicitly expects + * to be able to test ktls. + */ + 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)); + + 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. + */ + EXPECT_FALSE(ktls_expected); + END_TEST(); + } + 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++) { + 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(server); + 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 *writer = conns[mode]; + struct s2n_connection *reader = conns[S2N_PEER_MODE(mode)]; + EXPECT_SUCCESS(s2n_connection_ktls_enable_send(writer)); + + 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); + EXPECT_EQUAL(written, sizeof(test_data)); + EXPECT_EQUAL(blocked, S2N_NOT_BLOCKED); + + 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_sendv */ + for (size_t i = 0; i < 5; i++) { + int written = s2n_sendv(writer, + test_iovecs.iovecs, test_iovecs.iovecs_count, &blocked); + EXPECT_EQUAL(written, sizeof(test_data)); + EXPECT_EQUAL(blocked, S2N_NOT_BLOCKED); + + 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_sendv_with_offset */ + for (size_t offset_i = 0; offset_i < s2n_array_len(test_offsets); offset_i++) { + const size_t offset = test_offsets[offset_i]; + const size_t expected_written = sizeof(test_data) - offset; + + int written = s2n_sendv_with_offset(writer, + test_iovecs.iovecs, test_iovecs.iovecs_count, offset, &blocked); + EXPECT_EQUAL(written, expected_written); + EXPECT_EQUAL(blocked, S2N_NOT_BLOCKED); + + uint8_t buffer[sizeof(test_data)] = { 0 }; + int read = s2n_recv(reader, buffer, expected_written, &blocked); + EXPECT_EQUAL(read, expected_written); + EXPECT_EQUAL(blocked, S2N_NOT_BLOCKED); + + EXPECT_BYTEARRAY_EQUAL(test_data + offset, buffer, read); + }; + + /* Test: s2n_sendfile */ + for (size_t offset_i = 0; offset_i < s2n_array_len(test_offsets); offset_i++) { + const size_t offset = test_offsets[offset_i]; + const size_t expected_written = sizeof(test_data) - offset; + + size_t written = 0; + EXPECT_SUCCESS(s2n_sendfile(writer, file, offset, expected_written, + &written, &blocked)); + EXPECT_EQUAL(written, expected_written); + EXPECT_EQUAL(blocked, S2N_NOT_BLOCKED); + + uint8_t buffer[sizeof(file_test_data)] = { 0 }; + int read = s2n_recv(reader, buffer, expected_written, &blocked); + EXPECT_EQUAL(read, expected_written); + EXPECT_EQUAL(blocked, S2N_NOT_BLOCKED); + + EXPECT_BYTEARRAY_EQUAL(file_test_data + offset, buffer, read); + } + + /* Test: s2n_shutdown */ + { + EXPECT_SUCCESS(s2n_shutdown_send(writer, &blocked)); + EXPECT_EQUAL(blocked, S2N_NOT_BLOCKED); + + EXPECT_SUCCESS(s2n_shutdown(reader, &blocked)); + EXPECT_EQUAL(blocked, S2N_NOT_BLOCKED); + }; + }; + + END_TEST(); +} From 765afea3ff4b25a4686b04be0e98dc95b8a15a7e Mon Sep 17 00:00:00 2001 From: Lindsay Stewart Date: Wed, 13 Sep 2023 11:58:05 -0700 Subject: [PATCH 3/4] Reduce allocs in ktls app data send (#4181) --- tests/unit/s2n_ktls_io_test.c | 218 +++++++++++++++++++++++++++++----- tls/s2n_ktls_io.c | 99 ++++++++++----- 2 files changed, 258 insertions(+), 59 deletions(-) diff --git a/tests/unit/s2n_ktls_io_test.c b/tests/unit/s2n_ktls_io_test.c index 65dc4c8eca4..f58d89c361e 100644 --- a/tests/unit/s2n_ktls_io_test.c +++ b/tests/unit/s2n_ktls_io_test.c @@ -15,6 +15,7 @@ #include "s2n_test.h" #include "testlib/s2n_ktls_test_utils.h" +#include "testlib/s2n_mem_testlib.h" #include "testlib/s2n_testlib.h" #include "tls/s2n_ktls.h" #include "utils/s2n_random.h" @@ -558,6 +559,9 @@ int main(int argc, char **argv) EXPECT_FAILURE_WITH_ERRNO( s2n_ktls_sendv_with_offset(&conn, NULL, 1, 0, &blocked), S2N_ERR_NULL); + EXPECT_FAILURE_WITH_ERRNO( + s2n_ktls_sendv_with_offset(&conn, NULL, 1, 1, &blocked), + S2N_ERR_NULL); EXPECT_FAILURE_WITH_ERRNO( s2n_ktls_sendv_with_offset(&conn, &test_iovec, 1, 0, NULL), S2N_ERR_NULL); @@ -671,38 +675,20 @@ int main(int argc, char **argv) EXPECT_OK(s2n_test_validate_data(&out, test_data, sizeof(test_data))); }; - /* Test: Send with very large number of iovecs */ - { - DEFER_CLEANUP(struct s2n_connection *conn = s2n_connection_new(S2N_SERVER), - s2n_connection_ptr_free); - EXPECT_NOT_NULL(conn); - - DEFER_CLEANUP(struct s2n_test_ktls_io_stuffer out = { 0 }, - s2n_ktls_io_stuffer_free); - EXPECT_OK(s2n_test_init_ktls_io_stuffer_send(conn, &out)); - - size_t many_iov_lens[1000] = { 0 }; - DEFER_CLEANUP(struct s2n_test_iovecs test_iovecs = { 0 }, s2n_test_iovecs_free); - EXPECT_OK(s2n_test_new_iovecs(&test_iovecs, &test_data_blob, - many_iov_lens, s2n_array_len(many_iov_lens))); - - s2n_blocked_status blocked = S2N_NOT_BLOCKED; - ssize_t result = s2n_ktls_sendv_with_offset(conn, - test_iovecs.iovecs, test_iovecs.iovecs_count, 0, &blocked); - EXPECT_EQUAL(result, sizeof(test_data)); - - EXPECT_EQUAL(out.sendmsg_invoked_count, 1); - EXPECT_EQUAL(blocked, S2N_NOT_BLOCKED); - EXPECT_OK(s2n_test_validate_ancillary(&out, TLS_APPLICATION_DATA, sizeof(test_data))); - EXPECT_OK(s2n_test_validate_data(&out, test_data, sizeof(test_data))); - }; - /* Test: Send with offset */ { DEFER_CLEANUP(struct s2n_test_iovecs test_iovecs = { 0 }, s2n_test_iovecs_free); EXPECT_OK(s2n_test_new_iovecs(&test_iovecs, &test_data_blob, test_iov_lens, s2n_array_len(test_iov_lens))); + size_t large_test_iov_lens[100] = { 0 }; + EXPECT_MEMCPY_SUCCESS(large_test_iov_lens, test_iov_lens, sizeof(test_iov_lens)); + + DEFER_CLEANUP(struct s2n_test_iovecs large_test_iovecs = { 0 }, + s2n_test_iovecs_free); + EXPECT_OK(s2n_test_new_iovecs(&large_test_iovecs, &test_data_blob, + large_test_iov_lens, s2n_array_len(large_test_iov_lens))); + /* Test: Send with invalid / too large offset */ { const size_t bad_offset = sizeof(test_data) + 1; @@ -716,16 +702,38 @@ int main(int argc, char **argv) EXPECT_OK(s2n_test_init_ktls_io_stuffer_send(conn, &out)); s2n_blocked_status blocked = S2N_NOT_BLOCKED; - ssize_t result = s2n_ktls_sendv_with_offset(conn, - test_iovecs.iovecs, test_iovecs.iovecs_count, bad_offset, &blocked); + ssize_t result = s2n_ktls_sendv_with_offset(conn, large_test_iovecs.iovecs, + large_test_iovecs.iovecs_count, bad_offset, &blocked); EXPECT_FAILURE_WITH_ERRNO(result, S2N_ERR_INVALID_ARGUMENT); EXPECT_EQUAL(out.sendmsg_invoked_count, 0); EXPECT_EQUAL(blocked, S2N_NOT_BLOCKED); EXPECT_OK(s2n_test_records_in_ancillary(&out, 0)); - } + }; + + /* Test: Send with offset equal to total data size */ + { + const size_t offset = sizeof(test_data); + + DEFER_CLEANUP(struct s2n_connection *conn = s2n_connection_new(S2N_SERVER), + s2n_connection_ptr_free); + EXPECT_NOT_NULL(conn); - /* Test: Send with all possible valid offsets */ + DEFER_CLEANUP(struct s2n_test_ktls_io_stuffer out = { 0 }, + s2n_ktls_io_stuffer_free); + EXPECT_OK(s2n_test_init_ktls_io_stuffer_send(conn, &out)); + + s2n_blocked_status blocked = S2N_NOT_BLOCKED; + int written = s2n_ktls_sendv_with_offset(conn, large_test_iovecs.iovecs, + large_test_iovecs.iovecs_count, offset, &blocked); + EXPECT_EQUAL(written, 0); + + EXPECT_EQUAL(out.sendmsg_invoked_count, 1); + EXPECT_EQUAL(blocked, S2N_NOT_BLOCKED); + EXPECT_OK(s2n_test_records_in_ancillary(&out, 0)); + }; + + /* Test: Send with small iovecs array and all possible valid offsets */ for (size_t offset = 0; offset < sizeof(test_data); offset++) { DEFER_CLEANUP(struct s2n_connection *conn = s2n_connection_new(S2N_SERVER), s2n_connection_ptr_free); @@ -748,6 +756,30 @@ int main(int argc, char **argv) EXPECT_OK(s2n_test_validate_ancillary(&out, TLS_APPLICATION_DATA, expected_sent)); EXPECT_OK(s2n_test_validate_data(&out, test_data + offset, expected_sent)); } + + /* Test: Send with large iovecs array and all possible valid offsets */ + for (size_t offset = 0; offset < sizeof(test_data); offset++) { + DEFER_CLEANUP(struct s2n_connection *conn = s2n_connection_new(S2N_SERVER), + s2n_connection_ptr_free); + EXPECT_NOT_NULL(conn); + + DEFER_CLEANUP(struct s2n_test_ktls_io_stuffer out = { 0 }, + s2n_ktls_io_stuffer_free); + EXPECT_OK(s2n_test_init_ktls_io_stuffer_send(conn, &out)); + + const size_t expected_sent = sizeof(test_data) - offset; + EXPECT_TRUE(expected_sent > 0); + + s2n_blocked_status blocked = S2N_NOT_BLOCKED; + ssize_t result = s2n_ktls_sendv_with_offset(conn, large_test_iovecs.iovecs, + large_test_iovecs.iovecs_count, offset, &blocked); + EXPECT_EQUAL(result, expected_sent); + + EXPECT_EQUAL(out.sendmsg_invoked_count, 1); + EXPECT_EQUAL(blocked, S2N_NOT_BLOCKED); + EXPECT_OK(s2n_test_validate_ancillary(&out, TLS_APPLICATION_DATA, expected_sent)); + EXPECT_OK(s2n_test_validate_data(&out, test_data + offset, expected_sent)); + } }; /* Test: Partial write */ @@ -799,6 +831,128 @@ int main(int argc, char **argv) EXPECT_EQUAL(io_ctx.invoked_count, 1); EXPECT_EQUAL(blocked, S2N_BLOCKED_ON_WRITE); }; + + /* Test: Memory usage */ + { + const size_t iov_lens[100] = { 10, 5, 0, 1, 100, 100, 10 }; + const size_t small_iov_lens_count = 10; + const size_t large_iov_lens_count = s2n_array_len(iov_lens); + + DEFER_CLEANUP(struct s2n_test_iovecs small_iovecs = { 0 }, s2n_test_iovecs_free); + EXPECT_OK(s2n_test_new_iovecs(&small_iovecs, &test_data_blob, + iov_lens, small_iov_lens_count)); + + DEFER_CLEANUP(struct s2n_test_iovecs large_iovecs = { 0 }, s2n_test_iovecs_free); + EXPECT_OK(s2n_test_new_iovecs(&large_iovecs, &test_data_blob, + iov_lens, large_iov_lens_count)); + + const size_t one_iovec_size = sizeof(struct iovec); + const size_t large_iovecs_size = large_iovecs.iovecs_count * one_iovec_size; + + struct { + struct s2n_test_iovecs *iovecs; + size_t offset; + uint32_t expected_malloc; + uint32_t expected_malloc_count; + } test_cases[] = { + /* Small iovecs never require an allocation */ + { + .iovecs = &small_iovecs, + .offset = 1, + .expected_malloc_count = 0, + }, + { + .iovecs = &small_iovecs, + .offset = iov_lens[0], + .expected_malloc_count = 0, + }, + { + .iovecs = &small_iovecs, + .offset = iov_lens[0] + 1, + .expected_malloc_count = 0, + }, + /* Large iovecs with offset evenly divisible by the iov_lens do + * not require an alloc. + * Example: { x, y, z }, offset=x -> { y, z } + */ + { + .iovecs = &large_iovecs, + .offset = iov_lens[0], + .expected_malloc_count = 0, + }, + { + .iovecs = &large_iovecs, + .offset = iov_lens[0] + iov_lens[1], + .expected_malloc_count = 0, + }, + /* Large iovecs with offset not evenly divisible by the iov_lens + * modify an entry so require an alloc. + * Example: { x, y, z }, offset=1 -> { x-1, y, z } + */ + { + .iovecs = &large_iovecs, + .offset = 1, + .expected_malloc_count = 1, + .expected_malloc = large_iovecs_size, + }, + { + .iovecs = &large_iovecs, + .offset = iov_lens[0] + 1, + .expected_malloc_count = 1, + .expected_malloc = large_iovecs_size - one_iovec_size, + }, + /* Large iovecs that become small iovecs when the offset + * is applied do not require an alloc. + */ + { + .iovecs = &large_iovecs, + .offset = sizeof(test_data) - 1, + .expected_malloc_count = 0, + }, + /* No alloc if the entire large iovec is skipped */ + { + .iovecs = &large_iovecs, + .offset = sizeof(test_data), + .expected_malloc_count = 0, + }, + }; + + for (size_t i = 0; i < s2n_array_len(test_cases); i++) { + struct iovec *iovecs = test_cases[i].iovecs->iovecs; + const size_t iovecs_count = test_cases[i].iovecs->iovecs_count; + const size_t offset = test_cases[i].offset; + + const size_t expected_send = sizeof(test_data) - offset; + + DEFER_CLEANUP(struct s2n_connection *conn = s2n_connection_new(S2N_SERVER), + s2n_connection_ptr_free); + EXPECT_NOT_NULL(conn); + + DEFER_CLEANUP(struct s2n_test_ktls_io_stuffer out = { 0 }, + s2n_ktls_io_stuffer_free); + EXPECT_OK(s2n_test_init_ktls_io_stuffer_send(conn, &out)); + + /* Preemptively allocate sendmsg memory to avoid false positives */ + EXPECT_SUCCESS(s2n_stuffer_resize(&out.data_buffer, expected_send)); + EXPECT_SUCCESS(s2n_stuffer_resize(&out.ancillary_buffer, 100)); + + DEFER_CLEANUP(struct s2n_mem_test_cb_scope scope = { 0 }, + s2n_mem_test_free_callbacks); + EXPECT_OK(s2n_mem_test_init_callbacks(&scope)); + + s2n_blocked_status blocked = S2N_NOT_BLOCKED; + ssize_t result = s2n_ktls_sendv_with_offset(conn, iovecs, iovecs_count, + offset, &blocked); + EXPECT_EQUAL(result, sizeof(test_data) - offset); + + size_t malloc_count = test_cases[i].expected_malloc_count; + EXPECT_OK(s2n_mem_test_assert_malloc_count(malloc_count)); + if (malloc_count) { + EXPECT_OK(s2n_mem_test_assert_malloc(test_cases[i].expected_malloc)); + } + EXPECT_OK(s2n_mem_test_assert_all_freed()); + } + }; }; /* Test: s2n_ktls_send_cb */ @@ -814,8 +968,8 @@ int main(int argc, char **argv) /* Safety */ { struct s2n_test_ktls_io_stuffer ctx = { 0 }; - EXPECT_FAILURE_WITH_ERRNO(s2n_ktls_send_cb(NULL, test_data, 1), S2N_ERR_IO); - EXPECT_FAILURE_WITH_ERRNO(s2n_ktls_send_cb(&ctx, NULL, 1), S2N_ERR_IO); + EXPECT_FAILURE_WITH_ERRNO(s2n_ktls_send_cb(NULL, test_data, 1), S2N_ERR_NULL); + EXPECT_FAILURE_WITH_ERRNO(s2n_ktls_send_cb(&ctx, NULL, 1), S2N_ERR_NULL); }; /* Test: Basic write succeeds */ diff --git a/tls/s2n_ktls_io.c b/tls/s2n_ktls_io.c index b0448ff6303..04e210932aa 100644 --- a/tls/s2n_ktls_io.c +++ b/tls/s2n_ktls_io.c @@ -39,6 +39,9 @@ #define S2N_KTLS_RECORD_TYPE_SIZE (sizeof(uint8_t)) #define S2N_KTLS_CONTROL_BUFFER_SIZE (CMSG_SPACE(S2N_KTLS_RECORD_TYPE_SIZE)) +#define S2N_MAX_STACK_IOVECS 16 +#define S2N_MAX_STACK_IOVECS_MEM (S2N_MAX_STACK_IOVECS * sizeof(struct iovec)) + /* Used to override sendmsg and recvmsg for testing. */ static ssize_t s2n_ktls_default_sendmsg(void *io_context, const struct msghdr *msg); static ssize_t s2n_ktls_default_recvmsg(void *io_context, struct msghdr *msg); @@ -274,32 +277,73 @@ S2N_RESULT s2n_ktls_recvmsg(void *io_context, uint8_t *record_type, uint8_t *buf return S2N_RESULT_OK; } -static S2N_RESULT s2n_ktls_new_iovecs_with_offset(const struct iovec *bufs, - size_t count, size_t offs, struct s2n_blob *mem) +/* The iovec array `bufs` is constant and owned by the application. + * + * However, we need to apply the given offset to `bufs`. That may involve + * updating the iov_base and iov_len of entries in `bufs` to reflect the bytes + * already sent. Because `bufs` is constant, we need to instead copy `bufs` and + * modify the copy. + * + * Since one of the primary benefits of kTLS is that we avoid buffering application + * data and can pass application data as-is to the kernel, we try to limit the + * situations where we need to copy `bufs` and use stack memory where possible. + * + * Note: We are copying an array of iovecs here, NOT the scattered application + * data the iovecs reference. On Linux, the maximum data copied would be + * 1024 (IOV_MAX on Linux) * 16 (sizeof(struct iovec)) = ~16KB. + * + * To avoid any copies when using a large number of iovecs, applications should + * call s2n_sendv instead of s2n_sendv_with_offset. + */ +static S2N_RESULT s2n_ktls_update_bufs_with_offset(const struct iovec **bufs, size_t *count, + size_t offs, struct s2n_blob *mem) { - RESULT_ENSURE(bufs != NULL || count == 0, S2N_ERR_NULL); + RESULT_ENSURE_REF(bufs); + RESULT_ENSURE_REF(count); + RESULT_ENSURE(*bufs != NULL || *count == 0, S2N_ERR_NULL); RESULT_ENSURE_REF(mem); - RESULT_GUARD_POSIX(s2n_realloc(mem, sizeof(struct iovec) * count)); - struct iovec *new_bufs = (struct iovec *) (void *) mem->data; - RESULT_ENSURE_REF(new_bufs); - - for (size_t i = 0; i < count; i++) { - size_t old_len = bufs[i].iov_len; - if (offs < old_len) { - new_bufs[i].iov_base = (uint8_t *) bufs[i].iov_base + offs; - new_bufs[i].iov_len = old_len - offs; - offs = 0; - } else { - /* Zero any iovec skipped by the offset. - * We could change the count of the copy instead, but this is simpler. */ - new_bufs[i].iov_base = NULL; - new_bufs[i].iov_len = 0; - offs -= old_len; + size_t skipped = 0; + while (offs > 0) { + /* If we need to skip more iovecs than actually exist, + * then the offset is too large and therefore invalid. + */ + RESULT_ENSURE(skipped < *count, S2N_ERR_INVALID_ARGUMENT); + + size_t iov_len = (*bufs)[skipped].iov_len; + + /* This is the last iovec affected by the offset. */ + if (offs < iov_len) { + break; } + + offs -= iov_len; + skipped++; } - /* The offset cannot be greater than the total size of all iovecs */ - RESULT_ENSURE(offs == 0, S2N_ERR_INVALID_ARGUMENT); + + *count = (*count) - skipped; + if (*count == 0) { + return S2N_RESULT_OK; + } + + *bufs = &(*bufs)[skipped]; + if (offs == 0) { + return S2N_RESULT_OK; + } + + size_t size = (*count) * (sizeof(struct iovec)); + /* If possible, use the existing stack memory in `mem` for the copy. + * Otherwise, we need to allocate sufficient new heap memory. */ + if (size > mem->size) { + RESULT_GUARD_POSIX(s2n_alloc(mem, size)); + } + + struct iovec *new_bufs = (struct iovec *) (void *) mem->data; + RESULT_CHECKED_MEMCPY(new_bufs, *bufs, size); + new_bufs[0].iov_base = (uint8_t *) new_bufs[0].iov_base + offs; + new_bufs[0].iov_len = new_bufs[0].iov_len - offs; + *bufs = new_bufs; + return S2N_RESULT_OK; } @@ -312,13 +356,11 @@ ssize_t s2n_ktls_sendv_with_offset(struct s2n_connection *conn, const struct iov POSIX_ENSURE(offs_in >= 0, S2N_ERR_INVALID_ARGUMENT); size_t offs = offs_in; - DEFER_CLEANUP(struct s2n_blob new_bufs = { 0 }, s2n_free); + DEFER_CLEANUP(struct s2n_blob new_bufs = { 0 }, s2n_free_or_wipe); + uint8_t new_bufs_mem[S2N_MAX_STACK_IOVECS_MEM] = { 0 }; + POSIX_GUARD(s2n_blob_init(&new_bufs, new_bufs_mem, sizeof(new_bufs_mem))); if (offs > 0) { - /* We can't modify the application-owned iovecs to reflect the offset. - * Therefore, we must alloc and modify a copy. - */ - POSIX_GUARD_RESULT(s2n_ktls_new_iovecs_with_offset(bufs, count, offs, &new_bufs)); - bufs = (const struct iovec *) (void *) new_bufs.data; + POSIX_GUARD_RESULT(s2n_ktls_update_bufs_with_offset(&bufs, &count, offs, &new_bufs)); } size_t bytes_written = 0; @@ -329,6 +371,9 @@ ssize_t s2n_ktls_sendv_with_offset(struct s2n_connection *conn, const struct iov int s2n_ktls_send_cb(void *io_context, const uint8_t *buf, uint32_t len) { + POSIX_ENSURE_REF(io_context); + POSIX_ENSURE_REF(buf); + /* For now, all control records are assumed to be alerts. * We can set the record_type on the io_context in the future. */ From 87f0c9e543578d98f00a54165bbdb2a43a4dac80 Mon Sep 17 00:00:00 2001 From: Lindsay Stewart Date: Fri, 15 Sep 2023 05:20:06 -0700 Subject: [PATCH 4/4] 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;