modelscan legacy (non-zip) PyTorch multi-pickle bypass — PoC (benign marker)
Security-research proof-of-concept submitted to huntr. The payload is benign:
it runs os.system("echo MS_LEGACY_EXEC_OK"), which only prints a marker string. No
files are modified, no network access, no harm. It exists solely to demonstrate a
scanner false-negative.
What it shows
model.pt is a PyTorch checkpoint saved in the legacy (non-zip) serialization format
(torch.save(obj, f, _use_new_zipfile_serialization=False)). That format is a stream of
concatenated pickles. ModelScan's PyTorch handler scans it with multiple_pickles=False,
so it only inspects the first pickle and stops — a malicious __reduce__ placed in a
later pickle is never seen.
| File | modelscan result |
|---|---|
model.pt (legacy / non-zip) |
No issues found! 🎉 — exit 0 (CLEAN) |
| same payload re-saved as zip-format | CRITICAL: Use of unsafe operator 'system' from module 'posix' — exit 1 |
Meanwhile torch.load("model.pt", weights_only=False) executes the payload (prints
MS_LEGACY_EXEC_OK). So a checkpoint ModelScan certifies clean runs arbitrary code on load.
Reproduce
pip install torch --index-url https://download.pytorch.org/whl/cpu
pip install modelscan
modelscan -p model.pt # -> No issues found! 🎉 (exit 0)
python -c "import torch; torch.load('model.pt', weights_only=False)" # -> MS_LEGACY_EXEC_OK
build_and_verify.py rebuilds the artifact and prints both scans + the execution proof.
Affected: protectai/modelscan v0.8.8 (latest). Root cause: scan_pytorch passes
multiple_pickles=False (modelscan/tools/picklescanner.py:269). Reported responsibly via huntr.