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 7e29fa061 Reject invalid literal/length and distance codes in
Deflate64 decoder (#785)
7e29fa061 is described below
commit 7e29fa061cb05243e6cacba731196aebaa037146
Author: KALI 834X <[email protected]>
AuthorDate: Tue Jul 14 22:35:49 2026 +0530
Reject invalid literal/length and distance codes in Deflate64 decoder (#785)
* reject invalid literal/length and distance codes in Deflate64 decoder
* add test for invalid Deflate64 distance code
---
.../compressors/deflate64/HuffmanDecoder.java | 10 ++++
.../compressors/deflate64/HuffmanDecoderTest.java | 53 ++++++++++++++++++++++
2 files changed, 63 insertions(+)
diff --git
a/src/main/java/org/apache/commons/compress/compressors/deflate64/HuffmanDecoder.java
b/src/main/java/org/apache/commons/compress/compressors/deflate64/HuffmanDecoder.java
index f28c7cac7..9ba65bd86 100644
---
a/src/main/java/org/apache/commons/compress/compressors/deflate64/HuffmanDecoder.java
+++
b/src/main/java/org/apache/commons/compress/compressors/deflate64/HuffmanDecoder.java
@@ -30,6 +30,7 @@
import java.nio.ByteOrder;
import java.util.Arrays;
+import org.apache.commons.compress.compressors.CompressorException;
import org.apache.commons.compress.utils.BitInputStream;
import org.apache.commons.compress.utils.ExactMath;
import org.apache.commons.lang3.ArrayFill;
@@ -171,14 +172,23 @@ private int decodeNext(final byte[] b, final int off,
final int len) throws IOEx
while (result < len) {
final int symbol = nextSymbol(reader, lengthTree);
if (symbol < 256) {
+ if (symbol < 0) {
+ throw new CompressorException("Invalid Deflate64
literal/length code %,d", symbol);
+ }
b[off + result++] = memory.add((byte) symbol);
} else if (symbol > 256) {
+ if (symbol - 257 >= RUN_LENGTH_TABLE.length) {
+ throw new CompressorException("Invalid Deflate64
literal/length code %,d", symbol);
+ }
final int runMask = RUN_LENGTH_TABLE[symbol - 257];
int run = runMask >>> 5;
final int runXtra = runMask & 0x1F;
run = ExactMath.add(run, readBits(runXtra));
final int distSym = nextSymbol(reader, distanceTree);
+ if (distSym < 0 || distSym >= DISTANCE_TABLE.length) {
+ throw new CompressorException("Invalid Deflate64
distance code %,d", distSym);
+ }
final int distMask = DISTANCE_TABLE[distSym];
int dist = distMask >>> 4;
diff --git
a/src/test/java/org/apache/commons/compress/compressors/deflate64/HuffmanDecoderTest.java
b/src/test/java/org/apache/commons/compress/compressors/deflate64/HuffmanDecoderTest.java
index bb20912d7..cb05bedda 100644
---
a/src/test/java/org/apache/commons/compress/compressors/deflate64/HuffmanDecoderTest.java
+++
b/src/test/java/org/apache/commons/compress/compressors/deflate64/HuffmanDecoderTest.java
@@ -25,6 +25,7 @@
import java.io.ByteArrayInputStream;
import java.util.Arrays;
+import org.apache.commons.compress.compressors.CompressorException;
import org.junit.jupiter.api.Test;
class HuffmanDecoderTest {
@@ -207,4 +208,56 @@ void testDecodeUncompressedBlockWithInvalidLenNLenValue()
throws Exception {
assertEquals("Illegal LEN / NLEN values", e.getMessage());
}
}
+
+ @Test
+ void testDecodeDynamicHuffmanBlockRejectsInvalidDistanceCode() throws
Exception {
+ // final block + dynamic huffman with HLIT=1, HDIST=0, HCLEN=14. The
code length tree
+ // assigns 1-bit codes to symbols 1 and 18, which encode 1-bit codes
for literal/length
+ // symbols 256 and 257 and a single 1-bit code for distance symbol 0.
The incomplete
+ // distance tree leaves the 1 branch empty, so the block data - length
symbol 257
+ // followed by a 1 bit on the distance side - decodes to distance
symbol -1.
+ final byte[] data = {
+ // |--- binary filling ---|76543210
+ 0b00000000000000000000000000001101, // final block + dynamic
huffman + HLIT 1
+ 0b11111111111111111111111111000000, // HDIST 0 + low 3 bits of
HCLEN 14
+ 0b11111111111111111111111110000001, // high bit of HCLEN +
zero lengths for 16, 17 + low bit of length 1 for 18
+ 0b00000000000000000000000000000000, // high bits of length for
18 + zero lengths for 0, 8
+ 0b00000000000000000000000000000000, // zero lengths for 7, 9 +
low bits of 6
+ 0b00000000000000000000000000000000, // zero lengths for 6, 10,
5
+ 0b00000000000000000000000000000000, // zero lengths for 11, 4,
12
+ 0b00000000000000000000000000000000, // zero lengths for 3, 13,
2
+ 0b11111111111111111111111110010000, // zero length for 14 +
length 1 for symbol 1 + code 18
+ 0b11111111111111111111111111111111, // 7 extra bits (127 ->
138 zero lengths) + code 18
+ 0b00000000000000000000000001101011, // 7 extra bits (107 ->
118 zero lengths) + code 1 (literal 256)
+ 0b00000000000000000000000000001100 // code 1 twice (literal
257, distance 0) + length symbol 257 + invalid distance bit 1
+ };
+ try (HuffmanDecoder decoder = new HuffmanDecoder(new
ByteArrayInputStream(data))) {
+ final byte[] result = new byte[100];
+ final CompressorException e =
assertThrows(CompressorException.class, () -> {
+ final int len = decoder.decode(result);
+ fail("Should have failed but returned " + len + " entries: " +
Arrays.toString(Arrays.copyOf(result, len)));
+ });
+ assertEquals("Invalid Deflate64 distance code -1", e.getMessage());
+ }
+ }
+
+ @Test
+ void testDecodeFixedHuffmanBlockRejectsReservedLiteralLengthCode() throws
Exception {
+ // final block + fixed huffman, followed by the 8-bit fixed code
0b11000110 for
+ // literal/length symbol 286. That symbol is a decodable leaf of the
fixed tree
+ // but is reserved by RFC 1951, so it has no entry in RUN_LENGTH_TABLE.
+ final byte[] data = {
+ // |--- binary filling ---|76543210
+ 0b00000000000000000000000000011011, // final block + fixed
huffman + low 5 bits of code 286
+ 0b00000000000000000000000000000011 // high 3 bits of code 286
+ };
+ try (HuffmanDecoder decoder = new HuffmanDecoder(new
ByteArrayInputStream(data))) {
+ final byte[] result = new byte[100];
+ final CompressorException e =
assertThrows(CompressorException.class, () -> {
+ final int len = decoder.decode(result);
+ fail("Should have failed but returned " + len + " entries: " +
Arrays.toString(Arrays.copyOf(result, len)));
+ });
+ assertEquals("Invalid Deflate64 literal/length code 286",
e.getMessage());
+ }
+ }
}