diff --git a/tests/suites/test_suite_ssl.data b/tests/suites/test_suite_ssl.data index 446cabbea0..82d2e3c37d 100644 --- a/tests/suites/test_suite_ssl.data +++ b/tests/suites/test_suite_ssl.data @@ -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 diff --git a/tests/suites/test_suite_ssl.function b/tests/suites/test_suite_ssl.function index 172d70fc73..4236c978f9 100644 --- a/tests/suites/test_suite_ssl.function +++ b/tests/suites/test_suite_ssl.function @@ -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,