Skip to content

Commit b1e3c11

Browse files
committed
Merge branch 'master' into dev
2 parents 327db34 + 0d07ec2 commit b1e3c11

9 files changed

Lines changed: 99 additions & 4 deletions

File tree

AUTHORS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,3 +76,4 @@ Authors in order of the timeline of their contributions:
7676
- [Jim Cipar](https://github.com/jcipar) for the fix recursion depth limit when hashing numpy.datetime64
7777
- [Enji Cooper](https://github.com/ngie-eign) for converting legacy setuptools use to pyproject.toml
7878
- [Diogo Correia](https://github.com/diogotcorreia) for reporting security vulnerability in Delta and DeepDiff that could allow remote code execution.
79+
- [am-periphery](https://github.com/am-periphery) for reporting CVE-2026-33155: denial-of-service via crafted pickle payloads triggering massive memory allocation.

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
- `to_dict()` and `to_json()` now accept a `verbose_level` parameter and always return a usable text-view dict. When the original view is `'tree'`, they default to `verbose_level=2` for full detail. The old `view_override` parameter is removed. To get the previous results, you will need to pass the explicit verbose_level to `to_json` and `to_dict` if you are using the tree view.
66
- Dropping support for Python 3.9
77
- Support for python 3.14
8-
8+
- v8-6-2
9+
- Security fix (CVE-2026-33155): Prevent denial-of-service via crafted pickle payloads that trigger massive memory allocation through the REDUCE opcode. Size-sensitive callables like `bytes()` and `bytearray()` are now wrapped to reject allocations exceeding 128 MB.
910
- v8-6-1
1011
- Patched security vulnerability in the Delta class which was vulnerable to class pollution via its constructor, and when combined with a gadget available in DeltaDiff itself, it could lead to Denial of Service and Remote Code Execution (via insecure Pickle deserialization).
1112

CITATION.cff

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,5 @@ authors:
66
orcid: "https://orcid.org/0009-0009-5828-4345"
77
title: "DeepDiff"
88
version: 8.7.0
9-
date-released: 2024
9+
date-released: 2026
1010
url: "https://github.com/seperman/deepdiff"

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ DeepDiff 8-7-0
2929
- Dropping support for Python 3.9
3030
- Support for python 3.14
3131

32+
DeepDiff 8-6-2
33+
- **Security (CVE-2026-33155):** Fixed a memory exhaustion DoS vulnerability in `_RestrictedUnpickler` by limiting the maximum allocation size for `bytes` and `bytearray` during deserialization.
3234

3335
DeepDiff 8-6-1
3436
- Patched security vulnerability in the Delta class which was vulnerable to class pollution via its constructor, and when combined with a gadget available in DeltaDiff itself, it could lead to Denial of Service and Remote Code Execution (via insecure Pickle deserialization).

deepdiff/serialization.py

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,35 @@ def pretty(self, prefix: Optional[Union[str, Callable]]=None):
342342
return "\n".join(f"{prefix}{r}" for r in result)
343343

344344

345+
# Maximum size allowed for integer arguments to constructors that allocate
346+
# memory proportional to the argument (e.g. bytes(n), bytearray(n)).
347+
# This prevents denial-of-service via crafted pickle payloads. (CVE-2026-33155)
348+
_MAX_ALLOC_SIZE = 128 * 1024 * 1024 # 128 MB
349+
350+
# Callables where an integer argument directly controls memory allocation size.
351+
_SIZE_SENSITIVE_CALLABLES = frozenset({bytes, bytearray})
352+
353+
354+
class _SafeConstructor:
355+
"""Wraps a type constructor to prevent excessive memory allocation via the REDUCE opcode."""
356+
__slots__ = ('_wrapped',)
357+
358+
def __init__(self, wrapped):
359+
self._wrapped = wrapped
360+
361+
def __call__(self, *args, **kwargs):
362+
for arg in args:
363+
if isinstance(arg, int) and arg > _MAX_ALLOC_SIZE:
364+
raise pickle.UnpicklingError(
365+
"Refusing to create {}() with size {}: "
366+
"exceeds the maximum allowed size of {} bytes. "
367+
"This could be a denial-of-service attack payload.".format(
368+
self._wrapped.__name__, arg, _MAX_ALLOC_SIZE
369+
)
370+
)
371+
return self._wrapped(*args, **kwargs)
372+
373+
345374
class _RestrictedUnpickler(pickle.Unpickler):
346375

347376
def __init__(self, *args, **kwargs):
@@ -366,7 +395,11 @@ def find_class(self, module, name):
366395
module_obj = sys.modules[module]
367396
except KeyError:
368397
raise ModuleNotFoundError(MODULE_NOT_FOUND_MSG.format(module_dot_class)) from None
369-
return getattr(module_obj, name)
398+
cls = getattr(module_obj, name)
399+
# Wrap size-sensitive callables to prevent DoS via large allocations
400+
if cls in _SIZE_SENSITIVE_CALLABLES:
401+
return _SafeConstructor(cls)
402+
return cls
370403
# Forbid everything else.
371404
raise ForbiddenModule(FORBIDDEN_MODULE_MSG.format(module_dot_class)) from None
372405

docs/authors.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ and polars support.
118118
- `Enji Cooper <https://github.com/ngie-eign>`__ for converting legacy
119119
setuptools use to pyproject.toml
120120
- `Diogo Correia <https://github.com/diogotcorreia>`__ for reporting security vulnerability in Delta and DeepDiff that could allow remote code execution.
121+
- `am-periphery <https://github.com/am-periphery>`__ for reporting CVE-2026-33155: denial-of-service via crafted pickle payloads triggering massive memory allocation.
121122

122123

123124
.. _Sep Dehpour (Seperman): http://www.zepworks.com

docs/changelog.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ DeepDiff Changelog
1010
- `to_dict()` and `to_json()` now accept a `verbose_level` parameter and always return a usable text-view dict. When the original view is `'tree'`, they default to `verbose_level=2` for full detail. The old `view_override` parameter is removed. To get the previous results, you will need to pass the explicit verbose_level to `to_json` and `to_dict` if you are using the tree view.
1111
- Dropping support for Python 3.9
1212
- Support for python 3.14
13-
13+
- v8-6-2
14+
- Security fix (CVE-2026-33155): Prevent denial-of-service via crafted pickle payloads that trigger massive memory allocation through the REDUCE opcode. Size-sensitive callables like ``bytes()`` and ``bytearray()`` are now wrapped to reject allocations exceeding 128 MB.
1415
- v8-6-1
1516
- Patched security vulnerability in the Delta class which was vulnerable to class pollution via its constructor, and when combined with a gadget available in DeltaDiff itself, it could lead to Denial of Service and Remote Code Execution (via insecure Pickle deserialization).
1617

docs/index.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ DeepDiff 8-7-0
3939
- Dropping support for Python 3.9
4040
- Support for python 3.14
4141

42+
DeepDiff 8-6-2
43+
--------------
44+
45+
- Security fix (CVE-2026-33155): Prevent denial-of-service via crafted pickle payloads that trigger massive memory allocation through the REDUCE opcode. Size-sensitive callables like ``bytes()`` and ``bytearray()`` are now wrapped to reject allocations exceeding 128 MB.
4246

4347
DeepDiff 8-6-1
4448
--------------

tests/test_serialization.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,58 @@ def test_load_path_content_when_unsupported_format(self):
223223
load_path_content(path)
224224

225225

226+
class TestPicklingSecurity:
227+
228+
@pytest.mark.skipif(sys.platform == "win32", reason="Resource module is Unix-only")
229+
def test_restricted_unpickler_memory_exhaustion_cve(self):
230+
"""CVE-2026-33155: Prevent DoS via massive allocation through REDUCE opcode.
231+
232+
The payload calls bytes(10_000_000_000) which is allowed by find_class
233+
but would allocate ~9.3GB of memory. The fix should reject this before
234+
the allocation happens.
235+
"""
236+
import resource
237+
238+
# 1. Cap memory to 500MB to prevent system freezes during the test
239+
soft, hard = resource.getrlimit(resource.RLIMIT_AS)
240+
maxsize_bytes = 500 * 1024 * 1024
241+
resource.setrlimit(resource.RLIMIT_AS, (maxsize_bytes, hard))
242+
243+
try:
244+
# 2. Malicious payload: attempts to allocate ~9.3GB via bytes(10000000000)
245+
# This uses allowed builtins but passes a massive integer via REDUCE
246+
payload = (
247+
b"(dp0\n"
248+
b"S'_'\n"
249+
b"cbuiltins\nbytes\n"
250+
b"(I10000000000\n"
251+
b"tR"
252+
b"s."
253+
)
254+
255+
# 3. After the patch, deepdiff should catch the size violation
256+
# and raise UnpicklingError before attempting allocation.
257+
with pytest.raises((ValueError, UnpicklingError)):
258+
pickle_load(payload)
259+
finally:
260+
# Restore original memory limit so other tests are not affected
261+
resource.setrlimit(resource.RLIMIT_AS, (soft, hard))
262+
263+
def test_restricted_unpickler_allows_small_bytes(self):
264+
"""Ensure legitimate small bytes objects can still be deserialized."""
265+
# Payload: {'_': bytes(100)} — well within the 128MB limit
266+
payload = (
267+
b"(dp0\n"
268+
b"S'_'\n"
269+
b"cbuiltins\nbytes\n"
270+
b"(I100\n"
271+
b"tR"
272+
b"s."
273+
)
274+
result = pickle_load(payload)
275+
assert result == {'_': bytes(100)}
276+
277+
226278
class TestPickling:
227279

228280
def test_serialize(self):

0 commit comments

Comments
 (0)