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