Skip to content

Signed-shift UB in zip-repair local-header read#640

Merged
xroche merged 1 commit into
masterfrom
fix-mztools-shift-ub
Jul 18, 2026
Merged

Signed-shift UB in zip-repair local-header read#640
xroche merged 1 commit into
masterfrom
fix-mztools-shift-ub

Conversation

@xroche

@xroche xroche commented Jul 18, 2026

Copy link
Copy Markdown
Owner

HTTrack's zip-repair reader (unzRepair in src/minizip/mztools.c, Xavier's own 2004 file, not vendored from upstream minizip, so nothing to send upstream) built 32-bit header fields with READ_32(adr) = READ_16(adr) | (READ_16((adr)+2) << 16). READ_16 is an int, so a high half with bit 15 set shifts past INT_MAX: signed overflow, and UBSan aborts on the CRC or size fields of a damaged header. Repair runs on hostile input, since it fires on a corrupt or foreign new.zip cache. The fix casts the halves to uLong before the shift, matching the genuinely-upstream siblings unzip.c and zip.c that already read 32-bit fields that way.

A new zip-repair-shift engine self-test drives unzRepair over a header whose CRC high word has bit 15 set, wrapped by tests/01_zlib-repair-shift.test. On master it aborts with mztools.c:54:28: runtime error: left shift of 35560 by 16 places cannot be represented in type 'int'; with the fix it recovers the one entry cleanly. The test stands on its own and does not depend on #634's 71_local-crange-repaircache.test, which trips the same UB only incidentally.

#634's sanitize leg goes green once this lands (merge-order dependency).

Closes #639

unzRepair parses the local file header of a damaged cache zip during
repair. READ_32 combined two int-typed READ_16 halves as
READ_16(adr) | (READ_16((adr)+2) << 16); when the high half has bit 15
set (>= 0x8000), shifting it left by 16 exceeds INT_MAX, which is signed
overflow. UBSan aborts on the CRC/size fields of a header whose high
16-bit word has that bit set. Repair runs on hostile input (a corrupt or
foreign new.zip). Cast the halves to uLong before the shift, matching the
sibling minizip readers in unzip.c and zip.c.

Closes #639

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>
@xroche
xroche merged commit 491774a into master Jul 18, 2026
21 checks passed
xroche added a commit that referenced this pull request Jul 18, 2026
Picks up the minizip READ_32 fix (#640) so the sanitize leg no longer aborts on
the repair path. The Windows offline runner pins its expected skip set exactly,
so a second Windows-skipped test (71_local-crange-repaircache) must be listed
there too, otherwise it fails with "unexpected skips".

Resolve tests/Makefile.am: keep 66/67/69 (merged) alongside this branch's 71.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Signed-shift undefined behavior in zip-repair local-header read

1 participant