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, functionmqtt_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:
- Out-of-bounds read:
b = client->rx_buffer[fixed_hdr_len];indexes with au8_tthat can reach 255 whilerx_bufferis 128 bytes. - Unsigned underflow: in the variable-header branch,
buffer_space = MQTT_VAR_HEADER_BUFFER_LEN - fixed_hdr_len;underflows theu16_twhenfixed_hdr_len > 128(reachable by sending 130+ continuation bytes and then one byte with the bit clear), producing a bogus copy length forpbuf_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.