GitHub Advisoryadvisory · PyPI

GHSA-wccx-j62j-r448 Fickling has `always_check_safety()` bypass: pickle.loads and _pickle.loads remain unhooked

HIGH Published 2026-03-04 Modified 2026-03-04 Official page ↗
Ecosystem
PyPI
Packages
fickling
Severity
High
CVSS vector
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N/E:P
Weaknesses
CWE-693
Reviewed
2026-03-04

Affected packages

• PyPI fickling: < 0.1.9 → fixed in 0.1.9

Description

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.

Related

Weakness · CWE (1)

  • CWE-693 Protection Mechanism Failure weakness

References