forked from pool/python-cbor2
Compare commits
4 Commits
| Author | SHA256 | Date | |
|---|---|---|---|
|
|
848a09ed4f | ||
| 6fe546da81 | |||
| 86be5cbd3a | |||
| 1ad01979cd |
71
CVE-2025-64076.patch
Normal file
71
CVE-2025-64076.patch
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
From 851473490281f82d82560b2368284ef33cf6e8f9 Mon Sep 17 00:00:00 2001
|
||||||
|
From: lizhenghao <sculizhenghao@foxmail.com>
|
||||||
|
Date: Wed, 22 Oct 2025 10:26:34 +0800
|
||||||
|
Subject: [PATCH 1/3] Fix: Fixed a read(-1) vulnerability caused by boundary
|
||||||
|
handling error in #264
|
||||||
|
|
||||||
|
---
|
||||||
|
source/decoder.c | 8 +++++++-
|
||||||
|
tests/test_decoder.py | 22 ++++++++++++++++++++++
|
||||||
|
2 files changed, 29 insertions(+), 1 deletion(-)
|
||||||
|
|
||||||
|
Index: cbor2-5.6.5/source/decoder.c
|
||||||
|
===================================================================
|
||||||
|
--- cbor2-5.6.5.orig/source/decoder.c
|
||||||
|
+++ cbor2-5.6.5/source/decoder.c
|
||||||
|
@@ -758,7 +758,7 @@ decode_definite_long_string(CBORDecoderO
|
||||||
|
char *buffer = NULL;
|
||||||
|
while (left) {
|
||||||
|
// Read up to 65536 bytes of data from the stream
|
||||||
|
- Py_ssize_t chunk_length = 65536 - buffer_size;
|
||||||
|
+ Py_ssize_t chunk_length = 65536 - buffer_length;
|
||||||
|
if (left < chunk_length)
|
||||||
|
chunk_length = left;
|
||||||
|
|
||||||
|
@@ -828,7 +828,13 @@ decode_definite_long_string(CBORDecoderO
|
||||||
|
memcpy(buffer, bytes_buffer + consumed, unconsumed);
|
||||||
|
}
|
||||||
|
buffer_length = unconsumed;
|
||||||
|
+ } else {
|
||||||
|
+ // All bytes consumed, reset buffer_length
|
||||||
|
+ buffer_length = 0;
|
||||||
|
}
|
||||||
|
+
|
||||||
|
+ Py_DECREF(chunk);
|
||||||
|
+ chunk = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ret && string_namespace_add(self, ret, length) == -1)
|
||||||
|
Index: cbor2-5.6.5/tests/test_decoder.py
|
||||||
|
===================================================================
|
||||||
|
--- cbor2-5.6.5.orig/tests/test_decoder.py
|
||||||
|
+++ cbor2-5.6.5/tests/test_decoder.py
|
||||||
|
@@ -260,6 +260,28 @@ def test_string_oversized(impl) -> None:
|
||||||
|
(impl.loads(unhexlify("aeaeaeaeaeaeaeaeae0108c29843d90100d8249f0000aeaeffc26ca799")),)
|
||||||
|
|
||||||
|
|
||||||
|
+def test_string_issue_264_multiple_chunks_utf8_boundary(impl) -> None:
|
||||||
|
+ """Test for Issue #264: UTF-8 characters split across multiple 65536-byte chunk boundaries."""
|
||||||
|
+ import struct
|
||||||
|
+
|
||||||
|
+ # Construct: 65535 'a' + '€' (3 bytes) + 65533 'b' + '€' (3 bytes) + 100 'd'
|
||||||
|
+ # Total: 131174 bytes, which spans 3 chunks (65536 + 65536 + 102)
|
||||||
|
+ total_bytes = 65535 + 3 + 65533 + 3 + 100
|
||||||
|
+
|
||||||
|
+ payload = b"\x7a" + struct.pack(">I", total_bytes) # major type 3, 4-byte length
|
||||||
|
+ payload += b"a" * 65535
|
||||||
|
+ payload += "€".encode() # U+20AC: E2 82 AC
|
||||||
|
+ payload += b"b" * 65533
|
||||||
|
+ payload += "€".encode()
|
||||||
|
+ payload += b"d" * 100
|
||||||
|
+
|
||||||
|
+ expected = "a" * 65535 + "€" + "b" * 65533 + "€" + "d" * 100
|
||||||
|
+
|
||||||
|
+ result = impl.loads(payload)
|
||||||
|
+ assert result == expected
|
||||||
|
+ assert len(result) == 131170 # 65535 + 1 + 65533 + 1 + 100 characters
|
||||||
|
+
|
||||||
|
+
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"payload, expected",
|
||||||
|
[
|
||||||
509
CVE-2025-68131.patch
Normal file
509
CVE-2025-68131.patch
Normal file
@@ -0,0 +1,509 @@
|
|||||||
|
From f1d701cd2c411ee40bb1fe383afe7f365f35abf0 Mon Sep 17 00:00:00 2001
|
||||||
|
From: Andreas Eriksen <andreer@vespa.ai>
|
||||||
|
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
|
||||||
|
---
|
||||||
|
cbor2/_decoder.py | 38 +++++++++++++++++-----
|
||||||
|
cbor2/_encoder.py | 44 +++++++++++++++++++++-----
|
||||||
|
docs/versionhistory.rst | 5 +++
|
||||||
|
source/decoder.c | 28 ++++++++++++++++-
|
||||||
|
source/decoder.h | 1 +
|
||||||
|
source/encoder.c | 23 ++++++++++++--
|
||||||
|
source/encoder.h | 1 +
|
||||||
|
tests/test_decoder.py | 62 ++++++++++++++++++++++++++++++++++++
|
||||||
|
tests/test_encoder.py | 70 +++++++++++++++++++++++++++++++++++++++++
|
||||||
|
9 files changed, 255 insertions(+), 17 deletions(-)
|
||||||
|
|
||||||
|
Index: cbor2-5.6.5/cbor2/_decoder.py
|
||||||
|
===================================================================
|
||||||
|
--- cbor2-5.6.5.orig/cbor2/_decoder.py
|
||||||
|
+++ cbor2-5.6.5/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: ...
|
||||||
|
Index: cbor2-5.6.5/cbor2/_encoder.py
|
||||||
|
===================================================================
|
||||||
|
--- cbor2-5.6.5.orig/cbor2/_encoder.py
|
||||||
|
+++ cbor2-5.6.5/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
|
||||||
|
Index: cbor2-5.6.5/source/decoder.c
|
||||||
|
===================================================================
|
||||||
|
--- cbor2-5.6.5.orig/source/decoder.c
|
||||||
|
+++ cbor2-5.6.5/source/decoder.c
|
||||||
|
@@ -142,6 +142,7 @@ CBORDecoder_new(PyTypeObject *type, PyOb
|
||||||
|
self->str_errors = PyBytes_FromString("strict");
|
||||||
|
self->immutable = false;
|
||||||
|
self->shared_index = -1;
|
||||||
|
+ self->decode_depth = 0;
|
||||||
|
}
|
||||||
|
return (PyObject *) self;
|
||||||
|
error:
|
||||||
|
@@ -2058,11 +2059,30 @@ decode(CBORDecoderObject *self, DecodeOp
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
+// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@@ -2075,6 +2095,7 @@ CBORDecoder_decode_from_bytes(CBORDecode
|
||||||
|
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) {
|
||||||
|
@@ -2086,6 +2107,11 @@ CBORDecoder_decode_from_bytes(CBORDecode
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
Index: cbor2-5.6.5/source/decoder.h
|
||||||
|
===================================================================
|
||||||
|
--- cbor2-5.6.5.orig/source/decoder.h
|
||||||
|
+++ cbor2-5.6.5/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;
|
||||||
|
Index: cbor2-5.6.5/source/encoder.c
|
||||||
|
===================================================================
|
||||||
|
--- cbor2-5.6.5.orig/source/encoder.c
|
||||||
|
+++ cbor2-5.6.5/source/encoder.c
|
||||||
|
@@ -113,6 +113,7 @@ CBOREncoder_new(PyTypeObject *type, PyOb
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
+// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
Index: cbor2-5.6.5/source/encoder.h
|
||||||
|
===================================================================
|
||||||
|
--- cbor2-5.6.5.orig/source/encoder.h
|
||||||
|
+++ cbor2-5.6.5/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;
|
||||||
|
Index: cbor2-5.6.5/tests/test_decoder.py
|
||||||
|
===================================================================
|
||||||
|
--- cbor2-5.6.5.orig/tests/test_decoder.py
|
||||||
|
+++ cbor2-5.6.5/tests/test_decoder.py
|
||||||
|
@@ -983,3 +983,65 @@ def test_oversized_read(impl, payload: b
|
||||||
|
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
|
||||||
|
Index: cbor2-5.6.5/tests/test_encoder.py
|
||||||
|
===================================================================
|
||||||
|
--- cbor2-5.6.5.orig/tests/test_encoder.py
|
||||||
|
+++ cbor2-5.6.5/tests/test_encoder.py
|
||||||
|
@@ -654,3 +654,72 @@ def test_invariant_encode_decode(impl, v
|
||||||
|
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"]
|
||||||
@@ -1,3 +1,26 @@
|
|||||||
|
-------------------------------------------------------------------
|
||||||
|
Wed Jan 7 12:30:01 UTC 2026 - Markéta Machová <mmachova@suse.com>
|
||||||
|
|
||||||
|
- Add upstream CVE-2025-68131.patch (bsc#1255783)
|
||||||
|
|
||||||
|
-------------------------------------------------------------------
|
||||||
|
Wed Nov 19 10:56:07 UTC 2025 - Daniel Garcia <daniel.garcia@suse.com>
|
||||||
|
|
||||||
|
- Add CVE-2025-64076.patch from upstream. Fix: bug in
|
||||||
|
decode_definite_long_string() that causes incorrect chunk length
|
||||||
|
calculation
|
||||||
|
(bsc#1253746, CVE-2025-64076, gh#agronholm/cbor2#265)
|
||||||
|
|
||||||
|
-------------------------------------------------------------------
|
||||||
|
Tue Aug 12 08:01:01 UTC 2025 - Markéta Machová <mmachova@suse.com>
|
||||||
|
|
||||||
|
- Make the libalternatives transition conditional
|
||||||
|
|
||||||
|
-------------------------------------------------------------------
|
||||||
|
Wed Jun 25 11:44:28 UTC 2025 - Markéta Machová <mmachova@suse.com>
|
||||||
|
|
||||||
|
- Convert to libalternatives
|
||||||
|
|
||||||
-------------------------------------------------------------------
|
-------------------------------------------------------------------
|
||||||
Tue Oct 22 13:48:00 UTC 2024 - Dirk Müller <dmueller@suse.com>
|
Tue Oct 22 13:48:00 UTC 2024 - Dirk Müller <dmueller@suse.com>
|
||||||
|
|
||||||
@@ -16,7 +39,7 @@ Thu Jun 6 10:36:17 UTC 2024 - John Paul Adrian Glaubitz <adrian.glaubitz@suse.c
|
|||||||
-------------------------------------------------------------------
|
-------------------------------------------------------------------
|
||||||
Wed Jun 5 15:47:30 UTC 2024 - John Paul Adrian Glaubitz <adrian.glaubitz@suse.com>
|
Wed Jun 5 15:47:30 UTC 2024 - John Paul Adrian Glaubitz <adrian.glaubitz@suse.com>
|
||||||
|
|
||||||
- Update to 5.6.3
|
- Update to 5.6.3 (bsc#1220096, CVE-2024-26134):
|
||||||
* Fixed decoding of epoch-based dates being affected by the local
|
* Fixed decoding of epoch-based dates being affected by the local
|
||||||
time zone in the C extension
|
time zone in the C extension
|
||||||
- from version 5.6.2
|
- from version 5.6.2
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#
|
#
|
||||||
# spec file for package python-cbor2
|
# spec file for package python-cbor2
|
||||||
#
|
#
|
||||||
# Copyright (c) 2024 SUSE LLC
|
# Copyright (c) 2025 SUSE LLC
|
||||||
#
|
#
|
||||||
# All modifications and additions to the file contributed by third parties
|
# All modifications and additions to the file contributed by third parties
|
||||||
# remain the property of their copyright owners, unless otherwise agreed
|
# remain the property of their copyright owners, unless otherwise agreed
|
||||||
@@ -16,6 +16,11 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
|
%if 0%{?suse_version} > 1500
|
||||||
|
%bcond_without libalternatives
|
||||||
|
%else
|
||||||
|
%bcond_with libalternatives
|
||||||
|
%endif
|
||||||
%{?sle15_python_module_pythons}
|
%{?sle15_python_module_pythons}
|
||||||
Name: python-cbor2
|
Name: python-cbor2
|
||||||
Version: 5.6.5
|
Version: 5.6.5
|
||||||
@@ -24,6 +29,10 @@ Summary: Pure Python CBOR (de)serializer with extensive tag support
|
|||||||
License: MIT
|
License: MIT
|
||||||
URL: https://github.com/agronholm/cbor2
|
URL: https://github.com/agronholm/cbor2
|
||||||
Source: https://files.pythonhosted.org/packages/source/c/cbor2/cbor2-%{version}.tar.gz
|
Source: https://files.pythonhosted.org/packages/source/c/cbor2/cbor2-%{version}.tar.gz
|
||||||
|
# PATCH-FIX-UPSTREAM CVE-2025-64076.patch bsc#1253746 gh#agronholm/cbor2#265
|
||||||
|
Patch0: CVE-2025-64076.patch
|
||||||
|
# PATCH-FIX-UPSTREAM CVE-2025-68131.patch bsc#1255783 gh#agronholm/cbor2#f1d701c
|
||||||
|
Patch1: CVE-2025-68131.patch
|
||||||
BuildRequires: %{python_module devel}
|
BuildRequires: %{python_module devel}
|
||||||
BuildRequires: %{python_module hypothesis}
|
BuildRequires: %{python_module hypothesis}
|
||||||
BuildRequires: %{python_module pip}
|
BuildRequires: %{python_module pip}
|
||||||
@@ -33,8 +42,13 @@ BuildRequires: %{python_module setuptools_scm >= 6.4}
|
|||||||
BuildRequires: %{python_module wheel}
|
BuildRequires: %{python_module wheel}
|
||||||
BuildRequires: fdupes
|
BuildRequires: fdupes
|
||||||
BuildRequires: python-rpm-macros
|
BuildRequires: python-rpm-macros
|
||||||
|
%if %{with libalternatives}
|
||||||
|
BuildRequires: alts
|
||||||
|
Requires: alts
|
||||||
|
%else
|
||||||
Requires(post): update-alternatives
|
Requires(post): update-alternatives
|
||||||
Requires(postun): update-alternatives
|
Requires(postun): update-alternatives
|
||||||
|
%endif
|
||||||
%python_subpackages
|
%python_subpackages
|
||||||
|
|
||||||
%description
|
%description
|
||||||
@@ -60,6 +74,9 @@ export LANG=en_US.UTF8
|
|||||||
export LANG=en_US.UTF8
|
export LANG=en_US.UTF8
|
||||||
%pytest_arch
|
%pytest_arch
|
||||||
|
|
||||||
|
%pre
|
||||||
|
%python_libalternatives_reset_alternative cbor2
|
||||||
|
|
||||||
%post
|
%post
|
||||||
%python_install_alternative cbor2
|
%python_install_alternative cbor2
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user