Fix 14 pre-existing defects found while working on fz - #143
Closed
ayzk wants to merge 1 commit into
Closed
Conversation
There was a problem hiding this comment.
Pull request overview
This PR backports a set of verified defect fixes from the fz branch onto master, focusing on memory-safety hardening, correctness fixes in encoding/prediction paths, and portability improvements (self-contained headers + gitignore/test tooling).
Changes:
- Add runtime bounds checks and safer buffer sizing to prevent out-of-bounds reads/writes and too-small compressed buffers.
- Fix correctness issues in regression coefficient loading, arithmetic decoding, time-series reconstruction, and k-means sampling.
- Improve portability by making headers self-contained and adding a header-compilation checker; fix
.gitignoreto avoid hidingtools/test/.
Reviewed changes
Copilot reviewed 32 out of 33 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| tools/test/check_headers.py | New script to compile each header standalone to catch transitive-include breakages. |
| tools/H5Z-SZ3/src/H5Z_SZ3.cpp | Uses SZ3 size bound to ensure minimum compressed buffer capacity for small chunks. |
| include/SZ3/utils/Statistic.hpp | Adds missing standard headers for self-contained compilation. |
| include/SZ3/utils/Sample.hpp | Adds missing <cassert> include ordering for self-contained compilation. |
| include/SZ3/utils/QuantOptimization.hpp | Adds missing <cmath> / SZ3/def.hpp for self-contained compilation. |
| include/SZ3/utils/MemoryUtil.hpp | Replaces release-stripped assert with exceptions for truncated streams. |
| include/SZ3/utils/KmeansUtil.hpp | Fixes UB in sampling vector sizing and adjusts random index distribution bounds. |
| include/SZ3/utils/Iterator.hpp | Adds missing SZ3/def.hpp include for self-contained compilation. |
| include/SZ3/utils/Extraction.hpp | Adds missing STL + project includes for self-contained compilation. |
| include/SZ3/utils/BlockwiseIterator.hpp | Adds missing includes and introduces block_data::values() to access unpadded layout. |
| include/SZ3/quantizer/Quantizer.hpp | Adds helper to query optional quantizer size_est() at compile time. |
| include/SZ3/quantizer/LinearQuantizer.hpp | Adds bounds check on unpredictable list during recovery. |
| include/SZ3/preprocessor/Transpose.hpp | Adds missing standard headers for self-contained compilation. |
| include/SZ3/preprocessor/PreFilter.hpp | Adds missing standard headers for self-contained compilation. |
| include/SZ3/predictor/RegressionPredictor.hpp | Fixes remaining-length accounting for Huffman-decoded coefficients. |
| include/SZ3/predictor/LorenzoPredictor.hpp | Adds missing standard headers for self-contained compilation. |
| include/SZ3/lossless/Lossless.hpp | Adds missing <cstddef> and project include for self-contained compilation. |
| include/SZ3/lossless/Lossless_bypass.hpp | Adds missing <cstdlib> include for self-contained compilation. |
| include/SZ3/encoder/XtcBasedEncoder.hpp | Adds missing includes for self-contained compilation. |
| include/SZ3/encoder/RunlengthEncoder.hpp | Implements size_est() to avoid compressor buffer under-sizing. |
| include/SZ3/encoder/HuffmanEncoder.hpp | Adds guard for excessively wide bin ranges to prevent invalid state-table sizing. |
| include/SZ3/encoder/ArithmeticEncoder.hpp | Fixes sign-extension issue by shifting an unsigned 64-bit value. |
| include/SZ3/decomposition/TimeSeriesDecomposition.hpp | Fixes timestep prediction to use reconstructed timestep-0 values consistently. |
| include/SZ3/decomposition/SZBioMDXtcDecomposition.hpp | Adds missing <limits> include for self-contained compilation. |
| include/SZ3/decomposition/NoPredictionDecomposition.hpp | Adds size_est() using quantizer estimate to prevent under-sized buffers. |
| include/SZ3/decomposition/InterpolationDecomposition.hpp | Adds size_est() and missing includes to avoid under-sized buffers and portability issues. |
| include/SZ3/decomposition/Decomposition.hpp | Adds missing includes for self-contained compilation. |
| include/SZ3/decomposition/BlockwiseDecomposition.hpp | Fixes include set/order for self-contained compilation. |
| include/SZ3/compressor/SZGenericCompressor.hpp | Adds missing <memory> include for self-contained compilation. |
| include/SZ3/compressor/specialized/SZExaaltCompressor.hpp | Adds missing includes for self-contained compilation. |
| include/SZ3/api/impl/SZAlgoInterp.hpp | Adds missing <memory> include for self-contained compilation. |
| include/SZ3/api/impl/SZAlgoBioMD.hpp | Fixes include ordering/requirements for self-contained compilation. |
| .gitignore | Changes test pattern to /test to avoid hiding tools/test/. |
Suppressed comments (1)
include/SZ3/utils/KmeansUtil.hpp:310
- The random sampling loop intends to avoid duplicate indices, but
sampledkeysis never updated, so duplicates are still possible. Alsouniform_int_distribution<>usesintbounds, which can truncate/overflow whennumexceedsINT_MAX. Use asize_tdistribution and insert into the set as you sample.
std::uniform_int_distribution<> dis2(0, static_cast<int>(num) - 1);
std::unordered_set<size_t> sampledkeys;
// printf("total_num=%lu, sample_num=%lu\n", num, sample_num);
for (size_t i = 0; i < sample_num; i++) {
do {
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
75
to
79
| void read(T1 *array, size_t num_elements, uchar const *&compressed_data_pos, size_t &remaining_length) { | ||
| assert(num_elements * sizeof(T1) <= remaining_length); | ||
| if (num_elements * sizeof(T1) > remaining_length) { | ||
| throw std::invalid_argument("SZ3: compressed stream is truncated"); | ||
| } | ||
| memcpy(array, compressed_data_pos, num_elements * sizeof(T1)); |
Comment on lines
+536
to
541
| // The state table is sized by the bin range, not the distinct count. | ||
| if (static_cast<double>(max) - static_cast<double>(offset) > 2e9) { | ||
| throw std::invalid_argument("HuffmanEncoder: bin range too wide; use HuffmanEncoderV2"); | ||
| } | ||
| int stateNum = max - offset + 2; | ||
| huffmanTree = createHuffmanTree(stateNum); |
Comment on lines
+117
to
+121
| const uchar *coeff_start = c; | ||
| regression_coeff_quant_inds = encoder.decode(c, coeff_size); | ||
| encoder.postprocess_decode(); | ||
| remaining_length -= coeff_size * sizeof(int); | ||
| // decode() advances `c` by the encoded byte count, not by the decoded bin count. | ||
| remaining_length -= static_cast<size_t>(c - coeff_start); |
Found while working on fz; each was verified to reproduce here. | Module | Defect | |---|---| | Lossless_bypass | compress() memcpy'd srcLen bytes regardless of dstCap, so any payload larger than the caller's buffer was an unconditional heap overflow | | MemoryUtil | remaining_length guarded by assert, compiled out of Release, so a truncated stream read past the buffer | | RegressionPredictor | load() debited remaining_length by the decoded rather than the encoded size | | TimeSeriesDecomposition | null-reference-frame path violated the error bound 1.94x | | ArithmeticEncoder | signed >> 20 sign-extends, corrupting about half of all streams | | HuffmanEncoder | stateNum narrowing overflow on a wide bin range | | LinearQuantizer | recover_unpred() had no bounds check | | RunlengthEncoder | no size_est(), so the caller's buffer overruns | | KmeansUtil | reserve + operator[] UB, and an off-by-one out-of-bounds read | | H5Z-SZ3 | compressed buffer sized below SZ_compress's own minimum for small chunks | | 2 decompositions | no size_est() override, so the compressor sized its buffer from 0 | | 6 headers | std::make_shared without <memory> | | 24 headers | not self-contained under libstdc++ or libc++ | | .gitignore | bare `test` pattern hid every test file | Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Collaborator
Author
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.
Every defect below was found while working on the
fzbranch and then verified to reproduce onmaster. None of the newfzmodules are included — this is only the fixes to codemasteralready has.Memory safety
Lossless_bypass::compresssrcLenbytes intodstregardless ofdstCap, so any payload larger than the caller's buffer was an unconditional heap overflow.Lossless_zstdalready checks and throws; bypass now does the same. Caught by AddressSanitizer on Linux:ALGO_BIOMDXTCis the one algorithm that pairs its codec withLossless_bypass, so nothing shrinks the payload, and glibc reported it asmunmap_chunk(): invalid pointer. macOS's allocator does not abort on it.MemoryUtil::readremaining_lengthwas guarded byassert, which Release compiles out, so a truncated or corrupt stream read past the end of the buffer. Now throws.RegressionPredictor::loadremaining_lengthbycoeff_size * sizeof(int)(the decoded bin count) whiledecode()advances the cursor by the much smaller encoded byte count, so the budget ran out early. This is what theMemoryUtilguard was hiding: on the HDF5 filter path withALGO_LORENZO_REGateb = 1e-4it read past the buffer on every miranda velocity field.LinearQuantizer::recover_unpredRunlengthEncodersize_est(), soSZGenericCompressorsized its buffer without accounting for the encoder andencode()overran it.KmeansUtilreserve()followed byoperator[]writes (UB), anduniform_int_distribution(0, num)indexingdata[num]. Live inmdzwhendims[1] > 5000.H5Z-SZ3sizeof(T) * num * 2, which is belowSZ_compress's own minimum for a small chunk. An explicit chunk of a few hundred elements aborted with "The buffer for compressed data is not large enough".NoPredictionDecomposition,InterpolationDecompositionsize_est()override, so the compressor sized its buffer from 0 while the quantizer's unpredictable list could be arbitrarily large.Correctness
TimeSeriesDecompositiondata_ts0 == nullptrthe timestep loop predicted from stale values:block_data's compress-side path has no write-back, so timestep 0 indatawas never updated with its reconstruction whiledecompress()predicted from its own. Measured 1.94x the error bound; 0.997x after the fix.ArithmeticEncoderbytesToInt64_bigEndian(bytes) >> 20sign-extends when the top byte's MSB is set, pushing the value outside the 44-bitMAX_CODEwindow. A 60-stream sweep gave 30 correct, 23 wrong, 7 SIGSEGV.HuffmanEncoderstateNum = max - offset + 2narrows toint, so a wide bin range goes negative and the state-tablemallocis UB. Now throws and points atHuffmanEncoderV2, which switches to a sparse map.Portability
std::make_sharedwithout<memory>. libc++ provides it transitively and libstdc++ does not, so this is latent until a translation unit changes its include order — it broke the Linux build onfzonce a new test file changed the include order.<limits>,<cmath>,<cstddef>,<array>,SZ3/def.hpp, or a project header). All 57 now compile standalone under both clang/libc++ and GCC 16/libstdc++. The script that checks this lives on thefzbranch (tools/test/check_headers.py) and is not part of this PR..gitignore's baretestpattern matchedtools/test/, hiding every test file from git. Now/test.Verification
BUILD_TESTING=ON+BUILD_H5Z_FILTER=ON: 0 errors, 0 warnings, all tests passTimeSeriesDecompositionandH5Z-SZ3numbers above are from those runs🤖 Generated with Claude Code