Talos Vulnerability Report

TALOS-2021-1406

Eclipse Foundation Paho MQTTClient-C library readPacket out-of-bounds write vulnerability

February 1, 2022
CVE Number

CVE-2021-41036

Summary

An out-of-bounds write vulnerability exists in the readPacket functionality of Eclipse Foundation Embedded Paho MQTTClient-C library v1.0.0. A specially-crafted MQTT payload can lead to an out-of-bounds write. An attacker can send a malicious MQTT message to trigger this vulnerability.

Tested Versions

Eclipse Foundation Embedded Paho MQTTClient-C library v1.0.0
Sealevel Systems, Inc. SeaConnect 370W v1.3.34

Product URLs

Embedded Paho MQTTClient-C library - https://www.eclipse.org/paho/index.php?page=clients/c/embedded/index.php SeaConnect 370W - https://www.sealevel.com/product/370w-a-wifi-to-form-c-relays-digital-inputs-a-d-inputs-and-1-wire-bus-seaconnect-multifunction-io-edge-module-powered-by-seacloud/

CVSSv3 Score

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

CWE

CWE-120 - Buffer Copy without Checking Size of Input (‘Classic Buffer Overflow’)

Details

The Embedded Paho MQTTClient-C library is a MQTT client library for the language C. This library is primarily designed for embedded devices.

This vulnerability was discovered while looking at the SeaConnect 370W. The mentioned device uses the Paho MQTTClient-C library version 1.0.0. In this version is present a buffer overflow vulnerability in the function readPacket. This was corrected in July 2017. This correction was treated as a security improvement, and no CVE was assigned to it. The consequence was that the SeaConnect 370W did not update its firmware with the correction. This, in turn, leads to a remote command execution vulnerability in the SeaConnect 370W due to the buffer overflow caused in the Paho MQTTClient-C library version 1.0.0.

The readPacket function in the SeaConnect 370W is:

  uint readPacket(MQTTClient *c,undefined4 param_2)

  {
    uint rc;
    undefined4 timer_;
    int iVar1;
    dword dVar2;
    undefined *ipstack;
    dword rem_len;

    read_volatile(PTR_DWORD_20012de8._0_1_);
    ipstack = c->ipstack;
    rem_len = 0;
    timer_ = TimerLeftMS(param_2);
    rc = (**(code **)(ipstack + 0x30))(ipstack,c->readbuf,1,timer_);                                    [1]
                      /* calling mqttread ^ */
    if (rc == 1) {
      timer_ = TimerLeftMS(param_2);
      decodePacket(c,&rem_len,timer_);                                                                  [2]
      iVar1 = MQTTPacket_encode(c->readbuf + 1,rem_len);
      if (0 < (int)rem_len) {
        ipstack = c->ipstack;
        timer_ = TimerLeftMS(param_2);
        dVar2 = (**(code **)(ipstack + 0x30))(ipstack,c->readbuf + iVar1 + 1,rem_len,timer_);           [3]
                      /* calling mqttread ^ */
        if (rem_len != dVar2) {
          return 1;
        }
      }
      rc = ((uint)(byte)*c->readbuf << 0x18) >> 0x1c;
    }
    return rc;
  }

This is a Paho MQTTClient-C’s function that handles a new MQTT mesage. It reads at [1] the heder byte and at [2] reads, from the packet, the remaining size of the packet. This size is then used at [3] to read into a heap buffer the remaining message.

The buffer used at [3], for the SeaConnect 370W, is allocated in SeaConnectMqtt_Init:

  void SeaConnectMqtt_Init(void)

  {
    [...]
    
    receiveBuffer_ptr = (int *)read_volatile_4(receiveBuffer);
    puVar1 = read_volatile_4(PTR_aSeaconnectmqtt_3_20010be8);
    if (*receiveBuffer_ptr == 0) {
      malloc_res = malloc(0x201);                                                                       [4]
      *receiveBuffer_ptr = malloc_res;
    [...]
    
    }

At [4] 0x201 bytes are allocated to manage a MQTT mesage, but the function readPacket does not control the size of the variable rem_len before reading the message content at [3]. This can lead to a buffer overflow.

The corrected Paho MQTTClient-C’s readPacket:

static int readPacket(MQTTClient* c, Timer* timer)
{
    MQTTHeader header = {0};
    int len = 0;
    int rem_len = 0;

    /* 1. read the header byte.  This has the packet type in it */
    int rc = c->ipstack->mqttread(c->ipstack, c->readbuf, 1, TimerLeftMS(timer));
    if (rc != 1)
        goto exit;

    len = 1;
    /* 2. read the remaining length.  This is variable in itself */
    decodePacket(c, &rem_len, TimerLeftMS(timer));
    len += MQTTPacket_encode(c->readbuf + 1, rem_len); /* put the original remaining length back into the buffer */

    if (rem_len > (c->readbuf_size - len))                                                              [5]
    {
        rc = BUFFER_OVERFLOW;
        goto exit;
    }

    /* 3. read the rest of the buffer using a callback to supply the rest of the data */
    if (rem_len > 0 && (rc = c->ipstack->mqttread(c->ipstack, c->readbuf + len, rem_len, TimerLeftMS(timer)) != rem_len)) {
        rc = 0;
        goto exit;
    }

    header.byte = c->readbuf[0];
    rc = header.bits.type;
exit:
    return rc;
}

A size check was introduced at [5] to avoid the buffer overflow.

The nature of the buffer overflow depends upon the library user. Indeed, the buffer used in readPacket is provided by the library utilizer and not allocated by the Paho MQTTClient-C library.

Timeline

2021-10-27 - Initial vendor contact
2021-11-03 - Vendor disclosure
2022-02-01 - Public Release

Credit

Discovered by Francesco Benvenuto and Matt Wiseman of Cisco Talos.