MQTT: Reject malformed no-payload packets with non-zero Remaining Length#16852
Merged
Merged
Conversation
Motivation: MqttDecoder.decodePayload's default branch returns null for payload-less message types (PINGREQ, PINGRESP, DISCONNECT, AUTH, CONNACK, PUBACK, PUBREC, PUBREL, PUBCOMP) without checking that bytesRemainingInVariablePart is 0. A fixed header that claims a non-zero Remaining Length for one of these types is silently accepted, and the leftover bytes are interpreted as another packet. The reporter's exact repro from netty#16851 is C0 02 D0 00: a PINGREQ with Remaining Length 2 (invalid per MQTT 3.1.1 and MQTT 5.0, which both require Remaining Length 0 for PINGREQ) is currently decoded as a valid PINGREQ followed by a valid PINGRESP. Modification: Call validateNoBytesRemain(0) at the start of the default branch. The helper, already used by the SUBSCRIBE and UNSUBSCRIBE payload decoders, subtracts 0 from bytesRemainingInVariablePart and throws DecoderException when the result is non-zero, surfacing a single invalidMessage instead of letting leftover bytes leak into the next decode pass. Added MqttCodecTest#testPingReqWithNonZeroRemainingLengthIsRejected that feeds the reporter's exact byte sequence and verifies one failed message is produced. Confirmed it fails on the unfixed code (expected <true> but was <false>) and passes with the fix; all 112 codec-mqtt tests pass. Result: Malformed payload-less MQTT frames are flagged as a single invalid message rather than allowed to silently spill into the next packet. The fix applies uniformly to every message type that goes through the default branch. Fixes netty#16851.
normanmaurer
requested changes
May 27, 2026
Co-authored-by: Norman Maurer <norman_maurer@apple.com>
normanmaurer
approved these changes
Jun 1, 2026
chrisvest
reviewed
Jun 1, 2026
chrisvest
left a comment
Member
There was a problem hiding this comment.
One nit but otherwise looks good
chrisvest
approved these changes
Jun 1, 2026
Contributor
|
Auto-port PR for 5.0: #16889 |
Contributor
|
Auto-port PR for 4.2: #16890 |
chrisvest
added a commit
that referenced
this pull request
Jun 2, 2026
…o Remaining Length (#16890) Auto-port of #16852 to 4.2 Cherry-picked commit: d89cf88 --- Motivation: `MqttDecoder.decodePayload`'s `default` branch returns `null` for payload-less message types (`PINGREQ`, `PINGRESP`, `DISCONNECT`, `AUTH`, `CONNACK`, `PUBACK`, `PUBREC`, `PUBREL`, `PUBCOMP`) without checking that `bytesRemainingInVariablePart` is `0`. A fixed header that claims a non-zero Remaining Length for one of these types is silently accepted, and the leftover bytes are interpreted as another packet. The reporter's exact repro from #16851 is `C0 02 D0 00`: a `PINGREQ` with Remaining Length `2` (invalid per [MQTT 3.1.1](https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html) and [MQTT 5.0](https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html), which both require Remaining Length `0` for `PINGREQ`) is currently decoded as a valid `PINGREQ` followed by a valid `PINGRESP`. Modification: Call `validateNoBytesRemain(0)` at the start of the `default` branch. The helper — already used by the `SUBSCRIBE` and `UNSUBSCRIBE` payload decoders — subtracts `0` from `bytesRemainingInVariablePart` and throws `DecoderException` when the result is non-zero, surfacing a single invalid message instead of letting leftover bytes leak into the next decode pass. Added `MqttCodecTest#testPingReqWithNonZeroRemainingLengthIsRejected` that feeds the reporter's exact byte sequence and verifies a single failed message is produced. Confirmed it fails on the unfixed code (`expected: <true> but was: <false>`) and passes with the fix; all 112 `codec-mqtt` tests pass locally. Result: Malformed payload-less MQTT frames are flagged as a single invalid message rather than allowed to silently spill into the next packet. The fix applies uniformly to every message type that goes through the `default` branch. Fixes #16851. Co-authored-by: Guimu <30684111+daguimu@users.noreply.github.com> Co-authored-by: Norman Maurer <norman_maurer@apple.com> Co-authored-by: Chris Vest <christianvest_hansen@apple.com>
chrisvest
added a commit
that referenced
this pull request
Jun 4, 2026
…o Remaining Length (#16889) Auto-port of #16852 to 5.0 Cherry-picked commit: d89cf88 --- Motivation: `MqttDecoder.decodePayload`'s `default` branch returns `null` for payload-less message types (`PINGREQ`, `PINGRESP`, `DISCONNECT`, `AUTH`, `CONNACK`, `PUBACK`, `PUBREC`, `PUBREL`, `PUBCOMP`) without checking that `bytesRemainingInVariablePart` is `0`. A fixed header that claims a non-zero Remaining Length for one of these types is silently accepted, and the leftover bytes are interpreted as another packet. The reporter's exact repro from #16851 is `C0 02 D0 00`: a `PINGREQ` with Remaining Length `2` (invalid per [MQTT 3.1.1](https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html) and [MQTT 5.0](https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html), which both require Remaining Length `0` for `PINGREQ`) is currently decoded as a valid `PINGREQ` followed by a valid `PINGRESP`. Modification: Call `validateNoBytesRemain(0)` at the start of the `default` branch. The helper — already used by the `SUBSCRIBE` and `UNSUBSCRIBE` payload decoders — subtracts `0` from `bytesRemainingInVariablePart` and throws `DecoderException` when the result is non-zero, surfacing a single invalid message instead of letting leftover bytes leak into the next decode pass. Added `MqttCodecTest#testPingReqWithNonZeroRemainingLengthIsRejected` that feeds the reporter's exact byte sequence and verifies a single failed message is produced. Confirmed it fails on the unfixed code (`expected: <true> but was: <false>`) and passes with the fix; all 112 `codec-mqtt` tests pass locally. Result: Malformed payload-less MQTT frames are flagged as a single invalid message rather than allowed to silently spill into the next packet. The fix applies uniformly to every message type that goes through the `default` branch. Fixes #16851. --------- Co-authored-by: Guimu <30684111+daguimu@users.noreply.github.com> Co-authored-by: Norman Maurer <norman_maurer@apple.com> Co-authored-by: Chris Vest <christianvest_hansen@apple.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation:
MqttDecoder.decodePayload'sdefaultbranch returnsnullfor payload-less message types (PINGREQ,PINGRESP,DISCONNECT,AUTH,CONNACK,PUBACK,PUBREC,PUBREL,PUBCOMP) without checking thatbytesRemainingInVariablePartis0. A fixed header that claims a non-zero Remaining Length for one of these types is silently accepted, and the leftover bytes are interpreted as another packet.The reporter's exact repro from #16851 is
C0 02 D0 00: aPINGREQwith Remaining Length2(invalid per MQTT 3.1.1 and MQTT 5.0, which both require Remaining Length0forPINGREQ) is currently decoded as a validPINGREQfollowed by a validPINGRESP.Modification:
Call
validateNoBytesRemain(0)at the start of thedefaultbranch. The helper — already used by theSUBSCRIBEandUNSUBSCRIBEpayload decoders — subtracts0frombytesRemainingInVariablePartand throwsDecoderExceptionwhen the result is non-zero, surfacing a single invalid message instead of letting leftover bytes leak into the next decode pass.Added
MqttCodecTest#testPingReqWithNonZeroRemainingLengthIsRejectedthat feeds the reporter's exact byte sequence and verifies a single failed message is produced. Confirmed it fails on the unfixed code (expected: <true> but was: <false>) and passes with the fix; all 112codec-mqtttests pass locally.Result:
Malformed payload-less MQTT frames are flagged as a single invalid message rather than allowed to silently spill into the next packet. The fix applies uniformly to every message type that goes through the
defaultbranch.Fixes #16851.