Skip to content

Commit ac401e7

Browse files
committed
[api] fix issue in Tar/Zip Utils that resulted in incorrect artifact extraction
1 parent 73f7cb0 commit ac401e7

File tree

7 files changed

+51
-14
lines changed

7 files changed

+51
-14
lines changed

api/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ dependencies {
1515
exclude("junit", "junit")
1616
}
1717
testImplementation(libs.slf4j.simple)
18+
testImplementation(project(":testing"))
1819
testRuntimeOnly(project(":engines:pytorch:pytorch-model-zoo"))
1920
testRuntimeOnly(project(":engines:pytorch:pytorch-jni"))
2021
}

api/src/main/java/ai/djl/util/TarUtils.java

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,7 @@ public static void untar(InputStream is, Path dir, boolean gzip) throws IOExcept
4848
try (TarArchiveInputStream tis = new TarArchiveInputStream(bis)) {
4949
TarArchiveEntry entry;
5050
while ((entry = tis.getNextEntry()) != null) {
51-
String entryName = ZipUtils.removeLeadingFileSeparator(entry.getName());
52-
if (entryName.contains("..")) {
53-
throw new IOException("Malicious zip entry: " + entryName);
54-
}
51+
String entryName = ZipUtils.sanitizeAndValidateArchiveEntry(entry.getName(), dir);
5552
Path file = dir.resolve(entryName).toAbsolutePath();
5653
if (entry.isDirectory()) {
5754
Files.createDirectories(file);

api/src/main/java/ai/djl/util/ZipUtils.java

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
*/
1313
package ai.djl.util;
1414

15+
import org.apache.commons.io.FilenameUtils;
16+
1517
import java.io.BufferedOutputStream;
1618
import java.io.ByteArrayOutputStream;
1719
import java.io.File;
@@ -52,10 +54,7 @@ public static void unzip(InputStream is, Path dest) throws IOException {
5254
ZipEntry entry;
5355
Set<String> set = new HashSet<>();
5456
while ((entry = zis.getNextEntry()) != null) {
55-
String name = removeLeadingFileSeparator(entry.getName());
56-
if (name.contains("..")) {
57-
throw new IOException("Malicious zip entry: " + name);
58-
}
57+
String name = sanitizeAndValidateArchiveEntry(entry.getName(), dest);
5958
set.add(name);
6059
Path file = dest.resolve(name).toAbsolutePath();
6160
if (entry.isDirectory()) {
@@ -121,13 +120,31 @@ private static void addToZip(Path root, Path file, ZipOutputStream zos) throws I
121120
}
122121
}
123122

123+
static String sanitizeAndValidateArchiveEntry(String name, Path destination)
124+
throws IOException {
125+
String sanitizedName = removeLeadingFileSeparator(name);
126+
Path expectedOutputPath = destination.resolve(sanitizedName).normalize();
127+
if (!expectedOutputPath.startsWith(destination.normalize())) {
128+
throw new IOException(
129+
"Bad archive entry "
130+
+ name
131+
+ ". Attempted write outside destination "
132+
+ destination);
133+
}
134+
return sanitizedName;
135+
}
136+
124137
static String removeLeadingFileSeparator(String name) {
138+
String osAwareArchiveEntryName = FilenameUtils.separatorsToSystem(name);
125139
int index = 0;
126-
for (; index < name.length(); index++) {
127-
if (name.charAt(index) != File.separatorChar) {
140+
for (; index < osAwareArchiveEntryName.length(); index++) {
141+
if (osAwareArchiveEntryName.charAt(index) != File.separatorChar) {
128142
break;
129143
}
130144
}
145+
// We return the substring of the original entry here because for zip archives
146+
// we validate this entry against the header and do not want to modify the
147+
// separator char as that can cause false positive issues in ValidationInputStream logic
131148
return name.substring(index);
132149
}
133150

api/src/test/java/ai/djl/util/ZipUtilsTest.java

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
*/
1313
package ai.djl.util;
1414

15+
import ai.djl.testing.TestRequirements;
16+
1517
import org.apache.commons.compress.archivers.zip.Zip64Mode;
1618
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
1719
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
@@ -49,13 +51,26 @@ public void testEmptyZipFile() throws IOException {
4951
public void testOffendingTar() throws IOException {
5052
Path path = Paths.get("src/test/resources/offending.tar");
5153
Path output = Paths.get("build/output");
52-
Path file = output.resolve("tmp/empty.txt");
53-
Utils.deleteQuietly(file);
5454
Files.createDirectories(output);
55+
Path outputFile = output.resolve("tmp/empty.txt");
56+
Utils.deleteQuietly(outputFile);
5557
try (InputStream is = Files.newInputStream(path)) {
5658
TarUtils.untar(is, output, false);
5759
}
58-
Assert.assertTrue(Files.exists(file));
60+
Assert.assertTrue(Files.exists(outputFile));
61+
}
62+
63+
@Test
64+
public void testLinuxCreatedWindowsUsedOffendingTar() throws IOException {
65+
TestRequirements.windows();
66+
Path tarPath = Paths.get("src/test/resources/linux_create_windows_use.tar");
67+
Path output = Paths.get("build/output");
68+
Files.createDirectories(output);
69+
Path outputFile = output.resolve("Windows/System32/drivers/etc/dummy.txt");
70+
try (InputStream is = Files.newInputStream(tarPath)) {
71+
TarUtils.untar(is, output, false);
72+
}
73+
Assert.assertTrue(Files.exists(outputFile));
5974
}
6075

6176
@Test
Binary file not shown.

integration/src/test/java/ai/djl/integration/IntegrationTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public void runIntegrationTests() {
3838
// TODO: windows CPU build is having OOM issue if 3 engines are loaded and running tests
3939
// together
4040
if (System.getProperty("os.name").startsWith("Win")) {
41-
engines.add("MXNet");
41+
engines.add("PyTorch");
4242
} else if ("aarch64".equals(System.getProperty("os.arch"))) {
4343
engines.add("PyTorch");
4444
} else {

testing/src/main/java/ai/djl/testing/TestRequirements.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,13 @@ public static void notWindows() {
9696
}
9797
}
9898

99+
/** Requires that the test runs on windows, not OSX or linux. */
100+
public static void windows() {
101+
if (!System.getProperty("os.name").toLowerCase().startsWith("win")) {
102+
throw new SkipException("This test requires windows");
103+
}
104+
}
105+
99106
/** Requires that the test runs on x86_64 arch. */
100107
public static void notArm() {
101108
if ("aarch64".equals(System.getProperty("os.arch"))) {

0 commit comments

Comments
 (0)