tags:
- security
- vulnerability
- poc
- parquet
- fastparquet
- decompression-bomb
- cwe-789
license: mit
fastparquet β Parquet PageHeader Decompression Bomb (PoC)
Repo: MBM7/fastparquet-parquet-decompression-bomb-poc
Status: Responsible disclosure β submitted to Huntr
Severity: High / CWE-789 (Uncontrolled Memory Allocation)
Summary
A crafted 492-byte .parquet file causes fastparquet to allocate
gigabytes of memory before failing β enabling OOM-kill on any service
that loads user-supplied Parquet files.
| File size | Fake uncompressed_page_size |
Peak allocation | Amplification |
|---|---|---|---|
| 492 bytes | 500,000,000 | 500 MB | 1 : 1,016,260 |
| 492 bytes | 2,000,000,000 | 2,000 MB | 1 : 4,065,040 |
Root Cause
fastparquet/compression.py β decompress_data():
if algorithm.upper() in decom_into:
x = np.empty(uncompressed_size, dtype='uint8') # β pre-allocates upfront
decom_into[algorithm.upper()](
np.frombuffer(data, dtype=np.uint8), x
)
return x
uncompressed_size is passed directly from ph.uncompressed_page_size
(Thrift compact i32, PageHeader field 2) with no bound check at any
point in the call chain:
fastparquet.ParquetFile.to_pandas()
β read_col_group() [core.py ~291]
β _read_page() [core.py ~20]
β decompress_data(
data,
page_header.uncompressed_page_size, β from Thrift, unchecked
codec
)
β np.empty(uncompressed_size) β GB allocation here
max_header_size or equivalent limit: does not exist in fastparquet.
Attack
Parquet format: PAR1 | PageHeader (Thrift compact) | compressed_data | ... | PAR1
PageHeader field 2 (uncompressed_page_size, type i32) is a
zigzag-encoded varint at byte offset 7 in a minimal single-column file.
Replacing the 1-byte varint (value 4) with a 5-byte varint
(value 500_000_000) produces a valid-looking file that triggers 500 MB
of allocation on any codec in decom_into (GZIP, SNAPPY, ZSTD, BROTLI).
Reproduce
pip install fastparquet pyarrow
python poc_fastparquet_parquet_bomb.py
Expected output:
Crafted file size : 492 bytes
Expected allocation : 500,000,000 bytes
Amplification : 1:1,016,260
Result : DecompressionError: unexpected end of file
Peak memory : 500 MB β allocation happened
Suggested Fix
fastparquet/compression.py, before np.empty():
MAX_UNCOMPRESSED = 256 * 1024 * 1024 # 256 MB configurable limit
if uncompressed_size > MAX_UNCOMPRESSED:
raise ValueError(
f"uncompressed_size {uncompressed_size} exceeds safety limit "
f"{MAX_UNCOMPRESSED}. Possible decompression bomb."
)
Environment
| Package | Version |
|---|---|
| fastparquet | 2026.5.0 |
| pyarrow | 25.0.0 |
| Python | 3.12 |
Files
| File | Description |
|---|---|
poc_fastparquet_parquet_bomb.py |
PoC script (self-contained) |
bomb_500mb.parquet |
Crafted payload (492 bytes) |
Discovered via empirical security research using UBDAF (Universal Bug Discovery & Analysis Framework) β Q2 CRITICAL pattern: arithmetic on externally-controlled value without bound check.