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.

PoC: Unbounded allocation from Parquet footer row_groups list count (apache/arrow-rs parquet crate)

Status: gated, access-controlled proof-of-concept for authorized security research / bug-bounty triage only.

Summary

The parquet crate in apache/arrow-rs (current main, crate version 59.1.0, commit 6035e287adcc3f1a574c6438b4b610faded2d9b6) aborts the process with an unrecoverable allocation failure when parsing a maliciously crafted Parquet file footer. The custom Thrift-compact decoder used for footer/metadata parsing reads the declared element count of the row_groups list directly from attacker-controlled bytes and immediately calls Vec::with_capacity(list_ident.size as usize) before validating that the declared count is remotely plausible given the actual size of the input.

A 490-byte crafted .parquet file (poc_row_groups_alloc_bomb.parquet in this repo) declares row_groups.size = 195,932,266. Because RowGroupMetaData is ~96 bytes, this drives an immediate ~18.8 GB allocation request, which fails and aborts the process via Rust's global allocator error handler (std::alloc::handle_alloc_error β†’ abort()).

This is a memory-exhaustion / denial-of-service vulnerability: opening a tiny, untrusted Parquet file (as any service that accepts user-uploaded Parquet data would do) crashes the process outright, or β€” on a host with enough RAM/overcommit to satisfy the allocation β€” reserves tens of gigabytes from a few hundred bytes of input, which can OOM-kill co-located processes/containers. Because this happens inside Rust's allocator error hook rather than as a panic!, it cannot be caught with std::panic::catch_unwind, regardless of the consuming application's panic strategy.

Where

  • Repo: https://github.com/apache/arrow-rs
  • File: parquet/src/file/metadata/thrift/mod.rs
  • Function: parquet_metadata_from_bytes (field id 4, row_groups)
  • Vulnerable line:
    let list_ident = prot.read_list_begin()?;
    // check for list of struct
    validate_list_type(ElementType::Struct, &list_ident)?;
    let mut rg_vec = Vec::with_capacity(list_ident.size as usize);   // <-- unvalidated
    
  • list_ident.size is an i32 read straight off the wire in parquet/src/parquet_thrift.rs::read_list_begin() (varint-decoded, capped only at i32::MAX, i.e. up to ~2.1 billion) β€” there is no check against the remaining bytes in the buffer before the count is used to size an allocation.
  • The exact same unvalidated pattern (Vec::with_capacity(list_ident.size as usize) immediately after read_list_begin()/validate_list_type(), with no bound check) also appears in:
    • parquet/src/parquet_thrift.rs::read_thrift_vec() β€” the generic Thrift list reader used for schema, key_value_metadata, column_orders, and other Thrift lists throughout metadata parsing.
    • parquet/src/file/page_index/offset_index.rs:94 β€” page_locations list when decoding the Parquet page index (offset index). These two additional call sites were not independently fuzzed/PoC'd in this pass (only the row_groups path was confirmed with a live crash) but they share the identical unbounded-allocation-from-attacker-controlled-count pattern and are flagged here for the maintainers' awareness as the same root-cause class.

How it was found

Coverage-guided fuzzing with AFL++ (cargo-afl / afl.rs), 3 parallel instances (-M main + 2x -S), ~10-20 min wall-clock, driving the real, unmodified parquet crate straight from a fresh clone of apache/arrow-rs main via a Cargo path dependency (no source patching). Build flags: overflow-checks = true, debug-assertions = true, panic = "abort", release opt-level 1, so any panic or allocator abort is caught by AFL as a crash. Seeds: a handful of small valid .parquet files from parquet-testing and the arrow-rs test suite. The campaign found this same allocation-failure root cause independently 5 times across all 3 fuzzer instances (different declared sizes: ~18.8GB, ~22.8GB, ~25.4GB, ~25.8GB, ~45.6GB β€” same code path, same bug).

The harness (harness_main.rs in this repo) calls ParquetMetaDataReader::new().with_page_index_policy(PageIndexPolicy::Optional).try_parse(&bytes) directly on the fuzzer input, i.e. it exercises exactly the footer + row-group

  • column-chunk metadata parsing path.

