diff --git a/srsly/msgpack/_unpacker.pyx b/srsly/msgpack/_unpacker.pyx index 631b9b5..0023f32 100644 --- a/srsly/msgpack/_unpacker.pyx +++ b/srsly/msgpack/_unpacker.pyx @@ -482,7 +482,7 @@ cdef class Unpacker: obj = unpack_data(&self.ctx) unpack_init(&self.ctx) return obj - elif ret == 0: + if ret == 0: if self.file_like is not None: self.read_from_file() continue @@ -490,7 +490,9 @@ cdef class Unpacker: raise StopIteration("No more data to unpack.") else: raise OutOfData("No more data to unpack.") - elif ret == -2: + + unpack_clear(&self.ctx) + if ret == -2: raise FormatError elif ret == -3: raise StackError diff --git a/srsly/msgpack/unpack_template.h b/srsly/msgpack/unpack_template.h index cce29e7..0e51564 100644 --- a/srsly/msgpack/unpack_template.h +++ b/srsly/msgpack/unpack_template.h @@ -72,7 +72,14 @@ static inline PyObject* unpack_data(unpack_context* ctx) static inline void unpack_clear(unpack_context *ctx) { - Py_CLEAR(ctx->stack[0].obj); + for (unsigned int i = 0; i < ctx->top; i++) { + /* map_key holds a live reference only while waiting for the value */ + if (ctx->stack[i].ct == CT_MAP_VALUE) { + Py_CLEAR(ctx->stack[i].map_key); + } + Py_CLEAR(ctx->stack[i].obj); + } + unpack_init(ctx); } static inline int unpack_execute(bool construct, unpack_context* ctx, const char* data, Py_ssize_t len, Py_ssize_t* off) @@ -336,6 +343,7 @@ static inline int unpack_execute(bool construct, unpack_context* ctx, const char goto _header_again; case CT_MAP_VALUE: if(construct_cb(_map_item)(user, c->count, &c->obj, c->map_key, obj) < 0) { goto _failed; } + c->map_key = NULL; if(++c->count == c->size) { obj = c->obj; if (construct_cb(_map_end)(user, &obj) < 0) { goto _failed; } @@ -398,10 +406,18 @@ static inline int unpack_execute(bool construct, unpack_context* ctx, const char #undef start_container static int unpack_construct(unpack_context *ctx, const char *data, Py_ssize_t len, Py_ssize_t *off) { - return unpack_execute(1, ctx, data, len, off); + int ret = unpack_execute(1, ctx, data, len, off); + if (ret == -1) { + unpack_clear(ctx); + } + return ret; } static int unpack_skip(unpack_context *ctx, const char *data, Py_ssize_t len, Py_ssize_t *off) { - return unpack_execute(0, ctx, data, len, off); + int ret = unpack_execute(0, ctx, data, len, off); + if (ret == -1) { + unpack_clear(ctx); + } + return ret; } #define unpack_container_header read_array_header diff --git a/srsly/tests/msgpack/test_except.py b/srsly/tests/msgpack/test_except.py index 2322e08..3a762cc 100644 --- a/srsly/tests/msgpack/test_except.py +++ b/srsly/tests/msgpack/test_except.py @@ -28,6 +28,23 @@ def hook(obj): ) +def test_unpacker_raise_from_object_hook(): + def hook(obj): + raise DummyException + + up = Unpacker(object_hook=hook) + + def up_unpack(x): + up.feed(x) + return up.unpack() + + raises(DummyException, up_unpack, packb({})) + raises(DummyException, up_unpack, packb({"fizz": "buzz"})) + raises(DummyException, up_unpack, packb({"fizz": "buzz"})) + raises(DummyException, up_unpack, packb({"fizz": {"buzz": "spam"}})) + raises(DummyException, up_unpack, packb({"fizz": {"buzz": "spam"}})) + + def test_invalidvalue(): incomplete = b"\xd9\x97#DL_" # raw8 - length=0x97 with raises(ValueError): @@ -57,3 +74,23 @@ def test_strict_map_key(): packed = packb(invalid, use_bin_type=True) with raises(ValueError): unpackb(packed, raw=False, strict_map_key=True) + + +def test_unpacker_should_not_crash_after_exception(): + # CVE-2026-57585: reusing an Unpacker after a failed unpack resumed from a + # corrupt parser context and could segfault. + up = Unpacker(strict_map_key=True) + up.feed(b"\x83\x73\xc4\x00") # fixmap(3): int key (rejected) + empty bin8 + with raises(ValueError): + up.unpack() # int is not allowed for map key + up.skip() # SIGSEGV before the fix + + +def test_unpacker_usable_after_exception(): + up = Unpacker(strict_map_key=True, raw=False) + up.feed(packb({42: 1})) + with raises(ValueError): + up.unpack() + # The parser stops on the rejected value, then resumes from a clean state. + up.feed(packb({"a": [1, 2, {"b": 3}]})) + assert list(up) == [1, {"a": [1, 2, {"b": 3}]}]