Stop rejecting sparse CHDv5 images - #191
Merged
Merged
Conversation
5cc52fd bounded totalhunks by the file's own size in bits, on the reasoning that "every hunk map entry consumes at least one bit in the compressed on-disk map". That is not true of CHDv5: its map is run-length and Huffman coded, so a run of identical, uncompressed or self-referenced hunks costs far less than one bit each. 64 MiB of zeros becomes a 203-byte file carrying 16384 hunks - eighty hunks per byte where the check allowed eight - and chd_open() refused it while chdman verified it. Reported in #190 against a 50 GB image of about 23 KB, and the better an image compresses the more certainly it is rejected. The guard is still needed: totalhunks sizes the map allocation, and a malformed header could ask for gigabytes against a tiny file. Replaced with a bound on each version's actual constraint. v1-v4 have an exact one. Their map is a plain array of fixed-size entries stored immediately after the header, so header->length + totalhunks * entrysize has to fit in the file. Tighter than the old check, and true. v5 has no such invariant, so bound what is really allocated instead: map_size_v5() now caps the materialized map at CHD_MAX_MAP_SIZE. 256 MiB covers over 400 GB at CD geometry and about 89 GB at 4 KB hunks, which is past anything this library claims to handle, while keeping a corrupt hunkcount from requesting more than a host would give. LOWRAM_TARGET never materializes the map at all and is unaffected. tests/sparse_map.c opens the seed and decodes every hunk; it fails before this change with "invalid data". The seed comes from generate.sh, built with chdman from a zero-filled file, so it needs no third-party data. Checked in six build configurations - default, LOWRAM_TARGET, CD_SCRATCH_BUFFER off, system zlib, the no-subcode/no-CRC combination, and LOWRAM with system zlib. All accept the sparse image and all still reject three hand-corrupted headers (v5 logicalbytes at 1 TiB and at INT64_MAX, v4 totalhunks at 2^30) under a hard 300 MB address-space limit, so nothing slipped through by allocating first. Decoded output is unchanged across the corpus - 36 files, no difference in data or error codes - and ASan/UBSan is clean on the targeted cases and the mutant set. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01846EhHuAFk5qvxwEA5Gq6y
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.
Fixes #190.
5cc52fdboundedtotalhunksby the file's own size in bits, on the reasoning that "every hunk map entry consumes at least one bit in the compressed on-disk map". That does not hold for CHDv5: its map is run-length and Huffman coded, so a run of identical, uncompressed or self-referenced hunks costs far less than one bit each.Reproduced exactly as reported, and worse than the numbers in the issue:
Eighty hunks per byte, where the check allowed eight. As @seanbrar notes, the better an image compresses the more certainly it is refused.
The fix
The guard is still needed —
totalhunkssizes the map allocation, and a malformed header could otherwise ask for gigabytes against a tiny file. It is replaced by a bound on each version's actual constraint.v1-v4 have an exact one. Their map is a plain array of fixed-size entries stored immediately after the header, so
header->length + totalhunks * entrysizehas to fit in the file. That is tighter than the old check, and true.v5 has no such invariant, so this bounds what is really allocated:
map_size_v5()caps the materialized map atCHD_MAX_MAP_SIZE. 256 MiB covers over 400 GB at CD geometry and about 89 GB at 4 KB hunks — past anything this library claims to handle — while keeping a corrupthunkcountfrom requesting more than a host would give.LOWRAM_TARGETnever materializes the map and is unaffected either way.This is the direction the issue suggested: bound the allocation rather than infer a ratio.
Testing
tests/sparse_map.copens the seed and decodes every hunk. It fails before this change withopen failed: invalid data. The seed is built bygenerate.shwith chdman from a zero-filled file, so it needs no third-party data.Six build configurations — default,
CHDR_LOWRAM_TARGET=ON,CHDR_CD_SCRATCH_BUFFER=OFF,WITH_SYSTEM_ZLIB=ON, the no-subcode/no-CRC combination, and LOWRAM with system zlib. All accept the sparse image, and all still reject three hand-corrupted headers (v5logicalbytesat 1 TiB and atINT64_MAX, v4totalhunksat 2^30) under a hard 300 MB address-space limit, so nothing gets through by allocating first and failing later.Decoded output is unchanged across the corpus — 36 files, no difference in data or in error codes. ASan/UBSan clean on the targeted cases and on the mutant set.
🤖 Generated with Claude Code