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:

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:

rows=1

Expected complete-file trigger:

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

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:

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:

++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.