trevdatastreams's picture
Add ORC stream-column heap OOB PoC
7d19222 verified
|
Raw
History Blame Contribute Delete
4.16 kB

Apache ORC stripe stream column-id heap-buffer-overflow

Status: reproduced on current main; prior-art gate open.

Apache ORC's public C++ Reader::getStripeStatistics() path trusts the attacker-controlled column id of every ROW_INDEX stream in a stripe footer. It uses that id with unchecked std::vector::operator[], then calls push_back() through the out-of-bounds vector object.

A 272-byte ORC file with three valid columns and one mutated stream column id reliably produces an AddressSanitizer heap-buffer-overflow. The control differs by one byte and exits normally.

Tested at Apache ORC commit af4cbf36b051c176f0d13ae5a7ac436ada8aeccf (2026-07-29).

Root cause

ReaderImpl::getStripeStatistics() sizes indexStats from the number of column-statistics entries:

size_t num_cols = contents_->metadata->stripe_stats(stripeIndex).col_stats_size();
std::vector<std::vector<proto::ColumnStatistics>> indexStats(num_cols);
getRowIndexStatistics(..., &indexStats);

getRowIndexStatistics() then trusts stream.column() without checking it against indexStats->size():

size_t column = static_cast<size_t>(stream.column());
for (int j = 0; j < num_entries; j++) {
  const proto::RowIndexEntry& entry = rowIndex.entry(j);
  (*indexStats)[column].push_back(entry.statistics());
}

The same attacker-controlled field is also used without bounds checks in extractReadRangesForStripe() and RowReaderImpl::loadStripeIndex():

selectedColumns[stream.column()]
selectedColumns_[colId]

Differential proof

Fixture Third ROW_INDEX stream column Result
control-valid-column.orc 2 Prints all statistics; exit 0
trigger-column-3.orc 3 ASan heap-buffer-overflow; abort

Both fixtures are 272 bytes. They differ at byte 74 only. Column 3 is exactly one past the three valid columns 0, 1, and 2.

AddressSanitizer reports an 8-byte read eight bytes after the 72-byte vector<vector<ColumnStatistics>> allocation. The stack reaches Reader.cc:834 through the official orc-statistics --withIndex tool. Controls exited 0 and triggers aborted in three of three runs.

Reproduce

Build current Apache ORC with explicit sanitizer flags:

cmake -S orc -B orc-sanitized \
  -DCMAKE_BUILD_TYPE=Debug \
  -DBUILD_JAVA=OFF \
  -DBUILD_CPP_TESTS=OFF \
  -DBUILD_TOOLS=ON \
  -DCMAKE_C_FLAGS="-fsanitize=address,undefined -fno-omit-frame-pointer" \
  -DCMAKE_CXX_FLAGS="-fsanitize=address,undefined -fno-omit-frame-pointer" \
  -DCMAKE_EXE_LINKER_FLAGS="-fsanitize=address,undefined"
cmake --build orc-sanitized --target orc-statistics --parallel 4

Then:

ORC_STATISTICS=/path/to/orc-sanitized/tools/src/orc-statistics ./reproduce.sh

generate_fixture.py materializes both deterministic files and asserts their SHA-256 hashes.

Impact

An application or service that extracts row-index statistics from an untrusted ORC file can be crashed by a one-byte column-id mutation. This is a native out-of-bounds access in a file parser, not allocation pressure.

The adjacent ordinary row-reader paths trust the same field, broadening the set of potentially affected consumers.

Demonstrated impact: deterministic denial of service and native heap out-of-bounds access. No code-execution claim is made.

Suggested remediation

Before every use of a stripe stream's column id:

  • reject stream.column() >= indexStats->size() in getRowIndexStatistics();
  • reject stream.column() >= selectedColumns.size() in extractReadRangesForStripe();
  • reject colId >= selectedColumns_.size() and validate the parallel currentStripeFooter_.columns() lookup in loadStripeIndex().

Return ParseError for malformed stripe metadata and add regression fixtures for column == size, very large column ids, and missing column fields.

Novelty

The exact scan found no Hugging Face or local matches. Apache ORC issue #1475 concerns writer stream ordering, PR #2144 introduced the row-index statistics API, and ORC-2198/ORC-2200 validate byte extents. None reports or fixes attacker-controlled stripe stream column ids indexing these vectors.