Skip to content

Heap buffer overflow in Packer(buf_size=…) with an unstable __index__ #723

Description

@marinelay

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions