fix(security): refuse pickle on object-dtype decode by default (CWE-502)#58
Open
willardjansen wants to merge 1 commit into
Open
fix(security): refuse pickle on object-dtype decode by default (CWE-502)#58willardjansen wants to merge 1 commit into
willardjansen wants to merge 1 commit into
Conversation
decode() called pickle.loads() with zero validation on attacker-controlled
data whenever a payload set kind=b'O'. Any caller of unpackb() / unpack() /
Unpacker was exposed to arbitrary code execution from a 122-byte crafted
.msgpack file — the pickle reduce protocol allows attacker payloads to
invoke os.system, subprocess.Popen, etc.
This commit gates the pickle path on an explicit allow_pickle kwarg:
* allow_pickle=False (new default) — raises ValueError on kind=b'O'
* allow_pickle='restricted' — RestrictedUnpickler that
allowlists numpy reconstruction
primitives + safe Python builtins
and blocks known pickle-RCE
gadgets (eval/exec/getattr/...)
* allow_pickle=True — legacy pickle.loads (use only with
trusted sources)
Threaded through decode(), Unpacker (all three msgpack-version branches),
unpack(), and unpackb(). pack() / packb() accept and ignore the kwarg for
symmetry.
The existing test_numpy_array_object now opts in via
allow_pickle='restricted'. Five new tests cover:
- default refusal raises ValueError
- allow_pickle=True restores legacy behavior
- hand-crafted os.system __reduce__ payload is refused by default
- same payload is refused by the restricted unpickler
- builtins.eval gadget is on the explicit block list
- bad allow_pickle values raise ValueError
Fixes the unfixed CWE-502 documented in issue lebedov#57 and extends PR lebedov#52
with a restricted-unpickler opt-in mode.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
decode()callspickle.loads()with zero validation whenever a msgpack payload setskind=b'O', so any caller ofunpackb()/unpack()/Unpackerwill execute arbitrary code embedded in the pickle stream. The pickle reduce protocol lets a 122-byte crafted.msgpackfile invokeos.system,subprocess.Popen, etc. This is the unfixed vulnerability documented in #57 and partially addressed by the unmerged #52.This PR gates the pickle path on an explicit
allow_picklekwarg, matching numpy's ownnp.load(allow_pickle=...)convention:False(new default)ValueErrorwhenkind=b'O'is encountered'restricted'RestrictedUnpicklerthat allowlists numpy reconstruction primitives + safe Python builtins and blocks known pickle-RCE gadgets (eval,exec,getattr,__import__, …)Truepickle.loads— for fully trusted sources onlyThe kwarg is threaded through
decode(), all threeUnpacker.__init__branches (msgpack < 0.4 / < 1.0 / ≥ 1.0),unpack(), andunpackb().pack()/packb()accept and discard it for symmetry.Compatibility
This is a breaking change for callers that round-trip
object-dtype ndarrays throughunpackb()without specifyingallow_pickle. The fix is one keyword:Existing object-dtype callers can migrate either to
'restricted'(recommended) or toTrue(explicit opt-in to the legacy unrestricted path).Tests
The existing
test_numpy_array_objectopts in viaallow_pickle='restricted'. Five new tests cover:ValueErrorwith a message namingallow_pickleallow_pickle=Truerestores legacy round-trip behavioros.system__reduce__payload is refused with the defaultpickle.UnpicklingErrorbuiltins.evalgadget is on the explicit block listallow_picklevalues raiseValueErrorAll 36 tests pass locally on Python 3.13 / numpy 2.4.2 / msgpack 1.1.1.
Disclosure
Filed via huntr.com on 2026-04-12 and validated by huntr triage on 2026-05-29 (CWE-502, CVSS 8.8). This PR is the proposed coordinated-disclosure fix.
Closes #57. Supersedes #52 (extends with restricted-unpickler opt-in).