This is an automated email from the ASF dual-hosted git repository.

rmetzger pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/flink.git


The following commit(s) were added to refs/heads/master by this push:
     new 0da60ca1a47 [FLINK-29122][core] Improve robustness of 
FileUtils.expandDirectory() (#24307)
0da60ca1a47 is described below

commit 0da60ca1a4754f858cf7c52dd4f0c97ae0e1b0cb
Author: Anupam Aggarwal <aaggar...@confluent.io>
AuthorDate: Wed Mar 13 20:31:09 2024 +0530

    [FLINK-29122][core] Improve robustness of FileUtils.expandDirectory() 
(#24307)
---
 .../main/java/org/apache/flink/util/FileUtils.java | 14 +++++++
 .../java/org/apache/flink/util/FileUtilsTest.java  | 49 ++++++++++++++++++++++
 2 files changed, 63 insertions(+)

diff --git a/flink-core/src/main/java/org/apache/flink/util/FileUtils.java 
b/flink-core/src/main/java/org/apache/flink/util/FileUtils.java
index e09ec67f987..1571906a006 100644
--- a/flink-core/src/main/java/org/apache/flink/util/FileUtils.java
+++ b/flink-core/src/main/java/org/apache/flink/util/FileUtils.java
@@ -515,12 +515,22 @@ public final class FileUtils {
         }
     }
 
+    /**
+     * Un-archives files inside the target directory. Illegal fs access 
outside target directory is
+     * not permitted.
+     *
+     * @param file path to zipped/archived file
+     * @param targetDirectory directory path where file needs to be unarchived
+     * @return path to folder with unarchived contents
+     * @throws IOException if file open fails or in case of unsafe access 
outside target directory
+     */
     public static Path expandDirectory(Path file, Path targetDirectory) throws 
IOException {
         FileSystem sourceFs = file.getFileSystem();
         FileSystem targetFs = targetDirectory.getFileSystem();
         Path rootDir = null;
         try (ZipInputStream zis = new ZipInputStream(sourceFs.open(file))) {
             ZipEntry entry;
+            String targetDirStr = targetDirectory.toString();
             while ((entry = zis.getNextEntry()) != null) {
                 Path relativePath = new Path(entry.getName());
                 if (rootDir == null) {
@@ -529,6 +539,10 @@ public final class FileUtils {
                 }
 
                 Path newFile = new Path(targetDirectory, relativePath);
+                if (!newFile.toString().startsWith(targetDirStr)) {
+                    throw new IOException("Illegal escape from target 
directory");
+                }
+
                 if (entry.isDirectory()) {
                     targetFs.mkdirs(newFile);
                 } else {
diff --git a/flink-core/src/test/java/org/apache/flink/util/FileUtilsTest.java 
b/flink-core/src/test/java/org/apache/flink/util/FileUtilsTest.java
index 0a9953c060b..1576305d0aa 100644
--- a/flink-core/src/test/java/org/apache/flink/util/FileUtilsTest.java
+++ b/flink-core/src/test/java/org/apache/flink/util/FileUtilsTest.java
@@ -51,6 +51,8 @@ import java.util.List;
 import java.util.Random;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
@@ -517,6 +519,53 @@ public class FileUtilsTest {
         assertDirEquals(compressDir.resolve(originalDir), 
extractDir.resolve(originalDir));
     }
 
+    /**
+     * Generates zip archive, containing path to file/dir passed in, 
un-archives the generated zip
+     * using {@link 
org.apache.flink.util.FileUtils#expandDirectory(org.apache.flink.core.fs.Path,
+     * org.apache.flink.core.fs.Path)} and returns path to expanded folder.
+     *
+     * @param prefix prefix to use for creating source and destination folders
+     * @param path path to contents of zip
+     * @return Path to folder containing unzipped contents
+     * @throws IOException if I/O error in file creation, un-archiving detects 
unsafe access outside
+     *     target folder
+     */
+    private Path writeZipAndFetchExpandedPath(String prefix, String path) 
throws IOException {
+        // random source folder
+        String sourcePath = prefix + "src";
+        String dstPath = prefix + "dst";
+        java.nio.file.Path srcFolder = TempDirUtils.newFolder(temporaryFolder, 
sourcePath).toPath();
+        java.nio.file.Path zippedFile = srcFolder.resolve("file.zip");
+        ZipOutputStream out = new 
ZipOutputStream(Files.newOutputStream(zippedFile));
+        ZipEntry e1 = new ZipEntry(path);
+        out.putNextEntry(e1);
+        out.close();
+        java.nio.file.Path dstFolder = TempDirUtils.newFolder(temporaryFolder, 
dstPath).toPath();
+        return FileUtils.expandDirectory(
+                new Path(zippedFile.toString()), new 
Path(dstFolder.toString()));
+    }
+
+    @Test
+    public void testExpandDirWithValidPaths() {
+        Assertions.assertDoesNotThrow(() -> writeZipAndFetchExpandedPath("t0", 
"/level1/level2/"));
+        Assertions.assertDoesNotThrow(
+                () -> writeZipAndFetchExpandedPath("t1", 
"/level1/level2/file.txt"));
+        Assertions.assertDoesNotThrow(
+                () -> writeZipAndFetchExpandedPath("t2", 
"/level1/../level1/file.txt"));
+        Assertions.assertDoesNotThrow(
+                () -> writeZipAndFetchExpandedPath("t3", 
"/level1/level2/level3/../"));
+        Assertions.assertThrows(
+                IOException.class,
+                () -> writeZipAndFetchExpandedPath("t2", 
"/level1/level2/../../pass"));
+    }
+
+    @Test
+    public void testExpandDirWithForbiddenEscape() {
+        Assertions.assertThrows(
+                IOException.class, () -> writeZipAndFetchExpandedPath("t1", 
"/../../"));
+        Assertions.assertThrows(IOException.class, () -> 
writeZipAndFetchExpandedPath("t2", "../"));
+    }
+
     /**
      * Generates a random content file.
      *

Reply via email to