| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
All checks were successful
standards workspaces build + test / which workspaces this change touches (pull_request) Successful in 10s
repository checks / lockfiles, tests in CI, workflow hygiene, spec and section references, public text (pull_request) Successful in 1m18s
joule-code conformance gate / Build + round-trip C1-C4 vectors (pull_request) Successful in 2m5s
standards workspaces build + test / macOS energy backends (pull_request) Successful in 6m32s
standards workspaces build + test / joule-code-rs (joule-code/joule-code-rs, joule-code-rs, linux-arm64, true) (pull_request) Successful in 1m47s
The `CompressionStrategy` trait documents its third argument as
"`max_output` caps the decompressed size to prevent decompression
bombs". `ZstdCompression::decompress` implemented that as
decoder.take(max_output as u64).read_to_end(&mut output)?;
Ok(output)
`Read::take(n)` plus `read_to_end` does not detect the cap being hit.
It yields exactly `n` bytes and reports success, discarding the rest,
so the bomb case — the one condition `max_output` exists for — came
back as a value rather than a failure, and the caller could not tell a
complete payload from a truncated prefix of an arbitrarily larger one.
The consequence is not confined to the strategy. `AdaptiveCodec::decode`
calls `decompress(&raw_payload, MAX_PAYLOAD_LEN)` with a 16 MiB cap,
and on `Ok` goes on to rewrite the header — `payload_length:
payload.len() as u32`, commented "Normalize payload_length to
decompressed size" — and to fold that length into the connection
statistics via `observe_frame`. A peer frame whose zstd payload expands
to 20 MiB was therefore accepted as a well-formed, complete 16 MiB
frame, with the truncation recorded nowhere.
`Lz4Compression::decompress`, behind the same trait, has always
returned `CompressionError` for exactly this condition, so the two
implementations had opposite semantics. Only the lz4 side was tested
(`lz4_rejects_oversized_decompression`), which is why the divergence
was invisible.
Read one byte past the cap and reject when it arrives. That
distinguishes "exactly at the cap", which is valid and still returns
the whole payload, from "over the cap", which is now an error — and
brings zstd into line with lz4. The limit is computed with
`saturating_add` so a `max_output` of `usize::MAX` cannot wrap it.
Tests: `zstd_rejects_oversized_decompression` mirrors the lz4 test and
asserts on the fixture actually exceeding the cap, so it cannot pass
vacuously; `zstd_accepts_output_exactly_at_the_cap` pins the boundary
the extra byte must not break.
Co-Authored-By: Claude Opus 5 <[email protected]>
|
||
| .. | ||
| adaptive_codec.rs | ||
| codec.rs | ||
| compression.rs | ||
| encoding.rs | ||
| error.rs | ||
| frame.rs | ||
| lib.rs | ||
| negotiation.rs | ||
| profile.rs | ||
| state_machine.rs | ||
| transport.rs | ||
| transport_quic.rs | ||
| transport_tcp.rs | ||