From c6b4a481ed574b8b785c4487d7ef9b592c8b4f2b Mon Sep 17 00:00:00 2001 From: max <87073104+maxtaran2010@users.noreply.github.com> Date: Fri, 10 Jul 2026 03:02:40 -0700 Subject: [PATCH 1/9] Fix typos in test and docs (#712) --- docs/conf.py | 2 +- test/test_unpack.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 28116cd6..d7215a94 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -269,7 +269,7 @@ # The format is a list of tuples containing the path and title. # epub_pre_files = [] -# HTML files shat should be inserted after the pages created by sphinx. +# HTML files that should be inserted after the pages created by sphinx. # The format is a list of tuples containing the path and title. # epub_post_files = [] diff --git a/test/test_unpack.py b/test/test_unpack.py index ae6ea10c..5811c27f 100644 --- a/test/test_unpack.py +++ b/test/test_unpack.py @@ -90,10 +90,10 @@ def test_unpacker_tell_read_bytes(): objects = 1, "abc", "ghi" packed = b"\x01\x02\xa3abc\xa3def\xa3ghi" raw_data = b"\x02", b"\xa3def", b"" - lenghts = 1, 4, 999 + lengths = 1, 4, 999 positions = 1, 6, 14 unpacker = Unpacker(BytesIO(packed)) - for obj, unp, pos, n, raw in zip(objects, unpacker, positions, lenghts, raw_data): + for obj, unp, pos, n, raw in zip(objects, unpacker, positions, lengths, raw_data): assert obj == unp assert pos == unpacker.tell() assert unpacker.read_bytes(n) == raw From 3f5d0d77b174533033ebe5f12170588291611278 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 29 Jul 2026 18:29:16 +0900 Subject: [PATCH 2/9] Bump the all-dependencies group across 1 directory with 5 updates (#719) --- .github/workflows/docs.yaml | 4 ++-- .github/workflows/lint.yaml | 2 +- .github/workflows/test.yml | 4 ++-- .github/workflows/test_debug.yml | 2 +- .github/workflows/wheel.yml | 10 +++++----- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index f7bc83e9..344eedd1 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -11,10 +11,10 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Setup Python - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: '3.x' cache: "pip" diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 1ad0a1e6..6e9c8c14 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-slim steps: - name: Checkout - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: ruff check run: | diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9049b469..bc7efe06 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -26,10 +26,10 @@ jobs: steps: - name: Checkout - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Set up Python - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: ${{ matrix.py }} allow-prereleases: true diff --git a/.github/workflows/test_debug.yml b/.github/workflows/test_debug.yml index 4642a85c..5fbf185e 100644 --- a/.github/workflows/test_debug.yml +++ b/.github/workflows/test_debug.yml @@ -11,7 +11,7 @@ jobs: PYTHON_VERSION: 3.14.6 steps: - name: Checkout - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: diff --git a/.github/workflows/wheel.yml b/.github/workflows/wheel.yml index 0e95e092..d162d5c7 100644 --- a/.github/workflows/wheel.yml +++ b/.github/workflows/wheel.yml @@ -26,8 +26,8 @@ jobs: name: Build wheels on ${{ matrix.os }}${{ matrix.name_suffix || '' }} steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: "3.x" cache: "pip" @@ -39,12 +39,12 @@ jobs: - name: Set up QEMU for emulation if: matrix.cibw_archs == 'riscv64' - uses: docker/setup-qemu-action@06116385d9baf250c9f4dcb4858b16962ea869c3 # v4.1.0 + uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4.2.0 with: platforms: ${{ matrix.cibw_archs }} - name: Build - uses: pypa/cibuildwheel@294735312765b09d24a2fbec22660ce817587d55 # v4.1.0 + uses: pypa/cibuildwheel@4726cd35bb13f7bde50cf2761f2499ac7b3aa32c # v4.1.1 env: CIBW_TEST_REQUIRES: "pytest" CIBW_TEST_COMMAND: "pytest {package}/test" @@ -99,6 +99,6 @@ jobs: path: dist merge-multiple: true - - uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0 + - uses: pypa/gh-action-pypi-publish@ba38be9e461d3875417946c167d0b5f3d385a247 # v1.14.1 #with: # To test: repository-url: https://test.pypi.org/legacy/ From 091ae208005aaa93b644a199a517129d198b5bef Mon Sep 17 00:00:00 2001 From: TW Date: Wed, 5 Aug 2026 11:49:36 +0200 Subject: [PATCH 3/9] fix: use-after-free in unpackb() ExtraData path for non-contiguous input (#722) 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`. --- msgpack/_unpacker.pyx | 14 ++++++++------ test/test_memoryview.py | 21 ++++++++++++++++++++- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/msgpack/_unpacker.pyx b/msgpack/_unpacker.pyx index e0463617..e2953b1e 100644 --- a/msgpack/_unpacker.pyx +++ b/msgpack/_unpacker.pyx @@ -168,6 +168,7 @@ def unpackb(object packed, *, object object_hook=None, object list_hook=None, cdef char* buf = NULL cdef Py_ssize_t buf_len cdef const char* cerr = NULL + cdef object extra = None if unicode_errors is not None: cerr = unicode_errors @@ -190,15 +191,16 @@ def unpackb(object packed, *, object object_hook=None, object list_hook=None, use_list, raw, timestamp, strict_map_key, cerr, max_str_len, max_bin_len, max_array_len, max_map_len, max_ext_len) ret = unpack_construct(&ctx, buf, buf_len, &off) + if ret == 1: + obj = unpack_data(&ctx) + if off < buf_len: + # buf may point into a temporary contiguous copy owned by view, + # so the extra data must be copied out before releasing view. + raise ExtraData(obj, PyBytes_FromStringAndSize(buf+off, buf_len-off)) + return obj finally: PyBuffer_Release(&view); - if ret == 1: - obj = unpack_data(&ctx) - if off < buf_len: - raise ExtraData(obj, PyBytes_FromStringAndSize(buf+off, buf_len-off)) - return obj - unpack_clear(&ctx) if ret == 0: raise ValueError("Unpack failed: incomplete input") diff --git a/test/test_memoryview.py b/test/test_memoryview.py index 3f6a39d4..fd124dce 100644 --- a/test/test_memoryview.py +++ b/test/test_memoryview.py @@ -2,7 +2,9 @@ from array import array -from msgpack import packb, unpackb +from pytest import raises + +from msgpack import ExtraData, packb, unpackb def make_array(f, data): @@ -109,3 +111,20 @@ def test_unpack_noncontiguous_memoryview(): noncont = memoryview(bytes(padded))[::2] assert not noncont.c_contiguous assert unpackb(noncont) == 2**32 + + +def test_unpack_noncontiguous_memoryview_extra_data(): + # See https://github.com/msgpack/msgpack-python/issues/720 + # ExtraData.extra must be copied out of the temporary contiguous copy + # before that copy is released. + packed = packb(0) + b"extra" + padded = bytearray() + for byte in packed: + padded.append(byte) + padded.append(0) + noncont = memoryview(bytes(padded))[::2] + assert not noncont.c_contiguous + with raises(ExtraData) as excinfo: + unpackb(noncont) + assert excinfo.value.unpacked == 0 + assert excinfo.value.extra == b"extra" From ea6b84f5ee6d97ae2089f1eb025034a6847d1ccb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 6 Aug 2026 15:17:13 +0900 Subject: [PATCH 4/9] Bump pypa/gh-action-pypi-publish from 1.14.1 to 1.14.2 in the all-dependencies group (#721) --- .github/workflows/wheel.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/wheel.yml b/.github/workflows/wheel.yml index d162d5c7..a4ba230b 100644 --- a/.github/workflows/wheel.yml +++ b/.github/workflows/wheel.yml @@ -99,6 +99,6 @@ jobs: path: dist merge-multiple: true - - uses: pypa/gh-action-pypi-publish@ba38be9e461d3875417946c167d0b5f3d385a247 # v1.14.1 + - uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2 #with: # To test: repository-url: https://test.pypi.org/legacy/ From 186d4dbc126b659eff1509c850300e207d8aaad5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 11 Aug 2026 19:27:38 +0900 Subject: [PATCH 5/9] Bump pypa/cibuildwheel from 4.1.1 to 4.2.0(#724) --- .github/workflows/wheel.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/wheel.yml b/.github/workflows/wheel.yml index a4ba230b..6250cfd0 100644 --- a/.github/workflows/wheel.yml +++ b/.github/workflows/wheel.yml @@ -44,7 +44,7 @@ jobs: platforms: ${{ matrix.cibw_archs }} - name: Build - uses: pypa/cibuildwheel@4726cd35bb13f7bde50cf2761f2499ac7b3aa32c # v4.1.1 + uses: pypa/cibuildwheel@1828c10ab37f080699c7b81cea34097c684a7074 # v4.2.0 env: CIBW_TEST_REQUIRES: "pytest" CIBW_TEST_COMMAND: "pytest {package}/test" From 809bfcda6d7186fbb34f6ea1b984a61344ba55d2 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Thu, 13 Aug 2026 00:32:59 +0900 Subject: [PATCH 6/9] remove unused variable --- msgpack/_unpacker.pyx | 1 - 1 file changed, 1 deletion(-) diff --git a/msgpack/_unpacker.pyx b/msgpack/_unpacker.pyx index e2953b1e..7e406622 100644 --- a/msgpack/_unpacker.pyx +++ b/msgpack/_unpacker.pyx @@ -168,7 +168,6 @@ def unpackb(object packed, *, object object_hook=None, object list_hook=None, cdef char* buf = NULL cdef Py_ssize_t buf_len cdef const char* cerr = NULL - cdef object extra = None if unicode_errors is not None: cerr = unicode_errors From 9f9bdae0773166e8ed50dfdd1ed13cc4296819ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=C3=AFm=20Dimer?= <94395445+hdimer@users.noreply.github.com> Date: Fri, 14 Aug 2026 00:48:27 -0700 Subject: [PATCH 7/9] Convert Packer's buf_size once (#726) Fixes #723. --- msgpack/_packer.pyx | 2 +- test/test_pack.py | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/msgpack/_packer.pyx b/msgpack/_packer.pyx index 277239d8..e816c814 100644 --- a/msgpack/_packer.pyx +++ b/msgpack/_packer.pyx @@ -110,7 +110,7 @@ cdef class Packer: cdef bint autoreset cdef bint datetime - def __cinit__(self, buf_size=256*1024, **_kwargs): + def __cinit__(self, size_t buf_size=256*1024, **_kwargs): self.pk.buf = PyMem_Malloc(buf_size) if self.pk.buf == NULL: raise MemoryError("Unable to allocate internal buffer.") diff --git a/test/test_pack.py b/test/test_pack.py index 374d1549..f44bd557 100644 --- a/test/test_pack.py +++ b/test/test_pack.py @@ -179,3 +179,24 @@ def test_get_buffer(): expected = packb([1, 2], use_bin_type=True) assert written == expected + + +@pytest.mark.skipif( + Packer.__module__ == "msgpack.fallback", + reason="buf_size only allocates in the C extension", +) +def test_buf_size_is_converted_once(): + # Asking twice let the allocation and the recorded capacity disagree, + # so the packer overflowed a buffer smaller than the size it recorded. + class Counting: + count = 0 + + def __int__(self): + self.count += 1 + return 600 + + __index__ = __int__ + + buf_size = Counting() + Packer(buf_size=buf_size) + assert buf_size.count == 1 From 2e4be090f6a42e1ad19526ca6f6ee67f3476389a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=C3=AFm=20Dimer?= <94395445+hdimer@users.noreply.github.com> Date: Sun, 16 Aug 2026 22:59:09 -0700 Subject: [PATCH 8/9] fix: translate RecursionError to StackError in fallback Unpacker.skip() (#727) Finding #6 of #683. --- msgpack/fallback.py | 5 ++++- test/test_except.py | 5 +++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/msgpack/fallback.py b/msgpack/fallback.py index 824f59d5..e219786e 100644 --- a/msgpack/fallback.py +++ b/msgpack/fallback.py @@ -582,7 +582,10 @@ def __next__(self): next = __next__ def skip(self): - self._unpack(EX_SKIP) + try: + self._unpack(EX_SKIP) + except RecursionError: + raise StackError self._consume() def unpack(self): diff --git a/test/test_except.py b/test/test_except.py index a3bf4675..1e9c0045 100644 --- a/test/test_except.py +++ b/test/test_except.py @@ -97,6 +97,11 @@ def test_invalidvalue(): with raises(StackError): unpackb(b"\x91" * 3000) # nested fixarray(len=1) + with raises(StackError): + unpacker = Unpacker() + unpacker.feed(b"\x91" * 3000) + unpacker.skip() + def test_no_memory_leak_on_nested_invalid_tag() -> None: """Regression test: unpacking nested arrays containing an invalid tag must not leak objects.""" From e94e1dc078547c58855c7cc60b6e54c44deb903d Mon Sep 17 00:00:00 2001 From: Andrew Chen Date: Tue, 25 Aug 2026 12:25:45 +0800 Subject: [PATCH 9/9] Validate nanoseconds range when unpacking timestamps in the C extension (#716) --- msgpack/unpack.h | 9 +++++++-- test/test_timestamp.py | 9 +++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/msgpack/unpack.h b/msgpack/unpack.h index eb4330ac..774354b6 100644 --- a/msgpack/unpack.h +++ b/msgpack/unpack.h @@ -271,16 +271,21 @@ static int unpack_timestamp(const char* buf, unsigned int buflen, msgpack_timest uint64_t value =_msgpack_load64(uint64_t, buf); ts->tv_nsec = (uint32_t)(value >> 34); ts->tv_sec = value & 0x00000003ffffffffLL; - return 0; + break; } case 12: ts->tv_nsec = _msgpack_load32(uint32_t, buf); ts->tv_sec = _msgpack_load64(int64_t, buf + 4); - return 0; + break; default: PyErr_Format(PyExc_ValueError, "invalid timestamp data (length %u)", buflen); return -1; } + if (ts->tv_nsec > 999999999) { + PyErr_Format(PyExc_ValueError, "nanoseconds must be a non-negative integer not greater than 999999999."); + return -1; + } + return 0; } #include "datetime.h" diff --git a/test/test_timestamp.py b/test/test_timestamp.py index 2083356a..3e753e11 100644 --- a/test/test_timestamp.py +++ b/test/test_timestamp.py @@ -71,6 +71,15 @@ def test_unpack_timestamp(): msgpack.unpackb(b"\xc7\x05\xff\0\0\0\0\0") # ext8 (len=5) +def test_unpack_timestamp_out_of_range_nanoseconds(): + # An out-of-range nanoseconds field must be rejected in every timestamp= + # mode (spec: nanoseconds must not exceed 999999999), not only the default. + for data in (b"\xd7\xff" + b"\xff" * 8, b"\xc7\x0c\xff" + b"\xff" * 12): + for mode in (0, 1, 2, 3): + with pytest.raises(ValueError): + msgpack.unpackb(data, timestamp=mode) + + def test_timestamp_from(): t = Timestamp(42, 14000) assert Timestamp.from_unix(42.000014) == t