This is an automated email from the ASF dual-hosted git repository.
lidavidm pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-java.git
The following commit(s) were added to refs/heads/main by this push:
new 9e863319b GH-1206: validate decompressed length in Lz4CompressionCodec
(#1207)
9e863319b is described below
commit 9e863319b1e3bedda45dbb12d5989f6b1330f664
Author: Abdul Rawoof Khan <[email protected]>
AuthorDate: Mon Jul 6 06:39:33 2026 +0530
GH-1206: validate decompressed length in Lz4CompressionCodec (#1207)
## What's Changed
`Lz4CompressionCodec.doDecompress` sizes the output buffer to the bytes
it actually decompressed, but sets `writerIndex` to the length taken
from the untrusted 8-byte prefix. A buffer whose prefix claims more than
the real output leaves the returned `ArrowBuf` with a `writerIndex` past
its capacity, and consumers then read off-heap memory beyond the
allocation. This adds the actual-vs-claimed length check the ZSTD codec
already does, so a mismatch throws instead of producing an over-long
buffer.
Closes #1206.
---
.../arrow/compression/Lz4CompressionCodec.java | 7 +++++++
.../arrow/compression/TestCompressionCodec.java | 22 ++++++++++++++++++++++
2 files changed, 29 insertions(+)
diff --git
a/compression/src/main/java/org/apache/arrow/compression/Lz4CompressionCodec.java
b/compression/src/main/java/org/apache/arrow/compression/Lz4CompressionCodec.java
index 91cefc2a9..f268e815f 100644
---
a/compression/src/main/java/org/apache/arrow/compression/Lz4CompressionCodec.java
+++
b/compression/src/main/java/org/apache/arrow/compression/Lz4CompressionCodec.java
@@ -80,6 +80,13 @@ public class Lz4CompressionCodec extends
AbstractCompressionCodec {
}
byte[] outBytes = out.toByteArray();
+ if (outBytes.length != decompressedLength) {
+ throw new RuntimeException(
+ "Expected != actual decompressed length: "
+ + decompressedLength
+ + " != "
+ + outBytes.length);
+ }
ArrowBuf decompressedBuffer = allocator.buffer(outBytes.length);
decompressedBuffer.setBytes(/* index= */ 0, outBytes);
decompressedBuffer.writerIndex(decompressedLength);
diff --git
a/compression/src/test/java/org/apache/arrow/compression/TestCompressionCodec.java
b/compression/src/test/java/org/apache/arrow/compression/TestCompressionCodec.java
index b8fb4e28b..d2d292164 100644
---
a/compression/src/test/java/org/apache/arrow/compression/TestCompressionCodec.java
+++
b/compression/src/test/java/org/apache/arrow/compression/TestCompressionCodec.java
@@ -20,6 +20,7 @@ import static
org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.ByteArrayOutputStream;
@@ -59,6 +60,7 @@ import org.apache.arrow.vector.types.pojo.Schema;
import org.apache.arrow.vector.util.ByteArrayReadableSeekableByteChannel;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
@@ -231,6 +233,26 @@ class TestCompressionCodec {
AutoCloseables.close(decompressedBuffers);
}
+ @Test
+ void testLz4DecompressRejectsWrongLength() {
+ byte[] data = new byte[512]; // all zeros, highly compressible
+ ArrowBuf orig = allocator.buffer(data.length);
+ orig.setBytes(0, data);
+ orig.writerIndex(data.length);
+
+ CompressionCodec codec = new Lz4CompressionCodec();
+ ArrowBuf compressed = codec.compress(allocator, orig);
+
+ // tamper with the 8-byte uncompressed-length prefix so it no longer
matches
+ // the real decompressed size
+ compressed.setLong(0, 1_000_000L);
+
+ RuntimeException e =
+ assertThrows(RuntimeException.class, () -> codec.decompress(allocator,
compressed));
+ assertTrue(e.getMessage().contains("decompressed length"));
+ compressed.close();
+ }
+
private static Stream<CompressionUtil.CodecType> codecTypes() {
return Arrays.stream(CompressionUtil.CodecType.values());
}