docs(ledger): file CF-002 — demux TooLarge skip allocates peer-declared length
Cross-crate finding from alkhttp Review 001 (WS-12), filed here per the ledger's purpose (consumer-surfaced alkcall findings). src/channels/adapter.rs:144: the ChunkError::TooLarge arm allocates vec![0u8; length] from the peer's untrusted 8-byte header before reading; length is u32, so ~4 GiB can be pinned per connection and held indefinitely by a dribbling peer. Reachable via the alkhttp WS path by any authenticated browser. Skip/resync logic is correct; the memory shape is wrong — stream-skip with a bounded buffer instead. The normal payload arm (:159) is safe (parse_header bounds it at MAX_CHUNK_LEN); only the TooLarge arm is unbounded.
This commit is contained in:
1 parent
4c99b877e3
commit
84fe94c4d7
1 file changed
+34
@@ -11,6 +11,40 @@ Format: date | found-in (alkhttp context) | severity | status.
|
||||
|
||||
## Open
|
||||
|
||||
### CF-002 — demux `TooLarge` skip allocates the full peer-declared length up front (up to ~4 GiB from an 8-byte header) (2026-08-30)
|
||||
|
||||
- **Found in:** alkhttp Review 001, finding WS-12 (cross-crate;
|
||||
`docs/reviews/001-initial-implementation-review.md`, Part B,
|
||||
verified against tree `4a825d3`). Filed to this ledger 2026-08-30.
|
||||
`src/channels/adapter.rs:144` (`run_demux_loop_for_client`) — when a
|
||||
parsed chunk header carries `length > MAX_CHUNK_LEN`, the
|
||||
`ChunkError::TooLarge` arm allocates
|
||||
`let mut discard = vec![0u8; length as usize];` **before** reading:
|
||||
the buffer is sized from the peer's untrusted 8-byte header, and
|
||||
`length` is a `u32` (`0xFFFFFFFF` ≈ 4 GiB).
|
||||
- **Impact:** memory-shape DoS, not a correctness bug (the resync/skip
|
||||
itself is correct — alkcall's own resync test covers it). Any
|
||||
authenticated peer can send header `[ch][len = 0xFFFFFFFF]` and then
|
||||
dribble; the allocation is pinned for as long as the payload takes to
|
||||
arrive (a dribbling peer can hold it indefinitely). Via the alkhttp WS
|
||||
path this is trivially reachable by any authenticated browser; K
|
||||
concurrent connections × 4 GiB headers → OOM before the dribble even
|
||||
matters. The *normal* payload arm (`:159`, `vec![0u8;
|
||||
header.length as usize]`) is safe — `parse_header` rejects
|
||||
`length > MAX_CHUNK_LEN`, so it is bounded at 16 MiB; only the
|
||||
`TooLarge` skip arm is unbounded.
|
||||
- **Suggested direction:** stream-skip with a bounded fixed buffer
|
||||
(e.g. a 64 KiB stack/fixed buffer, `read_exact` loop consuming
|
||||
`length` bytes) instead of the pre-sized allocation. Optionally bound
|
||||
cumulative skipped bytes per connection before tearing it down, so a
|
||||
peer cannot loop `TooLarge` headers to burn bandwidth/CPU forever.
|
||||
- **alkhttp side:** nothing to change there — this is demux-internal.
|
||||
alkhttp's own read-side hardening (review-001-ws-pump-consolidation:
|
||||
explicit inbound `max_message_size`/`max_frame_size`, idle-read
|
||||
timeout) bounds the WS ingress, but a header that survives those caps
|
||||
still hits this arm, so the fix belongs on the alkcall side.
|
||||
- **Status:** open — no alkcall change made.
|
||||
|
||||
### CF-001 — `call_single_stream` write-failure maps to non-retryable `INTERNAL` (2026-08-29)
|
||||
|
||||
- **Found in:** alkhttp `from_wss` drop-monitor race tests (WS-02/CON-02,
|
||||
|
||||
Reference in new issue
Block a user