Repository navigation
Conversation
decode_huffman_code_block_stateless_01 and _04 decode most of a block in a main loop that stops IN_BUFFER_SLOP (8) bytes before the end of the input and OUT_BUFFER_SLOP (274) bytes before the end of the output, by moving end_in and end_out back. The exits from the main loop at the end of a block or near the ends of the buffers move them forward again, but the exits for an invalid lit/len symbol, an invalid distance symbol, and an invalid look-back distance do not. avail_in then comes out 8 bytes short and avail_out 274 bytes short, so next_in + avail_in and next_out + avail_out no longer reach the ends of the buffers. The main loop can load input up to 6 bytes past its end_in, so avail_in can also wrap to about 4 GB. Compute avail_in and avail_out from the buffer ends given in the inflate_state, which the function does not change before it returns. This gives the same results for the exits that already restored end_in and end_out. Signed-off-by: Parker Loflin <parker@cronstable.dev>
In the main loop of decode_huffman_code_block_stateless_01 and _04, a length symbol moves next_out past the whole copy before the distance is decoded and checked. If the distance symbol is invalid, or the distance reaches back before the start of the output, the function returns ISAL_INVALID_SYMBOL or ISAL_INVALID_LOOKBACK with next_out still past the copy. total_out then counts up to 258 bytes that the copy never wrote, and avail_out is short by as much. isal_inflate_stateless() reports those bytes of the output buffer as output. isal_inflate() also copies them to the caller when it decodes into tmp_out_buffer. The first of them come from the decoder itself: its 8-byte store of the length symbol, and copies that wrote past their end. The rest are whatever the buffer held before: earlier output of the same stream, data of an earlier stream inflated with the same inflate_state, or memory that was never initialized, since isal_inflate_init() and isal_inflate_reset() do not clear it. Move next_out back by the length of the copy on these two exits. The exits outside the main loop check the distance before they move next_out, and do not change. Signed-off-by: Parker Loflin <parker@cronstable.dev>
Add a test to igzip_rand_test that inflates a block of literals followed by an invalid lit/len symbol, or by a length with an invalid distance symbol or with a distance before the start of the output. The errors come near and far from the ends of the input and output buffers, and inflate runs both stateless and stateful. The test checks that inflate returns the error with exactly the literals as output, and that next_in + avail_in and next_out + avail_out are the ends of the buffers. Without the two previous commits, the test fails on x86 when the error is far from the ends of the buffers. Signed-off-by: Parker Loflin <parker@cronstable.dev>
This was referenced Oct 20, 2026
ptweezy
added a commit
to ptweezy/isa-l
that referenced
this pull request
Oct 21, 2026
The main loop of the x86 inflate decoder stops OUT_BUFFER_SLOP (274) bytes before the end of the output buffer and leaves the rest to end_loop_block, which decodes a symbol at a time and copies matches with rep movsb. When the output buffer is the size of the output, that is a quarter of a 1 KB stream. Within OUT_BUFFER_SLOP of the end, decode the next lookup in the main loop if its output fits: 8 bytes for literals, and 1 + the length + 2 * COPY_SIZE bytes for a length, which covers the literal before it and the stores of both copy loops. Only the last bytes, and codes longer than the lookup, go to end_loop_block. Errors in the last 274 bytes of the output then take the main loop's error exits, whose total_out, avail_in and avail_out counts intel#446 fixes. Errors further from the end already take them, and avail_out wraps around in a few of those cases; near the end it wraps in most. Together with the next commit, inflate of 1 KB zlib -6 streams into an output buffer of their size gets 11% to 15% faster on GitHub-hosted x86 runners, and of 4 KB streams 4% to 7% faster. Signed-off-by: Parker Loflin <parker@cronstable.dev>
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.
When the main loop of the x86 inflate decoders (
decode_huffman_code_block_stateless_01and_04, built fromigzip/igzip_decode_block_stateless.asm) finds an invalid distance symbol, a distance before the start of the output, or an invalid lit/len symbol, it leaves wrong counts ininflate_state. In stateful inflate, it can also hand stale bytes to the caller.The bugs
next_outpast the whole copy of a length/distance pair before it decodes and checks the distance, and both distance errors return from there.total_outthen counts up to 258 bytes that the copy never wrote, andavail_outis short by as much.isal_inflate_stateless()reports those bytes of the output buffer as output.isal_inflate()copies them to the caller with the error. For the first 64 KB of a stream it decodes intotmp_out_buffer, so apart from the first few bytes, the caller gets whatever that buffer held: earlier output of the same stream, output of an earlier stream that used the sameinflate_state, or uninitialized memory (isal_inflate_init()andisal_inflate_reset()do not clear it). Later in a stream, the caller gets the old contents of its own buffer.end_inandend_outback, and its three error exits do not restore them.avail_incomes out 8 bytes short andavail_out274 bytes short on top of bug 1. Both can wrap to about 4 GB, and a caller that callsisal_inflate()again after the error then reads past the end of its input.The exits outside the main loop are correct. The C and aarch64 decoders check the distance before moving
next_outand track the real buffer ends, so they are not affected.igzip_rand_testmisses these bugs because it checks the inflate state only when inflate succeeds.Changes
avail_inandavail_outat the end of the function from the buffer ends ininflate_state, which the function does not change. The exits that already restoredend_inandend_outgive master's results.next_outback by the length of the copy.igzip_rand_testthat inflates a block of literals followed by an invalid lit/len symbol, a length with an invalid distance symbol, or a distance before the start of the output, with the error near and far from the buffer ends, stateless and stateful. It checks the return code,total_out, the output, and thatnext_in + avail_inandnext_out + avail_outare the ends of the buffers.Testing
On Linux x86-64 with an AVX2 CPU, where
make checkruns_04:make -f Makefile.unx checkpasses for x86-64 andarch=noarch, andtools/check_format.shpasses.total_out3 andavail_out23 instead of 0 and 300. With only the first fix, the distance cases still fail. The test passes on master witharch=noarch, and on this PR with_01dispatched.arch=noarch). This PR matches the C decoder's return code,total_out, output and statelessavail_outin every case; master'stotal_outdiffers in 2,035.avail_incan differ by a few bytes because the decoders buffer different amounts of input, butnext_in + avail_inis the end of the input in both, except for stateless inflates whose input ends inside a dynamic block header (a separate bug in the C code ofisal_inflate_stateless()).On Windows 11 (Ryzen 7 9800X3D), the mingw-w64 x86-64 build passes
make checknatively, including the new test, with_04and with_01dispatched.On aarch64,
make checkpasses on macOS (Apple M4; each commit witharch=aarch64, the tip witharch=noarch) and at the tip on Linux in Docker (GCC 14.2,Makefile.unxand autotools), and the x86 commits leave every aarch64 library object unchanged. Added to master alone, the new test passes with the aarch64 and C decoders, and fails with a copy of the C decoder changed to count a copy whose distance check fails, as the x86 decoder does. In 100,000 inflates on the M4 (stateless and stateful, about a third corrupted), the aarch64 and C decoders give the same return code,total_out, output,avail_out,avail_inandnext_in.