Preserve DTLS association on invalid record headers during handshake#10826
Preserve DTLS association on invalid record headers during handshake#10826yosuke-wolfssl wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
Pull request overview
This PR hardens DTLS handshake robustness by ensuring invalid DTLS record headers received during an in-progress handshake are silently dropped (instead of aborting the association), aligning handshake behavior with the already-droppable post-handshake path and RFC DoS guidance.
Changes:
- Extend
DtlsShouldDrop()to treatUNKNOWN_RECORD_TYPEandLENGTH_ERRORas droppable DTLS errors during the handshake window. - Add a DTLS unit test that corrupts a real handshake record header (unknown ContentType / over-length) and verifies the peer returns
WANT_READ, sends nothing back, then successfully completes the handshake when the genuine record is re-delivered. - Register the new unit test in the DTLS test header and test list.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| tests/api/test_dtls.h | Adds prototype + registers the new DTLS handshake drop test in the DTLS test group. |
| tests/api/test_dtls.c | Adds a new memio-based DTLS test that corrupts handshake record headers and asserts silent-drop + handshake completion. |
| src/internal.c | Updates DTLS drop policy to include UNKNOWN_RECORD_TYPE and LENGTH_ERROR during handshake processing. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
868aae1 to
14e10f6
Compare
wolfSSL-Fenrir-bot
left a comment
There was a problem hiding this comment.
Fenrir Automated Review — PR #10826
Scan targets checked: wolfcrypt-rs-bugs, wolfssl-bugs, wolfssl-src
No new issues found in the changed files. ✅
14e10f6 to
f0d6dff
Compare
f0d6dff to
7ff29b4
Compare
| /* Sized for the small cookie-exchange/HRR/ClientHello records this test | ||
| * corrupts; a PQC-default build with a large key_share may need more, and | ||
| * the ExpectIntLE(recSz) guard below fails loudly rather than overflowing. */ | ||
| unsigned char rec[2048]; |
There was a problem hiding this comment.
Test is build-sensitive to a single-datagram first flight
The corrupt/re-inject model here assumes the peer's flight is a single datagram — enforced by ExpectIntEQ(... c_msg_count : s_msg_count, 1) and the fixed rec[2048] size. That holds for default builds, but a PQC-default build with a large key_share fragments the first flight across multiple datagrams. When that happens c_msg_count/s_msg_count != 1 and this case fails (loudly, not unsafely — the ExpectIntLE(recSz) guard prevents any overflow) rather than exercising the drop path.
Rather than only documenting the assumption, consider pinning a non-PQC group on both CTXs right after test_memio_setup so the single-datagram invariant holds regardless of build defaults. Something like a file-scope static const int secp256r1_group[] = { WOLFSSL_ECC_SECP256R1 }; and then:
ExpectIntEQ(wolfSSL_CTX_set_groups(ctx_c, (int*)secp256r1_group, 1), WOLFSSL_SUCCESS);
ExpectIntEQ(wolfSSL_CTX_set_groups(ctx_s, (int*)secp256r1_group, 1), WOLFSSL_SUCCESS);This keeps the clean corrupt/re-inject logic and makes the test deterministic across CI matrices that change DTLS defaults. Non-blocking — the drop-path validation is header-level and doesn't depend on whether the flight was fragmented.
Preserve DTLS association on invalid record headers during the handshake
Summary
While a DTLS handshake is in progress (
handShakeDone == 0), a singleinjected or corrupted datagram whose record header fails validation
(
UNKNOWN_RECORD_TYPEorLENGTH_ERROR) aborted the handshake instead ofbeing silently discarded. This let an off-path attacker tear down an
in-progress DTLS handshake with one spoofed packet. This PR makes those
record-header errors droppable during the handshake window, matching the
already-correct post-handshake behavior.
Root cause
In
DoProcessReplyEx, afterGetRecordHeaderruns, the record is onlydiscarded when
DtlsShouldDrop()returns 1; otherwise the negative error isreturned up the stack and the connection is torn down.
DtlsShouldDrop()dropped
SEQUENCE_ERROR,DTLS_CID_ERROR,DTLS_PARTIAL_RECORD_READ, and thepost-handshake catch-all (
handShakeDone && retcode != 0), but notUNKNOWN_RECORD_TYPE(unrecognized ContentType) orLENGTH_ERROR(oversized/zero-length/malformed records) while
handShakeDone == 0.The result was an asymmetry: an established DTLS connection already shrugged
off such a datagram, but a connection mid-handshake — the more
attacker-interesting window — was torn down by it.
Fix
Add
UNKNOWN_RECORD_TYPEandLENGTH_ERRORto the silent-drop set inDtlsShouldDrop(). The change is version-agnostic (applies to DTLS 1.2 and1.3) and is scoped to DTLS only —
DtlsShouldDrop()is called from the replypath solely when
ssl->options.dtlsis set.This aligns wolfSSL with RFC 6347 §4.1.2.7 / RFC 9147 §4.5.2, which
recommend (SHOULD) silently discarding invalid records — including bad length
and unknown type — to preserve the association, and explicitly warn that the
alert/teardown alternative is NOT RECOMMENDED over UDP (DoS).
Behavior change
Under
HAVE_MAX_FRAGMENT, an over-length record during a DTLS handshakepreviously reached the
record_overflowfatal-alert path inDoProcessReplyEx;it is now silently dropped before that switch. This is intentional and
RFC-preferred for DTLS. The alert path is unchanged for TLS — the call site is
gated on
ssl->options.dtls, so TLS still sendsrecord_overflow.VERSION_ERRORis deliberately not added, so the legitimate version-alert pathfor a downgrading client is untouched.
Testing
Added
test_dtls_drop_invalid_record_during_handshake(tests/api/test_dtls.c),built on a small helper parameterized by side (client/server drop) and
corruption mode (unknown ContentType / over-length). It corrupts a genuine
handshake-flight record the peer is about to read, asserts the peer returns
WANT_READ(no fatal error) and sends nothing back, then re-delivers thegenuine record and confirms the handshake completes and data flows.
Matrix — all four
(side x corruption)combinations for both DTLS 1.2 andDTLS 1.3 (8 cases).
Verification:
(handshake torn down); with the fix, all pass.
#elseTEST_SKIPPEDstub keeps the unit testlinking; the test reports skipped.
Compatibility
No public API changes. No behavior change for TLS. For DTLS, the handshake
window now behaves like an established connection for these record-header
errors.
Addressed by f_6539.