feat(core): harden HTTP/2 abuse resistance
This commit is contained in:
@@ -982,21 +982,43 @@ window and pool byte capacity together; never raise credit independently of boun
|
||||
|
||||
---
|
||||
|
||||
## DEC-29 — Keep HTTP/2 opt-in until the adversarial phase is complete
|
||||
## DEC-29 — Keep HTTP/2 opt-in through the cleartext rollout boundary
|
||||
|
||||
**Context.** Trailers and push streaming make the protocol feature-complete for ordinary and gRPC-
|
||||
shaped traffic, but the dedicated rate-based and composite abuse controls are deliberately owned
|
||||
by the following security phase.
|
||||
|
||||
**Decision.** Keep `FlashConfiguration.http2Enabled` defaulting to `false` during this phase.
|
||||
Applications can enable the complete path explicitly; the default changes only after the hostile-
|
||||
peer suite and its limits are green.
|
||||
**Decision.** Keep `FlashConfiguration.http2Enabled` defaulting to `false`. Applications can
|
||||
enable the complete path explicitly. The Phase 13 hostile-peer suite is now green, but the same
|
||||
flag currently also admits cleartext prior-knowledge traffic; Phase 14 owns splitting that into a
|
||||
separate `http2CleartextEnabled` opt-in before the general protocol default can change safely.
|
||||
|
||||
**Consequence.** Existing deployments do not silently expose a newly completed protocol before its
|
||||
adversarial gate. This is rollout sequencing, not an architectural separation: both protocols use
|
||||
the same public request/response, header, trailer and streaming APIs.
|
||||
|
||||
**Revisit when.** At Phase 13 closure; either flip the default with evidence or record why it must
|
||||
remain opt-in.
|
||||
**Revisit when.** At Phase 14 closure, after TLS HTTP/2 and cleartext h2c have independent rollout
|
||||
controls.
|
||||
|
||||
---
|
||||
|
||||
## DEC-30 — Rate-limit aggregate non-progress work as one class
|
||||
|
||||
**Context.** SETTINGS, PING, PRIORITY, WINDOW_UPDATE, empty DATA and unknown extension frames have
|
||||
different wire semantics but share the abuse property that they can consume parser/control work
|
||||
without advancing an application message. Separate limits leave gaps when an attacker alternates
|
||||
frame types below every individual threshold.
|
||||
|
||||
**Decision.** Keep dedicated lower limits for mandatory SETTINGS and PING replies, plus one
|
||||
connection-owned two-bucket counter for the aggregate non-progress class. RST_STREAM and stream
|
||||
creation retain dedicated CVE-2023-44487 counters because their expensive effect is stream
|
||||
lifecycle churn, not merely frame parsing.
|
||||
|
||||
**Consequence.** Mixed floods are bounded without six timers or maps. All counters are fixed fields
|
||||
on the connection, use `System.nanoTime()`, allocate nothing per increment and require no reaper
|
||||
thread. A fixed control-intent pool and one-in-flight intent per live stream bound write queues.
|
||||
|
||||
**Revisit when.** Production telemetry shows legitimate control-heavy traffic approaching the
|
||||
aggregate default; tune the threshold from evidence without splitting the defence by frame type.
|
||||
|
||||
---
|
||||
|
||||
@@ -74,7 +74,7 @@ Status values: `not started` / `in progress` / `blocked` / `done`.
|
||||
| 10 — Stream state machine + dispatch | done | `feature/core/http2` | Explicit stream transition table, bounded primitive stream table and pool, pseudo-header/message validation, protocol-neutral `Request` assembly, virtual-thread dispatch and exception path, cancellation-safe release, raw h2c + Java HTTP/2 integration. Phase 11 closed the two deferred content-length/DATA cases; h2spec sections 5/8 are now 39/39. JMH pooled lifecycle: 458.499 ns/op, 0.003 B/op, no GC. 618/618 tests green at phase closure. |
|
||||
| 11 — DATA, flow control, bodies | done | `feature/core/http2` | Two-level receive/send flow control, consumption-driven WINDOW_UPDATE hysteresis, bounded/coalescing DATA pool, inline and blocking streaming request bodies through the existing `RequestBody`, resumable fixed/known/unknown response streams, content-length and empty-DATA validation. Real TLS HTTP/2 transfer: 100 MiB upload + 100 MiB download verified byte-for-byte. h2spec combined sections 5, 6.1, 6.9 and 8: 50 passed, 1 tool-skipped, 0 failed. JMH: inline materialization exactly one 1,040-byte array; request streaming 0.001 B/op; response streaming 0.002 B/op; full pooled lifecycle 0.003 B/op. 633/633 tests green from a clean `-Pjmh` build. |
|
||||
| 12 — Trailers, half-close, gRPC | done | `feature/core/http2` | Protocol-neutral request/response trailers, bounded push streaming, four half-close orderings and authority-form CONNECT tunnels complete. Real grpcurl 1.9.3 unary/server-streaming/error interop passes. EX-48/49 fixed. HTTP/2 remains opt-in until the Phase 13 hostile-peer gate (DEC-29). 649/649 tests green from a clean `-Pjmh` build. |
|
||||
| 13 — Security hardening & abuse resistance | not started | — | — |
|
||||
| 13 — Security hardening & abuse resistance | done | `feature/core/http2` | Two-bucket Rapid Reset/stream/settings/ping/aggregate counters, control/write queue bounds, optional stream/byte/lifetime budgets, absolute header and idle-stream deadlines, and hostile-peer suite complete. Security review found+fixed EX-50/51. JMH counter: 38.083 ns/op, ~10^-4 B/op, no GC. 100k-CONTINUATION attack terminates in under 2 s with bounded retained heap. 663/663 tests green from a clean `-Pjmh` build. |
|
||||
| 14 — h2c prior knowledge + proxy support | not started | — | — |
|
||||
| 15 — RFC 8441 extended CONNECT (WS over h2) | not started | — | — |
|
||||
| 16 — Compliance test suite | not started | — | — |
|
||||
@@ -794,6 +794,25 @@ targets and HTTP/2 `:authority` arrive without that prefix, so the existing CONN
|
||||
never match its documented target. **Fix**: normalize CONNECT authority targets separately in the
|
||||
shared router registration path and verify a live bidirectional HTTP/2 tunnel. **Phase**: 12.
|
||||
|
||||
### EX-50 — Declared HTTP/2 header and stream idle deadlines were not enforced
|
||||
|
||||
Found during the whole-package hostile-peer review. `HEADER_BLOCK_ASSEMBLY_TIMEOUT_MS` and
|
||||
`STREAM_IDLE_TIMEOUT_MS` existed in `Http2Limits` and were described as enforced defences, but no
|
||||
production path read either constant. A peer could retain a CONTINUATION assembly or an open
|
||||
stream indefinitely. **Fix**: give header assembly an absolute non-renewable deadline checked on
|
||||
frames and read wakeups; track per-stream activity and cancel idle streams with `RST_STREAM
|
||||
CANCEL`; expose the stream deadline operationally and add deadline regression tests. **Phase**: 13.
|
||||
|
||||
### EX-51 — Concurrent half-close could retire the same pooled HTTP/2 stream twice
|
||||
|
||||
Found when the clean integration suite logged an internal error despite passing its assertions.
|
||||
The demultiplexer and response-completion thread could both observe a closed stream, then one
|
||||
thread could recycle it before the other read its id. The loser attempted to remove stream id
|
||||
zero; a more unfortunate interleaving could have touched a reused pooled object. **Fix**: make
|
||||
stream retirement atomic in `Http2StreamTable` and require both the expected stream id and object
|
||||
identity to match the live table entry. A regression test proves that a stale retirement cannot
|
||||
remove the next generation of the same pooled object. **Phase**: 13.
|
||||
|
||||
---
|
||||
|
||||
# PART III — The phases
|
||||
@@ -2834,9 +2853,11 @@ of allocated memory (assert with a heap sample, not a hope).
|
||||
applicable, and how to tune it. This is the document an operator reads at 3 a.m.
|
||||
|
||||
### DoD
|
||||
- [ ] Every attack in this phase has a test that proves the defence.
|
||||
- [ ] Every limit is documented with its rationale.
|
||||
- [ ] A `security-review` pass over the whole `h2` package is completed and its findings fixed.
|
||||
- [x] Every attack in this phase has a test that proves the defence.
|
||||
- [x] Every limit is documented with its rationale.
|
||||
- [x] A `security-review` pass over the whole `h2` package is completed and its findings fixed
|
||||
(`EX-50`: declared header-assembly and idle-stream deadlines were not wired; `EX-51`:
|
||||
concurrent half-close could retire the same pooled stream twice).
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
# HTTP/2 security controls
|
||||
|
||||
HTTP/2 multiplexing lets one connection create disproportionate parser, stream and response work.
|
||||
Flash therefore combines structural bounds, flow-control bounds and rate bounds. Rate counters use
|
||||
two fixed half-window buckets, allocate nothing per frame and need no timer thread.
|
||||
JMH on JDK 21 measures one rate-counter increment at 38.083 ns/op and approximately
|
||||
`10^-4 B/op` (allocation noise floor, no GC).
|
||||
|
||||
| Limit | Default | Defence / tuning guidance |
|
||||
|---|---:|---|
|
||||
| `MAX_CONCURRENT_STREAMS` | 64 | Bounds simultaneously retained stream state. |
|
||||
| `MAX_STREAMS_CREATED_PER_INTERVAL` | 400 / 10 s | Companion to Rapid Reset; tune with `h2MaxStreamsCreatedPerInterval`. |
|
||||
| `MAX_RESET_STREAMS_PER_INTERVAL` | 200 / 10 s | CVE-2023-44487 Rapid Reset; tune with `h2MaxResetStreamsPerInterval`. |
|
||||
| `MAX_CONTINUATION_FRAMES_PER_BLOCK` | 8 | CVE-2024-27316 CONTINUATION flood. |
|
||||
| `MAX_HEADER_LIST_SIZE` | 32 KiB | Stops HPACK expansion before fields reach stream storage. |
|
||||
| `MAX_HPACK_STRING_LENGTH` | 8 KiB | Bounds one decoded literal, including Huffman expansion. |
|
||||
| `MAX_SETTINGS_PER_INTERVAL` | 100 / 10 s | Bounds mandatory SETTINGS acknowledgements. |
|
||||
| `MAX_PINGS_PER_INTERVAL` | 200 / 10 s | Bounds mandatory PING acknowledgements. |
|
||||
| `MAX_USELESS_FRAMES_PER_INTERVAL` | 10,000 / 10 s | Aggregate CPU bound for PRIORITY, WINDOW_UPDATE, empty DATA and unknown frames. |
|
||||
| `MAX_SETTINGS_ACK_QUEUE_DEPTH` | 64 | Bounds queued SETTINGS control writes. |
|
||||
| `MAX_PING_QUEUE_DEPTH` | 64 | Bounds queued PING control writes. |
|
||||
| `MAX_EMPTY_DATA_FRAMES_PER_STREAM` | 1,000 | Stops DATA work that spends no flow-control credit. |
|
||||
| `INITIAL_WINDOW_SIZE_LOCAL` | 1 MiB | Matches the bounded DATA pool; consumption, not receipt, returns credit. |
|
||||
| `MAX_REQUEST_BODY_SIZE` | 100 MiB | Hard per-stream request body bound. |
|
||||
| `HEADER_BLOCK_ASSEMBLY_TIMEOUT_MS` | 10 s | Absolute HEADERS-to-END_HEADERS deadline. |
|
||||
| `STREAM_IDLE_TIMEOUT_MS` | 60 s | Cancels retained inactive streams; tune with `h2StreamIdleTimeoutMs`. |
|
||||
| `FRAME_READ_TIMEOUT_MS` | 20 s | Absolute partial-frame deadline. |
|
||||
| `WRITE_TIMEOUT_MS` | 30 s | Interrupts a socket writer blocked by a peer that stopped reading. |
|
||||
| `MAX_STREAMS_PER_CONNECTION` | 100,000 | Optional connection churn budget; zero disables, tune with `h2MaxStreamsPerConnection`. |
|
||||
| `MAX_BYTES_PER_CONNECTION` | disabled | Optional wire-byte budget; tune with `h2MaxBytesPerConnection`. |
|
||||
| `MAX_CONNECTION_LIFETIME_MS` | disabled | Optional lifetime rotation; tune with `h2MaxConnectionLifetimeMs`. |
|
||||
|
||||
`h2AbuseRateIntervalMs` changes the rolling interval for reset and stream-creation operator
|
||||
limits. Breaching a connection-wide rate or budget produces GOAWAY `ENHANCE_YOUR_CALM`; malformed
|
||||
stream-local messages use the RFC-defined stream error. The ordinary write queue is bounded by the
|
||||
64 live streams and their single-in-flight response intent; control writes use the fixed scratch
|
||||
slots above, so a slow reader cannot create an unbounded application queue.
|
||||
|
||||
The security suite covers Rapid Reset, stream churn, SETTINGS/PING/non-progress floods, 100,000
|
||||
CONTINUATION frames, HPACK expansion, malformed names/pseudo-fields, configurable resource budgets,
|
||||
header assembly deadlines and idle-stream cancellation. HTTP/1 request/header/body security tests
|
||||
remain in the full suite and the shared public message model uses the same bounds on both paths.
|
||||
Reference in New Issue
Block a user