From 36fd127a003bd6c54c43097d7af9d82b45fa88e9 Mon Sep 17 00:00:00 2001 From: chuenchen309 <48723787+chuenchen309@users.noreply.github.com> Date: Sun, 19 Jul 2026 22:32:50 +0800 Subject: [PATCH] Validate nanoseconds range when unpacking timestamps in the C extension unpack_timestamp() decoded the nanoseconds field of a timestamp64/96 but never range-checked it, so with timestamp=1|2|3 the C extension silently accepted a malformed value >= 1e9, while the pure-Python fallback and the C extension's own timestamp=0 path both reject it. The MessagePack spec states nanoseconds must not be larger than 999999999. Add the range check in unpack_timestamp() so all modes reject consistently. Co-Authored-By: Claude Opus 4.8 (1M context) --- 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..0541fc19 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 less 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