Expand description
A bound on RESP2 nesting and declared lengths, applied before a decoder ever sees the bytes.
redis-protocol 6.0 has no depth limit. resp2::decode walks a frame with
d_parse_frame → d_parse_array → nom::multi::count(d_parse_frame, len), one level of
Rust recursion per nested array, and build_owned_frame then recurses over the result a
second time. Nothing in either path counts how deep it has gone.
*1\r\n opens a level in four bytes, so a few hundred kilobytes overflow a tokio
worker’s 2 MiB stack, and the Redis server’s only size bound is its 64 MiB
incomplete-frame buffer. This is not a panic. A Rust stack overflow raises SIGSEGV
against the guard page and the runtime aborts; tokio::spawn cannot contain it. One
unauthenticated connection takes the whole of netget down with it. Confirmed by sending
100 000 levels to the unguarded server: the test binary died with
fatal runtime error: stack overflow.
So the bytes are walked iteratively first. scan_resp2_frame keeps one counter per
open aggregate in a fixed array of MAX_RESP_DEPTH slots, allocates nothing, never
recurses, and reads each byte at most twice (once to find a line’s CRLF, once to parse it).
Only a frame it reports as RespScan::Complete is handed to the decoder.
It follows the decoder’s grammar exactly where the two could otherwise disagree — a length
is parsed with the same str::parse::<isize> the crate uses, a bulk string’s two trailing
bytes are skipped without being checked for CRLF just as nom_take(2) does — because a
guard that rejected something the decoder would have accepted is a behaviour change, and one
that accepted something the decoder would then recurse into is no guard at all.
Declared lengths. nom 7.1.3’s count caps its initial Vec::with_capacity at
64 KiB (MAX_INITIAL_CAPACITY_BYTES, src/multi/mod.rs:577), so *4000000000\r\n is not
an allocation bomb by itself. With depth bounded, the most the decoder can pre-allocate at
once is MAX_RESP_DEPTH × 64 KiB. What a declared length is is a promise about bytes
that have not arrived, and one the caller’s buffer cap guarantees can never be kept should
be refused on the header rather than after the peer has filled the buffer.
RespLimits::max_bulk_len does that for $N, and RespLimits::max_elements bounds
both any single *N and the total number of values in one frame, which is what bounds the
decoder’s per-value allocation (a RangeFrame and then an OwnedFrame for every three-byte
+\r\n the peer sends).
Structs§
- Resp
Limits - The bounds
scan_resp2_frameenforces.
Enums§
- Resp
Length - Which declared length was refused.
- Resp
Scan - The verdict on the front of a buffer.
Constants§
- MAX_
RESP_ DEPTH - Deepest nesting of arrays a RESP2 frame may use before it is refused.
- MAX_
RESP_ ELEMENTS - Largest declared array length, and the most values one frame may contain in total.
Functions§
- scan_
resp2_ frame - Scan the RESP2 frame at the front of
datawithout recursing or allocating.