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:
```cpp
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()`:
```cpp
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()`:
```cpp
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:
```bash
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:
```bash
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.