Views
No views yet
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.poison.pkl — a pickle that, on pickle.load(), resolves and runs the denylisted
builtins.exec("print('CELVEX-MARKER')"), yet modelscan reports No issues found.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.range() ValueError crash), though it lands
in the same "scanner error → 0 issues" outcome class.1pip install modelscan
2python -c "import pickle; pickle.load(open('poison.pkl','rb'))" # prints CELVEX-MARKER (payload runs)
3modelscan -p poison.pkl # => "No issues found! 🎉" (+ swallowed scanner error)poison.pkl → pickle.load executes builtins.exec (marker fires) and modelscan -p prints No issues found! 🎉.builtins.exec without the memo-poison is correctly flagged CRITICAL._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.