You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@orc.apache.org by do...@apache.org on 2026/07/09 06:26:41 UTC
(orc) branch branch-2.3 updated: ORC-2200: Validate stripe offset/index/data/footer lengths in the Java reader
This is an automated email from the ASF dual-hosted git repository.
dongjoon-hyun pushed a commit to branch branch-2.3
in repository https://gitbox.apache.org/repos/asf/orc.git
The following commit(s) were added to refs/heads/branch-2.3 by this push:
new d04ccf8d9 ORC-2200: Validate stripe offset/index/data/footer lengths in the Java reader
d04ccf8d9 is described below
commit d04ccf8d9823d02114415cca899dcb7301dddd23
Author: Dongjoon Hyun <do...@apache.org>
AuthorDate: Wed Jul 8 23:26:24 2026 -0700
ORC-2200: Validate stripe offset/index/data/footer lengths in the Java reader
### What changes were proposed in this pull request?
This PR aims to validate stripe `offset`/`indexLength`/`dataLength`/`footerLength` in the Java reader before reading a stripe, like the C++ reader (`RowReaderImpl::startNextStripe`). `RecordReaderImpl` now throws `FileFormatException` if any value is negative, `footerLength` exceeds `Integer.MAX_VALUE`, or the sum overflows or exceeds the file length.
### Why are the changes needed?
A crafted `StripeInformation` could cause a `NegativeArraySizeException`, a huge allocation, or a read at a bogus offset. This matches the existing C++ reader validation.
### How was this patch tested?
Pass the CIs with the newly added test case.
### Was this patch authored or co-authored using generative AI tooling?
Generated-by: Claude Fable 5
Closes #2683 from dongjoon-hyun/ORC-2200.
Authored-by: Dongjoon Hyun <do...@apache.org>
Signed-off-by: Dongjoon Hyun <do...@apache.org>
(cherry picked from commit 1c441dc9a68f94071c8ecac622b9eefd99d81153)
Signed-off-by: Dongjoon Hyun <do...@apache.org>
---
.../java/org/apache/orc/impl/RecordReaderImpl.java | 37 +++++++++++++++++
.../test/org/apache/orc/impl/TestReaderImpl.java | 46 ++++++++++++++++++++++
2 files changed, 83 insertions(+)
diff --git a/java/core/src/java/org/apache/orc/impl/RecordReaderImpl.java b/java/core/src/java/org/apache/orc/impl/RecordReaderImpl.java
index 34fd415bb..34ffe4520 100644
--- a/java/core/src/java/org/apache/orc/impl/RecordReaderImpl.java
+++ b/java/core/src/java/org/apache/orc/impl/RecordReaderImpl.java
@@ -36,6 +36,7 @@ import org.apache.orc.DataReader;
import org.apache.orc.DateColumnStatistics;
import org.apache.orc.DecimalColumnStatistics;
import org.apache.orc.DoubleColumnStatistics;
+import org.apache.orc.FileFormatException;
import org.apache.orc.IntegerColumnStatistics;
import org.apache.orc.OrcConf;
import org.apache.orc.OrcFile;
@@ -89,6 +90,7 @@ public class RecordReaderImpl implements RecordReader {
.setBytesOnDisk(0)
.build();
protected final Path path;
+ private final long fileLength;
private final long firstRow;
private final List<StripeInformation> stripes = new ArrayList<>();
private OrcProto.StripeFooter stripeFooter;
@@ -233,6 +235,7 @@ public class RecordReaderImpl implements RecordReader {
LOG.debug("noSelectedVector={}", this.noSelectedVector);
this.schema = evolution.getReaderSchema();
this.path = fileReader.path;
+ this.fileLength = fileReader.getFileTail().getFileLength();
this.rowIndexStride = fileReader.rowIndexStride;
boolean ignoreNonUtf8BloomFilter =
OrcConf.IGNORE_NON_UTF8_BLOOM_FILTERS.getBoolean(fileReader.conf);
@@ -1331,8 +1334,42 @@ public class RecordReaderImpl implements RecordReader {
}
}
+ /**
+ * Reject a malformed StripeInformation whose offset/index/data/footer lengths
+ * are negative (protobuf uint64 values above Long.MAX_VALUE read as negative
+ * longs), whose footerLength would overflow the int cast and ByteBuffer
+ * allocation in DataReader.readStripeFooter, or whose extents overflow or
+ * exceed the file length, mirroring the C++ RowReaderImpl::startNextStripe
+ * checks.
+ */
+ static void validateStripeInformation(StripeInformation stripe, long fileLength, Path path)
+ throws FileFormatException {
+ long offset = stripe.getOffset();
+ long indexLength = stripe.getIndexLength();
+ long dataLength = stripe.getDataLength();
+ long footerLength = stripe.getFooterLength();
+ boolean malformed = offset < 0 || indexLength < 0 || dataLength < 0 ||
+ footerLength < 0 || footerLength > Integer.MAX_VALUE;
+ if (!malformed) {
+ try {
+ long total = Math.addExact(Math.addExact(Math.addExact(offset, indexLength),
+ dataLength), footerLength);
+ malformed = total >= fileLength;
+ } catch (ArithmeticException e) {
+ malformed = true;
+ }
+ }
+ if (malformed) {
+ throw new FileFormatException("Malformed ORC file " + path
+ + ". Invalid stripe offset/length. fileLength=" + fileLength
+ + ", offset=" + offset + ", indexLength=" + indexLength
+ + ", dataLength=" + dataLength + ", footerLength=" + footerLength);
+ }
+ }
+
private StripeInformation beginReadStripe() throws IOException {
StripeInformation stripe = stripes.get(currentStripe);
+ validateStripeInformation(stripe, fileLength, path);
stripeFooter = readStripeFooter(stripe);
clearStreams();
// setup the position in the stripe
diff --git a/java/core/src/test/org/apache/orc/impl/TestReaderImpl.java b/java/core/src/test/org/apache/orc/impl/TestReaderImpl.java
index e3f4cd630..665162461 100644
--- a/java/core/src/test/org/apache/orc/impl/TestReaderImpl.java
+++ b/java/core/src/test/org/apache/orc/impl/TestReaderImpl.java
@@ -41,6 +41,7 @@ import org.apache.orc.OrcProto;
import org.apache.orc.OrcUtils;
import org.apache.orc.Reader;
import org.apache.orc.RecordReader;
+import org.apache.orc.StripeInformation;
import org.apache.orc.StripeStatistics;
import org.apache.orc.TestConf;
import org.apache.orc.TestVectorOrcFile;
@@ -230,6 +231,51 @@ public class TestReaderImpl implements TestConf {
return buf.array();
}
+ @Test
+ public void testMalformedStripeLength() throws Exception {
+ long fileLength = 1000;
+ // A valid stripe within the file length is accepted.
+ RecordReaderImpl.validateStripeInformation(
+ composeStripeInformation(3, 10, 20, 30), fileLength, path);
+ // Each quadruple is a malformed (offset, indexLength, dataLength,
+ // footerLength): a footerLength that would truncate or go negative when
+ // narrowed to int (2^31, 2^32), uint64 values above Long.MAX_VALUE read as
+ // negative longs (-1L), a sum that overflows long, and extents past the
+ // file length.
+ long[][] cases = new long[][]{
+ {0, 0, 0, 1L << 31},
+ {0, 0, 0, 1L << 32},
+ {0, 0, 0, -1L},
+ {-1L, 0, 0, 0},
+ {0, -1L, 0, 0},
+ {0, 0, -1L, 0},
+ {1L << 62, 0, 1L << 62, 0},
+ {900, 0, 0, 200},
+ };
+ for (long[] c : cases) {
+ FileFormatException e = assertThrows(FileFormatException.class, () ->
+ RecordReaderImpl.validateStripeInformation(
+ composeStripeInformation(c[0], c[1], c[2], c[3]), fileLength, path));
+ assertTrue(e.getMessage().contains("Malformed ORC file"),
+ "Unexpected message: " + e.getMessage());
+ assertTrue(e.getMessage().contains("Invalid stripe offset/length"),
+ "Unexpected message: " + e.getMessage());
+ }
+ }
+
+ private static StripeInformation composeStripeInformation(long offset,
+ long indexLength, long dataLength, long footerLength) {
+ return new ReaderImpl.StripeInformationImpl(
+ OrcProto.StripeInformation.newBuilder()
+ .setOffset(offset)
+ .setIndexLength(indexLength)
+ .setDataLength(dataLength)
+ .setFooterLength(footerLength)
+ .setNumberOfRows(0)
+ .build(),
+ 0, 0, null);
+ }
+
@Test
public void testOptionSafety() throws IOException {
Reader.Options options = new Reader.Options();