curl · Report #3702718
MQTT state machine confusion: PINGRESP/DISCONNECT with non-zero remaining_length dispatches to stale nextstate
How it was found
Summary:
In lib/mqtt.c, the state machine in mqtt_doing (lines 894-911 in curl 8.20.0) does not validate that PINGRESP (0xD0) and DISCONNECT (0xE0) packets have remaining_length == 0 as required by MQTT 3.1.1 spec sections 3.13.1 and 3.14.1. A malicious broker can send a PINGRESP fixed header with non-zero remaining_length followed by trailing bytes, and the state machine will dispatch to whatever mqtt->nextstate was set to from a previous cycle (typically MQTT_PUBWAIT after a SUBSCRIBE) and parse the trailing bytes as that state's payload. Depending on nextstate, this allows a malicious broker to inject PUBLISH payload data, fake a SUBACK/CONNACK, or otherwise corrupt the client's protocol state. A patch is available at https://github.com/GovClawAPI/curl/commit/7ad90263bc which validates remaining_length == 0 for both packet types before state dispatch and returns CURLE_WEIRD_SERVER_REPLY on violation.
Affected version
curl 8.20.0 (the current stable release at time of audit). The affected code in lib/mqtt.c:894-911 appears unchanged in current main. Built and tested on Linux x86_64.
Steps To Reproduce:
- Run a malicious MQTT broker that completes the standard CONNECT/CONNACK and SUBSCRIBE/SUBACK handshake with a curl client.
- After the SUBACK, instead of a normal PUBLISH or PINGRESP, send the bytes
0xD0 0x02 0x00 0x00(PINGRESP fixed header claiming remaining_length=2, plus two payload bytes). - Observe that curl does not reject the malformed PINGRESP. The state machine consumes the two trailing bytes as the start of the next expected message (typically the variable-header of a PUBLISH), corrupting subsequent message parsing.
- The same applies to DISCONNECT (0xE0) packets with non-zero remaining_length.
Again: there is no bug-bounty for curl. We do not offer any rewards, only proper credits.
Impact
Summary:
A malicious or compromised MQTT broker can cause protocol-level state confusion in curl's MQTT client. The trailing bytes from a malformed PINGRESP or DISCONNECT are interpreted as the payload of whatever state was queued in mqtt->nextstate. Concrete consequences depending on the queued state: trailing bytes parsed as PUBLISH payload allow the broker to inject arbitrary content into the client's receive callback; trailing bytes parsed as SUBACK can cause the application to believe a subscription was confirmed when it was not; trailing bytes parsed as CONNACK can cause incorrect connection-state assumptions. The exposure is to MQTT clients connecting to untrusted or compromised brokers, which is a realistic threat model for MQTT given its common deployment across IoT and edge environments.