PT-2026-25526 · Pypi · Fickling

Published

2026-03-04

·

Updated

2026-03-04

CVSS v4.0

8.9

High

VectorAV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N/E:P

Assessment

The missing pickle entrypoints pickle.loads, pickle.loads, and pickle.load were added to the hook https://github.com/trailofbits/fickling/commit/8c24c6edabceab156cfd41f4d70b650e1cdad1f7.

Original report

Summary

fickling.always check safety() does not hook all pickle entry points. pickle.loads, pickle.loads, and pickle.load remain unprotected, enabling malicious payload execution despite global safety mode being enabled.

Affected versions

<= 0.1.8 (verified on current upstream HEAD as of 2026-03-03)

Non-duplication check against published Fickling GHSAs

No published advisory covers hook-coverage bypass in run hook(). Existing advisories are blocklist/detection bypasses (runpy, pty, cProfile, marshal/types, builtins, network constructors, OBJ visibility, etc.), not runtime hook coverage parity.

Root cause

run hook() patches only:
  • pickle.load
  • pickle.Unpickler
  • pickle.Unpickler
It does not patch:
  • pickle.loads
  • pickle.load
  • pickle.loads

Reproduction (clean upstream)

python
import io, pickle, pickle
from unittest.mock import patch
import fickling
from fickling.exception import UnsafeFileError

class Payload:
  def  reduce (self):
    import subprocess
    return (subprocess.Popen, (['echo','BYPASS'],))

data = pickle.dumps(Payload())
fickling.always check safety()

# Bypass path
with patch('subprocess.Popen') as popen mock:
  pickle.loads(data)
  print('bypass sink called?', popen mock.called) # True

# Control path is blocked
with patch('subprocess.Popen') as popen mock:
  try:
    pickle.load(io.BytesIO(data))
  except UnsafeFileError:
    pass
  print('blocked sink called?', popen mock.called) # False
Observed on vulnerable code:
  • pickle.loads executes payload
  • pickle.load is blocked

Minimal patch diff

diff
--- a/fickling/hook.py
+++ b/fickling/hook.py
@@
 def run hook():
-  pickle.load = loader.load
+  pickle.load = loader.load
+   pickle.load = loader.load
+  pickle.loads = loader.loads
+   pickle.loads = loader.loads

Validation after patch

  • pickle.loads, pickle.loads, and pickle.load all raise UnsafeFileError
  • sink not called in any path
Regression tests added locally:
  • test run hook blocks pickle loads
  • test run hook blocks pickle load and loads in test/test security regressions 20260303.py

Impact

High-confidence runtime protection bypass for applications that trust always check safety() as global guard.

Fix

Protection Mechanism Failure

Found an issue in the description? Have something to add? Feel free to write us 👾

Weakness Enumeration

Related Identifiers

GHSA-WCCX-J62J-R448

Affected Products

Fickling