summaryrefslogtreecommitdiffstats
path: root/meta-python/recipes-devtools/python/python3-cbor2
diff options
context:
space:
mode:
authorHitendra Prajapati <hprajapati@mvista.com>2026-02-26 18:06:45 +0530
committerGyorgy Sarvari <skandigraun@gmail.com>2026-02-27 14:28:43 +0100
commitf19f8995e2a97859a94cfac6d8b1588b283b8f84 (patch)
tree7c5639837e6475875ccb4c1506aed04925a0ae80 /meta-python/recipes-devtools/python/python3-cbor2
parentd9010e70c426fd08a2483c1587ada2b1f46719fe (diff)
downloadmeta-openembedded-f19f8995e2a97859a94cfac6d8b1588b283b8f84.tar.gz
python3-cbor2: patch CVE-2025-68131
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 Dropped changes to the changelog from the original commit. Signed-off-by: Hitendra Prajapati <hprajapati@mvista.com> Signed-off-by: Gyorgy Sarvari <skandigraun@gmail.com>
Diffstat (limited to 'meta-python/recipes-devtools/python/python3-cbor2')
-rw-r--r--meta-python/recipes-devtools/python/python3-cbor2/CVE-2025-68131.patch507
1 files changed, 507 insertions, 0 deletions
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..4c5310edfa
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-cbor2/CVE-2025-68131.patch
@@ -0,0 +1,507 @@
1From 7be0ee8272a541e291f13ed67d69b951ae42a9da Mon Sep 17 00:00:00 2001
2From: Andreas Eriksen <andreer@vespa.ai>
3Date: Thu, 18 Dec 2025 16:48:26 +0100
4Subject: [PATCH] Merge commit from fork
5
6* track depth of recursive encode/decode, clear shared refs on start
7
8* test that shared refs are cleared on start
9
10* add fix-shared-state-reset to version history
11
12* clear shared state _after_ encode/decode
13
14* use PY_SSIZE_T_MAX to clear shareables list
15
16* use context manager for python decoder depth tracking
17
18* use context manager for python encoder depth tracking
19
20CVE: CVE-2025-68131
21Upstream-Status: Backport [https://github.com/agronholm/cbor2/commit/f1d701cd2c411ee40bb1fe383afe7f365f35abf0]
22Signed-off-by: Hitendra Prajapati <hprajapati@mvista.com>
23---
24 cbor2/decoder.py | 26 ++++++++++++++--
25 cbor2/encoder.py | 42 +++++++++++++++++++++-----
26 source/decoder.c | 28 +++++++++++++++++-
27 source/decoder.h | 1 +
28 source/encoder.c | 23 +++++++++++++--
29 source/encoder.h | 1 +
30 tests/test_decoder.py | 62 ++++++++++++++++++++++++++++++++++++++
31 tests/test_encoder.py | 69 +++++++++++++++++++++++++++++++++++++++++++
32 8 files changed, 239 insertions(+), 13 deletions(-)
33
34diff --git a/cbor2/decoder.py b/cbor2/decoder.py
35index be7198b..f2d818c 100644
36--- a/cbor2/decoder.py
37+++ b/cbor2/decoder.py
38@@ -2,6 +2,7 @@ import re
39 import struct
40 import sys
41 from collections.abc import Mapping
42+from contextlib import contextmanager
43 from datetime import datetime, timedelta, timezone
44 from io import BytesIO
45
46@@ -40,7 +41,7 @@ class CBORDecoder:
47
48 __slots__ = (
49 '_tag_hook', '_object_hook', '_share_index', '_shareables', '_fp_read',
50- '_immutable', '_str_errors', '_stringref_namespace')
51+ '_immutable', '_str_errors', '_stringref_namespace', '_decode_depth')
52
53 def __init__(self, fp, tag_hook=None, object_hook=None,
54 str_errors='strict'):
55@@ -52,6 +53,7 @@ class CBORDecoder:
56 self._shareables = []
57 self._stringref_namespace = None
58 self._immutable = False
59+ self._decode_depth = 0
60
61 @property
62 def immutable(self):
63@@ -173,13 +175,32 @@ class CBORDecoder:
64 if unshared:
65 self._share_index = old_index
66
67+ @contextmanager
68+ def _decoding_context(self):
69+ """
70+ Context manager for tracking decode depth and clearing shared state.
71+ Shared state is cleared at the end of each top-level decode to prevent
72+ shared references from leaking between independent decode operations.
73+ Nested calls (from hooks) must preserve the state.
74+ """
75+ self._decode_depth += 1
76+ try:
77+ yield
78+ finally:
79+ self._decode_depth -= 1
80+ assert self._decode_depth >= 0
81+ if self._decode_depth == 0:
82+ self._shareables.clear()
83+ self._share_index = None
84+
85 def decode(self):
86 """
87 Decode the next value from the stream.
88
89 :raises CBORDecodeError: if there is any problem decoding the stream
90 """
91- return self._decode()
92+ with self._decoding_context():
93+ return self._decode()
94
95 def decode_from_bytes(self, buf):
96 """
97@@ -190,6 +211,7 @@ class CBORDecoder:
98 object needs to be decoded separately from the rest but while still
99 taking advantage of the shared value registry.
100 """
101+ with self._decoding_context():
102 with BytesIO(buf) as fp:
103 old_fp = self.fp
104 self.fp = fp
105diff --git a/cbor2/encoder.py b/cbor2/encoder.py
106index 42526c0..0a5722d 100644
107--- a/cbor2/encoder.py
108+++ b/cbor2/encoder.py
109@@ -109,7 +109,7 @@ class CBOREncoder:
110 __slots__ = (
111 'datetime_as_timestamp', '_timezone', '_default', 'value_sharing',
112 '_fp_write', '_shared_containers', '_encoders', '_canonical',
113- 'string_referencing', 'string_namespacing', '_string_references')
114+ 'string_referencing', 'string_namespacing', '_string_references', '_encode_depth')
115
116 def __init__(self, fp, datetime_as_timestamp=False, timezone=None,
117 value_sharing=False, default=None, canonical=False,
118@@ -124,6 +124,7 @@ class CBOREncoder:
119 self._canonical = canonical
120 self._shared_containers = {} # indexes used for value sharing
121 self._string_references = {} # indexes used for string references
122+ self._encode_depth = 0
123 self._encoders = default_encoders.copy()
124 if canonical:
125 self._encoders.update(canonical_encoders)
126@@ -236,6 +237,23 @@ class CBOREncoder:
127 """
128 self._fp_write(data)
129
130+ @contextmanager
131+ def _encoding_context(self):
132+ """
133+ Context manager for tracking encode depth and clearing shared state.
134+ Shared state is cleared at the end of each top-level encode to prevent
135+ shared references from leaking between independent encode operations.
136+ Nested calls (from hooks) must preserve the state.
137+ """
138+ self._encode_depth += 1
139+ try:
140+ yield
141+ finally:
142+ self._encode_depth -= 1
143+ if self._encode_depth == 0:
144+ self._shared_containers.clear()
145+ self._string_references.clear()
146+
147 def encode(self, obj):
148 """
149 Encode the given object using CBOR.
150@@ -243,6 +261,14 @@ class CBOREncoder:
151 :param obj:
152 the object to encode
153 """
154+ with self._encoding_context():
155+ self._encode_value(obj)
156+ def _encode_value(self, obj: Any) -> None:
157+ """
158+ Internal fast path for encoding - used by built-in encoders.
159+ External code should use encode() instead, which properly manages
160+ shared state between independent encode operations.
161+ """
162 obj_type = obj.__class__
163 encoder = (
164 self._encoders.get(obj_type) or
165@@ -390,14 +416,14 @@ class CBOREncoder:
166 def encode_array(self, value):
167 self.encode_length(4, len(value))
168 for item in value:
169- self.encode(item)
170+ self._encode_value(item)
171
172 @container_encoder
173 def encode_map(self, value):
174 self.encode_length(5, len(value))
175 for key, val in value.items():
176- self.encode(key)
177- self.encode(val)
178+ self._encode_value(key)
179+ self._encode_value(val)
180
181 def encode_sortable_key(self, value):
182 """
183@@ -422,10 +448,10 @@ class CBOREncoder:
184 # String referencing requires that the order encoded is
185 # the same as the order emitted so string references are
186 # generated after an order is determined
187- self.encode(realkey)
188+ self._encode_value(realkey)
189 else:
190 self._fp_write(sortkey[1])
191- self.encode(value)
192+ self._encode_value(value)
193
194 def encode_semantic(self, value):
195 # Nested string reference domains are distinct
196@@ -436,7 +462,7 @@ class CBOREncoder:
197 self._string_references = {}
198
199 self.encode_length(6, value.tag)
200- self.encode(value.value)
201+ self._encode_value(value.value)
202
203 self.string_referencing = old_string_referencing
204 self._string_references = old_string_references
205@@ -489,7 +515,7 @@ class CBOREncoder:
206 def encode_stringref(self, value):
207 # Semantic tag 25
208 if not self._stringref(value):
209- self.encode(value)
210+ self._encode_value(value)
211
212 def encode_rational(self, value):
213 # Semantic tag 30
214diff --git a/source/decoder.c b/source/decoder.c
215index d4da393..d8df2b5 100644
216--- a/source/decoder.c
217+++ b/source/decoder.c
218@@ -139,6 +139,7 @@ CBORDecoder_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
219 self->str_errors = PyBytes_FromString("strict");
220 self->immutable = false;
221 self->shared_index = -1;
222+ self->decode_depth = 0;
223 }
224 return (PyObject *) self;
225 error:
226@@ -1747,11 +1748,30 @@ decode(CBORDecoderObject *self, DecodeOptions options)
227 }
228
229
230+// Reset shared state at the end of each top-level decode to prevent
231+// shared references from leaking between independent decode operations.
232+// Nested calls (from hooks) must preserve the state.
233+static inline void
234+clear_shareable_state(CBORDecoderObject *self)
235+{
236+ PyList_SetSlice(self->shareables, 0, PY_SSIZE_T_MAX, NULL);
237+ self->shared_index = -1;
238+}
239+
240+
241 // CBORDecoder.decode(self) -> obj
242 PyObject *
243 CBORDecoder_decode(CBORDecoderObject *self)
244 {
245- return decode(self, DECODE_NORMAL);
246+ PyObject *ret;
247+ self->decode_depth++;
248+ ret = decode(self, DECODE_NORMAL);
249+ self->decode_depth--;
250+ assert(self->decode_depth >= 0);
251+ if (self->decode_depth == 0) {
252+ clear_shareable_state(self);
253+ }
254+ return ret;
255 }
256
257
258@@ -1764,6 +1784,7 @@ CBORDecoder_decode_from_bytes(CBORDecoderObject *self, PyObject *data)
259 if (!_CBOR2_BytesIO && _CBOR2_init_BytesIO() == -1)
260 return NULL;
261
262+ self->decode_depth++;
263 save_read = self->read;
264 buf = PyObject_CallFunctionObjArgs(_CBOR2_BytesIO, data, NULL);
265 if (buf) {
266@@ -1775,6 +1796,11 @@ CBORDecoder_decode_from_bytes(CBORDecoderObject *self, PyObject *data)
267 Py_DECREF(buf);
268 }
269 self->read = save_read;
270+ self->decode_depth--;
271+ assert(self->decode_depth >= 0);
272+ if (self->decode_depth == 0) {
273+ clear_shareable_state(self);
274+ }
275 return ret;
276 }
277
278diff --git a/source/decoder.h b/source/decoder.h
279index 6bb6d52..a2f1bcb 100644
280--- a/source/decoder.h
281+++ b/source/decoder.h
282@@ -13,6 +13,7 @@ typedef struct {
283 PyObject *str_errors;
284 bool immutable;
285 Py_ssize_t shared_index;
286+ Py_ssize_t decode_depth;
287 } CBORDecoderObject;
288
289 extern PyTypeObject CBORDecoderType;
290diff --git a/source/encoder.c b/source/encoder.c
291index af2b9c5..ae3fd64 100644
292--- a/source/encoder.c
293+++ b/source/encoder.c
294@@ -112,6 +112,7 @@ CBOREncoder_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
295 self->shared_handler = NULL;
296 self->string_referencing = false;
297 self->string_namespacing = false;
298+ self->encode_depth = 0;
299 }
300 return (PyObject *) self;
301 }
302@@ -2016,17 +2017,35 @@ encode(CBOREncoderObject *self, PyObject *value)
303 }
304
305
306+// Reset shared state at the end of each top-level encode to prevent
307+// shared references from leaking between independent encode operations.
308+// Nested calls (from hooks or recursive encoding) must preserve the state.
309+static inline void
310+clear_shared_state(CBOREncoderObject *self)
311+{
312+ PyDict_Clear(self->shared);
313+ PyDict_Clear(self->string_references);
314+}
315+
316+
317 // CBOREncoder.encode(self, value)
318 PyObject *
319 CBOREncoder_encode(CBOREncoderObject *self, PyObject *value)
320 {
321 PyObject *ret;
322
323- // TODO reset shared dict?
324- if (Py_EnterRecursiveCall(" in CBOREncoder.encode"))
325+ self->encode_depth++;
326+ if (Py_EnterRecursiveCall(" in CBOREncoder.encode")) {
327+ self->encode_depth--;
328 return NULL;
329+ }
330 ret = encode(self, value);
331 Py_LeaveRecursiveCall();
332+ self->encode_depth--;
333+ assert(self->encode_depth >= 0);
334+ if (self->encode_depth == 0) {
335+ clear_shared_state(self);
336+ }
337 return ret;
338 }
339
340diff --git a/source/encoder.h b/source/encoder.h
341index b994ed9..7a68e2f 100644
342--- a/source/encoder.h
343+++ b/source/encoder.h
344@@ -23,6 +23,7 @@ typedef struct {
345 bool value_sharing;
346 bool string_referencing;
347 bool string_namespacing;
348+ Py_ssize_t encode_depth;
349 } CBOREncoderObject;
350
351 extern PyTypeObject CBOREncoderType;
352diff --git a/tests/test_decoder.py b/tests/test_decoder.py
353index d03e288..cc3af11 100644
354--- a/tests/test_decoder.py
355+++ b/tests/test_decoder.py
356@@ -713,6 +713,68 @@ def test_reserved_special_tags(impl, data, expected):
357 ('c400', '4'), ('c500', '5')
358 ],
359 )
360+
361+class TestDecoderReuse:
362+ """
363+ Tests for correct behavior when reusing CBORDecoder instances.
364+ """
365+
366+ def test_decoder_reuse_resets_shared_refs(self, impl):
367+ """
368+ Shared references should be scoped to a single decode operation,
369+ not persist across multiple decodes on the same decoder instance.
370+ """
371+ # Message with shareable tag (28)
372+ msg1 = impl.dumps(impl.CBORTag(28, "first_value"))
373+
374+ # Message with sharedref tag (29) referencing index 0
375+ msg2 = impl.dumps(impl.CBORTag(29, 0))
376+
377+ # Reuse decoder across messages
378+ decoder = impl.CBORDecoder(BytesIO(msg1))
379+ result1 = decoder.decode()
380+ assert result1 == "first_value"
381+
382+ # Second decode should fail - sharedref(0) doesn't exist in this context
383+ decoder.fp = BytesIO(msg2)
384+ with pytest.raises(impl.CBORDecodeValueError, match="shared reference"):
385+ decoder.decode()
386+
387+ def test_decode_from_bytes_resets_shared_refs(self, impl):
388+ """
389+ decode_from_bytes should also reset shared references between calls.
390+ """
391+ msg1 = impl.dumps(impl.CBORTag(28, "value"))
392+ msg2 = impl.dumps(impl.CBORTag(29, 0))
393+
394+ decoder = impl.CBORDecoder(BytesIO(b""))
395+ decoder.decode_from_bytes(msg1)
396+
397+ with pytest.raises(impl.CBORDecodeValueError, match="shared reference"):
398+ decoder.decode_from_bytes(msg2)
399+
400+ def test_shared_refs_within_single_decode(self, impl):
401+ """
402+ Shared references must work correctly within a single decode operation.
403+
404+ Note: This tests non-cyclic sibling references [shareable(x), sharedref(0)],
405+ which is a different pattern from test_cyclic_array/test_cyclic_map that
406+ test self-referencing structures like shareable([sharedref(0)]).
407+ """
408+ # [shareable("hello"), sharedref(0)] -> ["hello", "hello"]
409+ data = unhexlify(
410+ "82" # array(2)
411+ "d81c" # tag(28) shareable
412+ "65" # text(5)
413+ "68656c6c6f" # "hello"
414+ "d81d" # tag(29) sharedref
415+ "00" # unsigned(0)
416+ )
417+
418+ result = impl.loads(data)
419+ assert result == ["hello", "hello"]
420+ assert result[0] is result[1] # Same object reference
421+
422 def test_decimal_payload_unpacking(impl, data, expected):
423 with pytest.raises(impl.CBORDecodeValueError) as exc_info:
424 impl.loads(unhexlify(data))
425diff --git a/tests/test_encoder.py b/tests/test_encoder.py
426index 8c40000..c76d5e0 100644
427--- a/tests/test_encoder.py
428+++ b/tests/test_encoder.py
429@@ -522,6 +522,75 @@ def test_encode_stringrefs_dict(impl):
430 assert impl.dumps(value, string_referencing=True, canonical=True) == expected
431
432
433+class TestEncoderReuse:
434+ """
435+ Tests for correct behavior when reusing CBOREncoder instances.
436+ """
437+
438+ def test_encoder_reuse_resets_shared_containers(self, impl):
439+ """
440+ Shared container tracking should be scoped to a single encode operation,
441+ not persist across multiple encodes on the same encoder instance.
442+ """
443+ fp = BytesIO()
444+ encoder = impl.CBOREncoder(fp, value_sharing=True)
445+ shared_obj = ["hello"]
446+
447+ # First encode: object is tracked in shared containers
448+ encoder.encode([shared_obj, shared_obj])
449+
450+ # Second encode on new fp: should produce valid standalone CBOR
451+ # (not a sharedref pointing to stale first-encode data)
452+ encoder.fp = BytesIO()
453+ encoder.encode(shared_obj)
454+ second_output = encoder.fp.getvalue()
455+
456+ # The second output must be decodable on its own
457+ result = impl.loads(second_output)
458+ assert result == ["hello"]
459+
460+ def test_encode_to_bytes_resets_shared_containers(self, impl):
461+ """
462+ encode_to_bytes should also reset shared container tracking between calls.
463+ """
464+ fp = BytesIO()
465+ encoder = impl.CBOREncoder(fp, value_sharing=True)
466+ shared_obj = ["hello"]
467+
468+ # First encode
469+ encoder.encode_to_bytes([shared_obj, shared_obj])
470+
471+ # Second encode should produce valid standalone CBOR
472+ result_bytes = encoder.encode_to_bytes(shared_obj)
473+ result = impl.loads(result_bytes)
474+ assert result == ["hello"]
475+
476+ def test_encoder_hook_does_not_reset_state(self, impl):
477+ """
478+ When a custom encoder hook calls encode(), the shared container
479+ tracking should be preserved (not reset mid-operation).
480+ """
481+
482+ class Custom:
483+ def __init__(self, value):
484+ self.value = value
485+
486+ def custom_encoder(encoder, obj):
487+ # Hook encodes the wrapped value
488+ encoder.encode(obj.value)
489+
490+ # Encode a Custom wrapping a list
491+ data = impl.dumps(Custom(["a", "b"]), default=custom_encoder)
492+
493+ # Verify the output decodes correctly
494+ result = impl.loads(data)
495+ assert result == ["a", "b"]
496+
497+ # Test nested Custom objects - hook should work recursively
498+ data2 = impl.dumps(Custom(Custom(["x"])), default=custom_encoder)
499+ result2 = impl.loads(data2)
500+ assert result2 == ["x"]
501+
502 @pytest.mark.parametrize('tag', [-1, 2**64, 'f'], ids=['too small', 'too large', 'wrong type'])
503 def test_invalid_tag(impl, tag):
504 with pytest.raises(TypeError):
505--
5062.50.1
507