Skip to content

Security: BaseModel.load uses torch.load without weights_only, enabling RCE on torch<2.6 #836

@shaun0927

Description

@shaun0927

1. System Info

  • PyPOTS: main @ 35cc268 (latest)
  • Environment-independent (loader-side issue)

2. Information

  • The official example scripts
  • My own created scripts

3. Reproduction

pypots/base.py:384 loads a saved model with plain torch.load:

PyPOTS/pypots/base.py

Lines 383 to 384 in 35cc268

map_location = self.device[0] if isinstance(self.device, list) else self.device
loaded_file = torch.load(path, map_location=map_location)

map_location = self.device[0] if isinstance(self.device, list) else self.device
loaded_file = torch.load(path, map_location=map_location)

weights_only is not passed, so the behavior depends on the installed PyTorch version:

torch version default weights_only behavior when loading a crafted .pypots file
1.10 – 2.5 False unpickles arbitrary Python objects → RCE via __reduce__
2.6+ True legitimate pypots model files fail to load (safe-mode refuses the pickled dict)

Minimal PoC (run against a file produced with torch.save):

import os, pathlib, tempfile, torch

marker = pathlib.Path(tempfile.gettempdir()) / "pwned.txt"

class RCE:
    def __reduce__(self):
        return (os.system, (f"echo PWNED > {marker}",))

payload = {"model_state_dict": {"x": torch.tensor([1.0])}, "boom": RCE()}
torch.save(payload, "evil.pt")

# Reproduces the torch<2.6 default behavior that PyPOTS currently inherits.
torch.load("evil.pt", weights_only=False)
print(marker.read_text())  # -> PWNED

PyPOTS's existing requirements/requirements.txt pin is torch>=1.10, so
both branches of the table above apply to real users today.

4. Expected behavior

Explicitly set weights_only=True by default in BaseModel.load(). Provide
an opt-in (e.g. load(..., allow_untrusted=True)) that logs a warning and
falls back to the unsafe loader only when the caller knowingly asks for it.
That way:

  • torch<2.6 users stop silently executing arbitrary code on load.
  • torch>=2.6 users stop getting confusing safe-mode failures; the project
    chooses the boundary instead of deferring to torch defaults.

Happy to open a follow-up PR with the code change + a regression test once
you confirm the preferred shape of the opt-in kwarg (or if you'd rather
bump torch>=2.6 and drop the legacy branch entirely).

Metadata

Metadata

Assignees

No one assigned

    Labels

    completedThe issue has been completedenhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions