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

aajisaka pushed a commit to branch branch-3.5
in repository https://gitbox.apache.org/repos/asf/hadoop.git


The following commit(s) were added to refs/heads/branch-3.5 by this push:
     new 577f8b0009a HADOOP-19908. ZStandardDecompressor should reset 
compressedDirectBuf correctly (#8526)
577f8b0009a is described below

commit 577f8b0009a0ed8e2e2dc0b6e242aa06656956e6
Author: Cheng Pan <[email protected]>
AuthorDate: Thu Jun 4 09:57:33 2026 +0800

    HADOOP-19908. ZStandardDecompressor should reset compressedDirectBuf 
correctly (#8526)
    
    Signed-off-by: Akira Ajisaka <[email protected]>
    (cherry picked from commit 2e8f5cf47bb468c12c85c2e43d04531ac340c611)
---
 .../io/compress/zstd/ZStandardCompressor.java      |  2 +-
 .../io/compress/zstd/ZStandardDecompressor.java    |  7 ++-
 .../zstd/TestZStandardCompressorDecompressor.java  | 61 ++++++++++++++++++++++
 3 files changed, 68 insertions(+), 2 deletions(-)

diff --git 
a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/io/compress/zstd/ZStandardCompressor.java
 
b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/io/compress/zstd/ZStandardCompressor.java
index 8d989ec96db..b029e3e7652 100644
--- 
a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/io/compress/zstd/ZStandardCompressor.java
+++ 
b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/io/compress/zstd/ZStandardCompressor.java
@@ -301,7 +301,7 @@ public void reset() {
     finished = false;
     bytesRead = 0;
     bytesWritten = 0;
-    uncompressedDirectBuf.rewind();
+    uncompressedDirectBuf.clear();
     uncompressedDirectBufOff = 0;
     uncompressedDirectBufLen = 0;
     keepUncompressedBuf = false;
diff --git 
a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/io/compress/zstd/ZStandardDecompressor.java
 
b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/io/compress/zstd/ZStandardDecompressor.java
index c7134e7ff52..e927f1233c4 100644
--- 
a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/io/compress/zstd/ZStandardDecompressor.java
+++ 
b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/io/compress/zstd/ZStandardDecompressor.java
@@ -92,7 +92,7 @@ private void setInputFromSavedData() {
       bytesInCompressedBuffer = directBufferSize;
     }
 
-    compressedDirectBuf.rewind();
+    compressedDirectBuf.clear();
     compressedDirectBuf.put(
         userBuf, userBufOff, bytesInCompressedBuffer);
 
@@ -188,6 +188,9 @@ public int decompress(byte[] b, int off, int len)
       }
 
       // Restore limit so setInputFromSavedData() can rewind+put on next call.
+      // There is possible that `decompressDirectByteBufferStream` throws 
exception
+      // on decompressing corrupt data, code won't reach here, the caller must
+      // call `reset` to clear the state before resuing this decompressor.
       compressedDirectBuf.limit(directBufferSize);
     } else {
       n = 0;
@@ -225,6 +228,8 @@ public void reset() {
     finished = false;
     compressedDirectBufOff = 0;
     bytesInCompressedBuffer = 0;
+    compressedDirectBuf.limit(directBufferSize);
+    compressedDirectBuf.position(0);
     uncompressedDirectBuf.limit(directBufferSize);
     uncompressedDirectBuf.position(directBufferSize);
     userBufOff = 0;
diff --git 
a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/io/compress/zstd/TestZStandardCompressorDecompressor.java
 
b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/io/compress/zstd/TestZStandardCompressorDecompressor.java
index e1546cdbbfa..176196f10a5 100644
--- 
a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/io/compress/zstd/TestZStandardCompressorDecompressor.java
+++ 
b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/io/compress/zstd/TestZStandardCompressorDecompressor.java
@@ -15,6 +15,7 @@
  */
 package org.apache.hadoop.io.compress.zstd;
 
+import com.github.luben.zstd.ZstdException;
 import org.apache.commons.io.FileUtils;
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.io.DataInputBuffer;
@@ -50,6 +51,7 @@
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
 
 public class TestZStandardCompressorDecompressor {
   private final static char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
@@ -557,6 +559,65 @@ public void testDecompressReturnsWhenNothingToDecompress() 
throws Exception {
     assertEquals(0, result);
   }
 
+  /**
+   * Verify that {@code setInput()} does not throw {@code 
BufferOverflowException}
+   * after a previous {@code decompress()} call threw an exception.
+   *
+   * <p>When {@code decompress()} processes compressed data, it sets
+   * {@code compressedDirectBuf.limit(bytesInCompressedBuffer)} — a value that
+   * may be smaller than {@code directBufferSize}. If {@code 
decompressDirectByteBufferStream}
+   * throws (e.g. on corrupted input), the limit is never restored. A 
subsequent
+   * {@code reset()} also does not restore {@code compressedDirectBuf.limit}.
+   * So the next {@code setInput()} call will hit {@code 
BufferOverflowException}
+   * because {@code setInputFromSavedData()} tries to {@code put()} more bytes
+   * than the current limit allows.</p>
+   *
+   * <p>This scenario occurs in practice when reading multiple zstd-compressed
+   * files from a directory: a corrupted file causes an exception 
mid-decompress,
+   * the decompressor is returned to the pool and reset, but the limit stays
+   * small. The next file's {@code setInput()} then fails.</p>
+   */
+  @Test
+  public void testSetInputAfterDecompressThrowsOnCorruptedData() throws 
Exception {
+    byte[] rawData = generate(400);
+    int bufSize = IO_FILE_BUFFER_SIZE_DEFAULT;
+
+    ByteArrayOutputStream baos = new ByteArrayOutputStream();
+    try (CompressionOutputStream cos = new CompressorStream(baos,
+        new ZStandardCompressor(), bufSize)) {
+      cos.write(rawData);
+    }
+    byte[] compressed = baos.toByteArray();
+
+    // Corrupt the compressed data by dropping the first 10 bytes.
+    byte[] corrupted = new byte[compressed.length - 10];
+    System.arraycopy(compressed, 10, corrupted, 0, corrupted.length);
+
+    ZStandardDecompressor decompressor = new ZStandardDecompressor(bufSize);
+    byte[] out = new byte[bufSize];
+
+    // Feed corrupted data — decompress() sets limit to corrupted.length, then 
throws.
+    decompressor.setInput(corrupted, 0, corrupted.length);
+    try {
+      decompressor.decompress(out, 0, out.length);
+      fail("decompress should throw exception on corrupted data");
+    } catch (ZstdException e) {
+      // Expected: corrupted data causes an exception.
+    }
+
+    // Reset the decompressor (as the codec pool would).
+    decompressor.reset();
+
+    // Feed valid data — this must NOT throw BufferOverflowException.
+    decompressor.setInput(compressed, 0, compressed.length);
+    int n = decompressor.decompress(out, 0, out.length);
+    assertTrue(n >= 0, "decompress should return >= 0 after reset");
+
+    while (!decompressor.finished()) {
+      decompressor.decompress(out, 0, out.length);
+    }
+  }
+
   // workers > 0 should produce data that round-trips correctly through the
   // decompressor, matching the bytes produced with the default workers=0.
   @Test


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to