Skip to content

Commit 664ddc9

Browse files
committed
Reject a mode change while an object is incomplete
Unpacker.skip() does not build the objects on the parser stack. So stack[].obj stays uninitialized after an incomplete skip(). A later Unpacker.unpack() writes an array item through that pointer and the process crashes. The reverse order drops the reference that the construct pass took. Record the mode that started the object in unpack_context. unpack_construct() and unpack_skip() now raise ValueError on a change. The parser state stays intact, so the original mode still finishes the object.
1 parent 9f4909c commit 664ddc9

2 files changed

Lines changed: 54 additions & 1 deletion

File tree

msgpack/unpack_template.h

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ struct unpack_context {
3535
unsigned int cs;
3636
unsigned int trail;
3737
unsigned int top;
38+
/* mode that started the object on the stack. See unpack_check_mode(). */
39+
bool construct;
3840
unpack_stack stack[MSGPACK_EMBED_STACK_SIZE];
3941
};
4042

@@ -44,6 +46,7 @@ static inline void unpack_init(unpack_context* ctx)
4446
ctx->cs = CS_HEADER;
4547
ctx->trail = 0;
4648
ctx->top = 0;
49+
ctx->construct = true;
4750
ctx->stack[0].obj = NULL;
4851
}
4952

@@ -386,14 +389,37 @@ static inline int unpack_execute(bool construct, unpack_context* ctx, const char
386389
#undef again_fixed_trail_if_zero
387390
#undef start_container
388391

392+
/*
393+
* skip mode does not build the objects on the stack, so stack[].obj stays
394+
* uninitialized. A later construct pass writes an item through that pointer.
395+
* The reverse order drops the reference that the construct pass took.
396+
* Reject the mode change while an object is still open.
397+
*/
398+
static inline int unpack_check_mode(unpack_context *ctx, bool construct)
399+
{
400+
if (ctx->top != 0 && ctx->construct != construct) {
401+
PyErr_SetString(PyExc_ValueError,
402+
"cannot switch between unpack and skip while an object is incomplete");
403+
return -1;
404+
}
405+
ctx->construct = construct;
406+
return 0;
407+
}
408+
389409
static int unpack_construct(unpack_context *ctx, const char *data, Py_ssize_t len, Py_ssize_t *off) {
410+
if (unpack_check_mode(ctx, true) < 0) {
411+
return -1;
412+
}
390413
int ret = unpack_execute(1, ctx, data, len, off);
391414
if (ret == -1) {
392415
unpack_clear(ctx);
393416
}
394417
return ret;
395418
}
396419
static int unpack_skip(unpack_context *ctx, const char *data, Py_ssize_t len, Py_ssize_t *off) {
420+
if (unpack_check_mode(ctx, false) < 0) {
421+
return -1;
422+
}
397423
int ret = unpack_execute(0, ctx, data, len, off);
398424
if (ret == -1) {
399425
unpack_clear(ctx);

test/test_sequnpack.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#!/usr/bin/env python
22
import io
33

4-
from pytest import raises
4+
from pytest import mark, raises
55

66
from msgpack import BufferFull, Unpacker, pack, packb
77
from msgpack.exceptions import OutOfData
@@ -146,3 +146,30 @@ def test_unpack_tell():
146146
m2 = next(unpacker)
147147
assert m == m2
148148
assert o == unpacker.tell()
149+
150+
151+
@mark.skipif(
152+
Unpacker.__module__ == "msgpack.fallback",
153+
reason="only the C extension keeps parser state between calls",
154+
)
155+
def test_mode_switch_while_incomplete():
156+
# skip() does not build the objects that unpack() needs, so the parser
157+
# state of one mode is not valid for the other. The unpacker must reject
158+
# the change instead of a crash. The original mode still finishes.
159+
unpacker = Unpacker()
160+
unpacker.feed(b"\x91")
161+
with raises(OutOfData):
162+
unpacker.skip()
163+
unpacker.feed(b"\x00")
164+
with raises(ValueError):
165+
unpacker.unpack()
166+
assert unpacker.skip() is None
167+
168+
unpacker = Unpacker()
169+
unpacker.feed(b"\x91")
170+
with raises(OutOfData):
171+
unpacker.unpack()
172+
unpacker.feed(b"\x00")
173+
with raises(ValueError):
174+
unpacker.skip()
175+
assert unpacker.unpack() == [0]

0 commit comments

Comments
 (0)