Skip to main content

Module resp

Module resp 

Source
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§

RespLimits
The bounds scan_resp2_frame enforces.

Enums§

RespLength
Which declared length was refused.
RespScan
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 data without recursing or allocating.