modelscan memo-poison scanner bypass β€” Proof of Concept

Security research artifact. This repository hosts a benign proof-of-concept for a Protect AI modelscan detection bypass (root cause: STACK_GLOBAL memo reconstruction desync). It is submitted as a coordinated huntr Model File Vulnerability (MFV) disclosure. The payload is benign β€” it only prints/writes the literal marker CELVEX-MARKER. No destructive, networked, or harmful behaviour.

Files

  • poison.pkl β€” a pickle that, on pickle.load(), resolves and runs the denylisted builtins.exec("print('CELVEX-MARKER')"), yet modelscan reports No issues found.

Root cause

modelscan's pickle scanner (modelscan/tools/picklescanner.py::_list_globals) memoizes the textually previous opcode's parsed value (ops[n-1][1]) when it sees MEMOIZE/PUT/BINPUT, instead of the actual pickle-VM stack top. If the opcode immediately before the memo-put is stack-consuming (e.g. POP), its parsed value is None, so modelscan stores memo[k]=None. A later BINGET k then injects None into the (module, name) pair reconstructed for STACK_GLOBAL. _build_scan_result then evaluates <denied_name> in None, raising TypeError: argument of type 'NoneType' is not iterable, which modelscan swallows as a scanner error and reports zero issues β€” while the real pickle VM happily resolves the denylisted callable and executes the payload.

This is a distinct root cause from CVE-2025-46417 (a range() ValueError crash), though it lands in the same "scanner error β†’ 0 issues" outcome class.

Reproduce

pip install modelscan
python -c "import pickle; pickle.load(open('poison.pkl','rb'))"   # prints CELVEX-MARKER (payload runs)
modelscan -p poison.pkl                                            # => "No issues found! πŸŽ‰" (+ swallowed scanner error)

Verified against modelscan 0.8.8:

  • poison.pkl β†’ pickle.load executes builtins.exec (marker fires) and modelscan -p prints No issues found! πŸŽ‰.
  • A control pickle using the same builtins.exec without the memo-poison is correctly flagged CRITICAL.

Remediation

In _list_globals, key the memo to the value actually pushed onto the pickle VM stack (track stack effects per opcode) rather than the textually previous opcode's parsed value; and in _build_scan_result, treat a reconstructed (None, …) / (…, None) global as suspicious instead of letting the in None comparison raise and be swallowed.

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