Merge pull request #10826 from yosuke-wolfssl/fix/f_6539

Preserve DTLS association on invalid record headers during handshake
This commit is contained in:
David Garske
2026-07-09 10:11:39 -07:00
committed by GitHub
3 changed files with 154 additions and 1 deletions
+3 -1
View File
@@ -23351,7 +23351,9 @@ static int DtlsShouldDrop(WOLFSSL* ssl, int retcode)
if ((ssl->options.handShakeDone && retcode != 0)
|| retcode == WC_NO_ERR_TRACE(SEQUENCE_ERROR)
|| retcode == WC_NO_ERR_TRACE(DTLS_CID_ERROR)
|| retcode == WC_NO_ERR_TRACE(DTLS_PARTIAL_RECORD_READ)) {
|| retcode == WC_NO_ERR_TRACE(DTLS_PARTIAL_RECORD_READ)
|| retcode == WC_NO_ERR_TRACE(UNKNOWN_RECORD_TYPE)
|| retcode == WC_NO_ERR_TRACE(LENGTH_ERROR)) {
WOLFSSL_MSG_EX("Silently dropping DTLS message: %d", retcode);
return 1;
}
+148
View File
@@ -1158,6 +1158,150 @@ static int test_dtls_communication(WOLFSSL *s, WOLFSSL *c)
return EXPECT_RESULT();
}
/* Drive a DTLS handshake until the dropping peer reads a genuine flight record,
* corrupt its header, confirm the peer silently discards it, then re-deliver
* the record and confirm the handshake still completes. */
static int test_dtls_drop_invalid_record(method_provider method_c,
method_provider method_s, int dropServer, int unknownType)
{
EXPECT_DECLS;
WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL;
WOLFSSL *ssl_c = NULL, *ssl_s = NULL;
struct test_memio_ctx test_ctx;
/* Holds one cookie-exchange/HRR/ClientHello datagram. Pinning a classical
* group below keeps that flight small, and the ExpectIntLE(recSz) guard
* fails loudly rather than overflowing. */
unsigned char rec[2048];
unsigned char* recBuf;
int recSz = 0;
int readIsClient = dropServer ? 0 : 1;
#if defined(HAVE_SUPPORTED_CURVES) && !defined(NO_ECC_SECP) && \
!defined(NO_ECC256)
int groups[1] = { WOLFSSL_ECC_SECP256R1 };
#endif
XMEMSET(&test_ctx, 0, sizeof(test_ctx));
recBuf = readIsClient ? test_ctx.c_buff : test_ctx.s_buff;
ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s,
method_c, method_s), 0);
#if defined(HAVE_SUPPORTED_CURVES) && !defined(NO_ECC_SECP) && \
!defined(NO_ECC256)
/* Pin a classical group so a PQC-default build cannot fragment the first
* flight across datagrams and break the corrupt/replay model. Set it on the
* WOLFSSL objects, which test_memio_setup() has already created. */
ExpectIntEQ(wolfSSL_set_groups(ssl_c, groups, 1), WOLFSSL_SUCCESS);
ExpectIntEQ(wolfSSL_set_groups(ssl_s, groups, 1), WOLFSSL_SUCCESS);
#endif
/* Client sends the ClientHello into the server's buffer. */
wolfSSL_SetLoggingPrefix("client");
ExpectIntEQ(wolfSSL_connect(ssl_c), WOLFSSL_FATAL_ERROR);
ExpectIntEQ(wolfSSL_get_error(ssl_c, WOLFSSL_FATAL_ERROR),
WOLFSSL_ERROR_WANT_READ);
/* When dropping at the client, let the server produce its first flight into
* the client's buffer. */
if (!dropServer) {
wolfSSL_SetLoggingPrefix("server");
ExpectIntEQ(wolfSSL_accept(ssl_s), WOLFSSL_FATAL_ERROR);
ExpectIntEQ(wolfSSL_get_error(ssl_s, WOLFSSL_FATAL_ERROR),
WOLFSSL_ERROR_WANT_READ);
}
/* Corrupt then replay the datagram the dropping peer is about to read. This
* assumes a single-datagram flight (asserted below) so re-injecting cannot
* merge record boundaries or drop other queued datagrams. */
ExpectIntEQ(readIsClient ? test_ctx.c_msg_count : test_ctx.s_msg_count, 1);
recSz = readIsClient ? test_ctx.c_len : test_ctx.s_len;
ExpectIntGT(recSz, DTLS_RECORD_HEADER_SZ);
ExpectIntLE(recSz, (int)sizeof(rec));
if (EXPECT_SUCCESS()) {
XMEMCPY(rec, recBuf, (size_t)recSz);
if (unknownType) {
/* Not a valid ContentType, and not a DTLS 1.3 unified-header byte
* (those have top bits 001, i.e. 0x20-0x3F). */
recBuf[0] = 0x63;
}
else {
recBuf[DTLS_RECORD_HEADER_SZ - 2] = 0xff;
recBuf[DTLS_RECORD_HEADER_SZ - 1] = 0xff;
}
}
/* The dropping peer must silently discard the datagram: no fatal error and
* nothing sent back to the other peer. */
if (dropServer) {
wolfSSL_SetLoggingPrefix("server");
ExpectIntEQ(wolfSSL_accept(ssl_s), WOLFSSL_FATAL_ERROR);
ExpectIntEQ(wolfSSL_get_error(ssl_s, WOLFSSL_FATAL_ERROR),
WOLFSSL_ERROR_WANT_READ);
ExpectIntEQ(test_ctx.c_len, 0);
}
else {
wolfSSL_SetLoggingPrefix("client");
ExpectIntEQ(wolfSSL_connect(ssl_c), WOLFSSL_FATAL_ERROR);
ExpectIntEQ(wolfSSL_get_error(ssl_c, WOLFSSL_FATAL_ERROR),
WOLFSSL_ERROR_WANT_READ);
ExpectIntEQ(test_ctx.s_len, 0);
}
/* Re-deliver the genuine record and finish the handshake to prove the
* association was preserved. */
test_memio_clear_buffer(&test_ctx, readIsClient);
ExpectIntEQ(test_memio_inject_message(&test_ctx, readIsClient,
(const char*)rec, recSz),
0);
ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0);
ExpectIntEQ(test_dtls_communication(ssl_s, ssl_c), TEST_SUCCESS);
wolfSSL_SetLoggingPrefix(NULL);
wolfSSL_free(ssl_c);
wolfSSL_free(ssl_s);
wolfSSL_CTX_free(ctx_c);
wolfSSL_CTX_free(ctx_s);
return EXPECT_RESULT();
}
/* A DTLS peer must silently discard a handshake record whose header fails
* validation (UNKNOWN_RECORD_TYPE, LENGTH_ERROR) and still finish the handshake.
* Cover client and server paths for DTLS 1.2 and 1.3. */
int test_dtls_drop_invalid_record_during_handshake(void)
{
EXPECT_DECLS;
/* Client drops a corrupted server flight: unknown type, then over-length. */
ExpectIntEQ(test_dtls_drop_invalid_record(wolfDTLSv1_2_client_method,
wolfDTLSv1_2_server_method, 0, 1), TEST_SUCCESS);
ExpectIntEQ(test_dtls_drop_invalid_record(wolfDTLSv1_2_client_method,
wolfDTLSv1_2_server_method, 0, 0), TEST_SUCCESS);
/* Server drops a ClientHello with a bad record header: over-length, then
* unknown ContentType (dropped by the new UNKNOWN_RECORD_TYPE clause, which
* precedes the server-side non-stateful drop branch). */
ExpectIntEQ(test_dtls_drop_invalid_record(wolfDTLSv1_2_client_method,
wolfDTLSv1_2_server_method, 1, 0), TEST_SUCCESS);
ExpectIntEQ(test_dtls_drop_invalid_record(wolfDTLSv1_2_client_method,
wolfDTLSv1_2_server_method, 1, 1), TEST_SUCCESS);
#ifdef WOLFSSL_DTLS13
/* Same silent-drop behavior on the DTLS 1.3 receive path (all four
* side/corruption combinations). */
ExpectIntEQ(test_dtls_drop_invalid_record(wolfDTLSv1_3_client_method,
wolfDTLSv1_3_server_method, 0, 1), TEST_SUCCESS);
ExpectIntEQ(test_dtls_drop_invalid_record(wolfDTLSv1_3_client_method,
wolfDTLSv1_3_server_method, 0, 0), TEST_SUCCESS);
ExpectIntEQ(test_dtls_drop_invalid_record(wolfDTLSv1_3_client_method,
wolfDTLSv1_3_server_method, 1, 0), TEST_SUCCESS);
ExpectIntEQ(test_dtls_drop_invalid_record(wolfDTLSv1_3_client_method,
wolfDTLSv1_3_server_method, 1, 1), TEST_SUCCESS);
#endif
return EXPECT_RESULT();
}
#if defined(WOLFSSL_DTLS13) && !defined(WOLFSSL_DTLS_RECORDS_CAN_SPAN_DATAGRAMS)
int test_dtls13_longer_length(void)
{
@@ -1490,6 +1634,10 @@ int test_dtls_short_ciphertext(void)
return EXPECT_RESULT();
}
#else
int test_dtls_drop_invalid_record_during_handshake(void)
{
return TEST_SKIPPED;
}
int test_dtls_short_ciphertext(void)
{
return TEST_SKIPPED;
+3
View File
@@ -31,6 +31,7 @@ int test_dtls13_new_connection_id_not_negotiated(void);
int test_dtls13_request_connection_id(void);
int test_dtls13_cid_msg_malformed(void);
int test_dtls_version_checking(void);
int test_dtls_drop_invalid_record_during_handshake(void);
int test_dtls_short_ciphertext(void);
int test_dtls12_record_length_mismatch(void);
int test_dtls12_short_read(void);
@@ -116,6 +117,8 @@ int test_WOLFSSL_dtls_version_alert(void);
TEST_DECL_GROUP("dtls", test_dtls13_request_connection_id), \
TEST_DECL_GROUP("dtls", test_dtls13_cid_msg_malformed), \
TEST_DECL_GROUP("dtls", test_dtls_version_checking), \
TEST_DECL_GROUP("dtls", \
test_dtls_drop_invalid_record_during_handshake), \
TEST_DECL_GROUP("dtls", test_dtls_short_ciphertext), \
TEST_DECL_GROUP("dtls", test_dtls12_record_length_mismatch), \
TEST_DECL_GROUP("dtls", test_dtls12_short_read), \