CVE-2016-9036
An exploitable incorrect return value vulnerability exists in the mp_check function of Tarantool’s Msgpuck library 1.0.3. A specially crafted packet can cause the mp_check function to incorrectly return success when trying to check if decoding a map16 packet will read outside the bounds of a buffer, resulting in a denial of service vulnerability.
Msgpuck 1.0.3
https://github.com/tarantool/msgpuck/tree/1.0.3
7.5 - CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
CWE-125: Out-of-bounds Read
The Msgpuck library is used to encode and decode data that is serialized with the MsgPack (http://msgpack.org) format. This library was originally implemented to be the default library used for serialization and deserialization for the Tarantool Application Server, but is also distributed as an independent library to provide support for the MsgPack format to other C or C++ applications.
When deserializing data that is encoded with the MsgPack format, the Msgpuck library provides a function named mp_check that’s used to validate the Msgpack data before it is decoded. This function takes two arguments, one to the beginning of the MsgPack data and another to the end of the data which is used to determine if decoding the packet will read outside the bounds of the data. An example of how this is intended to be used is as follows:
// Validate
char buf[1024];
const char* b = buf;
if (!mp_check(&b, b+sizeof(buf)))
return FAILURE;
// Decode
const char* r = buf;
uint32_t count = mp_decode_map(&r);
for (int i = 0; i < count; i++) {
k = mp_decode_uint(&r);
v = mp_decode_uint(&r);
}
...
For optimization purposes, each of the Msgpuck functions are inlined. When calling mp_check, the following code will
be executed. First the library will read a byte that determines the type. This type will then be used to determine
how many more bytes are expected for the encoded type. When the type is a map16 type, the library will check to see
if the sum of the current read position and the size of a uint16_t seeks past the end pointer. Due to a typo, however,
the library will incorrectly return false which is a result that’s different from the function’s failure result.
One can see that the result of a map32 returns a constant 1
when that particular failure occurs. This means that
if the 2 bytes determining the map16’s length cause the sum to seek past the end pointer, the function will succeed.
Later when the library tries to decode this data, the library will read outside the bounds of the source data buffer.
msgpuck/msgpuck.h:1819
MP_IMPL int
mp_check(const char **data, const char *end)
{
int k;
for (k = 1; k > 0; k--) {
if (mp_unlikely(*data >= end))
return 1;
uint8_t c = mp_load_u8(data);
int l = mp_parser_hint[c];
if (mp_likely(l >= 0)) {
*data += l;
continue;
} else if (mp_likely(l > MP_HINT)) {
k -= l;
continue;
}
uint32_t len;
switch (l) {
...
case MP_HINT_MAP_16:
/* MP_MAP (16) */
if (mp_unlikely(*data + sizeof(uint16_t) > end))
return false; // XXX: Should return 1 on failure.
k += 2 * mp_load_u16(data);
break;
case MP_HINT_MAP_32:
/* MP_MAP (32) */
if (mp_unlikely(*data + sizeof(uint32_t) > end))
return 1;
k += 2 * mp_load_u32(data);
break;
...
default:
mp_unreachable();
}
}
if (mp_unlikely(*data > end))
return 1;
return 0;
}
$ gdb --quiet --args ./poc-server.out 0.0.0.0:57005
...
$ python poc 127.0.0.1:57005
...
Catchpoint 4 (signal SIGSEGV), 0x0000000000402bdc in mp_load_u16 ()
(gdb) x/i $pc
=> 0x402bdc <mp_load_u16+15>: movzwl (%rax),%eax
(gdb) i r rax
rax 0x7ffff7ff6fff 0x7ffff7ff6fff
In order to demonstrate the out-of-bounds read, a server that reads a MsgPack decoded map type is provided. This
server allocates space for the source buffer followed by a guard-page to show the exact instruction that reads outside
the allocated buffer. To compile this, simply copy the poc-server.cc
file to the root of the Tarantool directory and
type in the following. This will create a binary named poc-server.out
which will run the MsgPack server. The arguments
to this binary control which interface and port the server will bind to.
$ g++ -Wall -Isrc/lib/msgpuck src/lib/msgpuck/msgpuck.c -std=c++11 -o poc-server.out poc-server.cc
$ ./poc-server.out
Usage: ./poc-server.out host:port
$ ./poc-server.out 0.0.0.0:57005
Listening on 0.0.0.0:57005
...
Once the server is running, the proof-of-concept can be executed against the server using python. This is done using a similar syntax. The proof-of-concept will send 5 packets. The first 3 packets that are sent will exercise the fixmap, map16, and map32 encoded types. The 4th packet will send a malformed map16 type. The 5th and last packet will trigger the vulnerability causing the out-of-bounds read.
$ python poc host:port
Sending a valid fixmap {1:1, 2:2, 3:3, 4:4, 5:5}
Sending a valid map16 {1:1, 2:2, 3:3, 4:4, 5:5}
Sending a valid map32 {1:1, 2:2, 3:3, 4:4, 5:5}
Sending a invalid map16 {1:1, 2:2, 3:3, 4:4, 5:5}
Sending a vulnerable map16 {1:1, 2:2, 3:3, 4:4, 5:5}
2016-12-14 - Vendor Disclosure
2016-12-16 - Public Release
Discovered by the Cisco Talos Team