Skip to content

Harden pickle loading against RCE (RestrictedUnpickler) - #441

Merged
kthare10 merged 1 commit into
rel-2.0.1from
fix/restricted-unpickler
Jul 24, 2026
Merged

kthare10 merged 1 commit into
rel-2.0.1from
fix/restricted-unpickler

Conversation

@kthare10

Copy link
Copy Markdown
Collaborator

Summary

Domain objects (Slices, Reservations, Units, Delegations, POAs) are stored as pickles in Postgres, and pickled slivers ride Kafka. Plain pickle.loads on such data means anyone able to write the store or the bus can execute arbitrary code on load (pickle can import and call any global).

This adds interim hardening — the on-disk / wire format is unchanged — that closes the practical remote-code-execution vectors.

Changes

  • fabric_cf/actor/security/restricted_unpickler.pyrestricted_loads() + RestrictedUnpickler:
    • Always blocks the standard pickle gadget modules/callables in find_class: os, subprocess, sys, socket, ssl, shutil, ctypes, importlib, pty, marshal, requests/urllib/http, … and builtins.eval/exec/compile/open/__import__/breakpoint/input.
    • Allows FABRIC packages (fabric_cf, fim, fabric_mb, fabrictestbed, fss_utils) and the ordinary stdlib types that legitimate pickled objects contain (datetime, uuid, logging, enum, collections, ipaddress, networkx, …) — the allowlist was derived empirically by pickling real domain/sliver objects.
    • Unlisted modules: audit-logged at WARNING and allowed by default, so a too-narrow allowlist can never break loading of real production data. Set FABRIC_PICKLE_ENFORCE=1 to switch to strict allowlist enforcement once the audit logs are confirmed clean.
  • Route all 15 pickle.loads call sites through restricted_loads (actor/server/substrate databases, container database, proxy).
  • fabric_cf/actor/test/unit/test_restricted_unpickler.py — infra-free tests: legit round-trips (dict/datetime/uuid/Capacities/Labels) and blocked os.system / eval / subprocess gadgets.

Verification

  • compileall passes; the 5 wired modules import cleanly (no circular import — the helper depends only on stdlib).
  • Unit tests pass locally (5/5). Manual check confirms os.system (resolved as posix.system) and eval payloads raise UnpicklingError while legit objects round-trip.

Rollout note

Because unlisted modules are allowed-and-logged by default, this is safe to deploy as-is. On rel-2.0.1, grep actor logs for restricted_unpickler: allowing unlisted pickle global to discover any legitimate module missing from the allowlist, add them, and only then set FABRIC_PICKLE_ENFORCE=1 for full allowlist enforcement.

Merge notes (targets rel-2.0.1)

Touches actor_database.py / server_actor_database.py / substrate_actor_database.py, which are also modified by the lock-idiom PR (#437) and (for actor_database.py) the DB-error PR (#440). The pickle.loads -> restricted_loads edits are on different lines, so conflicts should be trivial. Recommended order: #439 (CI) -> #437 (locks) -> #440 (db errors) -> this.

Theme 3 (interim) of the improvement sweep. A full pickle-to-versioned-JSON migration remains a separate, larger project tracked in IMPROVEMENTS.md.

Domain objects (Slices/Reservations/Units/Delegations/POAs) are stored as
pickles in Postgres and pickled slivers ride Kafka, so plain pickle.loads means
anyone able to write the store/bus can execute arbitrary code on load.

Add fabric_cf/actor/security/restricted_unpickler.py with restricted_loads(),
which blocks the standard pickle RCE/exfil gadget modules and callables
(os/subprocess/sys/socket/ctypes/importlib, builtins.eval/exec/compile/open/
__import__, ...) in find_class, while allowing the FABRIC domain packages and
the ordinary stdlib types (datetime/uuid/logging/enum/ipaddress/networkx/...)
that legitimate pickled objects contain.

Interim hardening only -- the on-disk/wire format is unchanged. Unlisted modules
are audit-logged and allowed by default (so a too-narrow allowlist cannot break
loading of real data); set FABRIC_PICKLE_ENFORCE=1 to switch to strict allowlist
enforcement after the audit logs are confirmed clean.

Route all 15 pickle.loads call sites (actor/server/substrate databases,
container database, proxy) through restricted_loads. Adds infra-free unit tests
covering legit round-trips and blocked os.system/eval/subprocess gadgets.
@kthare10
kthare10 merged commit 0a89ad6 into rel-2.0.1 Jul 24, 2026
4 of 6 checks passed
@kthare10
kthare10 deleted the fix/restricted-unpickler branch July 24, 2026 01:33
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