Author: fanningpj
Date: Thu Aug 10 09:27:26 2023
New Revision: 1911588

URL: http://svn.apache.org/viewvc?rev=1911588&view=rev
Log:
add check for number of files inside zip

Added:
    
poi/trunk/poi-ooxml/src/test/java/org/apache/poi/openxml4j/TestOPCPackageFileLimit.java
   (with props)
    
poi/trunk/poi-ooxml/src/test/java/org/apache/poi/xssf/TestXSSFFileChecks.java   
(with props)
Modified:
    
poi/trunk/poi-ooxml/src/main/java/org/apache/poi/openxml4j/opc/ZipPackage.java
    
poi/trunk/poi-ooxml/src/main/java/org/apache/poi/openxml4j/util/ZipArchiveThresholdInputStream.java
    
poi/trunk/poi-ooxml/src/main/java/org/apache/poi/openxml4j/util/ZipSecureFile.java
    
poi/trunk/poi-ooxml/src/test/java/org/apache/poi/openxml4j/util/TestZipSecureFile.java
    poi/trunk/poi-ooxml/src/test/java9/module-info.java

Modified: 
poi/trunk/poi-ooxml/src/main/java/org/apache/poi/openxml4j/opc/ZipPackage.java
URL: 
http://svn.apache.org/viewvc/poi/trunk/poi-ooxml/src/main/java/org/apache/poi/openxml4j/opc/ZipPackage.java?rev=1911588&r1=1911587&r2=1911588&view=diff
==============================================================================
--- 
poi/trunk/poi-ooxml/src/main/java/org/apache/poi/openxml4j/opc/ZipPackage.java 
(original)
+++ 
poi/trunk/poi-ooxml/src/main/java/org/apache/poi/openxml4j/opc/ZipPackage.java 
Thu Aug 10 09:27:26 2023
@@ -29,6 +29,7 @@ import java.io.OutputStream;
 import java.util.Collections;
 import java.util.Enumeration;
 import java.util.List;
+import java.util.Locale;
 import java.util.stream.Collectors;
 
 import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
@@ -50,6 +51,7 @@ import org.apache.poi.openxml4j.util.Zip
 import org.apache.poi.openxml4j.util.ZipEntrySource;
 import org.apache.poi.openxml4j.util.ZipFileZipEntrySource;
 import org.apache.poi.openxml4j.util.ZipInputStreamZipEntrySource;
+import org.apache.poi.openxml4j.util.ZipSecureFile;
 import org.apache.poi.util.IOUtils;
 import org.apache.poi.util.TempFile;
 
@@ -308,8 +310,13 @@ public final class ZipPackage extends OP
         // (Need to create relationships before other
         //  parts, otherwise we might create a part before
         //  its relationship exists, and then it won't tie up)
+        final List<? extends ZipArchiveEntry> list = 
Collections.list(zipEntries);
+        if (list.size() > ZipSecureFile.getMaxFileCount()) {
+            throw new InvalidFormatException(String.format(
+                    Locale.ROOT, ZipSecureFile.MAX_FILE_COUNT_MSG, 
ZipSecureFile.getMaxFileCount()));
+        }
         final List<EntryTriple> entries =
-                Collections.list(zipEntries).stream()
+                list.stream()
                         .filter(zipArchiveEntry -> 
!ignoreEntry(zipArchiveEntry))
                         .map(zae -> new EntryTriple(zae, contentTypeManager))
                         .filter(mm -> mm.partName != null)

Modified: 
poi/trunk/poi-ooxml/src/main/java/org/apache/poi/openxml4j/util/ZipArchiveThresholdInputStream.java
URL: 
http://svn.apache.org/viewvc/poi/trunk/poi-ooxml/src/main/java/org/apache/poi/openxml4j/util/ZipArchiveThresholdInputStream.java?rev=1911588&r1=1911587&r2=1911588&view=diff
==============================================================================
--- 
poi/trunk/poi-ooxml/src/main/java/org/apache/poi/openxml4j/util/ZipArchiveThresholdInputStream.java
 (original)
+++ 
poi/trunk/poi-ooxml/src/main/java/org/apache/poi/openxml4j/util/ZipArchiveThresholdInputStream.java
 Thu Aug 10 09:27:26 2023
@@ -17,9 +17,6 @@
 
 package org.apache.poi.openxml4j.util;
 
