From b25bf7f54afc1b6c3459d23b4aabd471fcefb6c2 Mon Sep 17 00:00:00 2001 From: Apoorv Kothari Date: Fri, 23 Jun 2023 15:55:55 -0700 Subject: [PATCH] ktls: send and recv cmsg --- tests/unit/s2n_ktls_cmsg_test.c | 193 ++++++++++++++++++++++++++++ tls/s2n_ktls.c | 2 +- tls/s2n_ktls.h | 12 ++ tls/s2n_ktls_io.c | 221 ++++++++++++++++++++++++++++++++ tls/s2n_ktls_linux.h | 39 ++++++ tls/s2n_ktls_unsupported.h | 33 +++++ 6 files changed, 499 insertions(+), 1 deletion(-) create mode 100644 tests/unit/s2n_ktls_cmsg_test.c create mode 100644 tls/s2n_ktls_io.c create mode 100644 tls/s2n_ktls_linux.h create mode 100644 tls/s2n_ktls_unsupported.h diff --git a/tests/unit/s2n_ktls_cmsg_test.c b/tests/unit/s2n_ktls_cmsg_test.c new file mode 100644 index 00000000000..db290933d7e --- /dev/null +++ b/tests/unit/s2n_ktls_cmsg_test.c @@ -0,0 +1,193 @@ +/* + * 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 "error/s2n_errno.h" +#include "s2n.h" +#include "s2n_test.h" +#include "testlib/s2n_testlib.h" +#include "tls/s2n_ktls.h" + +S2N_RESULT s2n_ktls_send_msg_impl(int sock, struct msghdr *msg, + const struct iovec *msg_iov, s2n_blocked_status *blocked, ssize_t *result); +S2N_RESULT s2n_ktls_recv_msg_impl(int sock, struct msghdr *msg, + struct iovec *msg_iov, s2n_blocked_status *blocked, ssize_t *result); +S2N_RESULT s2n_ktls_send_control_msg(int sock, struct msghdr *msg, + uint8_t record_type, s2n_blocked_status *blocked, ssize_t *result); +S2N_RESULT s2n_ktls_recv_control_msg(int sock, struct msghdr *msg, + uint8_t *record_type, s2n_blocked_status *blocked, ssize_t *result); + +#define MAX_DATA_LEN 20000 + +S2N_RESULT helper_generate_test_data(struct s2n_blob *test_data) +{ + struct s2n_stuffer test_data_stuffer = { 0 }; + RESULT_GUARD_POSIX(s2n_stuffer_init(&test_data_stuffer, test_data)); + for (int i = 1; i < MAX_DATA_LEN; i++) { + RESULT_GUARD_POSIX(s2n_stuffer_write_uint8(&test_data_stuffer, i)); + } + return S2N_RESULT_OK; +} + +int main(int argc, char **argv) +{ + BEGIN_TEST(); + + uint8_t test_data[MAX_DATA_LEN] = { 0 }; + struct s2n_blob test_data_blob = { 0 }; + EXPECT_SUCCESS(s2n_blob_init(&test_data_blob, test_data, sizeof(test_data))); + EXPECT_OK(helper_generate_test_data(&test_data_blob)); + + struct msghdr msg = { 0 }; + struct iovec msg_iov = { 0 }; + +#if defined(S2N_KTLS_SUPPORTED) + /* ctrl_msg send and recv data */ + for (size_t to_send = 1; to_send < MAX_DATA_LEN; to_send += 500) { + /* Create a pipe */ + struct s2n_test_io_pair io_pair; + EXPECT_SUCCESS(s2n_io_pair_init_non_blocking(&io_pair)); + s2n_blocked_status blocked = S2N_NOT_BLOCKED; + + { + msg_iov.iov_base = (void *) (uintptr_t) test_data; + msg_iov.iov_len = to_send; + + ssize_t sent_len = 0; + EXPECT_OK(s2n_ktls_send_msg_impl(io_pair.client, &msg, &msg_iov, &blocked, &sent_len)); + EXPECT_EQUAL(sent_len, to_send); + } + + uint8_t recv_buffer[MAX_DATA_LEN] = { 0 }; + /* confirm test_data and recv_buffer dont match */ + EXPECT_NOT_EQUAL(memcmp(test_data, recv_buffer, to_send), 0); + { + msg_iov.iov_base = recv_buffer; + msg_iov.iov_len = to_send; + + ssize_t recv_len = 0; + EXPECT_OK(s2n_ktls_recv_msg_impl(io_pair.server, &msg, &msg_iov, &blocked, &recv_len)); + EXPECT_EQUAL(recv_len, to_send); + EXPECT_EQUAL(memcmp(test_data, recv_buffer, recv_len), 0); + } + } + + /* test blocked data and partial reads */ + { + /* Create a pipe */ + struct s2n_test_io_pair io_pair; + EXPECT_SUCCESS(s2n_io_pair_init_non_blocking(&io_pair)); + s2n_blocked_status blocked = S2N_NOT_BLOCKED; + + /* only read half the total data sent to simulate multiple reads */ + size_t to_send = 10; + size_t to_recv = 5; + + uint8_t recv_buffer[MAX_DATA_LEN] = { 0 }; + /* confirm test_data and recv_buffer dont match */ + EXPECT_NOT_EQUAL(memcmp(test_data, recv_buffer, to_send), 0); + + /* calling recv when nothing has been sent blocks */ + { + msg_iov.iov_base = recv_buffer; + msg_iov.iov_len = to_recv; + + ssize_t recv_len = 0; + EXPECT_ERROR_WITH_ERRNO(s2n_ktls_recv_msg_impl(io_pair.server, &msg, &msg_iov, &blocked, &recv_len), S2N_ERR_IO_BLOCKED); + EXPECT_EQUAL(blocked, S2N_BLOCKED_ON_READ); + } + + /* send data */ + { + msg_iov.iov_base = (void *) (uintptr_t) test_data; + msg_iov.iov_len = to_send; + + ssize_t sent_len = 0; + EXPECT_OK(s2n_ktls_send_msg_impl(io_pair.client, &msg, &msg_iov, &blocked, &sent_len)); + EXPECT_EQUAL(sent_len, to_send); + } + + /* only read half the amount of data sent */ + { + msg_iov.iov_base = recv_buffer; + msg_iov.iov_len = to_recv; + + ssize_t recv_len = 0; + EXPECT_OK(s2n_ktls_recv_msg_impl(io_pair.server, &msg, &msg_iov, &blocked, &recv_len)); + EXPECT_EQUAL(recv_len, to_recv); + EXPECT_EQUAL(memcmp(test_data, recv_buffer, to_recv), 0); + } + + /* read the other half of data sent */ + { + /* offset the read buffer by the amount already read */ + msg_iov.iov_base = recv_buffer + to_recv; + msg_iov.iov_len = to_recv; + + ssize_t recv_len = 0; + EXPECT_OK(s2n_ktls_recv_msg_impl(io_pair.server, &msg, &msg_iov, &blocked, &recv_len)); + EXPECT_EQUAL(recv_len, to_recv); + } + + /* confirm that all data was read and matches sent data of length `to_send` */ + EXPECT_EQUAL(memcmp(test_data, recv_buffer, to_send), 0); + } + + /* cmsg send and recv ancillary data */ + { + uint8_t send_record_type = 10; + int fd = 0; + s2n_blocked_status blocked = S2N_NOT_BLOCKED; + ssize_t result = 0; + union { + char buf[CMSG_SPACE(sizeof(send_record_type))]; + /* Space large enough to hold a ucred structure */ + struct cmsghdr align; + } control_msg; + + /* Init msghdr */ + struct msghdr s_msg = { 0 }; + s_msg.msg_name = NULL; + s_msg.msg_namelen = 0; + s_msg.msg_control = control_msg.buf; + s_msg.msg_controllen = sizeof(control_msg.buf); + + /* create the control_msg */ + EXPECT_OK(s2n_ktls_send_control_msg(fd, &s_msg, send_record_type, &blocked, &result)); + + /* modify control_msg for the recv side + * + * cmsg_type is converted to GET on the recv side if record type is not TLS_APPLICATION_DATA + */ + struct cmsghdr *hdr = CMSG_FIRSTHDR(&s_msg); + hdr->cmsg_type = S2N_TLS_GET_RECORD_TYPE; + + /* assert that we can parse the same record_type */ + uint8_t recv_record_type = 0; + EXPECT_OK(s2n_ktls_recv_control_msg(fd, &s_msg, &recv_record_type, &blocked, &result)); + EXPECT_EQUAL(recv_record_type, send_record_type); + + /* record_type should default to TLS_APPLICATION_DATA */ + hdr->cmsg_type = 0; + hdr->cmsg_level = 0; + recv_record_type = 0; + EXPECT_OK(s2n_ktls_recv_control_msg(fd, &s_msg, &recv_record_type, &blocked, &result)); + EXPECT_EQUAL(recv_record_type, TLS_APPLICATION_DATA); + } +#endif + + END_TEST(); +} diff --git a/tls/s2n_ktls.c b/tls/s2n_ktls.c index 2900337a83f..dc5ffb4b576 100644 --- a/tls/s2n_ktls.c +++ b/tls/s2n_ktls.c @@ -17,7 +17,7 @@ bool s2n_ktls_is_supported_on_platform() { -#if defined(__linux__) +#if defined(S2N_KTLS_SUPPORTED) return true; #else return false; diff --git a/tls/s2n_ktls.h b/tls/s2n_ktls.h index 5e2de31955f..b5ba2338149 100644 --- a/tls/s2n_ktls.h +++ b/tls/s2n_ktls.h @@ -17,6 +17,18 @@ #include "tls/s2n_config.h" +/* Enable kTLS on tested platforms. + * + * Define headers needed to enable and use kTLS. + */ +#if defined(__linux__) + #define S2N_KTLS_SUPPORTED true + #include "tls/s2n_ktls_linux.h" +#else + #undef S2N_KTLS_SUPPORTED + #include "tls/s2n_ktls_unsupported.h" +#endif + /* A set of kTLS configurations representing the combination of sending * and receiving. */ diff --git a/tls/s2n_ktls_io.c b/tls/s2n_ktls_io.c new file mode 100644 index 00000000000..de4355a1885 --- /dev/null +++ b/tls/s2n_ktls_io.c @@ -0,0 +1,221 @@ +/* + * 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 "tls/s2n_ktls.h" +#include "utils/s2n_socket.h" + +/* + * sendmsg and recvmsg are syscalls which can be used to send 'real' data along + * with ancillary data. Ancillary data is used to communicate to the socket the + * type of the TLS record being sent/received. + * + * Ancillary data macros (CMSG_*) are linux specific and gated. + */ + +S2N_RESULT s2n_ktls_send_msg_impl(int sock, struct msghdr *msg, + const struct iovec *msg_iov, s2n_blocked_status *blocked, ssize_t *result) +{ + RESULT_ENSURE_REF(msg); + RESULT_ENSURE_REF(msg_iov); + RESULT_ENSURE_REF(blocked); + RESULT_ENSURE_REF(result); + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-qual" + /* set send buffer */ + msg->msg_iov = (struct iovec *) msg_iov; +#pragma GCC diagnostic pop + msg->msg_iovlen = 1; + + *blocked = S2N_BLOCKED_ON_WRITE; + + *result = sendmsg(sock, msg, 0); + if (*result < 0) { + if (errno == EWOULDBLOCK || errno == EAGAIN) { + RESULT_BAIL(S2N_ERR_IO_BLOCKED); + } + } + + *blocked = S2N_NOT_BLOCKED; + + return S2N_RESULT_OK; +} + +S2N_RESULT s2n_ktls_send_control_msg(int sock, struct msghdr *msg, + uint8_t record_type, s2n_blocked_status *blocked, ssize_t *result) +{ + RESULT_ENSURE_REF(msg); + RESULT_ENSURE_REF(blocked); + RESULT_ENSURE_REF(result); + +#if defined(__linux__) + /* set ancillary data */ + struct cmsghdr *hdr = CMSG_FIRSTHDR(msg); + hdr->cmsg_level = S2N_SOL_TLS; + hdr->cmsg_type = S2N_TLS_SET_RECORD_TYPE; + hdr->cmsg_len = CMSG_LEN(sizeof(record_type)); + RESULT_CHECKED_MEMCPY(CMSG_DATA(hdr), &record_type, sizeof(record_type)); +#endif + + return S2N_RESULT_OK; +} + +/* Best practices taken from + * https://man7.org/tlpi/code/online/dist/sockets/scm_cred_send.c.html */ +S2N_RESULT s2n_ktls_send_msg( + int sock, uint8_t record_type, const struct iovec *msg_iov, + s2n_blocked_status *blocked, ssize_t *result) +{ + RESULT_ENSURE_REF(msg_iov); + RESULT_ENSURE_REF(msg_iov->iov_base); + RESULT_ENSURE_GT(msg_iov->iov_len, 0); + RESULT_ENSURE_REF(blocked); + RESULT_ENSURE_REF(result); + + /* Init msghdr + * + * The control message buffer must be zero-initialized in order for the + * CMSG_NXTHDR() macro to work correctly. However since we dont use + * CMSG_NXTHDR for kTLS and this code path is performance sensitive we skip + * this step. + * + * RESULT_CHECKED_MEMSET(&control_msg.buf, 0, sizeof(control_msg.buf)); + */ + struct msghdr msg = { 0 }; + msg.msg_name = NULL; + msg.msg_namelen = 0; + +#if defined(__linux__) + /* Allocate a char array of suitable size to hold the ancillary data. + * However, since this buffer is in reality a 'struct cmsghdr', use a + * union to ensure that it is aligned as required for that structure. + */ + union { + char buf[CMSG_SPACE(sizeof(record_type))]; + /* Space large enough to hold a ucred structure */ + struct cmsghdr align; + } control_msg; + + msg.msg_control = control_msg.buf; + msg.msg_controllen = sizeof(control_msg.buf); +#endif + + RESULT_GUARD(s2n_ktls_send_control_msg(sock, &msg, record_type, blocked, result)); + + RESULT_GUARD(s2n_ktls_send_msg_impl(sock, &msg, msg_iov, blocked, result)); + + return S2N_RESULT_OK; +} + +S2N_RESULT s2n_ktls_recv_msg_impl(int sock, struct msghdr *msg, + struct iovec *msg_iov, s2n_blocked_status *blocked, ssize_t *result) +{ + RESULT_ENSURE_REF(msg); + RESULT_ENSURE_REF(msg_iov); + RESULT_ENSURE_REF(blocked); + RESULT_ENSURE_REF(result); + + /* set receive buffer */ + msg->msg_iov = msg_iov; + msg->msg_iovlen = 1; + + *blocked = S2N_BLOCKED_ON_READ; + *result = recvmsg(sock, msg, 0); + if (*result == -1) { + if (errno == EWOULDBLOCK || errno == EAGAIN) { + RESULT_BAIL(S2N_ERR_IO_BLOCKED); + } + return S2N_RESULT_ERROR; + } + *blocked = S2N_NOT_BLOCKED; + + if (*result == 0) { + /* The return value will be 0 when the peer has performed an orderly shutdown. */ + return S2N_RESULT_ERROR; + } + + return S2N_RESULT_OK; +} + +S2N_RESULT s2n_ktls_recv_control_msg(int sock, struct msghdr *msg, + uint8_t *record_type, s2n_blocked_status *blocked, ssize_t *result) +{ + RESULT_ENSURE_REF(msg); + RESULT_ENSURE_REF(record_type); + RESULT_ENSURE_REF(blocked); + RESULT_ENSURE_REF(result); + +#if defined(__linux__) + /* attempt to read the ancillary data */ + struct cmsghdr *hdr = CMSG_FIRSTHDR(msg); + if (hdr == NULL) { + return S2N_RESULT_ERROR; + } + if (hdr->cmsg_level == S2N_SOL_TLS && hdr->cmsg_type == S2N_TLS_GET_RECORD_TYPE) { + *record_type = *(unsigned char *) CMSG_DATA(hdr); + } else { + /* if no RECORD_TYPE is included then assume its TLS_APPLICATION_DATA */ + *record_type = TLS_APPLICATION_DATA; + } +#endif + + return S2N_RESULT_OK; +} + +/* Best practices taken from + * https://man7.org/tlpi/code/online/dist/sockets/scm_cred_recv.c.html */ +S2N_RESULT s2n_ktls_recv_msg(int sock, uint8_t *buf, size_t length, + uint8_t *record_type, s2n_blocked_status *blocked, + ssize_t *result) +{ + RESULT_ENSURE_REF(buf); + RESULT_ENSURE_REF(record_type); + RESULT_ENSURE_REF(blocked); + RESULT_ENSURE_REF(result); + RESULT_ENSURE_GT(length, 0); + + /* Init msghdr */ + struct msghdr msg = { 0 }; + msg.msg_name = NULL; + msg.msg_namelen = 0; + +#if defined(__linux__) + /* Allocate a char array of suitable size to hold the ancillary data. + * However, since this buffer is in reality a 'struct cmsghdr', use a + * union to ensure that it is aligned as required for that structure. + */ + union { + char buf[CMSG_SPACE(sizeof(record_type))]; + /* Space large enough to hold a ucred structure */ + struct cmsghdr align; + } control_msg; + + msg.msg_control = control_msg.buf; + msg.msg_controllen = sizeof(control_msg.buf); +#endif + + struct iovec msg_iov; + msg_iov.iov_base = buf; + msg_iov.iov_len = length; + + /* receive msg */ + RESULT_GUARD(s2n_ktls_recv_msg_impl(sock, &msg, &msg_iov, blocked, result)); + + RESULT_GUARD(s2n_ktls_recv_control_msg(sock, &msg, record_type, blocked, result)); + + return S2N_RESULT_OK; +} diff --git a/tls/s2n_ktls_linux.h b/tls/s2n_ktls_linux.h new file mode 100644 index 00000000000..6453df24606 --- /dev/null +++ b/tls/s2n_ktls_linux.h @@ -0,0 +1,39 @@ +/* + * 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. + */ + +#pragma once + +/* ################################## + * START kTLS specific headers + * ################################## + * + * - https://elixir.bootlin.com/linux/v6.3.8/A/ident/TCP_ULP + * - https://elixir.bootlin.com/linux/v6.3.8/A/ident/SOL_TCP + * + * Linux doesn't expose kTLS headers in its uapi. Its possible to get these headers + * via glibc but support can vary depending on the version of glibc on the host. + * Instead we define the headers inline and gate compilation to linux. + */ +/* socket definitions */ +#define S2N_SOL_TLS 282 + +/* cmsg */ +#define S2N_TLS_SET_RECORD_TYPE 1 +#define S2N_TLS_GET_RECORD_TYPE 2 + +/* ################################## + * END kTLS specific headers + * ################################## */ + diff --git a/tls/s2n_ktls_unsupported.h b/tls/s2n_ktls_unsupported.h new file mode 100644 index 00000000000..66863be5469 --- /dev/null +++ b/tls/s2n_ktls_unsupported.h @@ -0,0 +1,33 @@ +/* + * 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. + */ + +#pragma once + +/* ################################## + * START kTLS specific headers + * ################################## + * + * For unsupported platforms 0-init all values. + */ +/* socket definitions */ +#define S2N_SOL_TLS 0 + +/* cmsg */ +#define S2N_TLS_SET_RECORD_TYPE 0 +#define S2N_TLS_GET_RECORD_TYPE 0 + +/* ################################## + * END kTLS specific headers + * ################################## */