This is an automated email from the ASF dual-hosted git repository.
JingsongLi pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/paimon.git
The following commit(s) were added to refs/heads/master by this push:
new 1d5cc316db [format] Fail fast when skipping DELTA_LENGTH_BYTE_ARRAY
data past end of page (#9571)
1d5cc316db is described below
commit 1d5cc316db12acec719bae6130b81c17b8a50dca
Author: YangJie <[email protected]>
AuthorDate: Fri Sep 4 03:24:32 2026 -0400
[format] Fail fast when skipping DELTA_LENGTH_BYTE_ARRAY data past end of
page (#9571)
---
.../VectorizedDeltaLengthByteArrayReader.java | 13 ++++++--
.../reader/DeltaLengthByteArrayEncodingTest.java | 35 ++++++++++++++++++++++
2 files changed, 45 insertions(+), 3 deletions(-)
diff --git
a/paimon-format/src/main/java/org/apache/paimon/format/parquet/reader/VectorizedDeltaLengthByteArrayReader.java
b/paimon-format/src/main/java/org/apache/paimon/format/parquet/reader/VectorizedDeltaLengthByteArrayReader.java
index 97df39ac57..cab5cfc971 100644
---
a/paimon-format/src/main/java/org/apache/paimon/format/parquet/reader/VectorizedDeltaLengthByteArrayReader.java
+++
b/paimon-format/src/main/java/org/apache/paimon/format/parquet/reader/VectorizedDeltaLengthByteArrayReader.java
@@ -81,9 +81,16 @@ public class VectorizedDeltaLengthByteArrayReader extends
VectorizedReaderBase
@Override
public void skipBinary(int total) {
for (int i = 0; i < total; i++) {
- int remaining = lengthsVector.getInt(currentRow + i);
- while (remaining > 0) {
- remaining -= in.skip(remaining);
+ int length = lengthsVector.getInt(currentRow + i);
+ // A corrupt length can be negative, and skipFully would then move
the stream
+ // backwards instead of forwards.
+ if (length < 0) {
+ throw new ParquetDecodingException("Negative byte array
length: " + length);
+ }
+ try {
+ in.skipFully(length);
+ } catch (IOException e) {
+ throw new ParquetDecodingException("Failed to skip " + length
+ " bytes", e);
}
}
currentRow += total;
diff --git
a/paimon-format/src/test/java/org/apache/paimon/format/parquet/reader/DeltaLengthByteArrayEncodingTest.java
b/paimon-format/src/test/java/org/apache/paimon/format/parquet/reader/DeltaLengthByteArrayEncodingTest.java
index f6cd6e891b..d08efc9a20 100644
---
a/paimon-format/src/test/java/org/apache/paimon/format/parquet/reader/DeltaLengthByteArrayEncodingTest.java
+++
b/paimon-format/src/test/java/org/apache/paimon/format/parquet/reader/DeltaLengthByteArrayEncodingTest.java
@@ -25,16 +25,21 @@ import org.apache.commons.lang3.RandomStringUtils;
import org.apache.parquet.bytes.ByteBufferInputStream;
import org.apache.parquet.bytes.DirectByteBufferAllocator;
import org.apache.parquet.column.values.Utils;
+import
org.apache.parquet.column.values.delta.DeltaBinaryPackingValuesWriterForInteger;
import
org.apache.parquet.column.values.deltalengthbytearray.DeltaLengthByteArrayValuesWriter;
+import org.apache.parquet.io.ParquetDecodingException;
import org.apache.parquet.io.api.Binary;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
import java.util.Random;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
/** Test for delta length byte array encoding. */
public class DeltaLengthByteArrayEncodingTest {
@@ -127,6 +132,36 @@ public class DeltaLengthByteArrayEncodingTest {
}
}
+ @Test
+ public void testSkipPastEndOfPage() throws Exception {
+ writeData(writer, values);
+ byte[] page = writer.getBytes().toByteArray();
+ // Drop three bytes of data. The lengths at the front of the page
still add up to
+ // more than the page holds, which is what a truncated page looks like.
+ byte[] truncated = Arrays.copyOf(page, page.length - 3);
+ reader.initFromPage(values.length,
ByteBufferInputStream.wrap(ByteBuffer.wrap(truncated)));
+
+ ParquetDecodingException e =
+ assertThrows(
+ ParquetDecodingException.class, () ->
reader.skipBinary(values.length));
+ assertEquals("Failed to skip " + values[2].length() + " bytes",
e.getMessage());
+ }
+
+ @Test
+ public void testSkipNegativeLength() throws Exception {
+ // A length section that decodes to a negative value cannot be
produced by the
+ // writer, so write one directly and append no data at all.
+ DeltaBinaryPackingValuesWriterForInteger lengthWriter =
+ new DeltaBinaryPackingValuesWriterForInteger(
+ 64 * 1024, 64 * 1024, new DirectByteBufferAllocator());
+ lengthWriter.writeInteger(-1);
+ reader.initFromPage(1, lengthWriter.getBytes().toInputStream());
+
+ ParquetDecodingException e =
+ assertThrows(ParquetDecodingException.class, () ->
reader.skipBinary(1));
+ assertEquals("Negative byte array length: -1", e.getMessage());
+ }
+
private void writeData(DeltaLengthByteArrayValuesWriter writer, String[]
values) {
for (String value : values) {
writer.writeBytes(Binary.fromString(value));