CVE-2017-2892
An exploitable arbitrary memory read vulnerability exists in the MQTT packet parsing functionality of Cesanta Mongoose 6.8. A specially crafted MQTT packet can cause an arbitrary out-of-bounds memory read and write potentially resulting in information disclosure, denial of service and remote code execution. An attacker needs to send a specially crafted MQTT packet over network to trigger this vulnerability.
Cesanta Mongoose 6.8
9.8 - CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
CWE-190: Integer Overflow or Wraparound
Mongoose is a monolithic library implementing a number of networking protocols, including HTTP, MQTT, MDNS and others. It is designed with embedded devices in mind and as such is used in many IoT devices and runs on virtually all platforms.
While parsing an MQTT packet with variable length header no check is performed to assure the calculated payload length corresponds to the actual received packet. An arbitrary length is used in pointer arithmetic leading to arbitrary memory access. Variable payload length in mqtt packet is encoded by 7 bit fields with 8th bit in a byte being used as continuation bit. The following code from the parse_mqtt
function decodes this:
/* decode mqtt variable length */
do
len += (*p & 127) << 7 * (p - &io->buf[1]);
while ((*p++ & 128) != 0 && ((size_t)(p - io->buf) <= io->len));
In the above code, no check is performed on the calculated len
value which can be arbitrarily large. By the MQTT standard, the largest MQTT packet can be at most 256 megabytes. Further, a following check is performed:
end = p + len;
if (end > io->buf + io->len + 1)
return -1;
In the above code, end
should point to the end of message, and the if
tries to check if it’s in bounds of the buffer, but since the check is comparing pointers, an integer overflow can cause end
to wrap around and point before the start of message buffer, while still having huge len
value calculated before.
This can cause further memory corruption down the line when actually handling the commands sent in the packet. For example, this can be exploited by sending a “PUBLISH” command, which ends up notifying all the clients subscribed to a certain topic. Still in the parse_mqtt
function we see:
case MG_MQTT_CMD_PUBLISH: {
if (MG_MQTT_GET_QOS(header) > 0)
mm->message_id = getu16(p);
p += 2;
p = scanto(p, &mm->topic);
mm->payload.p = p;
mm->payload.len = end - p;
break;
The above code deals with the “PUBLISH” command and uses the end
pointer and p
to calculate the length, due to the previous integer overflow , end
can point to before p
leading to a large payload.len
value which is later used when sending the notification to subscribed clients.
With precise memory layout control, this can be abused to cause an arbitrary write which could lead to remote code execution. On the other hand, there is a potential to abuse this vulnerability to leak large amount of data from the process as the overflown value is used when sending data to clients. The vulnerability can be triggered by sending the supplied proof of concept packet to sample mqtt_broker
application supplied with the library. It should be noted that depending on memory layout, the proof of concept packet might not crash the application, but it does trigger the bug.
Valgrind output:
==118470== Memcheck, a memory error detector
==118470== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==118470== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==118470== Command: ../../../vanilla/mongoose/examples/mqtt_broker/mqtt_broker
==118470==
MQTT broker started on 0.0.0.0:8113
ffff==118470== Invalid read of size 1
==118470== at 0x4C3236C: memcpy@GLIBC_2.2.5 (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==118470== by 0x403E95: mbuf_insert (mongoose.c:1073)
==118470== by 0x40EB8D: mg_mqtt_prepend_header (mongoose.c:9824)
==118470== by 0x40ECCA: mg_mqtt_publish (mongoose.c:9843)
==118470== by 0x40F9A2: mg_mqtt_broker_handle_publish (mongoose.c:10104)
==118470== by 0x40FAF4: mg_mqtt_broker (mongoose.c:10136)
==118470== by 0x40E648: mqtt_handler (mongoose.c:9712)
==118470== by 0x4071B6: mg_call (mongoose.c:2051)
==118470== by 0x408362: mg_recv_common (mongoose.c:2505)
==118470== by 0x408393: mg_if_recv_tcp_cb (mongoose.c:2509)
==118470== by 0x40A712: mg_handle_tcp_read (mongoose.c:3376)
==118470== by 0x40AC8A: mg_mgr_handle_conn (mongoose.c:3501)
==118470== Address 0x5ce0796 is 6 bytes inside a block of size 10 free'd
==118470== at 0x4C2EDEB: free (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==118470== by 0x4C2FDB7: realloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==118470== by 0x403F74: mbuf_insert (mongoose.c:1080)
==118470== by 0x404055: mbuf_append (mongoose.c:1096)
==118470== by 0x409E83: mg_socket_if_tcp_send (mongoose.c:3167)
==118470== by 0x408158: mg_send (mongoose.c:2463)
==118470== by 0x40ECA4: mg_mqtt_publish (mongoose.c:9841)
==118470== by 0x40F9A2: mg_mqtt_broker_handle_publish (mongoose.c:10104)
==118470== by 0x40FAF4: mg_mqtt_broker (mongoose.c:10136)
==118470== by 0x40E648: mqtt_handler (mongoose.c:9712)
==118470== by 0x4071B6: mg_call (mongoose.c:2051)
==118470== by 0x408362: mg_recv_common (mongoose.c:2505)
==118470== Block was alloc'd at
==118470== at 0x4C2FD5F: realloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==118470== by 0x403F74: mbuf_insert (mongoose.c:1080)
==118470== by 0x404055: mbuf_append (mongoose.c:1096)
==118470== by 0x409E83: mg_socket_if_tcp_send (mongoose.c:3167)
==118470== by 0x408158: mg_send (mongoose.c:2463)
==118470== by 0x40EC51: mg_mqtt_publish (mongoose.c:9836)
==118470== by 0x40F9A2: mg_mqtt_broker_handle_publish (mongoose.c:10104)
==118470== by 0x40FAF4: mg_mqtt_broker (mongoose.c:10136)
==118470== by 0x40E648: mqtt_handler (mongoose.c:9712)
==118470== by 0x4071B6: mg_call (mongoose.c:2051)
==118470== by 0x408362: mg_recv_common (mongoose.c:2505)
==118470== by 0x408393: mg_if_recv_tcp_cb (mongoose.c:2509)
==118470==
==118470== Invalid write of size 1
==118470== at 0x4C32372: memcpy@GLIBC_2.2.5 (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==118470== by 0x403E95: mbuf_insert (mongoose.c:1073)
==118470== by 0x40EB8D: mg_mqtt_prepend_header (mongoose.c:9824)
==118470== by 0x40ECCA: mg_mqtt_publish (mongoose.c:9843)
==118470== by 0x40F9A2: mg_mqtt_broker_handle_publish (mongoose.c:10104)
==118470== by 0x40FAF4: mg_mqtt_broker (mongoose.c:10136)
==118470== by 0x40E648: mqtt_handler (mongoose.c:9712)
==118470== by 0x4071B6: mg_call (mongoose.c:2051)
==118470== by 0x408362: mg_recv_common (mongoose.c:2505)
==118470== by 0x408393: mg_if_recv_tcp_cb (mongoose.c:2509)
==118470== by 0x40A712: mg_handle_tcp_read (mongoose.c:3376)
==118470== by 0x40AC8A: mg_mgr_handle_conn (mongoose.c:3501)
==118470== Address 0x5ce0798 is 8 bytes inside a block of size 10 free'd
==118470== at 0x4C2EDEB: free (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==118470== by 0x4C2FDB7: realloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==118470== by 0x403F74: mbuf_insert (mongoose.c:1080)
==118470== by 0x404055: mbuf_append (mongoose.c:1096)
==118470== by 0x409E83: mg_socket_if_tcp_send (mongoose.c:3167)
==118470== by 0x408158: mg_send (mongoose.c:2463)
==118470== by 0x40ECA4: mg_mqtt_publish (mongoose.c:9841)
==118470== by 0x40F9A2: mg_mqtt_broker_handle_publish (mongoose.c:10104)
==118470== by 0x40FAF4: mg_mqtt_broker (mongoose.c:10136)
==118470== by 0x40E648: mqtt_handler (mongoose.c:9712)
==118470== by 0x4071B6: mg_call (mongoose.c:2051)
==118470== by 0x408362: mg_recv_common (mongoose.c:2505)
==118470== Block was alloc'd at
==118470== at 0x4C2FD5F: realloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==118470== by 0x403F74: mbuf_insert (mongoose.c:1080)
==118470== by 0x404055: mbuf_append (mongoose.c:1096)
==118470== by 0x409E83: mg_socket_if_tcp_send (mongoose.c:3167)
==118470== by 0x408158: mg_send (mongoose.c:2463)
==118470== by 0x40EC51: mg_mqtt_publish (mongoose.c:9836)
==118470== by 0x40F9A2: mg_mqtt_broker_handle_publish (mongoose.c:10104)
==118470== by 0x40FAF4: mg_mqtt_broker (mongoose.c:10136)
==118470== by 0x40E648: mqtt_handler (mongoose.c:9712)
==118470== by 0x4071B6: mg_call (mongoose.c:2051)
==118470== by 0x408362: mg_recv_common (mongoose.c:2505)
==118470== by 0x408393: mg_if_recv_tcp_cb (mongoose.c:2509)
==118470==
==118470== Invalid write of size 2
==118470== at 0x4C32723: memcpy@@GLIBC_2.14 (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==118470== by 0x403EBE: mbuf_insert (mongoose.c:1075)
==118470== by 0x40EB8D: mg_mqtt_prepend_header (mongoose.c:9824)
==118470== by 0x40ECCA: mg_mqtt_publish (mongoose.c:9843)
==118470== by 0x40F9A2: mg_mqtt_broker_handle_publish (mongoose.c:10104)
==118470== by 0x40FAF4: mg_mqtt_broker (mongoose.c:10136)
==118470== by 0x40E648: mqtt_handler (mongoose.c:9712)
==118470== by 0x4071B6: mg_call (mongoose.c:2051)
==118470== by 0x408362: mg_recv_common (mongoose.c:2505)
==118470== by 0x408393: mg_if_recv_tcp_cb (mongoose.c:2509)
==118470== by 0x40A712: mg_handle_tcp_read (mongoose.c:3376)
==118470== by 0x40AC8A: mg_mgr_handle_conn (mongoose.c:3501)
==118470== Address 0x5ce0790 is 0 bytes inside a block of size 10 free'd
==118470== at 0x4C2EDEB: free (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==118470== by 0x4C2FDB7: realloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==118470== by 0x403F74: mbuf_insert (mongoose.c:1080)
==118470== by 0x404055: mbuf_append (mongoose.c:1096)
==118470== by 0x409E83: mg_socket_if_tcp_send (mongoose.c:3167)
==118470== by 0x408158: mg_send (mongoose.c:2463)
==118470== by 0x40ECA4: mg_mqtt_publish (mongoose.c:9841)
==118470== by 0x40F9A2: mg_mqtt_broker_handle_publish (mongoose.c:10104)
==118470== by 0x40FAF4: mg_mqtt_broker (mongoose.c:10136)
==118470== by 0x40E648: mqtt_handler (mongoose.c:9712)
==118470== by 0x4071B6: mg_call (mongoose.c:2051)
==118470== by 0x408362: mg_recv_common (mongoose.c:2505)
==118470== Block was alloc'd at
==118470== at 0x4C2FD5F: realloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==118470== by 0x403F74: mbuf_insert (mongoose.c:1080)
==118470== by 0x404055: mbuf_append (mongoose.c:1096)
==118470== by 0x409E83: mg_socket_if_tcp_send (mongoose.c:3167)
==118470== by 0x408158: mg_send (mongoose.c:2463)
==118470== by 0x40EC51: mg_mqtt_publish (mongoose.c:9836)
==118470== by 0x40F9A2: mg_mqtt_broker_handle_publish (mongoose.c:10104)
==118470== by 0x40FAF4: mg_mqtt_broker (mongoose.c:10136)
==118470== by 0x40E648: mqtt_handler (mongoose.c:9712)
==118470== by 0x4071B6: mg_call (mongoose.c:2051)
==118470== by 0x408362: mg_recv_common (mongoose.c:2505)
==118470== by 0x408393: mg_if_recv_tcp_cb (mongoose.c:2509)
==118470==
==118470== Syscall param socketcall.sendto(msg) points to unaddressable byte(s)
==118470== at 0x54F799D: send (send.c:26)
==118470== by 0x40A40E: mg_write_to_socket (mongoose.c:3316)
==118470== by 0x40ACC2: mg_mgr_handle_conn (mongoose.c:3508)
==118470== by 0x40B6C9: mg_socket_if_poll (mongoose.c:3694)
==118470== by 0x407935: mg_mgr_poll (mongoose.c:2232)
==118470== by 0x4022A6: main (mqtt_broker.c:43)
==118470== Address 0x5ce0790 is 0 bytes inside a block of size 10 free'd
==118470== at 0x4C2EDEB: free (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==118470== by 0x4C2FDB7: realloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==118470== by 0x403F74: mbuf_insert (mongoose.c:1080)
==118470== by 0x404055: mbuf_append (mongoose.c:1096)
==118470== by 0x409E83: mg_socket_if_tcp_send (mongoose.c:3167)
==118470== by 0x408158: mg_send (mongoose.c:2463)
==118470== by 0x40ECA4: mg_mqtt_publish (mongoose.c:9841)
==118470== by 0x40F9A2: mg_mqtt_broker_handle_publish (mongoose.c:10104)
==118470== by 0x40FAF4: mg_mqtt_broker (mongoose.c:10136)
==118470== by 0x40E648: mqtt_handler (mongoose.c:9712)
==118470== by 0x4071B6: mg_call (mongoose.c:2051)
==118470== by 0x408362: mg_recv_common (mongoose.c:2505)
==118470== Block was alloc'd at
==118470== at 0x4C2FD5F: realloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==118470== by 0x403F74: mbuf_insert (mongoose.c:1080)
==118470== by 0x404055: mbuf_append (mongoose.c:1096)
==118470== by 0x409E83: mg_socket_if_tcp_send (mongoose.c:3167)
==118470== by 0x408158: mg_send (mongoose.c:2463)
==118470== by 0x40EC51: mg_mqtt_publish (mongoose.c:9836)
==118470== by 0x40F9A2: mg_mqtt_broker_handle_publish (mongoose.c:10104)
==118470== by 0x40FAF4: mg_mqtt_broker (mongoose.c:10136)
==118470== by 0x40E648: mqtt_handler (mongoose.c:9712)
==118470== by 0x4071B6: mg_call (mongoose.c:2051)
==118470== by 0x408362: mg_recv_common (mongoose.c:2505)
==118470== by 0x408393: mg_if_recv_tcp_cb (mongoose.c:2509)
2017-08-30 - Vendor Disclosure
2017-10-31 - Public Release
Discovered by Aleksandar Nikolic of Cisco Talos.