From 1cea7506e63e660ed881a1c93c076b4cba64b79f Mon Sep 17 00:00:00 2001 From: Pieter De Gendt Date: Tue, 8 Sep 2026 14:10:21 +0200 Subject: [PATCH 1/2] Fix NULL dereference in DTLS keying material export On DTLS, the endpoint that sent the last flight of the handshake (the server in a full handshake) defers its handshake wrapup until the retransmission window closes, so ssl->transform is not installed yet right after the handshake completes. mbedtls_ssl_tls12_export_keying_material() dereferenced ssl->transform unconditionally and crashed in that state. This broke DTLS-SRTP (RFC 5764) style usage, where keying material is exported immediately after the handshake and possibly no application data ever flows to trigger the wrapup. Fall back to ssl->transform_in, the negotiated transform, which is always installed by the time the handshake is over; the exporter only runs after mbedtls_ssl_is_handshake_over(). Assisted-by: Claude:claude-fable-5 Signed-off-by: Pieter De Gendt --- ChangeLog.d/fix-dtls-exporter-before-wrapup.txt | 7 +++++++ library/ssl_tls.c | 10 ++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 ChangeLog.d/fix-dtls-exporter-before-wrapup.txt diff --git a/ChangeLog.d/fix-dtls-exporter-before-wrapup.txt b/ChangeLog.d/fix-dtls-exporter-before-wrapup.txt new file mode 100644 index 0000000000..9356092512 --- /dev/null +++ b/ChangeLog.d/fix-dtls-exporter-before-wrapup.txt @@ -0,0 +1,7 @@ +Bugfix + * Fix a NULL pointer dereference in mbedtls_ssl_export_keying_material() + when called on a DTLS 1.2 endpoint that sent the last flight of the + handshake (typically the server), after the handshake has completed but + before the retransmission window has closed. This notably affected + DTLS-SRTP (RFC 5764) style usage, where keying material is exported + immediately after the handshake. diff --git a/library/ssl_tls.c b/library/ssl_tls.c index 6a15d8bace..fe54741cf6 100644 --- a/library/ssl_tls.c +++ b/library/ssl_tls.c @@ -8948,11 +8948,17 @@ static int mbedtls_ssl_tls12_export_keying_material(const mbedtls_ssl_context *s return MBEDTLS_ERR_SSL_ALLOC_FAILED; } + /* On DTLS, the endpoint that sent the last flight of the handshake + * keeps ssl->transform unset until its handshake wrapup runs at the end + * of the retransmission window. The negotiated transform is already + * installed for reading at that point, so fall back to it. */ + const mbedtls_ssl_transform *transform = + ssl->transform != NULL ? ssl->transform : ssl->transform_in; memcpy(prf_input, - ssl->transform->randbytes + MBEDTLS_SERVER_HELLO_RANDOM_LEN, + transform->randbytes + MBEDTLS_SERVER_HELLO_RANDOM_LEN, MBEDTLS_CLIENT_HELLO_RANDOM_LEN); memcpy(prf_input + MBEDTLS_CLIENT_HELLO_RANDOM_LEN, - ssl->transform->randbytes, + transform->randbytes, MBEDTLS_SERVER_HELLO_RANDOM_LEN); if (use_context) { MBEDTLS_PUT_UINT16_BE(context_len, prf_input, randbytes_len); From bdfe103cc75e0c332467e17a9b85945be01cb900 Mon Sep 17 00:00:00 2001 From: Pieter De Gendt Date: Tue, 8 Sep 2026 14:04:47 +0200 Subject: [PATCH 2/2] Add regression test for DTLS exporter use before handshake wrapup Complete a DTLS 1.2 handshake between two in-process endpoints and export keying material from the server without exchanging application data, so the export runs while the server-side handshake wrapup is still pending and ssl->transform is not installed yet. mbedtls_ssl_export_keying_material() used to crash on a NULL ssl->transform in this state, which DTLS-SRTP (RFC 5764) style usage hits routinely. Assisted-by: Claude:claude-fable-5 Signed-off-by: Pieter De Gendt --- tests/suites/test_suite_ssl.data | 4 ++ tests/suites/test_suite_ssl.function | 64 ++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/tests/suites/test_suite_ssl.data b/tests/suites/test_suite_ssl.data index a2703e1027..881008bd0b 100644 --- a/tests/suites/test_suite_ssl.data +++ b/tests/suites/test_suite_ssl.data @@ -3546,6 +3546,10 @@ TLS 1.2 Keying Material Exporter: Handshake not done depends_on:MBEDTLS_SSL_PROTO_TLS1_2:MBEDTLS_CAN_HANDLE_RSA_TEST_KEY ssl_tls_exporter_too_early:MBEDTLS_SSL_VERSION_TLS1_2:1:MBEDTLS_SSL_SERVER_CERTIFICATE +DTLS 1.2 Keying Material Exporter: Export before server-side wrapup +depends_on:MBEDTLS_SSL_PROTO_TLS1_2:MBEDTLS_CAN_HANDLE_RSA_TEST_KEY +ssl_dtls_exporter_before_wrapup + TLS 1.3 Keying Material Exporter: Consistent results, no context depends_on:MBEDTLS_SSL_PROTO_TLS1_3:MBEDTLS_TEST_AT_LEAST_ONE_TLS1_3_CIPHERSUITE:MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_EPHEMERAL_ENABLED:PSA_WANT_ALG_RSA_PKCS1V15_SIGN:MBEDTLS_X509_RSASSA_PSS_SUPPORT ssl_tls_exporter_consistent_result:MBEDTLS_SSL_VERSION_TLS1_3:24:0 diff --git a/tests/suites/test_suite_ssl.function b/tests/suites/test_suite_ssl.function index 8a463c5472..700fef99f9 100644 --- a/tests/suites/test_suite_ssl.function +++ b/tests/suites/test_suite_ssl.function @@ -6529,6 +6529,70 @@ exit: } /* END_CASE */ +/* BEGIN_CASE depends_on:MBEDTLS_SSL_KEYING_MATERIAL_EXPORT:MBEDTLS_SSL_PROTO_DTLS:MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED:PSA_WANT_ECC_SECP_R1_384:PSA_WANT_ALG_SHA_256 */ +void ssl_dtls_exporter_before_wrapup() +{ + /* On DTLS the sender of the last handshake flight (the server in a full + * handshake) defers its handshake wrapup until the retransmission window + * closes. Exporting keying material in that state must work, as + * DTLS-SRTP (RFC 5764) does with possibly no application data ever + * flowing; regression test for a NULL dereference of ssl->transform. */ + + int ret = -1; + unsigned char key_server[48]; + unsigned char key_client[48]; + char label[] = "test-label"; + mbedtls_test_ssl_endpoint client_ep, server_ep; + memset(&client_ep, 0, sizeof(client_ep)); + memset(&server_ep, 0, sizeof(server_ep)); + mbedtls_test_handshake_test_options options; + + MD_OR_USE_PSA_INIT(); + + mbedtls_test_init_handshake_options(&options); + options.dtls = 1; + + ret = mbedtls_test_ssl_endpoint_init(&client_ep, MBEDTLS_SSL_IS_CLIENT, &options); + TEST_EQUAL(ret, 0); + ret = mbedtls_test_ssl_endpoint_init(&server_ep, MBEDTLS_SSL_IS_SERVER, &options); + TEST_EQUAL(ret, 0); + ret = mbedtls_test_ssl_dtls_join_endpoints(&client_ep, &server_ep); + TEST_EQUAL(ret, 0); + ret = mbedtls_test_mock_socket_connect(&client_ep.socket, &server_ep.socket, 17000); + TEST_EQUAL(ret, 0); + + ret = mbedtls_test_move_handshake_to_state(&client_ep.ssl, &server_ep.ssl, + MBEDTLS_SSL_HANDSHAKE_OVER); + TEST_EQUAL(ret, 0); + ret = mbedtls_test_move_handshake_to_state(&server_ep.ssl, &client_ep.ssl, + MBEDTLS_SSL_HANDSHAKE_OVER); + TEST_ASSERT(ret == 0 || + ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE); + TEST_ASSERT(mbedtls_ssl_is_handshake_over(&client_ep.ssl)); + TEST_ASSERT(mbedtls_ssl_is_handshake_over(&server_ep.ssl)); + + /* No application data is exchanged: the server-side wrapup is still + * pending at this point. */ + ret = mbedtls_ssl_export_keying_material(&server_ep.ssl, + key_server, sizeof(key_server), + label, sizeof(label), + NULL, 0, 0); + TEST_EQUAL(ret, 0); + ret = mbedtls_ssl_export_keying_material(&client_ep.ssl, + key_client, sizeof(key_client), + label, sizeof(label), + NULL, 0, 0); + TEST_EQUAL(ret, 0); + TEST_EQUAL(memcmp(key_server, key_client, sizeof(key_server)), 0); + +exit: + mbedtls_test_ssl_endpoint_free(&server_ep); + mbedtls_test_ssl_endpoint_free(&client_ep); + mbedtls_test_free_handshake_options(&options); + MD_OR_USE_PSA_DONE(); +} +/* END_CASE */ + /* BEGIN_CASE depends_on:MBEDTLS_SSL_KEYING_MATERIAL_EXPORT:MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED:PSA_WANT_ECC_SECP_R1_384:PSA_WANT_ALG_SHA_256 */ void ssl_tls_exporter_uses_label(int proto) {