enc/dec: harden integer overflow checks in size computations (defense-in-depth)#1471
Closed
SongT-50 wants to merge 1 commit into
Closed
enc/dec: harden integer overflow checks in size computations (defense-in-depth)#1471SongT-50 wants to merge 1 commit into
SongT-50 wants to merge 1 commit into
Conversation
…-in-depth) Add defense-in-depth size_t-overflow guards at five size-computation sites in brotli's encoder and decoder paths: - P1 c/enc/block_encoder_inc.h:18 (BuildAndStoreEntropyCodes): guard histograms_size * histogram_length_ multiplication - P2 c/dec/state.c:157 (BrotliDecoderHuffmanTreeGroupInit): guard alphabet_size_limit + 376 addition + sizeof(HuffmanCode) * ntrees * max_table_size cascade + code_size + htree_size addition - P3 c/enc/block_splitter.c:59 (CopyLiteralsToByteArray): guard from_pos + insert_len size_t wrap before > mask check - P4 c/enc/block_splitter_inc.h:457 (FindBlocks workspace): guard data_size * num_histograms and length * bitmaplen multiplications - P5 c/enc/block_splitter_inc.h:211 (ClusterBlocks workspace): guard num_blocks + 4 * HISTOGRAMS_PER_BATCH addition The cluster was identified by a multi-perspective audit (architect / defender / attacker perspectives) on the current HEAD. The 15-cell multi-perspective analysis indicates these patterns are guarded by existing decoder/encoder caps (no demonstrated reachable exploit path under the current decoder/encoder caps). Existing caps that bound the affected values under normal use: BROTLI_LARGE_MAX_WBITS = 30 (decoder window cap; 1 GB << INT_MAX) BROTLI_BLOCK_SIZE_CAP = 1 << 24 (16 MiB metablock cap) SHARED_BROTLI_MIN_DICTIONARY_WORD_LENGTH = 4 kRingBufferWriteAheadSlack = 542 decoder transform_idx range checks already present in parse path The hardening is proposed as defensive narrowing rather than a fix for a demonstrated vulnerability. All five guards are pure additions: on currently-reachable inputs the new checks are not triggered and behavior is identical; on inputs that would have wrapped size_t the code now returns early, matching the surrounding BROTLI_IS_OOM(m) / return BROTLI_FALSE pattern. CWE-190 (Integer Overflow), CWE-680 (Integer Overflow to Buffer Overflow). No public API or ABI change.
eustas
reviewed
May 4, 2026
| size_t num_blocks = 0; | ||
| const size_t bitmaplen = (num_histograms + 7) >> 3; | ||
| /* defense-in-depth: refuse if either multiplication wraps size_t. */ | ||
| if (num_histograms != 0 && data_size > SIZE_MAX / num_histograms) return; |
Collaborator
There was a problem hiding this comment.
data_size is at most 704; num_histograms is at most 100
eustas
reviewed
May 4, 2026
| const size_t bitmaplen = (num_histograms + 7) >> 3; | ||
| /* defense-in-depth: refuse if either multiplication wraps size_t. */ | ||
| if (num_histograms != 0 && data_size > SIZE_MAX / num_histograms) return; | ||
| if (bitmaplen != 0 && length > SIZE_MAX / bitmaplen) return; |
Collaborator
There was a problem hiding this comment.
bitmaplen is at most 13; length is at most 16M
eustas
reviewed
May 4, 2026
| uint8_t* block_ids, | ||
| BlockSplit* split) { | ||
| /* defense-in-depth: refuse if num_blocks + 4 * HISTOGRAMS_PER_BATCH wraps. */ | ||
| if (num_blocks > SIZE_MAX - 4 * HISTOGRAMS_PER_BATCH) return; |
Collaborator
There was a problem hiding this comment.
4 * HISTOGRAMS_PER_BATCH is 256; num_blocks is at most 16M (greatly overestimated)
eustas
reviewed
May 4, 2026
| from_pos. cmds[i].insert_len_ is encoder-internal and bounded by the | ||
| command builder; this is a defensive narrowing for hypothetical reuse | ||
| with caller-controlled insert_len. */ | ||
| if (insert_len > (size_t)0 - from_pos) { |
Collaborator
There was a problem hiding this comment.
Just return will cause catastrophic consequences.
Not speaking that both insert_len and mask below 16M, thus this situation is not possible
eustas
reviewed
May 4, 2026
| size_t* storage_ix, uint8_t* storage) { | ||
| /* defense-in-depth: refuse if histograms_size * histogram_length_ wraps size_t */ | ||
| if (self->histogram_length_ != 0 && | ||
| histograms_size > SIZE_MAX / self->histogram_length_) { |
Collaborator
There was a problem hiding this comment.
histogram_size is at most 257; histogram_length is at most 704.
eustas
reviewed
May 4, 2026
| less than 16 symbols). */ | ||
| /* defense-in-depth: each step of the size cascade can wrap size_t under | ||
| hypothetical caller / metablock values that bypass the parse-time caps. */ | ||
| if (alphabet_size_limit > SIZE_MAX - 376) return BROTLI_FALSE; |
Collaborator
There was a problem hiding this comment.
alphabet_size is at most few K.
eustas
reviewed
May 4, 2026
| if (alphabet_size_limit > SIZE_MAX - 376) return BROTLI_FALSE; | ||
| const size_t max_table_size = alphabet_size_limit + 376; | ||
| if (max_table_size != 0 && | ||
| ntrees > SIZE_MAX / sizeof(HuffmanCode) / max_table_size) { |
eustas
reviewed
May 4, 2026
| } | ||
| const size_t code_size = sizeof(HuffmanCode) * ntrees * max_table_size; | ||
| if (ntrees != 0 && ntrees > SIZE_MAX / sizeof(HuffmanCode*)) { | ||
| return BROTLI_FALSE; |
Collaborator
|
If any of limits are hard to track, prefer BROTLI_DCHECK to ensure them. |
SongT-50
added a commit
to SongT-50/brotli
that referenced
this pull request
May 4, 2026
…review) Following @eustas review of google#1471: replace early-return overflow guards with BROTLI_DCHECK assertions. This preserves the intent (document invariants) while avoiding any runtime control-flow impact. Per reviewer suggestion: "If any of limits are hard to track, prefer BROTLI_DCHECK to ensure them." - block_splitter_inc.h FN(ClusterBlocks): addition bound check - block_splitter_inc.h FN(SplitByteVector): 2 multiplication bound checks - block_splitter.c CopyLiteralsToByteArray: wrap guard - block_encoder_inc.h FN(BuildAndStoreEntropyCodes): multiplication bound - dec/state.c BrotliDecoderHuffmanTreeGroupInit: 3 bound checks
Author
|
수정된 전체 초안
Hello @eustas,
Thank you for the detailed feedback. I completely agree that
the guards were dead code and that early returns could cause
unexpected behavior if ever reached.
I have followed your advice and replaced them with
BROTLI_DCHECKs to document these invariants without
affecting control flow.
I've opened a new, clean PR here: #1472
Please let me know if it looks good to you. Thanks!
2026년 5월 4일 (월) 오후 5:47, Eugene Kliuchnikov ***@***.***>님이 작성:
*eustas* left a comment (google/brotli#1471)
<#1471 (comment)>
If any of limits are hard to track, prefer BROTLI_DCHECK to ensure them.
—
Reply to this email directly, view it on GitHub
<#1471 (comment)>, or
unsubscribe
<https://github.com/notifications/unsubscribe-auth/BJQZSLCHW2RHSJ4BAVQVLQ34ZBKIVAVCNFSM6AAAAACYO2N3PGVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHM2DGNRZGQ4DINJQGY>
.
You are receiving this because you authored the thread.Message ID:
***@***.***>
ᐧ
|
SongT-50
added a commit
to SongT-50/brotli
that referenced
this pull request
May 7, 2026
…2nd review) Following @eustas's review of PR v2: > BROTLI_DCHECK declares some readable fact, e.g. "we know/expect that > alphabet size is not more than this constant"; in this PR BROTLI_DCHECK > checks that overflow should not happen - that does not help reader to > understand why. This v3 changes the BROTLI_DCHECKs from overflow-check form (X * Y < SIZE_MAX) to declarative-invariant form (X <= LIMIT), using the bounds @eustas specified inline on PR google#1471. Changes (per @eustas inline review of google#1471) - block_splitter_inc.h SplitByteVector: data_size <= 704 (BROTLI_NUM_COMMAND_SYMBOLS, the largest alphabet that reaches SplitByteVector), num_histograms <= 100 (kMaxLiteralHistograms), bitmaplen <= 13 (derived: (100 + 7) >> 3), length <= 16 MiB (meta-block length cap per RFC 7932). - block_splitter_inc.h ClusterBlocks: num_blocks <= 16 MiB. - block_splitter.c CopyLiteralsToByteArray: BROTLI_DCHECK removed, replaced with an explanatory comment. Per @eustas: "this situation is not possible". Both insert_len and from_pos are bounded by the meta-block length (<= 16 MiB per RFC 7932), so from_pos + insert_len cannot wrap. - block_encoder_inc.h BuildAndStoreEntropyCodes: histograms_size <= 257 (number of block types per RFC 7932), histogram_length_ <= 704 (BROTLI_NUM_COMMAND_SYMBOLS). - dec/state.c BrotliDecoderHuffmanTreeGroupInit: * alphabet_size_limit <= BROTLI_NUM_DISTANCE_SYMBOLS (== 1128 per RFC 7932; the distance alphabet is the largest one handled by the decoder). * max_table_size <= BROTLI_NUM_DISTANCE_SYMBOLS + 376 (== 1504), derived from the alphabet bound plus the 376-entry second-level mix-table overhead documented just above. * ntrees < 1024. BROTLI_MAX_NUMBER_OF_BLOCK_TYPES is 256, so 1024 leaves comfortable margin. A single DCHECK at line 172 covers both the code_size and htree_size multiplications that follow. Constants verified against c/common/constants.h, c/dec/huffman.h, and c/enc/block_splitter.c: * BROTLI_NUM_DISTANCE_SYMBOLS == 1128 (constants.h:70 comment) * BROTLI_NUM_COMMAND_SYMBOLS == 704 (constants.h:25) * BROTLI_MAX_NUMBER_OF_BLOCK_TYPES == 256 (constants.h:21) * HISTOGRAMS_PER_BATCH == 64 (block_splitter.c:86, file-local define) The names BROTLI_MAX_ALPHABET_SIZE, BROTLI_HUFFMAN_MAX_TABLE_SIZE, and BROTLI_HUFFMAN_MAX_TABLE_SIZE_CODE_LENGTHS are not present in the tree; literal numeric bounds with explanatory comments are used in the corresponding slots, deliberately avoiding the HUFFMAN_MAX_CODE_LENGTH-derived value (15) which applies only to the 18-symbol code-lengths tree, not to the literal/distance trees handled here. Build: cmake -DCMAKE_BUILD_TYPE=Debug succeeds with no warnings or errors.
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.
Summary
This PR adds defense-in-depth Integer Overflow / size_t wrap checks at five
size-computation sites in brotli's encoder and decoder. The checks are
defensive: triggering the underlying issues from a normal caller path is not
currently demonstrated. The motivation is to reduce the defense-in-depth gap
and to match the hardening style already used in surrounding code (e.g. early
returns on
BROTLI_DECODER_ALLOCfailures followed by buffer/length resets).The cluster was identified by an external multi-perspective code review
(architect / defender / attacker perspectives) followed by static audit
against the current decoder/encoder caps. No exploit path was needed for
this defensive hardening.
Sites covered
c/enc/block_encoder_inc.h:18histograms_size * histogram_length_not checked for size_t overflow before allocatingdepths_/bits_c/dec/state.c:157sizeof(HuffmanCode) * ntrees * max_table_size + htree_sizecascades arithmetic without intermediate checksc/enc/block_splitter.c:59from_pos + insert_len > maskperforms the addition before the bound check, so wrap can bypass the split logicinsert_lenagainst the available tailc/enc/block_splitter_inc.h:457data_size * num_histogramsandlength * bitmaplenallocated without overflow guardsc/enc/block_splitter_inc.h:211num_blocks + 4 * HISTOGRAMS_PER_BATCHnot checked; the result is used as both an allocation size and as an offset baseAll five fixes are pure additions: when the input values would cause size_t
to wrap, the code returns early (or signals the existing out-of-memory /
format-error path that the surrounding code already handles). Behavior on
currently-reachable inputs is unchanged.
Severity / impact (honest disclosure)
These are defense-in-depth changes. We have not demonstrated a reachable
exploit path under brotli's current decoder/encoder caps:
BROTLI_LARGE_MAX_WBITS = 30(decoder window cap; 1 GB << INT_MAX)BROTLI_BLOCK_SIZE_CAP = 1 << 24(16 MiB metablock cap)SHARED_BROTLI_MIN_DICTIONARY_WORD_LENGTH = 4(dictionary word lower bound)kRingBufferWriteAheadSlack = 542(transform output slack)transform_idxrange checks already present in the parse pathUnder those caps, the size-computation sites covered here are not reachable
from a normal caller / standard brotli stream. The sites become reachable if:
an unusual path not currently exercised.
In all three cases, the additions in this PR turn an undefined-behavior
size_t wrap into a clean early return, matching the rest of the codebase's
defensive style.
We therefore frame the changes as hardening rather than as fixes for
confirmed exploitability. CWE-190 and CWE-680 are listed because the
underlying primitives are integer overflow into allocation size.
Compatibility
The added checks are pure additions. On all currently-reachable inputs the
new checks are not triggered and behavior is identical. On inputs that
would have wrapped size_t the code now returns early (encoder: no-op
return / decoder: BROTLI_FALSE), matching how the surrounding code already
handles
BROTLI_ALLOCfailure.No public API or ABI change.
Tests
Static analysis: each site mirrors the structure of an existing
overflow-aware site elsewhere in the codebase. We extracted the audit
trail from a multi-perspective review (architect / defender / attacker
perspectives) and verified each fix against the surrounding caps and
caller invariants.
Unit / harness tests: not added in this PR. The host environment used to
prepare the patch did not have a working C toolchain (no gcc / clang / cl
/ tcc on PATH), so we deferred ASan / UBSan binary verification. We're
happy to add minimal Edge-Case Test cases for each site (driven via the
encoder / decoder internal harness) in a follow-up before merge — please
let us know if maintainers prefer tests bundled in the same PR or as a
follow-up.
Honest disclosure
fuzzing campaign. No corpus / repro input is included.
"boundary defense-in-depth, not currently reachable from a standard
brotli stream under the current caps".
demonstrated end-to-end exploit, only an audit-level defense gap.
internal state (e.g. very large
histograms_size×histogram_length_)which is not reachable from caller-controlled input under the current
caps.
If maintainers prefer a different framing (e.g. split per-file, drop some
sites that are deemed unreachable even with cap relaxations, require
ASan-verified repros), we'll happily revise.
References