Skip to content

fix: use-after-free in unpackb() ExtraData path for non-contiguous input - #722

Open
ThomasWaldmann wants to merge 1 commit into
msgpack:mainfrom
ThomasWaldmann:fix-extradata-uaf-720
Open

fix: use-after-free in unpackb() ExtraData path for non-contiguous input#722
ThomasWaldmann wants to merge 1 commit into
msgpack:mainfrom
ThomasWaldmann:fix-extradata-uaf-720

Conversation

@ThomasWaldmann

Copy link
Copy Markdown
Contributor

Fixes #720.

Problem

For non-contiguous input, get_data_from_buffer() releases the original view and makes a temporary contiguous copy, so buf points into memory owned by view:

https://github.com/msgpack/msgpack-python/blob/main/msgpack/_unpacker.pyx#L129-L134

unpackb() then releases view in its finally block — freeing that copy — and only afterwards reads buf+off to build the ExtraData payload. ExtraData.extra is therefore filled from freed memory.

This is the same class of bug as #677, on a code path that fix did not cover.

Reproducer (from #720)

import msgpack

buf = bytearray(b"\x00\xffa\xffb\xffc\xff")
view = memoryview(buf)[::2]
assert bytes(view) == b"\x00abc"

try:
    msgpack.unpackb(view)
except msgpack.ExtraData as e:
    print(e.unpacked)   # 0        (correct)
    print(e.extra)      # b'ab\x00' (expected b'abc')

Reproduced on macOS 15 / arm64 / CPython 3.14 with 1.2.1 and with current main. Running the same reproducer under the macOS allocator's free-poisoning makes the use-after-free unambiguous:

PYTHONMALLOC=malloc MallocScribble=1 python repro.py
extra=b'\xaa\xaa\x00'   # expected b'abc'

so arbitrary heap contents can end up in ExtraData.extra, not merely a shifted copy of the input. The reporter's ASan output (memcpy-param-overlap) is the same event seen from the other side: PyBytes_FromStringAndSize re-allocates the just-freed block and copies it onto itself.

Unpacker.feed() uses the same helper but copies via append_buffer() before releasing, so it is unaffected. fallback.py is also unaffected.

Fix

Copy the extra data out before releasing the view. Kept minimal: unpack_data() still runs outside the try, so only the PyBytes_FromStringAndSize call moved.

Test

test_unpack_noncontiguous_memoryview_extra_data in test/test_memoryview.py, next to the #677 test. Verified it fails on main (At index 4 diff: b'\x00' != b'a') and passes with the fix.

Full suite passes for both implementations — 135 passed (Cython), 133 passed / 2 skipped (MSGPACK_PUREPYTHON=1) — and also with MallocScribble=1.

Note that whether the corruption is observable depends on allocator reuse, so the test uses a payload/extra size combination that reliably triggers it on CPython's pymalloc; the assertion itself is unconditionally correct.

For non-contiguous input, get_data_from_buffer() makes a temporary
contiguous copy and buf points into it. unpackb() released the buffer
view (freeing that copy) in the finally block, and only afterwards read
buf+off to build the ExtraData payload, so ExtraData.extra was filled
from freed memory.

Copy the extra data out before releasing the view.

Fixes msgpack#720
@ThomasWaldmann

Copy link
Copy Markdown
Contributor Author

^ made with Claude Opus 5, please review carefully.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

unpackb() may return corrupted ExtraData.extra for non-contiguous input

1 participant