Skip to content

Security: gate marshal-based function deserialization behind enable_opaque_pickle#412

Open
koonnamchok wants to merge 1 commit into
google:mainfrom
koonnamchok:koonnamchok-patch-1
Open

Security: gate marshal-based function deserialization behind enable_opaque_pickle#412
koonnamchok wants to merge 1 commit into
google:mainfrom
koonnamchok:koonnamchok-patch-1

Conversation

@koonnamchok

Copy link
Copy Markdown

Summary

from_json reconstructs Python functions from inline code via marshal.loads() in _function_from_json. A payload {"_type": "function", "code": <base64 marshal>, ...} becomes a live types.FunctionType; calling it runs the embedded bytecode. Any consumer doing pg.from_json / pg.from_json_str / pg.load on data it does not fully control is one call away from code execution.

PyGlove already ships a documented mitigation, enable_opaque_pickle(False) (its docstring targets "cloud services that process untrusted JSON"), but it only guarded the pickle _OpaqueObject path — the marshal/function path was unconditionally on, so an operator who followed the documented advice was still exposed.

This was reported to and confirmed by the Google OSS VRP (issue 523598901).

Change

  • _function_from_json: the inline-code (marshal.loads) path is now gated by the same _opaque_pickle_enabled flag that guards pickle deserialization. When opaque pickle is disabled it raises TypeError instead of producing an executable function. The name-based path (_load_symbol) is unaffected.
  • enable_opaque_pickle docstring updated to note it now also covers function-code deserialization.
  • Tests: enable_opaque_pickle(False) rejects an inline-code function payload; default round-trip behaviour is preserved.

No default-behaviour change (enable_opaque_pickle defaults to True). Services that already adopted the documented mitigation are now protected against this path too.

Verification

python -m unittest pyglove.core.utils.json_conversion_test — all 25 tests pass (2 new + existing, no regression). End-to-end PoC: by default a crafted function payload deserializes into a callable that executes on call; under enable_opaque_pickle(False) the same payload is rejected and nothing executes.

…paque_pickle

Gate the marshal.loads() inline-code path in _function_from_json behind the existing _opaque_pickle_enabled flag, so enable_opaque_pickle(False) (the documented untrusted-JSON mitigation) also disables function-code deserialization. Reported and confirmed via Google OSS VRP (issue 523598901). No default-behaviour change; adds tests. See PR description.
@google-cla

google-cla Bot commented Jun 13, 2026

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@koonnamchok

Copy link
Copy Markdown
Author

@googlebot I signed it!

@koonnamchok koonnamchok reopened this Jun 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant