Skip to content

fix(zip): guard zip64 extra-field parsing against malformed input - #3541

Merged
ibgreen merged 1 commit into
visgl:masterfrom
SimYunSup:fix/zip-zip64-extra-guard
Aug 4, 2026
Merged

fix(zip): guard zip64 extra-field parsing against malformed input#3541
ibgreen merged 1 commit into
visgl:masterfrom
SimYunSup:fix/zip-zip64-extra-guard

Conversation

@SimYunSup

Copy link
Copy Markdown
Contributor

Problem

A single malformed or malicious ZIP makes the central-directory parser in @loaders.gl/zip throw an
uncaught RangeError: Offset is outside the bounds of the DataView. It surfaces on public entry
points that walk the central directory (ZipFileSystem.readdir() / fetch(),
makeZipCDHeaderIterator(), SLPK/i3s loading), so untrusted input can abort the caller.

Root cause

findZip64DataInExtra() in modules/zip/src/parse-zip/cd-file-header.ts located the zip64 extra
record with a byte-wise scan and then read from the result without validating it:

const offsetInExtraData = new Uint8Array(extraField.buffer).findIndex(/* ... */);
for (const note of zip64dataList) {
  zip64DataRes[note.name] = extraField.getBigUint64(offsetInExtraData + 4 + offset, true);
}

When a CD size/offset field holds the zip64 sentinel 0xffffffff but no matching zip64 record is
present, findIndex() returns -1 and the code calls getBigUint64(3, true) — out of bounds for an
empty or short extra field, hence the RangeError. A truncated zip64 record hits the same path: the
record header matches, but the declared 16-byte payload is not there to read.

Two further defects came from the same scan:

  • The local getUint16(...bytes) helper decoded little-endian as bytes[0] + bytes[1] * 16 instead
    of * 256, so a dataSize field of 256 (00 01) compared equal to 16.
  • Scanning raw bytes cannot tell a record header from record contents, so 01 00 appearing inside a
    preceding extra record's payload was accepted as the zip64 header, and a short zip64 record could
    read into the bytes of the following record.

Fix

findZip64DataInExtra() now walks the extra field as the spec describes it — a sequence of
[headerId: uint16][dataSize: uint16][payload] records:

  • Record headers are only read while at least 4 bytes remain.
  • A record whose declared payload runs past the end of the extra field ends the walk.
  • The zip64 record is matched on headerId === 0x0001 && dataSize === zip64chunkSize, and each
    8-byte field read is bounds-checked against that record's payload end.
  • If no matching record is found, the zip64 augmentation is skipped and the 32-bit CD values are
    returned unchanged — no exception.

The broken getUint16 helper is removed; DataView.getUint16(offset, true) is used directly.

Tests

modules/zip/test/zip-utils/cd-file-header.spec.ts gains four cases:

  • zip64 sentinel with an empty extra field — parses without throwing, keeps the 32-bit values.
  • zip64 sentinel with a truncated zip64 record (declares 16 payload bytes, provides 4) — same.
  • valid zip64 record produced by this repo's own createZip64Info() — 64-bit sizes still decoded.
  • valid zip64 record preceded by an unrelated record whose payload contains 01 00 10 00 — values
    come from the real record, not from the byte pattern inside the previous payload.

The first two cases throw RangeError on master; the last one silently decoded garbage there.

Risk / compatibility

Behavior changes only for malformed input. Well-formed zip64 archives are parsed exactly as before —
the record they carry has headerId 0x0001 and a dataSize equal to the number of sentinel fields
(8/16/24), which the new walk matches. The removed * 16 decode agreed with a correct little-endian
decode for those sizes, so no valid archive relied on it.

Verification needed

Dependency install and the test run could not be executed in the environment this patch was prepared
in, so the four new tests have been validated by byte-level tracing only. Please let CI run
modules/zip (yarn test-node) on this branch.

@ibgreen
ibgreen merged commit b80b392 into visgl:master Aug 4, 2026
7 checks passed
@SimYunSup
SimYunSup deleted the fix/zip-zip64-extra-guard branch August 5, 2026 05:55
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.

2 participants