metaUtils: consume the trailer when the output buffer fills first - #147
Conversation
MET_PerformUncompression rejects valid compressed data when the gzip trailer falls in a different input chunk than the last data byte. The decompression loop feeds input in MET_MaxChunkSize (1 GiB) pieces; if the output buffer is exactly full when a chunk boundary lands inside the 8-byte trailer, the next inflate call has no output capacity, returns Z_BUF_ERROR, and the outer loop treats that as terminal even though input remains. The stream-end requirement then rejects a payload that was fully and correctly decompressed. Keep feeding the remaining input into a one-byte scratch buffer until zlib reports Z_STREAM_END or a real error, so the CRC is still verified and corrupt or truncated streams are still rejected. Verified against a 1073741829-byte compressed stream, aligned so five trailer bytes fall in the second input chunk: rejected with zlib error -5 before this change, accepted with matching content after. Corrupt-CRC and truncated inputs remain rejected. Assisted-by: Claude Code -- reproduction, alignment search, and verification
|
@dzenanz would you review and merge this one? I don't have merge rights here. CI note: the red CircleCI check is pre-existing and unrelated to this change. GitHub Actions (Ubuntu 24.04), which builds and runs the suite, passes. Evidence that CircleCI is failing project-wideEvery recent
The step breakdown is identical on The job reports Companion PR #148 adds a regression test for this defect. It depends on this one — please merge this first, then I will rebase #148 on top of it. |
…e trailer case MET_MaxChunkSize was a file-local constexpr of 1 GiB, so the input-chunking path in MET_PerformCompression and MET_PerformUncompression could only be reached by a test allocating several gigabytes. Expose MET_SetMaxChunkSize and MET_GetMaxChunkSize so the boundary can be placed where a test needs it, following the existing META_DEBUG precedent for a settable diagnostic knob. Values of zero or less are ignored. testMeta15UncompressChunkBoundary uses this to put a chunk boundary inside the 8-byte gzip trailer with 64 KiB of data, and asserts both that a valid stream is accepted and that a corrupt CRC trailer is still rejected. The test runs in 0.01 s. The accepted case fails on master today; it passes with the fix in Kitware#147. This change is independent of that one and can merge in either order. Assisted-by: Claude Code -- test design and verification
…e trailer case MET_MaxChunkSize was a file-local constexpr of 1 GiB, so the input-chunking path in MET_PerformCompression and MET_PerformUncompression could only be reached by a test allocating several gigabytes. Expose MET_SetMaxChunkSize and MET_GetMaxChunkSize so the boundary can be placed where a test needs it, following the existing META_DEBUG precedent for a settable diagnostic knob. Values of zero or less are ignored. testMeta15UncompressChunkBoundary uses this to put a chunk boundary inside the 8-byte gzip trailer with 64 KiB of data, and asserts both that a valid stream is accepted and that a corrupt CRC trailer is still rejected. The test runs in 0.01 s. The accepted case fails on master today; it passes with the fix in Kitware#147. This change is independent of that one and can merge in either order. Assisted-by: Claude Code -- test design and verification
…e trailer case MET_MaxChunkSize was a file-local constexpr of 1 GiB, so the input-chunking path in MET_PerformCompression and MET_PerformUncompression could only be reached by a test allocating several gigabytes. Expose MET_SetMaxChunkSize and MET_GetMaxChunkSize so the boundary can be placed where a test needs it, following the existing META_DEBUG precedent for a settable diagnostic knob. Values of zero or less are ignored. testMeta15UncompressChunkBoundary uses this to put a chunk boundary inside the 8-byte gzip trailer with 64 KiB of data, and asserts both that a valid stream is accepted and that a corrupt CRC trailer is still rejected. The test runs in 0.01 s. The accepted case fails on master today; it passes with the fix in #147. This change is independent of that one and can merge in either order. Assisted-by: Claude Code -- test design and verification
Fixes a regression I introduced in #145:
MET_PerformUncompressionrejects valid compressed data when the gzip trailer lands in a different input chunk than the last data byte.Reported by an automated review on the ITK side (InsightSoftwareConsortium/ITK#6835), where #145 arrived via the MetaIO sync.
Root cause
The decompression loop feeds input in
MET_MaxChunkSize(1 GiB) pieces. When the output buffer is exactly full and the chunk boundary falls inside the 8-byte trailer, the nextinflatecall has no output capacity and returnsZ_BUF_ERROR. The outer looptreats that as terminal even though input remains, so the trailer is never consumed. The
Z_STREAM_ENDrequirement added in #145 then rejects a payload that was fully and correctly decompressed.The inner loop already documents this return as benign:
so the post-loop check contradicted a comment three lines above it. The underlying loop behavior predates #145; requiring
Z_STREAM_ENDonly made it observable.The fix, and why not simply drop the check
zlib validates the CRC32 trailer only at stream end, so removing the requirement would restore a hole where a payload with a corrupt trailer is accepted whenever the byte count happens to match — the
dest_pos != uncompressedDataSizetest catches truncation but not corruption.Instead, keep feeding the remaining input into a one-byte scratch output until zlib reports
Z_STREAM_ENDor a genuine error. Valid deferred-trailer streams are accepted; corrupt and truncated ones are still rejected.Verification
The trigger is narrow: crossing the 1 GiB boundary is not sufficient, since
inflatereturnsZ_STREAM_ENDat an exactly-full output buffer as long as the trailer is in the same input chunk. A 1216502568-byte stream was accepted unchanged. The boundary must fall inside the trailer.Binary-searching the raw size produced that alignment —
raw=1018073420,compressed=1073741829, five trailer bytes in the second chunk:zlib error -5(content correct)-3)-5)Behavior was exercised against a build of this function in ITK (which vendors zlib-ng); this branch also compiles clean in a standalone MetaIO build.