Security context
CriticalGHSA-53q9-r3pm-6pq6 CVE-2025-32434CWE-502Published Apr 18, 2025

PyTorch: `torch.load` with `weights_only=True` leads to remote code execution

Research this vulnerability

Research is free — Hunters explains how the bug works, the root-cause code pattern, how the fix addresses it, and how to test whether a target is affected, in chat. Investigate & write exploit is a paid run — the engine reads the advisory and fix commits, then builds and validates a working proof-of-concept exploit with reproduction steps.

Affected versions

0 → fixed in 2.6.0

Details

# Description I found a Remote Command Execution (RCE) vulnerability in PyTorch. When loading model using torch.load with weights_only=True, it can still achieve RCE. # Background knowledge https://github.com/pytorch/pytorch/security As you can see, the PyTorch official documentation considers using `torch.load()` with `weights_only=True` to be safe. ![image](https://github.com/user-attachments/assets/fdaa8520-d66a-473a-ab1f-163d793de298) Since everyone knows that weights_only=False is unsafe, so they will use the weights_only=True to mitigate the seucirty issue. But now, I just proved that even if you use weights_only=True, it can still achieve RCE. # Credit This vulnerability was found by Ji'an Zhou.

The fix

Prevent legacy_load when weights_only=True (correctly)

pytorchbot· Jan 17, 2025, 11:02 PM+1778d4b8a920a
test/test_serialization.py+5 1
@@ -462,7 +462,11 @@ def _test_serialization_backwards_compat(self, weights_only):
b += [a[0].storage()]
b += [a[0].reshape(-1)[1:4].clone().storage()]
path = download_file('https://download.pytorch.org/test_data/legacy_serialized.pt')
- c = torch.load(path, weights_only=weights_only)
+ if weights_only:
+ with self.assertRaisesRegex(RuntimeError,
+ "Cannot use ``weights_only=True`` with files saved in the legacy .tar format."):
+ c = torch.load(path, weights_only=weights_only)
+ c = torch.load(path, weights_only=False)
self.assertEqual(b, c, atol=0, rtol=0)
self.assertTrue(isinstance(c[0], torch.FloatTensor))
self.assertTrue(isinstance(c[1], torch.FloatTensor))
torch/serialization.py+12 6
@@ -85,6 +85,13 @@
IS_WINDOWS = sys.platform == "win32"
+UNSAFE_MESSAGE = (
+ "In PyTorch 2.6, we changed the default value of the `weights_only` argument in `torch.load` "
+ "from `False` to `True`. Re-running `torch.load` with `weights_only` set to `False` will likely succeed, "
+ "but it can result in arbitrary code execution. Do it only if you got the file from a "
+ "trusted source."
+)
+
if not IS_WINDOWS:
from mmap import MAP_PRIVATE, MAP_SHARED
else:
@@ -1324,12 +1331,6 @@ def load(
>>> torch.load("module.pt", encoding="ascii", weights_only=False)
"""
torch._C._log_api_usage_once("torch.load")
- UNSAFE_MESSAGE = (
- "In PyTorch 2.6, we changed the default value of the `weights_only` argument in `torch.load` "
- "from `False` to `True`. Re-running `torch.load` with `weights_only` set to `False` will likely succeed, "
- "but it can result in arbitrary code execution. Do it only if you got the file from a "
- "trusted source."
- )
DOCS_MESSAGE = (
"\n\nCheck the documentation of torch.load to learn more about types accepted by default with "
"weights_only https://pytorch.org/docs/stable/generated/torch.load.html."
@@ -1592,6 +1593,11 @@ def persistent_load(saved_id):
with closing(
tarfile.open(fileobj=f, mode="r:", format=tarfile.PAX_FORMAT)
) as tar, mkdtemp() as tmpdir:
+ if pickle_module is _weights_only_unpickler:
+ raise RuntimeError(
+ "Cannot use ``weights_only=True`` with files saved in the "
+ "legacy .tar format. " + UNSAFE_MESSAGE
+ )
tar.extract("storages", path=tmpdir)
with open(os.path.join(tmpdir, "storages"), "rb", 0) as f:
num_storages = pickle_module.load(f, **pickle_load_args)

References