Skip to content

fix: Auto-sense server only falls back on CH-shaped parse errors#106

Merged
algesten merged 5 commits intomainfrom
fix/auto-sense-fallback-discriminate-parse-errors
Apr 25, 2026
Merged

fix: Auto-sense server only falls back on CH-shaped parse errors#106
algesten merged 5 commits intomainfrom
fix/auto-sense-fallback-discriminate-parse-errors

Conversation

@algesten
Copy link
Copy Markdown
Owner

Summary

  • Previously, Dtls::handle_pending_auto fell back to DTLS 1.2 on any ParseError/ParseIncomplete returned by the 1.3 engine during AwaitClientHello. A single corrupted fragment of a real 1.3 ClientHello, or stray non-handshake traffic, could force a downgrade.
  • Add looks_like_client_hello(packet) — a lightweight structural check (Handshake record carrying msg_type=ClientHello) — and gate the parse-error fallback on it. Garbage packets bubble the error up; the server stays in 1.3 auto-sense.
  • The check runs unconditionally before matching on the parser result so the time spent in the dispatch does not depend on which error branch was taken.
  • The clean Dtls12Fallback path (supported_versions parsed but did not include 1.3) is unchanged — that is the intended downgrade trigger for real 1.2 clients.

Test plan

  • New unit tests for looks_like_client_hello covering: valid CH record, non-handshake content type, other handshake message types (ServerHello, HVR, Certificate, Finished), truncated packets, short handshake body.
  • New auto_server_drops_garbage_without_falling_back test confirms a garbage first packet does not flip the auto-sense server to Server12.
  • All existing tests/auto/* integration tests pass (62/62), including fragmented-CH and PSK-fallback scenarios.
  • Full test suite: 408 passed, 0 failed.

The DTLS 1.3 auto-sense server previously fell back to DTLS 1.2 on any
ParseError or ParseIncomplete during AwaitClientHello. That meant a
single corrupted fragment of a real DTLS 1.3 ClientHello, or a stray
non-handshake packet from off-path traffic, could force a downgrade.

Gate the parse-error fallback on a lightweight structural check:
fall back only when the packet at least claims to be a Handshake
record carrying a ClientHello message. Random/garbage packets still
bubble the parse error up and the server stays in 1.3 auto-sense.

The check runs unconditionally before matching on the parser result so
the time spent in the auto-sense dispatch does not depend on which
error branch was taken.

The clean Dtls12Fallback path (supported_versions parsed but did not
include 1.3) is unchanged.
Extract client_hello_handshake() so looks_like_client_hello and
client_hello_wants_psk both go through the same record-header +
handshake-header validation. Pure dedup, no behavior change.
@algesten algesten force-pushed the fix/auto-sense-fallback-discriminate-parse-errors branch from bcf80b7 to 0044ddf Compare April 25, 2026 15:07
The previous structural check accepted any record with content_type=22
and a 12-byte handshake header starting with msg_type=ClientHello —
including a header-only fake with declared length=0. That kept the
DTLS 1.2 fallback predicate too loose: a tiny crafted packet could
still trigger fallback even though no real ClientHello followed.

Tighten the check to also require:
- fragment_offset + fragment_length <= length (no fragment overflows
  the declared total)
- 12 + fragment_length <= record_body.len() (the fragment bytes
  declared are actually present in the record)
- For an unfragmented CH (frag_off == 0 && frag_len == length),
  length must be >= 41 — the minimum byte count any real DTLS 1.2
  ClientHello can carry (version + random + sid_len + cookie_len +
  suites_len + comp_len + comp).

Fragmented first/middle fragments still pass since the size floor
only applies to single-record CHs.

Replace the test that asserted a header-only CH passes with one that
uses a minimum-shape body. Add negative tests for: header-only CH,
undersized unfragmented CH, fragment-offset/length overflow, missing
fragment bytes. Add positive tests for first-fragment and non-first-
fragment of a fragmented CH so the legitimate fragmented path stays
covered.
Tighten looks_like_client_hello to also require fragment_offset == 0.
A non-first fragment arriving alone could be a spoofed packet aimed at
forcing a downgrade — real fragmented ClientHellos always include a
frag_off=0 fragment, and the clean Dtls12Fallback path (driven by
supported_versions, not by this gate) handles fully reassembled
fragmented 1.2 CHs once they complete.

Replace the looks_like_client_hello_accepts_non_first_fragment test
with looks_like_client_hello_rejects_non_first_fragment.

Add auto_server_falls_back_on_ch_shaped_malformed_packet — an
integration test that documents the intentional behavior of the gated
fallback: a packet that is structurally a ClientHello but whose body
the DTLS 1.3 engine cannot parse should still flip the auto-sense
server into DTLS 1.2 mode. (Pairs with
auto_server_drops_garbage_without_falling_back, which verifies that
random non-CH garbage does not.)
@algesten algesten merged commit d139db3 into main Apr 25, 2026
46 checks passed
@algesten algesten deleted the fix/auto-sense-fallback-discriminate-parse-errors branch April 25, 2026 15:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants