Convert Packer's buf_size once - #726
Open
hdimer wants to merge 1 commit into
Open
Conversation
buf_size was untyped, so Cython converted it to size_t separately for the PyMem_Malloc call and for pk.buf_size. An object whose __int__ answers differently each call made the packer allocate one size and record another, and pack.h then grew the buffer against the recorded capacity, so a large enough payload was memcpy'd past the allocation. Typing the parameter converts it once during argument unpacking, the same way Unpacker takes read_size and max_buffer_size. Fixes msgpack#723
hdimer
marked this pull request as ready for review
August 12, 2026 18:35
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #723.
Packer.__cinit__takesbuf_sizeas an untyped object, and Cython converts it tosize_tseparately at each of the two lines that use it:An object that answers differently each time therefore sizes the allocation from one answer and the recorded capacity from the other. Everything afterwards trusts the recorded capacity:
so a payload that fits the recorded capacity but not the real block is copied straight past the allocation. On the reporter's example the packer holds 600 bytes, records 1 MiB, and
pack(b"A" * 100000)writes 100000 bytes into the 600-byte block.The fix
Type the parameter, so the conversion happens once during argument unpacking:
This is how
Unpackeralready takesread_sizeandmax_buffer_size(_unpacker.pyx:330), and it leaves no local for a later reader to fold back. It also removes a smaller wart: previously a second conversion that raised would leave__cinit__failing withpk.bufalready allocated.Same converter (
__Pyx_PyInt_As_size_t), so nothing user-visible moves. I checked the constructor's edge cases against a pre-fix build and they are identical, message for message:-1givesOverflowError: can't convert negative value to size_t,1 << 62givesMemoryError,"x"andNonegiveTypeError: an integer is required, and0,1.5,Trueand the default are accepted as before.Tests
test_buf_size_is_converted_onceasserts the object is queried exactly once. The overflow itself cannot be asserted portably (it takes the interpreter down rather than failing a test), so the test pins the invariant that prevents it. It fails onmainwithassert 2 == 1and passes with the change.The
skipiffollows the existing convention intest_unpack.py, sincefallback.pyacceptsbuf_sizeand ignores it. The helper's__int__is the load-bearing slot on Cython 3.2.5, with__index__aliased to it so the test keeps counting if a future Cython routes throughPyNumber_Indexinstead.Verified on CPython 3.14: full suite green in both the C-extension and
MSGPACK_PUREPYTHON=1configurations, andruff check/ruff formatclean. The reporter's reproducer no longer crashes.Disclosure: I used an AI assistant while investigating and writing this patch. I reproduced the overflow myself, confirmed the pre-fix crash and the edge-case parity against a locally built extension, and I stand behind the change.