Skip to content

Commit bf05d32

Browse files
committed
Check exported buffers after the default callback returns
The default callback can export the internal buffer with getbuffer(). The packer then continues and can reallocate that buffer. The export points at freed memory after the reallocation. Call _check_exports() after the callback returns. The packer now raises BufferError. The pure Python packer already raises BufferError in this case.
1 parent 9f4909c commit bf05d32

2 files changed

Lines changed: 25 additions & 0 deletions

File tree

msgpack/_packer.pyx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,9 @@ cdef class Packer:
264264
ret = self._pack_inner(o, 1, nest_limit)
265265
if ret == -2:
266266
o = self._default(o)
267+
# The callback may have exported the internal buffer.
268+
# Packing on would reallocate it and invalidate the export.
269+
self._check_exports()
267270
else:
268271
return ret
269272
return self._pack_inner(o, 0, nest_limit)

test/test_buffer.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,25 @@ def test_packer_getbuffer():
4747
buffer.release()
4848
packer.pack(42)
4949
assert bytes(packer) == b"\x92*\xa5hello*"
50+
51+
52+
def test_packer_getbuffer_in_default():
53+
# The default callback can export the internal buffer.
54+
# The packer must refuse to pack on, because packing on reallocates
55+
# the buffer and leaves the export pointing at freed memory.
56+
exported = []
57+
58+
class Unsupported:
59+
pass
60+
61+
def default(obj):
62+
exported.append(packer.getbuffer())
63+
return b"A" * (2 * 1024 * 1024)
64+
65+
packer = Packer(default=default, autoreset=False)
66+
with raises(BufferError):
67+
packer.pack([Unsupported()])
68+
69+
assert len(exported) == 1
70+
assert bytes(exported[0]) == b"\x91"
71+
exported[0].release()

0 commit comments

Comments
 (0)