/* * 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 /* Use usleep */ #define _XOPEN_SOURCE 500 #include #include #include "api/s2n.h" #include "error/s2n_errno.h" #include "stuffer/s2n_stuffer.h" #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" #include "tls/s2n_tls.h" #include "utils/s2n_blob.h" #include "utils/s2n_io.h" #include "utils/s2n_safety.h" #include "utils/s2n_socket.h" S2N_RESULT s2n_recv_in_init(struct s2n_connection *conn, uint32_t written, uint32_t total) { RESULT_ENSURE_REF(conn); /* If we're going to initialize conn->in to point to more memory than * is actually readable, make sure that the additional memory exists. */ RESULT_ENSURE_LTE(written, total); uint32_t remaining = total - written; RESULT_ENSURE_LTE(remaining, s2n_stuffer_space_remaining(&conn->buffer_in)); uint8_t *data = s2n_stuffer_raw_read(&conn->buffer_in, written); RESULT_ENSURE_REF(data); RESULT_GUARD_POSIX(s2n_stuffer_free(&conn->in)); RESULT_GUARD_POSIX(s2n_blob_init(&conn->in.blob, data, total)); RESULT_GUARD_POSIX(s2n_stuffer_skip_write(&conn->in, written)); return S2N_RESULT_OK; } S2N_RESULT s2n_read_in_bytes(struct s2n_connection *conn, struct s2n_stuffer *output, uint32_t length) { while (s2n_stuffer_data_available(output) < length) { uint32_t remaining = length - s2n_stuffer_data_available(output); if (conn->recv_buffering) { remaining = MAX(remaining, s2n_stuffer_space_remaining(output)); } errno = 0; int r = s2n_connection_recv_stuffer(output, conn, remaining); if (r == 0) { s2n_atomic_flag_set(&conn->read_closed); } RESULT_GUARD(s2n_io_check_read_result(r)); conn->wire_bytes_in += r; } return S2N_RESULT_OK; } static S2N_RESULT s2n_recv_buffer_in(struct s2n_connection *conn, size_t min_size) { RESULT_GUARD_POSIX(s2n_stuffer_resize_if_empty(&conn->buffer_in, S2N_LARGE_FRAGMENT_LENGTH)); uint32_t buffer_in_available = s2n_stuffer_data_available(&conn->buffer_in); if (buffer_in_available < min_size) { uint32_t remaining = min_size - buffer_in_available; if (s2n_stuffer_space_remaining(&conn->buffer_in) < remaining) { RESULT_GUARD_POSIX(s2n_stuffer_shift(&conn->buffer_in)); } RESULT_GUARD(s2n_read_in_bytes(conn, &conn->buffer_in, min_size)); } return S2N_RESULT_OK; } int s2n_read_full_record(struct s2n_connection *conn, uint8_t *record_type, int *isSSLv2) { *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 */ *record_type = TLS_APPLICATION_DATA; return S2N_SUCCESS; } /* Read the record until we at least have a header */ POSIX_GUARD(s2n_stuffer_reread(&conn->header_in)); uint32_t header_available = s2n_stuffer_data_available(&conn->header_in); if (header_available < S2N_TLS_RECORD_HEADER_LENGTH) { uint32_t header_remaining = S2N_TLS_RECORD_HEADER_LENGTH - header_available; s2n_result ret = s2n_recv_buffer_in(conn, header_remaining); uint32_t header_read = MIN(header_remaining, s2n_stuffer_data_available(&conn->buffer_in)); POSIX_GUARD(s2n_stuffer_copy(&conn->buffer_in, &conn->header_in, header_read)); POSIX_GUARD_RESULT(ret); } uint16_t fragment_length = 0; /* If the first bit is set then this is an SSLv2 record */ if (conn->header_in.blob.data[0] & S2N_TLS_SSLV2_HEADER_FLAG) { *isSSLv2 = 1; WITH_ERROR_BLINDING(conn, POSIX_GUARD(s2n_sslv2_record_header_parse(conn, record_type, &conn->client_protocol_version, &fragment_length))); } else { WITH_ERROR_BLINDING(conn, POSIX_GUARD(s2n_record_header_parse(conn, record_type, &fragment_length))); } /* Read enough to have the whole record */ uint32_t fragment_available = s2n_stuffer_data_available(&conn->in); if (fragment_available < fragment_length || fragment_length == 0) { POSIX_GUARD(s2n_stuffer_rewind_read(&conn->buffer_in, fragment_available)); s2n_result ret = s2n_recv_buffer_in(conn, fragment_length); uint32_t fragment_read = MIN(fragment_length, s2n_stuffer_data_available(&conn->buffer_in)); POSIX_GUARD_RESULT(s2n_recv_in_init(conn, fragment_read, fragment_length)); POSIX_GUARD_RESULT(ret); } if (*isSSLv2) { return 0; } /* Decrypt and parse the record */ if (s2n_early_data_is_trial_decryption_allowed(conn, *record_type)) { POSIX_ENSURE(s2n_record_parse(conn) >= S2N_SUCCESS, S2N_ERR_EARLY_DATA_TRIAL_DECRYPT); } else { WITH_ERROR_BLINDING(conn, POSIX_GUARD(s2n_record_parse(conn))); } /* In TLS 1.3, encrypted handshake records would appear to be of record type * TLS_APPLICATION_DATA. The actual record content type is found after the encrypted * is decrypted. */ if (conn->actual_protocol_version == S2N_TLS13 && *record_type == TLS_APPLICATION_DATA) { POSIX_GUARD(s2n_tls13_parse_record_type(&conn->in, record_type)); } return 0; } ssize_t s2n_recv_impl(struct s2n_connection *conn, void *buf, ssize_t size_signed, s2n_blocked_status *blocked) { POSIX_ENSURE_GTE(size_signed, 0); size_t size = size_signed; ssize_t bytes_read = 0; struct s2n_blob out = { 0 }; POSIX_GUARD(s2n_blob_init(&out, (uint8_t *) buf, 0)); /* * Set the `blocked` status to BLOCKED_ON_READ by default * * The only case in which it should be updated is on a successful read into the provided buffer. * * Unfortunately, the current `blocked` behavior has become ossified by buggy applications that ignore * error types and only read `blocked`. As such, it's very important to avoid changing how this value is updated * as it could break applications. */ *blocked = S2N_BLOCKED_ON_READ; if (!s2n_connection_check_io_status(conn, S2N_IO_READABLE)) { /* *= https://www.rfc-editor.org/rfc/rfc8446#6.1 *# If a transport-level close *# is received prior to a "close_notify", the receiver cannot know that *# all the data that was sent has been received. * *= https://www.rfc-editor.org/rfc/rfc8446#6.1 *# If the application protocol using TLS provides that any data may be *# carried over the underlying transport after the TLS connection is *# closed, the TLS implementation MUST receive a "close_notify" alert *# before indicating end-of-data to the application layer. */ POSIX_ENSURE(s2n_atomic_flag_test(&conn->close_notify_received), S2N_ERR_CLOSED); *blocked = S2N_NOT_BLOCKED; return 0; } POSIX_ENSURE(!s2n_connection_is_quic_enabled(conn), S2N_ERR_UNSUPPORTED_WITH_QUIC); POSIX_GUARD_RESULT(s2n_early_data_validate_recv(conn)); while (size && s2n_connection_check_io_status(conn, S2N_IO_READABLE)) { int isSSLv2 = 0; uint8_t record_type = 0; int r = s2n_read_full_record(conn, &record_type, &isSSLv2); if (r < 0) { /* Don't propagate the error if we already read some bytes. */ if (bytes_read && (s2n_errno == S2N_ERR_CLOSED || s2n_errno == S2N_ERR_IO_BLOCKED)) { break; } /* If we get here, it's an error condition */ if (s2n_errno != S2N_ERR_IO_BLOCKED && s2n_allowed_to_cache_connection(conn) && conn->session_id_len) { conn->config->cache_delete(conn, conn->config->cache_delete_data, conn->session_id, conn->session_id_len); } S2N_ERROR_PRESERVE_ERRNO(); } S2N_ERROR_IF(isSSLv2, S2N_ERR_BAD_MESSAGE); if (record_type != TLS_HANDSHAKE) { /* *= https://www.rfc-editor.org/rfc/rfc8446#section-5.1 *# - Handshake messages MUST NOT be interleaved with other record *# types. That is, if a handshake message is split over two or more *# records, there MUST NOT be any other records between them. */ POSIX_ENSURE(s2n_stuffer_is_wiped(&conn->post_handshake.in), S2N_ERR_BAD_MESSAGE); /* If not handling a handshake message, free the post-handshake memory. * Post-handshake messages are infrequent enough that we don't want to * keep a potentially large buffer around unnecessarily. */ if (!s2n_stuffer_is_freed(&conn->post_handshake.in)) { POSIX_GUARD(s2n_stuffer_free(&conn->post_handshake.in)); } } if (record_type != TLS_APPLICATION_DATA) { switch (record_type) { case TLS_ALERT: POSIX_GUARD(s2n_process_alert_fragment(conn)); break; case TLS_HANDSHAKE: { s2n_result result = s2n_post_handshake_recv(conn); /* Ignore any errors due to insufficient input data from io. * The next iteration of this loop will attempt to read more input data. */ if (s2n_result_is_error(result) && s2n_errno != S2N_ERR_IO_BLOCKED) { WITH_ERROR_BLINDING(conn, POSIX_GUARD_RESULT(result)); } break; } } POSIX_GUARD_RESULT(s2n_record_wipe(conn)); continue; } out.size = MIN(size, s2n_stuffer_data_available(&conn->in)); POSIX_GUARD(s2n_stuffer_erase_and_read(&conn->in, &out)); bytes_read += out.size; out.data += out.size; size -= out.size; /* Are we ready for more encrypted data? */ if (s2n_stuffer_data_available(&conn->in) == 0) { POSIX_GUARD_RESULT(s2n_record_wipe(conn)); } /* If we've read some data, return it in legacy mode */ if (bytes_read && !conn->config->recv_multi_record) { break; } } /* Due to the history of this API, some applications depend on the blocked status to know if * the connection's `in` stuffer was completely cleared. This behavior needs to be preserved. * * Moving forward, applications should instead use `s2n_peek`, which accomplishes the same thing * without conflating being blocked on reading from the OS socket vs blocked on the application's * buffer size. */ if (s2n_stuffer_data_available(&conn->in) == 0) { *blocked = S2N_NOT_BLOCKED; } return bytes_read; } ssize_t s2n_recv(struct s2n_connection *conn, void *buf, ssize_t size, s2n_blocked_status *blocked) { POSIX_ENSURE(!conn->recv_in_use, S2N_ERR_REENTRANCY); conn->recv_in_use = true; ssize_t result = s2n_recv_impl(conn, buf, size, blocked); POSIX_GUARD_RESULT(s2n_early_data_record_bytes(conn, result)); /* finish the recv call */ POSIX_GUARD_RESULT(s2n_connection_dynamic_free_in_buffer(conn)); conn->recv_in_use = false; return result; } uint32_t s2n_peek(struct s2n_connection *conn) { if (conn == NULL) { return 0; } /* If we have partially buffered an encrypted record, * we should not report those bytes as available to read. */ if (conn->in_status != PLAINTEXT) { return 0; } return s2n_stuffer_data_available(&conn->in); } uint32_t s2n_peek_buffered(struct s2n_connection *conn) { if (conn == NULL) { return 0; } return s2n_stuffer_data_available(&conn->buffer_in); }