Skip to content

Commit

Permalink
ktls: mock send/recvmsg IO
Browse files Browse the repository at this point in the history
  • Loading branch information
toidiu committed Jul 26, 2023
1 parent 65e74ca commit d400d46
Show file tree
Hide file tree
Showing 7 changed files with 458 additions and 5 deletions.
202 changes: 202 additions & 0 deletions tests/testlib/s2n_ktls_test_utils.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
/*
* 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"

/* These MOCK_IO* macros set errno before returning an error. These macros are
* mainly used for IO related operations (stuffer writes, NULL arguments,
* invalid length). Other sanity checks are guarded by the POSIX* macros.
*
* EINVAL will always be treated as a fatal errno code so its sufficient to
* use that as the generic errno code in the guard macros. */
#define MOCK_IO_ENSURE(x) \
do { \
if (!(x)) { \
errno = EINVAL; \
return -1; \
} \
} while (0)

#define MOCK_IO_GUARD(x) \
MOCK_IO_ENSURE((x) > S2N_FAILURE)

#define MOCK_IO_ENSURE_REF(x) \
MOCK_IO_ENSURE(S2N_OBJECT_PTR_IS_READABLE(x))

static S2N_RESULT s2n_ktls_validate_ktls_io(struct s2n_test_ktls_io_stuffer *io_ctx)
{
RESULT_ENSURE_REF(io_ctx);

/* Assert ancillary_buffer is growable, which simplifies IO logic. Blocking IO can
* be mocked by restricting the data_buffer. */
RESULT_ENSURE(io_ctx->ancillary_buffer.growable, S2N_ERR_SAFETY);

uint32_t ancillary_len = s2n_stuffer_data_available(&io_ctx->ancillary_buffer);
uint32_t data_len = s2n_stuffer_data_available(&io_ctx->data_buffer);

/* ensure ancillary data is not malformed */
RESULT_ENSURE(ancillary_len % S2N_TEST_KTLS_MOCK_HEADER_SIZE == 0, S2N_ERR_SAFETY);
if (ancillary_len == 0) {
RESULT_ENSURE(data_len == 0, S2N_ERR_SAFETY);
}

return S2N_RESULT_OK;
}

S2N_RESULT s2n_test_ktls_rewrite_prev_header_len(struct s2n_test_ktls_io_stuffer *io_ctx, uint16_t remaining_len)
{
RESULT_ENSURE_REF(io_ctx);
RESULT_ENSURE(remaining_len > 0, S2N_ERR_SAFETY);

/* rewind the read ptr */
RESULT_GUARD_POSIX(s2n_stuffer_rewind_read(&io_ctx->ancillary_buffer, S2N_TEST_KTLS_MOCK_HEADER_LENGTH_SIZE));

/* rewrite the length */
uint8_t *ptr = s2n_stuffer_raw_read(&io_ctx->ancillary_buffer, S2N_TEST_KTLS_MOCK_HEADER_LENGTH_SIZE);
struct s2n_blob ancillary_blob = { 0 };
RESULT_GUARD_POSIX(s2n_blob_init(&ancillary_blob, ptr, S2N_TEST_KTLS_MOCK_HEADER_LENGTH_SIZE));
struct s2n_stuffer ancillary_stuffer = { 0 };
RESULT_GUARD_POSIX(s2n_stuffer_init(&ancillary_stuffer, &ancillary_blob));
RESULT_GUARD_POSIX(s2n_stuffer_write_uint16(&ancillary_stuffer, remaining_len));

/* rewind to re-read the record with the remaining length */
RESULT_GUARD_POSIX(s2n_stuffer_rewind_read(&io_ctx->ancillary_buffer, S2N_TEST_KTLS_MOCK_HEADER_SIZE));

return S2N_RESULT_OK;
}

