Skip to content

MQTT: Fix MQTT decoder size check after variable header replay#16916

Merged
chrisvest merged 1 commit into
netty:4.1from
daguimu:fix/mqtt-4.1-variable-header-toolong-regression
Jun 5, 2026
Merged

MQTT: Fix MQTT decoder size check after variable header replay#16916
chrisvest merged 1 commit into
netty:4.1from
daguimu:fix/mqtt-4.1-variable-header-toolong-regression

Conversation

@daguimu
Copy link
Copy Markdown
Contributor

@daguimu daguimu commented Jun 5, 2026

Motivation

#16787 fixed the READ_VARIABLE_HEADER too-long check on the 4.2 branch by replacing a ReplayingDecoderByteBuf.readableBytes() probe with the message size declared by the fixed header. That fix was auto-ported to 4.1 in #16838, but a later CVE-2026-44248 security merge re-introduced the broken code on 4.1, so the 4.1 branch still carries the regression:

int initialAvailableBytes = buffer.readableBytes();
...
if (initialAvailableBytes < maxBytesInMessage) {
    throw signal;   // REPLAY
} else {
    bailOut = true; // too long
}

Because MqttDecoder extends ReplayingDecoder, buffer is a ReplayingDecoderByteBuf whose readableBytes() returns Integer.MAX_VALUE - readerIndex rather than the bytes actually buffered. With the default maxBytesInMessage of 8092, the initialAvailableBytes < maxBytesInMessage check is therefore effectively always false. So when decodeVariableHeader asks for a REPLAY because the variable header has not fully arrived yet, the decoder takes the bailOut branch and rejects the message with a TooLongFrameException instead of waiting for the rest — a valid message whose variable header is split across reads is dropped.

Modification

Re-apply the #16787 fix on 4.1: drop the initialAvailableBytes / readableBytes() probe and decide based on bytesRemainingBeforeVariableHeader (the remaining length declared by the fixed header). A genuinely oversized message is still rejected by the existing bytesRemainingBeforeVariableHeader > maxBytesInMessage check; an incomplete one now correctly REPLAYs.

Result

A message whose variable header arrives in chunks is decoded once complete, instead of being rejected as too long, while declared-oversize messages still fail with TooLongFrameException. The two regression tests from #16787 are ported here. All codec-mqtt tests pass locally.

Note: this targets 4.1 only and intentionally carries no cherry-pick label — 4.2 already has the fix via #16787. Found while addressing @chrisvest's review comment on #16813 about the same readableBytes() antipattern.

Motivation:

netty#16787 fixed the READ_VARIABLE_HEADER too-long check on 4.2 by replacing
a ReplayingDecoderByteBuf.readableBytes() probe with the message size
declared by the fixed header. That fix was auto-ported to 4.1 (netty#16838),
but a later CVE-2026-44248 security merge re-introduced the broken code
on 4.1, so 4.1 still carries the regression:

    int initialAvailableBytes = buffer.readableBytes();
    ...
    if (initialAvailableBytes < maxBytesInMessage) {
        throw signal; // REPLAY
    } else {
        bailOut = true; // too long
    }

Because MqttDecoder extends ReplayingDecoder, buffer is a
ReplayingDecoderByteBuf whose readableBytes() returns
Integer.MAX_VALUE - readerIndex rather than the bytes actually buffered.
With the default maxBytesInMessage of 8092 the
initialAvailableBytes < maxBytesInMessage check is therefore effectively
always false, so when decodeVariableHeader asks for a REPLAY because the
variable header has not fully arrived, the decoder bails out and rejects
the message with a TooLongFrameException instead of waiting for the rest.

Modification:

Re-apply the netty#16787 fix on 4.1: drop the initialAvailableBytes /
readableBytes() probe and decide based on bytesRemainingBeforeVariableHeader,
the remaining length declared by the fixed header. A genuinely oversized
message is still rejected; an incomplete one now correctly REPLAYs.

Result:

A message whose variable header arrives in chunks is decoded once it is
complete instead of being rejected as too long, while declared-oversize
messages still fail with TooLongFrameException. Ports the two regression
tests from netty#16787. All codec-mqtt tests pass locally.
@chrisvest chrisvest merged commit eeb6378 into netty:4.1 Jun 5, 2026
17 of 18 checks passed
@chrisvest
Copy link
Copy Markdown
Member

Thanks!

@daguimu daguimu deleted the fix/mqtt-4.1-variable-header-toolong-regression branch June 7, 2026 13:15
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.

2 participants