Can you please rebase this and send a v2?

On Wed, Jan 14, 2026 at 9:01 PM Ankur Tyagi via lists.openembedded.org
<[email protected]> wrote:
>
> From: Ankur Tyagi <[email protected]>
>
> Backport the patch[1] which fixes this vulnerability as mentioned in the
> comment[2].
> Details: https://nvd.nist.gov/vuln/detail/CVE-2025-68131
>
> [1] 
> https://github.com/agronholm/cbor2/commit/f1d701cd2c411ee40bb1fe383afe7f365f35abf0
> [2] https://github.com/agronholm/cbor2/pull/268#issuecomment-3719179000
>
> Signed-off-by: Ankur Tyagi <[email protected]>
> ---
>  .../python/python3-cbor2/CVE-2025-68131.patch | 514 ++++++++++++++++++
>  .../python/python3-cbor2_5.6.4.bb             |   1 +
>  2 files changed, 515 insertions(+)
>  create mode 100644 
> meta-python/recipes-devtools/python/python3-cbor2/CVE-2025-68131.patch
>
> diff --git 
> a/meta-python/recipes-devtools/python/python3-cbor2/CVE-2025-68131.patch 
> b/meta-python/recipes-devtools/python/python3-cbor2/CVE-2025-68131.patch
> new file mode 100644
> index 0000000000..dd1131f0d1
> --- /dev/null
> +++ b/meta-python/recipes-devtools/python/python3-cbor2/CVE-2025-68131.patch
> @@ -0,0 +1,514 @@
> +From 7be0ee8272a541e291f13ed67d69b951ae42a9da Mon Sep 17 00:00:00 2001
> +From: Andreas Eriksen <[email protected]>
> +Date: Thu, 18 Dec 2025 16:48:26 +0100
> +Subject: [PATCH] Merge commit from fork
> +
> +* track depth of recursive encode/decode, clear shared refs on start
> +
> +* test that shared refs are cleared on start
> +
> +* add fix-shared-state-reset to version history
> +
> +* clear shared state _after_ encode/decode
> +
> +* use PY_SSIZE_T_MAX to clear shareables list
> +
> +* use context manager for python decoder depth tracking
> +
> +* use context manager for python encoder depth tracking
> +
> +CVE: CVE-2025-68131
> +Upstream-Status: Backport 
> [https://github.com/agronholm/cbor2/commit/f1d701cd2c411ee40bb1fe383afe7f365f35abf0]
> +Signed-off-by: Ankur Tyagi <[email protected]>
> +---
> + cbor2/_decoder.py     | 38 ++++++++++++++++++-----
> + cbor2/_encoder.py     | 44 ++++++++++++++++++++++-----
> + source/decoder.c      | 28 ++++++++++++++++-
> + source/decoder.h      |  1 +
> + source/encoder.c      | 23 ++++++++++++--
> + source/encoder.h      |  1 +
> + tests/test_decoder.py | 61 +++++++++++++++++++++++++++++++++++++
> + tests/test_encoder.py | 70 +++++++++++++++++++++++++++++++++++++++++++
> + 8 files changed, 249 insertions(+), 17 deletions(-)
> +
> +diff --git a/cbor2/_decoder.py b/cbor2/_decoder.py
> +index c8f1a8f..4aeadcf 100644
> +--- a/cbor2/_decoder.py
> ++++ b/cbor2/_decoder.py
> +@@ -5,6 +5,7 @@ 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
> +@@ -59,6 +60,7 @@ class CBORDecoder:
> +         "_immutable",
> +         "_str_errors",
> +         "_stringref_namespace",
> ++        "_decode_depth",
> +     )
> +
> +     _fp: IO[bytes]
> +@@ -100,6 +102,7 @@ class CBORDecoder:
> +         self._shareables: list[object] = []
> +         self._stringref_namespace: list[str | bytes] | None = None
> +         self._immutable = False
> ++        self._decode_depth = 0
> +
> +     @property
> +     def immutable(self) -> bool:
> +@@ -225,13 +228,33 @@ 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
> +         """
> +-        return self._decode()
> ++        with self._decoding_context():
> ++            return self._decode()
> +
> +     def decode_from_bytes(self, buf: bytes) -> object:
> +         """
> +@@ -242,12 +265,13 @@ class CBORDecoder:
> +         object needs to be decoded separately from the rest but while still
> +         taking advantage of the shared value registry.
> +         """
> +-        with BytesIO(buf) as fp:
> +-            old_fp = self.fp
> +-            self.fp = fp
> +-            retval = self._decode()
> +-            self.fp = old_fp
> +-            return retval
> ++        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
> +
> +     @overload
> +     def _decode_length(self, subtype: int) -> int: ...
> +diff --git a/cbor2/_encoder.py b/cbor2/_encoder.py
> +index 699c656..a653026 100644
> +--- a/cbor2/_encoder.py
> ++++ b/cbor2/_encoder.py
> +@@ -123,6 +123,7 @@ class CBOREncoder:
> +         "string_referencing",
> +         "string_namespacing",
> +         "_string_references",
> ++        "_encode_depth",
> +     )
> +
> +     _fp: IO[bytes]
> +@@ -183,6 +184,7 @@ class CBOREncoder:
> +             int, tuple[object, int | None]
> +         ] = {}  # indexes used for value sharing
> +         self._string_references: dict[str | bytes, int] = {}  # indexes 
> used for string references
> ++        self._encode_depth = 0
> +         self._encoders = default_encoders.copy()
> +         if canonical:
> +             self._encoders.update(canonical_encoders)
> +@@ -298,6 +300,24 @@ class CBOREncoder:
> +         """
> +         self._fp_write(data)
> +
> ++    @contextmanager
> ++    def _encoding_context(self):
> ++        """
> ++        Context manager for tracking encode depth and clearing shared state.
> ++
> ++        Shared state is cleared at the end of each top-level encode to 
> prevent
> ++        shared references from leaking between independent encode 
> operations.
> ++        Nested calls (from hooks) must preserve the state.
> ++        """
> ++        self._encode_depth += 1
> ++        try:
> ++            yield
> ++        finally:
> ++            self._encode_depth -= 1
> ++            if self._encode_depth == 0:
> ++                self._shared_containers.clear()
> ++                self._string_references.clear()
> ++
> +     def encode(self, obj: Any) -> None:
> +         """
> +         Encode the given object using CBOR.
> +@@ -305,6 +325,16 @@ class CBOREncoder:
> +         :param obj:
> +             the object to encode
> +         """
> ++        with self._encoding_context():
> ++            self._encode_value(obj)
> ++
> ++    def _encode_value(self, obj: Any) -> None:
> ++        """
> ++        Internal fast path for encoding - used by built-in encoders.
> ++
> ++        External code should use encode() instead, which properly manages
> ++        shared state between independent encode operations.
> ++        """
> +         obj_type = obj.__class__
> +         encoder = self._encoders.get(obj_type) or 
> self._find_encoder(obj_type) or self._default
> +         if not encoder:
> +@@ -448,14 +478,14 @@ class CBOREncoder:
> +     def encode_array(self, value: Sequence[Any]) -> None:
> +         self.encode_length(4, len(value))
> +         for item in value:
> +-            self.encode(item)
> ++            self._encode_value(item)
> +
> +     @container_encoder
> +     def encode_map(self, value: Mapping[Any, Any]) -> None:
> +         self.encode_length(5, len(value))
> +         for key, val in value.items():
> +-            self.encode(key)
> +-            self.encode(val)
> ++            self._encode_value(key)
> ++            self._encode_value(val)
> +
> +     def encode_sortable_key(self, value: Any) -> tuple[int, bytes]:
> +         """
> +@@ -477,10 +507,10 @@ class CBOREncoder:
> +                 # String referencing requires that the order encoded is
> +                 # the same as the order emitted so string references are
> +                 # generated after an order is determined
> +-                self.encode(realkey)
> ++                self._encode_value(realkey)
> +             else:
> +                 self._fp_write(sortkey[1])
> +-            self.encode(value)
> ++            self._encode_value(value)
> +
> +     def encode_semantic(self, value: CBORTag) -> None:
> +         # Nested string reference domains are distinct
> +@@ -491,7 +521,7 @@ class CBOREncoder:
> +             self._string_references = {}
> +
> +         self.encode_length(6, value.tag)
> +-        self.encode(value.value)
> ++        self._encode_value(value.value)
> +
> +         self.string_referencing = old_string_referencing
> +         self._string_references = old_string_references
> +@@ -554,7 +584,7 @@ class CBOREncoder:
> +     def encode_stringref(self, value: str | bytes) -> None:
> +         # Semantic tag 25
> +         if not self._stringref(value):
> +-            self.encode(value)
> ++            self._encode_value(value)
> +
> +     def encode_rational(self, value: Fraction) -> None:
> +         # Semantic tag 30
> +diff --git a/source/decoder.c b/source/decoder.c
> +index fd4d70c..033b73f 100644
> +--- a/source/decoder.c
> ++++ b/source/decoder.c
> +@@ -142,6 +142,7 @@ CBORDecoder_new(PyTypeObject *type, PyObject *args, 
> PyObject *kwargs)
> +         self->str_errors = PyBytes_FromString("strict");
> +         self->immutable = false;
> +         self->shared_index = -1;
> ++        self->decode_depth = 0;
> +     }
> +     return (PyObject *) self;
> + error:
> +@@ -2052,11 +2053,30 @@ decode(CBORDecoderObject *self, DecodeOptions 
> options)
> + }
> +
> +
> ++// Reset shared state 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.
> ++static inline void
> ++clear_shareable_state(CBORDecoderObject *self)
> ++{
> ++    PyList_SetSlice(self->shareables, 0, PY_SSIZE_T_MAX, NULL);
> ++    self->shared_index = -1;
> ++}
> ++
> ++
> + // CBORDecoder.decode(self) -> obj
> + PyObject *
> + CBORDecoder_decode(CBORDecoderObject *self)
> + {
> +-    return decode(self, DECODE_NORMAL);
> ++    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);
> ++    }
> ++    return ret;
> + }
> +
> +
> +@@ -2069,6 +2089,7 @@ CBORDecoder_decode_from_bytes(CBORDecoderObject *self, 
> PyObject *data)
> +     if (!_CBOR2_BytesIO && _CBOR2_init_BytesIO() == -1)
> +         return NULL;
> +
> ++    self->decode_depth++;
> +     save_read = self->read;
> +     buf = PyObject_CallFunctionObjArgs(_CBOR2_BytesIO, data, NULL);
> +     if (buf) {
> +@@ -2080,6 +2101,11 @@ CBORDecoder_decode_from_bytes(CBORDecoderObject 
> *self, PyObject *data)
> +         Py_DECREF(buf);
> +     }
> +     self->read = save_read;
> ++    self->decode_depth--;
> ++    assert(self->decode_depth >= 0);
> ++    if (self->decode_depth == 0) {
> ++        clear_shareable_state(self);
> ++    }
> +     return ret;
> + }
> +
> +diff --git a/source/decoder.h b/source/decoder.h
> +index 6bb6d52..a2f1bcb 100644
> +--- a/source/decoder.h
> ++++ b/source/decoder.h
> +@@ -13,6 +13,7 @@ typedef struct {
> +     PyObject *str_errors;
> +     bool immutable;
> +     Py_ssize_t shared_index;
> ++    Py_ssize_t decode_depth;
> + } CBORDecoderObject;
> +
> + extern PyTypeObject CBORDecoderType;
> +diff --git a/source/encoder.c b/source/encoder.c
> +index a0670aa..a7738a0 100644
> +--- a/source/encoder.c
> ++++ b/source/encoder.c
> +@@ -113,6 +113,7 @@ CBOREncoder_new(PyTypeObject *type, PyObject *args, 
> PyObject *kwargs)
> +         self->shared_handler = NULL;
> +         self->string_referencing = false;
> +         self->string_namespacing = false;
> ++        self->encode_depth = 0;
> +     }
> +     return (PyObject *) self;
> + }
> +@@ -2027,17 +2028,35 @@ encode(CBOREncoderObject *self, PyObject *value)
> + }
> +
> +
> ++// Reset shared state at the end of each top-level encode to prevent
> ++// shared references from leaking between independent encode operations.
> ++// Nested calls (from hooks or recursive encoding) must preserve the state.
> ++static inline void
> ++clear_shared_state(CBOREncoderObject *self)
> ++{
> ++    PyDict_Clear(self->shared);
> ++    PyDict_Clear(self->string_references);
> ++}
> ++
> ++
> + // CBOREncoder.encode(self, value)
> + PyObject *
> + CBOREncoder_encode(CBOREncoderObject *self, PyObject *value)
> + {
> +     PyObject *ret;
> +
> +-    // TODO reset shared dict?
> +-    if (Py_EnterRecursiveCall(" in CBOREncoder.encode"))
> ++    self->encode_depth++;
> ++    if (Py_EnterRecursiveCall(" in CBOREncoder.encode")) {
> ++        self->encode_depth--;
> +         return NULL;
> ++    }
> +     ret = encode(self, value);
> +     Py_LeaveRecursiveCall();
> ++    self->encode_depth--;
> ++    assert(self->encode_depth >= 0);
> ++    if (self->encode_depth == 0) {
> ++        clear_shared_state(self);
> ++    }
> +     return ret;
> + }
> +
> +diff --git a/source/encoder.h b/source/encoder.h
> +index 8b2d696..0dcc46d 100644
> +--- a/source/encoder.h
> ++++ b/source/encoder.h
> +@@ -24,6 +24,7 @@ typedef struct {
> +     bool value_sharing;
> +     bool string_referencing;
> +     bool string_namespacing;
> ++    Py_ssize_t encode_depth;
> + } CBOREncoderObject;
> +
> + extern PyTypeObject CBOREncoderType;
> +diff --git a/tests/test_decoder.py b/tests/test_decoder.py
> +index 485c604..253d079 100644
> +--- a/tests/test_decoder.py
> ++++ b/tests/test_decoder.py
> +@@ -961,3 +961,64 @@ def test_oversized_read(impl, payload: bytes, tmp_path: 
> Path) -> None:
> +         dummy_path.write_bytes(payload)
> +         with dummy_path.open("rb") as f:
> +             impl.load(f)
> ++
> ++class TestDecoderReuse:
> ++    """
> ++    Tests for correct behavior when reusing CBORDecoder instances.
> ++    """
> ++
> ++    def test_decoder_reuse_resets_shared_refs(self, impl):
> ++        """
> ++        Shared references should be scoped to a single decode operation,
> ++        not persist across multiple decodes on the same decoder instance.
> ++        """
> ++        # Message with shareable tag (28)
> ++        msg1 = impl.dumps(impl.CBORTag(28, "first_value"))
> ++
> ++        # Message with sharedref tag (29) referencing index 0
> ++        msg2 = impl.dumps(impl.CBORTag(29, 0))
> ++
> ++        # Reuse decoder across messages
> ++        decoder = impl.CBORDecoder(BytesIO(msg1))
> ++        result1 = decoder.decode()
> ++        assert result1 == "first_value"
> ++
> ++        # Second decode should fail - sharedref(0) doesn't exist in this 
> context
> ++        decoder.fp = BytesIO(msg2)
> ++        with pytest.raises(impl.CBORDecodeValueError, match="shared 
> reference"):
> ++            decoder.decode()
> ++
> ++    def test_decode_from_bytes_resets_shared_refs(self, impl):
> ++        """
> ++        decode_from_bytes should also reset shared references between calls.
> ++        """
> ++        msg1 = impl.dumps(impl.CBORTag(28, "value"))
> ++        msg2 = impl.dumps(impl.CBORTag(29, 0))
> ++
> ++        decoder = impl.CBORDecoder(BytesIO(b""))
> ++        decoder.decode_from_bytes(msg1)
> ++
> ++        with pytest.raises(impl.CBORDecodeValueError, match="shared 
> reference"):
> ++            decoder.decode_from_bytes(msg2)
> ++
> ++    def test_shared_refs_within_single_decode(self, impl):
> ++        """
> ++        Shared references must work correctly within a single decode 
> operation.
> ++
> ++        Note: This tests non-cyclic sibling references [shareable(x), 
> sharedref(0)],
> ++        which is a different pattern from test_cyclic_array/test_cyclic_map 
> that
> ++        test self-referencing structures like shareable([sharedref(0)]).
> ++        """
> ++        # [shareable("hello"), sharedref(0)] -> ["hello", "hello"]
> ++        data = unhexlify(
> ++            "82"  # array(2)
> ++            "d81c"  # tag(28) shareable
> ++            "65"  # text(5)
> ++            "68656c6c6f"  # "hello"
> ++            "d81d"  # tag(29) sharedref
> ++            "00"  # unsigned(0)
> ++        )
> ++
> ++        result = impl.loads(data)
> ++        assert result == ["hello", "hello"]
> ++        assert result[0] is result[1]  # Same object reference
> +\ No newline at end of file
> +diff --git a/tests/test_encoder.py b/tests/test_encoder.py
> +index f2ef248..3ca6a95 100644
> +--- a/tests/test_encoder.py
> ++++ b/tests/test_encoder.py
> +@@ -654,3 +654,73 @@ def test_invariant_encode_decode(impl, val):
> +     undergoing an encode and decode)
> +     """
> +     assert impl.loads(impl.dumps(val)) == val
> ++
> ++
> ++class TestEncoderReuse:
> ++    """
> ++    Tests for correct behavior when reusing CBOREncoder instances.
> ++    """
> ++
> ++    def test_encoder_reuse_resets_shared_containers(self, impl):
> ++        """
> ++        Shared container tracking should be scoped to a single encode 
> operation,
> ++        not persist across multiple encodes on the same encoder instance.
> ++        """
> ++        fp = BytesIO()
> ++        encoder = impl.CBOREncoder(fp, value_sharing=True)
> ++        shared_obj = ["hello"]
> ++
> ++        # First encode: object is tracked in shared containers
> ++        encoder.encode([shared_obj, shared_obj])
> ++
> ++        # Second encode on new fp: should produce valid standalone CBOR
> ++        # (not a sharedref pointing to stale first-encode data)
> ++        encoder.fp = BytesIO()
> ++        encoder.encode(shared_obj)
> ++        second_output = encoder.fp.getvalue()
> ++
> ++        # The second output must be decodable on its own
> ++        result = impl.loads(second_output)
> ++        assert result == ["hello"]
> ++
> ++    def test_encode_to_bytes_resets_shared_containers(self, impl):
> ++        """
> ++        encode_to_bytes should also reset shared container tracking between 
> calls.
> ++        """
> ++        fp = BytesIO()
> ++        encoder = impl.CBOREncoder(fp, value_sharing=True)
> ++        shared_obj = ["hello"]
> ++
> ++        # First encode
> ++        encoder.encode_to_bytes([shared_obj, shared_obj])
> ++
> ++        # Second encode should produce valid standalone CBOR
> ++        result_bytes = encoder.encode_to_bytes(shared_obj)
> ++        result = impl.loads(result_bytes)
> ++        assert result == ["hello"]
> ++
> ++    def test_encoder_hook_does_not_reset_state(self, impl):
> ++        """
> ++        When a custom encoder hook calls encode(), the shared container
> ++        tracking should be preserved (not reset mid-operation).
> ++        """
> ++
> ++        class Custom:
> ++            def __init__(self, value):
> ++                self.value = value
> ++
> ++        def custom_encoder(encoder, obj):
> ++            # Hook encodes the wrapped value
> ++            encoder.encode(obj.value)
> ++
> ++        # Encode a Custom wrapping a list
> ++        data = impl.dumps(Custom(["a", "b"]), default=custom_encoder)
> ++
> ++        # Verify the output decodes correctly
> ++        result = impl.loads(data)
> ++        assert result == ["a", "b"]
> ++
> ++        # Test nested Custom objects - hook should work recursively
> ++        data2 = impl.dumps(Custom(Custom(["x"])), default=custom_encoder)
> ++        result2 = impl.loads(data2)
> ++        assert result2 == ["x"]
> +\ No newline at end of file
> diff --git a/meta-python/recipes-devtools/python/python3-cbor2_5.6.4.bb 
> b/meta-python/recipes-devtools/python/python3-cbor2_5.6.4.bb
> index f0c2964f34..0c2a4588ef 100644
> --- a/meta-python/recipes-devtools/python/python3-cbor2_5.6.4.bb
> +++ b/meta-python/recipes-devtools/python/python3-cbor2_5.6.4.bb
> @@ -12,6 +12,7 @@ DEPENDS += "python3-setuptools-scm-native"
>
>  SRC_URI += " \
>          file://run-ptest \
> +        file://CVE-2025-68131.patch \
>  "
>
>  RDEPENDS:${PN}-ptest += " \
>
> 
>
-=-=-=-=-=-=-=-=-=-=-=-
Links: You receive all messages sent to this group.
View/Reply Online (#123581): 
https://lists.openembedded.org/g/openembedded-devel/message/123581
Mute This Topic: https://lists.openembedded.org/mt/117260217/21656
Group Owner: [email protected]
Unsubscribe: https://lists.openembedded.org/g/openembedded-devel/unsub 
[[email protected]]
-=-=-=-=-=-=-=-=-=-=-=-

Reply via email to