-import static org.apache.poi.openxml4j.util.ZipSecureFile.MAX_ENTRY_SIZE;
-import static org.apache.poi.openxml4j.util.ZipSecureFile.MIN_INFLATE_RATIO;
-
 import java.io.EOFException;
 import java.io.FilterInputStream;
 import java.io.IOException;
@@ -34,10 +31,10 @@ import org.apache.poi.openxml4j.exceptio
 import org.apache.poi.util.IOUtils;
 import org.apache.poi.util.Internal;
 
+import static org.apache.poi.openxml4j.util.ZipSecureFile.*;
+
 @Internal
 public class ZipArchiveThresholdInputStream extends FilterInputStream {
-    // don't alert for expanded sizes smaller than 100k
-    private static final long GRACE_ENTRY_SIZE = 100*1024L;
 
     private static final String MAX_ENTRY_SIZE_MSG =
         "Zip bomb detected! The file would exceed the max size of the expanded 
data in the zip-file.\n" +
@@ -58,6 +55,7 @@ public class ZipArchiveThresholdInputStr
      */
     private ZipArchiveEntry entry;
     private boolean guardState = true;
+    private long entryCount;
 
     public ZipArchiveThresholdInputStream(InputStream is) {
         super(is);
@@ -125,7 +123,7 @@ public class ZipArchiveThresholdInputStr
         final String entryName = entry == null ? "not set" : entry.getName();
 
         // check the file size first, in case we are working on uncompressed 
streams
-        if(payloadSize > MAX_ENTRY_SIZE) {
+        if (payloadSize > MAX_ENTRY_SIZE) {
             throw new IOException(String.format(Locale.ROOT, 
MAX_ENTRY_SIZE_MSG, payloadSize, rawSize, MAX_ENTRY_SIZE, entryName));
         }
 
@@ -150,6 +148,11 @@ public class ZipArchiveThresholdInputStr
 
         try {
             entry = ((ZipArchiveInputStream) in).getNextZipEntry();
+            if (guardState && entry != null) {
+                if (++entryCount > MAX_FILE_COUNT) {
+                    throw new IOException(String.format(Locale.ROOT, 
MAX_FILE_COUNT_MSG, MAX_FILE_COUNT));
+                }
+            }
             return entry;
         } catch (ZipException ze) {
             if (ze.getMessage().startsWith("Unexpected record signature")) {

Modified: 
poi/trunk/poi-ooxml/src/main/java/org/apache/poi/openxml4j/util/ZipSecureFile.java
URL: 
http://svn.apache.org/viewvc/poi/trunk/poi-ooxml/src/main/java/org/apache/poi/openxml4j/util/ZipSecureFile.java?rev=1911588&r1=1911587&r2=1911588&view=diff
==============================================================================
--- 
poi/trunk/poi-ooxml/src/main/java/org/apache/poi/openxml4j/util/ZipSecureFile.java
 (original)
+++ 
poi/trunk/poi-ooxml/src/main/java/org/apache/poi/openxml4j/util/ZipSecureFile.java
 Thu Aug 10 09:27:26 2023
@@ -37,12 +37,23 @@ public class ZipSecureFile extends ZipFi
     private static final Logger LOG = 
LogManager.getLogger(ZipSecureFile.class);
     /* package */ static double MIN_INFLATE_RATIO = 0.01d;
     /* package */ static final long DEFAULT_MAX_ENTRY_SIZE = 0xFFFFFFFFL;
+    /* package */ static final long DEFAULT_MAX_FILE_COUNT = 1000;
+    /* package */ static final long DEFAULT_GRACE_ENTRY_SIZE = 100*1024L;
     /* package */ static long MAX_ENTRY_SIZE = DEFAULT_MAX_ENTRY_SIZE;
+    /* package */ static long MAX_FILE_COUNT = DEFAULT_MAX_FILE_COUNT;
+    /* package */ static long GRACE_ENTRY_SIZE = DEFAULT_GRACE_ENTRY_SIZE;
 
     // The maximum chars of extracted text
     /* package */ static final long DEFAULT_MAX_TEXT_SIZE = 10*1024*1024L;
     private static long MAX_TEXT_SIZE = DEFAULT_MAX_TEXT_SIZE;
 
+    public static final String MAX_FILE_COUNT_MSG =
+            "The file appears to be potentially malicious. This file embeds 
more internal file entries than expected.\n" +
+                    "This may indicates that the file could pose a security 
risk.\n" +
+                    "You can adjust this limit via 
ZipSecureFile.setMaxFileCount() if you need to work with files which are very 
large.\n" +
+                    "Limits: MAX_FILE_COUNT: %d";
+
+
     private final String fileName;
 
     /**
@@ -69,6 +80,29 @@ public class ZipSecureFile extends ZipFi
     }
 
     /**
+     * Returns the current maximum file count that is used.
+     *
+     * See setMaxFileCount() for details.
+     *
+     * @return The max accepted file count (i.e. the max number of files we 
allow inside zip files that we read - including OOXML files like xlsx, docx, 
pptx, etc.).
+     * @since POI 5.2.4
+     */
+    public static long getMaxFileCount() {
+        return MAX_FILE_COUNT;
+    }
+
+    /**
+     * Sets the maximum file count that we allow inside zip files that we read 
-
+     * including OOXML files like xlsx, docx, pptx, etc. The default is 1000.
+     *
+     * @param maxFileCount The max accepted file count
+     * @since POI 5.2.4
+     */
+    public static void setMaxFileCount(final long maxFileCount) {
+        MAX_FILE_COUNT = maxFileCount;
+    }
+
+    /**
      * Sets the maximum file size of a single zip entry. It defaults to 4GB,
      * i.e. the 32-bit zip format maximum.
      * 
@@ -99,6 +133,39 @@ public class ZipSecureFile extends ZipFi
     }
 
     /**
+     * Sets the grace entry size of a single zip entry. It defaults to 100Kb.
+     *
+     * When decompressed data in a zip entry is smaller than this size, the
+     * Minimum Inflation Ratio check is ignored.
+     *
+     * Setting this to a very small value may lead to more files being flagged
+     * as potential Zip Bombs are rejected as a result.
+     *
+     * @param graceEntrySize the grace entry size of a single zip entry
+     * @throws IllegalArgumentException for negative graceEntrySize
+     * @since POI 5.2.4
+     */
+    public static void setGraceEntrySize(long graceEntrySize) {
+        if (graceEntrySize < 0) {
+            throw new IllegalArgumentException("Grace entry size must be 
greater than or equal to zero");
+        }
+        GRACE_ENTRY_SIZE = graceEntrySize;
+    }
+
+    /**
+     * Returns the current threshold for decompressed data in zip entries that 
are regarded as too small
+     * to worry about from a Zip Bomb perspective (default is 100Kb).
+     *
+     * See setGraceEntrySize() for details.
+     *
+     * @return The current grace entry size
+     * @since POI 5.2.4
+     */
+    public static long getGraceEntrySize() {
+        return GRACE_ENTRY_SIZE;
+    }
+
+    /**
      * Sets the maximum number of characters of text that are
      * extracted before an exception is thrown during extracting
      * text from documents.

Added: 
poi/trunk/poi-ooxml/src/test/java/org/apache/poi/openxml4j/TestOPCPackageFileLimit.java
URL: 
http://svn.apache.org/viewvc/poi/trunk/poi-ooxml/src/test/java/org/apache/poi/openxml4j/TestOPCPackageFileLimit.java?rev=1911588&view=auto
==============================================================================
--- 
poi/trunk/poi-ooxml/src/test/java/org/apache/poi/openxml4j/TestOPCPackageFileLimit.java
 (added)
+++ 
poi/trunk/poi-ooxml/src/test/java/org/apache/poi/openxml4j/TestOPCPackageFileLimit.java
 Thu Aug 10 09:27:26 2023
@@ -0,0 +1,64 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+
+package org.apache.poi.openxml4j;
+
+import org.apache.poi.hssf.HSSFTestDataSamples;
+import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
+import org.apache.poi.openxml4j.opc.OPCPackage;
+import org.apache.poi.openxml4j.util.ZipSecureFile;
+import org.junit.jupiter.api.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+class TestOPCPackageFileLimit {
+    @Test
+    void testWithReducedFileLimit() throws InvalidFormatException {
+        final long defaultLimit = ZipSecureFile.getMaxFileCount();
+        ZipSecureFile.setMaxFileCount(5);
+        try (InputStream is = 
HSSFTestDataSamples.openSampleFileStream("HeaderFooterComplexFormats.xlsx")) {
+            OPCPackage opcPackage = OPCPackage.open(is);
+            fail("expected IOException");
+        } catch (IOException e) {
+            
assertTrue(e.getMessage().contains("ZipSecureFile.setMaxFileCount()"),
+                    "unexpected exception message: " + e.getMessage());
+        } finally {
+            ZipSecureFile.setMaxFileCount(defaultLimit);
+        }
+    }
+
+    @Test
+    void testFileWithReducedFileLimit() {
+        final File file = 
HSSFTestDataSamples.getSampleFile("HeaderFooterComplexFormats.xlsx");
+        final long defaultLimit = ZipSecureFile.getMaxFileCount();
+        ZipSecureFile.setMaxFileCount(5);
+        try {
+            OPCPackage opcPackage = OPCPackage.open(file);
+            fail("expected InvalidFormatException");
+        } catch (InvalidFormatException e) {
+            
assertTrue(e.getMessage().contains("ZipSecureFile.setMaxFileCount()"),
+                    "unexpected exception message: " + e.getMessage());
+        } finally {
+            ZipSecureFile.setMaxFileCount(defaultLimit);
+        }
+    }
+}

Propchange: 
poi/trunk/poi-ooxml/src/test/java/org/apache/poi/openxml4j/TestOPCPackageFileLimit.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: 
poi/trunk/poi-ooxml/src/test/java/org/apache/poi/openxml4j/util/TestZipSecureFile.java
URL: 
http://svn.apache.org/viewvc/poi/trunk/poi-ooxml/src/test/java/org/apache/poi/openxml4j/util/TestZipSecureFile.java?rev=1911588&r1=1911587&r2=1911588&view=diff
==============================================================================
--- 
poi/trunk/poi-ooxml/src/test/java/org/apache/poi/openxml4j/util/TestZipSecureFile.java
 (original)
+++ 
poi/trunk/poi-ooxml/src/test/java/org/apache/poi/openxml4j/util/TestZipSecureFile.java
 Thu Aug 10 09:27:26 2023
@@ -79,4 +79,25 @@ class TestZipSecureFile {
             ZipSecureFile.setMaxTextSize(ZipSecureFile.DEFAULT_MAX_TEXT_SIZE);
         }
     }
+
+    @Test
+    void testSettingGraceEntrySize() {
+        long approx8G = ZipSecureFile.MAX_ENTRY_SIZE * 2;
+        try {
+            ZipSecureFile.setGraceEntrySize(approx8G);
+            assertEquals(approx8G, ZipSecureFile.getGraceEntrySize());
+        } finally {
+            
ZipSecureFile.setGraceEntrySize(ZipSecureFile.DEFAULT_GRACE_ENTRY_SIZE);
+        }
+    }
+
+    @Test
+    void testSettingMaxFileCount() {
+        try {
+            ZipSecureFile.setMaxFileCount(123456789);
+            assertEquals(123456789, ZipSecureFile.getMaxFileCount());
+        } finally {
+            
ZipSecureFile.setMaxFileCount(ZipSecureFile.DEFAULT_MAX_FILE_COUNT);
+        }
+    }
 }

Added: 
poi/trunk/poi-ooxml/src/test/java/org/apache/poi/xssf/TestXSSFFileChecks.java
URL: 
http://svn.apache.org/viewvc/poi/trunk/poi-ooxml/src/test/java/org/apache/poi/xssf/TestXSSFFileChecks.java?rev=1911588&view=auto
==============================================================================
--- 
poi/trunk/poi-ooxml/src/test/java/org/apache/poi/xssf/TestXSSFFileChecks.java 
(added)
+++ 
poi/trunk/poi-ooxml/src/test/java/org/apache/poi/xssf/TestXSSFFileChecks.java 
Thu Aug 10 09:27:26 2023
@@ -0,0 +1,100 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+
+package org.apache.poi.xssf;
+
+import org.apache.poi.hssf.HSSFTestDataSamples;
+import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
+import org.apache.poi.openxml4j.util.ZipSecureFile;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.junit.jupiter.api.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+class TestXSSFFileChecks {
+    @Test
+    void testWithReducedFileLimit() {
+        final long defaultLimit = ZipSecureFile.getMaxFileCount();
+        ZipSecureFile.setMaxFileCount(5);
+        try (InputStream is = 
HSSFTestDataSamples.openSampleFileStream("HeaderFooterComplexFormats.xlsx")) {
+            XSSFWorkbook xssfWorkbook = new XSSFWorkbook(is);
+            fail("expected IOException");
+        } catch (IOException e) {
+            
assertTrue(e.getMessage().contains("ZipSecureFile.setMaxFileCount()"),
+                    "unexpected exception message: " + e.getMessage());
+        } finally {
+            ZipSecureFile.setMaxFileCount(defaultLimit);
+        }
+    }
+
+    @Test
+    void testFileWithReducedFileLimit() throws IOException {
+        final File file = 
HSSFTestDataSamples.getSampleFile("HeaderFooterComplexFormats.xlsx");
+        final long defaultLimit = ZipSecureFile.getMaxFileCount();
+        ZipSecureFile.setMaxFileCount(5);
+        try {
+            XSSFWorkbook xssfWorkbook = new XSSFWorkbook(file);
+            fail("expected InvalidFormatException");
+        } catch (InvalidFormatException e) {
+            
assertTrue(e.getMessage().contains("ZipSecureFile.setMaxFileCount()"),
+                    "unexpected exception message: " + e.getMessage());
+        } finally {
+            ZipSecureFile.setMaxFileCount(defaultLimit);
+        }
+    }
+
+    @Test
+    void testWithGraceEntrySize() throws IOException {
+        final double defaultInflateRatio = ZipSecureFile.getMinInflateRatio();
+        // setting MinInflateRatio but the default GraceEntrySize will mean 
this is ignored
+        // this exception will not happen with the default GraceEntrySize
+        ZipSecureFile.setMinInflateRatio(0.50);
+        try (InputStream is = 
HSSFTestDataSamples.openSampleFileStream("HeaderFooterComplexFormats.xlsx")) {
+            XSSFWorkbook xssfWorkbook = new XSSFWorkbook(is);
+            assertNotNull(xssfWorkbook);
+        } finally {
+            ZipSecureFile.setMinInflateRatio(defaultInflateRatio);
+        }
+    }
+
+    @Test
+    void testWithReducedGraceEntrySize() {
+        final long defaultGraceSize = ZipSecureFile.getGraceEntrySize();
+        final double defaultInflateRatio = ZipSecureFile.getMinInflateRatio();
+        ZipSecureFile.setGraceEntrySize(0);
+        // setting MinInflateRatio to cause an exception
+        // this exception will not happen with the default GraceEntrySize
+        ZipSecureFile.setMinInflateRatio(0.50);
+        try (InputStream is = 
HSSFTestDataSamples.openSampleFileStream("HeaderFooterComplexFormats.xlsx")) {
+            XSSFWorkbook xssfWorkbook = new XSSFWorkbook(is);
+            fail("expected IOException");
+        } catch (IOException e) {
+            
assertTrue(e.getMessage().contains("ZipSecureFile.setMinInflateRatio()"),
+                    "unexpected exception message: " + e.getMessage());
+        } finally {
+            ZipSecureFile.setMinInflateRatio(defaultInflateRatio);
+            ZipSecureFile.setGraceEntrySize(defaultGraceSize);
+        }
+    }
+
+}

Propchange: 
poi/trunk/poi-ooxml/src/test/java/org/apache/poi/xssf/TestXSSFFileChecks.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: poi/trunk/poi-ooxml/src/test/java9/module-info.java
URL: 
http://svn.apache.org/viewvc/poi/trunk/poi-ooxml/src/test/java9/module-info.java?rev=1911588&r1=1911587&r2=1911588&view=diff
==============================================================================
--- poi/trunk/poi-ooxml/src/test/java9/module-info.java (original)
+++ poi/trunk/poi-ooxml/src/test/java9/module-info.java Thu Aug 10 09:27:26 2023
@@ -81,6 +81,7 @@ module org.apache.poi.ooxml {
     exports org.apache.poi.poifs.crypt.temp;
 
     opens org.apache.poi.openxml4j.opc to org.apache.poi.poi, 
org.junit.platform.commons;
+    opens org.apache.poi.openxml4j to org.apache.poi.ooxml, 
org.junit.platform.commons;
 
 
     /* optional dependencies for xml signatures - you need to add a require 
entry your module-info



---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to