Problem Statement
The safe_pickle.py module's safe_dump function (lines 225-243) serializes objects using pickle.dumps with HIGHEST_PROTOCOL and then signs the result with HMAC. However, the serialization step itself (pickle.dumps) can trigger arbitrary code execution via the __reduce__, __reduce_ex__, __getstate__, or __getnewargs__ methods of the object being serialized. If a maliciously-constructed object (e.g., from user-supplied config or untrusted deserialization) is passed to safe_dump, these dunder methods execute during serialization, BEFORE any integrity signing occurs.
This is distinct from the deserialization vulnerability (RestrictedUnpickler addresses that). This is a serialization-time vulnerability where the act of pickling an object can trigger code execution.
v1.0 Impact
Medium -- this requires an attacker to first get a malicious object into the application state, which is a prerequisite that may already constitute a compromise. However, if the application processes user-supplied objects (e.g., from imported scenarios or configs that construct custom objects), this could be exploited. This should be fixed before v1.0 as defense-in-depth.
Affected Code
ergodic_insurance/safe_pickle.py:L239 -- data = pickle.dumps(obj, protocol=protocol)
ergodic_insurance/safe_pickle.py:L293 -- data = pickle.dumps(obj, protocol=protocol) in safe_dumps
Current Behavior
def safe_dump(obj, f, protocol=pickle.HIGHEST_PROTOCOL, key_dir=None):
data = pickle.dumps(obj, protocol=protocol) # __reduce__ executes here
key = _get_or_create_hmac_key(key_dir)
signature = hmac.new(key, data, hashlib.sha256).digest()
f.write(signature)
f.write(data)
If obj has a malicious __reduce__ method (e.g., def __reduce__(self): import os; os.system("whoami"); return (str, ("safe",))), the pickle.dumps call executes __reduce__ which runs the malicious code.
Attack scenario: An application imports scenario configurations that construct custom objects, or a deserialized object (that passed RestrictedUnpickler) has been crafted to have a malicious __reduce__ that only triggers on re-serialization.
Expected Behavior
safe_dump should validate the type of obj against an allowlist of known-safe types before serialization. Alternatively, use pickletools.dis to inspect the pickle stream for dangerous opcodes before writing.
Alternative Solutions Evaluated
- Type-check obj before serialization: Verify
type(obj) is in an allowlist of known-safe project types. Pros: Simple, prevents arbitrary object serialization. Cons: May be overly restrictive for container types with nested objects.
- Use copyreg to control serialization: Register custom pickle reducers for project types that avoid executing user-controlled code. Pros: Fine-grained control. Cons: High maintenance.
- Document the risk: Add a warning that only trusted objects should be passed to safe_dump. Pros: Low effort. Cons: Not a technical mitigation.
Recommended Approach
Option 1: Add a type allowlist check at the top of safe_dump. For container types (dict, list, tuple), recursively check contained elements. This is defense-in-depth -- the primary mitigation is ensuring untrusted objects don't enter the application state.
Acceptance Criteria
Problem Statement
The
safe_pickle.pymodule'ssafe_dumpfunction (lines 225-243) serializes objects usingpickle.dumpswithHIGHEST_PROTOCOLand then signs the result with HMAC. However, the serialization step itself (pickle.dumps) can trigger arbitrary code execution via the__reduce__,__reduce_ex__,__getstate__, or__getnewargs__methods of the object being serialized. If a maliciously-constructed object (e.g., from user-supplied config or untrusted deserialization) is passed tosafe_dump, these dunder methods execute during serialization, BEFORE any integrity signing occurs.This is distinct from the deserialization vulnerability (RestrictedUnpickler addresses that). This is a serialization-time vulnerability where the act of pickling an object can trigger code execution.
v1.0 Impact
Medium -- this requires an attacker to first get a malicious object into the application state, which is a prerequisite that may already constitute a compromise. However, if the application processes user-supplied objects (e.g., from imported scenarios or configs that construct custom objects), this could be exploited. This should be fixed before v1.0 as defense-in-depth.
Affected Code
ergodic_insurance/safe_pickle.py:L239--data = pickle.dumps(obj, protocol=protocol)ergodic_insurance/safe_pickle.py:L293--data = pickle.dumps(obj, protocol=protocol)insafe_dumpsCurrent Behavior
If
objhas a malicious__reduce__method (e.g.,def __reduce__(self): import os; os.system("whoami"); return (str, ("safe",))), thepickle.dumpscall executes__reduce__which runs the malicious code.Attack scenario: An application imports scenario configurations that construct custom objects, or a deserialized object (that passed RestrictedUnpickler) has been crafted to have a malicious
__reduce__that only triggers on re-serialization.Expected Behavior
safe_dumpshould validate the type ofobjagainst an allowlist of known-safe types before serialization. Alternatively, usepickletools.disto inspect the pickle stream for dangerous opcodes before writing.Alternative Solutions Evaluated
type(obj)is in an allowlist of known-safe project types. Pros: Simple, prevents arbitrary object serialization. Cons: May be overly restrictive for container types with nested objects.Recommended Approach
Option 1: Add a type allowlist check at the top of
safe_dump. For container types (dict, list, tuple), recursively check contained elements. This is defense-in-depth -- the primary mitigation is ensuring untrusted objects don't enter the application state.Acceptance Criteria
safe_dumpvalidates object type before serialization