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 bypass β€” pickle memo-slot temporal-reuse parser divergence (CWE-693 / CWE-436)

huntr Model File Vulnerability PoC. Target format: Pickle (.pkl) – Python. Bypassed scanner: protectai/modelscan 0.8.8 (Protect AI's own model scanner).

Payload is benign: os.system("touch /tmp/PWNED_DIVERGE_MARKER") β€” it only creates an empty marker file so you can confirm execution. Run in a throwaway sandbox.

What this is

A single 56-byte .pkl that modelscan reports as clean ("No issues found!") while CPython's real unpickler executes os.system. It is a genuine parser divergence (static analysis resolves a different global than the VM), NOT a deny-list gap and NOT the published offset-0 STACK_GLOBAL crash.

Files

  • modelscan_memo_divergence.pkl β€” the crafted malicious pickle (56 bytes).
  • modelscan_output.txt β€” modelscan reports clean (with JSON errors:[] / control CRITICAL / cross-tool notes).
  • vm_findclass_trace.py β€” non-executing proof CPython resolves ('os','system').
  • load_demo.py β€” full demo that loading the file runs the benign marker.

The opcode stream

80 02                          PROTO 2
8c 02 os                       SHORT_BINUNICODE 'os'
94                             MEMOIZE            -> memo[0] = 'os'
68 00                          BINGET 0
8c 06 system                   SHORT_BINUNICODE 'system'
71 00                          BINPUT 0           -> memo[0] = 'system'  (OVERWRITES slot 0)
93                             STACK_GLOBAL
8c 1f touch /tmp/PWNED_...      SHORT_BINUNICODE 'touch /tmp/PWNED_DIVERGE_MARKER'
85                             TUPLE1
52                             REDUCE             -> calls resolved_global(marker)
2e                             STOP

(pickletools.dis refuses this stream with ValueError: memo key 0 already defined β€” modelscan does not.)

Root cause (read from modelscan 0.8.8 source: modelscan/tools/picklescanner.py, _list_globals)

_list_globals has no value stack. It walks opcodes forward, maintaining a memo dict where MEMOIZE/BINPUT do a plain memo[key] = ops[n-1][1] assignment (overwriting an existing slot with no conflict check). On STACK_GLOBAL it walks backward for the two nearest value-producing opcodes, resolving a GET/BINGET as memo[int(operand)] against the memo's current (final) state.

  • By the time it reaches STACK_GLOBAL, memo[0] has been overwritten to 'system' (by the BINPUT 0 that lexically follows the BINGET 0). So its backward walk collects ['system'(literal), 'system'(BINGET 0 -> final memo[0])] and records the benign global ('system','system'). 'system' is not a module in modelscan's unsafe_globals, and both operands are valid strings, so it completes with no error and no issue.

  • CPython's VM (Lib/pickle.py Unpickler) executes BINGET 0 at its point in the stream, fetching memo[0] == 'os' (before BINPUT 0 overwrote it). The VM stack then feeds STACK_GLOBAL -> find_class('os','system') -> os.system, and REDUCE calls os.system(marker).

The divergence is purely static-final-memo vs dynamic-point-in-time-memo.

Why it qualifies / is novel

  • Scanner bypass = huntr's top-priority MFF class; malicious model file loads and executes while the scanner meant to catch it passes it.
  • Not a deny-list gap: os.system never appears in modelscan's reconstruction. modelaudit (which models memo state correctly) does catch the identical bytes.
  • Not the published offset-0 STACK_GLOBAL crash (that populates errors with a PickleGenopsError; this completes with errors:[]).
  • Deduped against CVE-2025-46417, GHSA-655q-fx9r-782v, GHSA-jgw4-cr84-mqxg, CVE-2025-10155/10156/10157, GHSA-f7qq-56ww-84cr, GHSA-p9w7-82w4-7q8m, the manja316 offset-0 PoC, Cisco "Breaking the Jar", and arXiv 2508.19774 β€” none describe a memoized-GET resolving to a memo slot's overwritten final value statically vs its live value at execution.

Remediation

In _list_globals, either model the memo the way the VM does (resolve GET against the memo state at the GET's position, not the final state), or reject/flag streams that overwrite an existing memo slot (mirroring pickletools' memo key already defined check), or track a real value stack for STACK_GLOBAL operand resolution.

All testing was local; the payload is a benign touch marker only.

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