Skip to content

fix(ipc): bound decompressed buffer size by the codec before allocating - #51

Merged
lorenzhs merged 1 commit into
release-24.0.0from
lorenz/ipc-bounded-decompress
Sep 29, 2026
Merged

lorenzhs merged 1 commit into
release-24.0.0from
lorenz/ipc-bounded-decompress

Conversation

@lorenzhs

@lorenzhs lorenzhs commented Sep 28, 2026 •

Copy link
Copy Markdown
Collaborator

DecompressBuffer allocated the uncompressed length from each buffer's 8-byte prefix before decompressing, so a 1 KiB stream could claim a multi-GiB or multi-TiB buffer.

Reject a declared size the codec cannot produce from the compressed bytes (LZ4 frame: 255x, ZSTD: 32768x, since a block yields at most 128 KiB from at least 4 bytes), and reject negative sizes other than the -1 "uncompressed" marker. Buffers up to 64 MiB keep the one-shot decode. Larger ones decode through the codec's streaming decompressor into a buffer that starts at 64 MiB and doubles up to the declared size, so an overstated size allocates only what the stream really yields.


Note

Medium Risk
Changes decompression allocation and validation on the IPC read path; behavior for malformed or adversarial metadata improves but legitimate edge cases near size limits need correct streaming and expansion math.

Overview
Hardens IPC buffer decompression so untrusted declared uncompressed lengths cannot force huge allocations before decode.

DecompressBuffer now rejects invalid negative lengths (other than -1), and uses codec-specific max expansion checks (LZ4 frame 255×, ZSTD ~32K×) to reject impossible declared sizes. Declarations above 64 MiB no longer get a single upfront allocation at the claimed size; they go through new StreamDecompressBuffer, which starts at 64 MiB, grows by doubling up to the declared cap, and errors if output exceeds the declared length or does not match it exactly.

Reviewed by Cursor Bugbot for commit 153b538. Bugbot is set up for automated code reviews on this repo. Configure here.

DecompressBuffer allocated the uncompressed length from each buffer's
8-byte prefix before decompressing, so a 1 KiB stream could claim a
multi-GiB or multi-TiB buffer.

Reject a declared size the codec cannot produce from the compressed
bytes (LZ4 frame: 255x, ZSTD: 32768x, since a block yields at most
128 KiB from at least 4 bytes), and reject negative sizes other than
the -1 "uncompressed" marker. Buffers up to 64 MiB keep the one-shot
decode. Larger ones decode through the codec's streaming decompressor
into a buffer that starts at 64 MiB and doubles up to the declared
size, so an overstated size costs only what the stream really yields.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
constexpr int64_t kMaxTrustedUncompressedSize = int64_t{64} << 20;

// Most output one input byte can produce: an LZ4 match length grows by 255 per
// extension byte, and a ZSTD block yields at most 128 KiB from at least 4 bytes.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an interesting approach - we probably can translate it to other cases - it's been a real struggle to validate uncompressed_length, because in principal it can be unbounded

return 255;
case Compression::ZSTD:
return (int64_t{128} << 10) / 4;
default:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so other schemes are still vulnerable ?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lorenzhs
lorenzhs merged commit ed0c4c9 into release-24.0.0 Sep 29, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants