From d774a25bfeed2a0d4af89b8e6f7180dc8f33712d Mon Sep 17 00:00:00 2001 From: Ronald Cron Date: Tue, 21 Apr 2026 09:26:07 +0200 Subject: [PATCH 01/20] Remove unnecessary dependency Signed-off-by: Ronald Cron --- tests/suites/test_suite_ssl.function | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/suites/test_suite_ssl.function b/tests/suites/test_suite_ssl.function index ae7ab55366..21e9a21d8b 100644 --- a/tests/suites/test_suite_ssl.function +++ b/tests/suites/test_suite_ssl.function @@ -2835,7 +2835,7 @@ exit: } /* END_CASE */ -/* BEGIN_CASE depends_on:MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED:MBEDTLS_RSA_C:MBEDTLS_ECP_HAVE_SECP384R1:!MBEDTLS_SSL_PROTO_TLS1_3:MBEDTLS_PKCS1_V15:MBEDTLS_MD_CAN_SHA256 */ +/* BEGIN_CASE depends_on:MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED:MBEDTLS_RSA_C:MBEDTLS_ECP_HAVE_SECP384R1:MBEDTLS_PKCS1_V15:MBEDTLS_MD_CAN_SHA256 */ void mbedtls_endpoint_sanity(int endpoint_type) { enum { BUFFSIZE = 1024 }; @@ -3073,7 +3073,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_PKCS1_V15:MBEDTLS_SSL_PROTO_TLS1_2:MBEDTLS_RSA_C:MBEDTLS_ECP_HAVE_SECP384R1:MBEDTLS_SSL_PROTO_DTLS:MBEDTLS_MD_CAN_SHA256:MBEDTLS_CAN_HANDLE_RSA_TEST_KEY */ +/* 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_MD_CAN_SHA256: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) @@ -3102,7 +3102,7 @@ exit: } /* END_CASE */ -/* BEGIN_CASE depends_on:MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED:!MBEDTLS_SSL_PROTO_TLS1_3:MBEDTLS_PKCS1_V15:MBEDTLS_RSA_C:MBEDTLS_SSL_HAVE_AES:MBEDTLS_ECP_HAVE_SECP384R1:MBEDTLS_DEBUG_C:MBEDTLS_SSL_MAX_FRAGMENT_LENGTH:MBEDTLS_SSL_HAVE_CBC */ +/* BEGIN_CASE depends_on:MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED:MBEDTLS_PKCS1_V15:MBEDTLS_RSA_C:MBEDTLS_SSL_HAVE_AES:MBEDTLS_ECP_HAVE_SECP384R1:MBEDTLS_DEBUG_C:MBEDTLS_SSL_MAX_FRAGMENT_LENGTH:MBEDTLS_SSL_HAVE_CBC */ void handshake_fragmentation(int mfl, int expected_srv_hs_fragmentation, int expected_cli_hs_fragmentation, @@ -3302,7 +3302,7 @@ exit: } /* END_CASE */ -/* BEGIN_CASE depends_on:MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED:!MBEDTLS_SSL_PROTO_TLS1_3:MBEDTLS_PKCS1_V15:MBEDTLS_SSL_PROTO_TLS1_2:MBEDTLS_RSA_C:MBEDTLS_ECP_HAVE_SECP384R1:MBEDTLS_SSL_PROTO_DTLS:MBEDTLS_SSL_RENEGOTIATION:MBEDTLS_MD_CAN_SHA256:MBEDTLS_CAN_HANDLE_RSA_TEST_KEY */ +/* 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_RENEGOTIATION:MBEDTLS_MD_CAN_SHA256:MBEDTLS_CAN_HANDLE_RSA_TEST_KEY */ void renegotiation(int legacy_renegotiation) { mbedtls_test_handshake_test_options options; @@ -3349,7 +3349,7 @@ exit: } /* END_CASE */ -/* BEGIN_CASE depends_on:MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED:!MBEDTLS_SSL_PROTO_TLS1_3:MBEDTLS_PKCS1_V15:MBEDTLS_SSL_VARIABLE_BUFFER_LENGTH:MBEDTLS_SSL_CONTEXT_SERIALIZATION:MBEDTLS_SSL_PROTO_TLS1_2:MBEDTLS_RSA_C:MBEDTLS_ECP_HAVE_SECP384R1:MBEDTLS_SSL_PROTO_DTLS:MBEDTLS_MD_CAN_SHA256:MBEDTLS_CAN_HANDLE_RSA_TEST_KEY */ +/* BEGIN_CASE depends_on:MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED:MBEDTLS_PKCS1_V15:MBEDTLS_SSL_VARIABLE_BUFFER_LENGTH:MBEDTLS_SSL_CONTEXT_SERIALIZATION:MBEDTLS_SSL_PROTO_TLS1_2:MBEDTLS_RSA_C:MBEDTLS_ECP_HAVE_SECP384R1:MBEDTLS_SSL_PROTO_DTLS:MBEDTLS_MD_CAN_SHA256:MBEDTLS_CAN_HANDLE_RSA_TEST_KEY */ void resize_buffers_serialize_mfl(int mfl) { test_resize_buffers(mfl, 0, MBEDTLS_SSL_LEGACY_NO_RENEGOTIATION, 1, 1, @@ -3359,7 +3359,7 @@ void resize_buffers_serialize_mfl(int mfl) } /* END_CASE */ -/* BEGIN_CASE depends_on:MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED:!MBEDTLS_SSL_PROTO_TLS1_3:MBEDTLS_PKCS1_V15:MBEDTLS_SSL_VARIABLE_BUFFER_LENGTH:MBEDTLS_SSL_RENEGOTIATION:MBEDTLS_SSL_PROTO_TLS1_2:MBEDTLS_RSA_C:MBEDTLS_ECP_HAVE_SECP384R1:MBEDTLS_MD_CAN_SHA256:MBEDTLS_CAN_HANDLE_RSA_TEST_KEY */ +/* BEGIN_CASE depends_on:MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED:MBEDTLS_PKCS1_V15:MBEDTLS_SSL_VARIABLE_BUFFER_LENGTH:MBEDTLS_SSL_RENEGOTIATION:MBEDTLS_SSL_PROTO_TLS1_2:MBEDTLS_RSA_C:MBEDTLS_ECP_HAVE_SECP384R1:MBEDTLS_MD_CAN_SHA256:MBEDTLS_CAN_HANDLE_RSA_TEST_KEY */ void resize_buffers_renegotiate_mfl(int mfl, int legacy_renegotiation, char *cipher) { From 2aa80688d985459933fc1403f11c845370f90b4b Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Fri, 5 Jun 2026 15:40:03 +0200 Subject: [PATCH 02/20] 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 21e9a21d8b..efc5307733 100644 --- a/tests/suites/test_suite_ssl.function +++ b/tests/suites/test_suite_ssl.function @@ -3102,7 +3102,7 @@ exit: } /* END_CASE */ -/* BEGIN_CASE depends_on:MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED:MBEDTLS_PKCS1_V15:MBEDTLS_RSA_C:MBEDTLS_SSL_HAVE_AES:MBEDTLS_ECP_HAVE_SECP384R1:MBEDTLS_DEBUG_C:MBEDTLS_SSL_MAX_FRAGMENT_LENGTH:MBEDTLS_SSL_HAVE_CBC */ +/* BEGIN_CASE depends_on:MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED:MBEDTLS_PKCS1_V15:MBEDTLS_RSA_C:MBEDTLS_SSL_HAVE_AES:MBEDTLS_ECP_HAVE_SECP384R1:MBEDTLS_MD_CAN_SHA256:MBEDTLS_DEBUG_C:MBEDTLS_SSL_MAX_FRAGMENT_LENGTH:MBEDTLS_SSL_HAVE_CBC */ void handshake_fragmentation(int mfl, int expected_srv_hs_fragmentation, int expected_cli_hs_fragmentation, From ba656e8f9b368d5d330593c17477bcf366f41dbc Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Wed, 3 Jun 2026 15:43:18 +0200 Subject: [PATCH 03/20] 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 84b847b203..56cc8c06ce 100644 --- a/tests/include/test/ssl_helpers.h +++ b/tests/include/test/ssl_helpers.h @@ -115,6 +115,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; @@ -145,6 +148,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 e3c0aa1cbf..fb798ac73e 100644 --- a/tests/src/test_helpers/ssl_helpers.c +++ b/tests/src/test_helpers/ssl_helpers.c @@ -2252,6 +2252,10 @@ void mbedtls_test_ssl_perform_handshake( 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_ASSERT(mbedtls_test_move_handshake_to_state(&(client.ssl), &(server.ssl), MBEDTLS_SSL_HANDSHAKE_OVER) @@ -2289,6 +2293,10 @@ void mbedtls_test_ssl_perform_handshake( 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 @@ -2315,6 +2323,11 @@ void mbedtls_test_ssl_perform_handshake( 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(options->dtls == 1); @@ -2449,6 +2462,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, options->dtls != 0 ? &client_context : NULL); From 929ce501bd757f3a8e89e605e33fa02762b49d30 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Wed, 3 Jun 2026 15:44:08 +0200 Subject: [PATCH 04/20] 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.2, 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. This bug will be fixed in a subsequent commit. Signed-off-by: Gilles Peskine --- tests/suites/test_suite_ssl.data | 12 +++++++++--- tests/suites/test_suite_ssl.function | 29 +++++++++++++++++++++++++++- 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/tests/suites/test_suite_ssl.data b/tests/suites/test_suite_ssl.data index 06e8b62303..6f611a15fc 100644 --- a/tests/suites/test_suite_ssl.data +++ b/tests/suites/test_suite_ssl.data @@ -669,13 +669,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 efc5307733..c7c5604bab 100644 --- a/tests/suites/test_suite_ssl.function +++ b/tests/suites/test_suite_ssl.function @@ -63,6 +63,31 @@ exit: } #endif +#if defined(MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED) && \ + defined(MBEDTLS_PKCS1_V15) && \ + defined(MBEDTLS_SSL_PROTO_TLS1_2) && \ + defined(MBEDTLS_RSA_C) && \ + defined(MBEDTLS_ECP_HAVE_SECP384R1) && \ + defined(MBEDTLS_SSL_PROTO_DTLS) && \ + defined(MBEDTLS_SSL_RENEGOTIATION) && \ + defined(MBEDTLS_MD_CAN_SHA256) && \ + 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_or_in_hsfraglen = 1; + break; + case MBEDTLS_SSL_IS_SERVER: + server->ssl.badmac_seen_or_in_hsfraglen = 1; + break; + } +} +#endif + typedef enum { RECOMBINE_NOMINAL, /* param: ignored */ RECOMBINE_SPLIT_FIRST, /* param: offset of split (<=0 means from end) */ @@ -3303,7 +3328,7 @@ 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_RENEGOTIATION:MBEDTLS_MD_CAN_SHA256: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); @@ -3312,6 +3337,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 e8d780f5afd5fe7c35b16fffe9f94b9f3b215e2e Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Thu, 28 May 2026 10:54:32 +0200 Subject: [PATCH 05/20] 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 ed0474272f..4491dcf264 100644 --- a/programs/test/udp_proxy.c +++ b/programs/test/udp_proxy.c @@ -108,6 +108,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" \ @@ -153,6 +159,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 */ @@ -224,6 +232,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; @@ -667,6 +685,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) @@ -720,6 +763,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) || @@ -854,6 +902,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 a9fa52a025ba35160461d923f5a9b71cd670ecb5 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Thu, 28 May 2026 11:04:52 +0200 Subject: [PATCH 06/20] 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 4491dcf264..b538933dca 100644 --- a/programs/test/udp_proxy.c +++ b/programs/test/udp_proxy.c @@ -127,6 +127,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" \ @@ -164,6 +170,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*/ @@ -294,6 +302,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) { @@ -549,6 +567,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; @@ -580,7 +627,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); @@ -904,6 +951,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 0fb05d3520bce2934b857fb70a07890999ead421 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Thu, 28 May 2026 14:03:49 +0200 Subject: [PATCH 07/20] 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 ed72095da5..c47ba5a8a1 100644 --- a/programs/ssl/ssl_client2.c +++ b/programs/ssl/ssl_client2.c @@ -92,6 +92,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 @@ -287,7 +288,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 @@ -548,6 +550,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 */ @@ -1012,6 +1015,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; @@ -1437,6 +1441,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) { @@ -1911,6 +1920,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 82c01e1168..5cbd7de006 100644 --- a/programs/ssl/ssl_server2.c +++ b/programs/ssl/ssl_server2.c @@ -374,9 +374,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" \ @@ -385,7 +382,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 @@ -536,7 +534,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 9124556b6d313eac995680d6641a4a6010464843 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Thu, 28 May 2026 19:59:10 +0200 Subject: [PATCH 08/20] 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 73515b4a92..59bc84c112 100755 --- a/tests/ssl-opt.sh +++ b/tests/ssl-opt.sh @@ -5568,6 +5568,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" @@ -5584,6 +5585,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" @@ -5601,6 +5603,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" @@ -5619,6 +5622,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" @@ -5636,6 +5640,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 @@ -5652,6 +5657,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 ee9ce31e96922e08c3aaa413e6ac6eaa34560985 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Thu, 28 May 2026 20:00:29 +0200 Subject: [PATCH 09/20] Add tests demonstrating badmac_seen_or_in_hsfraglen confusion 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 59bc84c112..a17d2b5412 100755 --- a/tests/ssl-opt.sh +++ b/tests/ssl-opt.sh @@ -12769,6 +12769,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 305bab2bbc2c96d9672f38e9de5483a378be4047 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Thu, 28 May 2026 20:52:38 +0200 Subject: [PATCH 10/20] 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 c47ba5a8a1..ef838d6d8a 100644 --- a/programs/ssl/ssl_client2.c +++ b/programs/ssl/ssl_client2.c @@ -314,7 +314,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 "" @@ -521,7 +521,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 */ @@ -1228,7 +1228,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) { @@ -2549,7 +2549,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). @@ -2924,6 +2924,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 397db25dc37250a93ce5907eaca2aa36d979e0b4 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Thu, 28 May 2026 20:52:38 +0200 Subject: [PATCH 11/20] Add tests demonstrating badmac_seen_or_in_hsfraglen confusion 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 a17d2b5412..4f8b4c1d12 100755 --- a/tests/ssl-opt.sh +++ b/tests/ssl-opt.sh @@ -12789,6 +12789,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" \ @@ -12807,6 +12826,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" \ @@ -12849,6 +12904,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 24c2875cb0eb655fffe7b6a7a956f34bacb7b0a4 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Sun, 10 May 2026 14:55:51 +0200 Subject: [PATCH 12/20] Fix confusion between ssl->badmac_seen and ssl->in_hsfraglen Since Mbed TLS 3.6.3, the field `badmac_seen_or_in_hsfraglen` in the `mbedtls_ssl_context` structure has a dual purpose: it is the new name of `badmac_seen` for DTLS, and it has a new meaning `in_hsfraglen` for TLS. This was done to add support for handshake message fragmentation in TLS while preserving the library ABI. There was a bug in some places in the code, which reads the dual-purpose field in the wrong protocol. Specifically: * `mbedtls_ssl_prepare_handshake_record()` (beginning): mistakenly reads `badmac_seen_or_in_hsfraglen` in DTLS to mean `in_hsfraglen`. (DTLS handshake defragmentation does not use `in_hsfraglen`.) As a consequence, in DTLS, after receiving a record with a bad MAC, the field `ssl->in_hslen` was not correctly updated, which causes problems further in handshake message processing. * `mbedtls_ssl_prepare_handshake_record()` (end): correctly updates `badmac_seen_or_in_hsfraglen` in TLS with the `in_hsfraglen` meaning. * `ssl_consume_current_message()`: incorrectly read `badmac_seen_or_in_hsfraglen` in DTLS to mean `in_hsfraglen`, and thus stops processing messages after receiving a record with a bad MAC. * `ssl_get_next_record()`: accesses `badmac_seen_or_in_hsfraglen` to mean `badmac_seen` in DTLS. No bug here. * `mbedtls_ssl_handle_message_type()`: reads `badmac_seen_or_in_hsfraglen` to mean `in_hsfraglen` in TLS. No bug here. * `mbedtls_ssl_session_reset_msg_layer()`: both fields need to be reset when not partial, and a partial reset only applies to DTLS where we do want to preserve `badmac_seen` in a partial reset. No bug here. * `mbedtls_ssl_context_save()`, `mbedtls_ssl_context_load()`: saving `badmac_seen` in DTLS is desired and done. Saving `in_hsfraglen` in TLS is harmless. To fix this, in `ssl_msg.c`, always access the dual-purpose field `badmac_seen_or_in_hsfraglen` through getters and setters that are specific to one purpose and that check that the purpose matches the protocol. Signed-off-by: Gilles Peskine --- library/ssl_misc.h | 63 ++++++++++++++++++++++++++++++++++++++++++++++ library/ssl_msg.c | 44 ++++++++++++++++---------------- 2 files changed, 86 insertions(+), 21 deletions(-) diff --git a/library/ssl_misc.h b/library/ssl_misc.h index 218a30d408..2f509f32e9 100644 --- a/library/ssl_misc.h +++ b/library/ssl_misc.h @@ -484,6 +484,69 @@ static inline size_t mbedtls_ssl_get_input_buflen(const mbedtls_ssl_context *ctx } #endif +static inline unsigned mbedtls_ssl_get_badmac_seen(const mbedtls_ssl_context *ssl) +{ +#if defined(MBEDTLS_SSL_PROTO_DTLS) + if (ssl->conf->transport == MBEDTLS_SSL_TRANSPORT_DATAGRAM) { + return ssl->badmac_seen_or_in_hsfraglen; + } +#endif + (void) ssl; + return 0; +} + +static inline void mbedtls_ssl_set_badmac_seen(mbedtls_ssl_context *ssl, + unsigned badmac_seen) +{ + ssl->badmac_seen_or_in_hsfraglen = badmac_seen; +} + +#if defined(MBEDTLS_SSL_PROTO_DTLS) +#define MBEDTLS_SSL_SET_BADMAC_SEEN(ssl, badmac_seen) \ + do { \ + if ((ssl)->conf->transport != MBEDTLS_SSL_TRANSPORT_DATAGRAM) { \ + MBEDTLS_SSL_DEBUG_RET(1, ("Internal error: trying to set badmac_seen in TLS"), \ + MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED); \ + return MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED; \ + } \ + mbedtls_ssl_set_badmac_seen(ssl, badmac_seen); \ + } while (0) +#else +#define MBEDTLS_SSL_SET_BADMAC_SEEN(ssl, badmac_seen) \ + mbedtls_ssl_set_badmac_seen(ssl, badmac_seen) +#endif + +static inline unsigned mbedtls_ssl_get_in_hsfraglen(const mbedtls_ssl_context *ssl) +{ +#if defined(MBEDTLS_SSL_PROTO_DTLS) + if (ssl->conf->transport == MBEDTLS_SSL_TRANSPORT_DATAGRAM) { + return 0; + } +#endif + return ssl->badmac_seen_or_in_hsfraglen; +} + +static inline void mbedtls_ssl_set_in_hsfraglen(mbedtls_ssl_context *ssl, + unsigned in_hsfraglen) +{ + ssl->badmac_seen_or_in_hsfraglen = in_hsfraglen; +} + +#if defined(MBEDTLS_SSL_PROTO_DTLS) +#define MBEDTLS_SSL_SET_IN_HSFRAGLEN(ssl, in_hsfraglen) \ + do { \ + if ((ssl)->conf->transport == MBEDTLS_SSL_TRANSPORT_DATAGRAM) { \ + MBEDTLS_SSL_DEBUG_RET(1, ("Internal error: trying to set in_hsfraglen in DTLS"), \ + MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED); \ + return MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED; \ + } \ + mbedtls_ssl_set_in_hsfraglen(ssl, in_hsfraglen); \ + } while (0) +#else +#define MBEDTLS_SSL_SET_IN_HSFRAGLEN(ssl, in_hsfraglen) \ + mbedtls_ssl_set_in_hsfraglen(ssl, in_hsfraglen) +#endif + /* * TLS extension flags (for extensions with outgoing ServerHello content * that need it (e.g. for RENEGOTIATION_INFO the server already knows because diff --git a/library/ssl_msg.c b/library/ssl_msg.c index b0ab608350..be4406b1a2 100644 --- a/library/ssl_msg.c +++ b/library/ssl_msg.c @@ -3224,7 +3224,10 @@ static uint32_t ssl_get_hs_total_len(mbedtls_ssl_context const *ssl) int mbedtls_ssl_prepare_handshake_record(mbedtls_ssl_context *ssl) { - if (ssl->badmac_seen_or_in_hsfraglen == 0) { + /* Set handshake record length if this is the first fragment. + * Do it unconditionally for DTLS, for which handshake fragmentation + * is handled completely differently. */ + if (mbedtls_ssl_get_in_hsfraglen(ssl) == 0) { /* The handshake message must at least include the header. * We may not have the full message yet in case of fragmentation. * To simplify the code, we insist on having the header (and in @@ -3364,9 +3367,10 @@ int mbedtls_ssl_prepare_handshake_record(mbedtls_ssl_context *ssl) ssl->in_buf + MBEDTLS_SSL_SEQUENCE_NUMBER_LEN; unsigned char *const payload_start = reassembled_record_start + mbedtls_ssl_in_hdr_len(ssl); - unsigned char *payload_end = payload_start + ssl->badmac_seen_or_in_hsfraglen; + unsigned in_hsfraglen = mbedtls_ssl_get_in_hsfraglen(ssl); + unsigned char *payload_end = payload_start + in_hsfraglen; /* How many more bytes we want to have a complete handshake message. */ - const size_t hs_remain = ssl->in_hslen - ssl->badmac_seen_or_in_hsfraglen; + const size_t hs_remain = ssl->in_hslen - in_hsfraglen; /* How many bytes of the current record are part of the first * handshake message. There may be more handshake messages (possibly * incomplete) in the same record; if so, we leave them after the @@ -3379,15 +3383,12 @@ int mbedtls_ssl_prepare_handshake_record(mbedtls_ssl_context *ssl) MBEDTLS_SSL_DEBUG_MSG(3, ("%s handshake fragment: %" MBEDTLS_PRINTF_SIZET ", %u..%u of %" MBEDTLS_PRINTF_SIZET, - (ssl->badmac_seen_or_in_hsfraglen != 0 ? - "subsequent" : - hs_this_fragment_len == ssl->in_hslen ? - "sole" : + (in_hsfraglen != 0 ? "subsequent" : + hs_this_fragment_len == ssl->in_hslen ? "sole" : "initial"), ssl->in_msglen, - ssl->badmac_seen_or_in_hsfraglen, - ssl->badmac_seen_or_in_hsfraglen + - (unsigned) hs_this_fragment_len, + in_hsfraglen, + in_hsfraglen + (unsigned) hs_this_fragment_len, ssl->in_hslen)); /* Move the received handshake fragment to have the whole message @@ -3417,20 +3418,21 @@ int mbedtls_ssl_prepare_handshake_record(mbedtls_ssl_context *ssl) } memmove(payload_end, ssl->in_msg, ssl->in_msglen); - ssl->badmac_seen_or_in_hsfraglen += (unsigned) ssl->in_msglen; + in_hsfraglen += (unsigned) ssl->in_msglen; payload_end += ssl->in_msglen; - if (ssl->badmac_seen_or_in_hsfraglen < ssl->in_hslen) { + if (in_hsfraglen < ssl->in_hslen) { MBEDTLS_SSL_DEBUG_MSG(3, ("Prepare: waiting for more handshake fragments " "%u/%" MBEDTLS_PRINTF_SIZET, - ssl->badmac_seen_or_in_hsfraglen, ssl->in_hslen)); + in_hsfraglen, ssl->in_hslen)); ssl->in_hdr = payload_end; ssl->in_msglen = 0; + MBEDTLS_SSL_SET_IN_HSFRAGLEN(ssl, in_hsfraglen); mbedtls_ssl_update_in_pointers(ssl); return MBEDTLS_ERR_SSL_CONTINUE_PROCESSING; } else { - ssl->in_msglen = ssl->badmac_seen_or_in_hsfraglen; - ssl->badmac_seen_or_in_hsfraglen = 0; + ssl->in_msglen = in_hsfraglen; + MBEDTLS_SSL_SET_IN_HSFRAGLEN(ssl, 0); ssl->in_hdr = reassembled_record_start; mbedtls_ssl_update_in_pointers(ssl); @@ -4785,11 +4787,11 @@ static int ssl_consume_current_message(mbedtls_ssl_context *ssl) return MBEDTLS_ERR_SSL_INTERNAL_ERROR; } - if (ssl->badmac_seen_or_in_hsfraglen != 0) { + if (mbedtls_ssl_get_in_hsfraglen(ssl) != 0) { /* Not all handshake fragments have arrived, do not consume. */ MBEDTLS_SSL_DEBUG_MSG(3, ("Consume: waiting for more handshake fragments " "%u/%" MBEDTLS_PRINTF_SIZET, - ssl->badmac_seen_or_in_hsfraglen, ssl->in_hslen)); + mbedtls_ssl_get_in_hsfraglen(ssl), ssl->in_hslen)); return 0; } @@ -5147,8 +5149,9 @@ static int ssl_get_next_record(mbedtls_ssl_context *ssl) } if (ssl->conf->badmac_limit != 0) { - ++ssl->badmac_seen_or_in_hsfraglen; - if (ssl->badmac_seen_or_in_hsfraglen >= ssl->conf->badmac_limit) { + unsigned badmac_seen = mbedtls_ssl_get_badmac_seen(ssl) + 1; + MBEDTLS_SSL_SET_BADMAC_SEEN(ssl, badmac_seen); + if (badmac_seen >= ssl->conf->badmac_limit) { MBEDTLS_SSL_DEBUG_MSG(1, ("too many records with bad MAC")); return MBEDTLS_ERR_SSL_INVALID_MAC; } @@ -5208,8 +5211,7 @@ int mbedtls_ssl_handle_message_type(mbedtls_ssl_context *ssl) * we don't accept any other message type. For TLS 1.3, the spec forbids * interleaving other message types between handshake fragments. For TLS * 1.2, the spec does not forbid it but we do. */ - if (ssl->conf->transport == MBEDTLS_SSL_TRANSPORT_STREAM && - ssl->badmac_seen_or_in_hsfraglen != 0 && + if (mbedtls_ssl_get_in_hsfraglen(ssl) != 0 && ssl->in_msgtype != MBEDTLS_SSL_MSG_HANDSHAKE) { MBEDTLS_SSL_DEBUG_MSG(1, ("non-handshake message in the middle" " of a fragmented handshake message")); From a818c0a8dd58569069a7c8eae5a00fcc29bb8148 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Wed, 3 Jun 2026 16:19:04 +0200 Subject: [PATCH 13/20] Changelog entry for badmac_seen_or_in_hsfraglen confusion Signed-off-by: Gilles Peskine --- ChangeLog.d/badmac_seen_or_in_hsfraglen.txt | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 ChangeLog.d/badmac_seen_or_in_hsfraglen.txt diff --git a/ChangeLog.d/badmac_seen_or_in_hsfraglen.txt b/ChangeLog.d/badmac_seen_or_in_hsfraglen.txt new file mode 100644 index 0000000000..430ecf6069 --- /dev/null +++ b/ChangeLog.d/badmac_seen_or_in_hsfraglen.txt @@ -0,0 +1,4 @@ +Security + * Fix renegotiation failing and potentially causing a buffer overflow + in DTLS when badmac_limit is enabled. Reported by Haruki Oyama. + CVE-2026-50713. From a939d850c7ff9310beeb97aa9fbf24eaf8af6a83 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Mon, 15 Jun 2026 13:08:43 +0200 Subject: [PATCH 14/20] 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 | 68 ++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+) diff --git a/tests/suites/test_suite_ssl.data b/tests/suites/test_suite_ssl.data index 6f611a15fc..8957fcc0b1 100644 --- a/tests/suites/test_suite_ssl.data +++ b/tests/suites/test_suite_ssl.data @@ -592,6 +592,14 @@ Handshake, select ECDH-ECDSA-WITH-CAMELLIA-256-CBC-SHA384, opaque, missing usage depends_on:MBEDTLS_MD_CAN_SHA384:MBEDTLS_SSL_HAVE_CAMELLIA:MBEDTLS_SSL_HAVE_CBC:MBEDTLS_ECP_HAVE_SECP256R1:MBEDTLS_ECP_HAVE_SECP384R1:MBEDTLS_KEY_EXCHANGE_ECDH_ECDSA_ENABLED:MBEDTLS_USE_PSA_CRYPTO handshake_ciphersuite_select:"TLS-ECDH-ECDSA-WITH-CAMELLIA-256-CBC-SHA384":MBEDTLS_PK_ECDSA:"":PSA_ALG_ECDSA(PSA_ALG_ANY_HASH):PSA_ALG_ECDH:PSA_KEY_USAGE_SIGN_HASH: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 c7c5604bab..8c04641fd5 100644 --- a/tests/suites/test_suite_ssl.function +++ b/tests/suites/test_suite_ssl.function @@ -63,6 +63,40 @@ exit: } #endif +typedef enum { + PRE_HANDSHAKE_BADMAC_SEEN = 1u << 0, +} pre_handshake_tweaks_t; + +#if defined(MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED) && \ + defined(MBEDTLS_PKCS1_V15) && \ + defined(MBEDTLS_RSA_C) && \ + defined(MBEDTLS_ECP_HAVE_SECP384R1) && \ + defined(MBEDTLS_MD_CAN_SHA256) && \ + defined(MBEDTLS_PK_HAVE_ECC_KEYS) && \ + 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_or_in_hsfraglen = 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_SSL_HANDSHAKE_WITH_CERT_ENABLED) && \ defined(MBEDTLS_PKCS1_V15) && \ defined(MBEDTLS_SSL_PROTO_TLS1_2) && \ @@ -3057,6 +3091,40 @@ exit: } /* END_CASE */ +/* BEGIN_CASE depends_on:MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED:MBEDTLS_PKCS1_V15:MBEDTLS_RSA_C:MBEDTLS_ECP_HAVE_SECP384R1:MBEDTLS_MD_CAN_SHA256:MBEDTLS_PK_HAVE_ECC_KEYS: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:MBEDTLS_PKCS1_V15:MBEDTLS_RSA_C:MBEDTLS_ECP_HAVE_SECP384R1:MBEDTLS_MD_CAN_SHA256 */ void app_data(int mfl, int cli_msg_len, int srv_msg_len, int expected_cli_fragments, From a145116bffef73454c13ef39f5e3de5569e5dc34 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Mon, 15 Jun 2026 13:37:54 +0200 Subject: [PATCH 15/20] 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 4f8b4c1d12..034186898a 100755 --- a/tests/ssl-opt.sh +++ b/tests/ssl-opt.sh @@ -12774,7 +12774,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 \ @@ -12796,7 +12796,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" \ @@ -12813,7 +12813,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 \ @@ -12833,7 +12833,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" \ @@ -12851,7 +12851,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" \ @@ -12911,7 +12911,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" \ @@ -12930,7 +12930,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 1db3c1c9a034654e49d153c908afc08ade27d17f Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Mon, 15 Jun 2026 13:58:49 +0200 Subject: [PATCH 16/20] 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 ef838d6d8a..f8747d89fd 100644 --- a/programs/ssl/ssl_client2.c +++ b/programs/ssl/ssl_client2.c @@ -2926,9 +2926,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 034186898a..0875181344 100755 --- a/tests/ssl-opt.sh +++ b/tests/ssl-opt.sh @@ -12769,6 +12769,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" \ @@ -12862,6 +12870,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" \ @@ -12884,6 +12901,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 1453fda32caa4df4df4eb90c12356c6be90bfc25 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Mon, 15 Jun 2026 18:22:11 +0200 Subject: [PATCH 17/20] 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 0875181344..2283b1c7bb 100755 --- a/tests/ssl-opt.sh +++ b/tests/ssl-opt.sh @@ -12798,7 +12798,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 \ @@ -12835,25 +12835,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 \ @@ -12928,43 +12910,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" \ From 93460dafc7ad4bc106c84227be27d8fdbf16dc53 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Mon, 15 Jun 2026 18:39:19 +0200 Subject: [PATCH 18/20] Reject MBEDTLS_SSL_SET_BADMAC_SEEN at compile time if DTLS is disabled Signed-off-by: Gilles Peskine --- library/ssl_misc.h | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/library/ssl_misc.h b/library/ssl_misc.h index 2f509f32e9..e4a01993f3 100644 --- a/library/ssl_misc.h +++ b/library/ssl_misc.h @@ -495,13 +495,13 @@ static inline unsigned mbedtls_ssl_get_badmac_seen(const mbedtls_ssl_context *ss return 0; } +#if defined(MBEDTLS_SSL_PROTO_DTLS) static inline void mbedtls_ssl_set_badmac_seen(mbedtls_ssl_context *ssl, unsigned badmac_seen) { ssl->badmac_seen_or_in_hsfraglen = badmac_seen; } -#if defined(MBEDTLS_SSL_PROTO_DTLS) #define MBEDTLS_SSL_SET_BADMAC_SEEN(ssl, badmac_seen) \ do { \ if ((ssl)->conf->transport != MBEDTLS_SSL_TRANSPORT_DATAGRAM) { \ @@ -512,8 +512,11 @@ static inline void mbedtls_ssl_set_badmac_seen(mbedtls_ssl_context *ssl, mbedtls_ssl_set_badmac_seen(ssl, badmac_seen); \ } while (0) #else -#define MBEDTLS_SSL_SET_BADMAC_SEEN(ssl, badmac_seen) \ - mbedtls_ssl_set_badmac_seen(ssl, badmac_seen) +/* We shouldn't be trying to set badmac_seen if DTLS support is disabled + * at compile time. If this is called from a code block that checks for the + * DTLS protocol at run time, it should be guarded by + * defined(MBEDTLS_SSL_PROTO_DTLS). */ +#undef MBEDTLS_SSL_SET_BADMAC_SEEN #endif static inline unsigned mbedtls_ssl_get_in_hsfraglen(const mbedtls_ssl_context *ssl) From 7c28ccf8abd06f8b468090f5b30f9b0409a2c023 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Tue, 16 Jun 2026 14:56:59 +0200 Subject: [PATCH 19/20] Use functions instead of macros for badmac_seen_or_in_hsfraglen setters Let the caller handle returning on error. This is less surprising, and it's more flexible in case the caller needs to perform some cleanup (which is currently not the case). Signed-off-by: Gilles Peskine --- library/ssl_misc.h | 57 +++++++++++++++++++--------------------------- library/ssl_msg.c | 12 +++++++--- 2 files changed, 32 insertions(+), 37 deletions(-) diff --git a/library/ssl_misc.h b/library/ssl_misc.h index e4a01993f3..db31548cdf 100644 --- a/library/ssl_misc.h +++ b/library/ssl_misc.h @@ -496,27 +496,22 @@ static inline unsigned mbedtls_ssl_get_badmac_seen(const mbedtls_ssl_context *ss } #if defined(MBEDTLS_SSL_PROTO_DTLS) -static inline void mbedtls_ssl_set_badmac_seen(mbedtls_ssl_context *ssl, - unsigned badmac_seen) -{ - ssl->badmac_seen_or_in_hsfraglen = badmac_seen; -} - -#define MBEDTLS_SSL_SET_BADMAC_SEEN(ssl, badmac_seen) \ - do { \ - if ((ssl)->conf->transport != MBEDTLS_SSL_TRANSPORT_DATAGRAM) { \ - MBEDTLS_SSL_DEBUG_RET(1, ("Internal error: trying to set badmac_seen in TLS"), \ - MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED); \ - return MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED; \ - } \ - mbedtls_ssl_set_badmac_seen(ssl, badmac_seen); \ - } while (0) -#else /* We shouldn't be trying to set badmac_seen if DTLS support is disabled * at compile time. If this is called from a code block that checks for the * DTLS protocol at run time, it should be guarded by * defined(MBEDTLS_SSL_PROTO_DTLS). */ -#undef MBEDTLS_SSL_SET_BADMAC_SEEN +MBEDTLS_CHECK_RETURN_CRITICAL +static inline int mbedtls_ssl_set_badmac_seen(mbedtls_ssl_context *ssl, + unsigned badmac_seen) +{ + if ((ssl)->conf->transport != MBEDTLS_SSL_TRANSPORT_DATAGRAM) { + MBEDTLS_SSL_DEBUG_RET(1, ("Internal error: trying to set badmac_seen in TLS"), + MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED); + return MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED; + } + ssl->badmac_seen_or_in_hsfraglen = badmac_seen; + return 0; +} #endif static inline unsigned mbedtls_ssl_get_in_hsfraglen(const mbedtls_ssl_context *ssl) @@ -529,26 +524,20 @@ static inline unsigned mbedtls_ssl_get_in_hsfraglen(const mbedtls_ssl_context *s return ssl->badmac_seen_or_in_hsfraglen; } -static inline void mbedtls_ssl_set_in_hsfraglen(mbedtls_ssl_context *ssl, - unsigned in_hsfraglen) +MBEDTLS_CHECK_RETURN_CRITICAL +static inline int mbedtls_ssl_set_in_hsfraglen(mbedtls_ssl_context *ssl, + unsigned in_hsfraglen) { - ssl->badmac_seen_or_in_hsfraglen = in_hsfraglen; -} - #if defined(MBEDTLS_SSL_PROTO_DTLS) -#define MBEDTLS_SSL_SET_IN_HSFRAGLEN(ssl, in_hsfraglen) \ - do { \ - if ((ssl)->conf->transport == MBEDTLS_SSL_TRANSPORT_DATAGRAM) { \ - MBEDTLS_SSL_DEBUG_RET(1, ("Internal error: trying to set in_hsfraglen in DTLS"), \ - MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED); \ - return MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED; \ - } \ - mbedtls_ssl_set_in_hsfraglen(ssl, in_hsfraglen); \ - } while (0) -#else -#define MBEDTLS_SSL_SET_IN_HSFRAGLEN(ssl, in_hsfraglen) \ - mbedtls_ssl_set_in_hsfraglen(ssl, in_hsfraglen) + if ((ssl)->conf->transport == MBEDTLS_SSL_TRANSPORT_DATAGRAM) { + MBEDTLS_SSL_DEBUG_RET(1, ("Internal error: trying to set in_hsfraglen in DTLS"), + MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED); + return MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED; + } #endif + ssl->badmac_seen_or_in_hsfraglen = in_hsfraglen; + return 0; +} /* * TLS extension flags (for extensions with outgoing ServerHello content diff --git a/library/ssl_msg.c b/library/ssl_msg.c index be4406b1a2..b4fa74ca9e 100644 --- a/library/ssl_msg.c +++ b/library/ssl_msg.c @@ -3427,12 +3427,16 @@ int mbedtls_ssl_prepare_handshake_record(mbedtls_ssl_context *ssl) in_hsfraglen, ssl->in_hslen)); ssl->in_hdr = payload_end; ssl->in_msglen = 0; - MBEDTLS_SSL_SET_IN_HSFRAGLEN(ssl, in_hsfraglen); + if (mbedtls_ssl_set_in_hsfraglen(ssl, in_hsfraglen) != 0) { + return MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED; + } mbedtls_ssl_update_in_pointers(ssl); return MBEDTLS_ERR_SSL_CONTINUE_PROCESSING; } else { ssl->in_msglen = in_hsfraglen; - MBEDTLS_SSL_SET_IN_HSFRAGLEN(ssl, 0); + if (mbedtls_ssl_set_in_hsfraglen(ssl, 0) != 0) { + return MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED; + } ssl->in_hdr = reassembled_record_start; mbedtls_ssl_update_in_pointers(ssl); @@ -5150,7 +5154,9 @@ static int ssl_get_next_record(mbedtls_ssl_context *ssl) if (ssl->conf->badmac_limit != 0) { unsigned badmac_seen = mbedtls_ssl_get_badmac_seen(ssl) + 1; - MBEDTLS_SSL_SET_BADMAC_SEEN(ssl, badmac_seen); + if (mbedtls_ssl_set_badmac_seen(ssl, badmac_seen) != 0) { + return MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED; + } if (badmac_seen >= ssl->conf->badmac_limit) { MBEDTLS_SSL_DEBUG_MSG(1, ("too many records with bad MAC")); return MBEDTLS_ERR_SSL_INVALID_MAC; From 794e1a89db1fdf101ce39134647d08b0bd936e3f Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Tue, 16 Jun 2026 15:04:35 +0200 Subject: [PATCH 20/20] Add documentation for the new getters and setters Signed-off-by: Gilles Peskine --- library/ssl_misc.h | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/library/ssl_misc.h b/library/ssl_misc.h index db31548cdf..864314d1d8 100644 --- a/library/ssl_misc.h +++ b/library/ssl_misc.h @@ -484,6 +484,16 @@ static inline size_t mbedtls_ssl_get_input_buflen(const mbedtls_ssl_context *ctx } #endif +/** Get `ssl->badmac_seen`. This field is encoded as + * mbedtls_ssl_context::badmac_seen_or_in_hsfraglen in DTLS contexts, + * and doesn't exist in TLS contexts. + * + * \param[in] ssl The SSL context to read. + * + * \return In DTLS, the value of `badmac_seen`. In TLS, 0 (there can't have + * been a record with a bad MAC in TLS, since those abort the + * connection immediately). + */ static inline unsigned mbedtls_ssl_get_badmac_seen(const mbedtls_ssl_context *ssl) { #if defined(MBEDTLS_SSL_PROTO_DTLS) @@ -500,6 +510,15 @@ static inline unsigned mbedtls_ssl_get_badmac_seen(const mbedtls_ssl_context *ss * at compile time. If this is called from a code block that checks for the * DTLS protocol at run time, it should be guarded by * defined(MBEDTLS_SSL_PROTO_DTLS). */ +/** Set `ssl->badmac_seen`. This field is encoded as + * mbedtls_ssl_context::badmac_seen_or_in_hsfraglen in DTLS contexts, + * and doesn't exist in TLS contexts. + * + * \param[in,out] ssl The SSL context to modify. + * \param badmac_seen The new value of `badmac_seen`. + * + * \return 0 in DTLS, #MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED in TLS. + */ MBEDTLS_CHECK_RETURN_CRITICAL static inline int mbedtls_ssl_set_badmac_seen(mbedtls_ssl_context *ssl, unsigned badmac_seen) @@ -514,6 +533,16 @@ static inline int mbedtls_ssl_set_badmac_seen(mbedtls_ssl_context *ssl, } #endif +/** Get `ssl->in_hsfraglen`. This field is encoded as + * mbedtls_ssl_context::badmac_seen_or_in_hsfraglen in TLS contexts, + * and doesn't exist in DTLS contexts. + * + * \param[in] ssl The SSL context to read. + * + * \return In TLS, the value of `in_hsfraglen`. In DTLS, 0 (handshake + * message defragmentation is handled different in DTLS, and + * does not use this field). + */ static inline unsigned mbedtls_ssl_get_in_hsfraglen(const mbedtls_ssl_context *ssl) { #if defined(MBEDTLS_SSL_PROTO_DTLS) @@ -524,6 +553,15 @@ static inline unsigned mbedtls_ssl_get_in_hsfraglen(const mbedtls_ssl_context *s return ssl->badmac_seen_or_in_hsfraglen; } +/** Set `ssl->in_hsfraglen`. This field is encoded as + * mbedtls_ssl_context::badmac_seen_or_in_hsfraglen in TLS contexts, + * and doesn't exist in DTLS contexts. + * + * \param[in,out] ssl The SSL context to modify. + * \param in_hsfraglen The new value of `in_hsfraglen`. + * + * \return 0 in TLS, #MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED in DTLS. + */ MBEDTLS_CHECK_RETURN_CRITICAL static inline int mbedtls_ssl_set_in_hsfraglen(mbedtls_ssl_context *ssl, unsigned in_hsfraglen)