[8.8.0] Fix Zip64 archive central directory extraction in ZipReader (https://github.com/bazelbuild/bazel/pull/30685) (#30764)
### Description
Fixes #30681.
In commit b12c06a2d54684cdf9a83d9ecc634850a7858db6 (#30530),
`ZipReader.readCentralDirectory()` was modified to compute the central
directory start as `actualCenStart = actualCenEnd -
zipData.getCentralDirectorySize()`, replacing the parsed
`zipData.getCentralDirectoryOffset()`.
This broke reading standard Zip64 archives where 32-bit EOCD fields do
not overflow (as the 76-byte Zip64 EOCD and locator sit between the
central directory and the 32-bit EOCD, shifting the computed offset 76
bytes into the central directory) or when `centralDirectorySize` is
`0xFFFFFFFF`.
This change:
1. Restores `ZipReader`'s standard central directory parsing to use
`zipData.getCentralDirectoryOffset()`.
2. Populates `centralDirectorySize` in
`Zip64EndOfCentralDirectory.read()`.
3. Restricts the unadjusted SFX central directory start computation
strictly to `AdjustSfx`.
4. Adds a unit test in `ZipReaderTest` covering Zip64 archives with
valid 32-bit EOCD offsets.
Closes #30685.
PiperOrigin-RevId: 966082613
Change-Id: I4c9583901610aeed4ad4bfce75915b32eb6b2c86
Commit
https://github.com/bazelbuild/bazel/commit/3dd0a0577005b741b83020ba669e8f9276969af3
Co-authored-by: Yun Peng <pcloudy@google.com>
Co-authored-by: Ian (Hee) Cha <heec@google.com>
diff --git a/src/java_tools/singlejar/java/com/google/devtools/build/zip/AdjustSfx.java b/src/java_tools/singlejar/java/com/google/devtools/build/zip/AdjustSfx.java
index d9b4b8b..4e3d158 100644
--- a/src/java_tools/singlejar/java/com/google/devtools/build/zip/AdjustSfx.java
+++ b/src/java_tools/singlejar/java/com/google/devtools/build/zip/AdjustSfx.java
@@ -41,7 +41,7 @@
File inputFile = new File(args[0]);
File outputFile = new File(args[1]);
- try (ZipReader reader = new ZipReader(inputFile, UTF_8, false);
+ try (ZipReader reader = ZipReader.createForAdjustSfx(inputFile, UTF_8);
InputStream in = new FileInputStream(inputFile);
OutputStream out = new BufferedOutputStream(new FileOutputStream(outputFile))) {
diff --git a/src/java_tools/singlejar/java/com/google/devtools/build/zip/Zip64EndOfCentralDirectory.java b/src/java_tools/singlejar/java/com/google/devtools/build/zip/Zip64EndOfCentralDirectory.java
index 58424b8..9432f27 100644
--- a/src/java_tools/singlejar/java/com/google/devtools/build/zip/Zip64EndOfCentralDirectory.java
+++ b/src/java_tools/singlejar/java/com/google/devtools/build/zip/Zip64EndOfCentralDirectory.java
@@ -51,6 +51,7 @@
"Malformed Zip64 End of Central Directory; does not start with %08x", SIGNATURE));
}
file.setZip64(true);
+ file.setCentralDirectorySize(ZipUtil.getUnsignedLong(fixedSizeData, CD_SIZE_OFFSET));
file.setCentralDirectoryOffset(ZipUtil.getUnsignedLong(fixedSizeData, CD_OFFSET_OFFSET));
file.setExpectedEntries(ZipUtil.getUnsignedLong(fixedSizeData, TOTAL_ENTRIES_OFFSET));
return file;
diff --git a/src/java_tools/singlejar/java/com/google/devtools/build/zip/ZipReader.java b/src/java_tools/singlejar/java/com/google/devtools/build/zip/ZipReader.java
index 0945431..2769c02 100644
--- a/src/java_tools/singlejar/java/com/google/devtools/build/zip/ZipReader.java
+++ b/src/java_tools/singlejar/java/com/google/devtools/build/zip/ZipReader.java
@@ -85,13 +85,22 @@
* @throws IOException if an I/O error has occurred
*/
public ZipReader(File file, Charset charset, boolean strictEntries) throws IOException {
+ this(file, charset, strictEntries, /* isUnadjustedSfx= */ false);
+ }
+
+ ZipReader(File file, Charset charset, boolean strictEntries, boolean isUnadjustedSfx)
+ throws IOException {
if (file == null || charset == null) {
throw new NullPointerException();
}
this.file = file;
this.in = new RandomAccessFile(file, "r");
this.zipData = new ZipFileData(charset);
- readCentralDirectory(strictEntries);
+ readCentralDirectory(strictEntries, isUnadjustedSfx);
+ }
+
+ static ZipReader createForAdjustSfx(File file, Charset charset) throws IOException {
+ return new ZipReader(file, charset, /* strictEntries= */ false, /* isUnadjustedSfx= */ true);
}
/**
@@ -189,12 +198,13 @@
/**
* Finds, reads and parses ZIP file entries from the central directory.
*
- * @param strictEntries force parsing to use the number of entries recorded in the end of
- * central directory as the correct value, not as an estimate
+ * @param strictEntries force parsing to use the number of entries recorded in the end of central
+ * directory as the correct value, not as an estimate
* @throws ZipException if a ZIP format error has occurred
* @throws IOException if an I/O error has occurred
*/
- private void readCentralDirectory(boolean strictEntries) throws IOException {
+ private void readCentralDirectory(boolean strictEntries, boolean isUnadjustedSfx)
+ throws IOException {
long eocdLocation = findEndOfCentralDirectoryRecord();
InputStream stream = getStreamAt(eocdLocation);
EndOfCentralDirectoryRecord.read(stream, zipData);
@@ -211,16 +221,21 @@
}
}
- long actualCenEnd =
- (zipData.isZip64() && zipData.getZip64EndOfCentralDirectoryOffset() > 0)
- ? zipData.getZip64EndOfCentralDirectoryOffset()
- : eocdLocation;
- long actualCenStart = actualCenEnd - zipData.getCentralDirectorySize();
+ if (isUnadjustedSfx) {
+ long actualCenEnd =
+ (zipData.isZip64() && zipData.getZip64EndOfCentralDirectoryOffset() > 0)
+ ? zipData.getZip64EndOfCentralDirectoryOffset()
+ : eocdLocation;
+ long actualCenStart = actualCenEnd - zipData.getCentralDirectorySize();
+ readCentralDirectoryFileHeaders(zipData.getExpectedEntries(), actualCenStart);
+ return;
+ }
if (zipData.isZip64() || strictEntries) {
// If in Zip64 format or using strict entry numbers, use the parsed information as is to read
// the central directory file headers.
- readCentralDirectoryFileHeaders(zipData.getExpectedEntries(), actualCenStart);
+ readCentralDirectoryFileHeaders(
+ zipData.getExpectedEntries(), zipData.getCentralDirectoryOffset());
} else {
// If not in Zip64 format, compute central directory offset by end of central directory record
// offset and central directory size to allow reading large non-compliant Zip32 directories.
@@ -230,7 +245,8 @@
if ((int) centralDirectoryOffset == (int) zipData.getCentralDirectoryOffset()) {
readCentralDirectoryFileHeaders(centralDirectoryOffset);
} else {
- readCentralDirectoryFileHeaders(zipData.getExpectedEntries(), actualCenStart);
+ readCentralDirectoryFileHeaders(
+ zipData.getExpectedEntries(), zipData.getCentralDirectoryOffset());
}
}
}
diff --git a/src/java_tools/singlejar/javatests/com/google/devtools/build/zip/ZipReaderTest.java b/src/java_tools/singlejar/javatests/com/google/devtools/build/zip/ZipReaderTest.java
index ae3ae00..4705df9 100644
--- a/src/java_tools/singlejar/javatests/com/google/devtools/build/zip/ZipReaderTest.java
+++ b/src/java_tools/singlejar/javatests/com/google/devtools/build/zip/ZipReaderTest.java
@@ -477,6 +477,45 @@
}
}
+ @Test
+ public void testZip64_WithZip32Offsets() throws IOException {
+ // A zip file with Zip64 EOCD and Locator, but with non-overflowing 32-bit values in EOCD (as
+ // generated by Python / standard tools for archives with Zip64 entries).
+ byte[] data =
+ new byte[] {
+ 0x50, 0x4b, 0x03, 0x04, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x74,
+ 0x65,
+ 0x73, 0x74, 0x50, 0x4b, 0x01, 0x02, 0x14, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04,
+ 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00,
+ 0x74, 0x65, 0x73, 0x74, 0x50, 0x4b, 0x06, 0x06, 0x2c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00,
+ 0x2d, 0x00, 0x2d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+ 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00,
+ 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x4b, 0x06,
+ 0x07,
+ 0x00, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+ 0x00,
+ 0x50, 0x4b, 0x05, 0x06, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x32, 0x00, 0x00,
+ 0x00,
+ 0x22, 0x00, 0x00, 0x00, 0x00, 0x00
+ };
+ try (FileOutputStream out = new FileOutputStream(test)) {
+ out.write(data);
+ }
+ try (ZipReader reader = new ZipReader(test, UTF_8)) {
+ assertThat(reader.entries()).hasSize(1);
+ assertThat(reader.getEntry("test")).isNotNull();
+ }
+ }
+
@Test public void testZip64_Potential() throws IOException {
try (ZipWriter writer = new ZipWriter(new FileOutputStream(test), UTF_8, true)) {
ZipFileEntry template = new ZipFileEntry("template");
@@ -612,7 +651,7 @@
// Initializing ZipReader parses the EOCD header. This verifies that signatureLocation + 20
// falling across the 64-byte buffer boundary is handled gracefully without throwing an
// ArrayIndexOutOfBoundsException.
- try (ZipReader reader = new ZipReader(sfxFile, UTF_8)) {
+ try (ZipReader reader = ZipReader.createForAdjustSfx(sfxFile, UTF_8)) {
assertThat(reader.entries()).hasSize(1);
ZipFileEntry entry = reader.getEntry("foo.txt");
assertThat(entry).isNotNull();