mirror of
https://github.com/Mbed-TLS/mbedtls.git
synced 2026-10-23 00:57:56 +00:00
Add regression tests for ssl_context_load length checks
Backport the ssl_context_load_rejects_oob_length test function from development in its final form, with cases for the serialized in_cid, out_cid and ALPN lengths. mbedtls_test_ssl_dtls_join_endpoints() and mbedtls_test_ssl_perform_connection() do not exist on this branch, so set up the DTLS endpoints and run the handshake with the helpers that do: mbedtls_test_ssl_endpoint_init() with message queues, mbedtls_test_mock_socket_connect() and mbedtls_test_move_handshake_to_state(). Exchange application data explicitly before saving the context, as mbedtls_test_ssl_perform_connection() does on development: the server keeps its handshake structure until then, and mbedtls_ssl_context_save() refuses to run while it exists. Signed-off-by: Naveed <[email protected]>
This commit is contained in:
@@ -3211,6 +3211,18 @@ TLS 1.3: Session serialization rejects ALPN without terminator
|
||||
depends_on:MBEDTLS_SSL_PROTO_TLS1_3:MBEDTLS_SSL_SESSION_TICKETS:MBEDTLS_SSL_SRV_C:MBEDTLS_SSL_EARLY_DATA:MBEDTLS_SSL_ALPN
|
||||
ssl_session_load_rejects_bad_serialized_data:MBEDTLS_SSL_VERSION_TLS1_3:MBEDTLS_SSL_IS_SERVER:TEST_TLS13_SESSION_LOAD_ALPN_NO_NUL
|
||||
|
||||
SSL context serialization rejects out-of-bounds DTLS in_cid length
|
||||
depends_on:MBEDTLS_SSL_DTLS_CONNECTION_ID
|
||||
ssl_context_load_rejects_oob_length:TEST_SSL_CONTEXT_LOAD_OOB_IN_CID
|
||||
|
||||
SSL context serialization rejects out-of-bounds DTLS out_cid length
|
||||
depends_on:MBEDTLS_SSL_DTLS_CONNECTION_ID
|
||||
ssl_context_load_rejects_oob_length:TEST_SSL_CONTEXT_LOAD_OOB_OUT_CID
|
||||
|
||||
SSL context serialization rejects out-of-bounds ALPN length
|
||||
depends_on:MBEDTLS_SSL_ALPN
|
||||
ssl_context_load_rejects_oob_length:TEST_SSL_CONTEXT_LOAD_OOB_ALPN
|
||||
|
||||
TLS 1.2: Session serialization rejects out-of-bounds session ID length (client)
|
||||
depends_on:MBEDTLS_SSL_PROTO_TLS1_2:MBEDTLS_SSL_CLI_C
|
||||
ssl_session_load_rejects_bad_serialized_data:MBEDTLS_SSL_VERSION_TLS1_2:MBEDTLS_SSL_IS_CLIENT:TEST_TLS12_SESSION_LOAD_OOB_ID_LEN
|
||||
|
||||
@@ -29,6 +29,11 @@
|
||||
#define TEST_TLS13_SESSION_LOAD_ALPN_NO_NUL 2
|
||||
#define TEST_TLS12_SESSION_LOAD_OOB_ID_LEN 3
|
||||
|
||||
/* Corrupted length fields for ssl_context_load_rejects_oob_length */
|
||||
#define TEST_SSL_CONTEXT_LOAD_OOB_IN_CID 0
|
||||
#define TEST_SSL_CONTEXT_LOAD_OOB_OUT_CID 1
|
||||
#define TEST_SSL_CONTEXT_LOAD_OOB_ALPN 2
|
||||
|
||||
#if defined(MBEDTLS_SSL_PROTO_TLS1_2) && \
|
||||
defined(MBEDTLS_SSL_SESSION_TICKETS) && \
|
||||
defined(MBEDTLS_SSL_CLI_C) && defined(MBEDTLS_SSL_SRV_C) && \
|
||||
@@ -3010,6 +3015,263 @@ exit:
|
||||
}
|
||||
/* END_CASE */
|
||||
|
||||
/* BEGIN_CASE depends_on:MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED:MBEDTLS_PKCS1_V15:MBEDTLS_SSL_PROTO_TLS1_2:MBEDTLS_RSA_C:MBEDTLS_ECP_HAVE_SECP384R1:MBEDTLS_SSL_PROTO_DTLS:MBEDTLS_SSL_CONTEXT_SERIALIZATION:MBEDTLS_MD_CAN_SHA256:MBEDTLS_CAN_HANDLE_RSA_TEST_KEY */
|
||||
void ssl_context_load_rejects_oob_length(int field)
|
||||
{
|
||||
#if defined(MBEDTLS_SSL_DTLS_CONNECTION_ID)
|
||||
/* Sentinel CIDs so we can find their location inside the serialized
|
||||
* buffer. The server's own CID is serialized as in_cid and the client's
|
||||
* as out_cid; they have different lengths so that the two fields can be
|
||||
* told apart. */
|
||||
const unsigned char server_cid[] = {
|
||||
0xAB, 0xCD, 0xEF, 0x12, 0x34, 0x56, 0x78, 0x9A
|
||||
};
|
||||
const unsigned char client_cid[] = {
|
||||
0x9A, 0x78, 0x56, 0x34, 0x12, 0xEF,
|
||||
0xCD, 0xAB, 0x10, 0x32, 0x54, 0x76
|
||||
};
|
||||
#endif
|
||||
#if defined(MBEDTLS_SSL_ALPN)
|
||||
/* The negotiated protocol (alpn[0]) is the last field in the serialized
|
||||
* context, so exactly strlen(alpn[0]) bytes remain after its length byte.
|
||||
* On load, ssl_context_load() only memcmp()s configured protocols whose
|
||||
* strlen() equals the length byte, so the corrupted length has to match a
|
||||
* configured protocol to reach the memcmp(): alpn[1] is one character
|
||||
* longer than alpn[0], which makes the corrupted length point exactly one
|
||||
* byte past the end of the serialized data. It shares alpn[0]'s prefix so
|
||||
* that all in-bounds bytes match and the memcmp() cannot stop before
|
||||
* reading past the end of the buffer. */
|
||||
const char negotiated[] = "alpnsentinel";
|
||||
const char *alpn[] = { negotiated, "alpnsentinel2", NULL };
|
||||
#endif
|
||||
|
||||
enum { BUFFSIZE = 17000 };
|
||||
mbedtls_test_handshake_test_options options;
|
||||
mbedtls_test_ssl_endpoint client_ep, server_ep;
|
||||
mbedtls_test_ssl_message_queue server_queue, client_queue;
|
||||
mbedtls_test_message_socket_context server_context, client_context;
|
||||
#if defined(MBEDTLS_TIMING_C)
|
||||
mbedtls_timing_delay_context timer_client, timer_server;
|
||||
#endif
|
||||
unsigned char *context_buf = NULL;
|
||||
unsigned char *load_buf = NULL;
|
||||
size_t context_buf_len, load_len, field_off, i;
|
||||
const unsigned char *sentinel = NULL;
|
||||
size_t sentinel_len = 0;
|
||||
unsigned char corrupt_len = 0;
|
||||
unsigned char *len_p = NULL;
|
||||
|
||||
memset(&client_ep, 0, sizeof(client_ep));
|
||||
memset(&server_ep, 0, sizeof(server_ep));
|
||||
mbedtls_test_message_socket_init(&server_context);
|
||||
mbedtls_test_message_socket_init(&client_context);
|
||||
|
||||
MD_OR_USE_PSA_INIT();
|
||||
mbedtls_test_init_handshake_options(&options);
|
||||
|
||||
options.dtls = 1;
|
||||
options.expected_negotiated_version = MBEDTLS_SSL_VERSION_TLS1_2;
|
||||
|
||||
TEST_EQUAL(mbedtls_test_ssl_endpoint_init(
|
||||
&client_ep, MBEDTLS_SSL_IS_CLIENT, &options,
|
||||
&client_context, &client_queue, &server_queue), 0);
|
||||
TEST_EQUAL(mbedtls_test_ssl_endpoint_init(
|
||||
&server_ep, MBEDTLS_SSL_IS_SERVER, &options,
|
||||
&server_context, &server_queue, &client_queue), 0);
|
||||
#if defined(MBEDTLS_TIMING_C)
|
||||
mbedtls_ssl_set_timer_cb(&client_ep.ssl, &timer_client,
|
||||
mbedtls_timing_set_delay,
|
||||
mbedtls_timing_get_delay);
|
||||
mbedtls_ssl_set_timer_cb(&server_ep.ssl, &timer_server,
|
||||
mbedtls_timing_set_delay,
|
||||
mbedtls_timing_get_delay);
|
||||
#endif
|
||||
|
||||
switch (field) {
|
||||
#if defined(MBEDTLS_SSL_DTLS_CONNECTION_ID)
|
||||
case TEST_SSL_CONTEXT_LOAD_OOB_IN_CID:
|
||||
case TEST_SSL_CONTEXT_LOAD_OOB_OUT_CID:
|
||||
/* Each peer's own CID has to fit in MBEDTLS_SSL_CID_IN_LEN_MAX and
|
||||
* its peer's CID in MBEDTLS_SSL_CID_OUT_LEN_MAX. */
|
||||
TEST_ASSUME(MBEDTLS_SSL_CID_IN_LEN_MAX >= sizeof(server_cid) &&
|
||||
MBEDTLS_SSL_CID_IN_LEN_MAX >= sizeof(client_cid));
|
||||
TEST_ASSUME(MBEDTLS_SSL_CID_OUT_LEN_MAX >= sizeof(server_cid) &&
|
||||
MBEDTLS_SSL_CID_OUT_LEN_MAX >= sizeof(client_cid));
|
||||
/* Configure CID with unique patterns so we can locate the length
|
||||
* bytes in the serialized context. On each peer the length passed
|
||||
* to conf_cid() must match the one passed to set_cid(). */
|
||||
TEST_EQUAL(mbedtls_ssl_conf_cid(
|
||||
&client_ep.conf, sizeof(client_cid),
|
||||
MBEDTLS_SSL_UNEXPECTED_CID_FAIL), 0);
|
||||
TEST_EQUAL(mbedtls_ssl_conf_cid(
|
||||
&server_ep.conf, sizeof(server_cid),
|
||||
MBEDTLS_SSL_UNEXPECTED_CID_FAIL), 0);
|
||||
TEST_EQUAL(mbedtls_ssl_set_cid(
|
||||
&client_ep.ssl, MBEDTLS_SSL_CID_ENABLED,
|
||||
client_cid, sizeof(client_cid)), 0);
|
||||
TEST_EQUAL(mbedtls_ssl_set_cid(
|
||||
&server_ep.ssl, MBEDTLS_SSL_CID_ENABLED,
|
||||
server_cid, sizeof(server_cid)), 0);
|
||||
/* The corrupted length is the smallest one that overflows the
|
||||
* in_cid or out_cid array in the SSL transform, so the size check
|
||||
* on that array, not the in-buffer bounds check before it, is
|
||||
* what must reject it. The loaded data gets the field grown to
|
||||
* this many bytes (see the construction of load_buf below) so
|
||||
* that the in-buffer bounds check indeed passes. A one-byte
|
||||
* serialized length cannot overflow a 255-byte CID array. */
|
||||
if (field == TEST_SSL_CONTEXT_LOAD_OOB_IN_CID) {
|
||||
TEST_ASSUME(MBEDTLS_SSL_CID_IN_LEN_MAX < 255);
|
||||
sentinel = server_cid;
|
||||
sentinel_len = sizeof(server_cid);
|
||||
corrupt_len = (unsigned char) (MBEDTLS_SSL_CID_IN_LEN_MAX + 1);
|
||||
} else {
|
||||
TEST_ASSUME(MBEDTLS_SSL_CID_OUT_LEN_MAX < 255);
|
||||
sentinel = client_cid;
|
||||
sentinel_len = sizeof(client_cid);
|
||||
corrupt_len = (unsigned char) (MBEDTLS_SSL_CID_OUT_LEN_MAX + 1);
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
#if defined(MBEDTLS_SSL_ALPN)
|
||||
case TEST_SSL_CONTEXT_LOAD_OOB_ALPN:
|
||||
/* Offer the same protocol list on both peers so alpn[0] is
|
||||
* negotiated and serialized into the context. */
|
||||
TEST_EQUAL(mbedtls_ssl_conf_alpn_protocols(&client_ep.conf, alpn),
|
||||
0);
|
||||
TEST_EQUAL(mbedtls_ssl_conf_alpn_protocols(&server_ep.conf, alpn),
|
||||
0);
|
||||
sentinel = (const unsigned char *) negotiated;
|
||||
sentinel_len = strlen(negotiated);
|
||||
/* One byte past the end of the serialized data; see the
|
||||
* declaration of alpn[] above. */
|
||||
corrupt_len = (unsigned char) strlen(alpn[1]);
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
TEST_ASSERT(0);
|
||||
}
|
||||
|
||||
TEST_EQUAL(mbedtls_test_mock_socket_connect(&client_ep.socket,
|
||||
&server_ep.socket,
|
||||
BUFFSIZE), 0);
|
||||
|
||||
TEST_EQUAL(mbedtls_test_move_handshake_to_state(
|
||||
&client_ep.ssl, &server_ep.ssl,
|
||||
MBEDTLS_SSL_HANDSHAKE_OVER), 0);
|
||||
/* Make sure server state is moved to HANDSHAKE_OVER also. */
|
||||
TEST_EQUAL(mbedtls_test_move_handshake_to_state(
|
||||
&server_ep.ssl, &client_ep.ssl,
|
||||
MBEDTLS_SSL_HANDSHAKE_OVER), 0);
|
||||
|
||||
/* The server keeps its handshake structure around, in case it has to
|
||||
* resend its last flight, until it receives application data, and
|
||||
* mbedtls_ssl_context_save() refuses to run while that structure
|
||||
* exists. */
|
||||
TEST_EQUAL(mbedtls_test_ssl_exchange_data(
|
||||
&client_ep.ssl, options.cli_msg_len,
|
||||
options.expected_cli_fragments,
|
||||
&server_ep.ssl, options.srv_msg_len,
|
||||
options.expected_srv_fragments), 0);
|
||||
|
||||
#if defined(MBEDTLS_SSL_ALPN)
|
||||
if (field == TEST_SSL_CONTEXT_LOAD_OOB_ALPN) {
|
||||
TEST_ASSERT(server_ep.ssl.alpn_chosen != NULL);
|
||||
TEST_EQUAL(strcmp(server_ep.ssl.alpn_chosen, negotiated), 0);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Serialize the post-handshake server context. */
|
||||
TEST_EQUAL(mbedtls_ssl_context_save(&server_ep.ssl, NULL, 0,
|
||||
&context_buf_len),
|
||||
MBEDTLS_ERR_SSL_BUFFER_TOO_SMALL);
|
||||
TEST_CALLOC(context_buf, context_buf_len);
|
||||
TEST_EQUAL(mbedtls_ssl_context_save(&server_ep.ssl, context_buf,
|
||||
context_buf_len, &context_buf_len), 0);
|
||||
|
||||
/* Double-check that the serialized data loads cleanly before it gets
|
||||
* corrupted. */
|
||||
mbedtls_ssl_free(&server_ep.ssl);
|
||||
mbedtls_ssl_init(&server_ep.ssl);
|
||||
TEST_EQUAL(mbedtls_ssl_setup(&server_ep.ssl, &server_ep.conf), 0);
|
||||
TEST_EQUAL(mbedtls_ssl_context_load(&server_ep.ssl, context_buf,
|
||||
context_buf_len), 0);
|
||||
|
||||
/* Both fields are serialized as [length byte][data bytes]; locate the
|
||||
* length byte by searching for the sentinel data. */
|
||||
for (i = 1; i + sentinel_len <= context_buf_len; i++) {
|
||||
if (context_buf[i - 1] == (unsigned char) sentinel_len &&
|
||||
memcmp(context_buf + i, sentinel, sentinel_len) == 0) {
|
||||
len_p = context_buf + i - 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
TEST_ASSERT(len_p != NULL);
|
||||
|
||||
field_off = (size_t) (len_p + 1 - context_buf);
|
||||
|
||||
if (field == TEST_SSL_CONTEXT_LOAD_OOB_IN_CID ||
|
||||
field == TEST_SSL_CONTEXT_LOAD_OOB_OUT_CID) {
|
||||
/* The CID fields sit in the middle of the serialized data, so a
|
||||
* corrupted length cannot reach past the end of the buffer. Instead
|
||||
* grow the field itself: keep its bytes, pad it to corrupt_len bytes
|
||||
* and append the untouched rest of the serialized data, so that the
|
||||
* in-buffer bounds check passes and the size check on the CID array
|
||||
* is reached. */
|
||||
load_len = context_buf_len + corrupt_len - sentinel_len;
|
||||
TEST_CALLOC(load_buf, load_len);
|
||||
memcpy(load_buf, context_buf, field_off + sentinel_len);
|
||||
memset(load_buf + field_off + sentinel_len, 0xA5,
|
||||
(size_t) corrupt_len - sentinel_len);
|
||||
memcpy(load_buf + field_off + corrupt_len,
|
||||
context_buf + field_off + sentinel_len,
|
||||
context_buf_len - field_off - sentinel_len);
|
||||
/* Double-check that corrupt_len bytes of field data plus the next
|
||||
* byte are present, which is the most the in-buffer bounds checks
|
||||
* require (the in_cid one also accounts for the out_cid length
|
||||
* byte). */
|
||||
TEST_ASSERT(load_len - field_off >= (size_t) corrupt_len + 1);
|
||||
} else {
|
||||
/* The ALPN field is the last one saved, so the buffer already ends
|
||||
* right after it and the corrupted length reaches exactly one byte
|
||||
* past the end. Load from a tightly allocated copy, so that reading
|
||||
* even one byte past the end of the data overruns the allocation and
|
||||
* gets caught by ASan or Valgrind. */
|
||||
TEST_EQUAL((size_t) corrupt_len, context_buf_len - field_off + 1);
|
||||
load_len = context_buf_len;
|
||||
TEST_CALLOC(load_buf, load_len);
|
||||
memcpy(load_buf, context_buf, load_len);
|
||||
}
|
||||
|
||||
load_buf[field_off - 1] = corrupt_len;
|
||||
|
||||
/* Reinitialise the server SSL context so we can load into a fresh one. */
|
||||
mbedtls_ssl_free(&server_ep.ssl);
|
||||
mbedtls_ssl_init(&server_ep.ssl);
|
||||
TEST_EQUAL(mbedtls_ssl_setup(&server_ep.ssl, &server_ep.conf), 0);
|
||||
|
||||
/* How a regression is caught differs between the two cases. In the ALPN
|
||||
* case, without the bounds check the load still fails, because the
|
||||
* out-of-bounds bytes are unlikely to match a configured protocol; what
|
||||
* the missing check leaks is the out-of-bounds read in memcmp() itself,
|
||||
* so that case catches a regression only when the test suite runs under
|
||||
* ASan or Valgrind. In the CID cases, without the size check the
|
||||
* memcpy() overflows the in_cid or out_cid array inside the SSL
|
||||
* transform (which stays within the enclosing struct, invisible to
|
||||
* ASan), the rest of the data then parses correctly aligned and the
|
||||
* load succeeds, so the check below catches a regression on its own. */
|
||||
TEST_EQUAL(mbedtls_ssl_context_load(&server_ep.ssl, load_buf, load_len),
|
||||
MBEDTLS_ERR_SSL_BAD_INPUT_DATA);
|
||||
|
||||
exit:
|
||||
mbedtls_test_ssl_endpoint_free(&client_ep, &client_context);
|
||||
mbedtls_test_ssl_endpoint_free(&server_ep, &server_context);
|
||||
mbedtls_test_free_handshake_options(&options);
|
||||
mbedtls_free(context_buf);
|
||||
mbedtls_free(load_buf);
|
||||
MD_OR_USE_PSA_DONE();
|
||||
}
|
||||
/* END_CASE */
|
||||
|
||||
/* BEGIN_CASE */
|
||||
void ssl_session_serialize_version_check(int corrupt_major,
|
||||
int corrupt_minor,
|
||||
|
||||
Reference in New Issue
Block a user