I found a heap buffer overflow fuzzing the C extension. The input is contrived (it needs an object whose __index__ answers differently each call) but sharing it in case it's useful.
Versions
msgpack 1.2.1 (pip install msgpack), CPython 3.12, Linux x86_64. The same
code is on main.
Reproducer
import msgpack
class Unstable:
"""__index__ answers with a small size first and a large one afterwards."""
def __init__(self, first, rest):
self._v = [first, rest]
self._n = 0
def __index__(self):
v = self._v[min(self._n, 1)]
self._n += 1
return v
__int__ = __index__
msgpack.Packer(buf_size=Unstable(600, 1 << 20)).pack(b"A" * 100000) # SIGSEGV
Output
[1] 123498 segmentation fault (core dumped) python
Sanitizer build
AddressSanitizer: heap-buffer-overflow
#1 msgpack_pack_write msgpack/pack.h:53:5
#2 msgpack_pack_raw_body msgpack/pack_template.h:488:16
#3 Packer._pack_inner _cmsgpack.c:7137:18
#4 Packer._pack _cmsgpack.c:8391:15
allocated by:
#1 Packer.__cinit__ _cmsgpack.c:5931
What happens
__cinit__ uses buf_size at two lines, and Cython converts it separately at each, so the object is asked for its value once per line:
def __cinit__(self, buf_size=256*1024, **_kwargs):
self.pk.buf = <char*> PyMem_Malloc(buf_size) # :114 -> 600
if self.pk.buf == NULL:
raise MemoryError("Unable to allocate internal buffer.")
self.pk.buf_size = buf_size # :117 -> 1048576
(_packer.pyx:114, :117)
With Unstable(600, 1 << 20) the packer holds 600 bytes and records 1048576 as its capacity, and everything afterwards works from the recorded number:
if (len + l > bs) { /* pack.h:45 — bs = pk.buf_size = 1048576 */
...grow...
}
memcpy(buf + len, data, l);
100000 is under 1048576, so the buffer never grows and the memcpy writes 100000 bytes into a 600-byte block.
(pack.h:45)
It reads like a bug to me, but the input is not a natural one, so I'm leaving it here.
Entirely reasonable if this is out of scope.
I found a heap buffer overflow fuzzing the C extension. The input is contrived (it needs an object whose
__index__answers differently each call) but sharing it in case it's useful.Versions
msgpack 1.2.1 (
pip install msgpack), CPython 3.12, Linux x86_64. The samecode is on
main.Reproducer
Output
Sanitizer build
What happens
__cinit__usesbuf_sizeat two lines, and Cython converts it separately at each, so the object is asked for its value once per line:(
_packer.pyx:114,:117)With
Unstable(600, 1 << 20)the packer holds 600 bytes and records 1048576 as its capacity, and everything afterwards works from the recorded number:100000 is under 1048576, so the buffer never grows and the
memcpywrites 100000 bytes into a 600-byte block.(
pack.h:45)It reads like a bug to me, but the input is not a natural one, so I'm leaving it here.
Entirely reasonable if this is out of scope.