mirror of
https://github.com/openembedded/meta-openembedded.git
synced 2026-09-09 06:40:17 +00:00
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>
174 lines
6.1 KiB
Diff
174 lines
6.1 KiB
Diff
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",
|
|
[
|