Apache Arrow Parquet DeltaLengthByteArray CWE-770/789 unbounded-resize PoC
Proof-of-concept material for a huntr Model Format Vulnerability report against
apache/arrow (C++ Parquet reader, cpp/src/parquet/decoder.cc).
Files
legit_reference.parquet- a real, valid 391-byte Parquet file written withpyarrow.parquet.write_table(..., column_encoding={"s": "DELTA_LENGTH_BYTE_ARRAY"})(pyarrow 25.0.0), containing 3 short strings ("a","bb","ccc"). Byte offset0x28in this real file (verified by hex-dumping the actual output, not guessed) contains the DeltaBinaryPacked header for the length stream:80 01 04 03 02=block_size=128(varint),num_mini_blocks=4(varint),total_value_count=3(varint, correctly matches the 3 real strings),first_value=1(zigzag varint, matches the length of"a").evil_delta_header.bin- the same 4-field DeltaBinaryPacked header encoding, re-created with the identical verified varint/zigzag format, but withtotal_value_countset to2000000000instead of3:80 01 04 80 a8 d6 b9 07 00(9 bytes total).
The bug
DeltaBitPackDecoder::InitHeader (decoder.cc) validates block_size and
num_mini_blocks (via values_per_block_/mini_blocks_per_block_ checks)
but does not validate total_value_count_ against the actual amount of
data remaining in the page. This count flows directly into
DeltaLengthByteArrayDecoder::DecodeLengths():
buffered_length_->Resize(num_length * sizeof(int32_t)) - executed before
the individual lengths are read/validated. The same unguarded pattern is
duplicated for DELTA_BYTE_ARRAY prefixes.
Reachable from the ordinary per-page decoder dispatch in column_reader.cc -
encoding is read directly from the (attacker-controlled) page header, so
creating a file with a DELTA_LENGTH_BYTE_ARRAY-encoded column is a normal,
documented write path (as legit_reference.parquet demonstrates), not an
exotic one.
Honest limits (not swept under the rug)
evil_delta_header.bin is the isolated encoded-header fragment, byte-for-byte
consistent with the real encoding verified against legit_reference.parquet -
it is not a complete, self-consistent .parquet container (splicing it
into a real file changes the page's byte length, which would also require
recomputing the Thrift-encoded compressed_page_size/uncompressed_page_size
fields that precede it - a full working end-to-end file was not built for
this report). This report's confidence rests on direct reading of
decoder.cc/column_reader.cc in a fresh clone, cross-checked by an
independent blind-adversarial review (separate clone, neutral prompt, told to
actively look for reasons this is NOT a bug) that reached the same
conclusion and narrowed the practical ceiling to ~8.5GB (not the full ~17GB
the uint32 field could nominally address, because int/size_t promotion
raises an exception above INT_MAX rather than silently wrapping) - a DoS/OOM
class finding, not memory corruption.
Impact: Denial of Service - an attacker who can supply or influence a
.parquet file with a DELTA_LENGTH_BYTE_ARRAY or DELTA_BYTE_ARRAY
encoded column can force a large (up to ~8.5GB), disproportionate buffer
resize from a page declaring a small amount of real data.