1. System Info
- PyPOTS: main @ 35cc268 (latest)
- Environment-independent (loader-side issue)
2. Information
3. Reproduction
pypots/base.py:384 loads a saved model with plain torch.load:
|
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).
1. System Info
2. Information
3. Reproduction
pypots/base.py:384loads a saved model with plaintorch.load:PyPOTS/pypots/base.py
Lines 383 to 384 in 35cc268
weights_onlyis not passed, so the behavior depends on the installed PyTorch version:weights_only.pypotsfileFalse__reduce__Truedict)Minimal PoC (run against a file produced with
torch.save):PyPOTS's existing
requirements/requirements.txtpin istorch>=1.10, soboth branches of the table above apply to real users today.
4. Expected behavior
Explicitly set
weights_only=Trueby default inBaseModel.load(). Providean opt-in (e.g.
load(..., allow_untrusted=True)) that logs a warning andfalls back to the unsafe loader only when the caller knowingly asks for it.
That way:
torch<2.6users stop silently executing arbitrary code on load.torch>=2.6users stop getting confusing safe-mode failures; the projectchooses 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.6and drop the legacy branch entirely).