Skip to content

Commit d92d943

Browse files
committed
ORC-2191: Reject overflowing PostScript tail lengths in ReaderImpl
### What changes were proposed in this pull request? This PR validates the PostScript tail lengths in `ReaderImpl.extractFileTail` before they are cast from `long` to `int`, throwing `FileFormatException` when `footerLength`, `metadataLength`, `stripeStatisticsLength`, or their sum is negative or exceeds the file length / `Integer.MAX_VALUE`. It is the Java counterpart of the C++ overflow checks in ORC-2167. ### Why are the changes needed? These fields are protobuf `uint64` read as Java `long`. A malformed value near `UINT64_MAX` or above 2GB would truncate/overflow on the `int` cast into a bogus offset or allocation size instead of a clear error. Per the ORC threat model this is a fail-fast robustness fix, not a CVE. ### How was this patch tested? Added `TestReaderImpl.testMalformedTailLengthOverflow`, which crafts files with tail lengths of `2^31`, `2^32`, and `UINT64_MAX` and asserts `OrcFile.createReader` throws `FileFormatException`. ### Was this patch authored or co-authored using generative AI tooling? Generated-by: Claude Fable 5 Closes #2672 from dongjoon-hyun/ORC-2191. Authored-by: Dongjoon Hyun <dongjoon@apache.org> Signed-off-by: Dongjoon Hyun <dongjoon@apache.org>
1 parent 6015de5 commit d92d943

2 files changed

Lines changed: 88 additions & 5 deletions

File tree

java/core/src/java/org/apache/orc/impl/ReaderImpl.java

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -854,13 +854,38 @@ protected OrcTail extractFileTail(FileSystem fs, Path path,
854854
CompressionKind.valueOf(ps.getCompression().name());
855855
fileTailBuilder.setPostscriptLength(psLen).setPostscript(ps);
856856

857-
int footerSize = (int) ps.getFooterLength();
858-
int metadataSize = (int) ps.getMetadataLength();
859-
int stripeStatSize = (int) ps.getStripeStatisticsLength();
857+
long footerLength = ps.getFooterLength();
858+
long metadataLength = ps.getMetadataLength();
859+
long stripeStatLength = ps.getStripeStatisticsLength();
860+
861+
// Reject malformed tail lengths before narrowing to int, symmetric with the
862+
// C++ overflow-safe checks added in ORC-2167. A crafted PostScript length near
863+
// UINT64_MAX (read as a negative long) or above 2GB would otherwise truncate or
864+
// overflow into a bogus seek offset or ByteBuffer allocation size.
865+
if (footerLength < 0 || footerLength > Integer.MAX_VALUE ||
866+
metadataLength < 0 || metadataLength > Integer.MAX_VALUE ||
867+
stripeStatLength < 0 || stripeStatLength > Integer.MAX_VALUE) {
868+
throw new FileFormatException("Malformed ORC file " + path
869+
+ ". Invalid tail length. fileLength=" + size
870+
+ ", postscriptLength=" + psLen + ", footerLength=" + footerLength
871+
+ ", metadataLength=" + metadataLength
872+
+ ", stripeStatisticsLength=" + stripeStatLength);
873+
}
874+
875+
int footerSize = (int) footerLength;
876+
int metadataSize = (int) metadataLength;
877+
int stripeStatSize = (int) stripeStatLength;
860878

861879
//check if extra bytes need to be read
862-
int tailSize = 1 + psLen + footerSize + metadataSize + stripeStatSize;
863-
int extra = Math.max(0, tailSize - readSize);
880+
long tailSize = 1L + psLen + footerSize + metadataSize + stripeStatSize;
881+
if (tailSize > size || tailSize > Integer.MAX_VALUE) {
882+
throw new FileFormatException("Malformed ORC file " + path
883+
+ ". Invalid tail size " + tailSize + " for file of length " + size
884+
+ " (postscriptLength=" + psLen + ", footerLength=" + footerSize
885+
+ ", metadataLength=" + metadataSize
886+
+ ", stripeStatisticsLength=" + stripeStatSize + ")");
887+
}
888+
int extra = Math.max(0, (int) tailSize - readSize);
864889
if (extra > 0) {
865890
//more bytes need to be read, seek back to the right place and read extra bytes
866891
BufferChunk orig = buffer;

java/core/src/test/org/apache/orc/impl/TestReaderImpl.java

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,64 @@ private static byte[] composeInvalidCompressionBlockSizeFile(long blockSize) {
172172
return buf.array();
173173
}
174174

175+
@Test
176+
public void testMalformedTailLengthOverflow() throws Exception {
177+
Path tmpDir = new Path(System.getProperty("test.tmp.dir",
178+
"target/test/tmp"));
179+
FileSystem fs = tmpDir.getFileSystem(conf);
180+
// Each triple sets one of footer/metadata/stripeStatistics length to a value
181+
// that would overflow or truncate when narrowed to int: 2^31 (negative int),
182+
// 2^32 (truncates to 0), and -1L (UINT64_MAX as read from protobuf uint64).
183+
long[][] cases = new long[][]{
184+
{1L << 31, 0, 0},
185+
{1L << 32, 0, 0},
186+
{-1L, 0, 0},
187+
{0, 1L << 31, 0},
188+
{0, -1L, 0},
189+
{0, 0, 1L << 31},
190+
{0, 0, -1L},
191+
};
192+
for (long[] c : cases) {
193+
byte[] fileBytes = composeMalformedTailFile(c[0], c[1], c[2]);
194+
Path tmpPath = new Path(tmpDir,
195+
"malformed-tail-" + c[0] + "-" + c[1] + "-" + c[2] + ".orc");
196+
try (FSDataOutputStream out = fs.create(tmpPath, true)) {
197+
out.write(fileBytes);
198+
}
199+
try {
200+
FileFormatException e = assertThrows(FileFormatException.class, () ->
201+
OrcFile.createReader(tmpPath, OrcFile.readerOptions(conf)).close());
202+
assertTrue(e.getMessage().contains("Malformed ORC file"),
203+
"Unexpected message: " + e.getMessage());
204+
assertTrue(e.getMessage().contains("tail"),
205+
"Unexpected message: " + e.getMessage());
206+
} finally {
207+
fs.delete(tmpPath, false);
208+
}
209+
}
210+
}
211+
212+
private static byte[] composeMalformedTailFile(long footerLength,
213+
long metadataLength, long stripeStatLength) {
214+
// Valid compressionBlockSize so the failure is triggered by the tail-length
215+
// check rather than the compression-block-size check.
216+
byte[] psBytes = OrcProto.PostScript.newBuilder()
217+
.setFooterLength(footerLength)
218+
.setCompression(OrcProto.CompressionKind.NONE)
219+
.setCompressionBlockSize(1 << 16)
220+
.addVersion(0).addVersion(12)
221+
.setMetadataLength(metadataLength)
222+
.setStripeStatisticsLength(stripeStatLength)
223+
.setWriterVersion(OrcFile.CURRENT_WRITER.getId())
224+
.setMagic(OrcFile.MAGIC)
225+
.build().toByteArray();
226+
ByteBuffer buf = ByteBuffer.allocate(3 + psBytes.length + 1);
227+
buf.put(OrcFile.MAGIC.getBytes(StandardCharsets.UTF_8));
228+
buf.put(psBytes);
229+
buf.put((byte) psBytes.length);
230+
return buf.array();
231+
}
232+
175233
@Test
176234
public void testOptionSafety() throws IOException {
177235
Reader.Options options = new Reader.Options();

0 commit comments

Comments
 (0)