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

garydgregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-compress.git


The following commit(s) were added to refs/heads/master by this push:
     new 81bbc1de9 fix packed stream count check in 
SevenZFile.sanityCheckUnpackInfo (#791)
81bbc1de9 is described below

commit 81bbc1de998dfe9925bd54b40830809ebf60a82d
Author: KALI 834X <[email protected]>
AuthorDate: Fri Jul 31 18:48:29 2026 +0530

    fix packed stream count check in SevenZFile.sanityCheckUnpackInfo (#791)
    
    folders consuming more packed streams than kPackInfo declares slipped past 
the comparison and overran packStreamOffsets in reopenFolderInputStream.
---
 .../compress/archivers/sevenz/SevenZFile.java      |   5 +-
 .../sevenz/SevenZPackedStreamCountTest.java        | 136 +++++++++++++++++++++
 2 files changed, 139 insertions(+), 2 deletions(-)

diff --git 
a/src/main/java/org/apache/commons/compress/archivers/sevenz/SevenZFile.java 
b/src/main/java/org/apache/commons/compress/archivers/sevenz/SevenZFile.java
index 2a9830078..e45c21501 100644
--- a/src/main/java/org/apache/commons/compress/archivers/sevenz/SevenZFile.java
+++ b/src/main/java/org/apache/commons/compress/archivers/sevenz/SevenZFile.java
@@ -2236,8 +2236,9 @@ private void sanityCheckUnpackInfo(final ByteBuffer 
header, final ArchiveStatist
         }
         final long totalNumberOfBindPairs = stats.numberOfOutStreams - 
stats.numberOfFolders;
         final long packedStreamsRequiredByFolders = stats.numberOfInStreams - 
totalNumberOfBindPairs;
-        if (packedStreamsRequiredByFolders < stats.numberOfPackedStreams) {
-            throw new ArchiveException("7z archive: Archive doesn't contain 
enough packed streams");
+        if (packedStreamsRequiredByFolders != stats.numberOfPackedStreams) {
+            throw new ArchiveException("7z archive: Folders require %,d packed 
streams but the archive contains %,d", packedStreamsRequiredByFolders,
+                    stats.numberOfPackedStreams);
         }
         nid = getUnsignedByte(header);
         if (nid != NID.kCodersUnpackSize) {
diff --git 
a/src/test/java/org/apache/commons/compress/archivers/sevenz/SevenZPackedStreamCountTest.java
 
b/src/test/java/org/apache/commons/compress/archivers/sevenz/SevenZPackedStreamCountTest.java
new file mode 100644
index 000000000..2ac1c6fa4
--- /dev/null
+++ 
b/src/test/java/org/apache/commons/compress/archivers/sevenz/SevenZPackedStreamCountTest.java
@@ -0,0 +1,136 @@
+/*
+ * 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
+ *
+ *   https://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.commons.compress.archivers.sevenz;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.zip.CRC32;
+
+import org.apache.commons.compress.archivers.ArchiveException;
+import org.apache.commons.compress.utils.SeekableInMemoryByteChannel;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests the number of packed streams the folders of an archive consume 
against the number of packed streams the header declares.
+ */
+class SevenZPackedStreamCountTest {
+
+    /**
+     * Builds an archive of {@code numFolders} folders, each holding a single 
one byte entry behind one COPY coder, and a header declaring
+     * {@code numPackStreams} packed streams. Every folder consumes one packed 
stream, so the archive is well formed only when the two counts are equal.
+     *
+     * @param numPackStreams the number of packed streams the header declares, 
must be lower than 128.
+     * @param numFolders     the number of folders, must be lower than 128.
+     * @return the archive bytes.
+     */
+    private static byte[] archive(final int numPackStreams, final int 
numFolders) {
+        final ByteArrayOutputStream header = new ByteArrayOutputStream();
+        header.write(NID.kHeader);
+        header.write(NID.kMainStreamsInfo);
+        header.write(NID.kPackInfo);
+        header.write(0); // packPos
+        header.write(numPackStreams);
+        header.write(NID.kSize);
+        for (int i = 0; i < numPackStreams; i++) {
+            header.write(1); // packSize
+        }
+        header.write(NID.kEnd); // of kPackInfo
+        header.write(NID.kUnpackInfo);
+        header.write(NID.kFolder);
+        header.write(numFolders);
+        header.write(0); // not external
+        for (int i = 0; i < numFolders; i++) {
+            header.write(1); // one coder
+            header.write(1); // a one byte coder id, one input and one output 
stream, no properties
+            header.write(0); // coder id COPY
+        }
+        header.write(NID.kCodersUnpackSize);
+        for (int i = 0; i < numFolders; i++) {
+            header.write(1); // unpack size
+        }
+        header.write(NID.kEnd); // of kUnpackInfo
+        header.write(NID.kSubStreamsInfo);
+        header.write(NID.kEnd); // of kSubStreamsInfo, one substream per folder
+        header.write(NID.kEnd); // of kMainStreamsInfo
+        header.write(NID.kFilesInfo);
+        header.write(numFolders); // one entry per folder
+        header.write(NID.kName);
+        header.write(1 + 4 * numFolders);
+        header.write(0); // not external
+        for (int i = 0; i < numFolders; i++) {
+            header.write('a' + i); // a one character UTF-16LE name
+            header.write(0);
+            header.write(0); // terminator
+            header.write(0);
+        }
+        header.write(NID.kEnd); // of kFilesInfo
+        header.write(NID.kEnd); // of kHeader
+        final byte[] headerBytes = header.toByteArray();
+        final CRC32 crc = new CRC32();
+        crc.update(headerBytes, 0, headerBytes.length);
+        final ByteBuffer startHeader = 
ByteBuffer.allocate(20).order(ByteOrder.LITTLE_ENDIAN);
+        startHeader.putLong(numPackStreams); // next header offset, past the 
packed streams
+        startHeader.putLong(headerBytes.length); // next header size
+        startHeader.putInt((int) crc.getValue());
+        crc.reset();
+        crc.update(startHeader.array(), 0, startHeader.capacity());
+        final ByteArrayOutputStream archive = new ByteArrayOutputStream();
+        archive.write(SevenZFile.SIGNATURE, 0, SevenZFile.SIGNATURE.length);
+        archive.write(0); // major version
+        archive.write(4); // minor version
+        final ByteBuffer startHeaderCrc = 
ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN);
+        startHeaderCrc.putInt((int) crc.getValue());
+        archive.write(startHeaderCrc.array(), 0, startHeaderCrc.capacity());
+        archive.write(startHeader.array(), 0, startHeader.capacity());
+        archive.write(new byte[numPackStreams], 0, numPackStreams); // the 
packed streams
+        archive.write(headerBytes, 0, headerBytes.length);
+        return archive.toByteArray();
+    }
+
+    private static SevenZFile open(final int numPackStreams, final int 
numFolders) throws IOException {
+        return SevenZFile.builder().setSeekableByteChannel(new 
SeekableInMemoryByteChannel(archive(numPackStreams, numFolders))).get();
+    }
+
+    @Test
+    void testFoldersConsumingMorePackedStreamsThanDeclared() {
+        // the second folder maps to a packed stream index the header never 
declared
+        assertThrows(ArchiveException.class, () -> open(1, 2).close());
+    }
+
+    @Test
+    void testMatchingPackedStreamCount() throws IOException {
+        try (SevenZFile sevenZFile = open(2, 2)) {
+            for (int i = 0; i < 2; i++) {
+                final SevenZArchiveEntry entry = sevenZFile.getNextEntry();
+                assertNotNull(entry);
+                assertEquals(String.valueOf((char) ('a' + i)), 
entry.getName());
+                assertEquals(1, entry.getSize());
+            }
+            assertNull(sevenZFile.getNextEntry());
+        }
+    }
+}

Reply via email to