You need to agree to share your contact information to access this model

This repository is publicly accessible, but you have to accept the conditions to access its files and content.

Log in or Sign Up to review the conditions and access this model content.

YAML Metadata Warning:empty or missing yaml metadata in repo card

Check out the documentation for more information.

Apache ORC C++ row-index entry-count PoC

Tested against Apache ORC commit:

af4cbf36b051c176f0d13ae5a7ac436ada8aeccf

Summary

The C++ row reader accepts a syntactically valid RowIndex protobuf without checking that it contains an entry for every row group. A subsequent public RowReader::seekToRow(0) call indexes entry zero unconditionally.

The 272-byte trigger changes one byte in an otherwise valid ORC file. It changes column zero's repeated RowIndex.entry tag (0x0a) into an unknown length-delimited field (0x12). Protobuf accepts the stream, but the parsed RowIndex has zero entries.

In a release-style sanitizer build, seeking the trigger produces a null-reference UBSan error followed by an AddressSanitizer invalid read at address 0x18. The unmodified 272-byte control seeks and reads one row normally.

Reproduce

Build prerequisites are the same as Apache ORC C++. Then run:

ORC_SRC=/path/to/orc ./reproduce.sh

The script builds current ORC with RelWithDebInfo, AddressSanitizer, and UndefinedBehaviorSanitizer before running both fixtures.

Expected control:

seeking row 0
seek completed
next=1 rows=1

Expected trigger:

repeated_ptr_field.h:292:12: runtime error: reference binding to null pointer
ERROR: AddressSanitizer: SEGV on unknown address 0x000000000018
The signal is caused by a READ memory access.

A debug build instead terminates at Protobuf's bounds assertion:

Check failed: index < current_size_ (0 vs. 0)

Fixtures

94024b8a77d819409445e6664affa64950003509a0366ef8c19aa8d3ea1e92f8  control-one-row-index-entry.orc
e9d4947a068c6b6643cd22f2016a19ec654fd873bedcec929b9d1ed7a63c8b38  trigger-zero-row-index-entries.orc

The generator changes only byte offset 3. Stream lengths, stripe offsets, file length, and all other metadata remain identical.

Root cause

At c++/src/Reader.cc:542-547, a parsed row index is stored without validating its entry count:

proto::RowIndex rowIndex;
if (!parseProtobufFromStream(&rowIndex, inStream.get())) {
  throw ParseError("Failed to parse the row index");
}
rowIndexes_[colId] = rowIndex;

At c++/src/Reader.cc:573-576, the row-group ID is used without checking rowGroupEntryId < rowIndex->second.entry_size():

const proto::RowIndexEntry& entry =
    rowIndex->second.entry(static_cast<int32_t>(rowGroupEntryId));

Prior-art distinction

ORC-990 / PR #903 also mentions seekToRowGroup, but it fixes MSVC iterator invalidation while building position providers. It does not validate attacker-controlled RowIndex.entry counts.

The separate row-index column-ID issue indexes the rowIndexes_ destination with an unvalidated stream column. This PoC uses a valid column ID and instead demonstrates an empty entry array consumed without a bounds check.

Suggested fix

Reject a parsed row index whose entry count does not cover the stripe's declared row groups. As defense in depth, check the requested row-group ID against every loaded row index before calling entry(), and throw ParseError for a malformed file.

Downloads last month

-

Downloads are not tracked for this model. How to track
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support