This is an automated email from the ASF dual-hosted git repository.
danny0405 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/hudi.git
The following commit(s) were added to refs/heads/master by this push:
new 8e0402db7cc7 fix(hfile): bound compressed block decompression (#19930)
8e0402db7cc7 is described below
commit 8e0402db7cc7264b5426ed01c3de7c0ba4ae4956
Author: Sherwin Tan <[email protected]>
AuthorDate: Mon Oct 5 18:48:51 2026 -0700
fix(hfile): bound compressed block decompression (#19930)
* fix(hfile): bound compressed block decompression
Exclude trailing checksum bytes from the native GZIP input stream and add a
regression test for Hudi issue #19929.
* test(hfile): cover HBase checksum tails in decompression regression
* test(hfile): reuse reader fixture for decompression regression
---------
Co-authored-by: danny0405 <[email protected]>
---
.../java/org/apache/hudi/io/hfile/HFileBlock.java | 17 +++++++++++++++--
.../org/apache/hudi/io/hfile/TestHFileReader.java | 6 ++++++
.../hudi_1_0_hbase_2_4_9_512KB_GZ_20000.hfile | Bin 101870 -> 101870 bytes
3 files changed, 21 insertions(+), 2 deletions(-)
diff --git a/hudi-io/src/main/java/org/apache/hudi/io/hfile/HFileBlock.java
b/hudi-io/src/main/java/org/apache/hudi/io/hfile/HFileBlock.java
index ae99ef2f1cf7..87fd8ed80f8c 100644
--- a/hudi-io/src/main/java/org/apache/hudi/io/hfile/HFileBlock.java
+++ b/hudi-io/src/main/java/org/apache/hudi/io/hfile/HFileBlock.java
@@ -92,6 +92,7 @@ public abstract class HFileBlock {
@Getter
private final HFileBlockType blockType;
protected final int onDiskSizeWithoutHeader;
+ protected final int onDiskDataSizeWithHeader;
protected final int uncompressedSizeWithoutHeader;
protected final int bytesPerChecksum;
private boolean isUnpacked = false;
@@ -115,6 +116,8 @@ public abstract class HFileBlock {
this.blockType = blockType;
this.onDiskSizeWithoutHeader = readInt(
byteBuff, startOffsetInBuff +
Header.ON_DISK_SIZE_WITHOUT_HEADER_INDEX);
+ this.onDiskDataSizeWithHeader = readInt(
+ byteBuff, startOffsetInBuff +
Header.ON_DISK_DATA_SIZE_WITH_HEADER_INDEX);
this.uncompressedSizeWithoutHeader = readInt(
byteBuff, startOffsetInBuff +
Header.UNCOMPRESSED_SIZE_WITHOUT_HEADER_INDEX);
this.bytesPerChecksum = readInt(
@@ -149,6 +152,7 @@ public abstract class HFileBlock {
this.sizeCheckSum = -1;
this.uncompressedEndOffset = -1;
this.onDiskSizeWithoutHeader = -1;
+ this.onDiskDataSizeWithHeader = -1;
this.uncompressedSizeWithoutHeader = -1;
this.bytesPerChecksum = -1;
}
@@ -232,13 +236,22 @@ public abstract class HFileBlock {
// Copy the block header which is not compressed
System.arraycopy(
compressedByteBuff, startOffsetInCompressedBuff, byteBuff, 0,
HFILEBLOCK_HEADER_SIZE);
+ // onDiskSizeWithoutHeader includes the trailing checksum bytes, while
+ // onDiskDataSizeWithHeader ends immediately after the compressed
payload.
+ // Passing the former to GZIPInputStream lets it interpret checksum
bytes as
+ // another gzip member and can result in ZipException (for example,
during
+ // metadata table compaction).
+ // byteBuff also reserves sizeCheckSum bytes, so limit output to
uncompressedSizeWithoutHeader.
+ // Using byteBuff.length - HFILEBLOCK_HEADER_SIZE makes readFully keep
reading beyond
+ // the first gzip stream to fill that checksum space.
+ int compressedDataSize = onDiskDataSizeWithHeader -
HFILEBLOCK_HEADER_SIZE;
try (InputStream byteBuffInputStream = new ByteArrayInputStream(
- compressedByteBuff, startOffsetInCompressedBuff +
HFILEBLOCK_HEADER_SIZE, onDiskSizeWithoutHeader)) {
+ compressedByteBuff, startOffsetInCompressedBuff +
HFILEBLOCK_HEADER_SIZE, compressedDataSize)) {
context.getCompressor().decompress(
byteBuffInputStream,
byteBuff,
HFILEBLOCK_HEADER_SIZE,
- byteBuff.length - HFILEBLOCK_HEADER_SIZE);
+ uncompressedSizeWithoutHeader);
}
}
isUnpacked = true;
diff --git
a/hudi-io/src/test/java/org/apache/hudi/io/hfile/TestHFileReader.java
b/hudi-io/src/test/java/org/apache/hudi/io/hfile/TestHFileReader.java
index 31259dce1f71..255502c569f4 100644
--- a/hudi-io/src/test/java/org/apache/hudi/io/hfile/TestHFileReader.java
+++ b/hudi-io/src/test/java/org/apache/hudi/io/hfile/TestHFileReader.java
@@ -251,6 +251,12 @@ public class TestHFileReader {
new KeyLookUpInfo("hudi-key-000019999b", SEEK_TO_EOF, "", "")
)
),
+ // The first two data blocks have 12 checksum bytes each, replaced
with gzip-like tails:
+ // block 0: 1f8b08000000000000000700 (invalid DEFLATE block type),
+ // block 1: 1f8b08000000000000000000 (truncated DEFLATE block).
+ // The native reader does not validate CRCs, but must exclude these
bytes from decompression.
+ // All headers and compressed payloads are unchanged. This exercises
both ZipException and
+ // EOFException regressions through the existing sequential, point,
and prefix lookup checks.
Arguments.of(
"/hfile/hudi_1_0_hbase_2_4_9_512KB_GZ_20000.hfile",
20000,
diff --git
a/hudi-io/src/test/resources/hfile/hudi_1_0_hbase_2_4_9_512KB_GZ_20000.hfile
b/hudi-io/src/test/resources/hfile/hudi_1_0_hbase_2_4_9_512KB_GZ_20000.hfile
index 4d35a3ec8868..a40abe4e266b 100644
Binary files
a/hudi-io/src/test/resources/hfile/hudi_1_0_hbase_2_4_9_512KB_GZ_20000.hfile
and
b/hudi-io/src/test/resources/hfile/hudi_1_0_hbase_2_4_9_512KB_GZ_20000.hfile
differ