trevdatastreams's picture
Upload 9 files
3d51c42 verified
|
Raw
History Blame Contribute Delete
3.21 kB
# Apache ORC C++ RLEv2 patch-list heap-buffer-overflow PoC
Tested against Apache ORC commit:
`af4cbf36b051c176f0d13ae5a7ac436ada8aeccf`
## Summary
Apache ORC's C++ RLEv2 decoder accepts a PATCHED_BASE patch-list entry that
means “continue the gap in the next entry” even when that entry is the last
element of the attacker-controlled patch list.
`RleDecoderV2::adjustGapAndPatch()` increments the patch index and immediately
dereferences it without checking the vector length. A one-entry patch list
whose entry encodes `gap == 255 && patch == 0` therefore causes an
eight-byte heap-buffer-overflow read.
The complete 277-byte `.orc` trigger reaches the flaw through the public
`createReader()` / `RowReader::next()` path. The corresponding control differs
in one patch-gap byte and parses one row normally.
## Reproduce
Build prerequisites are the same as Apache ORC C++. Then run:
```bash
ORC_SRC=/path/to/orc bash reproduce.sh
```
The script builds current ORC with AddressSanitizer and
UndefinedBehaviorSanitizer, tests the raw decoder control/trigger, and then
tests the complete ORC control/trigger.
Expected complete-file control:
```text
rows=1
```
Expected complete-file trigger:
```text
ERROR: AddressSanitizer: heap-buffer-overflow
READ of size 8
RleDecoderV2.cc:265 in orc::RleDecoderV2::adjustGapAndPatch(...)
RleDecoderV2.cc:346 in orc::RleDecoderV2::nextPatched<long long>(...)
ColumnReader.cc:966 in orc::ListColumnReader::nextInternal<false>(...)
Reader.cc in orc::RowReaderImpl::next(...)
```
## Fixture integrity
```text
098d1f9d7b64afa2cc15c257b4e8d5b57707f19402dd241ef7b8b26ffb94224d control-valid-patch-list.orc
9fbbd8fdc5c991f55fede3823193f439e1843146d6f5d44e0e4f7ca80859fba2 trigger-unterminated-gap-continuation.orc
```
The two generated ORC files are identical except for byte offset 53:
- control: patch-list bits decode to `gap == 1`
- trigger: patch-list bits decode to `gap == 255 && patch == 0`
Both files have internally consistent stream and stripe lengths.
## Root cause
At `c++/src/RleDecoderV2.cc:253-266`:
```cpp
uint64_t idx = *patchIdx;
uint64_t gap =
static_cast<uint64_t>(unpackedPatch_[idx]) >> patchBitSize;
int64_t patch = unpackedPatch_[idx] & patchMask;
while (gap == 255 && patch == 0) {
actualGap += 255;
++idx;
gap = static_cast<uint64_t>(unpackedPatch_[idx]) >> patchBitSize;
patch = unpackedPatch_[idx] & patchMask;
}
```
The loop does not check `idx < unpackedPatch_.size()` after incrementing it.
## Prior-art distinction
Fresh searches of Apache ORC issues and pull requests for
`adjustGapAndPatch`, PATCHED_BASE patch-list corruption, `gap == 255`, and
RLEv2 heap-buffer-overflow returned no matching disclosure.
ORC-2214 hardens the compressed input cursor in `readByte()`. It does not
validate the decoded patch-list index and does not prevent this heap read.
## Suggested fix
Pass the patch-list length into `adjustGapAndPatch()` and reject an
unterminated gap-continuation entry before the next dereference:
```cpp
++idx;
if (idx >= unpackedPatch_.size()) {
throw ParseError("Corrupt PATCHED_BASE patch list");
}
```
Add a regression test for a one-entry list ending in
`gap == 255 && patch == 0`.