Skip to content

Commit f0d6dff

Browse files
Preserve DTLS association on invalid record headers during handshake
1 parent 076dc5a commit f0d6dff

3 files changed

Lines changed: 141 additions & 1 deletion

File tree

src/internal.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22920,7 +22920,9 @@ static int DtlsShouldDrop(WOLFSSL* ssl, int retcode)
2292022920
if ((ssl->options.handShakeDone && retcode != 0)
2292122921
|| retcode == WC_NO_ERR_TRACE(SEQUENCE_ERROR)
2292222922
|| retcode == WC_NO_ERR_TRACE(DTLS_CID_ERROR)
22923-
|| retcode == WC_NO_ERR_TRACE(DTLS_PARTIAL_RECORD_READ)) {
22923+
|| retcode == WC_NO_ERR_TRACE(DTLS_PARTIAL_RECORD_READ)
22924+
|| retcode == WC_NO_ERR_TRACE(UNKNOWN_RECORD_TYPE)
22925+
|| retcode == WC_NO_ERR_TRACE(LENGTH_ERROR)) {
2292422926
WOLFSSL_MSG_EX("Silently dropping DTLS message: %d", retcode);
2292522927
return 1;
2292622928
}

tests/api/test_dtls.c

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -675,6 +675,137 @@ static int test_dtls_communication(WOLFSSL *s, WOLFSSL *c)
675675
return EXPECT_RESULT();
676676
}
677677

