diff --git a/cmake/pack_template.h.in b/cmake/pack_template.h.in index 33ce683fc..0c6152b45 100644 --- a/cmake/pack_template.h.in +++ b/cmake/pack_template.h.in @@ -41,6 +41,18 @@ #error msgpack_pack_append_buffer callback is not defined #endif +/* + * Container and raw lengths travel in a header field that is at most 32 + * bits wide. Refuse a size_t that does not fit instead of emitting its low + * 32 bits, which would leave the header disagreeing with the body. + */ +#if SIZE_MAX > UINT32_MAX +#define msgpack_pack_check_container_size(n) \ + do { if((n) > UINT32_MAX) { return -1; } } while(0) +#else +#define msgpack_pack_check_container_size(n) do { } while(0) +#endif + #if defined(_MSC_VER) # pragma warning(push) # pragma warning(disable : 4204) /* nonstandard extension used: non-constant aggregate initializer */ @@ -719,6 +731,7 @@ msgpack_pack_inline_func(_false)(msgpack_pack_user x) msgpack_pack_inline_func(_array)(msgpack_pack_user x, size_t n) { + msgpack_pack_check_container_size(n); if(n < 16) { unsigned char d = 0x90 | (uint8_t)n; msgpack_pack_append_buffer(x, &d, 1); @@ -740,6 +753,7 @@ msgpack_pack_inline_func(_array)(msgpack_pack_user x, size_t n) msgpack_pack_inline_func(_map)(msgpack_pack_user x, size_t n) { + msgpack_pack_check_container_size(n); if(n < 16) { unsigned char d = 0x80 | (uint8_t)n; msgpack_pack_append_buffer(x, &TAKE8_8(d), 1); @@ -761,6 +775,7 @@ msgpack_pack_inline_func(_map)(msgpack_pack_user x, size_t n) msgpack_pack_inline_func(_str)(msgpack_pack_user x, size_t l) { + msgpack_pack_check_container_size(l); if(l < 32) { unsigned char d = 0xa0 | (uint8_t)l; msgpack_pack_append_buffer(x, &TAKE8_8(d), 1); @@ -790,6 +805,7 @@ msgpack_pack_inline_func(_str_body)(msgpack_pack_user x, const void* b, size_t l msgpack_pack_inline_func(_v4raw)(msgpack_pack_user x, size_t l) { + msgpack_pack_check_container_size(l); if(l < 32) { unsigned char d = 0xa0 | (uint8_t)l; msgpack_pack_append_buffer(x, &TAKE8_8(d), 1); @@ -815,6 +831,7 @@ msgpack_pack_inline_func(_v4raw_body)(msgpack_pack_user x, const void* b, size_t msgpack_pack_inline_func(_bin)(msgpack_pack_user x, size_t l) { + msgpack_pack_check_container_size(l); if(l < 256) { unsigned char buf[2]; buf[0] = 0xc4; buf[1] = (uint8_t)l; @@ -841,6 +858,7 @@ msgpack_pack_inline_func(_bin_body)(msgpack_pack_user x, const void* b, size_t l msgpack_pack_inline_func(_ext)(msgpack_pack_user x, size_t l, int8_t type) { + msgpack_pack_check_container_size(l); switch(l) { case 1: { unsigned char buf[2]; @@ -938,6 +956,8 @@ msgpack_pack_inline_func(_timestamp)(msgpack_pack_user x, const msgpack_timestam #undef TAKE8_32 #undef TAKE8_64 +#undef msgpack_pack_check_container_size + #undef msgpack_pack_real_uint8 #undef msgpack_pack_real_uint16 #undef msgpack_pack_real_uint32 diff --git a/test/msgpack_c.cpp b/test/msgpack_c.cpp index 8bfb230cd..35d73c8d5 100644 --- a/test/msgpack_c.cpp +++ b/test/msgpack_c.cpp @@ -681,6 +681,46 @@ TEST(MSGPACKC, simple_buffer_ext_maxlen) } #endif // SIZE_MAX > UINT32_MAX +// The length field of a str/bin/ext/array/map header is at most 32 bits +// wide. A size_t that does not fit is rejected rather than narrowed into a +// header that disagrees with the body the caller appends next. +#if SIZE_MAX > UINT32_MAX +TEST(MSGPACKC, pack_length_over_uint32) +{ + // 2^32 + 1: the low 32 bits say "1 byte" + const size_t over = static_cast(UINT32_MAX) + 2; + + msgpack_sbuffer sbuf; + msgpack_sbuffer_init(&sbuf); + msgpack_packer pk; + msgpack_packer_init(&pk, &sbuf, msgpack_sbuffer_write); + + EXPECT_EQ(-1, msgpack_pack_str(&pk, over)); + EXPECT_EQ(-1, msgpack_pack_v4raw(&pk, over)); + EXPECT_EQ(-1, msgpack_pack_bin(&pk, over)); + EXPECT_EQ(-1, msgpack_pack_ext(&pk, over, 1)); + EXPECT_EQ(-1, msgpack_pack_array(&pk, over)); + EXPECT_EQ(-1, msgpack_pack_map(&pk, over)); + // a rejected header leaves nothing in the buffer + EXPECT_EQ(0u, sbuf.size); + + // the largest encodable length still goes out as str32 / ext32 + EXPECT_EQ(0, msgpack_pack_str(&pk, static_cast(UINT32_MAX))); + EXPECT_EQ(5u, sbuf.size); + EXPECT_EQ(static_cast(0xdbu), sbuf.data[0]); + EXPECT_EQ(static_cast(0xffu), sbuf.data[1]); + EXPECT_EQ(static_cast(0xffu), sbuf.data[4]); + EXPECT_EQ(0, msgpack_pack_ext(&pk, static_cast(UINT32_MAX), 1)); + EXPECT_EQ(11u, sbuf.size); + EXPECT_EQ(static_cast(0xc9u), sbuf.data[5]); + EXPECT_EQ(static_cast(0xffu), sbuf.data[6]); + EXPECT_EQ(static_cast(0xffu), sbuf.data[9]); + EXPECT_EQ(static_cast(0x01u), sbuf.data[10]); + + msgpack_sbuffer_destroy(&sbuf); +} +#endif // SIZE_MAX > UINT32_MAX + TEST(MSGPACKC, simple_buffer_timestamp_32) { msgpack_timestamp ts = {