Repository navigation
Conversation
The inflate decode tables keep codes longer than the short table index in long_code_lookup, which has 80 entries for the distance code and 1264 for the lit/len code. igzip_lib.h sizes it for the codes, entries that repeat codes shorter than the longest code of their group, and entries that no code uses, which only incomplete codes have. setup_dynamic_header() only rejects oversubscribed codes, so a header can define an incomplete code, and the bound on unused entries is too small: it allows 21 for the distance code, but a 15-bit code alone in the last group leaves 31 entries unused. An incomplete distance code can then need 86 entries: 30 codes, 25 repeats, and 31 unused entries. make_inflate_huff_code_dist() writes them without a check: it clears up to 12 bytes past long_code_lookup, which are the block_state, dict_length, and bfinal fields of inflate_state. With the stream added to the standard vectors, bfinal is cleared, and inflate returns ISAL_END_INPUT instead of finishing the final block. Before writing each group of long codes, check that it fits, and return ISAL_INVALID_BLOCK if it does not. Complete codes always fit: a complete distance code needs at most 48 entries. zlib rejects incomplete codes as invalid, except a lit/len or distance code of a single code of length 1 and an empty distance code. None of these has long codes, so the streams this rejects are not valid for zlib either. The lit/len table gets the same check, since its size was worked out the same way, though no lit/len code was found that needs more than 1208 of its 1264 entries. Signed-off-by: Parker Loflin <parker@cronstable.dev>
This was referenced Oct 21, 2026
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.
An incomplete distance code in a dynamic block header can make inflate write past
dist_huff_code.long_code_lookup, into theblock_state,dict_lengthandbfinalfields ofinflate_state. This PR checks that the long codes fit before writing them, and rejects the header withISAL_INVALID_BLOCKif they do not. The size and layout ofinflate_statedo not change.The bug
long_code_lookupholds the codes longer than the short table's index (10 bits for distances, 12 for lit/len), one part per group of long codes that share their first bits.igzip_lib.hsizes it at 80 entries for distances and 1264 for lit/len, counting the codes, the entries that repeat codes shorter than the longest in their group, and the entries no code uses, which only incomplete codes have.setup_dynamic_header()rejects only oversubscribed codes, so incomplete codes get through, and the bound on unused entries is too small: it allows 21 for distances, but a 15-bit code alone in the last group leaves 31 entries of that group unused.An exhaustive search over the code length counts of distance codes found incomplete codes that need up to 86 entries (30 codes, 25 repeats and 31 unused entries); no complete code needs more than 48.
make_inflate_huff_code_dist()writes the extra entries, always zeros, without a check, up to 12 bytes past the array. The write stays insideinflate_state, in C code that every architecture uses. Onlybfinalmatters, becauseblock_stateis set right after the tables are built and inflate does not readdict_length: on the stream this PR adds to the standard vectors, master'sisal_inflate_stateless()returnsISAL_END_INPUTinstead of finishing the final block.zlib rejects incomplete codes, except a single code of length 1 (lit/len or distance) and an empty distance code. None of these has long codes, and every code this PR rejects is incomplete, so zlib also rejects every stream this PR rejects.
Changes
Before writing each group of long codes,
make_inflate_huff_code_lit_len()andmake_inflate_huff_code_dist()check that it fits inlong_code_lookupand returnISAL_INVALID_BLOCKif not;setup_dynamic_header()and the static header fallback return that error. For lit/len, searches over code lengths found no code that needs more than 1,208 of the 1,264 entries, so that check is a safeguard. The commit adds the stream above to the standard vectors ofigzip_rand_test, which expectsISAL_INVALID_BLOCK.Testing
On Linux x86-64:
make -f Makefile.unx checkpasses for the x86-64 build andarch=noarch, andtools/check_format.shpasses. Without the fix, the new standard vector fails with both builds: inflate returnsISAL_END_INPUTinstead ofISAL_INVALID_BLOCK.On aarch64:
make checkpasses on macOS (Apple M4) withhost_cpu=aarch64 arch=aarch64and witharch=noarch, and on Linux in Docker (GCC 14.2) withMakefile.unxand autotools. Without the fix, the new standard vector fails with both macOS builds, as on x86.ISAL_INVALID_BLOCK, stateless and stateful; master returnsISAL_DECOMP_OKfor 85 of them. Every other stream, including all 711 that need exactly 80, gives master's results.igzip_rand_test,igzip_inflate_testand about 6.5 million header mutations give no reports. AddressSanitizer does not report the overflow on master, because the write stays insideinflate_state.