YAML Metadata Warning:empty or missing yaml metadata in repo card
Check out the documentation for more information.
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.