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.

modelscan scans only the first pickle of a legacy PyTorch checkpoint β€” RCE passes clean

Severity: High (arbitrary code execution; scanner false-clean, no skip/error) Affected tool: modelscan 0.8.8 (also current main) β€” modelscan/tools/picklescanner.py scan_pytorch / _list_globals. Category: ModelScan scanner-bypass on .pt/.pth/.bin/.ckpt (in-scope, Model File Formats).

Summary

The legacy (non-zip) PyTorch on-disk format is a sequence of concatenated pickles (MAGIC_NUMBER, protocol, sys_info, then the real object). modelscan's scan_pytorch calls scan_pickle_bytes(..., multiple_pickles=False), so _list_globals scans only the first pickle (the benign magic integer) and stops. A malicious __reduce__ in a later pickle is never disassembled β†’ modelscan returns 0 issues, no skip, no error (a true silent clean pass), while torch.load(weights_only=False) unpickles all of them and executes the payload.

Root cause

  • modelscan/tools/picklescanner.py scan_pytorch() (~lines 242-269): after get_magic_number() validates the torch MAGIC_NUMBER, it returns scan_pickle_bytes(model, settings, scan_name, multiple_pickles=False).
  • _list_globals() (lines 50-119): if not multiple_pickles: break after the first pickle buffer.
  • The magic number is the correct literal value, so the wrong-magic SKIP branch (lines 255-267) is not taken β€” the scanner runs to completion and reports clean.

Isolation proofs (in the PoC):

  • modelscan's own generic scanner with multiple_pickles=True β†’ detects ('nt','system'). So this is loop-coverage, not a blocklist gap.
  • Standalone picklescan 1.0.4 detects the same file β€” its scan_pytorch loops for _ in range(5): scan_pickle_bytes(... multiple_pickles=False). modelscan never ported that loop.
  • The same payload in the new zip format is caught by modelscan (single data.pkl). The miss is specific to the legacy multi-pickle format.

Reproduce

python poc/POC_FINAL_legacy_multipickle.py (env: modelscan 0.8.8, picklescan 1.0.4, torch, Python 3.12). Builds a valid 574-byte legacy .pt via torch.save(..., _use_new_zipfile_serialization=False). Asserts: [1] modelscan scan_pytorch β†’ CLEAN_0_ISSUES; [2] picklescan 1.0.4 β†’ DETECTED [('nt','system')]; [3] torch.load(weights_only=False) β†’ RCE marker written; [4] modelscan generic multiple_pickles=True β†’ ISSUES (root-cause isolation). A 300k-case differential fuzz found no simpler opcode-parsing variant, supporting that this loop-coverage gap is the genuine residual.

Driver note: the env's unrelated TF/numpy mismatch prevents the TF-dependent CLI from launching, so the PoC loads the genuine installed scan_pytorch/_list_globals/scan_pickle_bytes/DEFAULT_SETTINGS TF-free and exercises them directly (validated against known-detected and known-clean pickles). .pt/.ckpt/.bin/.pth dispatch to PyTorchUnsafeOpScan β†’ scan_pytorch, the identical function.

Impact

A valid legacy-format PyTorch checkpoint carrying os.system via __reduce__ passes modelscan with total_issues=0 (CLEAN, no error), then executes on torch.load(weights_only=False). Defeats modelscan as a CI/supply-chain gate for legacy torch artifacts. Honest caveat: the executor is torch.load with weights_only=False (PyTorch β‰₯2.6 defaults to True); weights_only=False remains pervasive in training-resume/checkpoint code and universal pre-2.6, and gating exactly such files is modelscan's stated purpose.

Dup-check

Distinct from GHSA-9gvj STACK_GLOBAL off-by-one (a crash bypass), CVE-2025-1889 / GHSA-769v-p64c-89pr (picklescan file-extension/wrong-magic SKIP β€” here the magic is correct and no skip occurs), CVE-2025-10156 (ZIP CRC), CVE-2025-10157 (subclass blocklist), the 29-ways article, and arXiv:2508.19774 EOP-3 (the STACK_GLOBAL-offset + eval-MAGIC crash path). Confirmed protectai/modelscan main still has multiple_pickles=False (unpatched). None of our R1-R4 findings cover the first-pickle-only legacy-checkpoint miss.

Convergence note: two independent round-5 hunters arrived at this same root cause from different starting targets β€” a strong real-bug signal. (One skeptic pass flagged it as plausibly an extension of known modelscan pickle handling; included here with that caveat so triage isn't surprised.)

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

Paper for EnigmaConsultant/huntr-r5-modelscan-legacy-multipickle