From c78077a8b8b85c7f2fccf14e3c10172b48e236e9 Mon Sep 17 00:00:00 2001 From: Kareem Date: Wed, 19 Aug 2026 15:40:26 -0700 Subject: [PATCH 1/4] Correct reassembly logic in sniffer and add test. Thanks to 007bsd for the report. --- src/sniffer.c | 93 +++++++++++++++++++++++--------------------------- tests/api.c | 94 +++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 134 insertions(+), 53 deletions(-) diff --git a/src/sniffer.c b/src/sniffer.c index a45e2f0c9a..c116c58f2d 100644 --- a/src/sniffer.c +++ b/src/sniffer.c @@ -5831,6 +5831,44 @@ static int AddFinCapture(SnifferSession* session, word32 sequence) return 1; } +/* Trim an in-order frame against the head of the reassembly list and queue + * anything that reaches past it. + * On entry *sslFrame is positioned at 'expected' and holds *sslBytes bytes. */ +static void TrimAgainstReassembly(SnifferSession* session, + PacketBuffer* reassemblyList, word32 expected, + int* sslBytes, const byte** sslFrame, + char* error) +{ + word32 newEnd; + + if (*sslBytes <= 0) + return; + /* newEnd is one past the frame's last byte, while a list entry's begin and + * end are both inclusive. */ + newEnd = expected + (word32)*sslBytes; + + if (newEnd > reassemblyList->begin) { + Trace(OVERLAP_REASSEMBLY_BEGIN_STR); + + /* keep only what comes before the list entry, the rest is already + held */ + *sslBytes = (reassemblyList->begin > expected) ? + (int)(reassemblyList->begin - expected) : 0; + } + if ((reassemblyList->end >= expected) && + (newEnd - 1 > reassemblyList->end)) { + /* may be past reassembly list end (could have more on list) + so try to add what's past the front->end */ + word32 offset = reassemblyList->end - expected + 1; + + Trace(OVERLAP_REASSEMBLY_END_STR); + + AddToReassembly(session->flags.side, reassemblyList->end + 1, + *sslFrame + offset, + (int)(newEnd - reassemblyList->end - 1), session, error); + } +} + /* Adjust incoming sequence based on side */ /* returns 0 on success (continue), -1 on error, 1 on success (end) */ static int AdjustSequence(TcpInfo* tcpInfo, SnifferSession* session, @@ -5868,37 +5906,13 @@ static int AdjustSequence(TcpInfo* tcpInfo, SnifferSession* session, Trace(OVERLAP_DUPLICATE_STR); } - /* The following conditional block is duplicated below. It is the - * same action but for a different setup case. If changing this - * block be sure to also update the block below. */ if (reassemblyList) { - word32 newEnd; - /* adjust to expected, remove duplicate */ *sslFrame += overlap; *sslBytes = (*sslBytes > overlap) ? *sslBytes - overlap : 0; - newEnd = *expected + *sslBytes; - if (newEnd > reassemblyList->begin) { - int covered_data_len; - - Trace(OVERLAP_REASSEMBLY_BEGIN_STR); - - /* remove bytes already on reassembly list */ - covered_data_len = newEnd - reassemblyList->begin; - *sslFrame += covered_data_len; - *sslBytes = (*sslBytes > covered_data_len) ? - *sslBytes - covered_data_len : 0; - } - if ((*sslBytes > 0) && (newEnd > reassemblyList->end)) { - Trace(OVERLAP_REASSEMBLY_END_STR); - - /* may be past reassembly list end (could have more on list) - so try to add what's past the front->end */ - AddToReassembly(session->flags.side, reassemblyList->end + 1, - *sslFrame + (reassemblyList->end - *expected + 1), - newEnd - reassemblyList->end, session, error); - } + TrimAgainstReassembly(session, reassemblyList, *expected, + sslBytes, sslFrame, error); } else if (*sslBytes > 0) { if (real + *sslBytes - 1 > *seqLast) { @@ -5964,32 +5978,9 @@ static int AdjustSequence(TcpInfo* tcpInfo, SnifferSession* session, *sslFrame, *sslBytes, session, error); ret = 0; } - /* The following conditional block is duplicated above. It is the - * same action but for a different setup case. If changing this - * block be sure to also update the block above. */ else if (reassemblyList) { - word32 newEnd = *expected + *sslBytes; - - if (newEnd > reassemblyList->begin) { - int covered_data_len; - - Trace(OVERLAP_REASSEMBLY_BEGIN_STR); - - /* remove bytes already on reassembly list */ - covered_data_len = newEnd - reassemblyList->begin; - *sslFrame += covered_data_len; - *sslBytes = (*sslBytes > covered_data_len) ? - *sslBytes - covered_data_len : 0; - } - if ((*sslBytes > 0) && (newEnd > reassemblyList->end)) { - Trace(OVERLAP_REASSEMBLY_END_STR); - - /* may be past reassembly list end (could have more on list) - so try to add what's past the front->end */ - AddToReassembly(session->flags.side, reassemblyList->end + 1, - *sslFrame + (reassemblyList->end - *expected + 1), - newEnd - reassemblyList->end, session, error); - } + TrimAgainstReassembly(session, reassemblyList, *expected, sslBytes, + sslFrame, error); } } else { diff --git a/tests/api.c b/tests/api.c index 2304f24f86..49b2375572 100644 --- a/tests/api.c +++ b/tests/api.c @@ -168,10 +168,12 @@ #include "wolfssl/internal.h" #endif -#if defined(WOLFSSL_SNIFFER) && defined(WOLFSSL_SNIFFER_CHAIN_INPUT) +#ifdef WOLFSSL_SNIFFER #include #include - #include + #ifdef WOLFSSL_SNIFFER_CHAIN_INPUT + #include + #endif #endif #ifdef WOLFSSL_HAVE_MLDSA @@ -38713,6 +38715,91 @@ static int test_sniffer_chain_input_overflow(void) } #endif /* WOLFSSL_SNIFFER && WOLFSSL_SNIFFER_CHAIN_INPUT */ +#if defined(WOLFSSL_SNIFFER) && !defined(NO_RSA) && !defined(NO_FILESYSTEM) + +#define SNIFFER_IP_HDR_SZ 20 +#define SNIFFER_TCP_HDR_SZ 20 + +/* Minimal IPv4/TCP packet, 10.0.0.9:50000 -> 127.0.0.1:443, with a filler + * payload. Allocated at exactly the captured length so that a read past the + * end of the frame faults under a sanitizer. */ +static byte* sniffer_tcp_packet(word32 seq, byte tcpFlags, int payloadSz, + int* pktSz) +{ + int total = SNIFFER_IP_HDR_SZ + SNIFFER_TCP_HDR_SZ + payloadSz; + byte* p = (byte*)XMALLOC((size_t)total, NULL, DYNAMIC_TYPE_TMP_BUFFER); + int i; + + if (p == NULL) + return NULL; + XMEMSET(p, 0, (size_t)total); + + p[0] = 0x45; /* IPv4, 5 word header */ + p[2] = (byte)(total >> 8); + p[3] = (byte)(total & 0xFF); + p[6] = 0x40; /* don't fragment */ + p[8] = 64; /* ttl */ + p[9] = 6; /* TCP */ + p[12] = 10; p[13] = 0; p[14] = 0; p[15] = 9; /* 10.0.0.9 */ + p[16] = 127; p[17] = 0; p[18] = 0; p[19] = 1; /* 127.0.0.1 */ + + p[20] = 0xC3; p[21] = 0x50; /* source port 50000 */ + p[22] = 0x01; p[23] = 0xBB; /* destination port 443 */ + c32toa(seq, p + 24); + p[32] = 0x50; /* data offset, 5 words */ + p[33] = tcpFlags; + p[34] = 0xFF; /* window */ + + for (i = 0; i < payloadSz; i++) + p[SNIFFER_IP_HDR_SZ + SNIFFER_TCP_HDR_SZ + i] = (byte)(0xE0 + (i & 0xF)); + + *pktSz = total; + return p; +} + +/* An in-order segment that overlaps a segment already held on the reassembly + * list is split into the part before the held segment and the part past it. + * Both parts have to be sourced from where they actually sit in the frame. */ +static int test_sniffer_reassembly_overlap(void) +{ + EXPECT_DECLS; + static const struct { + word32 seq; + byte flags; + int payloadSz; + } segs[] = { + { 999, 0x02, 0 }, /* SYN, relative sequence starts at 1000 */ + { 1004, 0x18, 64 }, /* out of order, held as [4, 67] */ + { 1000, 0x18, 72 }, /* in order, runs from 0 past 67 */ + }; + char error[WOLFSSL_MAX_ERROR_SZ]; + byte* data = NULL; + byte* pkt; + int pktSz = 0; + int i; + + ssl_InitSniffer(); + XMEMSET(error, 0, sizeof(error)); + ExpectIntEQ(ssl_SetPrivateKey("127.0.0.1", 443, svrKeyFile, + WOLFSSL_FILETYPE_PEM, NULL, error), 0); + + for (i = 0; i < (int)XELEM_CNT(segs); i++) { + pkt = sniffer_tcp_packet(segs[i].seq, segs[i].flags, segs[i].payloadSz, + &pktSz); + ExpectNotNull(pkt); + if (pkt != NULL) { + XMEMSET(error, 0, sizeof(error)); + ExpectIntGE(ssl_DecodePacket(pkt, pktSz, &data, error), -1); + XFREE(pkt, NULL, DYNAMIC_TYPE_TMP_BUFFER); + } + } + + ssl_FreeSniffer(); + + return EXPECT_RESULT(); +} +#endif /* WOLFSSL_SNIFFER && !NO_RSA && !NO_FILESYSTEM */ + /* Test: wc_DhAgree must reject p-1 as peer public key. * ffdhe2048 p ends with ...FFFFFFFFFFFFFFFF so p-1 ends ...FFFFFFFFFFFFFFFE */ static int test_DhAgree_rejects_p_minus_1(void) @@ -40696,6 +40783,9 @@ TEST_CASE testCases[] = { #if defined(WOLFSSL_SNIFFER) && defined(WOLFSSL_SNIFFER_CHAIN_INPUT) TEST_DECL(test_sniffer_chain_input_overflow), #endif +#if defined(WOLFSSL_SNIFFER) && !defined(NO_RSA) && !defined(NO_FILESYSTEM) + TEST_DECL(test_sniffer_reassembly_overlap), +#endif /* This test needs to stay at the end to clean up any caches allocated. */ TEST_DECL(test_wolfSSL_Cleanup) From 325d7851dfbcbfa016d8eb5cd1e0f05efcda7926 Mon Sep 17 00:00:00 2001 From: Kareem Date: Wed, 19 Aug 2026 15:44:10 -0700 Subject: [PATCH 2/4] Fix handshake message size in BuildCertificateStatus and add test. This was causing issues when using OCSP stapling with secure_renegotiation and renegotiating a connection. Thanks to 007bsd for the report. --- src/internal.c | 6 ++-- tests/api.c | 1 + tests/api/test_ocsp.c | 68 +++++++++++++++++++++++++++++++++++++++++++ tests/api/test_ocsp.h | 1 + 4 files changed, 74 insertions(+), 2 deletions(-) diff --git a/src/internal.c b/src/internal.c index afa472608b..4a51759636 100644 --- a/src/internal.c +++ b/src/internal.c @@ -28192,8 +28192,10 @@ static int BuildCertificateStatus(WOLFSSL* ssl, byte type, buffer* status, XMEMCPY(output + idx, status[i].buffer, status[i].length); idx += status[i].length; } - /* Send Message. Handled message fragmentation in the function if needed */ - ret = SendHandshakeMsg(ssl, output, (sendSz - headerSz), certificate_status, + /* Send Message. Handled message fragmentation in the function if needed. + * idx is the fill cursor, so idx - headerSz is the body actually written. + * sendSz may carry the cipher expansion slack on top of it. */ + ret = SendHandshakeMsg(ssl, output, (idx - headerSz), certificate_status, "Certificate Status"); XFREE(output, ssl->heap, DYNAMIC_TYPE_OCSP); diff --git a/tests/api.c b/tests/api.c index 49b2375572..05c24f47f3 100644 --- a/tests/api.c +++ b/tests/api.c @@ -40748,6 +40748,7 @@ TEST_CASE testCases[] = { TEST_DECL(test_wolfSSL_inject), TEST_DECL(test_wolfSSL_inject_partial_record), TEST_DECL(test_ocsp_status_callback), + TEST_DECL(test_ocsp_status_request_scr), TEST_DECL_GROUP("ocsp", test_ocsp_basic_verify), TEST_DECL_GROUP("ocsp", test_ocsp_ancestor_responder_rejected), TEST_DECL_GROUP("ocsp", test_ocsp_responder_keyhash_binding), diff --git a/tests/api/test_ocsp.c b/tests/api/test_ocsp.c index a77969891a..9dc74a0f9b 100644 --- a/tests/api/test_ocsp.c +++ b/tests/api/test_ocsp.c @@ -823,6 +823,74 @@ int test_ocsp_status_callback(void) !defined(WOLFSSL_NO_TLS12) \ && defined(OPENSSL_ALL) */ +#if defined(HAVE_OCSP) && defined(HAVE_SSL_MEMIO_TESTS_DEPENDENCIES) && \ + defined(HAVE_CERTIFICATE_STATUS_REQUEST) && !defined(WOLFSSL_NO_TLS12) && \ + defined(OPENSSL_ALL) && !defined(WOLFSSL_SMALL_CERT_VERIFY) && \ + defined(HAVE_SECURE_RENEGOTIATION) +/* A CertificateStatus sent during a renegotiation goes out encrypted. The + * length handed to the record layer then has to be the handshake body alone, + * with none of the cipher expansion slack the send buffer carries. */ +int test_ocsp_status_request_scr(void) +{ + EXPECT_DECLS; + const char* responseFile = "./certs/ocsp/test-leaf-response.der"; + struct _test_ocsp_status_callback_ctx cb_ctx; + struct test_ssl_memio_ctx test_ctx; + XFILE f = XBADFILE; + byte data[4096]; + char readBuf[16]; + + XMEMSET(&cb_ctx, 0, sizeof(cb_ctx)); + ExpectTrue((f = XFOPEN(responseFile, "rb")) != XBADFILE); + if (f != XBADFILE) { + cb_ctx.ocsp_resp_sz = (int)XFREAD(data, 1, sizeof(data), f); + XFCLOSE(f); + } + cb_ctx.ocsp_resp = data; + ExpectIntGT(cb_ctx.ocsp_resp_sz, 0); + + ExpectIntEQ(test_ocsp_status_callback_test_setup(&cb_ctx, &test_ctx, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), + TEST_SUCCESS); + ExpectIntEQ(SSL_CTX_set_tlsext_status_cb(test_ctx.s_ctx, + test_ocsp_status_callback_cb), + SSL_SUCCESS); + ExpectIntEQ(SSL_CTX_set_tlsext_status_arg(test_ctx.s_ctx, (void*)&cb_ctx), + SSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_EnableOCSPStapling(test_ctx.c_ctx), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_UseOCSPStapling(test_ctx.c_ssl, WOLFSSL_CSR_OCSP, 0), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_UseSecureRenegotiation(test_ctx.c_ssl), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_UseSecureRenegotiation(test_ctx.s_ssl), + WOLFSSL_SUCCESS); + + ExpectIntEQ(test_ssl_memio_do_handshake(&test_ctx, 10, NULL), TEST_SUCCESS); + ExpectIntEQ(cb_ctx.invoked, 1); + + /* Renegotiate. The second CertificateStatus is the encrypted one. */ + ExpectIntEQ(wolfSSL_Rehandshake(test_ctx.c_ssl), + WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR)); + ExpectIntEQ(wolfSSL_get_error(test_ctx.c_ssl, -1), + WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(wolfSSL_read(test_ctx.s_ssl, readBuf, sizeof(readBuf)), -1); + ExpectIntEQ(wolfSSL_get_error(test_ctx.s_ssl, -1), + WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(test_ssl_memio_do_handshake(&test_ctx, 10, NULL), TEST_SUCCESS); + ExpectIntEQ(cb_ctx.invoked, 2); + + test_ssl_memio_cleanup(&test_ctx); + + return EXPECT_RESULT(); +} +#else +int test_ocsp_status_request_scr(void) +{ + return TEST_SKIPPED; +} +#endif + #if !defined(NO_SHA) && defined(OPENSSL_ALL) && defined(HAVE_OCSP) && \ !defined(WOLFSSL_SM3) && !defined(WOLFSSL_SM2) && !defined(NO_RSA) int test_ocsp_certid_enc_dec(void) diff --git a/tests/api/test_ocsp.h b/tests/api/test_ocsp.h index 0c95e07f17..18ba3c0089 100644 --- a/tests/api/test_ocsp.h +++ b/tests/api/test_ocsp.h @@ -26,6 +26,7 @@ int test_ocsp_certid_enc_dec(void); int test_ocsp_certid_dup(void); int test_ocsp_resp_find_status_serial_prefix(void); int test_ocsp_status_callback(void); +int test_ocsp_status_request_scr(void); int test_ocsp_basic_verify(void); int test_ocsp_responder_keyhash_binding(void); int test_ocsp_response_parsing(void); From d786efccbc4055167a9773ab9db6c5f6e3239156 Mon Sep 17 00:00:00 2001 From: Kareem Date: Wed, 19 Aug 2026 15:58:11 -0700 Subject: [PATCH 3/4] Ensure MTU is set properly when using SCTP with DTLS. Add return code checks and fix wolfSSL_shutdown handling in SCTP examples. --- examples/sctp/sctp-client-dtls.c | 45 +++++++++++++++----- examples/sctp/sctp-server-dtls.c | 42 +++++++++++++++---- src/internal.c | 2 +- tests/api/test_dtls.c | 71 ++++++++++++++++++++++++++++++++ tests/api/test_dtls.h | 2 + tests/utils.c | 2 +- tests/utils.h | 4 ++ 7 files changed, 148 insertions(+), 20 deletions(-) diff --git a/examples/sctp/sctp-client-dtls.c b/examples/sctp/sctp-client-dtls.c index 88a4477ab2..763375e396 100644 --- a/examples/sctp/sctp-client-dtls.c +++ b/examples/sctp/sctp-client-dtls.c @@ -50,6 +50,17 @@ static int err_sys(const char* msg) perror(msg); exit(EXIT_FAILURE); } + +/* errno says nothing about a wolfSSL failure, so report the library error. */ +static void err_ssl(WOLFSSL* ssl, int ret, const char* msg) +{ + char buf[WOLFSSL_MAX_ERROR_SZ]; + + fprintf(stderr, "%s: %s\n", msg, + wolfSSL_ERR_error_string((unsigned long)wolfSSL_get_error(ssl, ret), + buf)); + exit(EXIT_FAILURE); +} #endif /* WOLFSSL_SCTP && WOLFSSL_DTLS && !WOLFSSL_NO_TLS12 */ int main(int argc, char **argv) @@ -101,30 +112,44 @@ int main(int argc, char **argv) printf("Cipher Suite is %s\n", wolfSSL_CIPHER_get_name(wolfSSL_get_current_cipher(ssl))); - wolfSSL_write(ssl, response, (int)strlen(response)); + int len = (int)strlen(response); + ret = wolfSSL_write(ssl, response, len); + if (ret != len) + err_ssl(ssl, ret, "ssl write failed"); + int got = wolfSSL_read(ssl, buffer, sizeof(buffer) - 1); - if (got > 0) { - buffer[got] = 0; - printf("server said: %s\n", buffer); - } + if (got <= 0) + err_ssl(ssl, got, "ssl read failed"); + buffer[got] = 0; + printf("server said: %s\n", buffer); unsigned char bigBuf[4096]; unsigned int i; - for (i = 0; i < (int)sizeof(bigBuf); i++) + for (i = 0; i < sizeof(bigBuf); i++) bigBuf[i] = (unsigned char)(i & 0xFF); - wolfSSL_write(ssl, bigBuf, sizeof(bigBuf)); + + ret = wolfSSL_write(ssl, bigBuf, sizeof(bigBuf)); + if (ret != (int)sizeof(bigBuf)) + err_ssl(ssl, ret, "ssl write of big message failed"); memset(bigBuf, 0, sizeof(bigBuf)); - wolfSSL_read(ssl, bigBuf, sizeof(bigBuf)); + ret = wolfSSL_read(ssl, bigBuf, sizeof(bigBuf)); + if (ret != (int)sizeof(bigBuf)) + err_ssl(ssl, ret, "ssl read of big message failed"); for (i = 0; i < sizeof(bigBuf); i++) { if (bigBuf[i] != (unsigned char)(i & 0xFF)) { fprintf(stderr, "big message check fail\n"); - break; + exit(EXIT_FAILURE); } } - wolfSSL_shutdown(ssl); + ret = wolfSSL_shutdown(ssl); + if (ret == WOLFSSL_SHUTDOWN_NOT_DONE) + ret = wolfSSL_shutdown(ssl); + if (ret != WOLFSSL_SUCCESS) + err_ssl(ssl, ret, "ssl shutdown failed"); + wolfSSL_free(ssl); wolfSSL_CTX_free(ctx); diff --git a/examples/sctp/sctp-server-dtls.c b/examples/sctp/sctp-server-dtls.c index 940dcfe98d..6f96e51c7e 100644 --- a/examples/sctp/sctp-server-dtls.c +++ b/examples/sctp/sctp-server-dtls.c @@ -51,6 +51,17 @@ static int err_sys(const char* msg) perror(msg); exit(EXIT_FAILURE); } + +/* errno says nothing about a wolfSSL failure, so report the library error. */ +static void err_ssl(WOLFSSL* ssl, int ret, const char* msg) +{ + char buf[WOLFSSL_MAX_ERROR_SZ]; + + fprintf(stderr, "%s: %s\n", msg, + wolfSSL_ERR_error_string((unsigned long)wolfSSL_get_error(ssl, ret), + buf)); + exit(EXIT_FAILURE); +} #endif /* WOLFSSL_SCTP && WOLFSSL_DTLS && !WOLFSSL_NO_TLS12 */ int main(int argc, char **argv) @@ -113,18 +124,33 @@ int main(int argc, char **argv) wolfSSL_CIPHER_get_name(wolfSSL_get_current_cipher(ssl))); int got = wolfSSL_read(ssl, buffer, sizeof(buffer) - 1); - if (got > 0) { - buffer[got] = 0; - printf("client said: %s\n", buffer); - } - wolfSSL_write(ssl, response, (int)strlen(response)); + if (got <= 0) + err_ssl(ssl, got, "ssl read failed"); + buffer[got] = 0; + printf("client said: %s\n", buffer); + + int len = (int)strlen(response); + ret = wolfSSL_write(ssl, response, len); + if (ret != len) + err_ssl(ssl, ret, "ssl write failed"); unsigned char bigBuf[4096]; - wolfSSL_read(ssl, bigBuf, sizeof(bigBuf)); - wolfSSL_write(ssl, bigBuf, sizeof(bigBuf)); + ret = wolfSSL_read(ssl, bigBuf, sizeof(bigBuf)); + if (ret != (int)sizeof(bigBuf)) + err_ssl(ssl, ret, "ssl read of big message failed"); + ret = wolfSSL_write(ssl, bigBuf, sizeof(bigBuf)); + if (ret != (int)sizeof(bigBuf)) + err_ssl(ssl, ret, "ssl write of big message failed"); + + /* Wait for the peer's close_notify so the socket is not torn down under + * the client while it is still shutting down. */ + ret = wolfSSL_shutdown(ssl); + if (ret == WOLFSSL_SHUTDOWN_NOT_DONE) + ret = wolfSSL_shutdown(ssl); + if (ret != WOLFSSL_SUCCESS) + err_ssl(ssl, ret, "ssl shutdown failed"); - wolfSSL_shutdown(ssl); wolfSSL_free(ssl); wolfSSL_CTX_free(ctx); diff --git a/src/internal.c b/src/internal.c index 4a51759636..8a53da489a 100644 --- a/src/internal.c +++ b/src/internal.c @@ -28983,7 +28983,7 @@ int SendData(WOLFSSL* ssl, const void* data, size_t sz) } #if defined(WOLFSSL_DTLS) if (ssl->options.dtls) { -#if defined(WOLFSSL_DTLS_MTU) +#if defined(WOLFSSL_DTLS_MTU) || defined(WOLFSSL_SCTP) int mtu = ssl->dtlsMtuSz; #else int mtu = MAX_MTU; diff --git a/tests/api/test_dtls.c b/tests/api/test_dtls.c index 97c523863f..0c178a0160 100644 --- a/tests/api/test_dtls.c +++ b/tests/api/test_dtls.c @@ -8097,3 +8097,74 @@ int test_wolfSSL_set_secret(void) return EXPECT_RESULT(); } +/* The DTLS send path has to bound application records by the configured MTU. + * WOLFSSL_SCTP raises that MTU well above MAX_MTU, so a build that ignores the + * configured value and falls back to MAX_MTU rejects records it should accept. + * The record between the two bounds is what catches that. */ +int test_dtls_sctp_app_data_size(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_SCTP) && defined(WOLFSSL_DTLS) && \ + !defined(WOLFSSL_NO_TLS12) && !defined(WOLFSSL_NO_DTLS_SIZE_CHECK) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) + /* Above MAX_MTU so the two branches of the size check differ, and below + * MAX_RECORD_SIZE so it is not the CTX default being observed. */ + #define TEST_SCTP_MTU 3000 + #define TEST_SCTP_FITS 2048 + #define TEST_SCTP_OVER 4096 + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + byte* msg = NULL; + byte* readBuf = NULL; + int i; + + wc_static_assert(TEST_SCTP_FITS > WOLFSSL_MAX_MTU); + wc_static_assert(TEST_SCTP_MTU < MAX_RECORD_SIZE); + + ExpectNotNull(msg = (byte*)XMALLOC(TEST_SCTP_OVER, NULL, + DYNAMIC_TYPE_TMP_BUFFER)); + ExpectNotNull(readBuf = (byte*)XMALLOC(TEST_SCTP_OVER, NULL, + DYNAMIC_TYPE_TMP_BUFFER)); + if (msg != NULL) { + for (i = 0; i < TEST_SCTP_OVER; i++) + msg[i] = (byte)(i & 0xFF); + } + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + test_ctx.sctp = 1; + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfDTLSv1_2_client_method, wolfDTLSv1_2_server_method), 0); + ExpectIntEQ(wolfSSL_dtls_set_sctp(ssl_c), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_dtls_set_sctp(ssl_s), WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + /* Set after the handshake so only the application record path is bound. */ + ExpectIntEQ(wolfSSL_dtls_set_mtu(ssl_c, TEST_SCTP_MTU), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_dtls_set_mtu(ssl_s, TEST_SCTP_MTU), WOLFSSL_SUCCESS); + + /* Within the configured MTU, beyond MAX_MTU: accepted. */ + ExpectIntEQ(wolfSSL_write(ssl_c, msg, TEST_SCTP_FITS), TEST_SCTP_FITS); + ExpectIntEQ(wolfSSL_read(ssl_s, readBuf, TEST_SCTP_FITS), TEST_SCTP_FITS); + ExpectIntEQ(XMEMCMP(msg, readBuf, TEST_SCTP_FITS), 0); + + /* Beyond the configured MTU: still refused. CheckAvailableSize() bounds + * this independently, so this does not isolate the send path - it is here + * so a change that widens the limit instead of reading it is not silently + * accepted end to end. */ + ExpectIntLT(wolfSSL_write(ssl_c, msg, TEST_SCTP_OVER), 0); + ExpectIntEQ(wolfSSL_get_error(ssl_c, WOLFSSL_FATAL_ERROR), + WC_NO_ERR_TRACE(DTLS_SIZE_ERROR)); + + wolfSSL_free(ssl_s); + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_s); + wolfSSL_CTX_free(ctx_c); + XFREE(msg, NULL, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(readBuf, NULL, DYNAMIC_TYPE_TMP_BUFFER); + #undef TEST_SCTP_MTU + #undef TEST_SCTP_FITS + #undef TEST_SCTP_OVER +#endif + return EXPECT_RESULT(); +} diff --git a/tests/api/test_dtls.h b/tests/api/test_dtls.h index b8fc7a87af..b5a66ed120 100644 --- a/tests/api/test_dtls.h +++ b/tests/api/test_dtls.h @@ -87,6 +87,7 @@ int test_dtls_client_hello_timeout(void); int test_dtls_dropped_ccs(void); int test_dtls_seq_num_downgrade(void); int test_dtls_old_seq_number(void); +int test_dtls_sctp_app_data_size(void); int test_dtls12_missing_finished(void); int test_dtls12_cookie_secret_secondary(void); int test_dtls12_cookie_secret_secondary_dropped(void); @@ -134,6 +135,7 @@ int test_WOLFSSL_dtls_version_alert(void); TEST_DECL_GROUP("dtls", test_dtls13_new_connection_id_not_negotiated), \ TEST_DECL_GROUP("dtls", test_dtls13_request_connection_id), \ TEST_DECL_GROUP("dtls", test_dtls13_cid_msg_malformed), \ + TEST_DECL_GROUP("dtls", test_dtls_sctp_app_data_size), \ TEST_DECL_GROUP("dtls", test_dtls_version_checking), \ TEST_DECL_GROUP("dtls", \ test_dtls_drop_invalid_record_during_handshake), \ diff --git a/tests/utils.c b/tests/utils.c index a8566adc86..e0ae51db2a 100644 --- a/tests/utils.c +++ b/tests/utils.c @@ -109,7 +109,7 @@ int test_memio_read_cb(WOLFSSL *ssl, char *data, int sz, void *ctx) int is_dtls; test_ctx = (struct test_memio_ctx*)ctx; - is_dtls = wolfSSL_dtls(ssl); + is_dtls = wolfSSL_dtls(ssl) && !test_ctx->sctp; if (wolfSSL_GetSide(ssl) == WOLFSSL_SERVER_END) { buf = test_ctx->s_buff; diff --git a/tests/utils.h b/tests/utils.h index a12c027ca6..3a8d2e6bfb 100644 --- a/tests/utils.h +++ b/tests/utils.h @@ -85,6 +85,10 @@ struct test_memio_ctx int c_force_want_write; int s_force_want_write; + /* Transport keeps a byte stream rather than datagrams (DTLS over SCTP), + * so a partial read must not drop the rest of the message. */ + byte sctp; + int c_msg_sizes[TEST_MEMIO_MAX_MSGS]; int c_msg_count; int c_msg_pos; From 7cd32b7bfbc376e95b6513f788f8f6402c90ca1c Mon Sep 17 00:00:00 2001 From: Kareem Date: Wed, 19 Aug 2026 16:23:10 -0700 Subject: [PATCH 4/4] Code review feedback --- src/internal.c | 13 ++++++++++--- tests/api/test_dtls.c | 16 ++++++++++++++++ 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/internal.c b/src/internal.c index 8a53da489a..2182beb915 100644 --- a/src/internal.c +++ b/src/internal.c @@ -28983,10 +28983,17 @@ int SendData(WOLFSSL* ssl, const void* data, size_t sz) } #if defined(WOLFSSL_DTLS) if (ssl->options.dtls) { -#if defined(WOLFSSL_DTLS_MTU) || defined(WOLFSSL_SCTP) - int mtu = ssl->dtlsMtuSz; + int mtu; + +#if defined(WOLFSSL_DTLS_MTU) + mtu = ssl->dtlsMtuSz; +#elif defined(WOLFSSL_SCTP) + /* An SCTP association is a reliable stream, so it is bounded by + * the configured record size rather than by a datagram MTU. A + * connection that never enabled SCTP still is. */ + mtu = IsDtlsNotSctpMode(ssl) ? MAX_MTU : ssl->dtlsMtuSz; #else - int mtu = MAX_MTU; + mtu = MAX_MTU; #endif outputSz = wolfssl_local_GetRecordSize(ssl, (word32)buffSz, 1); if (outputSz > mtu) { diff --git a/tests/api/test_dtls.c b/tests/api/test_dtls.c index 0c178a0160..fe245c3a59 100644 --- a/tests/api/test_dtls.c +++ b/tests/api/test_dtls.c @@ -8156,6 +8156,22 @@ int test_dtls_sctp_app_data_size(void) ExpectIntEQ(wolfSSL_get_error(ssl_c, WOLFSSL_FATAL_ERROR), WC_NO_ERR_TRACE(DTLS_SIZE_ERROR)); + wolfSSL_free(ssl_s); + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_s); + wolfSSL_CTX_free(ctx_c); + ssl_c = NULL; ssl_s = NULL; ctx_c = NULL; ctx_s = NULL; + + /* Same build, ordinary UDP DTLS object: compiling SCTP support in must not + * lift the datagram bound for a connection that never enabled SCTP. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfDTLSv1_2_client_method, wolfDTLSv1_2_server_method), 0); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntLT(wolfSSL_write(ssl_c, msg, TEST_SCTP_FITS), 0); + ExpectIntEQ(wolfSSL_get_error(ssl_c, WOLFSSL_FATAL_ERROR), + WC_NO_ERR_TRACE(DTLS_SIZE_ERROR)); + wolfSSL_free(ssl_s); wolfSSL_free(ssl_c); wolfSSL_CTX_free(ctx_s);