-
Notifications
You must be signed in to change notification settings - Fork 1
Description
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)insafe_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
-
safe_dumpvalidates object type before serialization - Type allowlist documented and configurable
- Warning emitted if object type is not in allowlist
- All existing tests continue to pass
- New security test: verify malicious reduce is caught before serialization