Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions srsly/msgpack/_unpacker.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -482,15 +482,17 @@ 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
if iter:
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
Expand Down
22 changes: 19 additions & 3 deletions srsly/msgpack/unpack_template.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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; }
Expand Down Expand Up @@ -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
Expand Down
37 changes: 37 additions & 0 deletions srsly/tests/msgpack/test_except.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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}]}]
Loading