Skip to content

Commit bf9c756

Browse files
committed
Improve JVM reader skipping over bogus END headers
1 parent f77a704 commit bf9c756

File tree

4 files changed

+38
-5
lines changed

4 files changed

+38
-5
lines changed

pom.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
<groupId>software.coley</groupId>
88
<artifactId>lljzip</artifactId>
9-
<version>2.7.1</version>
9+
<version>2.7.2</version>
1010

1111
<name>LL Java ZIP</name>
1212
<description>Lower level ZIP support for Java</description>

src/main/java/software/coley/lljzip/format/read/JvmZipReader.java

+25-4
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ public void read(@Nonnull ZipArchive zip, @Nonnull MemorySegment data) throws IO
9090
long len = data.byteSize();
9191
long centralDirectoryOffsetScanEnd = Math.max(Math.max(precedingEndOfCentralDirectory, 0), end.getCentralDirectoryOffset());
9292
long earliestCentralDirectoryOffset = len - ZipPatterns.CENTRAL_DIRECTORY_FILE_HEADER.length;
93-
long maxRelativeOffset = 0;
93+
long maxRelativeLFHOffset = 0;
9494
while (true) {
9595
// Look backwards for the next CEN magic quad.
9696
long offset = MemorySegmentUtil.lastIndexOfQuad(data, earliestCentralDirectoryOffset - 1L, ZipPatterns.CENTRAL_DIRECTORY_FILE_HEADER_QUAD);
@@ -107,6 +107,7 @@ public void read(@Nonnull ZipArchive zip, @Nonnull MemorySegment data) throws IO
107107
// Read central directories going forward from the first CEN header.
108108
// We do this "find the first, then read forward" so that we can skip over data that may
109109
// coincidentally hold the CEN magic, but not actually denote the beginning of a central directory entry.
110+
CentralDirectoryFileHeader latestOffsetCen = null;
110111
long centralDirectoryOffset = earliestCentralDirectoryOffset;
111112
while (centralDirectoryOffset >= 0L) {
112113
CentralDirectoryFileHeader directory = newCentralDirectoryFileHeader();
@@ -116,9 +117,17 @@ public void read(@Nonnull ZipArchive zip, @Nonnull MemorySegment data) throws IO
116117
// We cannot recover from the CEN reading encountering failures.
117118
throw new IOException(ex);
118119
}
120+
121+
// Add to zip
119122
zip.addPart(directory);
120-
if (directory.getRelativeOffsetOfLocalHeader() > maxRelativeOffset)
121-
maxRelativeOffset = directory.getRelativeOffsetOfLocalHeader();
123+
124+
// Track which CEN has the latest offset in the ZIP file
125+
if (directory.getRelativeOffsetOfLocalHeader() > maxRelativeLFHOffset) {
126+
maxRelativeLFHOffset = directory.getRelativeOffsetOfLocalHeader();
127+
latestOffsetCen = directory;
128+
}
129+
130+
// Step forward
122131
centralDirectoryOffset = MemorySegmentUtil.indexOfQuad(data, centralDirectoryOffset + CentralDirectoryFileHeader.MIN_FIXED_SIZE, ZipPatterns.CENTRAL_DIRECTORY_FILE_HEADER_QUAD);
123132
}
124133

@@ -130,14 +139,26 @@ public void read(@Nonnull ZipArchive zip, @Nonnull MemorySegment data) throws IO
130139
if (precedingEndOfCentralDirectory != -1) {
131140
// There was a prior end part, so we will seek past it's length and use that as the base offset.
132141
try {
142+
// We roughly compute the "size" of section of data occupied by LocalFileHeader contents.
143+
// The latest relative file offset, plus its header+data size then this should be the section size
144+
// even if we don't know if there is a preceding end header or not to base off of.
145+
if (latestOffsetCen != null) {
146+
long localFileHeaderSectionLength = LocalFileHeader.MIN_FIXED_SIZE + maxRelativeLFHOffset + latestOffsetCen.getCompressedSize();
147+
long startOfRelativeLocalFileHeaders = earliestCentralDirectoryOffset - localFileHeaderSectionLength;
148+
if (precedingEndOfCentralDirectory > startOfRelativeLocalFileHeaders) {
149+
// This occurs between the start of LocalFileHeader data and our current end and should be ignored.
150+
throw new IllegalStateException();
151+
}
152+
}
153+
133154
// Make sure it isn't bogus before we use it as a reference point
134155
EndOfCentralDirectory tempEnd = new EndOfCentralDirectory();
135156
tempEnd.read(data, precedingEndOfCentralDirectory);
136157

137158
// If we use this as a point of reference there must be enough data remaining
138159
// to read the largest offset specified by our central directories.
139160
long hypotheticalJvmBaseOffset = precedingEndOfCentralDirectory + tempEnd.length();
140-
if (len <= hypotheticalJvmBaseOffset + maxRelativeOffset)
161+
if (len <= hypotheticalJvmBaseOffset + maxRelativeLFHOffset)
141162
throw new IllegalStateException();
142163

143164
// TODO: Double check 'precedingEndOfCentralDirectory' points to a EndOfCentralDirectory that isn't bogus

src/test/java/software/coley/lljzip/PartParseTests.java

+12
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,18 @@ public void postProcessLocalFileHeader(@Nonnull LocalFileHeader file) {
187187
}
188188
}
189189

190+
@Test
191+
public void testEndInLocalDataDesc() {
192+
// The non-class files have been zeroed out in this sample, but we don't care.
193+
// The main point is that the "bin/3" and "bin/4" red-herring headers are properly dealt with,
194+
// and we can see that there are 639 class files in this jar file.
195+
try (ZipArchive archive = ZipIO.readJvm(Paths.get("src/test/resources/end-in-local-data-desc.jar"))) {
196+
assertEquals(639, archive.getNameFilteredLocalFiles(f -> f.endsWith(".class")).size());
197+
} catch (IOException ex) {
198+
fail(ex);
199+
}
200+
}
201+
190202
@Test
191203
public void testMergedFakeEmpty() {
192204
try (ZipArchive zipJvm = ZipIO.readJvm(Paths.get("src/test/resources/hello-merged-fake-empty.jar"))) {
3.65 MB
Binary file not shown.

0 commit comments

Comments
 (0)