From 91d7da23a811ca9739763f623dc694d441b7e763 Mon Sep 17 00:00:00 2001 From: Ronald Cron Date: Tue, 21 Apr 2026 09:26:07 +0200 Subject: [PATCH 01/15] Remove unnecessary dependency Signed-off-by: Ronald Cron --- tests/suites/test_suite_ssl.function | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/suites/test_suite_ssl.function b/tests/suites/test_suite_ssl.function index 39317cf3f9..8d8da35c09 100644 --- a/tests/suites/test_suite_ssl.function +++ b/tests/suites/test_suite_ssl.function @@ -2870,7 +2870,7 @@ exit: } /* END_CASE */ -/* BEGIN_CASE depends_on:MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED:PSA_HAVE_ALG_SOME_RSA_SIGN:PSA_WANT_ECC_SECP_R1_384:!MBEDTLS_SSL_PROTO_TLS1_3:PSA_WANT_ALG_SHA_256 */ +/* BEGIN_CASE depends_on:MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED:PSA_HAVE_ALG_SOME_RSA_SIGN:PSA_WANT_ECC_SECP_R1_384:PSA_WANT_ALG_SHA_256 */ void mbedtls_endpoint_sanity(int endpoint_type) { enum { BUFFSIZE = 1024 }; @@ -3106,7 +3106,7 @@ void app_data_tls(int mfl, int cli_msg_len, int srv_msg_len, } /* END_CASE */ -/* BEGIN_CASE depends_on:MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED:!MBEDTLS_SSL_PROTO_TLS1_3:MBEDTLS_SSL_PROTO_TLS1_2:PSA_HAVE_ALG_SOME_RSA_SIGN:PSA_WANT_ECC_SECP_R1_384:MBEDTLS_SSL_PROTO_DTLS:PSA_WANT_ALG_SHA_256:MBEDTLS_CAN_HANDLE_RSA_TEST_KEY */ +/* BEGIN_CASE depends_on:MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED:MBEDTLS_SSL_PROTO_TLS1_2:PSA_HAVE_ALG_SOME_RSA_SIGN:PSA_WANT_ECC_SECP_R1_384:MBEDTLS_SSL_PROTO_DTLS:PSA_WANT_ALG_SHA_256:MBEDTLS_CAN_HANDLE_RSA_TEST_KEY */ void app_data_dtls(int mfl, int cli_msg_len, int srv_msg_len, int expected_cli_fragments, int expected_srv_fragments) @@ -3135,7 +3135,7 @@ exit: } /* END_CASE */ -/* BEGIN_CASE depends_on:MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED:!MBEDTLS_SSL_PROTO_TLS1_3:PSA_HAVE_ALG_SOME_RSA_SIGN:PSA_WANT_KEY_TYPE_AES:PSA_WANT_ECC_SECP_R1_384:MBEDTLS_DEBUG_C:MBEDTLS_SSL_MAX_FRAGMENT_LENGTH:PSA_WANT_ALG_CBC_NO_PADDING:PSA_WANT_ALG_SHA_384:MBEDTLS_KEY_EXCHANGE_ECDHE_RSA_ENABLED */ +/* BEGIN_CASE depends_on:MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED:PSA_HAVE_ALG_SOME_RSA_SIGN:PSA_WANT_KEY_TYPE_AES:PSA_WANT_ECC_SECP_R1_384:MBEDTLS_DEBUG_C:MBEDTLS_SSL_MAX_FRAGMENT_LENGTH:PSA_WANT_ALG_CBC_NO_PADDING:PSA_WANT_ALG_SHA_384:MBEDTLS_KEY_EXCHANGE_ECDHE_RSA_ENABLED */ void handshake_fragmentation(int mfl, int expected_srv_hs_fragmentation, int expected_cli_hs_fragmentation) @@ -3332,7 +3332,7 @@ exit: } /* END_CASE */ -/* BEGIN_CASE depends_on:MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED:!MBEDTLS_SSL_PROTO_TLS1_3:MBEDTLS_SSL_PROTO_TLS1_2:PSA_HAVE_ALG_SOME_RSA_SIGN:PSA_WANT_ECC_SECP_R1_384:MBEDTLS_SSL_PROTO_DTLS:MBEDTLS_SSL_RENEGOTIATION:PSA_WANT_ALG_SHA_256:MBEDTLS_CAN_HANDLE_RSA_TEST_KEY */ +/* BEGIN_CASE depends_on:MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED:MBEDTLS_SSL_PROTO_TLS1_2:PSA_HAVE_ALG_SOME_RSA_SIGN:PSA_WANT_ECC_SECP_R1_384:MBEDTLS_SSL_PROTO_DTLS:MBEDTLS_SSL_RENEGOTIATION:PSA_WANT_ALG_SHA_256:MBEDTLS_CAN_HANDLE_RSA_TEST_KEY */ void renegotiation(int legacy_renegotiation) { mbedtls_test_handshake_test_options options; From 9fe8059464f42b3b0eb20f2161b0d8f09d366956 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Fri, 5 Jun 2026 15:40:03 +0200 Subject: [PATCH 02/15] Add a missing dependency Needed by a test certificate. Signed-off-by: Gilles Peskine --- tests/suites/test_suite_ssl.function | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/suites/test_suite_ssl.function b/tests/suites/test_suite_ssl.function index 8d8da35c09..97a70f997a 100644 --- a/tests/suites/test_suite_ssl.function +++ b/tests/suites/test_suite_ssl.function @@ -3135,7 +3135,7 @@ exit: } /* END_CASE */ -/* BEGIN_CASE depends_on:MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED:PSA_HAVE_ALG_SOME_RSA_SIGN:PSA_WANT_KEY_TYPE_AES:PSA_WANT_ECC_SECP_R1_384:MBEDTLS_DEBUG_C:MBEDTLS_SSL_MAX_FRAGMENT_LENGTH:PSA_WANT_ALG_CBC_NO_PADDING:PSA_WANT_ALG_SHA_384:MBEDTLS_KEY_EXCHANGE_ECDHE_RSA_ENABLED */ +/* BEGIN_CASE depends_on:MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED:PSA_HAVE_ALG_SOME_RSA_SIGN:PSA_WANT_KEY_TYPE_AES:PSA_WANT_ECC_SECP_R1_384:MBEDTLS_DEBUG_C:MBEDTLS_SSL_MAX_FRAGMENT_LENGTH:PSA_WANT_ALG_CBC_NO_PADDING:PSA_WANT_ALG_SHA_256:PSA_WANT_ALG_SHA_384:MBEDTLS_KEY_EXCHANGE_ECDHE_RSA_ENABLED */ void handshake_fragmentation(int mfl, int expected_srv_hs_fragmentation, int expected_cli_hs_fragmentation) From 025422b2e96f02e07ec4c2bf6846cea3eef26d44 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Wed, 3 Jun 2026 15:43:18 +0200 Subject: [PATCH 03/15] Add some hooks in mbedtls_test_ssl_perform_handshake() Let test code customize the kitchen sink function `mbedtls_test_ssl_perform_handshake()` at a few points. Signed-off-by: Gilles Peskine --- tests/include/test/ssl_helpers.h | 32 ++++++++++++++++++++++++++++ tests/src/test_helpers/ssl_helpers.c | 17 +++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/tests/include/test/ssl_helpers.h b/tests/include/test/ssl_helpers.h index a653c9643e..467a27f342 100644 --- a/tests/include/test/ssl_helpers.h +++ b/tests/include/test/ssl_helpers.h @@ -117,6 +117,9 @@ enum { #define MBEDTLS_TEST_MAX_ALPN_LIST_SIZE 10 #endif +/* Forward declaration. Defined below. */ +struct mbedtls_test_ssl_endpoint; + typedef struct mbedtls_test_ssl_log_pattern { const char *pattern; size_t counter; @@ -147,6 +150,35 @@ typedef struct mbedtls_test_handshake_test_options { int expected_srv_fragments; int renegotiate; int legacy_renegotiation; + /** Hook that mbedtls_test_ssl_perform_handshake() runs just before + * the initial handshake. */ + void (*pre_handshake_fun)(struct mbedtls_test_ssl_endpoint *client, + struct mbedtls_test_ssl_endpoint *server, + void *param); + /** Value passed to ::pre_handshake_fun. */ + void *pre_handshake_param; + /** Hook that mbedtls_test_ssl_perform_handshake() runs after + * the initial handshake succeeds. */ + void (*post_handshake_fun)(struct mbedtls_test_ssl_endpoint *client, + struct mbedtls_test_ssl_endpoint *server, + void *param); + /** Value passed to ::post_handshake_fun. */ + void *post_handshake_param; + /** Hook that mbedtls_test_ssl_perform_handshake() runs after + * exchanging some data, before testing additional features such as + * serialization and renegotiation. */ + void (*post_data_fun)(struct mbedtls_test_ssl_endpoint *client, + struct mbedtls_test_ssl_endpoint *server, + void *param); + /** Value passed to ::post_data_fun. */ + void *post_data_param; + /** Hook that mbedtls_test_ssl_perform_handshake() runs after a successful + * connection, just before closing down. */ + void (*pre_shutdown_fun)(struct mbedtls_test_ssl_endpoint *client, + struct mbedtls_test_ssl_endpoint *server, + void *param); + /** Value passed to ::pre_shutdown_fun. */ + void *pre_shutdown_param; void *srv_log_obj; void *cli_log_obj; void (*srv_log_fun)(void *, int, const char *, int, const char *); diff --git a/tests/src/test_helpers/ssl_helpers.c b/tests/src/test_helpers/ssl_helpers.c index db29413de6..08cfe252af 100644 --- a/tests/src/test_helpers/ssl_helpers.c +++ b/tests/src/test_helpers/ssl_helpers.c @@ -2282,6 +2282,10 @@ int mbedtls_test_ssl_perform_connection( expected_handshake_result = MBEDTLS_ERR_SSL_BAD_PROTOCOL_VERSION; } + if (options->pre_handshake_fun != NULL) { + options->pre_handshake_fun(client, server, options->pre_handshake_param); + } + TEST_EQUAL(mbedtls_test_move_handshake_to_state(&(client->ssl), &(server->ssl), MBEDTLS_SSL_HANDSHAKE_OVER), @@ -2313,6 +2317,10 @@ int mbedtls_test_ssl_perform_connection( options->expected_ciphersuite); } + if (options->post_handshake_fun != NULL) { + options->post_handshake_fun(client, server, options->post_handshake_param); + } + #if defined(MBEDTLS_SSL_VARIABLE_BUFFER_LENGTH) if (options->resize_buffers != 0) { /* A server, when using DTLS, might delay a buffer resize to happen @@ -2339,6 +2347,11 @@ int mbedtls_test_ssl_perform_connection( options->expected_srv_fragments), 0); } + + if (options->post_data_fun != NULL) { + options->post_data_fun(client, server, options->post_data_param); + } + #if defined(MBEDTLS_SSL_CONTEXT_SERIALIZATION) if (options->serialize == 1) { TEST_ASSERT(test_serialization(options, client, server)); @@ -2396,6 +2409,10 @@ void mbedtls_test_ssl_perform_handshake( TEST_ASSERT(mbedtls_ssl_conf_get_user_data_p(&server->conf) == server); TEST_ASSERT(mbedtls_ssl_get_user_data_p(&server->ssl) == server); + if (options->pre_shutdown_fun != NULL) { + options->pre_shutdown_fun(client, server, options->pre_shutdown_param); + } + exit: mbedtls_test_ssl_endpoint_free(client); mbedtls_test_ssl_endpoint_free(server); From 84f66de73f06aca1964afc193dac15cf00c30d91 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Wed, 3 Jun 2026 15:44:08 +0200 Subject: [PATCH 04/15] Test DTLS renegotiation when badmac_seen_or_in_hsfraglen != 0 Non-regression test for a bug introduced with TLS handshake defragmentation support in Mbed TLS 3.6.3, whereby the confusion between the `badmac_seen` meaning and the `in_hsfraglen` meaning of `ssl->badmac_seen_or_in_hsfraglen` causes renegotiation to be processed incorrectly after receiving a message with a bad MAC. The bug doesn't affect Mbed TLS 4.0.0 and above, but it's still good to have more testing. Signed-off-by: Gilles Peskine --- tests/suites/test_suite_ssl.data | 12 +++++++++--- tests/suites/test_suite_ssl.function | 28 +++++++++++++++++++++++++++- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/tests/suites/test_suite_ssl.data b/tests/suites/test_suite_ssl.data index 0c30c8987b..da975a5b0d 100644 --- a/tests/suites/test_suite_ssl.data +++ b/tests/suites/test_suite_ssl.data @@ -548,13 +548,19 @@ Sending app data via DTLS, without MFL and with fragmentation app_data_dtls:MBEDTLS_SSL_MAX_FRAG_LEN_NONE:16385:100000:0:0 DTLS renegotiation: no legacy renegotiation -renegotiation:MBEDTLS_SSL_LEGACY_NO_RENEGOTIATION +renegotiation:MBEDTLS_SSL_LEGACY_NO_RENEGOTIATION:-1 DTLS renegotiation: legacy renegotiation -renegotiation:MBEDTLS_SSL_LEGACY_ALLOW_RENEGOTIATION +renegotiation:MBEDTLS_SSL_LEGACY_ALLOW_RENEGOTIATION:-1 DTLS renegotiation: legacy break handshake -renegotiation:MBEDTLS_SSL_LEGACY_BREAK_HANDSHAKE +renegotiation:MBEDTLS_SSL_LEGACY_BREAK_HANDSHAKE:-1 + +DTLS renegotiation: after bad MAC on client +renegotiation:MBEDTLS_SSL_LEGACY_NO_RENEGOTIATION:MBEDTLS_SSL_IS_CLIENT + +DTLS renegotiation: after bad MAC on server +renegotiation:MBEDTLS_SSL_LEGACY_NO_RENEGOTIATION:MBEDTLS_SSL_IS_SERVER DTLS serialization with MFL=512 resize_buffers_serialize_mfl:MBEDTLS_SSL_MAX_FRAG_LEN_512 diff --git a/tests/suites/test_suite_ssl.function b/tests/suites/test_suite_ssl.function index 97a70f997a..f5492ff53c 100644 --- a/tests/suites/test_suite_ssl.function +++ b/tests/suites/test_suite_ssl.function @@ -109,6 +109,30 @@ static void resize_buffers(int mfl, int renegotiation, int legacy_renegotiation, #define TEST_GCM_OR_CHACHAPOLY_ENABLED #endif +#if defined(MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED) && \ + defined(MBEDTLS_SSL_PROTO_TLS1_2) && \ + defined(PSA_HAVE_ALG_SOME_RSA_SIGN) && \ + defined(PSA_WANT_ECC_SECP_R1_384) && \ + defined(MBEDTLS_SSL_PROTO_DTLS) && \ + defined(MBEDTLS_SSL_RENEGOTIATION) && \ + defined(PSA_WANT_ALG_SHA_256) && \ + defined(MBEDTLS_CAN_HANDLE_RSA_TEST_KEY) +static void simulate_badmac_seen_on(mbedtls_test_ssl_endpoint *client, + mbedtls_test_ssl_endpoint *server, + void *param) +{ + int on = *(int *) param; + switch (on) { + case MBEDTLS_SSL_IS_CLIENT: + client->ssl.badmac_seen = 1; + break; + case MBEDTLS_SSL_IS_SERVER: + server->ssl.badmac_seen = 1; + break; + } +} +#endif + typedef enum { RECOMBINE_NOMINAL, /* param: ignored */ RECOMBINE_SPLIT_FIRST, /* param: offset of split (<=0 means from end) */ @@ -3333,7 +3357,7 @@ exit: /* END_CASE */ /* BEGIN_CASE depends_on:MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED:MBEDTLS_SSL_PROTO_TLS1_2:PSA_HAVE_ALG_SOME_RSA_SIGN:PSA_WANT_ECC_SECP_R1_384:MBEDTLS_SSL_PROTO_DTLS:MBEDTLS_SSL_RENEGOTIATION:PSA_WANT_ALG_SHA_256:MBEDTLS_CAN_HANDLE_RSA_TEST_KEY */ -void renegotiation(int legacy_renegotiation) +void renegotiation(int legacy_renegotiation, int badmac_seen_on) { mbedtls_test_handshake_test_options options; mbedtls_test_init_handshake_options(&options); @@ -3342,6 +3366,8 @@ void renegotiation(int legacy_renegotiation) options.legacy_renegotiation = legacy_renegotiation; options.dtls = 1; options.expected_negotiated_version = MBEDTLS_SSL_VERSION_TLS1_2; + options.post_data_fun = &simulate_badmac_seen_on; + options.post_data_param = &badmac_seen_on; mbedtls_test_ssl_perform_handshake(&options); From 1509cc4509b27a53e7c9e07fc0c41164833a6064 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Thu, 28 May 2026 10:54:32 +0200 Subject: [PATCH 05/15] Add udp_proxy options to delay an encrypted handshake message This can be used to simulate data messages received in the wrong epoch, or to cause a handshake message to be buffered. Signed-off-by: Gilles Peskine --- programs/test/udp_proxy.c | 50 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/programs/test/udp_proxy.c b/programs/test/udp_proxy.c index eab15feb38..7712a5f908 100644 --- a/programs/test/udp_proxy.c +++ b/programs/test/udp_proxy.c @@ -103,6 +103,12 @@ int main(void) " May be used multiple times, even for the same\n" \ " message, in which case the respective message\n" \ " gets delayed multiple times.\n" \ + " delay_encrypted_hs_cli=%%d default: 0 (don't delay)\n" \ + " delay the Nth encrypted handshake message from\n" \ + " client to server.\n" \ + " delay_encrypted_hs_srv=%%d default: 0 (don't delay)\n" \ + " delay the Nth encrypted handshake message from\n" \ + " server to client.\n" \ " delay_srv=%%s Handshake message from server that should be\n" \ " delayed. Possible values are 'HelloRequest',\n" \ " 'ServerHello', 'ServerHelloDone', 'Certificate'\n" \ @@ -148,6 +154,8 @@ static struct options { char *delay_srv[MAX_DELAYED_HS]; /* handshake types of messages from * server that should be delayed. */ uint8_t delay_srv_cnt; /* Number of entries in delay_srv. */ + int delay_encrypted_hs_cli; /* Delay Nth encrypted HS from client. */ + int delay_encrypted_hs_srv; /* Delay Nth encrypted HS from server. */ int drop; /* drop 1 packet in N (none if 0) */ int mtu; /* drop packets larger than this */ int bad_ad; /* inject corrupted ApplicationData record */ @@ -219,6 +227,16 @@ static void get_options(int argc, char *argv[]) if (opt.delay_ccs < 0 || opt.delay_ccs > 1) { exit_usage(p, q); } + } else if (strcmp(p, "delay_encrypted_hs_cli") == 0) { + opt.delay_encrypted_hs_cli = atoi(q); + if (opt.delay_encrypted_hs_cli < 0) { + exit_usage(p, q); + } + } else if (strcmp(p, "delay_encrypted_hs_srv") == 0) { + opt.delay_encrypted_hs_srv = atoi(q); + if (opt.delay_encrypted_hs_srv < 0) { + exit_usage(p, q); + } } else if (strcmp(p, "delay_cli") == 0 || strcmp(p, "delay_srv") == 0) { uint8_t *delay_cnt; @@ -664,6 +682,31 @@ static int send_delayed(void) static unsigned char held[2048] = { 0 }; #define HOLD_MAX 2 +static unsigned encrypted_hs_cli_seen; +static unsigned encrypted_hs_srv_seen; + +static int delay_encrypted_hs(const packet *p) +{ + unsigned *seen; + int delay_at; + + if (strcmp(p->type, "Encrypted handshake") != 0) { + return 0; + } + + if (strcmp(p->way, "S <- C") == 0) { + seen = &encrypted_hs_cli_seen; + delay_at = opt.delay_encrypted_hs_cli; + } else { + seen = &encrypted_hs_srv_seen; + delay_at = opt.delay_encrypted_hs_srv; + } + + ++*seen; + + return delay_at > 0 && *seen == (unsigned) delay_at; +} + static int handle_message(const char *way, mbedtls_net_context *dst, mbedtls_net_context *src) @@ -717,6 +760,11 @@ static int handle_message(const char *way, } } + if (delay_encrypted_hs(&cur)) { + delay_packet(&cur); + return 0; + } + /* do we want to drop, delay, or forward it? */ if ((opt.mtu != 0 && cur.len > (unsigned) opt.mtu) || @@ -851,6 +899,8 @@ accept: */ clear_pending(); memset(held, 0, sizeof(held)); + encrypted_hs_cli_seen = 0; + encrypted_hs_srv_seen = 0; nb_fds = client_fd.fd; if (nb_fds < server_fd.fd) { From 1d354f2ae078d7394215d2b07b12a9354a3e1155 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Thu, 28 May 2026 11:04:52 +0200 Subject: [PATCH 06/15] Add one-shot bad AD options to udp_proxy Signed-off-by: Gilles Peskine --- programs/test/udp_proxy.c | 51 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/programs/test/udp_proxy.c b/programs/test/udp_proxy.c index 7712a5f908..04c2e3a7e7 100644 --- a/programs/test/udp_proxy.c +++ b/programs/test/udp_proxy.c @@ -122,6 +122,12 @@ int main(void) " mtu=%%d default: 0 (unlimited)\n" \ " drop packets larger than N bytes\n" \ " bad_ad=0/1 default: 0 (don't add bad ApplicationData)\n" \ + " bad_ad_cli_once=%%d default: 0 (don't add bad ApplicationData)\n" \ + " add bad ApplicationData before the Nth\n" \ + " ApplicationData from client to server.\n" \ + " bad_ad_srv_once=%%d default: 0 (don't add bad ApplicationData)\n" \ + " add bad ApplicationData before the Nth\n" \ + " ApplicationData from server to client.\n" \ " bad_cid=%%d default: 0 (don't corrupt Connection IDs)\n" \ " duplicate 1:N packets containing a CID,\n" \ " modifying CID in first instance of the packet.\n" \ @@ -159,6 +165,8 @@ static struct options { int drop; /* drop 1 packet in N (none if 0) */ int mtu; /* drop packets larger than this */ int bad_ad; /* inject corrupted ApplicationData record */ + int bad_ad_cli_once; /* Inject corrupted Nth AD from client. */ + int bad_ad_srv_once; /* Inject corrupted Nth AD from server. */ unsigned bad_cid; /* inject corrupted CID record */ int protect_hvr; /* never drop or delay HelloVerifyRequest */ int protect_len; /* never drop/delay packet of the given size*/ @@ -289,6 +297,16 @@ static void get_options(int argc, char *argv[]) if (opt.bad_ad < 0 || opt.bad_ad > 1) { exit_usage(p, q); } + } else if (strcmp(p, "bad_ad_cli_once") == 0) { + opt.bad_ad_cli_once = atoi(q); + if (opt.bad_ad_cli_once < 0) { + exit_usage(p, q); + } + } else if (strcmp(p, "bad_ad_srv_once") == 0) { + opt.bad_ad_srv_once = atoi(q); + if (opt.bad_ad_srv_once < 0) { + exit_usage(p, q); + } } #if defined(MBEDTLS_SSL_DTLS_CONNECTION_ID) else if (strcmp(p, "bad_cid") == 0) { @@ -546,6 +564,35 @@ typedef enum { static inject_clihlo_state_t inject_clihlo_state; static packet initial_clihlo; +static unsigned bad_ad_cli_seen; +static unsigned bad_ad_srv_seen; + +static int bad_ad_once(const packet *p) +{ + unsigned *seen; + int bad_ad_at; + + if (strcmp(p->type, "ApplicationData") != 0) { + return 0; + } + + if (strcmp(p->way, "S <- C") == 0) { + seen = &bad_ad_cli_seen; + bad_ad_at = opt.bad_ad_cli_once; + } else { + seen = &bad_ad_srv_seen; + bad_ad_at = opt.bad_ad_srv_once; + } + + if (bad_ad_at <= 0) { + return 0; + } + + ++*seen; + + return *seen == (unsigned) bad_ad_at; +} + static int send_packet(const packet *p, const char *why) { int ret; @@ -577,7 +624,7 @@ static int send_packet(const packet *p, const char *why) } /* insert corrupted ApplicationData record? */ - if (opt.bad_ad && + if ((bad_ad_once(p) || opt.bad_ad) && strcmp(p->type, "ApplicationData") == 0) { unsigned char buf[MAX_MSG_SIZE]; memcpy(buf, p->buf, p->len); @@ -901,6 +948,8 @@ accept: memset(held, 0, sizeof(held)); encrypted_hs_cli_seen = 0; encrypted_hs_srv_seen = 0; + bad_ad_cli_seen = 0; + bad_ad_srv_seen = 0; nb_fds = client_fd.fd; if (nb_fds < server_fd.fd) { From f99b4be7cd6870a7924f99acd5994b178782c24a Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Thu, 28 May 2026 14:03:49 +0200 Subject: [PATCH 07/15] Add badmac_limit option to ssl_client2 Signed-off-by: Gilles Peskine --- programs/ssl/ssl_client2.c | 15 ++++++++++++++- programs/ssl/ssl_server2.c | 7 ++----- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/programs/ssl/ssl_client2.c b/programs/ssl/ssl_client2.c index fc00473cfc..26ea63db9b 100644 --- a/programs/ssl/ssl_client2.c +++ b/programs/ssl/ssl_client2.c @@ -86,6 +86,7 @@ int main(void) #define DFL_HS_TO_MIN 0 #define DFL_HS_TO_MAX 0 #define DFL_DTLS_MTU -1 +#define DFL_BADMAC_LIMIT -1 #define DFL_DGRAM_PACKING 1 #define DFL_FALLBACK -1 #define DFL_EXTENDED_MS -1 @@ -269,7 +270,8 @@ int main(void) " mtu=%%d default: (library default: unlimited)\n" \ " dgram_packing=%%d default: 1 (allowed)\n" \ " allow or forbid packing of multiple\n" \ - " records within a single datgram.\n" + " records within a single datagram.\n" \ + " badmac_limit=%%d default: (library default: disabled)\n" #else #define USAGE_DTLS "" #endif @@ -518,6 +520,7 @@ struct options { int dtls_mtu; /* UDP Maximum transport unit for DTLS */ int fallback; /* is this a fallback connection? */ int dgram_packing; /* allow/forbid datagram packing */ + int badmac_limit; /* Limit of records with bad MAC */ int extended_ms; /* negotiate extended master secret? */ int etm; /* negotiate encrypt then mac? */ int context_crt_cb; /* use context-specific CRT verify callback */ @@ -959,6 +962,7 @@ int main(int argc, char *argv[]) opt.extended_ms = DFL_EXTENDED_MS; opt.etm = DFL_ETM; opt.dgram_packing = DFL_DGRAM_PACKING; + opt.badmac_limit = DFL_BADMAC_LIMIT; opt.serialize = DFL_SERIALIZE; opt.context_file = DFL_CONTEXT_FILE; opt.eap_tls = DFL_EAP_TLS; @@ -1372,6 +1376,11 @@ usage: opt.dgram_packing != 1) { goto usage; } + } else if (strcmp(p, "badmac_limit") == 0) { + opt.badmac_limit = atoi(q); + if (opt.badmac_limit < 0) { + goto usage; + } } else if (strcmp(p, "recsplit") == 0) { opt.recsplit = atoi(q); if (opt.recsplit < 0 || opt.recsplit > 1) { @@ -1835,6 +1844,10 @@ usage: if (opt.dgram_packing != DFL_DGRAM_PACKING) { mbedtls_ssl_set_datagram_packing(&ssl, opt.dgram_packing); } + + if (opt.badmac_limit != DFL_BADMAC_LIMIT) { + mbedtls_ssl_conf_dtls_badmac_limit(&conf, opt.badmac_limit); + } #endif /* MBEDTLS_SSL_PROTO_DTLS */ #if defined(MBEDTLS_SSL_MAX_FRAGMENT_LENGTH) diff --git a/programs/ssl/ssl_server2.c b/programs/ssl/ssl_server2.c index 79cbad877d..541c6a6b43 100644 --- a/programs/ssl/ssl_server2.c +++ b/programs/ssl/ssl_server2.c @@ -367,9 +367,6 @@ int main(void) #define USAGE_ANTI_REPLAY "" #endif -#define USAGE_BADMAC_LIMIT \ - " badmac_limit=%%d default: (library default: disabled)\n" - #if defined(MBEDTLS_SSL_PROTO_DTLS) #define USAGE_DTLS \ " dtls=%%d default: 0 (TLS)\n" \ @@ -378,7 +375,8 @@ int main(void) " mtu=%%d default: (library default: unlimited)\n" \ " dgram_packing=%%d default: 1 (allowed)\n" \ " allow or forbid packing of multiple\n" \ - " records within a single datgram.\n" + " records within a single datagram.\n" \ + " badmac_limit=%%d default: (library default: disabled)\n" #else #define USAGE_DTLS "" #endif @@ -524,7 +522,6 @@ int main(void) USAGE_SRTP \ USAGE_COOKIES \ USAGE_ANTI_REPLAY \ - USAGE_BADMAC_LIMIT \ "\n" #define USAGE2 \ " auth_mode=%%s default: (library default: none)\n" \ From b7718ad291a5c6038cf635a616cc33e7bfc3560b Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Thu, 28 May 2026 19:59:10 +0200 Subject: [PATCH 08/15] Check "refusing renegotiation" log in more renegotiation tests Signed-off-by: Gilles Peskine --- tests/ssl-opt.sh | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/ssl-opt.sh b/tests/ssl-opt.sh index 6fa14924b3..9f037148c7 100755 --- a/tests/ssl-opt.sh +++ b/tests/ssl-opt.sh @@ -5269,6 +5269,7 @@ run_test "Renegotiation: client-initiated, server-rejected" \ -c "=> renegotiate" \ -S "=> renegotiate" \ -S "write hello request" \ + -s "refusing renegotiation" \ -c "SSL - Unexpected message at ServerHello in renegotiation" \ -c "failed" @@ -5285,6 +5286,7 @@ run_test "Renegotiation: server-initiated, client-rejected, default" \ -C "=> renegotiate" \ -S "=> renegotiate" \ -s "write hello request" \ + -c "refusing renegotiation" \ -S "SSL - An unexpected message was received from our peer" \ -S "failed" @@ -5302,6 +5304,7 @@ run_test "Renegotiation: server-initiated, client-rejected, not enforced" \ -C "=> renegotiate" \ -S "=> renegotiate" \ -s "write hello request" \ + -c "refusing renegotiation" \ -S "SSL - An unexpected message was received from our peer" \ -S "failed" @@ -5320,6 +5323,7 @@ run_test "Renegotiation: server-initiated, client-rejected, delay 2" \ -C "=> renegotiate" \ -S "=> renegotiate" \ -s "write hello request" \ + -c "refusing renegotiation" \ -S "SSL - An unexpected message was received from our peer" \ -S "failed" @@ -5337,6 +5341,7 @@ run_test "Renegotiation: server-initiated, client-rejected, delay 0" \ -C "=> renegotiate" \ -S "=> renegotiate" \ -s "write hello request" \ + -c "refusing renegotiation" \ -s "SSL - An unexpected message was received from our peer" requires_config_enabled MBEDTLS_SSL_RENEGOTIATION @@ -5353,6 +5358,7 @@ run_test "Renegotiation: server-initiated, client-accepted, delay 0" \ -c "=> renegotiate" \ -s "=> renegotiate" \ -s "write hello request" \ + -C "refusing renegotiation" \ -S "SSL - An unexpected message was received from our peer" \ -S "failed" From a21f97139fde54199e943733244c043021631db9 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Thu, 28 May 2026 20:00:29 +0200 Subject: [PATCH 09/15] Add tests for renegotiation with badmac_seen=1 on the client After a message with a bad MAC (invalid application data record), try to renegotiate: * Test cases where renegotiation (nominal or delayed) is rejected. Nothing bad happens. * A test where renegotiation is misparsed and rejected. The subsequent parsing fails. * A test where the renegotiation request is delayed. This causes a heap buffer overflow in `ssl_buffer_message()`. Signed-off-by: Gilles Peskine --- tests/ssl-opt.sh | 80 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/tests/ssl-opt.sh b/tests/ssl-opt.sh index 9f037148c7..4d4b4541e3 100755 --- a/tests/ssl-opt.sh +++ b/tests/ssl-opt.sh @@ -11775,6 +11775,86 @@ run_test "DTLS proxy: inject invalid AD record, badmac_limit 2, exchanges 2"\ -s "too many records with bad MAC" \ -s "Verification of the message MAC failed" +requires_config_enabled MBEDTLS_SSL_RENEGOTIATION +run_test "DTLS proxy: client: get invalid AD record then reject renego" \ + -p "$P_PXY bad_ad_srv_once=1" \ + "$P_SRV dtls=1 dgram_packing=0 hs_timeout=500-10000 \ + renegotiate=1 renegotiation=1 exchanges=4 \ + badmac_limit=99 debug_level=3" \ + "$P_CLI dtls=1 dgram_packing=0 hs_timeout=500-10000 \ + exchanges=4 badmac_limit=99 debug_level=3" \ + 0 \ + -s "write hello request" \ + -c "refusing renegotiation" \ + -S "=> renegotiate" \ + -C "=> renegotiate" \ + -s "Extra-header:" \ + -c "HTTP/1.0 200 OK" \ + -c "discarding invalid record (mac)" \ + -C "too many records with bad MAC" \ + -C "Verification of the message MAC failed" \ + -C "Consume: waiting for more handshake fragments" + +requires_config_enabled MBEDTLS_SSL_RENEGOTIATION +run_test "DTLS proxy: client: get invalid AD record then accept renego" \ + -p "$P_PXY bad_ad_srv_once=1" \ + "$P_SRV dtls=1 dgram_packing=0 hs_timeout=500-10000 \ + renegotiate=1 renegotiation=1 exchanges=4 \ + badmac_limit=99 debug_level=3" \ + "$P_CLI dtls=1 dgram_packing=0 hs_timeout=500-10000 \ + renegotiation=1 exchanges=4 badmac_limit=99 debug_level=3" \ + 0 \ + -s "=> renegotiate" \ + -c "=> renegotiate" \ + -s "Extra-header:" \ + -c "HTTP/1.0 200 OK" \ + -c "discarding invalid record (mac)" \ + -C "too many records with bad MAC" \ + -C "Verification of the message MAC failed" \ + -C "Consume: waiting for more handshake fragments" + +requires_config_enabled MBEDTLS_SSL_RENEGOTIATION +run_test "DTLS proxy: client: get invalid AD record then reject early renego" \ + -p "$P_PXY bad_ad_srv_once=1 delay_encrypted_hs_srv=3" \ + "$P_SRV dtls=1 dgram_packing=0 hs_timeout=500-10000 \ + renegotiate=1 renegotiation=1 exchanges=4 \ + debug_level=3" \ + "$P_CLI dtls=1 dgram_packing=0 hs_timeout=500-10000 \ + exchanges=4 badmac_limit=99 debug_level=3" \ + 0 \ + -S "=> renegotiate" \ + -C "=> renegotiate" \ + -s "write hello request" \ + -c "refusing renegotiation" \ + -C "=> ssl_buffer_message" \ + -C "initialize reassembly, total length = 0" \ + -s "Extra-header:" \ + -c "HTTP/1.0 200 OK" \ + -c "discarding invalid record (mac)" \ + -C "too many records with bad MAC" \ + -C "Verification of the message MAC failed" \ + -C "Consume: waiting for more handshake fragments" + +requires_config_enabled MBEDTLS_SSL_RENEGOTIATION +run_test "DTLS proxy: client: get invalid AD record then accept early renego" \ + -p "$P_PXY bad_ad_srv_once=1 delay_encrypted_hs_srv=3" \ + "$P_SRV dtls=1 dgram_packing=0 hs_timeout=500-10000 \ + renegotiate=1 renegotiation=1 exchanges=4 \ + debug_level=3" \ + "$P_CLI dtls=1 dgram_packing=0 hs_timeout=500-10000 \ + renegotiation=1 exchanges=4 badmac_limit=99 debug_level=3" \ + 0 \ + -s "=> renegotiate" \ + -c "=> renegotiate" \ + -c "=> ssl_buffer_message" \ + -C "initialize reassembly, total length = 0" \ + -s "Extra-header:" \ + -c "HTTP/1.0 200 OK" \ + -c "discarding invalid record (mac)" \ + -C "too many records with bad MAC" \ + -C "Verification of the message MAC failed" \ + -C "Consume: waiting for more handshake fragments" + requires_config_enabled MBEDTLS_SSL_PROTO_TLS1_2 run_test "DTLS proxy: delay ChangeCipherSpec" \ -p "$P_PXY delay_ccs=1" \ From 4bb28f657354882e7900960ff98037e8fbf71d92 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Thu, 28 May 2026 20:52:38 +0200 Subject: [PATCH 10/15] Add renegotiate=2 option to ssl_client2 In `ssl_client2`, the existing option `renegotiate=1` causes the client to initiate renegotiation immediately after the initial handshake. Add a variant `renegotiate=2` that initiates renegotiation after one data exchange. Signed-off-by: Gilles Peskine --- programs/ssl/ssl_client2.c | 43 ++++++++++++++++++++++++++++++++++---- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/programs/ssl/ssl_client2.c b/programs/ssl/ssl_client2.c index 26ea63db9b..be1d2a8e2e 100644 --- a/programs/ssl/ssl_client2.c +++ b/programs/ssl/ssl_client2.c @@ -296,7 +296,7 @@ int main(void) #if defined(MBEDTLS_SSL_RENEGOTIATION) #define USAGE_RENEGO \ " renegotiation=%%d default: 0 (disabled)\n" \ - " renegotiate=%%d default: 0 (disabled)\n" \ + " renegotiate=%%d default: 0 (disabled), 1 immediately, 2 after first exchange\n" \ " renego_delay=%%d default: -2 (library default)\n" #else #define USAGE_RENEGO "" @@ -493,7 +493,7 @@ struct options { #endif /* MBEDTLS_SSL_PROTO_TLS1_3 */ int renegotiation; /* enable / disable renegotiation */ int allow_legacy; /* allow legacy renegotiation */ - int renegotiate; /* attempt renegotiation? */ + int renegotiate; /* attempt renegotiation? 1: before data, 2: after first exchange */ int renego_delay; /* delay before enforcing renegotiation */ int exchanges; /* number of data exchanges */ int min_version; /* minimum protocol version accepted */ @@ -1168,7 +1168,7 @@ usage: opt.renego_delay = (atoi(q)); } else if (strcmp(p, "renegotiate") == 0) { opt.renegotiate = atoi(q); - if (opt.renegotiate < 0 || opt.renegotiate > 1) { + if (opt.renegotiate < 0 || opt.renegotiate > 2) { goto usage; } } else if (strcmp(p, "exchanges") == 0) { @@ -2455,7 +2455,7 @@ usage: #endif /* MBEDTLS_SSL_DTLS_CONNECTION_ID */ #if defined(MBEDTLS_SSL_RENEGOTIATION) - if (opt.renegotiate) { + if (opt.renegotiate == 1) { /* * Perform renegotiation (this must be done when the server is waiting * for input from our side). @@ -2830,6 +2830,41 @@ send_request: goto send_request; } +#if defined(MBEDTLS_SSL_RENEGOTIATION) + if (opt.renegotiate == 2) { + opt.renegotiate = 0; + + /* Perform renegotiation after the first data exchange. */ + mbedtls_printf(" . Performing renegotiation..."); + fflush(stdout); + while ((ret = mbedtls_ssl_renegotiate(&ssl)) != 0) { + if (ret != MBEDTLS_ERR_SSL_WANT_READ && + ret != MBEDTLS_ERR_SSL_WANT_WRITE && + ret != MBEDTLS_ERR_SSL_CRYPTO_IN_PROGRESS) { + mbedtls_printf(" failed\n ! mbedtls_ssl_renegotiate returned %d\n\n", + ret); + goto exit; + } + +#if defined(MBEDTLS_ECP_RESTARTABLE) + if (ret == MBEDTLS_ERR_SSL_CRYPTO_IN_PROGRESS) { + continue; + } +#endif + + /* For event-driven IO, wait for socket to become available */ + if (opt.event == 1 /* level triggered IO */) { +#if defined(MBEDTLS_TIMING_C) + idle(&server_fd, &timer, ret); +#else + idle(&server_fd, ret); +#endif + } + } + mbedtls_printf(" ok\n"); + } +#endif /* MBEDTLS_SSL_RENEGOTIATION */ + /* * 7c. Simulate serialize/deserialize and go back to data exchange */ From cde26bd4ce6f11a3e5c76635a95cf6e0fdb3e8a3 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Thu, 28 May 2026 20:52:38 +0200 Subject: [PATCH 11/15] Add tests for renegotiation with badmac_seen=1 on the server Signed-off-by: Gilles Peskine --- tests/ssl-opt.sh | 92 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/tests/ssl-opt.sh b/tests/ssl-opt.sh index 4d4b4541e3..41280a7ab2 100755 --- a/tests/ssl-opt.sh +++ b/tests/ssl-opt.sh @@ -11795,6 +11795,25 @@ run_test "DTLS proxy: client: get invalid AD record then reject renego" \ -C "Verification of the message MAC failed" \ -C "Consume: waiting for more handshake fragments" +requires_config_enabled MBEDTLS_SSL_RENEGOTIATION +run_test "DTLS proxy: server: get invalid AD record then reject renego (cli)" \ + -p "$P_PXY bad_ad_cli_once=1" \ + "$P_SRV dtls=1 dgram_packing=0 hs_timeout=500-10000 \ + renegotiation=0 exchanges=4 \ + badmac_limit=99 debug_level=3" \ + "$P_CLI dtls=1 dgram_packing=0 hs_timeout=500-10000 \ + renegotiation=1 renegotiate=2 exchanges=4 badmac_limit=99 debug_level=3" \ + 1 \ + -s "discarding invalid record (mac)" \ + -c "=> renegotiate" \ + -S "=> renegotiate" \ + -s "refusing renegotiation" \ + -s "Extra-header:" \ + -c "HTTP/1.0 200 OK" \ + -S "too many records with bad MAC" \ + -S "Verification of the message MAC failed" \ + -S "Consume: waiting for more handshake fragments" + requires_config_enabled MBEDTLS_SSL_RENEGOTIATION run_test "DTLS proxy: client: get invalid AD record then accept renego" \ -p "$P_PXY bad_ad_srv_once=1" \ @@ -11813,6 +11832,42 @@ run_test "DTLS proxy: client: get invalid AD record then accept renego" \ -C "Verification of the message MAC failed" \ -C "Consume: waiting for more handshake fragments" +requires_config_enabled MBEDTLS_SSL_RENEGOTIATION +run_test "DTLS proxy: server: get invalid AD record then accept renego (srv)" \ + -p "$P_PXY bad_ad_cli_once=1" \ + "$P_SRV dtls=1 dgram_packing=0 hs_timeout=500-10000 \ + renegotiate=1 renegotiation=1 exchanges=4 \ + badmac_limit=99 debug_level=3" \ + "$P_CLI dtls=1 dgram_packing=0 hs_timeout=500-10000 \ + renegotiation=1 exchanges=4 badmac_limit=99 debug_level=3" \ + 0 \ + -s "discarding invalid record (mac)" \ + -s "=> renegotiate" \ + -c "=> renegotiate" \ + -s "Extra-header:" \ + -c "HTTP/1.0 200 OK" \ + -S "too many records with bad MAC" \ + -S "Verification of the message MAC failed" \ + -S "Consume: waiting for more handshake fragments" + +requires_config_enabled MBEDTLS_SSL_RENEGOTIATION +run_test "DTLS proxy: server: get invalid AD record then accept renego (cli)" \ + -p "$P_PXY bad_ad_cli_once=1" \ + "$P_SRV dtls=1 dgram_packing=0 hs_timeout=500-10000 \ + renegotiation=1 exchanges=4 \ + badmac_limit=99 debug_level=3" \ + "$P_CLI dtls=1 dgram_packing=0 hs_timeout=500-10000 \ + renegotiation=1 renegotiate=2 exchanges=4 badmac_limit=99 debug_level=3" \ + 0 \ + -s "discarding invalid record (mac)" \ + -c "=> renegotiate" \ + -s "=> renegotiate" \ + -s "Extra-header:" \ + -c "HTTP/1.0 200 OK" \ + -S "too many records with bad MAC" \ + -S "Verification of the message MAC failed" \ + -S "Consume: waiting for more handshake fragments" + requires_config_enabled MBEDTLS_SSL_RENEGOTIATION run_test "DTLS proxy: client: get invalid AD record then reject early renego" \ -p "$P_PXY bad_ad_srv_once=1 delay_encrypted_hs_srv=3" \ @@ -11855,6 +11910,43 @@ run_test "DTLS proxy: client: get invalid AD record then accept early renego" -C "Verification of the message MAC failed" \ -C "Consume: waiting for more handshake fragments" +requires_config_enabled MBEDTLS_SSL_RENEGOTIATION +run_test "DTLS proxy: server: get invalid AD record then reject early renego (cli)" \ + -p "$P_PXY bad_ad_cli_once=1 delay_encrypted_hs_cli=2" \ + "$P_SRV dtls=1 dgram_packing=0 hs_timeout=500-10000 \ + renegotiation=0 exchanges=4 \ + badmac_limit=99 debug_level=3" \ + "$P_CLI dtls=1 dgram_packing=0 hs_timeout=500-10000 \ + renegotiation=1 renegotiate=2 exchanges=4 badmac_limit=99 debug_level=3" \ + 1 \ + -s "discarding invalid record (mac)" \ + -c "=> renegotiate" \ + -S "=> renegotiate" \ + -s "refusing renegotiation" \ + -s "Extra-header:" \ + -c "HTTP/1.0 200 OK" \ + -S "too many records with bad MAC" \ + -S "Verification of the message MAC failed" \ + -S "Consume: waiting for more handshake fragments" + +requires_config_enabled MBEDTLS_SSL_RENEGOTIATION +run_test "DTLS proxy: server: get invalid AD record then accept early renego (cli)" \ + -p "$P_PXY bad_ad_cli_once=1 delay_encrypted_hs_cli=2" \ + "$P_SRV dtls=1 dgram_packing=0 hs_timeout=500-10000 \ + renegotiation=1 exchanges=4 \ + badmac_limit=99 debug_level=3" \ + "$P_CLI dtls=1 dgram_packing=0 hs_timeout=500-10000 \ + renegotiation=1 renegotiate=2 exchanges=4 badmac_limit=99 debug_level=3" \ + 0 \ + -s "discarding invalid record (mac)" \ + -c "=> renegotiate" \ + -s "=> renegotiate" \ + -s "Extra-header:" \ + -c "HTTP/1.0 200 OK" \ + -S "too many records with bad MAC" \ + -S "Verification of the message MAC failed" \ + -S "Consume: waiting for more handshake fragments" + requires_config_enabled MBEDTLS_SSL_PROTO_TLS1_2 run_test "DTLS proxy: delay ChangeCipherSpec" \ -p "$P_PXY delay_ccs=1" \ From 3d5e440b4153a86006442172acc6e14de2db268c Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Mon, 15 Jun 2026 13:08:43 +0200 Subject: [PATCH 12/15] Add DTLS handshake test with badmac_seen=1 Add a test entry point for a handshake with a specially crafted context. This can be used for defense in depths for scenarios that we think can't happen, but might actually happen due to a bug, or due to a new feature that is insufficiently tested. This can also be a proxy for testing aspects of renegotiation or session resumption in circumstances that are hard to arrange. In this commit, use it to test a DTLS handshake with `ssl->badmac_seen` :(actually `ssl->badmac_seen_or_in_hsfraglen` in the 3.6 branch) set to a nonzero value. This can't happen, as far as I know (`badmac_seen` can only be nonzero during renegotiation, not during a nominal handshake or in session resumption). But it could happen, for example, if `mbedtls_ssl_session_reset()` failed to reset `badmac_seen`, as was the case in Mbed TLS 4.0.0 to 4.1.0. Signed-off-by: Gilles Peskine --- tests/suites/test_suite_ssl.data | 8 ++++ tests/suites/test_suite_ssl.function | 67 ++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) diff --git a/tests/suites/test_suite_ssl.data b/tests/suites/test_suite_ssl.data index da975a5b0d..09da227dac 100644 --- a/tests/suites/test_suite_ssl.data +++ b/tests/suites/test_suite_ssl.data @@ -471,6 +471,14 @@ Handshake, select ECDHE-ECDSA-WITH-AES-256-CCM, opaque, bad usage depends_on:PSA_WANT_ALG_SHA_256:PSA_WANT_KEY_TYPE_AES:PSA_WANT_ALG_CCM:PSA_WANT_ECC_SECP_R1_256:PSA_WANT_ECC_SECP_R1_384:MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED:!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH handshake_ciphersuite_select:"TLS-ECDHE-ECDSA-WITH-AES-256-CCM":MBEDTLS_PK_ECDSA:"":PSA_ALG_ECDSA(PSA_ALG_ANY_HASH):PSA_ALG_NONE:PSA_KEY_USAGE_DERIVE:MBEDTLS_ERR_SSL_HANDSHAKE_FAILURE:0 +Handshake: DTLS 1.2, badmac_seen on client +depends_on:MBEDTLS_SSL_PROTO_DTLS:MBEDTLS_SSL_PROTO_TLS1_2 +handshake_with_forced_context:1:MBEDTLS_SSL_VERSION_TLS1_2:PRE_HANDSHAKE_BADMAC_SEEN:0 + +Handshake: DTLS 1.2, badmac_seen on server +depends_on:MBEDTLS_SSL_PROTO_DTLS:MBEDTLS_SSL_PROTO_TLS1_2 +handshake_with_forced_context:1:MBEDTLS_SSL_VERSION_TLS1_2:0:PRE_HANDSHAKE_BADMAC_SEEN + Sending app data via TLS, MFL=512 without fragmentation depends_on:MBEDTLS_SSL_MAX_FRAGMENT_LENGTH app_data_tls:MBEDTLS_SSL_MAX_FRAG_LEN_512:400:512:1:1 diff --git a/tests/suites/test_suite_ssl.function b/tests/suites/test_suite_ssl.function index f5492ff53c..b4bdf37553 100644 --- a/tests/suites/test_suite_ssl.function +++ b/tests/suites/test_suite_ssl.function @@ -66,6 +66,39 @@ exit: } #endif +typedef enum { + PRE_HANDSHAKE_BADMAC_SEEN = 1u << 0, +} pre_handshake_tweaks_t; + +#if defined(MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED) && \ + defined(PSA_HAVE_ALG_SOME_RSA_SIGN) && \ + defined(PSA_WANT_ECC_SECP_R1_384) && \ + defined(PSA_WANT_ALG_SHA_256) && \ + defined(PSA_WANT_KEY_TYPE_ECC_PUBLIC_KEY) && \ + defined(MBEDTLS_CAN_HANDLE_RSA_TEST_KEY) +typedef struct { + uint32_t client; + uint32_t server; +} pre_handshake_tweak_spec_t; + +static void pre_handshake_tweak_context(mbedtls_ssl_context *ssl, + int tweaks) +{ + if (tweaks & PRE_HANDSHAKE_BADMAC_SEEN) { + ssl->badmac_seen = 1; + } +} + +static void pre_handshake_tweak_contexts(mbedtls_test_ssl_endpoint *client, + mbedtls_test_ssl_endpoint *server, + void *param) +{ + const pre_handshake_tweak_spec_t *tweaks = param; + pre_handshake_tweak_context(&client->ssl, tweaks->client); + pre_handshake_tweak_context(&server->ssl, tweaks->server); +} +#endif + #if defined(MBEDTLS_KEY_EXCHANGE_WITH_CERT_ENABLED) && \ defined(MBEDTLS_SSL_VARIABLE_BUFFER_LENGTH) && \ defined(MBEDTLS_SSL_PROTO_TLS1_2) && \ @@ -3089,6 +3122,40 @@ exit: } /* END_CASE */ +/* BEGIN_CASE depends_on:MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED:PSA_HAVE_ALG_SOME_RSA_SIGN:PSA_WANT_ECC_SECP_R1_384:PSA_WANT_ALG_SHA_256:PSA_WANT_KEY_TYPE_ECC_PUBLIC_KEY:MBEDTLS_CAN_HANDLE_RSA_TEST_KEY */ +/* Test a handshake in forced conditions. + * + * This can be used for defense in depths for scenarios that we think + * can't happen, but might actually happen due to a bug, or due to + * a new feature that is insufficiently tested. + * + * This can also be a proxy for testing aspects of renegotiation or + * session resumption in circumstances that are hard to arrange. + */ +void handshake_with_forced_context(int dtls, int version, + int client_tweaks, int server_tweaks) +{ + pre_handshake_tweak_spec_t tweaks = { client_tweaks, server_tweaks }; + + mbedtls_test_handshake_test_options options; + mbedtls_test_init_handshake_options(&options); + options.dtls = dtls; + options.client_min_version = version; + options.client_max_version = version; + options.expected_negotiated_version = version; + options.pre_handshake_fun = &pre_handshake_tweak_contexts; + options.pre_handshake_param = &tweaks; + + mbedtls_test_ssl_perform_handshake(&options); + + /* The goto below is used to avoid an "unused label" warning.*/ + goto exit; + +exit: + mbedtls_test_free_handshake_options(&options); +} +/* END_CASE */ + /* BEGIN_CASE depends_on:MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED:PSA_HAVE_ALG_SOME_RSA_SIGN:PSA_WANT_ECC_SECP_R1_384:PSA_WANT_ALG_SHA_256 */ void app_data(int mfl, int cli_msg_len, int srv_msg_len, int expected_cli_fragments, From cbc94fbdf4b877a7d2f7f7045ec2fd1c25d6c717 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Mon, 15 Jun 2026 13:37:54 +0200 Subject: [PATCH 13/15] Don't pass badmac_limit when it would have no effect Only set `badmac_limit` on the side that actually receives a message with a bad MAC/tag (injected by the proxy). Signed-off-by: Gilles Peskine --- tests/ssl-opt.sh | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/ssl-opt.sh b/tests/ssl-opt.sh index 41280a7ab2..5b966d5a38 100755 --- a/tests/ssl-opt.sh +++ b/tests/ssl-opt.sh @@ -11780,7 +11780,7 @@ run_test "DTLS proxy: client: get invalid AD record then reject renego" \ -p "$P_PXY bad_ad_srv_once=1" \ "$P_SRV dtls=1 dgram_packing=0 hs_timeout=500-10000 \ renegotiate=1 renegotiation=1 exchanges=4 \ - badmac_limit=99 debug_level=3" \ + debug_level=3" \ "$P_CLI dtls=1 dgram_packing=0 hs_timeout=500-10000 \ exchanges=4 badmac_limit=99 debug_level=3" \ 0 \ @@ -11802,7 +11802,7 @@ run_test "DTLS proxy: server: get invalid AD record then reject renego (cli)" renegotiation=0 exchanges=4 \ badmac_limit=99 debug_level=3" \ "$P_CLI dtls=1 dgram_packing=0 hs_timeout=500-10000 \ - renegotiation=1 renegotiate=2 exchanges=4 badmac_limit=99 debug_level=3" \ + renegotiation=1 renegotiate=2 exchanges=4 debug_level=3" \ 1 \ -s "discarding invalid record (mac)" \ -c "=> renegotiate" \ @@ -11819,7 +11819,7 @@ run_test "DTLS proxy: client: get invalid AD record then accept renego" \ -p "$P_PXY bad_ad_srv_once=1" \ "$P_SRV dtls=1 dgram_packing=0 hs_timeout=500-10000 \ renegotiate=1 renegotiation=1 exchanges=4 \ - badmac_limit=99 debug_level=3" \ + debug_level=3" \ "$P_CLI dtls=1 dgram_packing=0 hs_timeout=500-10000 \ renegotiation=1 exchanges=4 badmac_limit=99 debug_level=3" \ 0 \ @@ -11839,7 +11839,7 @@ run_test "DTLS proxy: server: get invalid AD record then accept renego (srv)" renegotiate=1 renegotiation=1 exchanges=4 \ badmac_limit=99 debug_level=3" \ "$P_CLI dtls=1 dgram_packing=0 hs_timeout=500-10000 \ - renegotiation=1 exchanges=4 badmac_limit=99 debug_level=3" \ + renegotiation=1 exchanges=4 debug_level=3" \ 0 \ -s "discarding invalid record (mac)" \ -s "=> renegotiate" \ @@ -11857,7 +11857,7 @@ run_test "DTLS proxy: server: get invalid AD record then accept renego (cli)" renegotiation=1 exchanges=4 \ badmac_limit=99 debug_level=3" \ "$P_CLI dtls=1 dgram_packing=0 hs_timeout=500-10000 \ - renegotiation=1 renegotiate=2 exchanges=4 badmac_limit=99 debug_level=3" \ + renegotiation=1 renegotiate=2 exchanges=4 debug_level=3" \ 0 \ -s "discarding invalid record (mac)" \ -c "=> renegotiate" \ @@ -11917,7 +11917,7 @@ run_test "DTLS proxy: server: get invalid AD record then reject early renego renegotiation=0 exchanges=4 \ badmac_limit=99 debug_level=3" \ "$P_CLI dtls=1 dgram_packing=0 hs_timeout=500-10000 \ - renegotiation=1 renegotiate=2 exchanges=4 badmac_limit=99 debug_level=3" \ + renegotiation=1 renegotiate=2 exchanges=4 debug_level=3" \ 1 \ -s "discarding invalid record (mac)" \ -c "=> renegotiate" \ @@ -11936,7 +11936,7 @@ run_test "DTLS proxy: server: get invalid AD record then accept early renego renegotiation=1 exchanges=4 \ badmac_limit=99 debug_level=3" \ "$P_CLI dtls=1 dgram_packing=0 hs_timeout=500-10000 \ - renegotiation=1 renegotiate=2 exchanges=4 badmac_limit=99 debug_level=3" \ + renegotiation=1 renegotiate=2 exchanges=4 debug_level=3" \ 0 \ -s "discarding invalid record (mac)" \ -c "=> renegotiate" \ From 046e5a43ade2021c924178fc8733fd8a80093011 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Mon, 15 Jun 2026 13:58:49 +0200 Subject: [PATCH 14/15] Explain the "get invalid AD record .*renego" tests better Signed-off-by: Gilles Peskine --- programs/ssl/ssl_client2.c | 5 ++++- tests/ssl-opt.sh | 24 ++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/programs/ssl/ssl_client2.c b/programs/ssl/ssl_client2.c index be1d2a8e2e..9dc99c3e0b 100644 --- a/programs/ssl/ssl_client2.c +++ b/programs/ssl/ssl_client2.c @@ -2832,9 +2832,12 @@ send_request: #if defined(MBEDTLS_SSL_RENEGOTIATION) if (opt.renegotiate == 2) { + /* Perform renegotiation after the first data exchange. + * This is a one-time thing, we won't renegotiate even if there are + * more data exchanges that cause a `goto send_request` later. + */ opt.renegotiate = 0; - /* Perform renegotiation after the first data exchange. */ mbedtls_printf(" . Performing renegotiation..."); fflush(stdout); while ((ret = mbedtls_ssl_renegotiate(&ssl)) != 0) { diff --git a/tests/ssl-opt.sh b/tests/ssl-opt.sh index 5b966d5a38..d66c2026ae 100755 --- a/tests/ssl-opt.sh +++ b/tests/ssl-opt.sh @@ -11775,6 +11775,14 @@ run_test "DTLS proxy: inject invalid AD record, badmac_limit 2, exchanges 2"\ -s "too many records with bad MAC" \ -s "Verification of the message MAC failed" +# The next few tests exercise renegotiation in DTLS when `ssl->badmac_seen != 0`. +# +# This seems unrelated, but was the location of a bug in Mbed TLS 3.6.3 to +# 3.6.6 (CVE-2026-50713) due to `ssl->badmac_seen` being unified with +# `ssl->in_hsfraglen` (to preserve the ABI when adding support for handshake +# defragmentation in TLS in 3.6.3), with some mistakes in using the unified +# field that were fixed in 3.6.7. + requires_config_enabled MBEDTLS_SSL_RENEGOTIATION run_test "DTLS proxy: client: get invalid AD record then reject renego" \ -p "$P_PXY bad_ad_srv_once=1" \ @@ -11868,6 +11876,15 @@ run_test "DTLS proxy: server: get invalid AD record then accept renego (cli)" -S "Verification of the message MAC failed" \ -S "Consume: waiting for more handshake fragments" +# The next few tests have "early renego". A DTLS handshake message is delayed +# past another handshake message. From the perspective of the recipient, +# this means that a DTLS handshake arrives early (its epoch number is still +# in the future) and needs to be queued. +# +# In Mbed TLS 3.6.3 through 3.6.6, due to the fields `ssl->badmac_seen` +# and `ssl->in_hsfraglen` being unified, the early message was parsed +# incorrectly, leading to heap corruption. + requires_config_enabled MBEDTLS_SSL_RENEGOTIATION run_test "DTLS proxy: client: get invalid AD record then reject early renego" \ -p "$P_PXY bad_ad_srv_once=1 delay_encrypted_hs_srv=3" \ @@ -11890,6 +11907,13 @@ run_test "DTLS proxy: client: get invalid AD record then reject early renego" -C "Verification of the message MAC failed" \ -C "Consume: waiting for more handshake fragments" +# Delay the third encrypted handshake message from the server: +# 1. Finished message of the initial handshake. +# 2. HelloRequest to request renegotiation. +# 3. ServerHello of renegotiation, delayed. +# 4. Subsequent handshake message, which the client receives and enqueues. +# +# In Mbed TLS 3.6.3 though 3.6.6, the processing of (4) caused heap corruption. requires_config_enabled MBEDTLS_SSL_RENEGOTIATION run_test "DTLS proxy: client: get invalid AD record then accept early renego" \ -p "$P_PXY bad_ad_srv_once=1 delay_encrypted_hs_srv=3" \ From dc258c9b9b59f0ee24800efda9dd2bcac7ad115b Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Mon, 15 Jun 2026 18:22:11 +0200 Subject: [PATCH 15/15] Remove some test cases that are not really meaningful Remove the "srv" variant of "DTLS proxy: server: get invalid AD record then accept renego", where the server initiates the renegotiation process by sending a HelloRequest. The HelloRequest message is not relevant to this test. The first message of the actual renegotiation is the ClientHello message from the client, and the interesting part of the test happens when the client receives the server's response. Remove "DTLS proxy: server: get invalid AD record then accept early renego (cli)" and the corresponding "reject" control. This test case doesn't really do what it is advertised to do, and in particular does not lead to a call to `ssl_consume_current_message()` in the server. What happens is that the client sends a ClientHello message to start the renegotiation, then it blocks until the server has replied. So there is nothing to delay that ClientHello after:(unlike what happens in the corresponding client-side test, where the first flight of the renegotiation protocol contains multiple messages, so we can delay the first of these messages after the second one). What happens in practice is that the client times out, then resends its ClientHello, and the proxy sends the delayed ClientHello immediately after the resent one, which does not cause any interesting behavior in the server. Signed-off-by: Gilles Peskine --- tests/ssl-opt.sh | 59 ++---------------------------------------------- 1 file changed, 2 insertions(+), 57 deletions(-) diff --git a/tests/ssl-opt.sh b/tests/ssl-opt.sh index d66c2026ae..8ad596a6ea 100755 --- a/tests/ssl-opt.sh +++ b/tests/ssl-opt.sh @@ -11804,7 +11804,7 @@ run_test "DTLS proxy: client: get invalid AD record then reject renego" \ -C "Consume: waiting for more handshake fragments" requires_config_enabled MBEDTLS_SSL_RENEGOTIATION -run_test "DTLS proxy: server: get invalid AD record then reject renego (cli)" \ +run_test "DTLS proxy: server: get invalid AD record then reject renego" \ -p "$P_PXY bad_ad_cli_once=1" \ "$P_SRV dtls=1 dgram_packing=0 hs_timeout=500-10000 \ renegotiation=0 exchanges=4 \ @@ -11841,25 +11841,7 @@ run_test "DTLS proxy: client: get invalid AD record then accept renego" \ -C "Consume: waiting for more handshake fragments" requires_config_enabled MBEDTLS_SSL_RENEGOTIATION -run_test "DTLS proxy: server: get invalid AD record then accept renego (srv)" \ - -p "$P_PXY bad_ad_cli_once=1" \ - "$P_SRV dtls=1 dgram_packing=0 hs_timeout=500-10000 \ - renegotiate=1 renegotiation=1 exchanges=4 \ - badmac_limit=99 debug_level=3" \ - "$P_CLI dtls=1 dgram_packing=0 hs_timeout=500-10000 \ - renegotiation=1 exchanges=4 debug_level=3" \ - 0 \ - -s "discarding invalid record (mac)" \ - -s "=> renegotiate" \ - -c "=> renegotiate" \ - -s "Extra-header:" \ - -c "HTTP/1.0 200 OK" \ - -S "too many records with bad MAC" \ - -S "Verification of the message MAC failed" \ - -S "Consume: waiting for more handshake fragments" - -requires_config_enabled MBEDTLS_SSL_RENEGOTIATION -run_test "DTLS proxy: server: get invalid AD record then accept renego (cli)" \ +run_test "DTLS proxy: server: get invalid AD record then accept renego" \ -p "$P_PXY bad_ad_cli_once=1" \ "$P_SRV dtls=1 dgram_packing=0 hs_timeout=500-10000 \ renegotiation=1 exchanges=4 \ @@ -11934,43 +11916,6 @@ run_test "DTLS proxy: client: get invalid AD record then accept early renego" -C "Verification of the message MAC failed" \ -C "Consume: waiting for more handshake fragments" -requires_config_enabled MBEDTLS_SSL_RENEGOTIATION -run_test "DTLS proxy: server: get invalid AD record then reject early renego (cli)" \ - -p "$P_PXY bad_ad_cli_once=1 delay_encrypted_hs_cli=2" \ - "$P_SRV dtls=1 dgram_packing=0 hs_timeout=500-10000 \ - renegotiation=0 exchanges=4 \ - badmac_limit=99 debug_level=3" \ - "$P_CLI dtls=1 dgram_packing=0 hs_timeout=500-10000 \ - renegotiation=1 renegotiate=2 exchanges=4 debug_level=3" \ - 1 \ - -s "discarding invalid record (mac)" \ - -c "=> renegotiate" \ - -S "=> renegotiate" \ - -s "refusing renegotiation" \ - -s "Extra-header:" \ - -c "HTTP/1.0 200 OK" \ - -S "too many records with bad MAC" \ - -S "Verification of the message MAC failed" \ - -S "Consume: waiting for more handshake fragments" - -requires_config_enabled MBEDTLS_SSL_RENEGOTIATION -run_test "DTLS proxy: server: get invalid AD record then accept early renego (cli)" \ - -p "$P_PXY bad_ad_cli_once=1 delay_encrypted_hs_cli=2" \ - "$P_SRV dtls=1 dgram_packing=0 hs_timeout=500-10000 \ - renegotiation=1 exchanges=4 \ - badmac_limit=99 debug_level=3" \ - "$P_CLI dtls=1 dgram_packing=0 hs_timeout=500-10000 \ - renegotiation=1 renegotiate=2 exchanges=4 debug_level=3" \ - 0 \ - -s "discarding invalid record (mac)" \ - -c "=> renegotiate" \ - -s "=> renegotiate" \ - -s "Extra-header:" \ - -c "HTTP/1.0 200 OK" \ - -S "too many records with bad MAC" \ - -S "Verification of the message MAC failed" \ - -S "Consume: waiting for more handshake fragments" - requires_config_enabled MBEDTLS_SSL_PROTO_TLS1_2 run_test "DTLS proxy: delay ChangeCipherSpec" \ -p "$P_PXY delay_ccs=1" \