678+
/* Drive a DTLS handshake until the dropping peer reads a genuine flight record,
679+
* corrupt its header, confirm the peer silently discards it, then re-deliver
680+
* the record and confirm the handshake still completes. */
681+
static int test_dtls_drop_invalid_record(method_provider method_c,
682+
method_provider method_s, int dropServer, int unknownType)
683+
{
684+
EXPECT_DECLS;
685+
WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL;
686+
WOLFSSL *ssl_c = NULL, *ssl_s = NULL;
687+
struct test_memio_ctx test_ctx;
688+
/* Sized for the small cookie-exchange/HRR/ClientHello records this test
689+
* corrupts; a PQC-default build with a large key_share may need more, and
690+
* the ExpectIntLE(recSz) guard below fails loudly rather than overflowing. */
691+
unsigned char rec[2048];
692+
unsigned char* recBuf;
693+
int recSz = 0;
694+
int readIsClient = dropServer ? 0 : 1;
695+
696+
XMEMSET(&test_ctx, 0, sizeof(test_ctx));
697+
recBuf = readIsClient ? test_ctx.c_buff : test_ctx.s_buff;
698+
699+
ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s,
700+
method_c, method_s), 0);
701+
702+
/* Client sends the ClientHello into the server's buffer. */
703+
wolfSSL_SetLoggingPrefix("client");
704+
ExpectIntEQ(wolfSSL_connect(ssl_c), WOLFSSL_FATAL_ERROR);
705+
ExpectIntEQ(wolfSSL_get_error(ssl_c, WOLFSSL_FATAL_ERROR),
706+
WOLFSSL_ERROR_WANT_READ);
707+
708+
/* When dropping at the client, let the server produce its first flight into
709+
* the client's buffer. */
710+
if (!dropServer) {
711+
wolfSSL_SetLoggingPrefix("server");
712+
ExpectIntEQ(wolfSSL_accept(ssl_s), WOLFSSL_FATAL_ERROR);
713+
ExpectIntEQ(wolfSSL_get_error(ssl_s, WOLFSSL_FATAL_ERROR),
714+
WOLFSSL_ERROR_WANT_READ);
715+
}
716+
717+
/* Corrupt then replay the datagram the dropping peer is about to read. This
718+
* assumes a single-datagram flight (asserted below) so re-injecting cannot
719+
* merge record boundaries or drop other queued datagrams. */
720+
ExpectIntEQ(readIsClient ? test_ctx.c_msg_count : test_ctx.s_msg_count, 1);
721+
recSz = readIsClient ? test_ctx.c_len : test_ctx.s_len;
722+
ExpectIntGT(recSz, DTLS_RECORD_HEADER_SZ);
723+
ExpectIntLE(recSz, (int)sizeof(rec));
724+
if (EXPECT_SUCCESS()) {
725+
XMEMCPY(rec, recBuf, (size_t)recSz);
726+
if (unknownType) {
727+
/* Not a valid ContentType, and not a DTLS 1.3 unified-header byte
728+
* (those have top bits 001, i.e. 0x20-0x3F). */
729+
recBuf[0] = 0x63;
730+
}
731+
else {
732+
recBuf[DTLS_RECORD_HEADER_SZ - 2] = 0xff;
733+
recBuf[DTLS_RECORD_HEADER_SZ - 1] = 0xff;
734+
}
735+
}
736+
737+
/* The dropping peer must silently discard the datagram: no fatal error and
738+
* nothing sent back to the other peer. */
739+
if (dropServer) {
740+
wolfSSL_SetLoggingPrefix("server");
741+
ExpectIntEQ(wolfSSL_accept(ssl_s), WOLFSSL_FATAL_ERROR);
742+
ExpectIntEQ(wolfSSL_get_error(ssl_s, WOLFSSL_FATAL_ERROR),
743+
WOLFSSL_ERROR_WANT_READ);
744+
ExpectIntEQ(test_ctx.c_len, 0);
745+
}
746+
else {
747+
wolfSSL_SetLoggingPrefix("client");
748+
ExpectIntEQ(wolfSSL_connect(ssl_c), WOLFSSL_FATAL_ERROR);
749+
ExpectIntEQ(wolfSSL_get_error(ssl_c, WOLFSSL_FATAL_ERROR),
750+
WOLFSSL_ERROR_WANT_READ);
751+
ExpectIntEQ(test_ctx.s_len, 0);
752+
}
753+
754+
/* Re-deliver the genuine record and finish the handshake to prove the
755+
* association was preserved. */
756+
test_memio_clear_buffer(&test_ctx, readIsClient);
757+
ExpectIntEQ(test_memio_inject_message(&test_ctx, readIsClient,
758+
(const char*)rec, recSz),
759+
0);
760+
ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0);
761+
762+
ExpectIntEQ(test_dtls_communication(ssl_s, ssl_c), TEST_SUCCESS);
763+
764+
wolfSSL_SetLoggingPrefix(NULL);
765+
wolfSSL_free(ssl_c);
766+
wolfSSL_free(ssl_s);
767+
wolfSSL_CTX_free(ctx_c);
768+
wolfSSL_CTX_free(ctx_s);
769+
770+
return EXPECT_RESULT();
771+
}
772+
773+
/* A DTLS peer must silently discard a handshake record whose header fails
774+
* validation (UNKNOWN_RECORD_TYPE, LENGTH_ERROR) and still finish the handshake.
775+
* Cover client and server paths for DTLS 1.2 and 1.3. */
776+
int test_dtls_drop_invalid_record_during_handshake(void)
777+
{
778+
EXPECT_DECLS;
779+
780+
/* Client drops a corrupted server flight: unknown type, then over-length. */
781+
ExpectIntEQ(test_dtls_drop_invalid_record(wolfDTLSv1_2_client_method,
782+
wolfDTLSv1_2_server_method, 0, 1), TEST_SUCCESS);
783+
ExpectIntEQ(test_dtls_drop_invalid_record(wolfDTLSv1_2_client_method,
784+
wolfDTLSv1_2_server_method, 0, 0), TEST_SUCCESS);
785+
/* Server drops a ClientHello with a bad record header: over-length, then
786+
* unknown ContentType (dropped by the new UNKNOWN_RECORD_TYPE clause, which
787+
* precedes the server-side non-stateful drop branch). */
788+
ExpectIntEQ(test_dtls_drop_invalid_record(wolfDTLSv1_2_client_method,
789+
wolfDTLSv1_2_server_method, 1, 0), TEST_SUCCESS);
790+
ExpectIntEQ(test_dtls_drop_invalid_record(wolfDTLSv1_2_client_method,
791+
wolfDTLSv1_2_server_method, 1, 1), TEST_SUCCESS);
792+
793+
#ifdef WOLFSSL_DTLS13
794+
/* Same silent-drop behavior on the DTLS 1.3 receive path (all four
795+
* side/corruption combinations). */
796+
ExpectIntEQ(test_dtls_drop_invalid_record(wolfDTLSv1_3_client_method,
797+
wolfDTLSv1_3_server_method, 0, 1), TEST_SUCCESS);
798+
ExpectIntEQ(test_dtls_drop_invalid_record(wolfDTLSv1_3_client_method,
799+
wolfDTLSv1_3_server_method, 0, 0), TEST_SUCCESS);
800+
ExpectIntEQ(test_dtls_drop_invalid_record(wolfDTLSv1_3_client_method,
801+
wolfDTLSv1_3_server_method, 1, 0), TEST_SUCCESS);
802+
ExpectIntEQ(test_dtls_drop_invalid_record(wolfDTLSv1_3_client_method,
803+
wolfDTLSv1_3_server_method, 1, 1), TEST_SUCCESS);
804+
#endif
805+
806+
return EXPECT_RESULT();
807+
}
808+
678809
#if defined(WOLFSSL_DTLS13) && !defined(WOLFSSL_DTLS_RECORDS_CAN_SPAN_DATAGRAMS)
679810
int test_dtls13_longer_length(void)
680811
{
@@ -1007,6 +1138,10 @@ int test_dtls_short_ciphertext(void)
10071138
return EXPECT_RESULT();
10081139
}
10091140
#else
1141+
int test_dtls_drop_invalid_record_during_handshake(void)
1142+
{
1143+
return TEST_SKIPPED;
1144+
}
10101145
int test_dtls_short_ciphertext(void)
10111146
{
10121147
return TEST_SKIPPED;

tests/api/test_dtls.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ int test_dtls12_basic_connection_id(void);
2626
int test_wolfSSL_dtls_cid_parse(void);
2727
int test_wolfSSL_dtls_set_pending_peer(void);
2828
int test_dtls_version_checking(void);
29+
int test_dtls_drop_invalid_record_during_handshake(void);
2930
int test_dtls_short_ciphertext(void);
3031
int test_dtls12_record_length_mismatch(void);
3132
int test_dtls12_short_read(void);
@@ -106,6 +107,8 @@ int test_WOLFSSL_dtls_version_alert(void);
106107
TEST_DECL_GROUP("dtls", test_wolfSSL_dtls_cid_parse), \
107108
TEST_DECL_GROUP("dtls", test_wolfSSL_dtls_set_pending_peer), \
108109
TEST_DECL_GROUP("dtls", test_dtls_version_checking), \
110+
TEST_DECL_GROUP("dtls", \
111+
test_dtls_drop_invalid_record_during_handshake), \
109112
TEST_DECL_GROUP("dtls", test_dtls_short_ciphertext), \
110113
TEST_DECL_GROUP("dtls", test_dtls12_record_length_mismatch), \
111114
TEST_DECL_GROUP("dtls", test_dtls12_short_read), \

0 commit comments

Comments
 (0)