cloudflare / cloudflare/quiche
First-flight transport errors can make pending CONNECTION_CLOSE unsendable
- Dominant language
- Rust
- Stars
- 11.8k
- Forks
- 1.1k
- Avg merge
- 21h 9m
- Merged PRs (30d)
- 6
Description
In quiche 0.29.1 and 0.29.2, a transport error detected while processing the peer’s first flight can be recorded as a local connection error, but then made unsendable before `send()` can emit a peer-processable `CONNECTION_CLOSE`.
One concrete case is the existing `missing_initial_source_connection_id` test. The server detects `Error::InvalidTransportParam`, records a local error, and then marks the connection closed because `recv_count == 0`. A later `send()` therefore returns without emitting the pending transport close.
## Minimal reproduction
Extend the existing `missing_initial_source_connection_id` test in `quiche/src/tests.rs`:
```rust
assert_eq!(
pipe.server_recv(&mut buf[..len]),
Err(Error::InvalidTransportParam)
);
assert!(pipe.server.local_error().is_some());
assert!(pipe.server.send(&mut buf).is_ok());
```
Run:
```sh
cargo test -p quiche missing_initial_source_connection_id -- --nocapture
```
## Observed behavior
On unmodified quiche 0.29.1 and 0.29.2:
1. `server_recv()` returns `Err(Error::InvalidTransportParam)`.
2. `server.local_error()` is set.
3. `Connection::close()` marks the connection closed because `recv_count == 0`.
4. A subsequent `send()` cannot serialize the recorded close.
If the `recv_count == 0` early-close path is relaxed, `send()` can still select a Handshake packet while the TLS write level maps to `packet::Epoch::Application` and the handshake is not confirmed. In this first-flight error case, Initial keys are still available, and selecting Handshake can fail instead of emitting an Initial `CONNECTION_CLOSE`.
## Expected behavior
After the server has processed the Initial far enough to detect and record a transport-parameter connection error, `send()` should be able to emit a peer-processable transport `CONNECTION_CLOSE`, when Initial sealing keys are available.
For the `missing_initial_source_connection_id` case, the emitted close should be an Initial packet carrying a transport-level `CONNECTION_CLOSE` with error code `TRANSPORT_PARAMETER_ERROR` (`0x08`).
## Minimal patch demonstrating the issue
```diff
diff quiche/src/lib.rs
- if self.recv_count == 0 {
+ if self.recv_count == 0 && !self.derived_initial_secrets {
self.mark_closed();
}
if !self.handshake_confirmed {
match epoch {
+ packet::Epoch::Application
+ if self.crypto_ctx[packet::Epoch::Initial].has_keys() =>
+ return Ok(Type::Initial),
packet::Epoch::Application => return Ok(Type::Handshake),
```
With this change, the focused reproduction can emit an Initial transport `CONNECTION_CLOSE` instead of making the recorded local error unsendable.
This patch is intended as a minimal demonstration of the failing state transition, not necessarily as the final upstream design.
## RFC basis
RFC 9000 §7.3 requires the `initial_source_connection_id` transport parameter to be present. If it is absent, the endpoint MUST close the connection with `TRANSPORT_PARAMETER_ERROR`.
RFC 9000 §18.2 defines server-only transport parameters. If a client sends a server-only transport parameter, the server MUST treat that as a connection error of type `TRANSPORT_PARAMETER_ERROR`.
RFC 9000 §17.2 requires nonzero reserved bits in a protected long-header packet, after packet and header protection are removed, to be treated as a connection error of type `PROTOCOL_VIOLATION`.
RFC 9000 §17.3.1 requires nonzero reserved bits in a protected short-header packet, after packet and header protection are removed, to be treated as a connection error of type `PROTOCOL_VIOLATION`.
Relevant RFC sections:
* RFC 9000 §7.3: https://www.rfc-editor.org/rfc/rfc9000.html#section-7.3
* RFC 9000 §18.2: https://www.rfc-editor.org/rfc/rfc9000.html#section-18.2
* RFC 9000 §17.2: https://www.rfc-editor.org/rfc/rfc9000.html#section-17.2
* RFC 9000 §17.3.1: https://www.rfc-editor.org/rfc/rfc9000.html#section-17.3.1
Contributor guide
Research direction
Start with the missing_initial_source_connection_id test in quiche/src/tests.rs, then trace the related Connection::close() and send() paths in quiche/src/lib.rs. Run cargo test -p quiche missing_initial_source_connection_id -- --nocapture. Done means the recorded transport error remains sendable and produces an Initial CONNECTION_CLOSE with TRANSPORT_PARAMETER_ERROR when Initial keys are available.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- networking
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 52/100