Real-world confirmation (not just the harness)

The crash was independently reproduced against the crate's own shipped, unmodified parquet-read CLI binary (built with cargo build --release --bin parquet-read --features cli, no fuzzing harness or AFL instrumentation involved):

$ ./target/release/parquet-read poc_row_groups_alloc_bomb.parquet
memory allocation of 18809497536 bytes failed
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
[process aborts, exit code 134 / SIGABRT]

This confirms the bug is reachable through the crate's standard, documented API surface (ParquetMetaDataReader / SerializedFileReader), not an artifact of the fuzz harness.

Reproduction

git clone --depth 1 https://github.com/apache/arrow-rs.git
cd arrow-rs/parquet
cargo build --release --bin parquet-read --features cli
./target/release/parquet-read /path/to/poc_row_groups_alloc_bomb.parquet
# -> "memory allocation of 18809497536 bytes failed", process aborts (SIGABRT)

Or directly against the metadata reader:

let bytes = std::fs::read("poc_row_groups_alloc_bomb.parquet").unwrap();
let bytes = bytes::Bytes::from(bytes);
let _ = parquet::file::metadata::ParquetMetaDataReader::new()
    .parse_and_finish(&bytes); // aborts the process

ASan note

AFL++'s Rust bindings (afl.rs) do not currently support combining the bundled AFL LLVM coverage runtime with -Z sanitizer=address + -Z build-std in this toolchain without a duplicate lang item link conflict, so this campaign relied on AFL coverage instrumentation plus overflow-checks/debug-assertions/panic=abort rather than ASan. The crash reported here is a deterministic, 100%-reproducible allocator abort (std::alloc::handle_alloc_error), independently confirmed via the crate's own shipped CLI binary, so the lack of ASan does not weaken the evidence β€” there is no memory-corruption claim being made here, only unbounded resource consumption from an unvalidated attacker-controlled length field.

Dedup / prior-art check

Searched GitHub issues/PRs on apache/arrow-rs for prior reports of this class of bug before finalizing:

  • Issue #9705 / PR #9725 (merged 2026-04-16): fixed a different panic β€” an assert!(end <= remainder.len()) in ParquetMetaDataReader triggered by a truncated file during page index reading. Different code path, different trigger condition (an assertion on a byte range, not an unbounded Vec::with_capacity from a raw Thrift list count), already fixed in the commit this PoC was built against.
  • Issue #9742: tracks a plan to add a corruption/mutation fuzzer for the parquet crate (not yet implemented at the time of this campaign β€” confirmed by the absence of any fuzz/ directory or cargo-fuzz/libFuzzer/AFL harness anywhere in the apache/arrow-rs tree as of this clone).
  • No CVE/GHSA advisory, issue, or PR found describing an unbounded allocation from the row_groups, schema, key_value_metadata, column_orders, or page-index page_locations Thrift list counts.
  • The custom Thrift decoder itself (parquet/src/parquet_thrift.rs, parquet/src/file/metadata/thrift/) is relatively new, written for performance (see issue #5854 / PR #8530, "3x-9x Faster Apache Parquet Footer Metadata Using a Custom Thrift Parser in Rust", Oct 2025) and replaced use of the general-purpose thrift crate β€” consistent with no prior disclosure existing for this specific implementation's input validation gap.

Files in this repo

  • poc_row_groups_alloc_bomb.parquet β€” 490-byte minimized (via afl-tmin) crash input. Declares row_groups.size = 195,932,266 in the footer, driving an ~18.8 GB Vec::with_capacity request.
  • harness_main.rs β€” the exact AFL++ (afl.rs) fuzz harness used, calling ParquetMetaDataReader::try_parse (footer + row-group + column-chunk + page-index metadata parsing) on unmodified crate source via a Cargo path dependency.
  • harness_Cargo.toml β€” harness manifest (path dependency on apache/arrow-rs's parquet crate, default-features = false, features = ["base64"], panic = "abort", overflow-checks = true, debug-assertions = true).

Access

Gated β€” granted to protectai-bot and Enigma Partners Global bug-bounty reviewers for triage purposes. Not for public redistribution while the issue is unpatched upstream.

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