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.
3.7 KiB
3.7 KiB
Consumer findings ledger — alkhttp consuming alkcall
Findings from writing the first real consumer (alkhttp) against alkcall. alkcall is deliberately minimal; expect missing features and edge-case bugs to surface here first. Extract items into alkcall tasks/reviews as the project sees fit.
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 tree4a825d3). Filed to this ledger 2026-08-30.src/channels/adapter.rs:144(run_demux_loop_for_client) — when a parsed chunk header carrieslength > MAX_CHUNK_LEN, theChunkError::TooLargearm allocateslet mut discard = vec![0u8; length as usize];before reading: the buffer is sized from the peer's untrusted 8-byte header, andlengthis au32(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_headerrejectslength > MAX_CHUNK_LEN, so it is bounded at 16 MiB; only theTooLargeskip arm is unbounded. - Suggested direction: stream-skip with a bounded fixed buffer
(e.g. a 64 KiB stack/fixed buffer,
read_exactloop consuminglengthbytes) instead of the pre-sized allocation. Optionally bound cumulative skipped bytes per connection before tearing it down, so a peer cannot loopTooLargeheaders 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_wssdrop-monitor race tests (WS-02/CON-02, review-001-ws-eof-signal). When the transport mux dies mid-call, the write path fails fast and alkcall'scall_single_streamwrite-failure mapping produces a non-retryableINTERNAL: failed to write request frame— even though the call never reached the producer and a reconnect/retry would be safe. - Impact: consumers that drop connections under load (WS EOF, network flaps) get non-retryable errors for in-flight calls that are provably not processed. Callers must either tolerate-or-discriminate two envelope shapes for the same transport-death race, or miss retry opportunities.
- Suggested direction: classify write failures that occur before any
response frame could arrive as retryable (
CONNECTION_CLOSEDor equivalent) — or expose the distinction so consumers can decide. - alkhttp side: the race test tolerates both outcomes for now (asserts retryable only when the call is provably in-flight). If/when alkcall fixes the mapping, alkhttp's ws-eof-signal test should tighten back to retryable-only.
- Status: open — no alkcall change made.
Resolved
(empty)