ssize_t s2n_test_ktls_sendmsg_stuffer_io(struct s2n_connection *conn, struct msghdr *msg, uint8_t record_type)
{
POSIX_ENSURE_REF(conn);
POSIX_ENSURE_REF(conn->send_io_context);
POSIX_ENSURE_REF(msg);
POSIX_ENSURE_REF(msg->msg_iov);

struct s2n_test_ktls_io_stuffer *io_ctx = (struct s2n_test_ktls_io_stuffer *) conn->send_io_context;
POSIX_ENSURE_REF(io_ctx);
POSIX_GUARD_RESULT(s2n_ktls_validate_ktls_io(io_ctx));

size_t total_len = 0;
for (size_t count = 0; count < msg->msg_iovlen; count++) {
uint8_t *buf = msg->msg_iov[count].iov_base;
MOCK_IO_ENSURE_REF(buf);
size_t len = msg->msg_iov[count].iov_len;

/* If we fail to write to stuffer then return blocked */
if (s2n_stuffer_write_bytes(&io_ctx->data_buffer, buf, len) < 0) {
POSIX_GUARD_RESULT(s2n_ktls_validate_ktls_io(io_ctx));
errno = EAGAIN;
return -1;
}

/* write record_type and len after data was written successfully. ancillary_buffer is
* growable so this operation should always succeed. */
MOCK_IO_GUARD(s2n_stuffer_write_uint8(&io_ctx->ancillary_buffer, record_type));
MOCK_IO_GUARD(s2n_stuffer_write_uint16(&io_ctx->ancillary_buffer, len));

total_len += len;
}

POSIX_GUARD_RESULT(s2n_ktls_validate_ktls_io(io_ctx));
return total_len;
}

/* In userspace TLS, s2n first reads the header to determine the length of next record
* and then reads the entire record into conn->in. In kTLS its not possible to know
* the length of the next record. Instead the socket returns the minimum of
* bytes-requested and data-available; reading multiple consecutive records if they
* are of the same type. */
ssize_t s2n_test_ktls_recvmsg_stuffer_io(struct s2n_connection *conn, struct msghdr *msg, uint8_t *record_type)
{
POSIX_ENSURE_REF(conn);
POSIX_ENSURE_REF(conn->recv_io_context);
POSIX_ENSURE_REF(msg);
POSIX_ENSURE_REF(msg->msg_iov);

struct s2n_test_ktls_io_stuffer *io_ctx = (struct s2n_test_ktls_io_stuffer *) conn->recv_io_context;
POSIX_ENSURE_REF(io_ctx);
POSIX_GUARD_RESULT(s2n_ktls_validate_ktls_io(io_ctx));

/* s2n only receives using msg_iovlen of 1 */
MOCK_IO_ENSURE(msg->msg_iovlen == 1);

uint8_t *buf = msg->msg_iov->iov_base;
MOCK_IO_ENSURE_REF(buf);

/* There is no data available so return blocked */
if (!s2n_stuffer_data_available(&io_ctx->ancillary_buffer)) {
POSIX_GUARD_RESULT(s2n_ktls_validate_ktls_io(io_ctx));
errno = EAGAIN;
return -1;
}

ssize_t total_read = 0;
/* updated as partial or multiple records are read */
size_t updated_requested_len = msg->msg_iov->iov_len;
/* track two record_types since its possible to read multiple records of the same type */
*record_type = 0;
uint8_t next_record_type = 0;
while (*record_type == next_record_type) {
/* read record_type and number of bytes available in the next record */
MOCK_IO_GUARD(s2n_stuffer_read_uint8(&io_ctx->ancillary_buffer, record_type));
uint16_t n_avail = 0;
MOCK_IO_GUARD(s2n_stuffer_read_uint16(&io_ctx->ancillary_buffer, &n_avail));
POSIX_ENSURE_LTE(n_avail, s2n_stuffer_data_available(&io_ctx->data_buffer));

size_t n_read = MIN(updated_requested_len, n_avail);
MOCK_IO_ENSURE(n_read > 0);

/* we have already verified that there is more data, so this should succeed */
MOCK_IO_GUARD(s2n_stuffer_read_bytes(&io_ctx->data_buffer, buf + total_read, n_read));
updated_requested_len -= n_read;
POSIX_ENSURE_GTE(updated_requested_len, 0);
total_read += n_read;

/* if already read the requested amount then break */
if (updated_requested_len == 0) {
break;
}

/* Handle if we partially read a record */
ssize_t remaining_len = n_avail - n_read;
if (remaining_len) {
POSIX_GUARD_RESULT(s2n_test_ktls_rewrite_prev_header_len(io_ctx, remaining_len));
}

/* Attempt to read multiple records (must be of the same type) */
if (updated_requested_len) {
POSIX_GUARD(s2n_stuffer_peek_char(&io_ctx->ancillary_buffer, (char *) &next_record_type));

bool no_more_data = s2n_stuffer_data_available(&io_ctx->ancillary_buffer) == 0;
bool next_record_different_type = next_record_type != *record_type;

if (no_more_data || next_record_different_type) {
break;
}
}
}

return total_read;
}

