Skip to content

fix: panic on Initial packets with a zero-length DCID - #5

Merged
ChrisTeg merged 3 commits into
developfrom
fix/retry-token-odcid-panic
Sep 29, 2026
Merged

ChrisTeg merged 3 commits into
developfrom
fix/retry-token-odcid-panic

Conversation

@ChrisTeg

Copy link
Copy Markdown
Owner

Summary

Fix a remotely triggerable panic (unauthenticated DoS) in the QUIC server. An attacker crashes the server with two packets:

  1. An Initial packet with a zero-length DCID and a short garbage token triggers send_retry, which signs a Retry token embedding the zero-length DCID as the odcid.
  2. Replaying the poisoned token: decode restores odcid = None, and endpoint.rs panics on token.odcid.unwrap().

Root cause (three overlapping defects)

  1. packet.rs did not enforce the RFC 9000 Section 7.2 requirement that the first client Initial carries a DCID of at least 8 bytes.
  2. token.rs encoded/decoded Retry tokens with a zero-length odcid (Some(empty) → written as len 0 → decoded back as None).
  3. endpoint.rs consumed the decoded odcid with unwrap() under an incorrect "always success" assumption.

Fix (defense in depth)

  • endpoint: drop the first Initial packet with a DCID shorter than 8 bytes; a subsequent Initial carrying a valid Retry token keeps working (its DCID is the server-chosen rscid). Replace the unwrap() with explicit error handling returning InvalidToken.
  • token: encode() rejects Retry tokens whose odcid is shorter than 8 bytes; decode() rejects such tokens as InvalidToken (RFC 9000 Section 8.1.2.1).
  • tests: fix missing assert! in address_token tests (which let this inconsistency slip through), move now-invalid zero-length odcid cases to the invalid list, and add regression tests: hand-crafted poisoned tokens must be rejected by decode, and Initial packets with a zero-length DCID must be dropped without side effects.

Validation

  • cargo test --lib: 560 passed, 0 failed
  • cargo fmt -- --check: clean
  • cargo clippy --lib --tests: no new warnings

A remote unauthenticated attacker can crash a QUIC server with only
two packets:

1. Send an Initial packet with a zero-length DCID and a garbage token,
   which triggers the server to issue a Retry token embedding the
   zero-length DCID as the odcid.
2. Replay the poisoned Retry token; the decoded odcid is restored to
   None, and the server panics on `token.odcid.unwrap()`.

This patch applies defense in depth:

- endpoint: drop the first Initial packet with a DCID shorter than 8
  bytes, as required by RFC 9000 Section 7.2. The DCID of a subsequent
  Initial packet carrying a valid Retry token is not affected.
- endpoint: replace `token.odcid.unwrap()` with explicit error handling.
- token: reject encoding/decoding Retry tokens whose odcid is shorter
  than 8 bytes, as required by RFC 9000 Section 8.1.2.1.
- tests: fix missing assertions in address_token tests and add
  regression tests for the poisoned tokens.
- Pin typos-cli to 1.42.3: the latest release (1.50.3) requires
  rustc 1.95, which is newer than the toolchain pinned in this
  repository (1.90.0). 1.42.3 is the newest release whose MSRV
  (1.87) is compatible.
- Skip the Codecov upload step when no CODECOV_TOKEN is available
  (e.g. on forks, which do not inherit upstream secrets), instead
  of failing the whole unit testing job.
- Remove the unused MAX_CID_LEN import in token.rs.
- Correct the RFC 9000 reference: Retry tokens are described in
  Section 8.1.2 (Address Validation Using Retry Packets); there is
  no Section 8.1.2.1.
@ChrisTeg
ChrisTeg merged commit 10a1ffa into develop Sep 29, 2026
12 checks passed
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.

1 participant