From 84fe94c4d76aa62da2eb41584ae9073794197478 Mon Sep 17 00:00:00 2001 From: "glm-5.3-flash" Date: Sun, 30 Aug 2026 06:16:30 +0000 Subject: [PATCH] =?UTF-8?q?docs(ledger):=20file=20CF-002=20=E2=80=94=20dem?= =?UTF-8?q?ux=20TooLarge=20skip=20allocates=20peer-declared=20length?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- docs/reviews/consumer-findings-ledger.md | 34 ++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/docs/reviews/consumer-findings-ledger.md b/docs/reviews/consumer-findings-ledger.md index dca8b61..1a96c71 100644 --- a/docs/reviews/consumer-findings-ledger.md +++ b/docs/reviews/consumer-findings-ledger.md @@ -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,