python3-cbor2: Fix CVE-2026-26209

Scarthgap already contains 4810cd8c5b [3], which backports
fb4ee161 and e61a5f36 for read-ahead/read-size handling. The
associated submission [4] labels that work as CVE-2026-26209, but
it does not add the max_depth protection required for uncontrolled
recursion [2].

Complete the existing backport with the 5.9.0 max-depth chain:
- bcb6cea4: add the C decoder depth limit [1]
- 94e0d212: add the security-essential pure-Python depth limit
- 53521e7c: apply the required type correction
- a7ac10d5: raise the default depth limit to 400
- d903d62c: synchronize the C function signature default

The 5.9.0 upgrade description [5] also identifies max_depth as the
CVE fix. Full upstream commit links are recorded in the embedded
patch headers.

[1] https://github.com/agronholm/cbor2/commit/bcb6cea4edde1d00ff4f0eece883dea951f66e1b
[2] https://github.com/advisories/GHSA-3c37-wwvx-h642
[3] https://git.openembedded.org/meta-openembedded/commit/?id=4810cd8c5bbc0b4349a78eac85a6a882bc0b03a2
[4] https://www.mail-archive.com/openembedded-devel%40lists.openembedded.org/msg105607.html
[5] https://www.mail-archive.com/openembedded-devel%40lists.openembedded.org/msg105418.html

