Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,12 @@ build
*-build
.cache
compile_commands.json

# Release archives
*.tar.gz
*.tar.xz

# Editor backup / autosave files
*~
\#*\#
.\#*
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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<T[N]> 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<char, N> / <unsigned char, N>. (#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)
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
2 changes: 1 addition & 1 deletion appveyor.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
version: 8.0.0.{build}
version: 9.0.0.{build}

branches:
only:
Expand Down
16 changes: 14 additions & 2 deletions erb/v1/cpp03_zone.hpp.erb
Original file line number Diff line number Diff line change
Expand Up @@ -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<chunk*>(::malloc(sizeof(chunk) + sz));
if (!c) throw std::bad_alloc();

Expand Down Expand Up @@ -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 <typename T>
Expand Down
28 changes: 15 additions & 13 deletions include/msgpack/v1/adaptor/array_ref.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,8 @@ struct array_ref<T[N]> {
template <typename U>
bool operator==(array_ref<U> 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;
}
Expand All @@ -92,28 +90,32 @@ struct array_ref<T[N]> {
template <typename U>
bool operator< (array_ref<U> 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 <typename U>
bool operator> (array_ref<U> 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 <typename U>
bool operator<= (array_ref<U> const& t) const
{
return !(t.data < data);
return !(*this > t);
}
template <typename U>
bool operator>= (array_ref<U> const& t) const
{
return !(data < t.data);
return !(*this < t);
}
};

Expand Down
6 changes: 2 additions & 4 deletions include/msgpack/v1/adaptor/carray.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,9 @@ struct convert<T[N]> {
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;
}
};
Expand Down
2 changes: 1 addition & 1 deletion include/msgpack/v1/adaptor/cpp11/array_char.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ struct convert<std::array<char, N>> {
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();
Expand Down
2 changes: 1 addition & 1 deletion include/msgpack/v1/adaptor/cpp11/array_unsigned_char.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ struct convert<std::array<unsigned char, N>> {
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();
Expand Down
1 change: 1 addition & 0 deletions include/msgpack/v1/adaptor/cpp11/tuple.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ struct as<std::tuple<Args...>, typename std::enable_if<msgpack::any_of<msgpack::
std::tuple<Args...> 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<Args...>::as(o);
}
};
Expand Down
4 changes: 3 additions & 1 deletion include/msgpack/v1/adaptor/detail/cpp11_msgpack_tuple.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,8 @@ struct MsgpackTupleConverter<Tuple, 1> {
static void convert (
msgpack::object const& o,
Tuple& v) {
o.via.array.ptr[0].convert<typename std::remove_reference<decltype(v.template get<0>())>::type>(v.template get<0>());
if (o.via.array.size >= 1)
o.via.array.ptr[0].convert<typename std::remove_reference<decltype(v.template get<0>())>::type>(v.template get<0>());
}
};

Expand All @@ -150,6 +151,7 @@ struct as<msgpack::type::tuple<Args...>, typename std::enable_if<msgpack::any_of
msgpack::type::tuple<Args...> 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<Args...>::as(o);
}
};
Expand Down
6 changes: 3 additions & 3 deletions include/msgpack/v1/adaptor/ext.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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:
Expand Down
16 changes: 14 additions & 2 deletions include/msgpack/v1/detail/cpp03_zone.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<chunk*>(::malloc(sizeof(chunk) + sz));
if (!c) throw std::bad_alloc();

Expand Down Expand Up @@ -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 <typename T>
Expand Down
19 changes: 18 additions & 1 deletion include/msgpack/v1/detail/cpp11_zone.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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<chunk*>(::malloc(sizeof(chunk) + sz));
if (!c) throw std::bad_alloc();

Expand Down
16 changes: 8 additions & 8 deletions include/msgpack/v1/object.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ class object_parser {
break;
case msgpack::type::EXT:
msgpack::detail::check_container_size<sizeof(std::size_t)>(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<std::size_t>(m_current->via.ext.size) + 1)) return;
break;
case msgpack::type::ARRAY:
if (!v.start_array(m_current->via.array.size)) return;
Expand Down Expand Up @@ -351,9 +351,9 @@ 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<int8_t>(*v));
m_packer.pack_ext_body(v + 1, size - 1);
m_packer.pack_ext_body(v + 1, static_cast<uint32_t>(size - 1));
return true;
}
bool start_array(uint32_t num_elements) {
Expand Down Expand Up @@ -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)\"";
}
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -741,12 +741,12 @@ struct object_with_zone<msgpack::object> {
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.
// 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<uint32_t>(size - 1);

char* ptr = static_cast<char*>(m_zone.allocate_align(size, MSGPACK_ZONE_ALIGNOF(char)));
m_ptr->via.ext.ptr = ptr;
Expand Down Expand Up @@ -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) {
Expand Down
Loading
Loading