Decompress: guess a multiple of the input for unknown-size frames - #170
Merged
KrisKennawayDD merged 1 commit intoJul 27, 2026
Merged
Conversation
KrisKennawayDD
force-pushed
the
kris.kennaway/decompress-hint-multiple-of-src
branch
2 times, most recently
from
July 27, 2026 17:35
fedaad9 to
8d3c939
Compare
decompressSizeHint returned a flat lower bound of decompressSizeBufferLimit (1 MB) whenever the frame did not carry its decompressed size -- legacy zstd v0.5 frames and streaming frames compressed without a pledged size. Every such Decompress with no adequately sized caller buffer allocated at least 1 MB regardless of the real payload size. Now that decompressSizeHint reports whether the size was found, return 3x the input length for the unknown case instead of the flat bound, letting Decompress fall back to streaming if that is too small. This keeps a single decode path (no extra retry loop) and only ever sizes from the input length, so it does not weaken the zip-bomb guard; 3x is always within the existing 50x cap. BulkProcessor.Decompress has no streaming fallback (the streaming reader ignores the dictionary), so it keeps allocating the upper bound for unknown-size frames rather than the smaller guess, which it could not recover from. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
KrisKennawayDD
force-pushed
the
kris.kennaway/decompress-hint-multiple-of-src
branch
from
July 27, 2026 17:36
8d3c939 to
8912568
Compare
KrisKennawayDD
marked this pull request as ready for review
July 27, 2026 17:39
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Supersedes #169 (per review: keep a single decode path instead of adding a retry branch).
Problem
decompressSizeHintreturned a flat lower bound ofdecompressSizeBufferLimit(1 MB) whenever the frame did not carry its decompressed size — legacy zstd v0.5 frames and streaming frames compressed without a pledged size. So everyDecompressof such a frame with no adequately-sized caller buffer (e.g.Decompress(nil, src)) allocated at least 1 MB regardless of the real payload size.Background
The flat-bound sizing dates to two 2022 commits:
decompressSizeHint/decompressSizeBufferLimitto fix a DoS (Decoder doesn't check output size before allocating #60): the code was trustingZSTD_getFrameContentSize, so a crafted frame could force a huge allocation. The cap (≤ 50× input, ≥ 1 MB) + streaming fallback fixed that.That premise does not hold for legacy v0.5 frames or unpledged streaming frames — which is exactly this case. For them the hint is absent, so the code fell back to the flat 1 MB bound on every decode.
Fix
Now that
decompressSizeHintreports whether the size was found (added in #168), return 3× the input length for the unknown case instead of the flat bound.Decompressalready falls back to streaming if that is too small, so this needs no new branch on the decode path — the logic stays indecompressSizeHint.3×is always within the existing50×cap, so the DoS guard from 30c4b29 is unchanged.BulkProcessor.Decompresshas no streaming fallback (the streaming reader ignores the dictionary), so it keeps allocating the upper bound for unknown-size frames rather than this smaller guess, which it could not recover from.🤖 Generated with Claude Code