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());
+ }
+ }
+}