S2N_CLEANUP_RESULT s2n_ktls_io_pair_free(struct s2n_test_ktls_io_pair *ctx)
{
RESULT_ENSURE_REF(ctx);
RESULT_GUARD_POSIX(s2n_stuffer_free(&ctx->client_in.data_buffer));
RESULT_GUARD_POSIX(s2n_stuffer_free(&ctx->client_in.ancillary_buffer));
RESULT_GUARD_POSIX(s2n_stuffer_free(&ctx->server_in.data_buffer));
RESULT_GUARD_POSIX(s2n_stuffer_free(&ctx->server_in.ancillary_buffer));
return S2N_RESULT_OK;
}
33 changes: 33 additions & 0 deletions tests/testlib/s2n_testlib.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

#include "stuffer/s2n_stuffer.h"
#include "tls/s2n_connection.h"
#include "tls/s2n_ktls.h"

extern const struct s2n_ecc_preferences ecc_preferences_for_retry;
extern const struct s2n_security_policy security_policy_test_tls13_retry;
Expand Down Expand Up @@ -245,3 +246,35 @@ extern const s2n_parsed_extension EMPTY_PARSED_EXTENSIONS[S2N_PARSED_EXTENSIONS_
int s2n_kem_recv_public_key_fuzz_test(const uint8_t *buf, size_t len, struct s2n_kem_params *kem_params);
int s2n_kem_recv_ciphertext_fuzz_test(const uint8_t *buf, size_t len, struct s2n_kem_params *kem_params);
int s2n_kem_recv_ciphertext_fuzz_test_init(const char *kat_file_path, struct s2n_kem_params *kem_params);

/* kTLS */
/* The record_type is communicated via ancillary data when using kTLS. For this
* reason s2n must use `send/recvmsg` syscalls rather than `send/read`. To mimic
* the send/recvmsg calls more accurately, we mock the socket via two separate
* buffers: data_buffer and ancillary_buffer.
*
* The mock implementation, uses 3 bytes with a tag + len format to represent
* each record. The first byte(tag) is the record_type and the next two represent
* the length of the record. Length is represented as a u16 to capture the max
* possible TLS record length.
*
* ancillary_buffer memory layout:
* [ u8 | u16 ]
* record_type length
*/
#define S2N_TEST_KTLS_MOCK_HEADER_SIZE 3
#define S2N_TEST_KTLS_MOCK_HEADER_LENGTH_SIZE 2
struct s2n_test_ktls_io_stuffer {
struct s2n_stuffer ancillary_buffer;
struct s2n_stuffer data_buffer;
};
struct s2n_test_ktls_io_pair {
struct s2n_test_ktls_io_stuffer client_in;
struct s2n_test_ktls_io_stuffer server_in;
};
S2N_RESULT s2n_test_ktls_rewrite_prev_header_len(struct s2n_test_ktls_io_stuffer *io_ctx, uint16_t new_len);
S2N_CLEANUP_RESULT s2n_ktls_io_pair_free(struct s2n_test_ktls_io_pair *ctx);

/* Used to override sendmsg and recvmsg for testing */
ssize_t s2n_test_ktls_sendmsg_stuffer_io(struct s2n_connection *conn, struct msghdr *msg, uint8_t record_type);
ssize_t s2n_test_ktls_recvmsg_stuffer_io(struct s2n_connection *conn, struct msghdr *msg, uint8_t *record_type);
3 changes: 0 additions & 3 deletions tests/unit/s2n_ktls_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,6 @@
* permissions and limitations under the License.
*/

#include "tls/s2n_ktls.h"

#include "error/s2n_errno.h"
#include "s2n_test.h"
#include "testlib/s2n_testlib.h"
#include "utils/s2n_random.h"
Expand Down
Loading

0 comments on commit d400d46

Please sign in to comment.