Skip to content

fix(mqtt-proxy): avoid crash on malformed v5 properties length - #13953

Open
bhuvan-somisetty wants to merge 2 commits into
apache:masterfrom
bhuvan-somisetty:fix/mqtt-proxy-v5-properties-oob
Open

bhuvan-somisetty wants to merge 2 commits into
apache:masterfrom
bhuvan-somisetty:fix/mqtt-proxy-v5-properties-oob

Conversation

@bhuvan-somisetty

Copy link
Copy Markdown

Description

decode_variable_byte_int() in the mqtt-proxy stream plugin reads up to 4 bytes to decode an MQTT variable-byte-integer, but it never checked that the index it was reading stayed inside the buffer it was given. It's used to decode the MQTT v5 "Properties Length" field, and unlike the client-ID length handling a few lines below it (which correctly checks parsed_pos + client_id_len > #data before reading), there was no equivalent guard here.

If the properties-length bytes have the continuation bit (0x80) set all the way to the end of the peeked buffer, string.byte() returns nothing for the out-of-range index, byte ends up nil, and bit.band(nil, 127) raises an uncaught Lua error. That happens inside the preread phase, so instead of getting the plugin's normal core.log.error(...); return 503 handling that every other malformed-packet case in this file gets, the connection just errors out.

This PR adds the missing bounds check in decode_variable_byte_int, and propagates the failure up through parse_mqtt/parse_msg_hdr so preread() now rejects a malformed/truncated properties length with a clean 503, same as it already does for a bad packet type or a truncated client ID.

Which issue(s) this PR fixes:

Fixes #13952

Checklist

  • I have explained the need for this PR and the problem it solves
  • I have explained the changes or the new features added to this PR
  • I have added tests corresponding to this change
  • I have updated the documentation to reflect this change
  • I have verified that this change is backward compatible

decode_variable_byte_int() read up to 4 bytes without checking they
were still inside the peeked buffer. A CONNECT packet whose MQTT v5
properties length field has its continuation bit set right up to the
end of the buffer made string.byte() return nothing for the
out-of-range index, and bit.band(nil, 127) then raised an unhandled
Lua error instead of the usual 503 rejection.

Bail out with an error once the loop runs past the data we have, and
propagate that failure from parse_mqtt/parse_msg_hdr so preread()
rejects the connection the same way it already does for every other
malformed packet.

Fixes apache#13952
@janiussyafiq

Copy link
Copy Markdown
Contributor

A properties length that's a valid number (continuation bit clear) but larger than the packet still crashes, because parsed_pos skips past the buffer and the later client_id_len read does str_byte(data, ...) * 256 on a nil.
Repro packet:

10 0c 00 04 4d 51 54 54 05 02 00 3c 7f 00

7f = properties length 127, so parsing jumps well past the 14-byte buffer. On this branch it gives the same uncaught error / connection reset instead of a 503:

mqtt-proxy.lua:119: attempt to perform arithmetic on a nil value
lua entry thread aborted: runtime error ...

The root cause is that parse_mqtt does unchecked str_byte(...) * 256 reads for both protocol_len and client_id_len. Worth guarding those two reads (or bounds-checking before each fixed read) so every malformed v5 CONNECT returns 503, not just the continuation-bit case.

If you could alongside address this sibling issue that'd be great, thanks!

A valid (continuation-bit-clear) v5 properties length that is still
larger than the packet advances parsed_pos past the buffer, so the
following client_id_len read does str_byte(...) * 256 on nil and
crashes. Bounds-check the properties skip and the protocol name /
client id length reads the same way the remaining-length parsing
already does, so every malformed CONNECT returns 503 instead of
aborting the request.
@bhuvan-somisetty

bhuvan-somisetty commented Sep 17, 2026

Copy link
Copy Markdown
Author

Good catch, thanks. Pushed a fix, bounds-checked the protocol name length and client id length reads (both did unchecked str_byte(...) * 256), and added a check after the properties skip so a valid-but-oversized properties length can't push parsed_pos past the buffer either. Added a test with your exact repro packet, now returns 503 as expected.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: mqtt-proxy stream plugin crashes on malformed MQTT v5 properties length

2 participants