From bf4df4203bb6e67059184d14d98a78d64782950d Mon Sep 17 00:00:00 2001 From: Barak Gutman Date: Fri, 11 Sep 2026 22:27:51 +0000 Subject: [PATCH] Add MBEDTLS_SSL_RUNTIME_CONTENT_LEN and per-connection content lengths Signed-off-by: Barak Gutman --- ChangeLog.d/runtime-content-len.txt | 13 + include/mbedtls/mbedtls_config.h | 46 ++ include/mbedtls/ssl.h | 124 +++- library/ssl_misc.h | 206 ++++++- library/ssl_msg.c | 57 +- library/ssl_tls.c | 130 ++-- library/ssl_tls12_client.c | 23 +- library/ssl_tls12_server.c | 38 +- library/ssl_tls13_generic.c | 22 +- programs/ssl/ssl_client2.c | 43 ++ programs/ssl/ssl_server2.c | 43 ++ scripts/data_files/config-options-current.txt | 1 + tests/scripts/components-configuration-tls.sh | 34 ++ .../generate_ssl_session_reset_check.py | 16 +- tests/src/ssl_context_reset_verifier.c | 20 +- tests/src/test_helpers/ssl_helpers.c | 36 +- tests/ssl-opt.sh | 195 ++++++ tests/suites/test_suite_ssl.data | 191 ++++++ tests/suites/test_suite_ssl.function | 561 ++++++++++++++++++ 19 files changed, 1673 insertions(+), 126 deletions(-) create mode 100644 ChangeLog.d/runtime-content-len.txt diff --git a/ChangeLog.d/runtime-content-len.txt b/ChangeLog.d/runtime-content-len.txt new file mode 100644 index 0000000000..d53b1c78e8 --- /dev/null +++ b/ChangeLog.d/runtime-content-len.txt @@ -0,0 +1,13 @@ +Features + * Add the MBEDTLS_SSL_RUNTIME_CONTENT_LEN option and the + mbedtls_ssl_set_in_content_len() and mbedtls_ssl_set_out_content_len() + functions to choose the maximum plaintext fragment length of each + connection before mbedtls_ssl_setup(). An application that holds several + connections can then size each one's I/O buffers for its own peer + instead of for its most demanding peer. MBEDTLS_SSL_IN_CONTENT_LEN and + MBEDTLS_SSL_OUT_CONTENT_LEN remain the upper bound, so the worst-case + memory footprint is unchanged; enabling the option without calling + either function changes nothing. Shrinking the incoming length can break + against a conforming peer unless a maximum fragment length or + record_size_limit extension is negotiated; see the documentation of + mbedtls_ssl_set_in_content_len(). Fixes #4892. diff --git a/include/mbedtls/mbedtls_config.h b/include/mbedtls/mbedtls_config.h index a38b61c147..a7a492de2d 100644 --- a/include/mbedtls/mbedtls_config.h +++ b/include/mbedtls/mbedtls_config.h @@ -844,10 +844,56 @@ * When this option is enabled, the SSL buffer will be resized automatically * based on the negotiated maximum fragment length in each direction. * + * This option shrinks the buffers only once a Maximum Fragment Length + * negotiation has completed, so on its own it leaves the peak allocation of a + * connection at the build-time maximum. If you want to bound the allocation + * of a connection from the moment it is set up, see + * MBEDTLS_SSL_RUNTIME_CONTENT_LEN. The two options may be enabled together; + * that option describes how they combine. + * * Requires: MBEDTLS_SSL_MAX_FRAGMENT_LENGTH */ //#define MBEDTLS_SSL_VARIABLE_BUFFER_LENGTH +/** + * \def MBEDTLS_SSL_RUNTIME_CONTENT_LEN + * + * When this option is enabled, the maximum length of a plaintext fragment can + * be set per connection, at runtime, with + * mbedtls_ssl_set_in_content_len() and mbedtls_ssl_set_out_content_len(). + * The connection then allocates its I/O buffers from those lengths when it is + * set up, so an application holding several connections can size each one for + * its own peer instead of for its most demanding peer. + * + * MBEDTLS_SSL_IN_CONTENT_LEN and MBEDTLS_SSL_OUT_CONTENT_LEN keep their + * present meaning as a hard upper bound: a connection may ask for less than + * the compiled maximum, never for more. The worst-case build footprint is + * therefore unchanged; only the runtime heap of a connection shrinks. + * + * Enabling this option without calling either setter changes nothing + * observable: both lengths default to the corresponding build-time maximum. + * + * How this differs from MBEDTLS_SSL_VARIABLE_BUFFER_LENGTH: + * MBEDTLS_SSL_VARIABLE_BUFFER_LENGTH shrinks the buffers automatically, once + * a Maximum Fragment Length has been negotiated, so a connection still + * allocates the build-time maximum while it is set up and handshaking. + * MBEDTLS_SSL_RUNTIME_CONTENT_LEN bounds the allocation from + * mbedtls_ssl_setup() onwards, at a length the application chooses, and does + * not require MBEDTLS_SSL_MAX_FRAGMENT_LENGTH. In exchange, the peer learns + * of a shrunk incoming length only through a negotiated extension; see + * mbedtls_ssl_set_in_content_len() for what happens otherwise. + * + * The two options may be enabled together: the buffers of a connection then + * start at its own lengths, may shrink further after a handshake to a + * configured or negotiated Maximum Fragment Length, and grow back at the + * start of each later handshake (renegotiation, or after + * mbedtls_ssl_session_reset()) only as far as the connection's lengths, never + * to the build-time maximum. + * + * Uncomment this macro to enable per-connection content lengths. + */ +//#define MBEDTLS_SSL_RUNTIME_CONTENT_LEN + //#define MBEDTLS_PSK_MAX_LEN 32 /**< Max size of TLS pre-shared keys, in bytes (default 256 or 384 bits) */ //#define MBEDTLS_SSL_CACHE_DEFAULT_MAX_ENTRIES 50 /**< Maximum entries in cache */ //#define MBEDTLS_SSL_CACHE_DEFAULT_TIMEOUT 86400 /**< 1 day */ diff --git a/include/mbedtls/ssl.h b/include/mbedtls/ssl.h index 00e28442d6..98ecc7ccf2 100644 --- a/include/mbedtls/ssl.h +++ b/include/mbedtls/ssl.h @@ -1584,6 +1584,20 @@ struct mbedtls_ssl_config { mbedtls_ssl_unused_data_t MBEDTLS_PRIVATE(unused); }; +/** + * \def MBEDTLS_SSL_HAVE_BUFFER_LEN_FIELDS + * + * Defined when an SSL context records the length of each of its I/O buffers, + * because at least one enabled option lets a buffer be shorter than the + * build-time maximum: #MBEDTLS_SSL_VARIABLE_BUFFER_LENGTH shrinks it after a + * Maximum Fragment Length negotiation, and #MBEDTLS_SSL_RUNTIME_CONTENT_LEN + * allocates it short in the first place. + */ +#if defined(MBEDTLS_SSL_VARIABLE_BUFFER_LENGTH) || \ + defined(MBEDTLS_SSL_RUNTIME_CONTENT_LEN) +#define MBEDTLS_SSL_HAVE_BUFFER_LEN_FIELDS +#endif + /* * Warning: whenever a change is applied to "mbedtls_ssl_context" please re-run * the script "tests/scripts/generate_ssl_session_reset_check.py". @@ -1721,9 +1735,12 @@ struct mbedtls_ssl_context { int MBEDTLS_PRIVATE(in_msgtype); /*!< record header: message type */ size_t MBEDTLS_PRIVATE(in_msglen); /*!< record header: message length */ size_t MBEDTLS_PRIVATE(in_left); /*!< amount of data read so far */ -#if defined(MBEDTLS_SSL_VARIABLE_BUFFER_LENGTH) +#if defined(MBEDTLS_SSL_HAVE_BUFFER_LEN_FIELDS) size_t MBEDTLS_PRIVATE(in_buf_len); /*!< length of input buffer */ #endif +#if defined(MBEDTLS_SSL_RUNTIME_CONTENT_LEN) + size_t MBEDTLS_PRIVATE(in_content_len); /*!< max incoming plaintext fragment */ +#endif #if defined(MBEDTLS_SSL_PROTO_DTLS) uint16_t MBEDTLS_PRIVATE(in_epoch); /*!< DTLS epoch for incoming records */ size_t MBEDTLS_PRIVATE(next_record_offset); /*!< offset of the next record in datagram @@ -1798,9 +1815,12 @@ struct mbedtls_ssl_context { int MBEDTLS_PRIVATE(out_msgtype); /*!< record header: message type */ size_t MBEDTLS_PRIVATE(out_msglen); /*!< record header: message length */ size_t MBEDTLS_PRIVATE(out_left); /*!< amount of data not yet written */ -#if defined(MBEDTLS_SSL_VARIABLE_BUFFER_LENGTH) +#if defined(MBEDTLS_SSL_HAVE_BUFFER_LEN_FIELDS) size_t MBEDTLS_PRIVATE(out_buf_len); /*!< length of output buffer */ #endif +#if defined(MBEDTLS_SSL_RUNTIME_CONTENT_LEN) + size_t MBEDTLS_PRIVATE(out_content_len); /*!< max outgoing plaintext fragment */ +#endif unsigned char MBEDTLS_PRIVATE(cur_out_ctr)[MBEDTLS_SSL_SEQUENCE_NUMBER_LEN]; /*!< Outgoing record sequence number. */ @@ -1942,6 +1962,11 @@ void mbedtls_ssl_init(mbedtls_ssl_context *ssl); * * \return 0 if successful, or #PSA_ERROR_INSUFFICIENT_MEMORY if * memory allocation failed + * \return #MBEDTLS_ERR_SSL_BAD_CONFIG if \p conf is invalid, including, + * for a client that may negotiate TLS 1.2, a maximum fragment + * length configured with mbedtls_ssl_conf_max_frag_len() + * longer than the length set with + * mbedtls_ssl_set_in_content_len() */ int mbedtls_ssl_setup(mbedtls_ssl_context *ssl, const mbedtls_ssl_config *conf); @@ -2388,6 +2413,95 @@ int mbedtls_ssl_get_peer_cid(mbedtls_ssl_context *ssl, void mbedtls_ssl_set_mtu(mbedtls_ssl_context *ssl, uint16_t mtu); #endif /* MBEDTLS_SSL_PROTO_DTLS */ +#if defined(MBEDTLS_SSL_RUNTIME_CONTENT_LEN) +/** + * \brief Set the maximum length of a plaintext fragment that this + * connection can receive. + * + * mbedtls_ssl_setup() sizes the input buffer from this + * length, so a connection that asks for less than + * #MBEDTLS_SSL_IN_CONTENT_LEN allocates less. + * + * \note This function is valid only after mbedtls_ssl_init() and + * before mbedtls_ssl_setup(). It fails once + * mbedtls_ssl_setup() has succeeded, and also once + * mbedtls_ssl_setup() has rejected the configuration; free + * and re-initialise the context to choose another length. + * The length is kept across mbedtls_ssl_session_reset(). + * + * \warning Nothing tells the peer about this length unless a + * Maximum Fragment Length or record_size_limit no longer + * than this length is negotiated. Otherwise a conforming peer + * may send a record longer than this length. Over TLS, the + * call that reads such a record fails: with + * #MBEDTLS_ERR_SSL_INVALID_RECORD if the record still fits in + * the input buffer, otherwise with + * #MBEDTLS_ERR_SSL_BAD_INPUT_DATA. Over DTLS, the record is + * discarded, so its data is lost. A build with + * #MBEDTLS_SSL_IN_CONTENT_LEN set to the same length behaves + * the same way. + * + * \warning A server in particular has no standardised way to + * announce this length. Only a client requests a Maximum + * Fragment Length, and it may request one longer than this + * length; a TLS 1.3 server sends record_size_limit only if + * the client offered it. Shrink the incoming length of a + * server only when you know what its clients will send. + * + * \note With #MBEDTLS_SSL_RECORD_SIZE_LIMIT, a TLS 1.3 endpoint + * advertises this length as its record_size_limit, so a + * length below 64, the smallest limit RFC 8449 permits, makes + * the handshake fail. A client that may negotiate TLS 1.2 + * must not configure a maximum fragment length with + * mbedtls_ssl_conf_max_frag_len() longer than this length, or + * mbedtls_ssl_setup() fails. + * + * \param ssl SSL context + * \param len Maximum incoming plaintext fragment length in bytes. + * + * \return 0 on success. + * \return #MBEDTLS_ERR_SSL_BAD_INPUT_DATA if \p len is 0 or + * exceeds #MBEDTLS_SSL_IN_CONTENT_LEN, or if called outside + * the window described above. + */ +int mbedtls_ssl_set_in_content_len(mbedtls_ssl_context *ssl, size_t len); + +/** + * \brief Set the maximum length of a plaintext fragment that this + * connection can send. + * + * mbedtls_ssl_setup() sizes the output buffer from this + * length, so a connection that asks for less than + * #MBEDTLS_SSL_OUT_CONTENT_LEN allocates less. Application + * data longer than this length is treated as it is for a + * negotiated maximum fragment length: over TLS, + * mbedtls_ssl_write() sends only the first part of it, and + * over DTLS, mbedtls_ssl_write() rejects it. + * + * \note This function is valid only after mbedtls_ssl_init() and + * before mbedtls_ssl_setup(). It fails once + * mbedtls_ssl_setup() has succeeded, and also once + * mbedtls_ssl_setup() has rejected the configuration; free + * and re-initialise the context to choose another length. + * The length is kept across mbedtls_ssl_session_reset(). + * + * \note The lower bound this function enforces, 12 bytes or the + * TLS 1.3 padding granularity if larger, only keeps internal + * size arithmetic sound. Whether a handshake fits depends on + * the certificate chain, ciphersuites and extensions; a + * length too short for them makes the handshake fail. + * + * \param ssl SSL context + * \param len Maximum outgoing plaintext fragment length in bytes. + * + * \return 0 on success. + * \return #MBEDTLS_ERR_SSL_BAD_INPUT_DATA if \p len is below the + * lower bound or exceeds #MBEDTLS_SSL_OUT_CONTENT_LEN, or if + * called outside the window described above. + */ +int mbedtls_ssl_set_out_content_len(mbedtls_ssl_context *ssl, size_t len); +#endif /* MBEDTLS_SSL_RUNTIME_CONTENT_LEN */ + #if defined(MBEDTLS_X509_CRT_PARSE_C) /** * \brief Set a connection-specific verification callback (optional). @@ -4687,7 +4801,8 @@ int mbedtls_ssl_get_record_expansion(const mbedtls_ssl_context *ssl); * * \note The logic to determine the maximum outgoing record payload is * version-specific. It takes into account various factors, such as - * the mbedtls_config.h setting \c MBEDTLS_SSL_OUT_CONTENT_LEN, extensions + * the mbedtls_config.h setting \c MBEDTLS_SSL_OUT_CONTENT_LEN or + * the length set with \c mbedtls_ssl_set_out_content_len(), extensions * such as the max fragment length or record size limit extension if * used, and for DTLS the path MTU as configured and current * record expansion. @@ -4714,7 +4829,8 @@ int mbedtls_ssl_get_max_out_record_payload(const mbedtls_ssl_context *ssl); * * \note The logic to determine the maximum incoming record payload is * version-specific. It takes into account various factors, such as - * the mbedtls_config.h setting \c MBEDTLS_SSL_IN_CONTENT_LEN, extensions + * the mbedtls_config.h setting \c MBEDTLS_SSL_IN_CONTENT_LEN or + * the length set with \c mbedtls_ssl_set_in_content_len(), extensions * such as the max fragment length extension or record size limit * extension if used, and the current record expansion. * diff --git a/library/ssl_misc.h b/library/ssl_misc.h index ecd3e6cc07..226ef27bb9 100644 --- a/library/ssl_misc.h +++ b/library/ssl_misc.h @@ -399,6 +399,185 @@ uint32_t mbedtls_ssl_get_extension_mask(unsigned int extension_type); + (MBEDTLS_SSL_CID_OUT_LEN_MAX)) #endif +/* + * Smallest per-connection content lengths that + * mbedtls_ssl_set_in_content_len() and mbedtls_ssl_set_out_content_len() + * accept, under MBEDTLS_SSL_RUNTIME_CONTENT_LEN. These are not the smallest + * lengths a handshake can succeed with, which depend on the + * certificate chain, ciphersuite and extensions; they are the points below + * which internal size arithmetic stops meaning anything. + * + * Incoming: nothing subtracts from the incoming length, so any non-zero + * value is structurally sound. Keep this below + * MBEDTLS_SSL_RECORD_SIZE_LIMIT_MIN, so that the runtime check in the TLS 1.3 + * record_size_limit writer stays reachable. + * + * Outgoing: 12 covers the DTLS handshake header, which the message layer + * subtracts from the outgoing length, and with it the 7-byte handshake header + * plus 3-byte length that precede the first certificate. With TLS 1.3 the + * maximum outgoing record payload is rounded down to a multiple of the + * padding granularity and then decremented, so the length must be at least + * one granule. + * + * The documentation of both setters in ssl.h states these values; update it + * if they change. + */ +#define MBEDTLS_SSL_IN_CONTENT_LEN_MIN 1 + +#if defined(MBEDTLS_SSL_PROTO_TLS1_3) && \ + MBEDTLS_SSL_CID_TLS1_3_PADDING_GRANULARITY > 12 +#define MBEDTLS_SSL_OUT_CONTENT_LEN_MIN MBEDTLS_SSL_CID_TLS1_3_PADDING_GRANULARITY +#else +#define MBEDTLS_SSL_OUT_CONTENT_LEN_MIN 12 +#endif + +/** + * \brief Get the maximum length, in bytes, of a plaintext fragment + * that may be received on this connection. + * + * This is the single canonical way for the TLS code to ask + * how large an incoming plaintext fragment may be. Without + * #MBEDTLS_SSL_RUNTIME_CONTENT_LEN it is the build-time + * maximum #MBEDTLS_SSL_IN_CONTENT_LEN; with that option it is + * the per-connection length, which defaults to the build-time + * maximum and can never exceed it. + * + * \param ssl SSL context + * + * \return The maximum incoming plaintext fragment length in bytes. + */ +static inline size_t mbedtls_ssl_get_in_content_len(const mbedtls_ssl_context *ssl) +{ +#if defined(MBEDTLS_SSL_RUNTIME_CONTENT_LEN) + return ssl->in_content_len; +#else + (void) ssl; + return MBEDTLS_SSL_IN_CONTENT_LEN; +#endif +} + +/** + * \brief Get the maximum length, in bytes, of a plaintext fragment + * that may be sent on this connection. + * + * This is the single canonical way for the TLS code to ask + * how large an outgoing plaintext fragment may be. Without + * #MBEDTLS_SSL_RUNTIME_CONTENT_LEN it is the build-time + * maximum #MBEDTLS_SSL_OUT_CONTENT_LEN; with that option it is + * the per-connection length, which defaults to the build-time + * maximum and can never exceed it. + * + * \param ssl SSL context + * + * \return The maximum outgoing plaintext fragment length in bytes. + */ +static inline size_t mbedtls_ssl_get_out_content_len(const mbedtls_ssl_context *ssl) +{ +#if defined(MBEDTLS_SSL_RUNTIME_CONTENT_LEN) + return ssl->out_content_len; +#else + (void) ssl; + return MBEDTLS_SSL_OUT_CONTENT_LEN; +#endif +} + +/** + * \brief Get the length, in bytes, of this connection's input buffer. + * + * Valid once the connection has been set up with + * mbedtls_ssl_setup(); before that there is no buffer and the + * return value is meaningless. Allocation, teardown, the + * message-layer reset and the automatic-resize path must all + * agree on this one value. + * + * \param ssl SSL context + * + * \return The length of \c ssl->in_buf in bytes. + */ +static inline size_t mbedtls_ssl_get_in_buf_len(const mbedtls_ssl_context *ssl) +{ +#if defined(MBEDTLS_SSL_HAVE_BUFFER_LEN_FIELDS) + return ssl->in_buf_len; +#else + (void) ssl; + return MBEDTLS_SSL_IN_BUFFER_LEN; +#endif +} + +/** + * \brief Get the length, in bytes, of this connection's output + * buffer. + * + * Valid once the connection has been set up with + * mbedtls_ssl_setup(); before that there is no buffer and the + * return value is meaningless. Allocation, teardown, the + * message-layer reset and the automatic-resize path must all + * agree on this one value. + * + * \param ssl SSL context + * + * \return The length of \c ssl->out_buf in bytes. + */ +static inline size_t mbedtls_ssl_get_out_buf_len(const mbedtls_ssl_context *ssl) +{ +#if defined(MBEDTLS_SSL_HAVE_BUFFER_LEN_FIELDS) + return ssl->out_buf_len; +#else + (void) ssl; + return MBEDTLS_SSL_OUT_BUFFER_LEN; +#endif +} + +/** + * \brief Compute the input buffer length that holds a full-size + * incoming record for this connection. + * + * This is what mbedtls_ssl_setup() allocates: the connection's + * incoming content length plus the record header, the crypto + * expansion and, where enabled, the maximum incoming + * connection ID. + * + * \param ssl SSL context + * + * \return The required input buffer length in bytes. + */ +static inline size_t mbedtls_ssl_compute_in_buf_len(const mbedtls_ssl_context *ssl) +{ +#if defined(MBEDTLS_SSL_DTLS_CONNECTION_ID) + return mbedtls_ssl_get_in_content_len(ssl) + + MBEDTLS_SSL_HEADER_LEN + MBEDTLS_SSL_PAYLOAD_OVERHEAD + + MBEDTLS_SSL_CID_IN_LEN_MAX; +#else + return mbedtls_ssl_get_in_content_len(ssl) + + MBEDTLS_SSL_HEADER_LEN + MBEDTLS_SSL_PAYLOAD_OVERHEAD; +#endif +} + +/** + * \brief Compute the output buffer length that holds a full-size + * outgoing record for this connection. + * + * This is what mbedtls_ssl_setup() allocates: the connection's + * outgoing content length plus the record header, the crypto + * expansion and, where enabled, the maximum outgoing + * connection ID. + * + * \param ssl SSL context + * + * \return The required output buffer length in bytes. + */ +static inline size_t mbedtls_ssl_compute_out_buf_len(const mbedtls_ssl_context *ssl) +{ +#if defined(MBEDTLS_SSL_DTLS_CONNECTION_ID) + return mbedtls_ssl_get_out_content_len(ssl) + + MBEDTLS_SSL_HEADER_LEN + MBEDTLS_SSL_PAYLOAD_OVERHEAD + + MBEDTLS_SSL_CID_OUT_LEN_MAX; +#else + return mbedtls_ssl_get_out_content_len(ssl) + + MBEDTLS_SSL_HEADER_LEN + MBEDTLS_SSL_PAYLOAD_OVERHEAD; +#endif +} + #define MBEDTLS_CLIENT_HELLO_RANDOM_LEN 32 #define MBEDTLS_SERVER_HELLO_RANDOM_LEN 32 @@ -407,7 +586,8 @@ uint32_t mbedtls_ssl_get_extension_mask(unsigned int extension_type); * \brief Return the maximum fragment length (payload, in bytes) for * the output buffer. For the client, this is the configured * value. For the server, it is the minimum of two - the - * configured value and the negotiated one. + * configured value and the negotiated one. Either way it is + * no more than the connection's outgoing content length. * * \sa mbedtls_ssl_conf_max_frag_len() * \sa mbedtls_ssl_get_max_out_record_payload() @@ -421,9 +601,10 @@ size_t mbedtls_ssl_get_output_max_frag_len(const mbedtls_ssl_context *ssl); /** * \brief Return the maximum fragment length (payload, in bytes) for * the input buffer. This is the negotiated maximum fragment - * length, or, if there is none, MBEDTLS_SSL_IN_CONTENT_LEN. - * If it is not defined either, the value is 2^14. This function - * works as its predecessor, \c mbedtls_ssl_get_max_frag_len(). + * length, or, if there is none, the connection's incoming + * content length, and never more than the latter. This + * function works as its predecessor, + * \c mbedtls_ssl_get_max_frag_len(). * * \sa mbedtls_ssl_conf_max_frag_len() * \sa mbedtls_ssl_get_max_in_record_payload() @@ -2665,6 +2846,23 @@ int mbedtls_ssl_tls13_parse_record_size_limit_ext(mbedtls_ssl_context *ssl, const unsigned char *buf, const unsigned char *end); +/** + * \brief Write the record_size_limit extension, advertising this + * connection's maximum incoming plaintext fragment length. + * + * \param ssl SSL context + * \param buf Start of the extension write buffer + * \param end End of the extension write buffer + * \param out_len On success, the number of bytes written + * + * \return \c 0 on success. + * \return #MBEDTLS_ERR_SSL_BAD_INPUT_DATA if the connection's + * incoming content length is below + * #MBEDTLS_SSL_RECORD_SIZE_LIMIT_MIN, leaving nothing valid + * to advertise. + * \return #MBEDTLS_ERR_SSL_BUFFER_TOO_SMALL if the write buffer is + * too small for the extension. + */ MBEDTLS_CHECK_RETURN_CRITICAL int mbedtls_ssl_tls13_write_record_size_limit_ext(mbedtls_ssl_context *ssl, unsigned char *buf, diff --git a/library/ssl_msg.c b/library/ssl_msg.c index 87d64788bd..761dda9420 100644 --- a/library/ssl_msg.c +++ b/library/ssl_msg.c @@ -292,11 +292,7 @@ static int ssl_next_record_is_in_datagram(mbedtls_ssl_context *ssl); static size_t ssl_get_maximum_datagram_size(mbedtls_ssl_context const *ssl) { size_t mtu = mbedtls_ssl_get_current_mtu(ssl); -#if defined(MBEDTLS_SSL_VARIABLE_BUFFER_LENGTH) - size_t out_buf_len = ssl->out_buf_len; -#else - size_t out_buf_len = MBEDTLS_SSL_OUT_BUFFER_LEN; -#endif + size_t out_buf_len = mbedtls_ssl_get_out_buf_len(ssl); if (mtu != 0 && mtu < out_buf_len) { return mtu; @@ -326,7 +322,7 @@ static int ssl_get_remaining_payload_in_datagram(mbedtls_ssl_context const *ssl) { int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED; size_t remaining, expansion; - size_t max_len = MBEDTLS_SSL_OUT_CONTENT_LEN; + size_t max_len = mbedtls_ssl_get_out_content_len(ssl); #if defined(MBEDTLS_SSL_MAX_FRAGMENT_LENGTH) const size_t mfl = mbedtls_ssl_get_output_max_frag_len(ssl); @@ -830,6 +826,13 @@ int mbedtls_ssl_encrypt_buf(mbedtls_ssl_context *ssl, MBEDTLS_SSL_DEBUG_BUF(4, "before encrypt: output payload", data, rec->data_len); + /* This is a sanity check against the build-time ceiling, not against this + * connection's outgoing length: the ssl parameter is a debugging aid + * here and is NULL when MBEDTLS_DEBUG_C is off, so it cannot be asked. + * The per-connection bound is enforced before a record reaches this + * point: mbedtls_ssl_write_handshake_msg_ext() rejects an over-long + * handshake message, and application data is clamped to + * mbedtls_ssl_get_max_out_record_payload(). */ if (rec->data_len > MBEDTLS_SSL_OUT_CONTENT_LEN) { MBEDTLS_SSL_DEBUG_MSG(1, ("Record content %" MBEDTLS_PRINTF_SIZET " too large, maximum %" MBEDTLS_PRINTF_SIZET, @@ -1856,11 +1859,7 @@ int mbedtls_ssl_fetch_input(mbedtls_ssl_context *ssl, size_t nb_want) { int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED; size_t len; -#if defined(MBEDTLS_SSL_VARIABLE_BUFFER_LENGTH) - size_t in_buf_len = ssl->in_buf_len; -#else - size_t in_buf_len = MBEDTLS_SSL_IN_BUFFER_LEN; -#endif + size_t in_buf_len = mbedtls_ssl_get_in_buf_len(ssl); MBEDTLS_SSL_DEBUG_MSG(2, ("=> fetch input")); @@ -2453,7 +2452,7 @@ int mbedtls_ssl_start_handshake_msg(mbedtls_ssl_context *ssl, unsigned char hs_t * ... */ *buf = ssl->out_msg + 4; - *buf_len = MBEDTLS_SSL_OUT_CONTENT_LEN - 4; + *buf_len = mbedtls_ssl_get_out_content_len(ssl) - 4; ssl->out_msgtype = MBEDTLS_SSL_MSG_HANDSHAKE; ssl->out_msg[0] = hs_type; @@ -2528,12 +2527,12 @@ int mbedtls_ssl_write_handshake_msg_ext(mbedtls_ssl_context *ssl, * * Note: We deliberately do not check for the MTU or MFL here. */ - if (ssl->out_msglen > MBEDTLS_SSL_OUT_CONTENT_LEN) { + if (ssl->out_msglen > mbedtls_ssl_get_out_content_len(ssl)) { MBEDTLS_SSL_DEBUG_MSG(1, ("Record too large: " "size %" MBEDTLS_PRINTF_SIZET ", maximum %" MBEDTLS_PRINTF_SIZET, ssl->out_msglen, - (size_t) MBEDTLS_SSL_OUT_CONTENT_LEN)); + mbedtls_ssl_get_out_content_len(ssl))); return MBEDTLS_ERR_SSL_INTERNAL_ERROR; } @@ -2555,12 +2554,12 @@ int mbedtls_ssl_write_handshake_msg_ext(mbedtls_ssl_context *ssl, #if defined(MBEDTLS_SSL_PROTO_DTLS) if (ssl->conf->transport == MBEDTLS_SSL_TRANSPORT_DATAGRAM) { /* Make room for the additional DTLS fields */ - if (MBEDTLS_SSL_OUT_CONTENT_LEN - ssl->out_msglen < 8) { + if (mbedtls_ssl_get_out_content_len(ssl) - ssl->out_msglen < 8) { MBEDTLS_SSL_DEBUG_MSG(1, ("DTLS handshake message too large: " "size %" MBEDTLS_PRINTF_SIZET ", maximum %" MBEDTLS_PRINTF_SIZET, hs_len, - (size_t) (MBEDTLS_SSL_OUT_CONTENT_LEN - 12))); + mbedtls_ssl_get_out_content_len(ssl) - 12)); return MBEDTLS_ERR_SSL_BAD_INPUT_DATA; } @@ -2656,11 +2655,7 @@ int mbedtls_ssl_write_record(mbedtls_ssl_context *ssl, int force_flush) if (!done) { unsigned i; size_t protected_record_size; -#if defined(MBEDTLS_SSL_VARIABLE_BUFFER_LENGTH) - size_t out_buf_len = ssl->out_buf_len; -#else - size_t out_buf_len = MBEDTLS_SSL_OUT_BUFFER_LEN; -#endif + size_t out_buf_len = mbedtls_ssl_get_out_buf_len(ssl); /* Skip writing the record content type to after the encryption, * as it may change when using the CID extension. */ mbedtls_ssl_protocol_version tls_ver = ssl->tls_version; @@ -3107,11 +3102,7 @@ int mbedtls_ssl_prepare_handshake_record(mbedtls_ssl_context *ssl) * after the explicit IV, but here we move it to start where the * IV was. */ -#if defined(MBEDTLS_SSL_VARIABLE_BUFFER_LENGTH) - size_t const in_buf_len = ssl->in_buf_len; -#else - size_t const in_buf_len = MBEDTLS_SSL_IN_BUFFER_LEN; -#endif + size_t const in_buf_len = mbedtls_ssl_get_in_buf_len(ssl); if (payload_end + ssl->in_msglen > ssl->in_buf + in_buf_len) { MBEDTLS_SSL_DEBUG_MSG(1, ("Shouldn't happen: no room to move handshake fragment %" @@ -3492,7 +3483,7 @@ static int ssl_handle_possible_reconnect(mbedtls_ssl_context *ssl) ssl, ssl->cli_id, ssl->cli_id_len, ssl->in_buf, ssl->in_left, - ssl->out_buf, MBEDTLS_SSL_OUT_CONTENT_LEN, &len); + ssl->out_buf, mbedtls_ssl_get_out_content_len(ssl), &len); MBEDTLS_SSL_DEBUG_RET(2, "mbedtls_ssl_check_dtls_clihlo_cookie", ret); @@ -4008,7 +3999,7 @@ static int ssl_prepare_record_content(mbedtls_ssl_context *ssl, /* Check actual (decrypted) record content length against * configured maximum. */ - if (rec->data_len > MBEDTLS_SSL_IN_CONTENT_LEN) { + if (rec->data_len > mbedtls_ssl_get_in_content_len(ssl)) { MBEDTLS_SSL_DEBUG_MSG(1, ("bad message length")); return MBEDTLS_ERR_SSL_INVALID_RECORD; } @@ -4185,7 +4176,7 @@ static int ssl_load_buffered_message(mbedtls_ssl_context *ssl) /* Double-check that we haven't accidentally buffered * a message that doesn't fit into the input buffer. */ - if (msg_len + 12 > MBEDTLS_SSL_IN_CONTENT_LEN) { + if (msg_len + 12 > mbedtls_ssl_get_in_content_len(ssl)) { MBEDTLS_SSL_DEBUG_MSG(1, ("should never happen")); return MBEDTLS_ERR_SSL_INTERNAL_ERROR; } @@ -4321,7 +4312,7 @@ static int ssl_buffer_message(mbedtls_ssl_context *ssl) * This is an implementation-specific limitation * and not one from the standard, hence it is not * checked in ssl_check_hs_header(). */ - if (msg_len + 12 > MBEDTLS_SSL_IN_CONTENT_LEN) { + if (msg_len + 12 > mbedtls_ssl_get_in_content_len(ssl)) { /* Ignore message */ goto exit; } @@ -4583,11 +4574,7 @@ static int ssl_load_buffered_record(mbedtls_ssl_context *ssl) unsigned char *rec; size_t rec_len; unsigned rec_epoch; -#if defined(MBEDTLS_SSL_VARIABLE_BUFFER_LENGTH) - size_t in_buf_len = ssl->in_buf_len; -#else - size_t in_buf_len = MBEDTLS_SSL_IN_BUFFER_LEN; -#endif + size_t in_buf_len = mbedtls_ssl_get_in_buf_len(ssl); if (ssl->conf->transport != MBEDTLS_SSL_TRANSPORT_DATAGRAM) { return 0; } diff --git a/library/ssl_tls.c b/library/ssl_tls.c index 6a15d8bace..5315c5f077 100644 --- a/library/ssl_tls.c +++ b/library/ssl_tls.c @@ -1003,8 +1003,11 @@ static int ssl_handshake_init(mbedtls_ssl_context *ssl) #if defined(MBEDTLS_SSL_VARIABLE_BUFFER_LENGTH) /* If the buffers are too small - reallocate */ - handle_buffer_resizing(ssl, 0, MBEDTLS_SSL_IN_BUFFER_LEN, - MBEDTLS_SSL_OUT_BUFFER_LEN); + /* Grow back to what this connection asked for, not to the build-time + * maximum: growing to the maximum here would undo the saving that + * MBEDTLS_SSL_RUNTIME_CONTENT_LEN made at setup. */ + handle_buffer_resizing(ssl, 0, mbedtls_ssl_compute_in_buf_len(ssl), + mbedtls_ssl_compute_out_buf_len(ssl)); #endif /* All pointers should exist and can be directly freed without issue */ @@ -1114,6 +1117,14 @@ static int ssl_cookie_check_dummy(void *ctx, void mbedtls_ssl_init(mbedtls_ssl_context *ssl) { memset(ssl, 0, sizeof(mbedtls_ssl_context)); + +#if defined(MBEDTLS_SSL_RUNTIME_CONTENT_LEN) + /* Default to the build-time maxima, so that enabling + * MBEDTLS_SSL_RUNTIME_CONTENT_LEN without calling either setter is + * observably a no-op. */ + ssl->in_content_len = MBEDTLS_SSL_IN_CONTENT_LEN; + ssl->out_content_len = MBEDTLS_SSL_OUT_CONTENT_LEN; +#endif } MBEDTLS_CHECK_RETURN_CRITICAL @@ -1165,6 +1176,26 @@ static int ssl_conf_check(const mbedtls_ssl_context *ssl) return ret; } +#if defined(MBEDTLS_SSL_RUNTIME_CONTENT_LEN) && \ + defined(MBEDTLS_SSL_MAX_FRAGMENT_LENGTH) && defined(MBEDTLS_SSL_CLI_C) + /* A client proposing TLS 1.2 advertises its configured fragment length, + * so it must not invite records longer than this connection can take in. + * A server's configured fragment length only caps what it sends. */ + if (ssl->conf->endpoint == MBEDTLS_SSL_IS_CLIENT && + ssl->conf->min_tls_version <= MBEDTLS_SSL_VERSION_TLS1_2 && + ssl->conf->mfl_code != MBEDTLS_SSL_MAX_FRAG_LEN_NONE) { + const unsigned int mfl = ssl_mfl_code_to_length(ssl->conf->mfl_code); + const size_t in_content_len = mbedtls_ssl_get_in_content_len(ssl); + + if (mfl > in_content_len) { + MBEDTLS_SSL_DEBUG_MSG(1, ("max_fragment_length %u exceeds the incoming " + "content length %" MBEDTLS_PRINTF_SIZET, + mfl, in_content_len)); + return MBEDTLS_ERR_SSL_BAD_CONFIG; + } + } +#endif + /* Space for further checks */ return 0; @@ -1178,8 +1209,8 @@ int mbedtls_ssl_setup(mbedtls_ssl_context *ssl, const mbedtls_ssl_config *conf) { int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED; - size_t in_buf_len = MBEDTLS_SSL_IN_BUFFER_LEN; - size_t out_buf_len = MBEDTLS_SSL_OUT_BUFFER_LEN; + size_t in_buf_len = mbedtls_ssl_compute_in_buf_len(ssl); + size_t out_buf_len = mbedtls_ssl_compute_out_buf_len(ssl); ssl->conf = conf; @@ -1195,7 +1226,7 @@ int mbedtls_ssl_setup(mbedtls_ssl_context *ssl, /* Set to NULL in case of an error condition */ ssl->out_buf = NULL; -#if defined(MBEDTLS_SSL_VARIABLE_BUFFER_LENGTH) +#if defined(MBEDTLS_SSL_HAVE_BUFFER_LEN_FIELDS) ssl->in_buf_len = in_buf_len; #endif ssl->in_buf = mbedtls_calloc(1, in_buf_len); @@ -1205,7 +1236,7 @@ int mbedtls_ssl_setup(mbedtls_ssl_context *ssl, goto error; } -#if defined(MBEDTLS_SSL_VARIABLE_BUFFER_LENGTH) +#if defined(MBEDTLS_SSL_HAVE_BUFFER_LEN_FIELDS) ssl->out_buf_len = out_buf_len; #endif ssl->out_buf = mbedtls_calloc(1, out_buf_len); @@ -1234,7 +1265,7 @@ error: ssl->conf = NULL; -#if defined(MBEDTLS_SSL_VARIABLE_BUFFER_LENGTH) +#if defined(MBEDTLS_SSL_HAVE_BUFFER_LEN_FIELDS) ssl->in_buf_len = 0; ssl->out_buf_len = 0; #endif @@ -1266,13 +1297,8 @@ error: void mbedtls_ssl_session_reset_msg_layer(mbedtls_ssl_context *ssl, int partial) { -#if defined(MBEDTLS_SSL_VARIABLE_BUFFER_LENGTH) - size_t in_buf_len = ssl->in_buf_len; - size_t out_buf_len = ssl->out_buf_len; -#else - size_t in_buf_len = MBEDTLS_SSL_IN_BUFFER_LEN; - size_t out_buf_len = MBEDTLS_SSL_OUT_BUFFER_LEN; -#endif + size_t in_buf_len = mbedtls_ssl_get_in_buf_len(ssl); + size_t out_buf_len = mbedtls_ssl_get_out_buf_len(ssl); #if !defined(MBEDTLS_SSL_DTLS_CLIENT_PORT_REUSE) || !defined(MBEDTLS_SSL_SRV_C) partial = 0; @@ -1501,6 +1527,39 @@ void mbedtls_ssl_set_mtu(mbedtls_ssl_context *ssl, uint16_t mtu) } #endif +#if defined(MBEDTLS_SSL_RUNTIME_CONTENT_LEN) +int mbedtls_ssl_set_in_content_len(mbedtls_ssl_context *ssl, size_t len) +{ + /* Only before mbedtls_ssl_setup(): afterwards the buffers exist and + * may hold data and interior pointers sized from the current length. */ + if (ssl->conf != NULL) { + return MBEDTLS_ERR_SSL_BAD_INPUT_DATA; + } + + if (len < MBEDTLS_SSL_IN_CONTENT_LEN_MIN || len > MBEDTLS_SSL_IN_CONTENT_LEN) { + return MBEDTLS_ERR_SSL_BAD_INPUT_DATA; + } + + ssl->in_content_len = len; + return 0; +} + +int mbedtls_ssl_set_out_content_len(mbedtls_ssl_context *ssl, size_t len) +{ + /* Only before mbedtls_ssl_setup(), as for the incoming length. */ + if (ssl->conf != NULL) { + return MBEDTLS_ERR_SSL_BAD_INPUT_DATA; + } + + if (len < MBEDTLS_SSL_OUT_CONTENT_LEN_MIN || len > MBEDTLS_SSL_OUT_CONTENT_LEN) { + return MBEDTLS_ERR_SSL_BAD_INPUT_DATA; + } + + ssl->out_content_len = len; + return 0; +} +#endif /* MBEDTLS_SSL_RUNTIME_CONTENT_LEN */ + void mbedtls_ssl_conf_read_timeout(mbedtls_ssl_config *conf, uint32_t timeout) { conf->read_timeout = timeout; @@ -1919,7 +1978,8 @@ static int ssl_conf_set_psk_identity(mbedtls_ssl_config *conf, unsigned char const *psk_identity, size_t psk_identity_len) { - /* Identity len will be encoded on two bytes */ + /* Identity len will be encoded on two bytes. + * Configuration-scoped, so this checks the build-time ceiling. */ if (psk_identity == NULL || psk_identity_len == 0 || (psk_identity_len >> 16) != 0 || @@ -2819,7 +2879,7 @@ const char *mbedtls_ssl_get_version(const mbedtls_ssl_context *ssl) size_t mbedtls_ssl_get_output_record_size_limit(const mbedtls_ssl_context *ssl) { - const size_t max_len = MBEDTLS_SSL_OUT_CONTENT_LEN; + const size_t max_len = mbedtls_ssl_get_out_content_len(ssl); size_t record_size_limit = max_len; if (ssl->session != NULL && @@ -2843,14 +2903,18 @@ size_t mbedtls_ssl_get_output_record_size_limit(const mbedtls_ssl_context *ssl) #if defined(MBEDTLS_SSL_MAX_FRAGMENT_LENGTH) size_t mbedtls_ssl_get_input_max_frag_len(const mbedtls_ssl_context *ssl) { - size_t max_len = MBEDTLS_SSL_IN_CONTENT_LEN; + size_t max_len = mbedtls_ssl_get_in_content_len(ssl); size_t read_mfl; #if defined(MBEDTLS_SSL_PROTO_TLS1_2) /* Use the configured MFL for the client if we're past SERVER_HELLO_DONE */ if (ssl->conf->endpoint == MBEDTLS_SSL_IS_CLIENT && ssl->state >= MBEDTLS_SSL_SERVER_HELLO_DONE) { - return ssl_mfl_code_to_length(ssl->conf->mfl_code); + read_mfl = ssl_mfl_code_to_length(ssl->conf->mfl_code); + if (read_mfl < max_len) { + max_len = read_mfl; + } + return max_len; } #endif @@ -2875,12 +2939,14 @@ size_t mbedtls_ssl_get_input_max_frag_len(const mbedtls_ssl_context *ssl) size_t mbedtls_ssl_get_output_max_frag_len(const mbedtls_ssl_context *ssl) { - size_t max_len; + size_t max_len = mbedtls_ssl_get_out_content_len(ssl); /* * Assume mfl_code is correct since it was checked when set */ - max_len = ssl_mfl_code_to_length(ssl->conf->mfl_code); + if (ssl_mfl_code_to_length(ssl->conf->mfl_code) < max_len) { + max_len = ssl_mfl_code_to_length(ssl->conf->mfl_code); + } /* Check if a smaller max length was negotiated */ if (ssl->session_out != NULL && @@ -2916,7 +2982,7 @@ size_t mbedtls_ssl_get_current_mtu(const mbedtls_ssl_context *ssl) int mbedtls_ssl_get_max_out_record_payload(const mbedtls_ssl_context *ssl) { - size_t max_len = MBEDTLS_SSL_OUT_CONTENT_LEN; + size_t max_len = mbedtls_ssl_get_out_content_len(ssl); #if !defined(MBEDTLS_SSL_MAX_FRAGMENT_LENGTH) && \ !defined(MBEDTLS_SSL_RECORD_SIZE_LIMIT) && \ @@ -2988,7 +3054,7 @@ int mbedtls_ssl_get_max_out_record_payload(const mbedtls_ssl_context *ssl) int mbedtls_ssl_get_max_in_record_payload(const mbedtls_ssl_context *ssl) { - size_t max_len = MBEDTLS_SSL_IN_CONTENT_LEN; + size_t max_len = mbedtls_ssl_get_in_content_len(ssl); #if !defined(MBEDTLS_SSL_MAX_FRAGMENT_LENGTH) (void) ssl; @@ -5187,22 +5253,14 @@ void mbedtls_ssl_free(mbedtls_ssl_context *ssl) MBEDTLS_SSL_DEBUG_MSG(2, ("=> free")); if (ssl->out_buf != NULL) { -#if defined(MBEDTLS_SSL_VARIABLE_BUFFER_LENGTH) - size_t out_buf_len = ssl->out_buf_len; -#else - size_t out_buf_len = MBEDTLS_SSL_OUT_BUFFER_LEN; -#endif + size_t out_buf_len = mbedtls_ssl_get_out_buf_len(ssl); mbedtls_zeroize_and_free(ssl->out_buf, out_buf_len); ssl->out_buf = NULL; } if (ssl->in_buf != NULL) { -#if defined(MBEDTLS_SSL_VARIABLE_BUFFER_LENGTH) - size_t in_buf_len = ssl->in_buf_len; -#else - size_t in_buf_len = MBEDTLS_SSL_IN_BUFFER_LEN; -#endif + size_t in_buf_len = mbedtls_ssl_get_in_buf_len(ssl); mbedtls_zeroize_and_free(ssl->in_buf, in_buf_len); ssl->in_buf = NULL; @@ -6732,13 +6790,17 @@ int mbedtls_ssl_write_certificate(mbedtls_ssl_context *ssl) */ i = 7; crt = mbedtls_ssl_own_cert(ssl); + const size_t out_content_len = mbedtls_ssl_get_out_content_len(ssl); while (crt != NULL) { n = crt->raw.len; - if (n > MBEDTLS_SSL_OUT_CONTENT_LEN - 3 - i) { + /* Written as an addition rather than i + 3 subtracted from the + * outgoing length, so that a short outgoing length cannot underflow + * the bound into a huge value and wave the memcpy below through. */ + if (i + 3 + n > out_content_len) { MBEDTLS_SSL_DEBUG_MSG(1, ("certificate too large, %" MBEDTLS_PRINTF_SIZET " > %" MBEDTLS_PRINTF_SIZET, - i + 3 + n, (size_t) MBEDTLS_SSL_OUT_CONTENT_LEN)); + i + 3 + n, out_content_len)); return MBEDTLS_ERR_SSL_BUFFER_TOO_SMALL; } diff --git a/library/ssl_tls12_client.c b/library/ssl_tls12_client.c index c8149087d0..899b19d884 100644 --- a/library/ssl_tls12_client.c +++ b/library/ssl_tls12_client.c @@ -2415,7 +2415,7 @@ static int ssl_write_client_key_exchange(mbedtls_ssl_context *ssl) * The export format is an ECPoint structure as expected by TLS, * but we just need to add a length byte before that. */ unsigned char *own_pubkey = ssl->out_msg + header_len + 1; - unsigned char *end = ssl->out_msg + MBEDTLS_SSL_OUT_CONTENT_LEN; + unsigned char *end = ssl->out_msg + mbedtls_ssl_get_out_content_len(ssl); size_t own_pubkey_max_len = (size_t) (end - own_pubkey); size_t own_pubkey_len; @@ -2475,12 +2475,16 @@ static int ssl_write_client_key_exchange(mbedtls_ssl_context *ssl) const size_t ecpoint_max_len = PSA_EXPORT_PUBLIC_KEY_OUTPUT_SIZE(handshake->xxdh_psa_type, handshake->xxdh_psa_bits); + const size_t out_content_len = mbedtls_ssl_get_out_content_len(ssl); header_len = 4; - if (ecpoint_max_len > MBEDTLS_SSL_OUT_CONTENT_LEN - ecpoint_len_size || + /* The second comparison is only reached when the first one is false, + * that is when ecpoint_len_size + ecpoint_max_len <= out_content_len, + * so neither subtraction can wrap. */ + if (ecpoint_max_len > out_content_len - ecpoint_len_size || header_len + content_len_size + ssl->conf->psk_identity_len - > MBEDTLS_SSL_OUT_CONTENT_LEN - ecpoint_len_size - ecpoint_max_len) { + > out_content_len - ecpoint_len_size - ecpoint_max_len) { MBEDTLS_SSL_DEBUG_MSG(1, ("psk identity too long or SSL buffer too short")); return MBEDTLS_ERR_SSL_BUFFER_TOO_SMALL; @@ -2528,7 +2532,7 @@ static int ssl_write_client_key_exchange(mbedtls_ssl_context *ssl) * The export format is an ECPoint structure as expected by TLS, * but we just need to add a length byte before that. */ unsigned char *own_pubkey = p + 1; - unsigned char *end = ssl->out_msg + MBEDTLS_SSL_OUT_CONTENT_LEN; + unsigned char *end = ssl->out_msg + mbedtls_ssl_get_out_content_len(ssl); size_t own_pubkey_max_len = (size_t) (end - own_pubkey); size_t own_pubkey_len = 0; @@ -2595,7 +2599,7 @@ static int ssl_write_client_key_exchange(mbedtls_ssl_context *ssl) header_len = 4; content_len = ssl->conf->psk_identity_len; - if (header_len + 2 + content_len > MBEDTLS_SSL_OUT_CONTENT_LEN) { + if (header_len + 2 + content_len > mbedtls_ssl_get_out_content_len(ssl)) { MBEDTLS_SSL_DEBUG_MSG(1, ("psk identity too long or SSL buffer too short")); return MBEDTLS_ERR_SSL_BUFFER_TOO_SMALL; @@ -2626,7 +2630,7 @@ static int ssl_write_client_key_exchange(mbedtls_ssl_context *ssl) header_len = 4; unsigned char *out_p = ssl->out_msg + header_len; - unsigned char *end_p = ssl->out_msg + MBEDTLS_SSL_OUT_CONTENT_LEN - + unsigned char *end_p = ssl->out_msg + mbedtls_ssl_get_out_content_len(ssl) - header_len; ret = mbedtls_psa_ecjpake_write_round(&ssl->handshake->psa_pake_ctx, out_p, end_p - out_p, &content_len, @@ -2698,11 +2702,8 @@ static int ssl_write_certificate_verify(mbedtls_ssl_context *ssl) mbedtls_md_type_t md_alg = MBEDTLS_MD_NONE; size_t hashlen; void *rs_ctx = NULL; -#if defined(MBEDTLS_SSL_VARIABLE_BUFFER_LENGTH) - size_t out_buf_len = ssl->out_buf_len - (size_t) (ssl->out_msg - ssl->out_buf); -#else - size_t out_buf_len = MBEDTLS_SSL_OUT_BUFFER_LEN - (size_t) (ssl->out_msg - ssl->out_buf); -#endif + size_t out_buf_len = mbedtls_ssl_get_out_buf_len(ssl) + - (size_t) (ssl->out_msg - ssl->out_buf); MBEDTLS_SSL_DEBUG_MSG(2, ("=> write certificate verify")); diff --git a/library/ssl_tls12_server.c b/library/ssl_tls12_server.c index b02ae505ef..b8ab4a4cbf 100644 --- a/library/ssl_tls12_server.c +++ b/library/ssl_tls12_server.c @@ -1528,7 +1528,7 @@ static void ssl_write_cid_ext(mbedtls_ssl_context *ssl, { unsigned char *p = buf; size_t ext_len; - const unsigned char *end = ssl->out_msg + MBEDTLS_SSL_OUT_CONTENT_LEN; + const unsigned char *end = ssl->out_msg + mbedtls_ssl_get_out_content_len(ssl); *olen = 0; @@ -1763,7 +1763,7 @@ static int ssl_write_ecjpake_kkpp_ext(mbedtls_ssl_context *ssl, { int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED; unsigned char *p = buf; - const unsigned char *end = ssl->out_msg + MBEDTLS_SSL_OUT_CONTENT_LEN; + const unsigned char *end = ssl->out_msg + mbedtls_ssl_get_out_content_len(ssl); size_t kkpp_len; *olen = 0; @@ -1809,7 +1809,7 @@ static void ssl_write_use_srtp_ext(mbedtls_ssl_context *ssl, { size_t mki_len = 0, ext_len = 0; uint16_t profile_value = 0; - const unsigned char *end = ssl->out_msg + MBEDTLS_SSL_OUT_CONTENT_LEN; + const unsigned char *end = ssl->out_msg + mbedtls_ssl_get_out_content_len(ssl); *olen = 0; @@ -1898,7 +1898,8 @@ static int ssl_write_hello_verify_request(mbedtls_ssl_context *ssl) cookie_len_byte = p++; if ((ret = ssl->conf->f_cookie_write(ssl->conf->p_cookie, - &p, ssl->out_buf + MBEDTLS_SSL_OUT_BUFFER_LEN, + &p, + ssl->out_buf + mbedtls_ssl_get_out_buf_len(ssl), ssl->cli_id, ssl->cli_id_len)) != 0) { MBEDTLS_SSL_DEBUG_RET(1, "f_cookie_write", ret); return ret; @@ -2190,7 +2191,7 @@ static int ssl_write_server_hello(mbedtls_ssl_context *ssl) #endif #if defined(MBEDTLS_SSL_ALPN) - unsigned char *end = buf + MBEDTLS_SSL_OUT_CONTENT_LEN - 4; + unsigned char *end = buf + mbedtls_ssl_get_out_content_len(ssl) - 4; if ((ret = mbedtls_ssl_write_alpn_ext(ssl, p + 2 + ext_len, end, &olen)) != 0) { return ret; @@ -2251,7 +2252,7 @@ static int ssl_write_certificate_request(mbedtls_ssl_context *ssl) uint16_t dn_size, total_dn_size; /* excluding length bytes */ size_t ct_len, sa_len; /* including length bytes */ unsigned char *buf, *p; - const unsigned char * const end = ssl->out_msg + MBEDTLS_SSL_OUT_CONTENT_LEN; + const unsigned char * const end = ssl->out_msg + mbedtls_ssl_get_out_content_len(ssl); const mbedtls_x509_crt *crt; int authmode; @@ -2426,8 +2427,16 @@ static int ssl_resume_server_key_exchange(mbedtls_ssl_context *ssl, * ssl_write_server_key_exchange also takes care of incrementing * ssl->out_msglen. */ unsigned char *sig_start = ssl->out_msg + ssl->out_msglen + 2; - size_t sig_max_len = (ssl->out_buf + MBEDTLS_SSL_OUT_CONTENT_LEN - - sig_start); + /* Deliberately based on out_buf rather than out_msg: this bound therefore + * stops short of the end of the content area by the record header, which + * is conservative. Do not "simplify" it to out_msg without checking the + * non-async path below, which bounds the same write more loosely. */ + const unsigned char *end = ssl->out_buf + mbedtls_ssl_get_out_content_len(ssl); + /* Compare before subtracting: with a short per-connection outgoing + * length, sig_start can lie past end, and the difference would wrap. + * A zero budget leaves the callback no room, so the handshake fails + * instead. */ + size_t sig_max_len = sig_start < end ? (size_t) (end - sig_start) : 0; int ret = ssl->conf->f_async_resume(ssl, sig_start, signature_len, sig_max_len); if (ret != MBEDTLS_ERR_SSL_ASYNC_IN_PROGRESS) { @@ -2462,11 +2471,8 @@ static int ssl_prepare_server_key_exchange(mbedtls_ssl_context *ssl, #endif /* MBEDTLS_KEY_EXCHANGE_WITH_SERVER_SIGNATURE_ENABLED */ #if defined(MBEDTLS_KEY_EXCHANGE_WITH_SERVER_SIGNATURE_ENABLED) -#if defined(MBEDTLS_SSL_VARIABLE_BUFFER_LENGTH) - size_t out_buf_len = ssl->out_buf_len - (size_t) (ssl->out_msg - ssl->out_buf); -#else - size_t out_buf_len = MBEDTLS_SSL_OUT_BUFFER_LEN - (size_t) (ssl->out_msg - ssl->out_buf); -#endif + size_t out_buf_len = mbedtls_ssl_get_out_buf_len(ssl) + - (size_t) (ssl->out_msg - ssl->out_buf); #endif ssl->out_msglen = 4; /* header (type:1, length:3) to be written later */ @@ -2484,7 +2490,7 @@ static int ssl_prepare_server_key_exchange(mbedtls_ssl_context *ssl, if (ciphersuite_info->key_exchange == MBEDTLS_KEY_EXCHANGE_ECJPAKE) { int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED; unsigned char *out_p = ssl->out_msg + ssl->out_msglen; - unsigned char *end_p = ssl->out_msg + MBEDTLS_SSL_OUT_CONTENT_LEN - + unsigned char *end_p = ssl->out_msg + mbedtls_ssl_get_out_content_len(ssl) - ssl->out_msglen; size_t output_offset = 0; size_t output_len = 0; @@ -2635,7 +2641,7 @@ curve_matching_done: */ unsigned char *own_pubkey = p + data_length_size; - size_t own_pubkey_max_len = (size_t) (MBEDTLS_SSL_OUT_CONTENT_LEN + size_t own_pubkey_max_len = (size_t) (mbedtls_ssl_get_out_content_len(ssl) - (own_pubkey - ssl->out_msg)); status = psa_export_public_key(handshake->xxdh_psa_privkey, @@ -3407,7 +3413,7 @@ static int ssl_write_new_session_ticket(mbedtls_ssl_context *ssl) if ((ret = ssl->conf->f_ticket_write(ssl->conf->p_ticket, ssl->session_negotiate, ssl->out_msg + 10, - ssl->out_msg + MBEDTLS_SSL_OUT_CONTENT_LEN, + ssl->out_msg + mbedtls_ssl_get_out_content_len(ssl), &tlen, &lifetime)) != 0) { MBEDTLS_SSL_DEBUG_RET(1, "mbedtls_ssl_ticket_write", ret); } diff --git a/library/ssl_tls13_generic.c b/library/ssl_tls13_generic.c index 60c0f2348c..4eea3cd3dc 100644 --- a/library/ssl_tls13_generic.c +++ b/library/ssl_tls13_generic.c @@ -1307,7 +1307,7 @@ int mbedtls_ssl_tls13_write_change_cipher_spec(mbedtls_ssl_context *ssl) /* Write CCS message */ MBEDTLS_SSL_PROC_CHK(ssl_tls13_write_change_cipher_spec_body( ssl, ssl->out_msg, - ssl->out_msg + MBEDTLS_SSL_OUT_CONTENT_LEN, + ssl->out_msg + mbedtls_ssl_get_out_content_len(ssl), &ssl->out_msglen)); ssl->out_msgtype = MBEDTLS_SSL_MSG_CHANGE_CIPHER_SPEC; @@ -1746,23 +1746,31 @@ int mbedtls_ssl_tls13_write_record_size_limit_ext(mbedtls_ssl_context *ssl, size_t *out_len) { unsigned char *p = buf; + const size_t record_size_limit = mbedtls_ssl_get_in_content_len(ssl); *out_len = 0; - MBEDTLS_STATIC_ASSERT(MBEDTLS_SSL_IN_CONTENT_LEN >= MBEDTLS_SSL_RECORD_SIZE_LIMIT_MIN, - "MBEDTLS_SSL_IN_CONTENT_LEN is less than the " - "minimum record size limit"); + /* The incoming content length is a runtime value, so this bound cannot be + * checked at compile time. A connection that cannot hold the smallest + * record RFC 8449 permits has nothing valid to advertise. */ + if (record_size_limit < MBEDTLS_SSL_RECORD_SIZE_LIMIT_MIN) { + MBEDTLS_SSL_DEBUG_MSG(1, ("incoming content length %" MBEDTLS_PRINTF_SIZET + " is less than the minimum record size limit %d", + record_size_limit, + MBEDTLS_SSL_RECORD_SIZE_LIMIT_MIN)); + return MBEDTLS_ERR_SSL_BAD_INPUT_DATA; + } MBEDTLS_SSL_CHK_BUF_PTR(p, end, 6); MBEDTLS_PUT_UINT16_BE(MBEDTLS_TLS_EXT_RECORD_SIZE_LIMIT, p, 0); MBEDTLS_PUT_UINT16_BE(MBEDTLS_SSL_RECORD_SIZE_LIMIT_EXTENSION_DATA_LENGTH, p, 2); - MBEDTLS_PUT_UINT16_BE(MBEDTLS_SSL_IN_CONTENT_LEN, p, 4); + MBEDTLS_PUT_UINT16_BE(record_size_limit, p, 4); *out_len = 6; - MBEDTLS_SSL_DEBUG_MSG(2, ("Sent RecordSizeLimit: %d Bytes", - MBEDTLS_SSL_IN_CONTENT_LEN)); + MBEDTLS_SSL_DEBUG_MSG(2, ("Sent RecordSizeLimit: %" MBEDTLS_PRINTF_SIZET " Bytes", + record_size_limit)); mbedtls_ssl_tls13_set_hs_sent_ext_mask(ssl, MBEDTLS_TLS_EXT_RECORD_SIZE_LIMIT); diff --git a/programs/ssl/ssl_client2.c b/programs/ssl/ssl_client2.c index ad87e225da..dc9b112b48 100644 --- a/programs/ssl/ssl_client2.c +++ b/programs/ssl/ssl_client2.c @@ -67,6 +67,8 @@ int main(void) #define DFL_AUTH_MODE -1 #define DFL_SET_HOSTNAME 1 #define DFL_MFL_CODE MBEDTLS_SSL_MAX_FRAG_LEN_NONE +#define DFL_IN_CONTENT_LEN 0 +#define DFL_OUT_CONTENT_LEN 0 #define DFL_TRUNC_HMAC -1 #define DFL_RECSPLIT -1 #define DFL_RECONNECT 0 @@ -231,6 +233,14 @@ int main(void) #define USAGE_MAX_FRAG_LEN "" #endif /* MBEDTLS_SSL_MAX_FRAGMENT_LENGTH */ +#if defined(MBEDTLS_SSL_RUNTIME_CONTENT_LEN) +#define USAGE_CONTENT_LEN \ + " in_content_len=%%d default: MBEDTLS_SSL_IN_CONTENT_LEN\n" \ + " out_content_len=%%d default: MBEDTLS_SSL_OUT_CONTENT_LEN\n" +#else +#define USAGE_CONTENT_LEN "" +#endif /* MBEDTLS_SSL_RUNTIME_CONTENT_LEN */ + #if defined(MBEDTLS_SSL_ALPN) #define USAGE_ALPN \ " alpn=%%s default: \"\" (disabled)\n" \ @@ -425,6 +435,7 @@ int main(void) USAGE_TICKETS \ USAGE_EAP_TLS \ USAGE_MAX_FRAG_LEN \ + USAGE_CONTENT_LEN \ USAGE_CONTEXT_CRT_CB \ USAGE_ALPN \ USAGE_EMS \ @@ -504,6 +515,8 @@ struct options { int set_hostname; /* call mbedtls_ssl_set_hostname()? */ /* 0=no, 1=yes, -1=NULL */ unsigned char mfl_code; /* code for maximum fragment length */ + int in_content_len; /* incoming content length (0: default) */ + int out_content_len; /* outgoing content length (0: default) */ int trunc_hmac; /* negotiate truncated hmac or not */ int recsplit; /* enable record splitting? */ int reconnect; /* attempt to resume session */ @@ -941,6 +954,8 @@ int main(int argc, char *argv[]) opt.auth_mode = DFL_AUTH_MODE; opt.set_hostname = DFL_SET_HOSTNAME; opt.mfl_code = DFL_MFL_CODE; + opt.in_content_len = DFL_IN_CONTENT_LEN; + opt.out_content_len = DFL_OUT_CONTENT_LEN; opt.trunc_hmac = DFL_TRUNC_HMAC; opt.recsplit = DFL_RECSPLIT; opt.reconnect = DFL_RECONNECT; @@ -1350,6 +1365,18 @@ usage: } else { goto usage; } +#if defined(MBEDTLS_SSL_RUNTIME_CONTENT_LEN) + } else if (strcmp(p, "in_content_len") == 0) { + opt.in_content_len = atoi(q); + if (opt.in_content_len <= 0) { + goto usage; + } + } else if (strcmp(p, "out_content_len") == 0) { + opt.out_content_len = atoi(q); + if (opt.out_content_len <= 0) { + goto usage; + } +#endif /* MBEDTLS_SSL_RUNTIME_CONTENT_LEN */ } else if (strcmp(p, "trunc_hmac") == 0) { switch (atoi(q)) { case 0: opt.trunc_hmac = MBEDTLS_SSL_TRUNC_HMAC_DISABLED; break; @@ -2020,6 +2047,22 @@ usage: } #endif /* MBEDTLS_SSL_EARLY_DATA */ +#if defined(MBEDTLS_SSL_RUNTIME_CONTENT_LEN) + if (opt.in_content_len != DFL_IN_CONTENT_LEN && + (ret = mbedtls_ssl_set_in_content_len(&ssl, opt.in_content_len)) != 0) { + mbedtls_printf(" failed\n ! mbedtls_ssl_set_in_content_len returned -0x%x\n\n", + (unsigned int) -ret); + goto exit; + } + + if (opt.out_content_len != DFL_OUT_CONTENT_LEN && + (ret = mbedtls_ssl_set_out_content_len(&ssl, opt.out_content_len)) != 0) { + mbedtls_printf(" failed\n ! mbedtls_ssl_set_out_content_len returned -0x%x\n\n", + (unsigned int) -ret); + goto exit; + } +#endif /* MBEDTLS_SSL_RUNTIME_CONTENT_LEN */ + if ((ret = mbedtls_ssl_setup(&ssl, &conf)) != 0) { mbedtls_printf(" failed\n ! mbedtls_ssl_setup returned -0x%x\n\n", (unsigned int) -ret); diff --git a/programs/ssl/ssl_server2.c b/programs/ssl/ssl_server2.c index b5cdc817b8..3fce0879d0 100644 --- a/programs/ssl/ssl_server2.c +++ b/programs/ssl/ssl_server2.c @@ -111,6 +111,8 @@ int main(void) #define DFL_CERT_REQ_CA_LIST MBEDTLS_SSL_CERT_REQ_CA_LIST_ENABLED #define DFL_CERT_REQ_DN_HINT 0 #define DFL_MFL_CODE MBEDTLS_SSL_MAX_FRAG_LEN_NONE +#define DFL_IN_CONTENT_LEN 0 +#define DFL_OUT_CONTENT_LEN 0 #define DFL_TRUNC_HMAC -1 #define DFL_TICKETS MBEDTLS_SSL_SESSION_TICKETS_ENABLED #define DFL_DUMMY_TICKET 0 @@ -345,6 +347,14 @@ int main(void) #define USAGE_MAX_FRAG_LEN "" #endif /* MBEDTLS_SSL_MAX_FRAGMENT_LENGTH */ +#if defined(MBEDTLS_SSL_RUNTIME_CONTENT_LEN) +#define USAGE_CONTENT_LEN \ + " in_content_len=%%d default: MBEDTLS_SSL_IN_CONTENT_LEN\n" \ + " out_content_len=%%d default: MBEDTLS_SSL_OUT_CONTENT_LEN\n" +#else +#define USAGE_CONTENT_LEN "" +#endif /* MBEDTLS_SSL_RUNTIME_CONTENT_LEN */ + #if defined(MBEDTLS_SSL_ALPN) #define USAGE_ALPN \ " alpn=%%s default: \"\" (disabled)\n" \ @@ -550,6 +560,7 @@ int main(void) USAGE_CACHE \ USAGE_CACHE_TIME \ USAGE_MAX_FRAG_LEN \ + USAGE_CONTENT_LEN \ USAGE_ALPN \ USAGE_EMS \ USAGE_ETM \ @@ -650,6 +661,8 @@ struct options { int cert_req_ca_list; /* should we send the CA list? */ int cert_req_dn_hint; /* mode to set DN hints for CA list to send */ unsigned char mfl_code; /* code for maximum fragment length */ + int in_content_len; /* incoming content length (0: default) */ + int out_content_len; /* outgoing content length (0: default) */ int trunc_hmac; /* accept truncated hmac? */ int tickets; /* enable / disable session tickets */ int dummy_ticket; /* enable / disable dummy ticket generator */ @@ -1718,6 +1731,8 @@ int main(int argc, char *argv[]) opt.cert_req_ca_list = DFL_CERT_REQ_CA_LIST; opt.cert_req_dn_hint = DFL_CERT_REQ_DN_HINT; opt.mfl_code = DFL_MFL_CODE; + opt.in_content_len = DFL_IN_CONTENT_LEN; + opt.out_content_len = DFL_OUT_CONTENT_LEN; opt.trunc_hmac = DFL_TRUNC_HMAC; opt.tickets = DFL_TICKETS; opt.dummy_ticket = DFL_DUMMY_TICKET; @@ -2125,6 +2140,18 @@ usage: } else { goto usage; } +#if defined(MBEDTLS_SSL_RUNTIME_CONTENT_LEN) + } else if (strcmp(p, "in_content_len") == 0) { + opt.in_content_len = atoi(q); + if (opt.in_content_len <= 0) { + goto usage; + } + } else if (strcmp(p, "out_content_len") == 0) { + opt.out_content_len = atoi(q); + if (opt.out_content_len <= 0) { + goto usage; + } +#endif /* MBEDTLS_SSL_RUNTIME_CONTENT_LEN */ } else if (strcmp(p, "alpn") == 0) { opt.alpn_string = q; } else if (strcmp(p, "trunc_hmac") == 0) { @@ -3179,6 +3206,22 @@ usage: mbedtls_ssl_conf_max_tls_version(&conf, opt.max_version); } +#if defined(MBEDTLS_SSL_RUNTIME_CONTENT_LEN) + if (opt.in_content_len != DFL_IN_CONTENT_LEN && + (ret = mbedtls_ssl_set_in_content_len(&ssl, opt.in_content_len)) != 0) { + mbedtls_printf(" failed\n ! mbedtls_ssl_set_in_content_len returned -0x%x\n\n", + (unsigned int) -ret); + goto exit; + } + + if (opt.out_content_len != DFL_OUT_CONTENT_LEN && + (ret = mbedtls_ssl_set_out_content_len(&ssl, opt.out_content_len)) != 0) { + mbedtls_printf(" failed\n ! mbedtls_ssl_set_out_content_len returned -0x%x\n\n", + (unsigned int) -ret); + goto exit; + } +#endif /* MBEDTLS_SSL_RUNTIME_CONTENT_LEN */ + if ((ret = mbedtls_ssl_setup(&ssl, &conf)) != 0) { mbedtls_printf(" failed\n ! mbedtls_ssl_setup returned -0x%x\n\n", (unsigned int) -ret); goto exit; diff --git a/scripts/data_files/config-options-current.txt b/scripts/data_files/config-options-current.txt index 81b233804c..aa91fbb4f7 100644 --- a/scripts/data_files/config-options-current.txt +++ b/scripts/data_files/config-options-current.txt @@ -47,6 +47,7 @@ MBEDTLS_SSL_PROTO_TLS1_2 MBEDTLS_SSL_PROTO_TLS1_3 MBEDTLS_SSL_RECORD_SIZE_LIMIT MBEDTLS_SSL_RENEGOTIATION +MBEDTLS_SSL_RUNTIME_CONTENT_LEN MBEDTLS_SSL_SERVER_NAME_INDICATION MBEDTLS_SSL_SESSION_TICKETS MBEDTLS_SSL_SRV_C diff --git a/tests/scripts/components-configuration-tls.sh b/tests/scripts/components-configuration-tls.sh index 5a77c4defc..1e7048752d 100644 --- a/tests/scripts/components-configuration-tls.sh +++ b/tests/scripts/components-configuration-tls.sh @@ -344,6 +344,22 @@ component_test_variable_ssl_in_out_buffer_len () { tests/compat.sh } +component_test_ssl_runtime_content_len () { + msg "build: MBEDTLS_SSL_RUNTIME_CONTENT_LEN enabled (ASan build)" + scripts/config.py set MBEDTLS_SSL_RUNTIME_CONTENT_LEN + CC=$ASAN_CC cmake -D CMAKE_BUILD_TYPE:String=Asan . + make + + msg "test: MBEDTLS_SSL_RUNTIME_CONTENT_LEN enabled" + make test + + msg "test: ssl-opt.sh, MBEDTLS_SSL_RUNTIME_CONTENT_LEN enabled" + tests/ssl-opt.sh + + msg "test: compat.sh, MBEDTLS_SSL_RUNTIME_CONTENT_LEN enabled" + tests/compat.sh +} + component_test_ssl_alloc_buffer_and_mfl () { msg "build: default config with memory buffer allocator and MFL extension" scripts/config.py set MBEDTLS_MEMORY_BUFFER_ALLOC_C @@ -361,6 +377,24 @@ component_test_ssl_alloc_buffer_and_mfl () { tests/ssl-opt.sh -f "Handshake memory usage" } +component_test_ssl_alloc_buffer_and_runtime_content_len () { + msg "build: default config with memory buffer allocator and runtime content lengths" + scripts/config.py set MBEDTLS_MEMORY_BUFFER_ALLOC_C + scripts/config.py set MBEDTLS_PLATFORM_MEMORY + scripts/config.py set MBEDTLS_MEMORY_DEBUG + scripts/config.py set MBEDTLS_SSL_RUNTIME_CONTENT_LEN + scripts/config.py set MBEDTLS_SSL_VARIABLE_BUFFER_LENGTH + scripts/config.py set MBEDTLS_SSL_RECORD_SIZE_LIMIT + cmake -DCMAKE_BUILD_TYPE:String=Release . + make + + msg "test: MBEDTLS_SSL_RUNTIME_CONTENT_LEN, MBEDTLS_SSL_VARIABLE_BUFFER_LENGTH, MBEDTLS_MEMORY_BUFFER_ALLOC_C and MBEDTLS_MEMORY_DEBUG" + make test + + msg "test: ssl-opt.sh memory usage and runtime content length, MBEDTLS_SSL_RUNTIME_CONTENT_LEN and MBEDTLS_SSL_RECORD_SIZE_LIMIT" + tests/ssl-opt.sh -f "Handshake memory usage\|Runtime content length" +} + component_test_when_no_ciphersuites_have_mac () { msg "build: when no ciphersuites have MAC" scripts/config.py unset PSA_WANT_ALG_CBC_NO_PADDING diff --git a/tests/scripts/generate_ssl_session_reset_check.py b/tests/scripts/generate_ssl_session_reset_check.py index 8e26d9ee1a..9bbd486e31 100755 --- a/tests/scripts/generate_ssl_session_reset_check.py +++ b/tests/scripts/generate_ssl_session_reset_check.py @@ -48,6 +48,7 @@ RULES = { 'in_msglen': ssl_session_reset_check.ResetBehavior.RESET, 'in_left': ssl_session_reset_check.ResetBehavior.RESET, 'in_buf_len': ssl_session_reset_check.ResetBehavior.SPECIAL, + 'in_content_len': ssl_session_reset_check.ResetBehavior.KEEP, 'in_epoch': ssl_session_reset_check.ResetBehavior.RESET, 'next_record_offset': ssl_session_reset_check.ResetBehavior.RESET, 'in_window_top': ssl_session_reset_check.ResetBehavior.RESET, @@ -75,6 +76,7 @@ RULES = { 'out_msglen': ssl_session_reset_check.ResetBehavior.RESET, 'out_left': ssl_session_reset_check.ResetBehavior.RESET, 'out_buf_len': ssl_session_reset_check.ResetBehavior.SPECIAL, + 'out_content_len': ssl_session_reset_check.ResetBehavior.KEEP, 'cur_out_ctr': ssl_session_reset_check.ResetBehavior.RESET, 'mtu': ssl_session_reset_check.ResetBehavior.KEEP, 'hostname': ssl_session_reset_check.ResetBehavior.KEEP, @@ -102,8 +104,18 @@ SPECIAL_BEHAVIORS = { '(after->flags & MBEDTLS_SSL_CONTEXT_FLAGS_KEEP_AT_SESSION));'], 'tls_version': ['TEST_ASSERT(after->tls_version == after->conf->max_tls_version);'], 'user_data': ['TEST_ASSERT(before->user_data.n == after->user_data.n);'], - 'in_buf_len': ['TEST_ASSERT(after->in_buf_len == MBEDTLS_SSL_IN_BUFFER_LEN);'], - 'out_buf_len': ['TEST_ASSERT(after->out_buf_len == MBEDTLS_SSL_OUT_BUFFER_LEN);'], + 'in_buf_len': ['#if defined(MBEDTLS_SSL_RUNTIME_CONTENT_LEN)', + 'TEST_ASSERT(after->in_buf_len == MBEDTLS_SSL_IN_BUFFER_LEN -', + ' (MBEDTLS_SSL_IN_CONTENT_LEN - after->in_content_len));', + '#else', + 'TEST_ASSERT(after->in_buf_len == MBEDTLS_SSL_IN_BUFFER_LEN);', + '#endif'], + 'out_buf_len': ['#if defined(MBEDTLS_SSL_RUNTIME_CONTENT_LEN)', + 'TEST_ASSERT(after->out_buf_len == MBEDTLS_SSL_OUT_BUFFER_LEN -', + ' (MBEDTLS_SSL_OUT_CONTENT_LEN - after->out_content_len));', + '#else', + 'TEST_ASSERT(after->out_buf_len == MBEDTLS_SSL_OUT_BUFFER_LEN);', + '#endif'], } NAMED_STRUCTURES = frozenset([ diff --git a/tests/src/ssl_context_reset_verifier.c b/tests/src/ssl_context_reset_verifier.c index a0fda308ec..d493a434a4 100644 --- a/tests/src/ssl_context_reset_verifier.c +++ b/tests/src/ssl_context_reset_verifier.c @@ -84,8 +84,16 @@ int mbedtls_test_ssl_check_context_after_session_reset(const mbedtls_ssl_context TEST_ASSERT(after->in_msgtype == initial.in_msgtype); TEST_ASSERT(after->in_msglen == initial.in_msglen); TEST_ASSERT(after->in_left == initial.in_left); -#if defined(MBEDTLS_SSL_VARIABLE_BUFFER_LENGTH) +#if defined(MBEDTLS_SSL_HAVE_BUFFER_LEN_FIELDS) + #if defined(MBEDTLS_SSL_RUNTIME_CONTENT_LEN) + TEST_ASSERT(after->in_buf_len == MBEDTLS_SSL_IN_BUFFER_LEN - + (MBEDTLS_SSL_IN_CONTENT_LEN - after->in_content_len)); + #else TEST_ASSERT(after->in_buf_len == MBEDTLS_SSL_IN_BUFFER_LEN); + #endif +#endif +#if defined(MBEDTLS_SSL_RUNTIME_CONTENT_LEN) + TEST_EQUAL(before->in_content_len, after->in_content_len); #endif #if defined(MBEDTLS_SSL_PROTO_DTLS) TEST_ASSERT(after->in_epoch == initial.in_epoch); @@ -132,8 +140,16 @@ int mbedtls_test_ssl_check_context_after_session_reset(const mbedtls_ssl_context TEST_ASSERT(after->out_msgtype == initial.out_msgtype); TEST_ASSERT(after->out_msglen == initial.out_msglen); TEST_ASSERT(after->out_left == initial.out_left); -#if defined(MBEDTLS_SSL_VARIABLE_BUFFER_LENGTH) +#if defined(MBEDTLS_SSL_HAVE_BUFFER_LEN_FIELDS) + #if defined(MBEDTLS_SSL_RUNTIME_CONTENT_LEN) + TEST_ASSERT(after->out_buf_len == MBEDTLS_SSL_OUT_BUFFER_LEN - + (MBEDTLS_SSL_OUT_CONTENT_LEN - after->out_content_len)); + #else TEST_ASSERT(after->out_buf_len == MBEDTLS_SSL_OUT_BUFFER_LEN); + #endif +#endif +#if defined(MBEDTLS_SSL_RUNTIME_CONTENT_LEN) + TEST_EQUAL(before->out_content_len, after->out_content_len); #endif TEST_MEMORY_COMPARE(after->cur_out_ctr, sizeof(after->cur_out_ctr), initial.cur_out_ctr, sizeof(initial.cur_out_ctr)); #if defined(MBEDTLS_SSL_PROTO_DTLS) diff --git a/tests/src/test_helpers/ssl_helpers.c b/tests/src/test_helpers/ssl_helpers.c index ebb9103de9..f86c914caa 100644 --- a/tests/src/test_helpers/ssl_helpers.c +++ b/tests/src/test_helpers/ssl_helpers.c @@ -2152,9 +2152,14 @@ static int test_renegotiation(const mbedtls_test_handshake_test_options *options ret = mbedtls_ssl_renegotiate(&(client->ssl)); #if defined(MBEDTLS_SSL_VARIABLE_BUFFER_LENGTH) if (options->resize_buffers != 0) { - /* Ensure that the buffer sizes are appropriate before resizes */ - TEST_EQUAL(client->ssl.out_buf_len, MBEDTLS_SSL_OUT_BUFFER_LEN); - TEST_EQUAL(client->ssl.in_buf_len, MBEDTLS_SSL_IN_BUFFER_LEN); + /* Starting the renegotiation grew the buffers back to the full size + * of this connection: its own content lengths, not the build-time + * maximum, so that MBEDTLS_SSL_RUNTIME_CONTENT_LEN keeps its saving. + * Without that option the two are the same. */ + TEST_EQUAL(client->ssl.out_buf_len, + mbedtls_ssl_compute_out_buf_len(&client->ssl)); + TEST_EQUAL(client->ssl.in_buf_len, + mbedtls_ssl_compute_in_buf_len(&client->ssl)); } #endif TEST_ASSERT(ret == 0 || @@ -2233,9 +2238,11 @@ static int test_serialization(const mbedtls_test_handshake_test_options *options #endif #if defined(MBEDTLS_SSL_VARIABLE_BUFFER_LENGTH) if (options->resize_buffers != 0) { - /* Ensure that the buffer sizes are appropriate before resizes */ - TEST_EQUAL(server->ssl.out_buf_len, MBEDTLS_SSL_OUT_BUFFER_LEN); - TEST_EQUAL(server->ssl.in_buf_len, MBEDTLS_SSL_IN_BUFFER_LEN); + /* The freshly set up connection starts at its own full size. */ + TEST_EQUAL(server->ssl.out_buf_len, + mbedtls_ssl_compute_out_buf_len(&server->ssl)); + TEST_EQUAL(server->ssl.in_buf_len, + mbedtls_ssl_compute_in_buf_len(&server->ssl)); } #endif TEST_EQUAL(mbedtls_ssl_context_load(&(server->ssl), context_buf, @@ -2283,11 +2290,18 @@ int mbedtls_test_ssl_perform_connection( #if defined(MBEDTLS_SSL_VARIABLE_BUFFER_LENGTH) if (options->resize_buffers != 0) { - /* Ensure that the buffer sizes are appropriate before resizes */ - TEST_EQUAL(client->ssl.out_buf_len, MBEDTLS_SSL_OUT_BUFFER_LEN); - TEST_EQUAL(client->ssl.in_buf_len, MBEDTLS_SSL_IN_BUFFER_LEN); - TEST_EQUAL(server->ssl.out_buf_len, MBEDTLS_SSL_OUT_BUFFER_LEN); - TEST_EQUAL(server->ssl.in_buf_len, MBEDTLS_SSL_IN_BUFFER_LEN); + /* Before the handshake, including after a session reset, each + * buffer is the full size of its connection: its own content length, + * which is the build-time maximum unless + * MBEDTLS_SSL_RUNTIME_CONTENT_LEN shrank it. */ + TEST_EQUAL(client->ssl.out_buf_len, + mbedtls_ssl_compute_out_buf_len(&client->ssl)); + TEST_EQUAL(client->ssl.in_buf_len, + mbedtls_ssl_compute_in_buf_len(&client->ssl)); + TEST_EQUAL(server->ssl.out_buf_len, + mbedtls_ssl_compute_out_buf_len(&server->ssl)); + TEST_EQUAL(server->ssl.in_buf_len, + mbedtls_ssl_compute_in_buf_len(&server->ssl)); } #endif diff --git a/tests/ssl-opt.sh b/tests/ssl-opt.sh index fd57757aaa..1499abc3c2 100755 --- a/tests/ssl-opt.sh +++ b/tests/ssl-opt.sh @@ -1235,6 +1235,45 @@ handshake_memory_check() { fi } +# Get peak memory usage from server output and put it into the variable specified by the first argument +handshake_peak_memory_get() { + OUTPUT_VARIABLE="$1" + OUTPUT_FILE="$2" + + # Get peak memory usage from a pattern like "Heap memory usage after handshake: 23112 bytes. Peak memory usage was 33112" + MEM_USAGE=$(sed -n 's/.*Peak memory usage was //p' < "$OUTPUT_FILE" | grep -o "[0-9]*" | head -1) + + # Check if peak memory usage was read + if [ -z "$MEM_USAGE" ]; then + echo "Error: Can not read the value of peak memory usage" + return 1 + else + eval "$OUTPUT_VARIABLE=$MEM_USAGE" + return 0 + fi +} + +# Get peak memory usage from server output and check if this value +# is not higher than the maximum given by the first argument +handshake_peak_memory_check() { + MAX_MEMORY="$1" + OUTPUT_FILE="$2" + + # Get peak memory usage + if ! handshake_peak_memory_get "MEMORY_USAGE" "$OUTPUT_FILE"; then + return 1 + fi + + # Check if peak memory usage is below max value + if [ "$MEMORY_USAGE" -gt "$MAX_MEMORY" ]; then + echo "\nFailed: Peak memory usage was $MEMORY_USAGE bytes," \ + "but should be below $MAX_MEMORY bytes" + return 1 + else + return 0 + fi +} + # wait for client to terminate and set CLI_EXIT # must be called right after starting the client wait_client_done() { @@ -1910,6 +1949,55 @@ run_tests_memory_after_handshake() run_test_memory_after_handshake_with_mfl 512 "$MEMORY_USAGE_MFL_16K" } +# Test that the server's peak memory usage falls when it shrinks its own content +# lengths at runtime. +# first argument ($1) is the server's incoming content length +# second argument ($2) is the server's outgoing content length +# third argument ($3) is the server's peak memory usage with the default lengths +run_test_memory_with_runtime_content_len() +{ + # The I/O buffers shrink by exactly (16384 - $1) + (16384 - $2). Require + # most of that saving, so that the test fails if the lengths never reach + # the allocation. + MEMORY_USAGE_LIMIT="$(( $3 - ( ( 16384 - $1 ) + ( 16384 - $2 ) ) * 9 / 10 ))" + + run_test "Handshake memory usage (runtime content length in $1, out $2)" \ + "$P_SRV debug_level=3 auth_mode=required force_version=tls12 \ + in_content_len=$1 out_content_len=$2" \ + "$P_CLI debug_level=3 \ + crt_file=$DATA_FILES_PATH/server5.crt key_file=$DATA_FILES_PATH/server5.key \ + force_ciphersuite=TLS-ECDHE-ECDSA-WITH-AES-128-CCM" \ + 0 \ + -F "handshake_peak_memory_check $MEMORY_USAGE_LIMIT" +} + +# Test that the server's peak memory usage falls when it shrinks its incoming +# length, its outgoing length, or both +run_tests_memory_with_runtime_content_len() +{ + # all tests in this sequence requires the same configuration (see requires_config_enabled()) + SKIP_THIS_TESTS="$SKIP_NEXT" + + # first test with the default lengths is to get reference memory usage + PEAK_MEMORY_USAGE_16K=0 + run_test "Handshake memory usage initial (runtime content length default)" \ + "$P_SRV debug_level=3 auth_mode=required force_version=tls12" \ + "$P_CLI debug_level=3 \ + crt_file=$DATA_FILES_PATH/server5.crt key_file=$DATA_FILES_PATH/server5.key \ + force_ciphersuite=TLS-ECDHE-ECDSA-WITH-AES-128-CCM" \ + 0 \ + -F "handshake_peak_memory_get PEAK_MEMORY_USAGE_16K" + + SKIP_NEXT="$SKIP_THIS_TESTS" + run_test_memory_with_runtime_content_len 4096 4096 "$PEAK_MEMORY_USAGE_16K" + + SKIP_NEXT="$SKIP_THIS_TESTS" + run_test_memory_with_runtime_content_len 1024 16384 "$PEAK_MEMORY_USAGE_16K" + + SKIP_NEXT="$SKIP_THIS_TESTS" + run_test_memory_with_runtime_content_len 16384 1024 "$PEAK_MEMORY_USAGE_16K" +} + run_test_export_keying_material() { unset EXPORTED_KEY1 unset EXPORTED_KEY2 @@ -5134,6 +5222,105 @@ run_test "Record Size Limit: TLS 1.3 m->m: both peer comply with record size # End of Record size limit tests +# Tests for runtime content lengths: a client that shrinks its incoming length +# against a full-size server. Only a negotiated max_fragment_length or +# record_size_limit tells the server about it; without either, a record longer +# than the client's incoming length makes the read fail cleanly, the same way +# it does when MBEDTLS_SSL_IN_CONTENT_LEN is set to that length at build time. + +requires_config_enabled MBEDTLS_SSL_RUNTIME_CONTENT_LEN +requires_config_enabled MBEDTLS_SSL_MAX_FRAGMENT_LENGTH +requires_config_enabled MBEDTLS_SSL_PROTO_TLS1_2 +requires_max_content_len 2048 +run_test "Runtime content length: client in 1024, MFL 1024" \ + "$P_SRV debug_level=3 response_size=2000" \ + "$P_CLI debug_level=3 force_version=tls12 in_content_len=1024 max_frag_len=1024" \ + 0 \ + -c "client hello, adding max_fragment_length extension" \ + -s "server hello, max_fragment_length extension" \ + -c "Maximum incoming record payload length is 1024" \ + -s "Maximum outgoing record payload length is 1024" \ + -s "2000 bytes written in 2 fragments" \ + -c "1024 bytes read" \ + -c "976 bytes read" + +requires_config_enabled MBEDTLS_SSL_RUNTIME_CONTENT_LEN +requires_config_enabled MBEDTLS_SSL_CLI_C +requires_config_enabled MBEDTLS_SSL_SRV_C +requires_config_enabled MBEDTLS_DEBUG_C +requires_config_enabled MBEDTLS_SSL_RECORD_SIZE_LIMIT +requires_config_enabled MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_EPHEMERAL_ENABLED +requires_max_content_len 2048 +run_test "Runtime content length: client in 1024, record_size_limit" \ + "$P_SRV debug_level=3 response_size=2000" \ + "$P_CLI debug_level=3 force_version=tls13 in_content_len=1024" \ + 0 \ + -c "Sent RecordSizeLimit: 1024 Bytes" \ + -s "RecordSizeLimit: 1024 Bytes" \ + -c "Maximum incoming record payload length is 1024" \ + -s "Maximum outgoing record payload length is 1023" \ + -s "2000 bytes written in 2 fragments" \ + -c "1023 bytes read" \ + -c "977 bytes read" + +requires_config_enabled MBEDTLS_SSL_RUNTIME_CONTENT_LEN +requires_config_enabled MBEDTLS_SSL_PROTO_TLS1_2 +requires_max_content_len 2048 +run_test "Runtime content length: client in 1024, no extension, short record" \ + "$P_SRV debug_level=3" \ + "$P_CLI debug_level=3 force_version=tls12 in_content_len=1024" \ + 0 \ + -C "client hello, adding max_fragment_length extension" \ + -c "Maximum incoming record payload length is 1024" \ + -s "Maximum outgoing record payload length is 16384" \ + -c "[0-9]\+ bytes read" + +requires_config_enabled MBEDTLS_SSL_RUNTIME_CONTENT_LEN +requires_config_enabled MBEDTLS_SSL_PROTO_TLS1_2 +requires_max_content_len 2048 +run_test "Runtime content length: client in 1024, no extension, 1025 bytes" \ + "$P_SRV debug_level=3 response_size=1025" \ + "$P_CLI debug_level=3 force_version=tls12 in_content_len=1024" \ + 1 \ + -C "client hello, adding max_fragment_length extension" \ + -s "Maximum outgoing record payload length is 16384" \ + -s "1025 bytes written in 1 fragments" \ + -c "bad message length" \ + -c "mbedtls_ssl_read returned -0x7200" \ + -C "[0-9]\+ bytes read" + +requires_config_enabled MBEDTLS_SSL_RUNTIME_CONTENT_LEN +requires_config_enabled MBEDTLS_SSL_PROTO_TLS1_2 +requires_max_content_len 2048 +run_test "Runtime content length: client in 1024, no extension, 2000 bytes" \ + "$P_SRV debug_level=3 response_size=2000" \ + "$P_CLI debug_level=3 force_version=tls12 in_content_len=1024" \ + 1 \ + -C "client hello, adding max_fragment_length extension" \ + -s "Maximum outgoing record payload length is 16384" \ + -s "2000 bytes written in 1 fragments" \ + -c "requesting more data than fits" \ + -c "mbedtls_ssl_read returned -0x87" \ + -C "[0-9]\+ bytes read" + +requires_config_enabled MBEDTLS_SSL_RUNTIME_CONTENT_LEN +requires_config_enabled MBEDTLS_SSL_CLI_C +requires_config_enabled MBEDTLS_SSL_SRV_C +requires_config_disabled MBEDTLS_SSL_RECORD_SIZE_LIMIT +requires_config_enabled MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_EPHEMERAL_ENABLED +requires_max_content_len 2048 +run_test "Runtime content length: client in 1024, TLS 1.3, no extension" \ + "$P_SRV debug_level=3 response_size=2000" \ + "$P_CLI debug_level=3 force_version=tls13 in_content_len=1024" \ + 1 \ + -s "Maximum outgoing record payload length is 16383" \ + -s "2000 bytes written in 1 fragments" \ + -c "requesting more data than fits" \ + -c "mbedtls_ssl_read returned -0x87" \ + -C "[0-9]\+ bytes read" + +# End of runtime content length tests + # Tests for renegotiation # G_NEXT_SRV is used in renegotiation tests becuase of the increased @@ -14440,6 +14627,14 @@ requires_config_enabled MBEDTLS_SSL_MAX_FRAGMENT_LENGTH requires_max_content_len 16384 run_tests_memory_after_handshake +# Test peak heap memory usage with runtime content lengths +requires_config_enabled MBEDTLS_SSL_PROTO_TLS1_2 +requires_config_enabled MBEDTLS_MEMORY_DEBUG +requires_config_enabled MBEDTLS_MEMORY_BUFFER_ALLOC_C +requires_config_enabled MBEDTLS_SSL_RUNTIME_CONTENT_LEN +requires_max_content_len 16384 +run_tests_memory_with_runtime_content_len + if [ "$LIST_TESTS" -eq 0 ]; then # Final report diff --git a/tests/suites/test_suite_ssl.data b/tests/suites/test_suite_ssl.data index a2703e1027..7d81062675 100644 --- a/tests/suites/test_suite_ssl.data +++ b/tests/suites/test_suite_ssl.data @@ -3632,3 +3632,194 @@ tls13_record_boundary_alignment:MBEDTLS_SSL_IS_SERVER:MBEDTLS_SSL_HS_FINISHED TLS 1.3 EndOfEarlyData record boundary alignment enforcement depends_on:MBEDTLS_SSL_EARLY_DATA:MBEDTLS_SSL_SESSION_TICKETS tls13_record_boundary_alignment:MBEDTLS_SSL_IS_SERVER:MBEDTLS_SSL_HS_END_OF_EARLY_DATA + +Runtime content length: enabling the option alone changes nothing, TLS +depends_on:MBEDTLS_SSL_PROTO_TLS1_2 +ssl_runtime_content_len_default_is_noop:0 + +Runtime content length: enabling the option alone changes nothing, DTLS +depends_on:MBEDTLS_SSL_PROTO_TLS1_2:MBEDTLS_SSL_PROTO_DTLS +ssl_runtime_content_len_default_is_noop:1 + +Runtime content length: incoming above the build-time ceiling is rejected +ssl_runtime_content_len_set:0:0:MBEDTLS_SSL_IN_CONTENT_LEN + 1:MBEDTLS_ERR_SSL_BAD_INPUT_DATA + +Runtime content length: outgoing above the build-time ceiling is rejected +ssl_runtime_content_len_set:1:0:MBEDTLS_SSL_OUT_CONTENT_LEN + 1:MBEDTLS_ERR_SSL_BAD_INPUT_DATA + +Runtime content length: incoming at the build-time ceiling is accepted +ssl_runtime_content_len_set:0:0:MBEDTLS_SSL_IN_CONTENT_LEN:0 + +Runtime content length: outgoing at the build-time ceiling is accepted +ssl_runtime_content_len_set:1:0:MBEDTLS_SSL_OUT_CONTENT_LEN:0 + +Runtime content length: incoming below the floor is rejected +ssl_runtime_content_len_set:0:0:MBEDTLS_SSL_IN_CONTENT_LEN_MIN - 1:MBEDTLS_ERR_SSL_BAD_INPUT_DATA + +Runtime content length: incoming at the floor is accepted +ssl_runtime_content_len_set:0:0:MBEDTLS_SSL_IN_CONTENT_LEN_MIN:0 + +Runtime content length: outgoing below the floor is rejected +ssl_runtime_content_len_set:1:0:MBEDTLS_SSL_OUT_CONTENT_LEN_MIN - 1:MBEDTLS_ERR_SSL_BAD_INPUT_DATA + +Runtime content length: outgoing at the floor is accepted +ssl_runtime_content_len_set:1:0:MBEDTLS_SSL_OUT_CONTENT_LEN_MIN:0 + +Runtime content length: zero incoming is rejected +ssl_runtime_content_len_set:0:0:0:MBEDTLS_ERR_SSL_BAD_INPUT_DATA + +Runtime content length: outgoing too short for a certificate header is rejected +ssl_runtime_content_len_set:1:0:10:MBEDTLS_ERR_SSL_BAD_INPUT_DATA + +Runtime content length: outgoing too short for a DTLS handshake header is rejected +ssl_runtime_content_len_set:1:0:11:MBEDTLS_ERR_SSL_BAD_INPUT_DATA + +Runtime content length: outgoing below TLS 1.3 padding granularity is rejected +depends_on:MBEDTLS_SSL_PROTO_TLS1_3 +ssl_runtime_content_len_set:1:0:MBEDTLS_SSL_CID_TLS1_3_PADDING_GRANULARITY - 1:MBEDTLS_ERR_SSL_BAD_INPUT_DATA + +Runtime content length: incoming after setup is rejected +ssl_runtime_content_len_set:0:1:MBEDTLS_SSL_IN_CONTENT_LEN / 2:MBEDTLS_ERR_SSL_BAD_INPUT_DATA + +Runtime content length: outgoing after setup is rejected +ssl_runtime_content_len_set:1:1:MBEDTLS_SSL_OUT_CONTENT_LEN / 2:MBEDTLS_ERR_SSL_BAD_INPUT_DATA + +Runtime content length: incoming after session reset is rejected +ssl_runtime_content_len_set:0:2:MBEDTLS_SSL_IN_CONTENT_LEN / 2:MBEDTLS_ERR_SSL_BAD_INPUT_DATA + +Runtime content length: outgoing after session reset is rejected +ssl_runtime_content_len_set:1:2:MBEDTLS_SSL_OUT_CONTENT_LEN / 2:MBEDTLS_ERR_SSL_BAD_INPUT_DATA + +Runtime content length: one connection, different incoming and outgoing, TLS 1.2 +depends_on:MBEDTLS_SSL_PROTO_TLS1_2 +ssl_runtime_content_len_handshake:MBEDTLS_SSL_VERSION_TLS1_2:0:2048:512:0:0:1024:2:2048:1:0 + +Runtime content length: two peers with different lengths, TLS 1.2 +depends_on:MBEDTLS_SSL_PROTO_TLS1_2 +ssl_runtime_content_len_handshake:MBEDTLS_SSL_VERSION_TLS1_2:0:4096:1024:1024:4096:2048:2:4096:1:0 + +Runtime content length: two peers with different lengths, DTLS 1.2 +depends_on:MBEDTLS_SSL_PROTO_TLS1_2:MBEDTLS_SSL_PROTO_DTLS +ssl_runtime_content_len_handshake:MBEDTLS_SSL_VERSION_TLS1_2:1:4096:1024:1024:4096:1024:1:4096:1:0 + +Runtime content length: DTLS 1.2 refuses a message above the outgoing length +depends_on:MBEDTLS_SSL_PROTO_TLS1_2:MBEDTLS_SSL_PROTO_DTLS +ssl_runtime_content_len_handshake:MBEDTLS_SSL_VERSION_TLS1_2:1:4096:1024:1024:4096:1025:0:4096:1:0 + +Runtime content length: two peers with different lengths, TLS 1.3 +depends_on:MBEDTLS_SSL_PROTO_TLS1_3 +ssl_runtime_content_len_handshake:MBEDTLS_SSL_VERSION_TLS1_3:0:4096:1024:1024:4096:2048:3:4095:1:0 + +Runtime content length: lengths survive a session reset, TLS 1.2 +depends_on:MBEDTLS_SSL_PROTO_TLS1_2 +ssl_runtime_content_len_handshake:MBEDTLS_SSL_VERSION_TLS1_2:0:0:1024:0:1024:2048:2:2048:2:1 + +Runtime content length: lengths survive a session reset, TLS 1.3 +depends_on:MBEDTLS_SSL_PROTO_TLS1_3 +ssl_runtime_content_len_handshake:MBEDTLS_SSL_VERSION_TLS1_3:0:0:1024:0:1024:2046:2:2046:2:1 + +Runtime content length: resize, TLS 1.2 +ssl_runtime_content_len_resize_buffers:0:MBEDTLS_SSL_MAX_FRAG_LEN_NONE:0:2048:1024:1024:2048 + +Runtime content length: resize, MFL=512, TLS 1.2 +ssl_runtime_content_len_resize_buffers:0:MBEDTLS_SSL_MAX_FRAG_LEN_512:0:2048:1024:1024:2048 + +Runtime content length: resize, reneg, DTLS 1.2 +depends_on:MBEDTLS_SSL_RENEGOTIATION:MBEDTLS_SSL_PROTO_DTLS +ssl_runtime_content_len_resize_buffers:1:MBEDTLS_SSL_MAX_FRAG_LEN_NONE:1:2048:1024:1024:2048 + +Runtime content length: resize, MFL=512, reneg, DTLS 1.2 +depends_on:MBEDTLS_SSL_RENEGOTIATION:MBEDTLS_SSL_PROTO_DTLS +ssl_runtime_content_len_resize_buffers:1:MBEDTLS_SSL_MAX_FRAG_LEN_512:1:2048:1024:1024:2048 + +Runtime content length: resize, MFL above out len, reneg, DTLS 1.2 +depends_on:MBEDTLS_SSL_RENEGOTIATION:MBEDTLS_SSL_PROTO_DTLS +ssl_runtime_content_len_resize_buffers:1:MBEDTLS_SSL_MAX_FRAG_LEN_4096:1:4096:1024:1024:2048 + +Runtime content length: resize, server shrunk, reneg, DTLS 1.2 +depends_on:MBEDTLS_SSL_RENEGOTIATION:MBEDTLS_SSL_PROTO_DTLS +ssl_runtime_content_len_resize_buffers:1:MBEDTLS_SSL_MAX_FRAG_LEN_1024:1:0:0:2048:1024 + +Runtime content length: record at the incoming length is accepted, TLS 1.2 +depends_on:MBEDTLS_SSL_PROTO_TLS1_2 +ssl_runtime_content_len_oversized_record:MBEDTLS_SSL_VERSION_TLS1_2:1024:1024:0 + +Runtime content length: record above the incoming length is rejected, TLS 1.2 +depends_on:MBEDTLS_SSL_PROTO_TLS1_2 +ssl_runtime_content_len_oversized_record:MBEDTLS_SSL_VERSION_TLS1_2:1024:1025:MBEDTLS_ERR_SSL_INVALID_RECORD + +Runtime content length: record at the incoming length is accepted, TLS 1.3 +depends_on:MBEDTLS_SSL_PROTO_TLS1_3:!MBEDTLS_SSL_RECORD_SIZE_LIMIT +ssl_runtime_content_len_oversized_record:MBEDTLS_SSL_VERSION_TLS1_3:1024:1024:0 + +Runtime content length: record above the incoming length is rejected, TLS 1.3 +depends_on:MBEDTLS_SSL_PROTO_TLS1_3:!MBEDTLS_SSL_RECORD_SIZE_LIMIT +ssl_runtime_content_len_oversized_record:MBEDTLS_SSL_VERSION_TLS1_3:1024:1025:MBEDTLS_ERR_SSL_INVALID_RECORD + +Runtime content length: client setup accepts a fragment length at the incoming length +depends_on:MBEDTLS_SSL_CLI_C:MBEDTLS_SSL_PROTO_TLS1_2 +ssl_runtime_content_len_setup_mfl:MBEDTLS_SSL_IS_CLIENT:MBEDTLS_SSL_VERSION_TLS1_2:1024:MBEDTLS_SSL_MAX_FRAG_LEN_1024:0 + +Runtime content length: client setup rejects a fragment length above the incoming length +depends_on:MBEDTLS_SSL_CLI_C:MBEDTLS_SSL_PROTO_TLS1_2 +ssl_runtime_content_len_setup_mfl:MBEDTLS_SSL_IS_CLIENT:MBEDTLS_SSL_VERSION_TLS1_2:1023:MBEDTLS_SSL_MAX_FRAG_LEN_1024:MBEDTLS_ERR_SSL_BAD_CONFIG + +Runtime content length: client setup rejects the largest fragment length above the incoming length +depends_on:MBEDTLS_SSL_CLI_C:MBEDTLS_SSL_PROTO_TLS1_2 +ssl_runtime_content_len_setup_mfl:MBEDTLS_SSL_IS_CLIENT:MBEDTLS_SSL_VERSION_TLS1_2:4095:MBEDTLS_SSL_MAX_FRAG_LEN_4096:MBEDTLS_ERR_SSL_BAD_CONFIG + +Runtime content length: client setup accepts a short incoming length without a fragment length +depends_on:MBEDTLS_SSL_CLI_C:MBEDTLS_SSL_PROTO_TLS1_2 +ssl_runtime_content_len_setup_mfl:MBEDTLS_SSL_IS_CLIENT:MBEDTLS_SSL_VERSION_TLS1_2:512:MBEDTLS_SSL_MAX_FRAG_LEN_NONE:0 + +Runtime content length: TLS 1.3-only client setup accepts a fragment length above the incoming length +depends_on:MBEDTLS_SSL_CLI_C:MBEDTLS_SSL_PROTO_TLS1_3 +ssl_runtime_content_len_setup_mfl:MBEDTLS_SSL_IS_CLIENT:MBEDTLS_SSL_VERSION_TLS1_3:1023:MBEDTLS_SSL_MAX_FRAG_LEN_1024:0 + +Runtime content length: server setup accepts a fragment length above the incoming length +depends_on:MBEDTLS_SSL_SRV_C:MBEDTLS_SSL_PROTO_TLS1_2 +ssl_runtime_content_len_setup_mfl:MBEDTLS_SSL_IS_SERVER:MBEDTLS_SSL_VERSION_TLS1_2:1024:MBEDTLS_SSL_MAX_FRAG_LEN_4096:0 + +Runtime content length: ClientHello max_fragment_length at the incoming length +depends_on:MBEDTLS_SSL_PROTO_TLS1_2:MBEDTLS_SSL_MAX_FRAGMENT_LENGTH +ssl_runtime_content_len_client_hello_ext:MBEDTLS_SSL_VERSION_TLS1_2:1024:MBEDTLS_SSL_MAX_FRAG_LEN_1024:MBEDTLS_TLS_EXT_MAX_FRAGMENT_LENGTH:0:MBEDTLS_SSL_MAX_FRAG_LEN_1024 + +Runtime content length: ClientHello max_fragment_length below the incoming length +depends_on:MBEDTLS_SSL_PROTO_TLS1_2:MBEDTLS_SSL_MAX_FRAGMENT_LENGTH +ssl_runtime_content_len_client_hello_ext:MBEDTLS_SSL_VERSION_TLS1_2:1024:MBEDTLS_SSL_MAX_FRAG_LEN_512:MBEDTLS_TLS_EXT_MAX_FRAGMENT_LENGTH:0:MBEDTLS_SSL_MAX_FRAG_LEN_512 + +Runtime content length: ClientHello has no max_fragment_length unless configured +depends_on:MBEDTLS_SSL_PROTO_TLS1_2 +ssl_runtime_content_len_client_hello_ext:MBEDTLS_SSL_VERSION_TLS1_2:1024:MBEDTLS_SSL_MAX_FRAG_LEN_NONE:MBEDTLS_TLS_EXT_MAX_FRAGMENT_LENGTH:0:-1 + +Runtime content length: ClientHello record_size_limit, build-time incoming length +depends_on:MBEDTLS_SSL_PROTO_TLS1_3:MBEDTLS_SSL_RECORD_SIZE_LIMIT +ssl_runtime_content_len_client_hello_ext:MBEDTLS_SSL_VERSION_TLS1_3:MBEDTLS_SSL_IN_CONTENT_LEN:MBEDTLS_SSL_MAX_FRAG_LEN_NONE:MBEDTLS_TLS_EXT_RECORD_SIZE_LIMIT:0:MBEDTLS_SSL_IN_CONTENT_LEN + +Runtime content length: ClientHello record_size_limit carries the incoming length +depends_on:MBEDTLS_SSL_PROTO_TLS1_3:MBEDTLS_SSL_RECORD_SIZE_LIMIT +ssl_runtime_content_len_client_hello_ext:MBEDTLS_SSL_VERSION_TLS1_3:1024:MBEDTLS_SSL_MAX_FRAG_LEN_NONE:MBEDTLS_TLS_EXT_RECORD_SIZE_LIMIT:0:1024 + +Runtime content length: ClientHello record_size_limit at the RFC 8449 minimum +depends_on:MBEDTLS_SSL_PROTO_TLS1_3:MBEDTLS_SSL_RECORD_SIZE_LIMIT +ssl_runtime_content_len_client_hello_ext:MBEDTLS_SSL_VERSION_TLS1_3:64:MBEDTLS_SSL_MAX_FRAG_LEN_NONE:MBEDTLS_TLS_EXT_RECORD_SIZE_LIMIT:0:64 + +Runtime content length: ClientHello record_size_limit below the RFC 8449 minimum is refused +depends_on:MBEDTLS_SSL_PROTO_TLS1_3:MBEDTLS_SSL_RECORD_SIZE_LIMIT +ssl_runtime_content_len_client_hello_ext:MBEDTLS_SSL_VERSION_TLS1_3:63:MBEDTLS_SSL_MAX_FRAG_LEN_NONE:MBEDTLS_TLS_EXT_RECORD_SIZE_LIMIT:MBEDTLS_ERR_SSL_BAD_INPUT_DATA:-1 + +Runtime content length: max record payload reports the connection's lengths, TLS 1.2 +depends_on:MBEDTLS_SSL_PROTO_TLS1_2 +ssl_runtime_content_len_max_payload:MBEDTLS_SSL_VERSION_TLS1_2:2048:1024:MBEDTLS_SSL_MAX_FRAG_LEN_NONE:1024:2048:1024 + +Runtime content length: max record payload with a fragment length below both lengths, TLS 1.2 +depends_on:MBEDTLS_SSL_PROTO_TLS1_2:MBEDTLS_SSL_MAX_FRAGMENT_LENGTH +ssl_runtime_content_len_max_payload:MBEDTLS_SSL_VERSION_TLS1_2:2048:1024:MBEDTLS_SSL_MAX_FRAG_LEN_512:512:512:512 + +Runtime content length: max record payload with a fragment length at the incoming length, TLS 1.2 +depends_on:MBEDTLS_SSL_PROTO_TLS1_2:MBEDTLS_SSL_MAX_FRAGMENT_LENGTH +ssl_runtime_content_len_max_payload:MBEDTLS_SSL_VERSION_TLS1_2:2048:4096:MBEDTLS_SSL_MAX_FRAG_LEN_2048:2048:2048:2048 + +Runtime content length: max record payload reports the connection's lengths, TLS 1.3 +depends_on:MBEDTLS_SSL_PROTO_TLS1_3 +ssl_runtime_content_len_max_payload:MBEDTLS_SSL_VERSION_TLS1_3:2048:1024:MBEDTLS_SSL_MAX_FRAG_LEN_NONE:1024:2048:1023 diff --git a/tests/suites/test_suite_ssl.function b/tests/suites/test_suite_ssl.function index 8a463c5472..e608c73442 100644 --- a/tests/suites/test_suite_ssl.function +++ b/tests/suites/test_suite_ssl.function @@ -3825,6 +3825,567 @@ void resize_buffers_renegotiate_mfl(int mfl, int legacy_renegotiation, } /* END_CASE */ +/* BEGIN_CASE depends_on:MBEDTLS_SSL_RUNTIME_CONTENT_LEN:MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED:PSA_WANT_ECC_SECP_R1_384:PSA_WANT_ALG_SHA_256 */ +void ssl_runtime_content_len_default_is_noop(int dtls) +{ + /* MBEDTLS_SSL_RUNTIME_CONTENT_LEN promises that enabling it, without + * calling either setter, changes nothing. Prove it at the two places the + * option could leak: the lengths a fresh connection starts with, and the + * buffers that setting one up allocates. Then run a handshake and an + * application-data exchange over those buffers, and reset the connection, + * so that a length recorded wrongly shows up as a failure rather than as + * a number nobody reads. */ + mbedtls_test_ssl_endpoint client; + memset(&client, 0, sizeof(client)); + mbedtls_test_ssl_endpoint server; + memset(&server, 0, sizeof(server)); + mbedtls_test_handshake_test_options options; + mbedtls_test_init_handshake_options(&options); + mbedtls_ssl_context fresh; + mbedtls_ssl_init(&fresh); + + MD_OR_USE_PSA_INIT(); + + /* An initialised connection starts at the build-time maxima. */ + TEST_EQUAL(fresh.in_content_len, MBEDTLS_SSL_IN_CONTENT_LEN); + TEST_EQUAL(fresh.out_content_len, MBEDTLS_SSL_OUT_CONTENT_LEN); + + options.pk_alg = MBEDTLS_PK_ECDSA; + options.dtls = dtls; + if (dtls) { + options.expected_negotiated_version = MBEDTLS_SSL_VERSION_TLS1_2; + } + + TEST_EQUAL(mbedtls_test_ssl_endpoint_init(&client, MBEDTLS_SSL_IS_CLIENT, + &options), 0); + TEST_EQUAL(mbedtls_test_ssl_endpoint_init(&server, MBEDTLS_SSL_IS_SERVER, + &options), 0); + + /* Setting up a connection that asked for nothing allocates exactly what + * it allocated before the option existed. */ + TEST_EQUAL(client.ssl.in_buf_len, MBEDTLS_SSL_IN_BUFFER_LEN); + TEST_EQUAL(client.ssl.out_buf_len, MBEDTLS_SSL_OUT_BUFFER_LEN); + TEST_EQUAL(server.ssl.in_buf_len, MBEDTLS_SSL_IN_BUFFER_LEN); + TEST_EQUAL(server.ssl.out_buf_len, MBEDTLS_SSL_OUT_BUFFER_LEN); + + if (dtls) { + TEST_EQUAL(mbedtls_test_ssl_dtls_join_endpoints(&client, &server), 0); + } + + /* A full handshake plus an application-data exchange over those buffers. + * The handshake is where the automatic-resize path, when it is compiled + * in, would grow the buffers; either way they must land back on the same + * lengths as a connection that never asked for anything. */ + TEST_ASSERT(mbedtls_test_ssl_perform_connection(&options, &client, &server)); + + TEST_EQUAL(client.ssl.in_buf_len, MBEDTLS_SSL_IN_BUFFER_LEN); + TEST_EQUAL(client.ssl.out_buf_len, MBEDTLS_SSL_OUT_BUFFER_LEN); + TEST_EQUAL(server.ssl.in_buf_len, MBEDTLS_SSL_IN_BUFFER_LEN); + TEST_EQUAL(server.ssl.out_buf_len, MBEDTLS_SSL_OUT_BUFFER_LEN); + + /* Reset uses the recorded lengths to rebase the buffers; a disagreement + * between what was allocated and what reset believes was allocated shows + * up here. */ + TEST_EQUAL(mbedtls_ssl_session_reset(&client.ssl), 0); + TEST_EQUAL(client.ssl.in_buf_len, MBEDTLS_SSL_IN_BUFFER_LEN); + TEST_EQUAL(client.ssl.out_buf_len, MBEDTLS_SSL_OUT_BUFFER_LEN); + TEST_EQUAL(client.ssl.in_content_len, MBEDTLS_SSL_IN_CONTENT_LEN); + TEST_EQUAL(client.ssl.out_content_len, MBEDTLS_SSL_OUT_CONTENT_LEN); + +exit: + mbedtls_ssl_free(&fresh); + mbedtls_test_ssl_endpoint_free(&client); + mbedtls_test_ssl_endpoint_free(&server); + mbedtls_test_free_handshake_options(&options); + MD_OR_USE_PSA_DONE(); +} +/* END_CASE */ + +/* BEGIN_CASE depends_on:MBEDTLS_SSL_RUNTIME_CONTENT_LEN:MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED:PSA_WANT_ECC_SECP_R1_384:PSA_WANT_ALG_SHA_256 */ +void ssl_runtime_content_len_set(int outgoing, int after_setup, int len, + int expected_ret) +{ + /* Ask for a content length either in the window the setters allow, + * between configuring the connection and setting it up, or once the + * connection is set up (after_setup 1), or once it is set up and then + * recycled with mbedtls_ssl_session_reset() (after_setup 2), and check + * the verdict. A reset does not reopen the window. */ + mbedtls_test_ssl_endpoint ep; + memset(&ep, 0, sizeof(ep)); + mbedtls_test_handshake_test_options options; + mbedtls_test_init_handshake_options(&options); + + MD_OR_USE_PSA_INIT(); + + options.pk_alg = MBEDTLS_PK_ECDSA; + TEST_EQUAL(mbedtls_test_ssl_endpoint_init_conf(&ep, MBEDTLS_SSL_IS_CLIENT, + &options), 0); + if (after_setup) { + TEST_EQUAL(mbedtls_test_ssl_endpoint_init_ssl(&ep, &options), 0); + } + if (after_setup == 2) { + TEST_EQUAL(mbedtls_ssl_session_reset(&ep.ssl), 0); + } + + if (outgoing) { + TEST_EQUAL(mbedtls_ssl_set_out_content_len(&ep.ssl, len), expected_ret); + } else { + TEST_EQUAL(mbedtls_ssl_set_in_content_len(&ep.ssl, len), expected_ret); + } + +exit: + mbedtls_test_ssl_endpoint_free(&ep); + mbedtls_test_free_handshake_options(&options); + MD_OR_USE_PSA_DONE(); +} +/* END_CASE */ + +/* BEGIN_CASE depends_on:MBEDTLS_SSL_RUNTIME_CONTENT_LEN:MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED:PSA_WANT_ECC_SECP_R1_384:PSA_WANT_ALG_SHA_256 */ +void ssl_runtime_content_len_handshake(int version, int dtls, + int cli_in, int cli_out, + int srv_in, int srv_out, + int cli_msg_len, int expected_cli_fragments, + int srv_msg_len, int expected_srv_fragments, + int reset) +{ + /* Give each peer its own content lengths (0 leaves one at the build-time + * maximum), run a full handshake, then exchange application data. The + * number of records each message is split into is what shows the lengths + * took effect: a sender splits at its own outgoing length, and a message + * that arrives in one record fit the receiver's incoming length. + * + * With reset set (stream transport only), recycle both connections with + * mbedtls_ssl_session_reset() over fresh sockets and do it all again: a + * recycled connection keeps the lengths it was set up with. */ + mbedtls_test_ssl_endpoint client; + memset(&client, 0, sizeof(client)); + mbedtls_test_ssl_endpoint server; + memset(&server, 0, sizeof(server)); + mbedtls_test_handshake_test_options options; + mbedtls_test_init_handshake_options(&options); + + MD_OR_USE_PSA_INIT(); + + options.pk_alg = MBEDTLS_PK_ECDSA; + options.dtls = dtls; + options.client_min_version = version; + options.client_max_version = version; + options.expected_negotiated_version = version; + options.cli_msg_len = cli_msg_len; + options.expected_cli_fragments = expected_cli_fragments; + options.srv_msg_len = srv_msg_len; + options.expected_srv_fragments = expected_srv_fragments; + TEST_EQUAL(mbedtls_test_ssl_endpoint_init_conf(&client, MBEDTLS_SSL_IS_CLIENT, + &options), 0); + TEST_EQUAL(mbedtls_test_ssl_endpoint_init_conf(&server, MBEDTLS_SSL_IS_SERVER, + &options), 0); + + if (cli_in != 0) { + TEST_EQUAL(mbedtls_ssl_set_in_content_len(&client.ssl, cli_in), 0); + } + if (cli_out != 0) { + TEST_EQUAL(mbedtls_ssl_set_out_content_len(&client.ssl, cli_out), 0); + } + if (srv_in != 0) { + TEST_EQUAL(mbedtls_ssl_set_in_content_len(&server.ssl, srv_in), 0); + } + if (srv_out != 0) { + TEST_EQUAL(mbedtls_ssl_set_out_content_len(&server.ssl, srv_out), 0); + } + + TEST_EQUAL(mbedtls_test_ssl_endpoint_init_ssl(&client, &options), 0); + TEST_EQUAL(mbedtls_test_ssl_endpoint_init_ssl(&server, &options), 0); + + if (dtls) { + TEST_EQUAL(mbedtls_test_ssl_dtls_join_endpoints(&client, &server), 0); + } + + TEST_ASSERT(mbedtls_test_ssl_perform_connection(&options, &client, &server)); + + if (reset) { + mbedtls_test_mock_socket_close(&client.socket); + mbedtls_test_mock_socket_close(&server.socket); + TEST_EQUAL(mbedtls_ssl_session_reset(&client.ssl), 0); + TEST_EQUAL(mbedtls_ssl_session_reset(&server.ssl), 0); + + TEST_ASSERT(mbedtls_test_ssl_perform_connection(&options, &client, &server)); + } + +exit: + mbedtls_test_ssl_endpoint_free(&client); + mbedtls_test_ssl_endpoint_free(&server); + mbedtls_test_free_handshake_options(&options); + MD_OR_USE_PSA_DONE(); +} +/* END_CASE */ + +/* BEGIN_CASE depends_on:MBEDTLS_SSL_RUNTIME_CONTENT_LEN:MBEDTLS_SSL_VARIABLE_BUFFER_LENGTH:MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED:MBEDTLS_SSL_PROTO_TLS1_2:PSA_WANT_ECC_SECP_R1_384:PSA_WANT_ALG_SHA_256 */ +void ssl_runtime_content_len_resize_buffers(int dtls, int mfl, int renegotiate, + int cli_in, int cli_out, + int srv_in, int srv_out) +{ + /* With automatic buffer resizing as well, give each peer its own content + * lengths (0 leaves one at the build-time maximum) and run a handshake, + * optionally with a negotiated fragment length and a renegotiation, then + * recycle both connections with mbedtls_ssl_session_reset(). + * + * Every handshake starts by growing the buffers back to full size, and + * full size is this connection's own length: growing to the build-time + * maximum would undo the saving. mbedtls_test_ssl_perform_connection() + * checks the buffers at each resize, including in the middle of the + * renegotiation; this test checks them after setup and after reset. */ + mbedtls_test_ssl_endpoint client; + memset(&client, 0, sizeof(client)); + mbedtls_test_ssl_endpoint server; + memset(&server, 0, sizeof(server)); + mbedtls_test_handshake_test_options options; + mbedtls_test_init_handshake_options(&options); + /* A buffer is its content length plus a fixed overhead for the header, + * the crypto expansion and any connection ID. */ + const size_t cli_in_buf_len = (cli_in ? cli_in : MBEDTLS_SSL_IN_CONTENT_LEN) + + MBEDTLS_SSL_IN_BUFFER_LEN - MBEDTLS_SSL_IN_CONTENT_LEN; + const size_t cli_out_buf_len = (cli_out ? cli_out : MBEDTLS_SSL_OUT_CONTENT_LEN) + + MBEDTLS_SSL_OUT_BUFFER_LEN - MBEDTLS_SSL_OUT_CONTENT_LEN; + const size_t srv_in_buf_len = (srv_in ? srv_in : MBEDTLS_SSL_IN_CONTENT_LEN) + + MBEDTLS_SSL_IN_BUFFER_LEN - MBEDTLS_SSL_IN_CONTENT_LEN; + const size_t srv_out_buf_len = (srv_out ? srv_out : MBEDTLS_SSL_OUT_CONTENT_LEN) + + MBEDTLS_SSL_OUT_BUFFER_LEN - MBEDTLS_SSL_OUT_CONTENT_LEN; + + MD_OR_USE_PSA_INIT(); + + options.pk_alg = MBEDTLS_PK_ECDSA; + options.dtls = dtls; + options.client_min_version = MBEDTLS_SSL_VERSION_TLS1_2; + options.client_max_version = MBEDTLS_SSL_VERSION_TLS1_2; + options.expected_negotiated_version = MBEDTLS_SSL_VERSION_TLS1_2; + options.mfl = mfl; + options.renegotiate = renegotiate; + options.legacy_renegotiation = MBEDTLS_SSL_LEGACY_NO_RENEGOTIATION; + options.resize_buffers = 1; + + TEST_EQUAL(mbedtls_test_ssl_endpoint_init_conf(&client, MBEDTLS_SSL_IS_CLIENT, + &options), 0); + TEST_EQUAL(mbedtls_test_ssl_endpoint_init_conf(&server, MBEDTLS_SSL_IS_SERVER, + &options), 0); + + if (cli_in != 0) { + TEST_EQUAL(mbedtls_ssl_set_in_content_len(&client.ssl, cli_in), 0); + } + if (cli_out != 0) { + TEST_EQUAL(mbedtls_ssl_set_out_content_len(&client.ssl, cli_out), 0); + } + if (srv_in != 0) { + TEST_EQUAL(mbedtls_ssl_set_in_content_len(&server.ssl, srv_in), 0); + } + if (srv_out != 0) { + TEST_EQUAL(mbedtls_ssl_set_out_content_len(&server.ssl, srv_out), 0); + } + + TEST_EQUAL(mbedtls_test_ssl_endpoint_init_ssl(&client, &options), 0); + TEST_EQUAL(mbedtls_test_ssl_endpoint_init_ssl(&server, &options), 0); + + /* Setup allocates from the connection's lengths, then runs the grow step + * of the first handshake, which must leave them alone. */ + TEST_EQUAL(client.ssl.in_buf_len, cli_in_buf_len); + TEST_EQUAL(client.ssl.out_buf_len, cli_out_buf_len); + TEST_EQUAL(server.ssl.in_buf_len, srv_in_buf_len); + TEST_EQUAL(server.ssl.out_buf_len, srv_out_buf_len); + + if (dtls) { + TEST_EQUAL(mbedtls_test_ssl_dtls_join_endpoints(&client, &server), 0); + } + + TEST_ASSERT(mbedtls_test_ssl_perform_connection(&options, &client, &server)); + + /* Resizing after the handshake may have shrunk the buffers further, to a + * negotiated fragment length, but never grown them. */ + TEST_LE_U(client.ssl.in_buf_len, cli_in_buf_len); + TEST_LE_U(client.ssl.out_buf_len, cli_out_buf_len); + TEST_LE_U(server.ssl.in_buf_len, srv_in_buf_len); + TEST_LE_U(server.ssl.out_buf_len, srv_out_buf_len); + + /* A reset prepares the next handshake, growing the buffers back to the + * connection's lengths and no further. */ + TEST_EQUAL(mbedtls_ssl_session_reset(&client.ssl), 0); + TEST_EQUAL(mbedtls_ssl_session_reset(&server.ssl), 0); + TEST_EQUAL(client.ssl.in_buf_len, cli_in_buf_len); + TEST_EQUAL(client.ssl.out_buf_len, cli_out_buf_len); + TEST_EQUAL(server.ssl.in_buf_len, srv_in_buf_len); + TEST_EQUAL(server.ssl.out_buf_len, srv_out_buf_len); + +exit: + mbedtls_test_ssl_endpoint_free(&client); + mbedtls_test_ssl_endpoint_free(&server); + mbedtls_test_free_handshake_options(&options); + MD_OR_USE_PSA_DONE(); +} +/* END_CASE */ + +/* BEGIN_CASE depends_on:MBEDTLS_SSL_RUNTIME_CONTENT_LEN:MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED:PSA_WANT_ECC_SECP_R1_384:PSA_WANT_ALG_SHA_256 */ +void ssl_runtime_content_len_oversized_record(int version, int srv_in, + int msg_len, int expected_ret) +{ + /* Shrink only the server's incoming length, so that nothing on the + * wire tells the client (the TLS 1.3 cases therefore exclude + * MBEDTLS_SSL_RECORD_SIZE_LIMIT, which would), and have the full-size + * client send one record of msg_len bytes after the handshake. The + * server's verdict on that record is what shows its incoming length took + * effect. */ + enum { BUFFSIZE = 17000 }; + mbedtls_test_ssl_endpoint client; + memset(&client, 0, sizeof(client)); + mbedtls_test_ssl_endpoint server; + memset(&server, 0, sizeof(server)); + mbedtls_test_handshake_test_options options; + mbedtls_test_init_handshake_options(&options); + unsigned char *msg = NULL; + unsigned char *received = NULL; + + MD_OR_USE_PSA_INIT(); + + TEST_CALLOC(msg, msg_len); + TEST_CALLOC(received, msg_len); + memset(msg, 'x', msg_len); + + options.pk_alg = MBEDTLS_PK_ECDSA; + options.client_min_version = version; + options.client_max_version = version; + options.expected_negotiated_version = version; + + TEST_EQUAL(mbedtls_test_ssl_endpoint_init(&client, MBEDTLS_SSL_IS_CLIENT, + &options), 0); + TEST_EQUAL(mbedtls_test_ssl_endpoint_init_conf(&server, MBEDTLS_SSL_IS_SERVER, + &options), 0); + TEST_EQUAL(mbedtls_ssl_set_in_content_len(&server.ssl, srv_in), 0); + TEST_EQUAL(mbedtls_test_ssl_endpoint_init_ssl(&server, &options), 0); + + TEST_EQUAL(mbedtls_test_mock_socket_connect(&client.socket, &server.socket, + BUFFSIZE), 0); + TEST_EQUAL(mbedtls_test_move_handshake_to_state(&client.ssl, &server.ssl, + MBEDTLS_SSL_HANDSHAKE_OVER), + 0); + TEST_EQUAL(mbedtls_test_move_handshake_to_state(&server.ssl, &client.ssl, + MBEDTLS_SSL_HANDSHAKE_OVER), + 0); + + TEST_EQUAL(mbedtls_ssl_write(&client.ssl, msg, msg_len), msg_len); + + if (expected_ret == 0) { + TEST_EQUAL(mbedtls_ssl_read(&server.ssl, received, msg_len), msg_len); + TEST_MEMORY_COMPARE(received, msg_len, msg, msg_len); + } else { + TEST_EQUAL(mbedtls_ssl_read(&server.ssl, received, msg_len), + expected_ret); + } + +exit: + mbedtls_free(msg); + mbedtls_free(received); + mbedtls_test_ssl_endpoint_free(&client); + mbedtls_test_ssl_endpoint_free(&server); + mbedtls_test_free_handshake_options(&options); + MD_OR_USE_PSA_DONE(); +} +/* END_CASE */ + +/* BEGIN_CASE depends_on:MBEDTLS_SSL_RUNTIME_CONTENT_LEN:MBEDTLS_SSL_MAX_FRAGMENT_LENGTH:MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED:PSA_WANT_ECC_SECP_R1_384:PSA_WANT_ALG_SHA_256 */ +void ssl_runtime_content_len_setup_mfl(int endpoint, int min_version, + int in_len, int mfl, int expected_ret) +{ + /* A client whose configuration negotiates a fragment length above the + * connection's incoming length would invite records it cannot hold, so + * mbedtls_ssl_setup() refuses it. A client that only offers TLS 1.3 never + * sends a fragment length, and a server's configured fragment length only + * caps what it sends, so neither is a contradiction. */ + mbedtls_test_ssl_endpoint ep; + memset(&ep, 0, sizeof(ep)); + mbedtls_test_handshake_test_options options; + mbedtls_test_init_handshake_options(&options); + + MD_OR_USE_PSA_INIT(); + + options.pk_alg = MBEDTLS_PK_ECDSA; + options.client_min_version = min_version; + options.server_min_version = min_version; + options.mfl = mfl; + TEST_EQUAL(mbedtls_test_ssl_endpoint_init_conf(&ep, endpoint, &options), 0); + TEST_EQUAL(mbedtls_ssl_set_in_content_len(&ep.ssl, in_len), 0); + + TEST_EQUAL(mbedtls_ssl_setup(&ep.ssl, &ep.conf), expected_ret); + + if (expected_ret != 0) { + /* A refused connection has no buffers, and cannot be recycled the way + * mbedtls_test_ssl_endpoint_free() recycles a set-up one. */ + mbedtls_ssl_free(&ep.ssl); + mbedtls_ssl_init(&ep.ssl); + } + +exit: + mbedtls_test_ssl_endpoint_free(&ep); + mbedtls_test_free_handshake_options(&options); + MD_OR_USE_PSA_DONE(); +} +/* END_CASE */ + +/* BEGIN_CASE depends_on:MBEDTLS_SSL_RUNTIME_CONTENT_LEN:MBEDTLS_SSL_CLI_C:MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED:PSA_WANT_ECC_SECP_R1_384:PSA_WANT_ALG_SHA_256 */ +void ssl_runtime_content_len_client_hello_ext(int version, int cli_in, int mfl, + int ext_type, int expected_ret, + int expected_value) +{ + /* Set the client's incoming length, have it send its ClientHello, and + * read the extension of type ext_type off the wire: its value (a + * max_fragment_length code or a record_size_limit) is what the client + * invites its peer to send. expected_value -1 means the extension must + * be absent. expected_ret is the handshake's verdict before anything is + * sent; 0 means the ClientHello went out and the client waits for the + * server. */ + enum { BUFFSIZE = 17000 }; + mbedtls_test_ssl_endpoint client; + memset(&client, 0, sizeof(client)); + mbedtls_test_mock_socket peer; + mbedtls_test_mock_socket_init(&peer); + mbedtls_test_handshake_test_options options; + mbedtls_test_init_handshake_options(&options); + unsigned char buf[BUFFSIZE]; + size_t len = 0; + size_t off = 0; + size_t end = 0; + int ext_value = -1; + int ret; + + MD_OR_USE_PSA_INIT(); + + options.pk_alg = MBEDTLS_PK_ECDSA; + options.client_min_version = version; + options.client_max_version = version; + options.mfl = mfl; + + TEST_EQUAL(mbedtls_test_ssl_endpoint_init_conf(&client, MBEDTLS_SSL_IS_CLIENT, + &options), 0); + TEST_EQUAL(mbedtls_ssl_set_in_content_len(&client.ssl, cli_in), 0); + TEST_EQUAL(mbedtls_test_ssl_endpoint_init_ssl(&client, &options), 0); + TEST_EQUAL(mbedtls_test_mock_socket_connect(&client.socket, &peer, + BUFFSIZE), 0); + + if (expected_ret != 0) { + TEST_EQUAL(mbedtls_ssl_handshake(&client.ssl), expected_ret); + TEST_EQUAL(mbedtls_test_mock_tcp_recv_nb(&peer, buf, sizeof(buf)), + MBEDTLS_ERR_SSL_WANT_READ); + goto exit; + } + + TEST_EQUAL(mbedtls_ssl_handshake(&client.ssl), MBEDTLS_ERR_SSL_WANT_READ); + ret = mbedtls_test_mock_tcp_recv_nb(&peer, buf, sizeof(buf)); + TEST_LE_S(1, ret); + len = (size_t) ret; + + /* Record header (5), then handshake header (4): one ClientHello record. */ + TEST_LE_U(9, len); + TEST_EQUAL(buf[0], MBEDTLS_SSL_MSG_HANDSHAKE); + TEST_EQUAL(MBEDTLS_GET_UINT16_BE(buf, 3), len - 5); + TEST_EQUAL(buf[5], MBEDTLS_SSL_HS_CLIENT_HELLO); + /* legacy_version (2) and random (32) */ + off = 9 + 2 + 32; + /* legacy_session_id */ + TEST_LE_U(off + 1, len); + off += 1 + buf[off]; + /* cipher_suites */ + TEST_LE_U(off + 2, len); + off += 2 + MBEDTLS_GET_UINT16_BE(buf, off); + /* legacy_compression_methods */ + TEST_LE_U(off + 1, len); + off += 1 + buf[off]; + /* extensions */ + TEST_LE_U(off + 2, len); + end = off + 2 + MBEDTLS_GET_UINT16_BE(buf, off); + TEST_EQUAL(end, len); + off += 2; + while (off < end) { + size_t ext_len; + TEST_LE_U(off + 4, end); + ext_len = MBEDTLS_GET_UINT16_BE(buf, off + 2); + TEST_LE_U(off + 4 + ext_len, end); + if (MBEDTLS_GET_UINT16_BE(buf, off) == ext_type) { + TEST_EQUAL(ext_value, -1); + TEST_ASSERT(ext_len == 1 || ext_len == 2); + ext_value = ext_len == 1 ? buf[off + 4] : MBEDTLS_GET_UINT16_BE(buf, off + 4); + } + off += 4 + ext_len; + } + + TEST_EQUAL(ext_value, expected_value); + +exit: + mbedtls_test_ssl_endpoint_free(&client); + mbedtls_test_mock_socket_close(&peer); + mbedtls_test_free_handshake_options(&options); + MD_OR_USE_PSA_DONE(); +} +/* END_CASE */ + +/* BEGIN_CASE depends_on:MBEDTLS_SSL_RUNTIME_CONTENT_LEN:MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED:PSA_WANT_ECC_SECP_R1_384:PSA_WANT_ALG_SHA_256 */ +void ssl_runtime_content_len_max_payload(int version, int cli_in, int cli_out, + int mfl, int pre_handshake_out, + int expected_in, int expected_out) +{ + /* Give the client its own lengths (and optionally a fragment length to + * negotiate) and check what the public maximum-record-payload queries + * report on it, once set up and again once the handshake is over. The + * server sends no more than the client can take in, so that the + * handshake itself does not depend on negotiating anything. */ + enum { BUFFSIZE = 17000 }; + mbedtls_test_ssl_endpoint client; + memset(&client, 0, sizeof(client)); + mbedtls_test_ssl_endpoint server; + memset(&server, 0, sizeof(server)); + mbedtls_test_handshake_test_options options; + mbedtls_test_init_handshake_options(&options); + + MD_OR_USE_PSA_INIT(); + + options.pk_alg = MBEDTLS_PK_ECDSA; + options.client_min_version = version; + options.client_max_version = version; + options.expected_negotiated_version = version; + options.mfl = mfl; + + TEST_EQUAL(mbedtls_test_ssl_endpoint_init_conf(&client, MBEDTLS_SSL_IS_CLIENT, + &options), 0); + TEST_EQUAL(mbedtls_ssl_set_in_content_len(&client.ssl, cli_in), 0); + TEST_EQUAL(mbedtls_ssl_set_out_content_len(&client.ssl, cli_out), 0); + TEST_EQUAL(mbedtls_test_ssl_endpoint_init_ssl(&client, &options), 0); + TEST_EQUAL(mbedtls_test_ssl_endpoint_init_conf(&server, MBEDTLS_SSL_IS_SERVER, + &options), 0); + TEST_EQUAL(mbedtls_ssl_set_out_content_len(&server.ssl, cli_in), 0); + TEST_EQUAL(mbedtls_test_ssl_endpoint_init_ssl(&server, &options), 0); + + /* Before the handshake nothing is negotiated: the incoming length is the + * client's own, and the outgoing one is capped only by what the client + * itself configured. */ + TEST_EQUAL(mbedtls_ssl_get_max_in_record_payload(&client.ssl), cli_in); + TEST_EQUAL(mbedtls_ssl_get_max_out_record_payload(&client.ssl), + pre_handshake_out); + + TEST_EQUAL(mbedtls_test_mock_socket_connect(&client.socket, &server.socket, + BUFFSIZE), 0); + TEST_EQUAL(mbedtls_test_move_handshake_to_state(&client.ssl, &server.ssl, + MBEDTLS_SSL_HANDSHAKE_OVER), + 0); + TEST_EQUAL(mbedtls_test_move_handshake_to_state(&server.ssl, &client.ssl, + MBEDTLS_SSL_HANDSHAKE_OVER), + 0); + + TEST_EQUAL(mbedtls_ssl_get_max_in_record_payload(&client.ssl), expected_in); + TEST_EQUAL(mbedtls_ssl_get_max_out_record_payload(&client.ssl), expected_out); + +exit: + mbedtls_test_ssl_endpoint_free(&client); + mbedtls_test_ssl_endpoint_free(&server); + mbedtls_test_free_handshake_options(&options); + MD_OR_USE_PSA_DONE(); +} +/* END_CASE */ + /* BEGIN_CASE depends_on:MBEDTLS_SSL_HANDSHAKE_WITH_PSK_ENABLED */ void test_multiple_psks() {