fix(sdk): zip64/APPNOTE conformance in the TDF3 zip reader and writer (DSPX-4591) - #1017
Draft
dmihalcik-virtru wants to merge 1 commit into
Draft
fix(sdk): zip64/APPNOTE conformance in the TDF3 zip reader and writer (DSPX-4591)#1017dmihalcik-virtru wants to merge 1 commit into
dmihalcik-virtru wants to merge 1 commit into
Conversation
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: trueThanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
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.



https://virtru.atlassian.net/browse/DSPX-4591
Audit of java-sdk #393 against the Go
and Web zip implementations turned up four ZIP64/APPNOTE conformance divergences in
web-sdk. All four are addressed here.
Note on the writer's ZIP64 threshold: there isn't one, and that's deliberate.
ZipWritersetsthis.zip64 = truein its constructor and never clears it, soweb-sdk unconditionally emits ZIP64 structures. That is already the safest posture
and is unchanged by this PR. The siblings (DSPX-4589 java-sdk, DSPX-4590 go-sdk) are
the ones moving thresholds.
Finding 1 — the reader never parsed the end of central directory record
getCDBuffers()scanned the last 1000 bytes of the file backwards for anythingmatching the central directory signature
0x02014b50and treated every hit as anentry, inferring record boundaries from the position of the next hit.
Replaced with real end-of-central-directory parsing:
ZipReader.getEndOfCentralDirectory()locates the EOCD record by scanning the tailfor
0x06054b50and accepting a candidate only when its declared comment lengthputs the end of the comment exactly at the end of the archive. That rules out
payload bytes that happen to spell the signature, and it handles trailing archive
comments. The tail read starts at 1 KiB (enough for the comment-less containers we
write) and widens once to
22 + 0xffff + 20bytes if the record isn't in there.0xffffentry count,0xffffffffCD size,0xffffffffCD offset) the reader follows the ZIP64 end ofcentral directory locator that sits immediately before the EOCD and reads the ZIP64
EOCD record it points at, taking the entry count, CD size and CD offset from there.
getCDBuffers()now takes the central directory chunk plus the declared entrycount, and walks exactly that many records from the declared offset, advancing by
46 + fileNameLength + extraFieldLength + fileCommentLengthand validating thesignature and the remaining length at each step.
must be at least
46 * entryCount, and it is bounded at 16 MiB so a hostile orcorrupt EOCD can't ask us to buffer the sentinel 4 GiB.
getCDBufferschanged signature. It's a public method onZipReaderbutZipReaderis not re-exported from any package entry point (
lib/tdf3/src/utils/index.tsonly),so this isn't a published API break.
Finding 2 — ZIP64 extra field ignored unless
versionNeededToExtract >= 45Dropped the version gate in
parseCDBuffer. APPNOTE 4.5.3 does not condition thevalidity of a ZIP64 extended information extra field on the version-needed-to-extract
byte; the sentinel values in the fixed-size fields are what select it. The gate meant
a conformant producer writing a ZIP64 extra with a lower declared version had it
silently dropped, leaving
0xffffffffto flow intobyteStart/byteEndarithmeticas if it were a real number.
sliceExtraFieldsalready did the right thing (iterates the whole extra-field area,rejects conflicting duplicate header IDs, bounds-checks
dataSize, reads the ZIP64fields in APPNOTE order) and is unchanged. Also added a length guard to
parseCDBufferso a record shorter than the 46-byte fixed prefix is rejected rather than parsed out
of a short buffer.
Finding 3 — non-ZIP64 data descriptor wrote
uncompressedSizetwiceKept the branch and fixed it, rather than deleting it. Justification:
ZipWriter.zip64is a public mutable field and the existing unit tests exercise
zip64 = falsefor allfour writer methods (
getLocalFileHeader,writeDataDescriptor,writeCentralDirectoryRecord,writeEndOfCentralDirectoryRecord). Deleting one ofthose four branches would leave the writer unable to emit a coherent non-ZIP64 archive
while the other three remain, which is a worse state than the bug.
writeDataDescriptornow takes an explicit optionalcompressedSize(defaulting touncompressedSize, since we only ever STORE) and writes it into the compressed sizeslot in both the ZIP64 and non-ZIP64 branches. Output for every existing call site is
byte-identical; the existing characteristic tests are unchanged. Two new tests pass
distinct compressed and uncompressed sizes and assert each lands in its own slot.
Finding 4 — 32-bit shift on a potentially large size in an error path
(cdObj.uncompressedSize >> 10)becameMath.floor(cdObj.uncompressedSize / 1024).>>coerces to signed 32 bits, so a 2 GiB manifest used to be reported as-2,097,152 KiB. Error-message only.Tests
lib/tests/mocha/unit/zip.spec.tsgains abuildZiphelper that assembles completein-memory archives with
ZipWriter, so the reader is exercised end to end rather thanagainst hand-rolled record fragments. New coverage:
0x02014b50— the false-positive casecentral directory record and inside a full archive)
versionNeededToExtractforced to 20The
0x02014b50test was verified against the pre-change reader: with the old backwardscanner restored,
getCentralDirectory()returns four entries(
['', '', '0.manifest.json', '0.payload']) instead of two, the two empty-named onesbeing payload bytes misread as central directory records. It passes with the new
reader.
Deliberately not changed: segment-size emission
lib/tdf3/src/tdf.tsomitssegmentSize/encryptedSegmentSizefrom a segmentobject when they equal the manifest-level defaults, and that stays as it is. The
emission is legal:
manifest.schema.jsongivessegments/itemsnorequiredlist,and web-sdk's own reader defaults them back. go-sdk and java-sdk fail to apply that
fallback, which is why web-sdk TDFs over 1 MiB are currently unreadable by them —
those are the bugs, tracked as DSPX-4590 finding 7 and DSPX-4589 finding 4. Making
web-sdk write the redundant fields would paper over two real reader bugs and silently
un-cover them.
Cross-SDK interop
The shared interop harness is DSPX-4592 and is already built in
opentdf/testsonbranch
DSPX-4592-java-underflow. It needs nothing from this PR.