Repository navigation
feat: require opt-in to read data encoded with the pickle codec - #4465
bhaskargurram-ai wants to merge 7 commits into
Conversation
Decoding a chunk with the numcodecs `pickle` codec calls `pickle.loads`
on bytes from the store, so opening an untrusted Zarr v2 array can run
arbitrary code. Reading such data now raises `PickleCodecDisabledError`
unless the new `array.allow_pickle` config option is `True`, set with
`zarr.config.set({"array.allow_pickle": True})` or the environment
variable `ZARR_ARRAY__ALLOW_PICKLE=True`.
The check sits in `V2Codec._decode_sync`, before any codec runs, and
covers the pickle codec as compressor or filter. Opening the array and
writing with the pickle codec are unchanged.
Closes zarr-developers#4404
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #4465 +/- ##
=======================================
Coverage 94.71% 94.71%
=======================================
Files 94 94
Lines 13663 13672 +9
=======================================
+ Hits 12941 12950 +9
Misses 722 722
🚀 New features to boost your workflow:
|
|
@d-v-b this is ready for a look when you have a moment. The main open question is the option name (array.allow_pickle) and whether writes should be gated too, so I’m happy to adjust either. |
|
@d-v-b a short follow-up on #4404. This makes reading pickle-codec chunks opt-in through array.allow_pickle (default False), because unpickling chunk data can run arbitrary code. Opening arrays and writing are unchanged. The two open points are the option name and whether writes should be gated too. Could you take a look when there’s time? |
…n work with it numcodecs 0.16.6 and 0.17.1 refuse to unpickle unless the process opts in (GHSA-jqc8-qjfv-5m6r); 0.17.0 does not. Raise the floor and exclude 0.17.0 so a zarr upgrade carries the fix. The tests that read pickled data after opting in to zarr now also opt in to numcodecs, because its gate sits behind zarr's. A new test checks that zarr's opt-in does not bypass numcodecs. The PickleCodecDisabledError message, the config docs, and the changelog name both switches. Assisted-by: ClaudeCode:claude-fable-5.1 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
thanks for this @bhaskargurram-ai, in the interest of speed I pushed some changes on top of yours to leverage the patched version of numcodecs we shipped last night which gates pickle decode behind a flag that defaults to false.
|
|
Thanks @d-v-b, building on the numcodecs gate makes sense. Looks good to me. |
The zarr setting is now array.allow_unsafe_pickle_decode (environment variable ZARR_ARRAY__ALLOW_UNSAFE_PICKLE_DECODE), matching numcodecs' ALLOW_UNSAFE_PICKLE_DECODE and NUMCODECS_ALLOW_UNSAFE_PICKLE_DECODE, so users who learn one name can guess the other. No behavior change. Assisted-by: ClaudeCode:claude-fable-5.1 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
I pushed a change that makes |
Summary
Closes #4404.
picklecodec runspickle.loadson chunk bytes from the store, so reading an untrusted Zarr v2 array can execute arbitrary code. I reproduced this onmain: a.zarraywithfilters=[{"id": "vlen-utf8"}],compressor={"id": "pickle"}opens fine, and indexing it unpickles the chunk (a__reduce__payload runs).array.allow_pickle(defaultFalse). It can be enabled withzarr.config.set({"array.allow_pickle": True})or with the env varZARR_ARRAY__ALLOW_PICKLE=True(donfig's usual env mapping), matching the opt-in style ofarray.rectilinear_chunks.V2Codec._decode_syncand runs before any codec. It matches a compressor or filter whosecodec_id == "pickle". This is the only place pickle-encoded chunks get decoded; v3 has no pickle codec registered. Without the opt-in it raises a newzarr.errors.PickleCodecDisabledError(aBaseZarrError/ValueError). The message names the config key and the env var.Trueenables it (is True, not a truthiness check), because donfig keeps an env value it can'tliteral_eval(e.g.ZARR_ARRAY__ALLOW_PICKLE=false) as a non-empty string, which is truthy.pickle.dumpson the user's own data, so there is no untrusted input. A partial-chunk write that has to read the existing chunk back will hit the check, which is expected.docs/user-guide/config.md. Changelog:changes/4404.feature.md.For reviewers
array.allow_picklethe name/namespace you want? It is not anArrayConfigfield, so it can only be set globally, not per array viaconfig=. The same is true ofarray.rectilinear_chunks.get_numcodecresolves the codec from metadata) keeps groups listable and metadata inspectable. Would you rather it fail earlier, at open?removal/breaking changelog entry rather thanfeature, since reads that worked before now raise by default?Author attestation
TODO
docs/user-guide/*.mdchanges/Tests (
tests/test_v2.py): default read raises; a crafted malicious chunk is not unpickled; opt-in viazarr.config.set; opt-in via env var (Trueenables,falsedoes not); a non-pickle v2 array reads without opting in.tests/test_config.pydefaults updated.