While testing MQTT, I discovered an error in the Standard that will cause implementations to fail upon reception of PUBLISH messages in which the combination of topic length, QoS level, and payload size result in a fixed header remaining length > 2,097,151.
The culprit is an incorrect loop termination check in the algorithm for decoding the remaining length field provided in § 2.2.3. It causes decoding to fail for all encoded values greater than 2,097,151. Consequently, reception fails with valid PUBLISH messages for which 2,097,152 ≤ remaining length ≤ 268,435,455 holds. This failure is not recoverable by retransmission.
Although the algorithm is contained in a section labeled "non-normative comment", a correction to the Standard seems desirable because developers are likely to copy the algorithm verbatim.
Applicability and Impact
This error affects implementations using the algorithm in the MQTT 3.1.1 Standard. Implementations using other algorithms, including the older algorithm contained in MQTT 3.1, will not encounter this problem. However, as noted in MQTT-29 item 3, the MQTT 3.1 algorithm is susceptible to buffer overflow attacks and similar problems because it cannot detect malformed remaining length encoding.
Session partners of affected implementations can be indirectly affected because they will not receive acknowledgements from affected systems when using durable sessions and QoS > 0 because the failure is unrecoverable by retransmission.
Origin
This error does not appear in the MQTT 3.1 Standard, and seems to have been introduced in MQTT 3.1.1 WD08 or WD09, possibly in response to safety concerns raised in MQTT-29 (item 3).
Description of Error
Non-normative comments in § 2.2.3 presents the following algorithm for decoding the remaining length field. (line numbers and emphasis added) :
1 multiplier = 1
2 value = 0
3
4 do
5 encodedByte = 'next byte from stream'
6 value += (encodedByte AND 127) * multiplier
7 multiplier *= 128
8 if (multiplier > 128*128*128) << – Fails
9 throw Error(Malformed Remaining Length)
10 while ((encodedByte AND 128) != 0)
The range check (line 8) fails because 4 bytes are required to encode values greater than 2,097,151. In the fourth iteration of the do-while loop, the multiplier exceeds the limit of 128*128*128 causing the range check to fail.