← ALL ADVISORIES

CRITICAL BYTERAY-2026-0211Disclosed

lwIP — Heap Out-of-Bounds Write in MQTT Client Fixed-Header Parsing

The lwIP MQTT client does not enforce the MQTT limit on fixed-header length, so a malicious or man-in-the-middle broker can send a run of remaining-length continuation bytes and drive an unbounded write past a 128-byte heap buffer in the client. No MQTT authentication is required, and the primitive is a plausible path to remote code execution.

Advisory
BYTERAY-2026-0211
CVE
CVE-2026-87121
CWE
CWE-787
CVSS
9.8 (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H); 9.3 (CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N)
Vendor
lwIP
Product
MQTT Client Application
Reported
2026-07-26
Disclosure
2026-09-22
Affected
lwIP MQTT Client Application, versions 2.0.1 through 2.2.1 (vulnerable code introduced in commit 1e8246576638dc02eeea287c1c46511b3e43ce49, 2016-12-20)
Fixed in
Upstream commit f89407ea711879c04d91c92b35d67be78bbaf0f1

Executive Summary

The MQTT client bundled with lwIP parses the packet fixed header without ever checking how long that header is allowed to be. MQTT caps the fixed header at a few bytes, but the client keeps consuming and storing "remaining length" continuation bytes for as long as the broker keeps sending them. A hostile broker can therefore write past the end of a 128-byte buffer inside the client.

The overflow lands on the fields that immediately follow that buffer, which control the client's output ring. That turns the client's next transmit into a write at an attacker-influenced offset well outside the object. The parser runs on the very first bytes the broker sends after the TCP connection is established, so no MQTT-level login is required. The minimum outcome is a remote crash; because lwIP typically runs on MCU or RTOS targets with a flat address space, no ASLR, and no heap hardening, remote code execution is a realistic outcome, and the reporter developed a working end-to-end exploit.

Affected Products and Versions

  • Product: lwIP (lightweight TCP/IP stack), MQTT application client (src/apps/mqtt/mqtt.c, function mqtt_parse_incoming()).
  • Affected versions: 2.0.1 through 2.2.1.
  • The vulnerable code was introduced in commit 1e8246576638dc02eeea287c1c46511b3e43ce49 (2016-12-20) and every maintained release derived from master after that date carries it.
  • lwIP is vendored into many embedded SDKs, so downstream forks that bundle the MQTT client are expected to be affected as well.

Technical Details

MQTT encodes "remaining length" as a base-128 varint of at most 4 bytes; each byte with bit 0x80 set means another byte follows (MQTT 3.1.1 section 2.2.3). A complete fixed header is therefore never longer than 5 bytes.

mqtt_parse_incoming() never enforces that cap. The fixed-header branch is entered whenever (fixed_hdr_len < 2) || ((b & 0x80) != 0), and it stores each consumed byte with no bound check:

/* src/apps/mqtt/mqtt.c, mqtt_parse_incoming() */
if ((fixed_hdr_len < 2) || ((b & 0x80) != 0)) {
  if (fixed_hdr_len < client->msg_idx) {
    b = client->rx_buffer[fixed_hdr_len];
  } else {
    b = pbuf_get_at(p, in_offset++);
    client->rx_buffer[client->msg_idx++] = b;   /* no bound check */
  }
  fixed_hdr_len++;
  ...
} else {
  /* variable-header branch */
  ...
  if (client->msg_idx >= MQTT_VAR_HEADER_BUFFER_LEN) {   /* the guard that is
  ...                                                        absent above */

A broker that sends only bytes with 0x80 set keeps the loop in the fixed-header branch indefinitely, so client->msg_idx climbs without limit and rx_buffer (MQTT_VAR_HEADER_BUFFER_LEN = 128 bytes) is written past its end. The bytes immediately after rx_buffer are the output ring-buffer control fields, so the overflow corrupts them and the client's next transmit writes at an attacker-influenced offset roughly 32 KB outside the object.

The same missing bound makes two further defects reachable, and both are closed by capping the fixed-header length:

  1. Out-of-bounds read: b = client->rx_buffer[fixed_hdr_len]; indexes with a u8_t that can reach 255 while rx_buffer is 128 bytes.
  2. Unsigned underflow: in the variable-header branch, buffer_space = MQTT_VAR_HEADER_BUFFER_LEN - fixed_hdr_len; underflows the u16_t when fixed_hdr_len > 128 (reachable by sending 130+ continuation bytes and then one byte with the bit clear), producing a bogus copy length for pbuf_get_contiguous().

The malicious input needed to trigger the primary write is a single segment of roughly 130 bytes, all 0x80.

Reachability and Preconditions

  • The device connects to a broker the attacker controls, has compromised, or can intercept (plaintext MQTT, or TLS without certificate validation, both common in embedded deployments).
  • No MQTT-level authentication is required. mqtt_parse_incoming() processes the broker's first bytes and remains reachable for the life of the connection.
  • No user interaction is required.

Impact

Remote heap corruption in the MQTT client. The confirmed minimum outcome is denial of service through a crash on the device. Because the write offset is attacker-influenced and lwIP targets are typically MCU/RTOS environments with a flat address space, no ASLR, and no heap hardening, the primitive is a plausible path to remote code execution; the reporter has a working exploit that achieves remote code execution end to end over a TCP connection.

CISA rates the issue CVSS v3.1 9.8 (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H) and CVSS v4.0 9.3 (CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N).

Remediation and Mitigation

Update lwIP to a build that contains upstream commit f89407ea711879c04d91c92b35d67be78bbaf0f1, which CISA identifies as the commit carrying the fix. The fix enforces the protocol's fixed-header length limit so the parser stops and disconnects once a fixed header exceeds the legal maximum, which also closes the out-of-bounds read and the buffer_space underflow above. The lwIP source repository is at savannah.nongnu.org/projects/lwip.

Where updating is not immediately possible, reduce exposure by connecting only to trusted brokers over TLS with full server-certificate validation enabled. This limits, but does not remove, the attack surface: a compromised or malicious trusted broker can still reach the vulnerable parser.

Timeline

  • 2016-12-20 — Vulnerable code introduced upstream (commit 1e8246576638dc02eeea287c1c46511b3e43ce49).
  • 2026-09-22 — Public disclosure via CISA advisory ICSA-26-265-01; CVE-2026-87121 assigned.

Credits

Discovered and reported by Shahriyar Jalayeri and Mehrun P. Hunter of ByteRay Ltd. Coordination and publication handled by CISA.

References