Talos Vulnerability Report

TALOS-2016-0254

Tarantool Msgpuck mp_check Denial Of Service Vulnerability

December 16, 2016
CVE Number

CVE-2016-9036

Summary

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.

Tested Versions

Msgpuck 1.0.3

Product URLs

https://github.com/tarantool/msgpuck/tree/1.0.3

CVSSv3 Score

7.5 - CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

CWE

CWE-125: Out-of-bounds Read

Details

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;
}

Crash Information

$ 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

Exploit Proof-of-Concept

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}

Timeline

2016-12-14 - Vendor Disclosure
2016-12-16 - Public Release

Credit

Discovered by the Cisco Talos Team