High

Unsafe Torch Checkpoint Deserialization to Process Execution

PyTorch training checkpoints can carry hidden code that runs automatically when a program loads them to resume training. If an attacker plants a booby-trapped checkpoint file, simply resuming training can hand them command execution on the machine.

How the attack works

An attacker crafts a checkpoint file (rng_state*.pth, optimizer.pt, or a .bin file) containing a pickle payload with a __reduce__ method. When the HuggingFace Transformers Trainer resumes from this checkpoint, it calls torch.load() without the weights_only safety flag, and the pickle payload executes inside the Python process. That payload then spawns a child process (a shell, a script, or another tool) to carry out the attacker's actual goal. Because the malicious code runs inside the training process itself, the only visible trace is the process it spawns shortly after reading the checkpoint file.

Netzilo detection

Netzilo reports this behaviour when it is observed.

Signature ID
9c4b7e21-3f6a-4d18-b5e2-7a0c94d61f8b
Severity
High

Why it matters

An attacker who can get a poisoned checkpoint into a training pipeline (via a shared model hub, compromised artifact store, or supply chain dependency) gets arbitrary code execution on whatever machine resumes that checkpoint, potentially reaching training infrastructure, credentials, or data.

What you can do

  • Load checkpoints with torch.load(..., weights_only=True) wherever the loader supports it, and audit code paths like _load_rng_state() that may bypass this.
  • Only resume training from checkpoints you or trusted parties produced; verify checksums or signatures on checkpoint files pulled from external sources.
  • Restrict what training processes can execute or write to, especially avoiding writable staging directories like /tmp for checkpoint storage.
  • Review any case where a training process reads a checkpoint and then spawns a shell or script within minutes, especially if the checkpoint came from an unusual or writable location.

Known benign look-alikes

  • Training script that legitimately resumes from a checkpoint and then spawns curl or wget to download a dataset shard or upload an artifact. Mitigated by requiring the fetch to be spawned inside the lineage of the exact python instance that read a pickled checkpoint, within 5 minutes, and by reporting (never blocking).
  • MLflow / Weights-and-Biases integrations shelling out (git, gcloud, aws) around a checkpoint load. Mitigated by excluding bare shell invocations from the sufficient (tier-A) indicator set - a plain `sh -c` / `python -c` child is only reported when the checkpoint itself was read out of a writable staging directory (/tmp, /var/tmp, /dev/shm, Downloads, Users/Shared).
  • Distributed launchers (torchrun, deepspeed, accelerate) spawning worker python processes with `-c`. Same tier-B gate applies.
  • Bootstrap or Dockerfile-style setup steps that run `chmod +x` or a base64-decoded installer in the same process tree as a resumed training run.
  • Checkpoint conversion utilities that load a legacy .bin/.pth and then invoke a packaging helper. Reported once per (reader instance, artifact) thanks to the rule store de-duplication, so this cannot become alert spam.

References

Related threats