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

Reply via email to