ssize_t s2n_recv_impl()

in tls/s2n_recv.c [159:292]


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. 
             * For stateful resumption, invalidate the session on error to prevent resumption with 
             * potentially corrupted session state. This ensures that a bad session state does not 
             * lead to repeated failures during resumption attempts.
             */
            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;
}