Skip to content

Commit d95ffa1

Browse files
committed
Make jvm strategy not go beyond the END header's given directory offset
From testing, modifying the END's directory offset value breaks 'java -jar' execution, so this is correct behavior.
1 parent 922267b commit d95ffa1

File tree

8 files changed

+134
-56
lines changed

8 files changed

+134
-56
lines changed

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,11 @@ public void read(@Nonnull ZipArchive zip, @Nonnull ByteData data) throws IOExcep
5959
zip.addPart(end);
6060

6161
// Read central directories (going from the back to the front) up until the preceding ZIP file (if any)
62+
// but not surpassing the declared cen directory offset in the end of central directory header.
6263
long len = data.length();
6364
long centralDirectoryOffset = len - ZipPatterns.CENTRAL_DIRECTORY_FILE_HEADER.length;
6465
long maxRelativeOffset = 0;
65-
long centralDirectoryOffsetScanEnd = Math.max(precedingEndOfCentralDirectory, 0);
66+
long centralDirectoryOffsetScanEnd = Math.max(Math.max(precedingEndOfCentralDirectory, 0), end.getCentralDirectoryOffset());
6667
while (centralDirectoryOffset > centralDirectoryOffsetScanEnd) {
6768
centralDirectoryOffset = ByteDataUtil.lastIndexOfQuad(data, centralDirectoryOffset - 1L, ZipPatterns.CENTRAL_DIRECTORY_FILE_HEADER_QUAD);
6869
if (centralDirectoryOffset >= 0L) {
Lines changed: 1 addition & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,20 @@
11
package software.coley.lljzip;
22

33
import org.junit.jupiter.api.Test;
4-
import org.objectweb.asm.ClassReader;
5-
import org.objectweb.asm.ClassWriter;
6-
import software.coley.lljzip.format.compression.ZipCompressions;
74
import software.coley.lljzip.format.model.LocalFileHeader;
85
import software.coley.lljzip.format.model.ZipArchive;
96
import software.coley.lljzip.format.read.ForwardScanZipReader;
107
import software.coley.lljzip.format.read.JvmZipReader;
118
import software.coley.lljzip.format.transform.CentralAdoptingMapper;
129
import software.coley.lljzip.format.transform.IdentityZipPartMapper;
13-
import software.coley.lljzip.util.ByteData;
14-
import software.coley.lljzip.util.ByteDataUtil;
1510

1611
import java.io.IOException;
1712
import java.nio.file.Path;
1813
import java.nio.file.Paths;
19-
import java.util.function.Supplier;
2014

2115
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
2216
import static org.junit.jupiter.api.Assertions.fail;
17+
import static software.coley.lljzip.JarInJarUtils.handleJar;
2318

2419
/**
2520
* Parse tests ensuring {@link ForwardScanZipReader} and {@link JvmZipReader} correctly
@@ -65,53 +60,4 @@ public void validity() {
6560
}
6661
}), "Failed to read with read(standard) + mapping(central-adoption)");
6762
}
68-
69-
/**
70-
* @param archiveSupplier
71-
* Supplies a zip (jar) archive.
72-
*
73-
* @throws IOException
74-
* When the zip cannot be read.
75-
*/
76-
private static void handleJar(Supplier<ZipArchive> archiveSupplier) throws IOException {
77-
try (ZipArchive zipJvm = archiveSupplier.get()) {
78-
for (LocalFileHeader lfh : zipJvm.getLocalFiles()) {
79-
String entryName = lfh.getFileNameAsString();
80-
if (entryName.endsWith(".class")) {
81-
// We can verify the correctness of our zip model offsets and compression
82-
// by parsing and writing back the class files contained in the jar.
83-
// If anything is wrong, this process should fail.
84-
handleClass(entryName, lfh);
85-
} else if (entryName.endsWith(".jar")) {
86-
// We should be able to extract contents in the jar in-memory and make the same assumptions
87-
// as we do on the root (that classes can be parsed)
88-
handleJar(() -> {
89-
try {
90-
ByteData decompressed = ZipCompressions.decompress(lfh);
91-
return ZipIO.readStandard(decompressed);
92-
} catch (IOException ex) {
93-
throw new IllegalStateException("Failed to read inner jar: " + entryName, ex);
94-
}
95-
});
96-
}
97-
}
98-
}
99-
}
100-
101-
/**
102-
* @param localFileHeader
103-
* Local file header of class.
104-
*
105-
* @throws IOException
106-
* When the class couldn't be parsed.
107-
*/
108-
private static void handleClass(String name, LocalFileHeader localFileHeader) throws IOException {
109-
byte[] entryData = ByteDataUtil.toByteArray(ZipCompressions.decompress(localFileHeader));
110-
try {
111-
ClassReader reader = new ClassReader(entryData);
112-
reader.accept(new ClassWriter(0), 0);
113-
} catch (Throwable ex) {
114-
throw new IOException("Failed to parse class: " + name, ex);
115-
}
116-
}
11763
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package software.coley.lljzip;
2+
3+
import org.junit.jupiter.params.ParameterizedTest;
4+
import org.junit.jupiter.params.provider.ValueSource;
5+
import software.coley.lljzip.format.model.ZipArchive;
6+
import software.coley.lljzip.format.transform.CentralAdoptingMapper;
7+
import software.coley.lljzip.format.transform.IdentityZipPartMapper;
8+
9+
import java.io.IOException;
10+
import java.nio.file.Path;
11+
import java.nio.file.Paths;
12+
13+
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
14+
import static org.junit.jupiter.api.Assertions.fail;
15+
import static software.coley.lljzip.JarInJarUtils.handleJar;
16+
17+
/**
18+
* Similar to {@link DataDescriptorTests} but just checking for general handling of jar-in-jar embeddings.
19+
*
20+
* @author Matt Coley
21+
*/
22+
@SuppressWarnings("resource")
23+
public class JarInJarTests {
24+
@ParameterizedTest
25+
@ValueSource(strings = {
26+
"hello-copyjar-at-head.jar",
27+
"hello-copyjar-at-tail.jar",
28+
"hello-jar-in-in-jar-in-jar-in-jar-in-jar.jar",
29+
"jar-in-jar-with-data-descriptor.jar",
30+
})
31+
public void test(String name) {
32+
Path path = Paths.get("src/test/resources/" + name);
33+
34+
// The JVM strategy calculates file data ranges by mapping from the start
35+
// of the current expected data offset of a local file header, to the start of
36+
// the next local file header.
37+
assertDoesNotThrow(() -> handleJar(() -> {
38+
try {
39+
return ZipIO.readJvm(path);
40+
} catch (IOException ex) {
41+
fail(ex);
42+
return null;
43+
}
44+
}), "Failed to read with read(jvm)");
45+
46+
// The standard strategy calculates file data ranges by using the compressed size.
47+
// The value is assumed to be correct. We'll want to use the authoritative values from
48+
// the central directory file header though. The local sizes can be bogus.
49+
assertDoesNotThrow(() -> handleJar(() -> {
50+
try {
51+
ZipArchive archive = ZipIO.readStandard(path);
52+
return archive.withMapping(new CentralAdoptingMapper(new IdentityZipPartMapper()));
53+
} catch (IOException ex) {
54+
fail(ex);
55+
return null;
56+
}
57+
}), "Failed to read with read(standard) + mapping(central-adoption)");
58+
}
59+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package software.coley.lljzip;
2+
3+
import org.objectweb.asm.ClassReader;
4+
import org.objectweb.asm.ClassWriter;
5+
import software.coley.lljzip.format.compression.ZipCompressions;
6+
import software.coley.lljzip.format.model.LocalFileHeader;
7+
import software.coley.lljzip.format.model.ZipArchive;
8+
import software.coley.lljzip.util.ByteData;
9+
import software.coley.lljzip.util.ByteDataUtil;
10+
11+
import java.io.IOException;
12+
import java.util.function.Supplier;
13+
14+
/**
15+
* Utilities for zip tests where there are embedded zip files.
16+
*
17+
* @author Matt Coley
18+
*/
19+
public class JarInJarUtils {
20+
/**
21+
* @param archiveSupplier
22+
* Supplies a zip (jar) archive.
23+
*
24+
* @throws IOException
25+
* When the zip cannot be read.
26+
*/
27+
public static void handleJar(Supplier<ZipArchive> archiveSupplier) throws IOException {
28+
ZipArchive zipJvm = archiveSupplier.get();
29+
for (LocalFileHeader lfh : zipJvm.getLocalFiles()) {
30+
String entryName = lfh.getFileNameAsString();
31+
if (entryName.endsWith(".class")) {
32+
// We can verify the correctness of our zip model offsets and compression
33+
// by parsing and writing back the class files contained in the jar.
34+
// If anything is wrong, this process should fail.
35+
handleClass(entryName, lfh);
36+
} else if (entryName.endsWith(".jar")) {
37+
// We should be able to extract contents in the jar in-memory and make the same assumptions
38+
// as we do on the root (that classes can be parsed)
39+
handleJar(() -> {
40+
try {
41+
ByteData decompressed = ZipCompressions.decompress(lfh);
42+
return ZipIO.readStandard(decompressed);
43+
} catch (IOException ex) {
44+
throw new IllegalStateException("Failed to read inner jar: " + entryName, ex);
45+
}
46+
});
47+
}
48+
}
49+
}
50+
51+
/**
52+
* Attempts to parse the class file, and write it back. If there's an encoding/compression issue, this process fails.
53+
*
54+
* @param localFileHeader
55+
* Local file header of class.
56+
*
57+
* @throws IOException
58+
* When the class couldn't be parsed.
59+
*/
60+
public static void handleClass(String name, LocalFileHeader localFileHeader) throws IOException {
61+
byte[] entryData = ByteDataUtil.toByteArray(ZipCompressions.decompress(localFileHeader));
62+
try {
63+
ClassReader reader = new ClassReader(entryData);
64+
reader.accept(new ClassWriter(0), 0);
65+
} catch (Throwable ex) {
66+
throw new IOException("Failed to parse class: " + name, ex);
67+
}
68+
}
69+
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,11 @@ public class ZipComparisonShowcaseTest {
3131
"hello.jar",
3232
"hello-concat.jar",
3333
"hello-concat-junkheader.jar",
34+
"hello-copyjar-at-head.jar",
35+
"hello-copyjar-at-tail.jar",
3436
"hello-end-declares-0-entries.jar",
3537
"hello-end-declares-0-entries-0-offset.jar",
38+
"hello-jar-in-in-jar-in-jar-in-jar-in-jar.jar",
3639
"hello-junk-dir-length.jar",
3740
"hello-junk-eocd.jar",
3841
"hello-junk-local-length.jar",
1.65 KB
Binary file not shown.
1.65 KB
Binary file not shown.
Binary file not shown.

0 commit comments

Comments
 (0)