From 3353e6858882feca37b6e760f0794275e5303302 Mon Sep 17 00:00:00 2001 From: Takatoshi Kondo Date: Tue, 25 Aug 2026 14:08:49 +0900 Subject: [PATCH 01/18] Fix integer overflow. --- .gitignore | 9 +++++ include/msgpack/v1/unpack.hpp | 7 ++++ include/msgpack/v2/parse.hpp | 8 +++++ test/streaming.cpp | 62 +++++++++++++++++++++++++++++++++++ 4 files changed, 86 insertions(+) diff --git a/.gitignore b/.gitignore index 7b96d6dfc..87e70eb70 100644 --- a/.gitignore +++ b/.gitignore @@ -54,3 +54,12 @@ build *-build .cache compile_commands.json + +# Release archives +*.tar.gz +*.tar.xz + +# Editor backup / autosave files +*~ +\#*\# +.\#* diff --git a/include/msgpack/v1/unpack.hpp b/include/msgpack/v1/unpack.hpp index 489c285d4..2113c50f2 100644 --- a/include/msgpack/v1/unpack.hpp +++ b/include/msgpack/v1/unpack.hpp @@ -21,6 +21,7 @@ #include "msgpack/assert.hpp" #include +#include #if !defined(MSGPACK_USE_CPP03) @@ -1138,6 +1139,9 @@ inline void unpacker::expand_buffer(std::size_t size) } if(m_off == COUNTER_SIZE) { + if(size > std::numeric_limits::max() - m_used) { + throw std::bad_alloc(); + } std::size_t next_size = (m_used + m_free) * 2; // include COUNTER_SIZE while(next_size < size + m_used) { std::size_t tmp_next_size = next_size * 2; @@ -1159,6 +1163,9 @@ inline void unpacker::expand_buffer(std::size_t size) } else { std::size_t next_size = m_initial_buffer_size; // include COUNTER_SIZE std::size_t not_parsed = m_used - m_off; + if(size > std::numeric_limits::max() - not_parsed - COUNTER_SIZE) { + throw std::bad_alloc(); + } while(next_size < size + not_parsed + COUNTER_SIZE) { std::size_t tmp_next_size = next_size * 2; if (tmp_next_size <= next_size) { diff --git a/include/msgpack/v2/parse.hpp b/include/msgpack/v2/parse.hpp index bfdd1d007..ea0ef3619 100644 --- a/include/msgpack/v2/parse.hpp +++ b/include/msgpack/v2/parse.hpp @@ -13,6 +13,8 @@ #if MSGPACK_DEFAULT_API_VERSION >= 2 #include +#include +#include #include "msgpack/unpack_define.hpp" #include "msgpack/parse_return.hpp" @@ -865,6 +867,9 @@ inline void parser::expand_buffer(std::size } if(m_off == COUNTER_SIZE) { + if(size > std::numeric_limits::max() - m_used) { + throw std::bad_alloc(); + } std::size_t next_size = (m_used + m_free) * 2; // include COUNTER_SIZE while(next_size < size + m_used) { std::size_t tmp_next_size = next_size * 2; @@ -886,6 +891,9 @@ inline void parser::expand_buffer(std::size } else { std::size_t next_size = m_initial_buffer_size; // include COUNTER_SIZE std::size_t not_parsed = m_used - m_off; + if(size > std::numeric_limits::max() - not_parsed - COUNTER_SIZE) { + throw std::bad_alloc(); + } while(next_size < size + not_parsed + COUNTER_SIZE) { std::size_t tmp_next_size = next_size * 2; if (tmp_next_size <= next_size) { diff --git a/test/streaming.cpp b/test/streaming.cpp index 78309cee2..84159d98c 100644 --- a/test/streaming.cpp +++ b/test/streaming.cpp @@ -333,3 +333,65 @@ BOOST_AUTO_TEST_CASE(event_compat) } #endif // !defined(MSGPACK_USE_CPP03) + +// https://github.com/msgpack/msgpack-c/issues/1181 +template +void reserve_buffer_overflow_rewound_impl() +{ + Unpacker pac(MSGPACK_NULLPTR, MSGPACK_NULLPTR, 8); + + // off == COUNTER_SIZE path: size + used would wrap + std::size_t request = std::numeric_limits::max() - 2; + BOOST_CHECK_THROW(pac.reserve_buffer(request), std::bad_alloc); + + // a sane request still works + pac.reserve_buffer(64); + BOOST_CHECK_GE(pac.buffer_capacity(), static_cast(64)); +} + +template +void reserve_buffer_overflow_not_rewound_impl() +{ + Unpacker pac(MSGPACK_NULLPTR, MSGPACK_NULLPTR, 8); + + // consume part of the buffer so off != COUNTER_SIZE + msgpack::sbuffer sbuf; + msgpack::packer pk(&sbuf); + pk.pack(1); + pk.pack(2); + + pac.reserve_buffer(sbuf.size()); + std::memcpy(pac.buffer(), sbuf.data(), sbuf.size()); + pac.buffer_consumed(sbuf.size()); + + msgpack::object_handle oh; + BOOST_CHECK(pac.next(oh)); + BOOST_CHECK_EQUAL(oh.get().as(), 1); + + std::size_t request = std::numeric_limits::max() - 2; + BOOST_CHECK_THROW(pac.reserve_buffer(request), std::bad_alloc); + + // remaining data must still be parsable + BOOST_CHECK(pac.next(oh)); + BOOST_CHECK_EQUAL(oh.get().as(), 2); +} + +BOOST_AUTO_TEST_CASE(reserve_buffer_overflow_rewound) +{ + reserve_buffer_overflow_rewound_impl(); +} + +BOOST_AUTO_TEST_CASE(reserve_buffer_overflow_rewound_v1) +{ + reserve_buffer_overflow_rewound_impl(); +} + +BOOST_AUTO_TEST_CASE(reserve_buffer_overflow_not_rewound) +{ + reserve_buffer_overflow_not_rewound_impl(); +} + +BOOST_AUTO_TEST_CASE(reserve_buffer_overflow_not_rewound_v1) +{ + reserve_buffer_overflow_not_rewound_impl(); +} From 5ab45cca60d639b45a52c99c64433fa4dbbd6144 Mon Sep 17 00:00:00 2001 From: Takatoshi Kondo Date: Tue, 25 Aug 2026 15:21:52 +0900 Subject: [PATCH 02/18] Fix empty array treatment. --- include/msgpack/v1/adaptor/carray.hpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/include/msgpack/v1/adaptor/carray.hpp b/include/msgpack/v1/adaptor/carray.hpp index 8fff44688..1cfe808f1 100644 --- a/include/msgpack/v1/adaptor/carray.hpp +++ b/include/msgpack/v1/adaptor/carray.hpp @@ -30,11 +30,9 @@ struct convert { if (o.via.array.size > N) { throw msgpack::type_error(); } msgpack::object* p = o.via.array.ptr; msgpack::object* const pend = o.via.array.ptr + o.via.array.size; - do { + for (; p < pend; ++p, ++v) { p->convert(*v); - ++p; - ++v; - } while(p < pend); + } return o; } }; From 2e7530406092924e5b7218cd0107ef0102d41b1f Mon Sep 17 00:00:00 2001 From: Takatoshi Kondo Date: Tue, 25 Aug 2026 15:27:11 +0900 Subject: [PATCH 03/18] Fix std::tuple and msgpack::tuple size treatment. --- include/msgpack/v1/adaptor/cpp11/tuple.hpp | 1 + include/msgpack/v1/adaptor/detail/cpp11_msgpack_tuple.hpp | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/include/msgpack/v1/adaptor/cpp11/tuple.hpp b/include/msgpack/v1/adaptor/cpp11/tuple.hpp index fd1fc8e91..dc6547204 100644 --- a/include/msgpack/v1/adaptor/cpp11/tuple.hpp +++ b/include/msgpack/v1/adaptor/cpp11/tuple.hpp @@ -114,6 +114,7 @@ struct as, typename std::enable_if operator()( msgpack::object const& o) const { if (o.type != msgpack::type::ARRAY) { throw msgpack::type_error(); } + if (o.via.array.size < sizeof...(Args)) { throw msgpack::type_error(); } return StdTupleAs::as(o); } }; diff --git a/include/msgpack/v1/adaptor/detail/cpp11_msgpack_tuple.hpp b/include/msgpack/v1/adaptor/detail/cpp11_msgpack_tuple.hpp index 88d162912..d68168ec4 100644 --- a/include/msgpack/v1/adaptor/detail/cpp11_msgpack_tuple.hpp +++ b/include/msgpack/v1/adaptor/detail/cpp11_msgpack_tuple.hpp @@ -131,7 +131,8 @@ struct MsgpackTupleConverter { static void convert ( msgpack::object const& o, Tuple& v) { - o.via.array.ptr[0].convert())>::type>(v.template get<0>()); + if (o.via.array.size >= 1) + o.via.array.ptr[0].convert())>::type>(v.template get<0>()); } }; @@ -150,6 +151,7 @@ struct as, typename std::enable_if operator()( msgpack::object const& o) const { if (o.type != msgpack::type::ARRAY) { throw msgpack::type_error(); } + if (o.via.array.size < sizeof...(Args)) { throw msgpack::type_error(); } return MsgpackTupleAs::as(o); } }; From c9d581d2d735db4e71c76fc135d0dc37bf6cc9c8 Mon Sep 17 00:00:00 2001 From: Takatoshi Kondo Date: Tue, 25 Aug 2026 15:31:09 +0900 Subject: [PATCH 04/18] Fix data source out of bounds read error. --- include/msgpack/v1/adaptor/cpp11/array_char.hpp | 2 +- include/msgpack/v1/adaptor/cpp11/array_unsigned_char.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/msgpack/v1/adaptor/cpp11/array_char.hpp b/include/msgpack/v1/adaptor/cpp11/array_char.hpp index 01888f5a4..340262121 100644 --- a/include/msgpack/v1/adaptor/cpp11/array_char.hpp +++ b/include/msgpack/v1/adaptor/cpp11/array_char.hpp @@ -36,7 +36,7 @@ struct convert> { break; case msgpack::type::STR: if(o.via.str.size > N) { throw msgpack::type_error(); } - std::memcpy(v.data(), o.via.str.ptr, N); + std::memcpy(v.data(), o.via.str.ptr, o.via.str.size); break; default: throw msgpack::type_error(); diff --git a/include/msgpack/v1/adaptor/cpp11/array_unsigned_char.hpp b/include/msgpack/v1/adaptor/cpp11/array_unsigned_char.hpp index 0c698f0c2..8610998f2 100644 --- a/include/msgpack/v1/adaptor/cpp11/array_unsigned_char.hpp +++ b/include/msgpack/v1/adaptor/cpp11/array_unsigned_char.hpp @@ -36,7 +36,7 @@ struct convert> { break; case msgpack::type::STR: if(o.via.str.size > N) { throw msgpack::type_error(); } - std::memcpy(v.data(), o.via.str.ptr, N); + std::memcpy(v.data(), o.via.str.ptr, o.via.str.size); break; default: throw msgpack::type_error(); From 5d6871c4562e526008de4172b268ebbf1ae930de Mon Sep 17 00:00:00 2001 From: Takatoshi Kondo Date: Tue, 25 Aug 2026 15:34:48 +0900 Subject: [PATCH 05/18] Fix referenced_buffer_hook rebind isn't rebind correctly on move bug. --- include/msgpack/v2/parse.hpp | 20 +++++++++++++++----- include/msgpack/v2/unpack.hpp | 21 +++++++++++++++++++++ 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/include/msgpack/v2/parse.hpp b/include/msgpack/v2/parse.hpp index ea0ef3619..559d9b472 100644 --- a/include/msgpack/v2/parse.hpp +++ b/include/msgpack/v2/parse.hpp @@ -763,6 +763,14 @@ class parser : public detail::context { void expand_buffer(std::size_t size); parse_return execute_imp(); +protected: + // Re-point the buffer-hook, e.g. after a move of the owning object so that + // the hook refers to the moved-to object's member rather than the + // moved-from (soon to be destroyed) object's member. + void set_referenced_buffer_hook(ReferencedBufferHook& hook) { + m_referenced_buffer_hook = &hook; + } + private: char* m_buffer; std::size_t m_used; @@ -770,7 +778,7 @@ class parser : public detail::context { std::size_t m_off; std::size_t m_parsed; std::size_t m_initial_buffer_size; - ReferencedBufferHook& m_referenced_buffer_hook; + ReferencedBufferHook* m_referenced_buffer_hook; #if defined(MSGPACK_USE_CPP03) private: @@ -787,7 +795,7 @@ template inline parser::parser( ReferencedBufferHook& hook, std::size_t initial_buffer_size) - :m_referenced_buffer_hook(hook) + :m_referenced_buffer_hook(&hook) { if(initial_buffer_size < COUNTER_SIZE) { initial_buffer_size = COUNTER_SIZE; @@ -830,8 +838,10 @@ inline parser::parser(this_type&& other) template inline parser& parser::operator=(this_type&& other) { - this->~parser(); - new (this) this_type(std::move(other)); + if (this != &other) { + this->~parser(); + new (this) this_type(std::move(other)); + } return *this; } @@ -914,7 +924,7 @@ inline void parser::expand_buffer(std::size if(static_cast(*this).referenced()) { try { - m_referenced_buffer_hook(m_buffer); + (*m_referenced_buffer_hook)(m_buffer); } catch (...) { ::free(tmp); diff --git a/include/msgpack/v2/unpack.hpp b/include/msgpack/v2/unpack.hpp index 993643ab0..73993d4d0 100644 --- a/include/msgpack/v2/unpack.hpp +++ b/include/msgpack/v2/unpack.hpp @@ -48,6 +48,27 @@ class unpacker : public parser, set_referenced(false); } +#if !defined(MSGPACK_USE_CPP03) + unpacker(unpacker&& other) + :parser_t(std::move(other)), + detail::create_object_visitor(std::move(other)), + m_z(std::move(other.m_z)), + m_finalizer(std::move(other.m_finalizer)) { + // The parser base copied a hook pointer that still refers to the + // moved-from object's m_finalizer; re-point it to our own. The zone + // itself is heap-allocated and only ownership moved, so the zone + // pointers held by the visitor and by m_finalizer stay valid. + parser_t::set_referenced_buffer_hook(m_finalizer); + } + unpacker& operator=(unpacker&& other) { + if (this != &other) { + this->~unpacker(); + new (this) unpacker(std::move(other)); + } + return *this; + } +#endif // !defined(MSGPACK_USE_CPP03) + detail::create_object_visitor& visitor() { return *this; } /// Unpack one msgpack::object. /** From edb70a02b60b5e53fb9464e3cea02216ed76fd29 Mon Sep 17 00:00:00 2001 From: Takatoshi Kondo Date: Tue, 25 Aug 2026 15:45:58 +0900 Subject: [PATCH 06/18] Fix destruction order bug on move. Add overflow guard on allocate_expand(). --- include/msgpack/v1/detail/cpp11_zone.hpp | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/include/msgpack/v1/detail/cpp11_zone.hpp b/include/msgpack/v1/detail/cpp11_zone.hpp index 2586f2755..19eb40a71 100644 --- a/include/msgpack/v1/detail/cpp11_zone.hpp +++ b/include/msgpack/v1/detail/cpp11_zone.hpp @@ -150,6 +150,8 @@ class zone { chunk_list(chunk_list&& other) noexcept :m_free(other.m_free), m_ptr(other.m_ptr), m_head(other.m_head) { + other.m_free = 0; + other.m_ptr = MSGPACK_NULLPTR; other.m_head = MSGPACK_NULLPTR; } chunk_list& operator=(chunk_list&& other) noexcept @@ -208,7 +210,18 @@ class zone { T* allocate(Args... args); zone(zone&&) = default; - zone& operator=(zone&&) = default; + zone& operator=(zone&& other) { + if (this != &other) { + // Destroy in the correct order: run finalizers first (while our + // chunks are still alive), then release our chunks. A defaulted + // move-assignment would free the chunks before the finalizers run, + // causing use-after-free of zone-allocated objects. + m_finalizer_array = std::move(other.m_finalizer_array); + m_chunk_list = std::move(other.m_chunk_list); + m_chunk_size = other.m_chunk_size; + } + return *this; + } zone(const zone&) = delete; zone& operator=(const zone&) = delete; @@ -281,6 +294,10 @@ inline char* zone::allocate_expand(size_t size) sz = tmp_sz; } + if((sizeof(chunk) + sz) < sz) { + throw std::bad_alloc(); + } + chunk* c = static_cast(::malloc(sizeof(chunk) + sz)); if (!c) throw std::bad_alloc(); From f70f53e11d788a3af67b2845c7ff7d7e9ad5fd66 Mon Sep 17 00:00:00 2001 From: Takatoshi Kondo Date: Tue, 25 Aug 2026 15:49:08 +0900 Subject: [PATCH 07/18] Fix double free bug (C++03 only) Add overflow guard on allocate_expand(). --- erb/v1/cpp03_zone.hpp.erb | 16 ++++++++++++++-- include/msgpack/v1/detail/cpp03_zone.hpp | 16 ++++++++++++++-- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/erb/v1/cpp03_zone.hpp.erb b/erb/v1/cpp03_zone.hpp.erb index 6b3a57d3e..d61d74231 100644 --- a/erb/v1/cpp03_zone.hpp.erb +++ b/erb/v1/cpp03_zone.hpp.erb @@ -249,6 +249,10 @@ inline char* zone::allocate_expand(size_t size) sz = tmp_sz; } + if((sizeof(chunk) + sz) < sz) { + throw std::bad_alloc(); + } + chunk* c = static_cast(::malloc(sizeof(chunk) + sz)); if (!c) throw std::bad_alloc(); @@ -283,8 +287,16 @@ inline void zone::swap(zone& o) { using std::swap; swap(m_chunk_size, o.m_chunk_size); - swap(m_chunk_list, o.m_chunk_list); - swap(m_finalizer_array, o.m_finalizer_array); + // Swap the internal pointers directly. std::swap on chunk_list / + // finalizer_array would construct a temporary and run its owning + // destructor (freeing chunks and executing finalizers) on memory that + // has just been transferred to the other zone -> double free / UAF. + swap(m_chunk_list.m_free, o.m_chunk_list.m_free); + swap(m_chunk_list.m_ptr, o.m_chunk_list.m_ptr); + swap(m_chunk_list.m_head, o.m_chunk_list.m_head); + swap(m_finalizer_array.m_tail, o.m_finalizer_array.m_tail); + swap(m_finalizer_array.m_end, o.m_finalizer_array.m_end); + swap(m_finalizer_array.m_array, o.m_finalizer_array.m_array); } template diff --git a/include/msgpack/v1/detail/cpp03_zone.hpp b/include/msgpack/v1/detail/cpp03_zone.hpp index 62def9899..70a8b4ef5 100644 --- a/include/msgpack/v1/detail/cpp03_zone.hpp +++ b/include/msgpack/v1/detail/cpp03_zone.hpp @@ -294,6 +294,10 @@ inline char* zone::allocate_expand(size_t size) sz = tmp_sz; } + if((sizeof(chunk) + sz) < sz) { + throw std::bad_alloc(); + } + chunk* c = static_cast(::malloc(sizeof(chunk) + sz)); if (!c) throw std::bad_alloc(); @@ -328,8 +332,16 @@ inline void zone::swap(zone& o) { using std::swap; swap(m_chunk_size, o.m_chunk_size); - swap(m_chunk_list, o.m_chunk_list); - swap(m_finalizer_array, o.m_finalizer_array); + // Swap the internal pointers directly. std::swap on chunk_list / + // finalizer_array would construct a temporary and run its owning + // destructor (freeing chunks and executing finalizers) on memory that + // has just been transferred to the other zone -> double free / UAF. + swap(m_chunk_list.m_free, o.m_chunk_list.m_free); + swap(m_chunk_list.m_ptr, o.m_chunk_list.m_ptr); + swap(m_chunk_list.m_head, o.m_chunk_list.m_head); + swap(m_finalizer_array.m_tail, o.m_finalizer_array.m_tail); + swap(m_finalizer_array.m_end, o.m_finalizer_array.m_end); + swap(m_finalizer_array.m_array, o.m_finalizer_array.m_array); } template From eb1e385194face5b5d12095a688cd607ac588720 Mon Sep 17 00:00:00 2001 From: Takatoshi Kondo Date: Tue, 25 Aug 2026 15:50:46 +0900 Subject: [PATCH 08/18] Fix positive/negarive invalid uint8 conversion. --- include/msgpack/v2/x3_parse.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/msgpack/v2/x3_parse.hpp b/include/msgpack/v2/x3_parse.hpp index b55fb2cfd..fa30e8ed8 100644 --- a/include/msgpack/v2/x3_parse.hpp +++ b/include/msgpack/v2/x3_parse.hpp @@ -214,7 +214,7 @@ const auto mp_object_def = ( [](auto& ctx){ auto& app_specific = x3::get(ctx).get(); - app_specific.vis.visit_negative_integer(_attr(ctx)); + app_specific.vis.visit_positive_integer(_attr(ctx)); } ) ] From ad080887a12ebfd5b6718f20c9e1309eaa4bc0f5 Mon Sep 17 00:00:00 2001 From: Takatoshi Kondo Date: Tue, 25 Aug 2026 15:51:46 +0900 Subject: [PATCH 09/18] Fix size limit checking logic. --- include/msgpack/v1/unpack.hpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/include/msgpack/v1/unpack.hpp b/include/msgpack/v1/unpack.hpp index 2113c50f2..9dfdcbae4 100644 --- a/include/msgpack/v1/unpack.hpp +++ b/include/msgpack/v1/unpack.hpp @@ -167,12 +167,12 @@ inline void unpack_map_item(msgpack::object& c, msgpack::object const& k, msgpac inline void unpack_str(unpack_user& u, const char* p, uint32_t l, msgpack::object& o) { o.type = msgpack::type::STR; + if (l > u.limit().str()) throw msgpack::str_size_overflow("str size overflow"); if (u.reference_func() && u.reference_func()(o.type, l, u.user_data())) { o.via.str.ptr = p; u.set_referenced(true); } else if (l > 0) { - if (l > u.limit().str()) throw msgpack::str_size_overflow("str size overflow"); char* tmp = static_cast(u.zone().allocate_align(l, MSGPACK_ZONE_ALIGNOF(char))); std::memcpy(tmp, p, l); o.via.str.ptr = tmp; @@ -186,12 +186,12 @@ inline void unpack_str(unpack_user& u, const char* p, uint32_t l, msgpack::objec inline void unpack_bin(unpack_user& u, const char* p, uint32_t l, msgpack::object& o) { o.type = msgpack::type::BIN; + if (l > u.limit().bin()) throw msgpack::bin_size_overflow("bin size overflow"); if (u.reference_func() && u.reference_func()(o.type, l, u.user_data())) { o.via.bin.ptr = p; u.set_referenced(true); } else if (l > 0) { - if (l > u.limit().bin()) throw msgpack::bin_size_overflow("bin size overflow"); char* tmp = static_cast(u.zone().allocate_align(l, MSGPACK_ZONE_ALIGNOF(char))); std::memcpy(tmp, p, l); o.via.bin.ptr = tmp; @@ -205,12 +205,12 @@ inline void unpack_bin(unpack_user& u, const char* p, uint32_t l, msgpack::objec inline void unpack_ext(unpack_user& u, const char* p, std::size_t l, msgpack::object& o) { o.type = msgpack::type::EXT; + if (l > u.limit().ext()) throw msgpack::ext_size_overflow("ext size overflow"); if (u.reference_func() && u.reference_func()(o.type, l, u.user_data())) { o.via.ext.ptr = p; u.set_referenced(true); } else { - if (l > u.limit().ext()) throw msgpack::ext_size_overflow("ext size overflow"); char* tmp = static_cast(u.zone().allocate_align(l, MSGPACK_ZONE_ALIGNOF(char))); std::memcpy(tmp, p, l); o.via.ext.ptr = tmp; @@ -1105,8 +1105,10 @@ inline unpacker::unpacker(unpacker&& other) } inline unpacker& unpacker::operator=(unpacker&& other) { - this->~unpacker(); - new (this) unpacker(std::move(other)); + if (this != &other) { + this->~unpacker(); + new (this) unpacker(std::move(other)); + } return *this; } From ee24845c8220f92843c4f027e562a75740b9e416 Mon Sep 17 00:00:00 2001 From: Takatoshi Kondo Date: Tue, 25 Aug 2026 15:52:46 +0900 Subject: [PATCH 10/18] Fix invalid comparison on migrate(). --- include/msgpack/v1/vrefbuffer.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/msgpack/v1/vrefbuffer.hpp b/include/msgpack/v1/vrefbuffer.hpp index 1887a5c33..42b23c09f 100644 --- a/include/msgpack/v1/vrefbuffer.hpp +++ b/include/msgpack/v1/vrefbuffer.hpp @@ -213,7 +213,7 @@ class vrefbuffer { empty->next = MSGPACK_NULLPTR; const size_t nused = static_cast(m_tail - m_array); - if(to->m_tail + nused < m_end) { + if(to->m_tail + nused > to->m_end) { const size_t tosize = static_cast(to->m_tail - to->m_array); const size_t reqsize = nused + tosize; size_t nnext = static_cast(to->m_end - to->m_array) * 2; From e4b3c57bcaf22bda598c6cd8760f325bc85c45ad Mon Sep 17 00:00:00 2001 From: Takatoshi Kondo Date: Tue, 25 Aug 2026 15:55:18 +0900 Subject: [PATCH 11/18] Fix invalid comparison. --- include/msgpack/v1/adaptor/array_ref.hpp | 28 +++++++++++++----------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/include/msgpack/v1/adaptor/array_ref.hpp b/include/msgpack/v1/adaptor/array_ref.hpp index d44be1645..0838a70eb 100644 --- a/include/msgpack/v1/adaptor/array_ref.hpp +++ b/include/msgpack/v1/adaptor/array_ref.hpp @@ -78,10 +78,8 @@ struct array_ref { template bool operator==(array_ref const& t) const { if (N != t.size()) return false; - T const* pself = data; - U const* pother = t.data; - for (; pself != &data[N]; ++pself, ++pother) { - if (*pself != *pother) return false; + for (std::size_t i = 0; i < N; ++i) { + if (!(data[i] == t.data[i])) return false; } return true; } @@ -92,28 +90,32 @@ struct array_ref { template bool operator< (array_ref const& t) const { - T const* pself = data; - U const* pother = t.data; - for (; pself != &data[N] && pother != t.data[t.size()]; ++pself, ++pother) { - if (*pself < *pother) return true; + std::size_t n = (N < t.size()) ? N : t.size(); + for (std::size_t i = 0; i < n; ++i) { + if (data[i] < t.data[i]) return true; + if (t.data[i] < data[i]) return false; } - if (N < t.size()) return true; - return false; + return N < t.size(); } template bool operator> (array_ref const& t) const { - return t.data < data; + std::size_t n = (N < t.size()) ? N : t.size(); + for (std::size_t i = 0; i < n; ++i) { + if (t.data[i] < data[i]) return true; + if (data[i] < t.data[i]) return false; + } + return t.size() < N; } template bool operator<= (array_ref const& t) const { - return !(t.data < data); + return !(*this > t); } template bool operator>= (array_ref const& t) const { - return !(data < t.data); + return !(*this < t); } }; From 612f267e8d0f1e61daccc303c3d331f5b24bc329 Mon Sep 17 00:00:00 2001 From: Takatoshi Kondo Date: Tue, 25 Aug 2026 15:56:43 +0900 Subject: [PATCH 12/18] Fix self assign bug. --- include/msgpack/v2/create_object_visitor.hpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/include/msgpack/v2/create_object_visitor.hpp b/include/msgpack/v2/create_object_visitor.hpp index d0e234e92..492aa7292 100644 --- a/include/msgpack/v2/create_object_visitor.hpp +++ b/include/msgpack/v2/create_object_visitor.hpp @@ -44,8 +44,10 @@ class create_object_visitor : public msgpack::v2::null_visitor { m_stack[0] = &m_obj; } create_object_visitor& operator=(create_object_visitor&& other) { - this->~create_object_visitor(); - new (this) create_object_visitor(std::move(other)); + if (this != &other) { + this->~create_object_visitor(); + new (this) create_object_visitor(std::move(other)); + } return *this; } #endif // !defined(MSGPACK_USE_CPP03) From 5a117b3fa2eb94c685f5b753cae15b6c95f805da Mon Sep 17 00:00:00 2001 From: Takatoshi Kondo Date: Tue, 25 Aug 2026 16:11:55 +0900 Subject: [PATCH 13/18] Fix comparison error. --- include/msgpack/v1/adaptor/ext.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/msgpack/v1/adaptor/ext.hpp b/include/msgpack/v1/adaptor/ext.hpp index 11fed72f9..054d99c1a 100644 --- a/include/msgpack/v1/adaptor/ext.hpp +++ b/include/msgpack/v1/adaptor/ext.hpp @@ -149,7 +149,7 @@ class ext_ref { } bool operator== (const ext_ref& x) const { - return m_size == x.m_size && std::memcmp(m_ptr, x.m_ptr, m_size) == 0; + return m_size == x.m_size && std::memcmp(m_ptr, x.m_ptr, m_size + 1) == 0; } bool operator!= (const ext_ref& x) const { @@ -159,13 +159,13 @@ class ext_ref { bool operator< (const ext_ref& x) const { if (m_size < x.m_size) return true; if (m_size > x.m_size) return false; - return std::memcmp(m_ptr, x.m_ptr, m_size) < 0; + return std::memcmp(m_ptr, x.m_ptr, m_size + 1) < 0; } bool operator> (const ext_ref& x) const { if (m_size > x.m_size) return true; if (m_size < x.m_size) return false; - return std::memcmp(m_ptr, x.m_ptr, m_size) > 0; + return std::memcmp(m_ptr, x.m_ptr, m_size + 1) > 0; } private: From 101351766df30bb7c3589a38c3a4871cb2a56d4c Mon Sep 17 00:00:00 2001 From: Takatoshi Kondo Date: Tue, 25 Aug 2026 16:12:44 +0900 Subject: [PATCH 14/18] Add tests. --- test/CMakeLists.txt | 1 + test/security_fixes_cpp11.cpp | 175 ++++++++++++++++++++++++++++++++++ 2 files changed, 176 insertions(+) create mode 100644 test/security_fixes_cpp11.cpp diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index b29c07a2c..7a9113ac7 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -47,6 +47,7 @@ IF (MSGPACK_CXX11 OR MSGPACK_CXX14 OR MSGPACK_CXX17 OR MSGPACK_CXX20) msgpack_cpp11.cpp reference_cpp11.cpp reference_wrapper_cpp11.cpp + security_fixes_cpp11.cpp shared_ptr_cpp11.cpp unique_ptr_cpp11.cpp diff --git a/test/security_fixes_cpp11.cpp b/test/security_fixes_cpp11.cpp new file mode 100644 index 000000000..86fb0825f --- /dev/null +++ b/test/security_fixes_cpp11.cpp @@ -0,0 +1,175 @@ +#include + +#define BOOST_TEST_MODULE security_fixes +#include + +#include +#include +#include +#include +#include +#include +#include + +// Regression tests for memory-safety / correctness fixes. +// See the audit that accompanied the msgpack_unpacker_expand_buffer overflow fix. + +// A1: convert of an empty msgpack array into a C array must not dereference a +// null/oversized pointer. size < N leaves the remaining elements untouched; +// size > N throws. +BOOST_AUTO_TEST_CASE(carray_empty_array_no_crash) +{ + msgpack::object_handle oh = msgpack::unpack("\x90", 1); // empty fixarray + int v[3] = {7, 8, 9}; + oh.get().convert(v); // must not crash + BOOST_CHECK_EQUAL(v[0], 7); + + msgpack::object_handle oh2 = msgpack::unpack("\x94\x01\x02\x03\x04", 5); // 4 elems + int v2[3]; + BOOST_CHECK_THROW(oh2.get().convert(v2), msgpack::type_error); +} + +// A2: convert of an empty array into msgpack::type::tuple must not dereference +// a null pointer (the N==1 base specialization was missing the size guard). +BOOST_AUTO_TEST_CASE(msgpack_tuple_convert_empty_array) +{ + msgpack::object_handle oh = msgpack::unpack("\x90", 1); + msgpack::type::tuple t; + oh.get().convert(t); // must not crash + BOOST_CHECK(true); +} + +// A3: as on an array shorter than the tuple must throw, not read OOB +// (index computation size - sizeof...(Args) - 1 used to underflow). +BOOST_AUTO_TEST_CASE(tuple_as_short_array_throws) +{ + using tp = std::chrono::system_clock::time_point; + const char b[] = "\x91\x01"; // [1] + msgpack::object_handle oh = msgpack::unpack(b, 2); + BOOST_CHECK_THROW((oh.get().as >()), msgpack::type_error); + BOOST_CHECK_THROW((oh.get().as >()), msgpack::type_error); +} + +// A4: converting a short STR into std::array must copy only str.size +// bytes, not N (which over-read the source). +BOOST_AUTO_TEST_CASE(array_char_str_no_overread) +{ + msgpack::sbuffer sb; + msgpack::pack(sb, std::string("ab")); + msgpack::object_handle oh = msgpack::unpack(sb.data(), sb.size()); + std::array a; + a.fill('Z'); + oh.get().convert(a); + BOOST_CHECK_EQUAL(a[0], 'a'); + BOOST_CHECK_EQUAL(a[1], 'b'); + BOOST_CHECK_EQUAL(a[2], 'Z'); // untouched +} + +// B1: moving a (v2) unpacker must not leave the parser referencing the +// moved-from object's buffer hook. +BOOST_AUTO_TEST_CASE(unpacker_move_buffer_hook) +{ + msgpack::unpacker u1(MSGPACK_NULLPTR, MSGPACK_NULLPTR, 64); + msgpack::unpacker u2(std::move(u1)); + const char m[] = "\x92\xa3" "abc"; // array(2) + referencing str, 2nd elem missing + u2.reserve_buffer(5); + std::memcpy(u2.buffer(), m, 5); + u2.buffer_consumed(5); + msgpack::object_handle oh; + u2.next(oh); + u2.reserve_buffer(1 << 20); // exercises the referenced-buffer hook + BOOST_CHECK(true); + + // move-assignment and self-move-assignment + msgpack::unpacker u3(MSGPACK_NULLPTR, MSGPACK_NULLPTR, 64); + u3 = std::move(u2); +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wself-move" +#endif + u3 = std::move(u3); // self-move must not crash +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + BOOST_CHECK(true); +} + +// B2: zone move-assignment must run finalizers before freeing chunks. +BOOST_AUTO_TEST_CASE(zone_move_assignment) +{ + msgpack::zone z1; + z1.allocate("a fairly long string that lives in the chunk"); + msgpack::zone z2; + z1 = std::move(z2); // must not use-after-free + BOOST_CHECK(true); +} + +// B4: vrefbuffer::migrate must grow the destination iovec array when needed. +BOOST_AUTO_TEST_CASE(vrefbuffer_migrate) +{ + // Use distinct 256-byte buffers so each write becomes its own iovec and + // fills the initial iovec array of both buffers, forcing migrate() to grow + // the destination array. + std::vector bufs; + for (int i = 0; i < 8; ++i) bufs.push_back(std::string(256, static_cast('a' + i))); + msgpack::vrefbuffer from; + msgpack::vrefbuffer to; + for (int i = 0; i < 4; ++i) from.write(bufs[i].data(), bufs[i].size()); + for (int i = 4; i < 8; ++i) to.write(bufs[i].data(), bufs[i].size()); + const size_t from_n = from.vector_size(); + const size_t to_n = to.vector_size(); + from.migrate(&to); // must not overflow to's iovec array + BOOST_CHECK_EQUAL(to.vector_size(), from_n + to_n); +} + +// B5: an impossibly large zone allocation must throw, not wrap the malloc size. +BOOST_AUTO_TEST_CASE(zone_allocate_overflow) +{ + msgpack::zone z; + BOOST_CHECK_THROW( + z.allocate_no_align(std::numeric_limits::max() - 4), + std::bad_alloc); +} + +// C1: ext_ref comparison must include the whole payload (used to drop the last +// byte via memcmp(..., m_size)). +BOOST_AUTO_TEST_CASE(ext_ref_full_payload_compare) +{ + char ba[] = {7, 'x', 'y', 'A'}; + char bb[] = {7, 'x', 'y', 'B'}; + msgpack::type::ext_ref ra(ba, sizeof(ba)); + msgpack::type::ext_ref rb(bb, sizeof(bb)); + BOOST_CHECK(!(ra == rb)); + BOOST_CHECK(ra != rb); + BOOST_CHECK((ra < rb) || (rb < ra)); +} + +// C2: array_ref relational operators must compare element-wise. +BOOST_AUTO_TEST_CASE(array_ref_carray_compare) +{ + int x[3] = {1, 2, 3}; + int y[3] = {1, 2, 4}; + msgpack::type::array_ref rx = msgpack::type::make_array_ref(x); + msgpack::type::array_ref ry = msgpack::type::make_array_ref(y); + BOOST_CHECK(rx == rx); + BOOST_CHECK(rx != ry); + BOOST_CHECK(rx < ry); + BOOST_CHECK(ry > rx); + BOOST_CHECK(rx <= rx); + BOOST_CHECK(ry >= rx); +} + +// C3: v1 unpacker must enforce the str limit even on the reference path. +BOOST_AUTO_TEST_CASE(v1_unpacker_reference_path_limit) +{ + msgpack::sbuffer sb; + msgpack::pack(sb, std::string("0123456789")); + // array, map, str=2, ... + msgpack::v1::unpacker u(MSGPACK_NULLPTR, MSGPACK_NULLPTR, 64, + msgpack::unpack_limit(0xffffffff, 0xffffffff, 2)); + u.reserve_buffer(sb.size()); + std::memcpy(u.buffer(), sb.data(), sb.size()); + u.buffer_consumed(sb.size()); + msgpack::object_handle oh; + BOOST_CHECK_THROW(u.next(oh), msgpack::str_size_overflow); +} From a78b895d64f0896675353d08f0dcbe4e6dc50365 Mon Sep 17 00:00:00 2001 From: Takatoshi Kondo Date: Tue, 25 Aug 2026 16:40:40 +0900 Subject: [PATCH 15/18] Fix ext32 max size truncation on 64bit by widening visit_ext size to size_t --- include/msgpack/v1/object.hpp | 12 ++++++------ include/msgpack/v2/create_object_visitor.hpp | 2 +- include/msgpack/v2/null_visitor.hpp | 2 +- include/msgpack/v2/parse.hpp | 8 ++++---- include/msgpack/v2/x3_parse.hpp | 16 ++++++++-------- include/msgpack/v3/parse.hpp | 8 ++++---- 6 files changed, 24 insertions(+), 24 deletions(-) diff --git a/include/msgpack/v1/object.hpp b/include/msgpack/v1/object.hpp index e5a50e1af..e55ad3d61 100644 --- a/include/msgpack/v1/object.hpp +++ b/include/msgpack/v1/object.hpp @@ -265,7 +265,7 @@ class object_parser { break; case msgpack::type::EXT: msgpack::detail::check_container_size(m_current->via.ext.size); - if (!v.visit_ext(m_current->via.ext.ptr, m_current->via.ext.size + 1)) return; + if (!v.visit_ext(m_current->via.ext.ptr, static_cast(m_current->via.ext.size) + 1)) return; break; case msgpack::type::ARRAY: if (!v.start_array(m_current->via.array.size)) return; @@ -351,7 +351,7 @@ struct object_pack_visitor { m_packer.pack_bin_body(v, size); return true; } - bool visit_ext(const char* v, uint32_t size) { + bool visit_ext(const char* v, std::size_t size) { m_packer.pack_ext(size - 1, static_cast(*v)); m_packer.pack_ext_body(v + 1, size - 1); return true; @@ -470,7 +470,7 @@ struct object_stringize_visitor { m_os << "\"BIN(size:" << size << ")\""; return true; } - bool visit_ext(const char* v, uint32_t size) { + bool visit_ext(const char* v, std::size_t size) { if (size == 0) { m_os << "\"EXT(size:0)\""; } @@ -560,7 +560,7 @@ struct aligned_zone_size_visitor { m_size += msgpack::aligned_size(size, MSGPACK_ZONE_ALIGNOF(char)); return true; } - bool visit_ext(const char*, uint32_t size) { + bool visit_ext(const char*, std::size_t size) { m_size += msgpack::aligned_size(size, MSGPACK_ZONE_ALIGNOF(char)); return true; } @@ -741,7 +741,7 @@ struct object_with_zone { std::memcpy(ptr, v, size); return true; } - bool visit_ext(const char* v, uint32_t size) { + bool visit_ext(const char* v, std::size_t size) { m_ptr->type = msgpack::type::EXT; // v contains type but length(size) doesn't count the type byte. @@ -941,7 +941,7 @@ struct object_equal_visitor { } return true; } - bool visit_ext(const char* v, uint32_t size) { + bool visit_ext(const char* v, std::size_t size) { if (m_ptr->type != msgpack::type::EXT || m_ptr->via.ext.size != size - 1 || std::memcmp(m_ptr->via.ext.ptr, v, size) != 0) { diff --git a/include/msgpack/v2/create_object_visitor.hpp b/include/msgpack/v2/create_object_visitor.hpp index 492aa7292..2fe3bf598 100644 --- a/include/msgpack/v2/create_object_visitor.hpp +++ b/include/msgpack/v2/create_object_visitor.hpp @@ -156,7 +156,7 @@ class create_object_visitor : public msgpack::v2::null_visitor { } return true; } - bool visit_ext(const char* v, uint32_t size) { + bool visit_ext(const char* v, std::size_t size) { MSGPACK_ASSERT(v || size == 0); if (size > m_limit.ext()) throw msgpack::ext_size_overflow("ext size overflow"); msgpack::object* obj = m_stack.back(); diff --git a/include/msgpack/v2/null_visitor.hpp b/include/msgpack/v2/null_visitor.hpp index c9a1fdb9a..8fbbf1b8b 100644 --- a/include/msgpack/v2/null_visitor.hpp +++ b/include/msgpack/v2/null_visitor.hpp @@ -43,7 +43,7 @@ struct null_visitor { bool visit_bin(const char* /*v*/, uint32_t /*size*/) { return true; } - bool visit_ext(const char* /*v*/, uint32_t /*size*/) { + bool visit_ext(const char* /*v*/, std::size_t /*size*/) { return true; } bool start_array(uint32_t /*num_elements*/) { diff --git a/include/msgpack/v2/parse.hpp b/include/msgpack/v2/parse.hpp index 559d9b472..03495fe38 100644 --- a/include/msgpack/v2/parse.hpp +++ b/include/msgpack/v2/parse.hpp @@ -481,7 +481,7 @@ inline parse_return context::execute(const char* data, std::size_ load(tmp, n); m_trail = tmp + 1; if(m_trail == 0) { - bool visret = holder().visitor().visit_ext(n, static_cast(m_trail)); + bool visret = holder().visitor().visit_ext(n, m_trail); parse_return upr = after_visit_proc(visret, off); if (upr != PARSE_CONTINUE) return upr; } @@ -523,7 +523,7 @@ inline parse_return context::execute(const char* data, std::size_ load(tmp, n); m_trail = tmp + 1; if(m_trail == 0) { - bool visret = holder().visitor().visit_ext(n, static_cast(m_trail)); + bool visret = holder().visitor().visit_ext(n, m_trail); parse_return upr = after_visit_proc(visret, off); if (upr != PARSE_CONTINUE) return upr; } @@ -567,7 +567,7 @@ inline parse_return context::execute(const char* data, std::size_ m_trail = tmp; ++m_trail; if(m_trail == 0) { - bool visret = holder().visitor().visit_ext(n, static_cast(m_trail)); + bool visret = holder().visitor().visit_ext(n, m_trail); parse_return upr = after_visit_proc(visret, off); if (upr != PARSE_CONTINUE) return upr; } @@ -587,7 +587,7 @@ inline parse_return context::execute(const char* data, std::size_ if (upr != PARSE_CONTINUE) return upr; } break; case MSGPACK_ACS_EXT_VALUE: { - bool visret = holder().visitor().visit_ext(n, static_cast(m_trail)); + bool visret = holder().visitor().visit_ext(n, m_trail); parse_return upr = after_visit_proc(visret, off); if (upr != PARSE_CONTINUE) return upr; } break; diff --git a/include/msgpack/v2/x3_parse.hpp b/include/msgpack/v2/x3_parse.hpp index fa30e8ed8..3087620db 100644 --- a/include/msgpack/v2/x3_parse.hpp +++ b/include/msgpack/v2/x3_parse.hpp @@ -592,7 +592,7 @@ const auto mp_object_def = [](auto& ctx){ auto& app_specific = x3::get(ctx).get(); auto const& ext = _attr(ctx); - auto size = static_cast(std::distance(ext.begin(), ext.end())); + auto size = static_cast(std::distance(ext.begin(), ext.end())); app_specific.vis.visit_ext(size ? &ext.front() : nullptr, size); } ) @@ -616,7 +616,7 @@ const auto mp_object_def = [](auto& ctx){ auto& app_specific = x3::get(ctx).get(); auto const& ext = _attr(ctx); - auto size = static_cast(std::distance(ext.begin(), ext.end())); + auto size = static_cast(std::distance(ext.begin(), ext.end())); app_specific.vis.visit_ext(size ? &ext.front() : nullptr, size); } ) @@ -640,7 +640,7 @@ const auto mp_object_def = [](auto& ctx){ auto& app_specific = x3::get(ctx).get(); auto const& ext = _attr(ctx); - auto size = static_cast(std::distance(ext.begin(), ext.end())); + auto size = static_cast(std::distance(ext.begin(), ext.end())); app_specific.vis.visit_ext(size ? &ext.front() : nullptr, size); } ) @@ -664,7 +664,7 @@ const auto mp_object_def = [](auto& ctx){ auto& app_specific = x3::get(ctx).get(); auto const& ext = _attr(ctx); - auto size = static_cast(std::distance(ext.begin(), ext.end())); + auto size = static_cast(std::distance(ext.begin(), ext.end())); app_specific.vis.visit_ext(size ? &ext.front() : nullptr, size); } ) @@ -688,7 +688,7 @@ const auto mp_object_def = [](auto& ctx){ auto& app_specific = x3::get(ctx).get(); auto const& ext = _attr(ctx); - auto size = static_cast(std::distance(ext.begin(), ext.end())); + auto size = static_cast(std::distance(ext.begin(), ext.end())); app_specific.vis.visit_ext(size ? &ext.front() : nullptr, size); } ) @@ -712,7 +712,7 @@ const auto mp_object_def = [](auto& ctx){ auto& app_specific = x3::get(ctx).get(); auto const& ext = _attr(ctx); - auto size = static_cast(std::distance(ext.begin(), ext.end())); + auto size = static_cast(std::distance(ext.begin(), ext.end())); app_specific.vis.visit_ext(size ? &ext.front() : nullptr, size); } ) @@ -736,7 +736,7 @@ const auto mp_object_def = [](auto& ctx){ auto& app_specific = x3::get(ctx).get(); auto const& ext = _attr(ctx); - auto size = static_cast(std::distance(ext.begin(), ext.end())); + auto size = static_cast(std::distance(ext.begin(), ext.end())); app_specific.vis.visit_ext(size ? &ext.front() : nullptr, size); } ) @@ -760,7 +760,7 @@ const auto mp_object_def = [](auto& ctx){ auto& app_specific = x3::get(ctx).get(); auto const& ext = _attr(ctx); - auto size = static_cast(std::distance(ext.begin(), ext.end())); + auto size = static_cast(std::distance(ext.begin(), ext.end())); app_specific.vis.visit_ext(size ? &ext.front() : nullptr, size); } ) diff --git a/include/msgpack/v3/parse.hpp b/include/msgpack/v3/parse.hpp index 8a4b338ae..833bbbfb0 100644 --- a/include/msgpack/v3/parse.hpp +++ b/include/msgpack/v3/parse.hpp @@ -488,7 +488,7 @@ inline parse_return context::execute(const char* data, std::size_ load(tmp, n); m_trail = tmp + 1; if(m_trail == 0) { - bool visret = holder().visitor().visit_ext(n, static_cast(m_trail)); + bool visret = holder().visitor().visit_ext(n, m_trail); parse_return upr = after_visit_proc(visret, off); if (upr != PARSE_CONTINUE) return upr; } @@ -530,7 +530,7 @@ inline parse_return context::execute(const char* data, std::size_ load(tmp, n); m_trail = tmp + 1; if(m_trail == 0) { - bool visret = holder().visitor().visit_ext(n, static_cast(m_trail)); + bool visret = holder().visitor().visit_ext(n, m_trail); parse_return upr = after_visit_proc(visret, off); if (upr != PARSE_CONTINUE) return upr; } @@ -574,7 +574,7 @@ inline parse_return context::execute(const char* data, std::size_ m_trail = tmp; ++m_trail; if(m_trail == 0) { - bool visret = holder().visitor().visit_ext(n, static_cast(m_trail)); + bool visret = holder().visitor().visit_ext(n, m_trail); parse_return upr = after_visit_proc(visret, off); if (upr != PARSE_CONTINUE) return upr; } @@ -594,7 +594,7 @@ inline parse_return context::execute(const char* data, std::size_ if (upr != PARSE_CONTINUE) return upr; } break; case MSGPACK_ACS_EXT_VALUE: { - bool visret = holder().visitor().visit_ext(n, static_cast(m_trail)); + bool visret = holder().visitor().visit_ext(n, m_trail); parse_return upr = after_visit_proc(visret, off); if (upr != PARSE_CONTINUE) return upr; } break; From 2b70d8fe1355845d087d828c4f6d5df448ba6843 Mon Sep 17 00:00:00 2001 From: Takatoshi Kondo Date: Tue, 25 Aug 2026 16:58:03 +0900 Subject: [PATCH 16/18] Update the version to 9.0.0. --- CHANGELOG.md | 22 ++++++++++++++++++++++ README.md | 2 +- appveyor.yml | 2 +- include/msgpack/version_master.hpp | 2 +- uvis2.cpp | 23 +++++++++++++++++++++++ 5 files changed, 48 insertions(+), 3 deletions(-) create mode 100644 uvis2.cpp diff --git a/CHANGELOG.md b/CHANGELOG.md index b995eb3c3..e26dd46f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,25 @@ +# 2026-08-25 version 9.0.0 + * Add regression tests for the fixes below. (#1183) + * Fix ext_ref comparison operators ignoring the last payload byte. (#1183) + * Fix out-of-bounds write on self-move-assignment of the create object visitor. (#1183) + * Fix broken array_ref comparison operators. (#1183) + * Fix heap buffer overflow in vrefbuffer::migrate() caused by a wrong growth check. (#1183) + * Fix v1 unpacker not applying str/bin/ext size limits on the reference path. (#1183) + * Fix x3 parser treating uint8 values as negative integers. (#1183) + * Fix double free / use-after-free in zone::swap() (C++03 only). (#1183) + * Fix use-after-free from wrong member destruction order in zone move-assignment. (#1183) + * Fix use-after-free after moving an unpacker (dangling referenced buffer hook). (#1183) + * Fix out-of-bounds read converting a short STR into std::array / . (#1183) + * Fix out-of-bounds read and null dereference converting an array whose size differs from the tuple arity. (#1183) + * Fix null pointer dereference converting an empty array into a C array T[N]. (#1183) + * Fix integer overflow in the unpacker buffer expansion size arithmetic. (#1183) + +## << breaking changes >> + * Fix ext32 max size truncation on 64bit by widening visit_ext size to size_t. (#1183) + * If you have a custom visitor that implements visit_ext(), widen its size parameter from uint32_t to std::size_t: + * Before: bool visit_ext(const char* v, uint32_t size) + * After : bool visit_ext(const char* v, std::size_t size) + # 2026-05-30 version 8.0.0 * Add old style find boost applying option to cmake. (#1172) * Add missing include type_traits (#1162) diff --git a/README.md b/README.md index 108ba1c63..dadfa8506 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ `msgpack` for C++ =================== -Version 8.0.0 [![Build Status](https://github.com/msgpack/msgpack-c/workflows/CI/badge.svg?branch=cpp_master)](https://github.com/msgpack/msgpack-c/actions) [![Build status](https://ci.appveyor.com/api/projects/status/8kstcgt79qj123mw/branch/cpp_master?svg=true)](https://ci.appveyor.com/project/redboltz/msgpack-c/branch/cpp_master) +Version 9.0.0 [![Build Status](https://github.com/msgpack/msgpack-c/workflows/CI/badge.svg?branch=cpp_master)](https://github.com/msgpack/msgpack-c/actions) [![Build status](https://ci.appveyor.com/api/projects/status/8kstcgt79qj123mw/branch/cpp_master?svg=true)](https://ci.appveyor.com/project/redboltz/msgpack-c/branch/cpp_master) [![codecov](https://codecov.io/gh/msgpack/msgpack-c/branch/cpp_master/graph/badge.svg)](https://app.codecov.io/gh/msgpack/msgpack-c/tree/cpp_master) It's like JSON but smaller and faster. diff --git a/appveyor.yml b/appveyor.yml index 4bc0e1638..d1233c213 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -version: 8.0.0.{build} +version: 9.0.0.{build} branches: only: diff --git a/include/msgpack/version_master.hpp b/include/msgpack/version_master.hpp index 56072230f..aceea34d5 100644 --- a/include/msgpack/version_master.hpp +++ b/include/msgpack/version_master.hpp @@ -1,3 +1,3 @@ -#define MSGPACK_VERSION_MAJOR 8 +#define MSGPACK_VERSION_MAJOR 9 #define MSGPACK_VERSION_MINOR 0 #define MSGPACK_VERSION_REVISION 0 diff --git a/uvis2.cpp b/uvis2.cpp new file mode 100644 index 000000000..ef3b4e850 --- /dev/null +++ b/uvis2.cpp @@ -0,0 +1,23 @@ +#include +#include +#include +// A standalone visitor: does NOT derive from msgpack::null_visitor. +struct standalone_visitor { + bool visit_nil(){return true;} bool visit_boolean(bool){return true;} + bool visit_positive_integer(uint64_t){return true;} bool visit_negative_integer(int64_t){return true;} + bool visit_float32(float){return true;} bool visit_float64(double){return true;} + bool visit_str(const char*,uint32_t){return true;} bool visit_bin(const char*,uint32_t){return true;} + bool visit_ext(const char*, std::size_t){return true;} // implements visit_ext, no null_visitor base + bool start_array(uint32_t){return true;} bool start_array_item(){return true;} + bool end_array_item(){return true;} bool end_array(){return true;} + bool start_map(uint32_t){return true;} bool start_map_key(){return true;} + bool end_map_key(){return true;} bool start_map_value(){return true;} + bool end_map_value(){return true;} bool end_map(){return true;} + void parse_error(std::size_t,std::size_t){} void insufficient_bytes(std::size_t,std::size_t){} +}; +int main(){ + msgpack::sbuffer sb; char d[]={9}; msgpack::packer pk(&sb); + pk.pack_ext(1,7); pk.pack_ext_body(d,1); + standalone_visitor v; std::size_t off=0; + return msgpack::v2::parse(sb.data(), sb.size(), off, v) ? 0 : 1; +} From 342bbbe18ec40b59d50fe7fc8901907b3f83b89b Mon Sep 17 00:00:00 2001 From: Takatoshi Kondo Date: Tue, 25 Aug 2026 17:17:36 +0900 Subject: [PATCH 17/18] Fix visit_ext size operation. --- include/msgpack/v1/object.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/msgpack/v1/object.hpp b/include/msgpack/v1/object.hpp index e55ad3d61..77a96ac40 100644 --- a/include/msgpack/v1/object.hpp +++ b/include/msgpack/v1/object.hpp @@ -353,7 +353,7 @@ struct object_pack_visitor { } bool visit_ext(const char* v, std::size_t size) { m_packer.pack_ext(size - 1, static_cast(*v)); - m_packer.pack_ext_body(v + 1, size - 1); + m_packer.pack_ext_body(v + 1, static_cast(size - 1)); return true; } bool start_array(uint32_t num_elements) { @@ -746,7 +746,7 @@ struct object_with_zone { // v contains type but length(size) doesn't count the type byte. // See https://github.com/msgpack/msgpack/blob/master/spec.md#ext-format-family - m_ptr->via.ext.size = size - 1; + m_ptr->via.ext.size = static_cast(size - 1); char* ptr = static_cast(m_zone.allocate_align(size, MSGPACK_ZONE_ALIGNOF(char))); m_ptr->via.ext.ptr = ptr; From 5b1f141b3bf5ecb76ac0c6c8dc9c9bac2c5c1999 Mon Sep 17 00:00:00 2001 From: Takatoshi Kondo Date: Tue, 25 Aug 2026 17:31:18 +0900 Subject: [PATCH 18/18] Fix counter type. --- test/security_fixes_cpp11.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/security_fixes_cpp11.cpp b/test/security_fixes_cpp11.cpp index 86fb0825f..17bc3d60c 100644 --- a/test/security_fixes_cpp11.cpp +++ b/test/security_fixes_cpp11.cpp @@ -111,11 +111,11 @@ BOOST_AUTO_TEST_CASE(vrefbuffer_migrate) // fills the initial iovec array of both buffers, forcing migrate() to grow // the destination array. std::vector bufs; - for (int i = 0; i < 8; ++i) bufs.push_back(std::string(256, static_cast('a' + i))); + for (std::size_t i = 0; i < 8; ++i) bufs.push_back(std::string(256, static_cast('a' + i))); msgpack::vrefbuffer from; msgpack::vrefbuffer to; - for (int i = 0; i < 4; ++i) from.write(bufs[i].data(), bufs[i].size()); - for (int i = 4; i < 8; ++i) to.write(bufs[i].data(), bufs[i].size()); + for (std::size_t i = 0; i < 4; ++i) from.write(bufs[i].data(), bufs[i].size()); + for (std::size_t i = 4; i < 8; ++i) to.write(bufs[i].data(), bufs[i].size()); const size_t from_n = from.vector_size(); const size_t to_n = to.vector_size(); from.migrate(&to); // must not overflow to's iovec array