You need to agree to share your contact information to access this model

This repository is publicly accessible, but you have to accept the conditions to access its files and content.

Log in or Sign Up to review the conditions and access this model content.

YAML Metadata Warning:empty or missing yaml metadata in repo card

Check out the documentation for more information.

apache/orc C++ reader: integer overflow in DictionaryLoader leads to heap out-of-bounds write

Integer overflow (CWE-190) of the attacker-controlled dictionary_size field in apache/orc's C++ core reader leads to a zero-capacity heap allocation followed by an out-of-bounds write (CWE-787) while loading a string-dictionary-encoded column from a crafted .orc file.

  • Component: apache/orc, C++ core reader (liborc)
  • File: c++/src/DictionaryLoader.cc, function orc::loadStringDictionary
  • Format: ORC (Optimized Row Columnar)
  • Class: CWE-190 (Integer Overflow or Wraparound) leading to CWE-787 (Out-of-bounds Write)
  • Impact: heap out-of-bounds write when a consumer opens/reads an attacker-supplied .orc file

Root cause

loadStringDictionary (added 2025-11-05, commit cf85da12, JIRA ORC-2022) reads the dictionary size straight from the stripe-footer ColumnEncoding protobuf message with no upper-bound validation:

// c++/src/DictionaryLoader.cc  (main)
uint32_t dictSize = encoding.dictionary_size();          // line 50  attacker-controlled uint32

std::unique_ptr<SeekableInputStream> stream =
    stripe.getStream(columnId, proto::Stream_Kind_LENGTH, false);
if (dictSize > 0 && stream == nullptr) { ... }           // line 58  only a null-stream check

dictionary->dictionaryOffset.resize(dictSize + 1);       // line 67  dictSize + 1 wraps at 0xFFFFFFFF
int64_t* lengthArray = dictionary->dictionaryOffset.data();
lengthDecoder->next(lengthArray + 1, dictSize, nullptr); // line 69  writes dictSize int64 values

dictionaryOffset is orc::DataBuffer<int64_t> (c++/include/orc/Vector.hh:237).

dictSize is uint32_t. When an attacker sets ColumnEncoding.dictionary_size (proto field 2) to 0xFFFFFFFF, the expression dictSize + 1 is computed in uint32_t and wraps around to 0. dictionaryOffset.resize(0) therefore allocates a zero-capacity buffer. Line 69 then decodes dictSize (~4.29 billion) int64_t values from the LENGTH stream into lengthArray + 1, writing past the zero-capacity allocation on the very first decoded value.

The caller StringDictionaryColumnReader (c++/src/ColumnReader.cc:547) passes the untrusted dictSize through with no validation of its own.

Reachability

dictionary_size is a field of the ColumnEncoding message stored in each stripe's footer. Any consumer that opens a crafted .orc file and reads a StringDictionary (DICTIONARY / DICTIONARY_V2) encoded string column reaches loadStringDictionary, which is triggered during normal column-reader initialization. No special API use is required beyond reading the file.

malicious_column_encoding.bin in this repo is the exact proto2 wire encoding of the malicious ColumnEncoding (kind = DICTIONARY_V2, dictionary_size = 0xFFFFFFFF, hex 0803 10ff ffff ff0f) that a crafted .orc embeds in its stripe footer. craft_encoding.py regenerates it and documents the field layout.

Proof of concept

Because a full apache/orc build vendors protobuf/snappy/zstd/zlib via ExternalProject, this PoC uses a faithful standalone AddressSanitizer harness that compiles the real apache/orc allocator verbatim (c++/src/MemoryPool.cc at main, providing the genuine orc::DataBuffer<int64_t> resize/reserve/MemoryPoolImpl::malloc) and executes the exact vulnerable statements of loadStringDictionary (lines 50, 67-69) driven by the attacker-controlled dictionary_size = 0xFFFFFFFF.

build/harness.cc reproduces resize(dictSize + 1) on a real DataBuffer<int64_t> and then calls the length-stream decode (next(lengthArray + 1, dictSize, ...)). The RLE next() stand-in writes numValues sequential int64 values exactly as ORC's RleDecoderV1::next does when filling a caller-provided buffer; only the first write is needed to prove the overflow.

Build and run

cd build
./build_and_run.sh

(Requires clang++ or g++ with AddressSanitizer and curl. The script fetches the three real ORC source files it compiles verbatim.)

Observed AddressSanitizer output

See asan_output.txt. Key lines:

[*] attacker dictionary_size = 0xffffffff (4294967295)
[*] dictSize + 1 (uint32_t) = 0  <-- integer overflow to 0
[*] dictionaryOffset.size()=0 capacity=0 (zero-capacity buffer)

==ERROR: AddressSanitizer: heap-buffer-overflow ... WRITE of size 8 ...
0x... is located 7 bytes after 1-byte region
allocated by thread T0 here:
    #1 orc::MemoryPoolImpl::malloc(unsigned long)  MemoryPool.cc:44
    #2 orc::DataBuffer<long>::reserve(unsigned long) MemoryPool.cc:122
    #3 orc::DataBuffer<long>::DataBuffer(...)        MemoryPool.cc:59

The write of size 8 (one int64_t) lands past a heap region allocated by ORC's own MemoryPoolImpl::malloc / DataBuffer<int64_t> code, confirming the heap out-of-bounds write in the real allocation path.

Distinctness

This is a different file and different mechanism from the separately reported apache/orc RleDecoderV2::adjustGapAndPatch RLEv2 out-of-bounds READ. This report is a DictionaryLoader.cc dictionary_size integer-overflow leading to an out-of-bounds WRITE.

The only published ORC CVEs are CVE-2018-8015 (type-parsing recursion / DoS) and CVE-2025-47436 (LZO decompression). Neither touches DictionaryLoader.cc, which was added on 2025-11-05 (ORC-2022) and is not in OSS-Fuzz.

Suggested fix

Validate dictSize against a sane bound before use, and compute the + 1 in a 64-bit type, e.g.:

uint64_t dictSize = encoding.dictionary_size();
// reject implausible sizes and avoid 32-bit wraparound
dictionary->dictionaryOffset.resize(dictSize + 1);

plus an upper-bound check tied to the available LENGTH-stream data.

Files

  • build/harness.cc - faithful ASan harness driving the real vulnerable path
  • build/MemoryPool.cc, build/orc/*.hh - real apache/orc allocator, compiled verbatim
  • build/build_and_run.sh - build + run script
  • asan_output.txt - captured AddressSanitizer heap-buffer-overflow WRITE
  • craft_encoding.py - generates the malicious ColumnEncoding payload
  • malicious_column_encoding.bin - the attacker-controlled stripe-footer bytes
Downloads last month

-

Downloads are not tracked for this model. How to track
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support