Signed-off-by: Devansh Patel <devanshp@cisco.com>
Signed-off-by: Anuj Mittal <anuj.mittal@oss.qualcomm.com>
This commit is contained in:
Devansh Patel
2026-09-01 06:57:25 +05:30
committed by Anuj Mittal
parent 216e1b3a2e
commit b8bd073f05
6 changed files with 749 additions and 0 deletions
@@ -0,0 +1,27 @@
From d4213ba74d2bf8039f7f062b49955d37a0f4ec02 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Alex=20Gr=C3=B6nholm?= <alex.gronholm@nextday.fi>
Date: Tue, 3 Mar 2026 01:26:17 +0200
Subject: [PATCH] Fixed ssize_t to Py_ssize_t
CVE: CVE-2026-26209
Upstream-Status: Backport [https://github.com/agronholm/cbor2/commit/53521e7ca96c7a19f8a529fe59ef566212a24b3f]
(cherry picked from commit 53521e7ca96c7a19f8a529fe59ef566212a24b3f)
Signed-off-by: Devansh Patel <devanshp@cisco.com>
---
source/decoder.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/source/decoder.h b/source/decoder.h
index 4536a4a..c4ef1c1 100644
--- a/source/decoder.h
+++ b/source/decoder.h
@@ -22,7 +22,7 @@ typedef struct CBORDecoderObject_ {
PyObject *shareables;
PyObject *stringref_namespace;
PyObject *str_errors;
- ssize_t max_depth;
+ Py_ssize_t max_depth;
bool immutable;
Py_ssize_t shared_index;
Py_ssize_t decode_depth;
@@ -0,0 +1,173 @@
From e51c954fec4bd3c6f6c4768a20afd8d06b8c59ba Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Alex=20Gr=C3=B6nholm?= <alex.gronholm@nextday.fi>
Date: Sun, 1 Mar 2026 16:49:59 +0200
Subject: [PATCH] Added the max_depth decoder parameter
CVE: CVE-2026-26209
Upstream-Status: Backport [https://github.com/agronholm/cbor2/commit/bcb6cea4edde1d00ff4f0eece883dea951f66e1b]
Backport Changes:
- Omitted docs/versionhistory.rst after it failed to cherry-pick because
Scarthgap 5.6.4 lacks the later release sections; all source and test
changes are retained.
(cherry picked from commit bcb6cea4edde1d00ff4f0eece883dea951f66e1b)
Signed-off-by: Devansh Patel <devanshp@cisco.com>
---
source/decoder.c | 26 +++++++++++++++-----------
source/decoder.h | 2 ++
tests/test_decoder.py | 15 +++++++++++++++
3 files changed, 32 insertions(+), 11 deletions(-)
diff --git a/source/decoder.c b/source/decoder.c
index f8adc93..04c9142 100644
--- a/source/decoder.c
+++ b/source/decoder.c
@@ -152,6 +152,7 @@ CBORDecoder_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
Py_INCREF(Py_None);
self->object_hook = Py_None;
self->str_errors = PyBytes_FromString("strict");
+ self->max_depth = CBOR2_DEFAULT_MAX_DEPTH;
self->immutable = false;
self->shared_index = -1;
self->decode_depth = 0;
@@ -169,19 +170,19 @@ error:
// CBORDecoder.__init__(self, fp=None, tag_hook=None, object_hook=None,
-// str_errors='strict', read_size=1)
+// str_errors='strict', read_size=1, *, max_depth=100)
int
CBORDecoder_init(CBORDecoderObject *self, PyObject *args, PyObject *kwargs)
{
static char *keywords[] = {
- "fp", "tag_hook", "object_hook", "str_errors", "read_size", NULL
+ "fp", "tag_hook", "object_hook", "str_errors", "read_size", "max_depth", NULL
};
PyObject *fp = NULL, *tag_hook = NULL, *object_hook = NULL,
*str_errors = NULL;
Py_ssize_t read_size = CBOR2_DEFAULT_READ_SIZE;
- if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|OOOn", keywords,
- &fp, &tag_hook, &object_hook, &str_errors, &read_size))
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|OOOnn", keywords,
+ &fp, &tag_hook, &object_hook, &str_errors, &read_size, &self->max_depth))
return -1;
if (read_size < 1) {
@@ -2159,9 +2160,17 @@ decode(CBORDecoderObject *self, DecodeOptions options)
self->shared_index = -1;
}
+ if (self->decode_depth == self->max_depth) {
+ PyErr_Format(
+ _CBOR2_CBORDecodeError,
+ "maximum container nesting depth (%u) exceeded", self->max_depth);
+ return NULL;
+ }
+
if (Py_EnterRecursiveCall(" in CBORDecoder.decode"))
return NULL;
+ self->decode_depth++;
if (self->fp_read(self, &lead.byte, 1) == 0) {
switch (lead.major) {
case 0: ret = decode_uint(self, lead.subtype); break;
@@ -2177,6 +2186,8 @@ decode(CBORDecoderObject *self, DecodeOptions options)
}
Py_LeaveRecursiveCall();
+ self->decode_depth--;
+
if (options & DECODE_IMMUTABLE)
self->immutable = old_immutable;
if (options & DECODE_UNSHARED)
@@ -2201,10 +2212,7 @@ PyObject *
CBORDecoder_decode(CBORDecoderObject *self)
{
PyObject *ret;
- self->decode_depth++;
ret = decode(self, DECODE_NORMAL);
- self->decode_depth--;
- assert(self->decode_depth >= 0);
if (self->decode_depth == 0) {
clear_shareable_state(self);
}
@@ -2228,7 +2236,6 @@ CBORDecoder_decode_from_bytes(CBORDecoderObject *self, PyObject *data)
if (!buf)
return NULL;
- self->decode_depth++;
save_read = self->read;
Py_INCREF(save_read); // Keep alive while we use a different read method
save_read_pos = self->read_pos;
@@ -2248,7 +2255,6 @@ CBORDecoder_decode_from_bytes(CBORDecoderObject *self, PyObject *data)
}
Py_DECREF(save_read);
Py_DECREF(buf);
- self->decode_depth--;
return NULL;
}
@@ -2257,7 +2263,6 @@ CBORDecoder_decode_from_bytes(CBORDecoderObject *self, PyObject *data)
Py_XDECREF(self->read); // Decrement BytesIO read method
self->read = save_read; // Restore saved read (already has correct refcount)
Py_DECREF(buf);
- self->decode_depth--;
if (is_nested) {
PyMem_Free(self->readahead);
@@ -2266,7 +2271,6 @@ CBORDecoder_decode_from_bytes(CBORDecoderObject *self, PyObject *data)
self->read_pos = save_read_pos;
self->read_len = save_read_len;
- assert(self->decode_depth >= 0);
if (self->decode_depth == 0) {
clear_shareable_state(self);
}
diff --git a/source/decoder.h b/source/decoder.h
index 3efff8b..6d465a4 100644
--- a/source/decoder.h
+++ b/source/decoder.h
@@ -6,6 +6,7 @@
// Default readahead buffer size for streaming reads.
// Set to 1 for backwards compatibility (no buffering).
#define CBOR2_DEFAULT_READ_SIZE 1
+#define CBOR2_DEFAULT_MAX_DEPTH 500
// Forward declaration for function pointer typedef
struct CBORDecoderObject_;
@@ -21,6 +22,7 @@ typedef struct CBORDecoderObject_ {
PyObject *shareables;
PyObject *stringref_namespace;
PyObject *str_errors;
+ ssize_t max_depth;
bool immutable;
Py_ssize_t shared_index;
Py_ssize_t decode_depth;
diff --git a/tests/test_decoder.py b/tests/test_decoder.py
index c5d1a9c..e0631af 100644
--- a/tests/test_decoder.py
+++ b/tests/test_decoder.py
@@ -138,6 +138,21 @@ def test_stream_position_after_decode(impl):
assert stream.read() == extra_data
+class TestMaximumDepth:
+ def test_default(self, impl) -> None:
+ with pytest.raises(
+ impl.CBORDecodeError,
+ match="maximum container nesting depth \\(500\\) exceeded",
+ ):
+ impl.loads(b"\x81" * 1000 + b"\x80")
+
+ def test_explicit(self, impl) -> None:
+ with pytest.raises(
+ impl.CBORDecodeError, match=r"maximum container nesting depth \(9\) exceeded"
+ ):
+ impl.loads(b"\x81" * 10 + b"\x80", max_depth=9)
+
+
@pytest.mark.parametrize(
"payload, expected",
[
@@ -0,0 +1,435 @@
From abb6fef29ad304fb95e08ff151d52548543816b5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Alex=20Gr=C3=B6nholm?= <alex.gronholm@nextday.fi>
Date: Tue, 3 Mar 2026 01:11:34 +0200
Subject: [PATCH] Added missing Python counterpart for max_depth
CVE: CVE-2026-26209
Upstream-Status: Backport [https://github.com/agronholm/cbor2/commit/94e0d2125fbfb183606afa9ef07754a8dba50748]
Backport Changes:
- Omitted docs/versionhistory.rst after it failed to cherry-pick because
Scarthgap 5.6.4 lacks the later release sections.
- Preserved the existing CVE-2025-68131 top-level shared-state cleanup
while routing recursive decoding through the depth-checked path.
- Omitted decode_complex() because CBOR tag 43000 is absent from 5.6.4.
- Omitted the upstream Generator import and _decoding_context() return
annotation removal because Scarthgap's CVE-2025-68131 base never
added that import or annotation.
(cherry picked from commit 94e0d2125fbfb183606afa9ef07754a8dba50748)
Signed-off-by: Devansh Patel <devanshp@cisco.com>
---
cbor2/_decoder.py | 116 ++++++++++++++++++++++--------------------
source/decoder.h | 2 +-
tests/test_decoder.py | 4 +-
3 files changed, 63 insertions(+), 59 deletions(-)
diff --git a/cbor2/_decoder.py b/cbor2/_decoder.py
index 5a1f65b..024c403 100644
--- a/cbor2/_decoder.py
+++ b/cbor2/_decoder.py
@@ -5,13 +5,13 @@ import struct
import sys
from codecs import getincrementaldecoder
from collections.abc import Callable, Mapping, Sequence
-from contextlib import contextmanager
from datetime import date, datetime, timedelta, timezone
from io import BytesIO
from typing import IO, TYPE_CHECKING, Any, TypeVar, cast, overload
from ._types import (
CBORDecodeEOF,
+ CBORDecodeError,
CBORDecodeValueError,
CBORSimpleValue,
CBORTag,
@@ -60,6 +60,7 @@ class CBORDecoder:
"_immutable",
"_str_errors",
"_stringref_namespace",
+ "_max_depth",
"_decode_depth",
)
@@ -73,6 +74,8 @@ class CBORDecoder:
object_hook: Callable[[CBORDecoder, dict[Any, Any]], Any] | None = None,
str_errors: Literal["strict", "error", "replace"] = "strict",
read_size: int = 1,
+ *,
+ max_depth: int = 100,
):
"""
:param fp:
@@ -98,6 +101,8 @@ class CBORDecoder:
position beyond the decoded data. This only matters if you need to reuse the
stream after decoding.
Ignored in the pure Python implementation, but included for API compatibility.
+ :param max_depth:
+ the maximum allowed container nesting depth
.. _Error Handlers: https://docs.python.org/3/library/codecs.html#error-handlers
@@ -110,6 +115,7 @@ class CBORDecoder:
self._shareables: list[object] = []
self._stringref_namespace: list[str | bytes] | None = None
self._immutable = False
+ self._max_depth = max_depth
self._decode_depth = 0
@property
@@ -217,13 +223,24 @@ class CBORDecoder:
return data
- def _decode(self, immutable: bool = False, unshared: bool = False) -> Any:
+ def decode(self, immutable: bool = False, unshared: bool = False) -> Any:
+ """
+ Decode the next value from the stream.
+
+ :raises CBORDecodeError: if there is any problem decoding the stream
+
+ """
+ if self._decode_depth > self._max_depth:
+ raise CBORDecodeError(f"maximum container nesting depth ({self._max_depth}) exceeded")
+
if immutable:
old_immutable = self._immutable
self._immutable = True
if unshared:
old_index = self._share_index
self._share_index = None
+
+ self._decode_depth += 1
try:
initial_byte = self.read(1)[0]
major_type = initial_byte >> 5
@@ -236,34 +253,12 @@ class CBORDecoder:
if unshared:
self._share_index = old_index
- @contextmanager
- def _decoding_context(self):
- """
- Context manager for tracking decode depth and clearing shared state.
-
- Shared state is cleared at the end of each top-level decode to prevent
- shared references from leaking between independent decode operations.
- Nested calls (from hooks) must preserve the state.
- """
- self._decode_depth += 1
- try:
- yield
- finally:
self._decode_depth -= 1
assert self._decode_depth >= 0
if self._decode_depth == 0:
self._shareables.clear()
self._share_index = None
- def decode(self) -> object:
- """
- Decode the next value from the stream.
-
- :raises CBORDecodeError: if there is any problem decoding the stream
- """
- with self._decoding_context():
- return self._decode()
-
def decode_from_bytes(self, buf: bytes) -> object:
"""
Wrap the given bytestring as a file and call :meth:`decode` with it as
@@ -273,13 +268,12 @@ class CBORDecoder:
object needs to be decoded separately from the rest but while still
taking advantage of the shared value registry.
"""
- with self._decoding_context():
- with BytesIO(buf) as fp:
- old_fp = self.fp
- self.fp = fp
- retval = self._decode()
- self.fp = old_fp
- return retval
+ with BytesIO(buf) as fp:
+ old_fp = self.fp
+ self.fp = fp
+ retval = self.decode()
+ self.fp = old_fp
+ return retval
@overload
def _decode_length(self, subtype: int) -> int: ...
@@ -430,7 +424,7 @@ class CBORDecoder:
if not self._immutable:
self.set_shareable(items)
while True:
- value = self._decode()
+ value = self.decode(unshared=True)
if value is break_marker:
break
else:
@@ -444,7 +438,7 @@ class CBORDecoder:
self.set_shareable(items)
for index in range(length):
- items.append(self._decode())
+ items.append(self.decode(unshared=True))
if self._immutable:
items_tuple = tuple(items)
@@ -461,17 +455,17 @@ class CBORDecoder:
dictionary: dict[Any, Any] = {}
self.set_shareable(dictionary)
while True:
- key = self._decode(immutable=True, unshared=True)
+ key = self.decode(immutable=True, unshared=True)
if key is break_marker:
break
else:
- dictionary[key] = self._decode(unshared=True)
+ dictionary[key] = self.decode(unshared=True)
else:
dictionary = {}
self.set_shareable(dictionary)
for _ in range(length):
- key = self._decode(immutable=True, unshared=True)
- dictionary[key] = self._decode(unshared=True)
+ key = self.decode(immutable=True, unshared=True)
+ dictionary[key] = self.decode(unshared=True)
if self._object_hook:
dictionary = self._object_hook(self, dictionary)
@@ -491,7 +485,7 @@ class CBORDecoder:
tag = CBORTag(tagnum, None)
self.set_shareable(tag)
- tag.value = self._decode(unshared=True)
+ tag.value = self.decode(unshared=True)
if self._tag_hook:
tag = self._tag_hook(self, tag)
@@ -516,17 +510,17 @@ class CBORDecoder:
#
def decode_epoch_date(self) -> date:
# Semantic tag 100
- value = self._decode()
+ value = self.decode()
return self.set_shareable(date.fromordinal(value + 719163))
def decode_date_string(self) -> date:
# Semantic tag 1004
- value = self._decode()
+ value = self.decode()
return self.set_shareable(date.fromisoformat(value))
def decode_datetime_string(self) -> datetime:
# Semantic tag 0
- value = self._decode()
+ value = self.decode()
match = timestamp_re.match(value)
if match:
(
@@ -574,7 +568,7 @@ class CBORDecoder:
def decode_epoch_datetime(self) -> datetime:
# Semantic tag 1
- value = self._decode()
+ value = self.decode()
try:
tmp = datetime.fromtimestamp(value, timezone.utc)
@@ -587,7 +581,7 @@ class CBORDecoder:
# Semantic tag 2
from binascii import hexlify
- value = self._decode()
+ value = self.decode()
if not isinstance(value, bytes):
raise CBORDecodeValueError("invalid bignum value " + str(value))
@@ -602,7 +596,7 @@ class CBORDecoder:
from decimal import Decimal
try:
- exp, sig = self._decode()
+ exp, sig = self.decode()
except (TypeError, ValueError) as e:
raise CBORDecodeValueError("Incorrect tag 4 payload") from e
tmp = Decimal(sig).as_tuple()
@@ -613,7 +607,7 @@ class CBORDecoder:
from decimal import Decimal
try:
- exp, sig = self._decode()
+ exp, sig = self.decode()
except (TypeError, ValueError) as e:
raise CBORDecodeValueError("Incorrect tag 5 payload") from e
@@ -624,7 +618,7 @@ class CBORDecoder:
if self._stringref_namespace is None:
raise CBORDecodeValueError("string reference outside of namespace")
- index: int = self._decode()
+ index: int = self.decode()
try:
value = self._stringref_namespace[index]
except IndexError:
@@ -638,13 +632,13 @@ class CBORDecoder:
self._share_index = len(self._shareables)
self._shareables.append(None)
try:
- return self._decode()
+ return self.decode()
finally:
self._share_index = old_index
def decode_sharedref(self) -> Any:
# Semantic tag 29
- value = self._decode(unshared=True)
+ value = self.decode(unshared=True)
try:
shared = self._shareables[value]
except IndexError:
@@ -659,7 +653,7 @@ class CBORDecoder:
# Semantic tag 30
from fractions import Fraction
- inputval = self._decode(immutable=True, unshared=True)
+ inputval = self.decode(immutable=True, unshared=True)
try:
value = Fraction(*inputval)
except (TypeError, ZeroDivisionError) as exc:
@@ -675,7 +669,7 @@ class CBORDecoder:
def decode_regexp(self) -> re.Pattern[str]:
# Semantic tag 35
try:
- value = re.compile(self._decode())
+ value = re.compile(self.decode())
except re.error as exc:
raise CBORDecodeValueError("error decoding regular expression") from exc
@@ -686,7 +680,7 @@ class CBORDecoder:
from email.parser import Parser
try:
- value = Parser().parsestr(self._decode())
+ value = Parser().parsestr(self.decode())
except TypeError as exc:
raise CBORDecodeValueError("error decoding MIME message") from exc
@@ -697,7 +691,7 @@ class CBORDecoder:
from uuid import UUID
try:
- value = UUID(bytes=self._decode())
+ value = UUID(bytes=self.decode())
except (TypeError, ValueError) as exc:
raise CBORDecodeValueError("error decoding UUID value") from exc
@@ -707,16 +701,16 @@ class CBORDecoder:
# Semantic tag 256
old_namespace = self._stringref_namespace
self._stringref_namespace = []
- value = self._decode()
+ value = self.decode()
self._stringref_namespace = old_namespace
return value
def decode_set(self) -> set[Any] | frozenset[Any]:
# Semantic tag 258
if self._immutable:
- return self.set_shareable(frozenset(self._decode(immutable=True)))
+ return self.set_shareable(frozenset(self.decode(immutable=True)))
else:
- return self.set_shareable(set(self._decode(immutable=True)))
+ return self.set_shareable(set(self.decode(immutable=True)))
def decode_ipaddress(self) -> IPv4Address | IPv6Address | CBORTag:
# Semantic tag 260
@@ -749,7 +743,7 @@ class CBORDecoder:
def decode_self_describe_cbor(self) -> Any:
# Semantic tag 55799
- return self._decode()
+ return self.decode()
#
# Special decoders (major tag 7)
@@ -822,6 +816,8 @@ def loads(
object_hook: Callable[[CBORDecoder, dict[Any, Any]], Any] | None = None,
str_errors: Literal["strict", "error", "replace"] = "strict",
read_size: int = 1,
+ *,
+ max_depth: int = 100,
) -> Any:
"""
Deserialize an object from a bytestring.
@@ -844,6 +840,8 @@ def loads(
the minimum number of bytes to read at a time.
Setting this to a higher value like 4096 improves performance.
Ignored in the pure Python implementation, but included for API compatibility.
+ :param max_depth:
+ the maximum allowed container nesting depth
:return:
the deserialized object
@@ -857,6 +855,7 @@ def loads(
object_hook=object_hook,
str_errors=str_errors,
read_size=read_size,
+ max_depth=max_depth,
).decode()
@@ -866,6 +865,8 @@ def load(
object_hook: Callable[[CBORDecoder, dict[Any, Any]], Any] | None = None,
str_errors: Literal["strict", "error", "replace"] = "strict",
read_size: int = 1,
+ *,
+ max_depth: int = 100,
) -> Any:
"""
Deserialize an object from an open file.
@@ -891,6 +892,8 @@ def load(
position beyond the decoded data. This only matters if you need to reuse the
stream after decoding.
Ignored in the pure Python implementation, but included for API compatibility.
+ :param max_depth:
+ the maximum allowed container nesting depth
:return:
the deserialized object
@@ -903,4 +906,5 @@ def load(
object_hook=object_hook,
str_errors=str_errors,
read_size=read_size,
+ max_depth=max_depth,
).decode()
diff --git a/source/decoder.h b/source/decoder.h
index 6d465a4..4536a4a 100644
--- a/source/decoder.h
+++ b/source/decoder.h
@@ -6,7 +6,7 @@
// Default readahead buffer size for streaming reads.
// Set to 1 for backwards compatibility (no buffering).
#define CBOR2_DEFAULT_READ_SIZE 1
-#define CBOR2_DEFAULT_MAX_DEPTH 500
+#define CBOR2_DEFAULT_MAX_DEPTH 100
// Forward declaration for function pointer typedef
struct CBORDecoderObject_;
diff --git a/tests/test_decoder.py b/tests/test_decoder.py
index e0631af..5a90adf 100644
--- a/tests/test_decoder.py
+++ b/tests/test_decoder.py
@@ -142,9 +142,9 @@ class TestMaximumDepth:
def test_default(self, impl) -> None:
with pytest.raises(
impl.CBORDecodeError,
- match="maximum container nesting depth \\(500\\) exceeded",
+ match="maximum container nesting depth \\(100\\) exceeded",
):
- impl.loads(b"\x81" * 1000 + b"\x80")
+ impl.loads(b"\x81" * 101 + b"\x80")
def test_explicit(self, impl) -> None:
with pytest.raises(
@@ -0,0 +1,81 @@
From 3aa613d4b3ec1ed78dcaf5577cea867309d228c8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Alex=20Gr=C3=B6nholm?= <alex.gronholm@nextday.fi>
Date: Sat, 21 Mar 2026 23:48:20 +0200
Subject: [PATCH] Upped the max_depth value to 400
CVE: CVE-2026-26209
Upstream-Status: Backport [https://github.com/agronholm/cbor2/commit/a7ac10d5cbb7a8622e8270c201a131ac2abc26c7]
Backport Changes:
- Omitted docs/versionhistory.rst after it failed to cherry-pick because
Scarthgap 5.6.4 lacks the later release sections; all source and test
changes are retained.
(cherry picked from commit a7ac10d5cbb7a8622e8270c201a131ac2abc26c7)
Signed-off-by: Devansh Patel <devanshp@cisco.com>
---
cbor2/_decoder.py | 6 +++---
source/decoder.h | 2 +-
tests/test_decoder.py | 4 ++--
3 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/cbor2/_decoder.py b/cbor2/_decoder.py
index 024c403..cccd0ac 100644
--- a/cbor2/_decoder.py
+++ b/cbor2/_decoder.py
@@ -75,7 +75,7 @@ class CBORDecoder:
str_errors: Literal["strict", "error", "replace"] = "strict",
read_size: int = 1,
*,
- max_depth: int = 100,
+ max_depth: int = 400,
):
"""
:param fp:
@@ -817,7 +817,7 @@ def loads(
str_errors: Literal["strict", "error", "replace"] = "strict",
read_size: int = 1,
*,
- max_depth: int = 100,
+ max_depth: int = 400,
) -> Any:
"""
Deserialize an object from a bytestring.
@@ -866,7 +866,7 @@ def load(
str_errors: Literal["strict", "error", "replace"] = "strict",
read_size: int = 1,
*,
- max_depth: int = 100,
+ max_depth: int = 400,
) -> Any:
"""
Deserialize an object from an open file.
diff --git a/source/decoder.h b/source/decoder.h
index c4ef1c1..2989fc1 100644
--- a/source/decoder.h
+++ b/source/decoder.h
@@ -6,7 +6,7 @@
// Default readahead buffer size for streaming reads.
// Set to 1 for backwards compatibility (no buffering).
#define CBOR2_DEFAULT_READ_SIZE 1
-#define CBOR2_DEFAULT_MAX_DEPTH 100
+#define CBOR2_DEFAULT_MAX_DEPTH 400
// Forward declaration for function pointer typedef
struct CBORDecoderObject_;
diff --git a/tests/test_decoder.py b/tests/test_decoder.py
index 5a90adf..9e33ded 100644
--- a/tests/test_decoder.py
+++ b/tests/test_decoder.py
@@ -142,9 +142,9 @@ class TestMaximumDepth:
def test_default(self, impl) -> None:
with pytest.raises(
impl.CBORDecodeError,
- match="maximum container nesting depth \\(100\\) exceeded",
+ match="maximum container nesting depth \\(400\\) exceeded",
):
- impl.loads(b"\x81" * 101 + b"\x80")
+ impl.loads(b"\x81" * 401 + b"\x80")
def test_explicit(self, impl) -> None:
with pytest.raises(
@@ -0,0 +1,28 @@
From 83317379cb68b0caafae35f374e40d95eec106b0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Alex=20Gr=C3=B6nholm?= <alex.gronholm@nextday.fi>
Date: Sun, 22 Mar 2026 17:26:27 +0200
Subject: [PATCH] Updated the max_depth default value in the C function
signature
CVE: CVE-2026-26209
Upstream-Status: Backport [https://github.com/agronholm/cbor2/commit/d903d62c86de118e8abe626596f9be7b98ac44e9]
(cherry picked from commit d903d62c86de118e8abe626596f9be7b98ac44e9)
Signed-off-by: Devansh Patel <devanshp@cisco.com>
---
source/decoder.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/source/decoder.c b/source/decoder.c
index 04c9142..3ce2545 100644
--- a/source/decoder.c
+++ b/source/decoder.c
@@ -170,7 +170,7 @@ error:
// CBORDecoder.__init__(self, fp=None, tag_hook=None, object_hook=None,
-// str_errors='strict', read_size=1, *, max_depth=100)
+// str_errors='strict', read_size=1, *, max_depth=400)
int
CBORDecoder_init(CBORDecoderObject *self, PyObject *args, PyObject *kwargs)
{