Skip to content

fix(binding-mqtt, binding-mqtt-kafka): prevent engine crash on MQTT CONNECT with Will message - #2429

Open
sfr-oc wants to merge 2 commits into
aklivity:developfrom
sfr-oc:fix/mqtt-connect-will-decode-crash
Open

fix(binding-mqtt, binding-mqtt-kafka): prevent engine crash on MQTT CONNECT with Will message#2429
sfr-oc wants to merge 2 commits into
aklivity:developfrom
sfr-oc:fix/mqtt-connect-will-decode-crash

Conversation

@sfr-oc

@sfr-oc sfr-oc commented Aug 25, 2026

Copy link
Copy Markdown

Fixes #2428

Two independent bugs combined to let a single MQTT client with a Last Will and Testament crash the whole engine (and in production, segfault the JVM):

  1. binding-mqtt: onDecodeConnectWillPayload computed a payload size from session.initialBudget() - headerSize with no floor. When the session stream's granted window is smaller than the encoded will-message header alone, this goes negative and corrupts a subsequent OctetsFW.wrap() call, throwing an uncaught IndexOutOfBoundsException that terminates the engine worker. Fixed by checking session.hasSessionWindow(headerSize) first, mirroring the existing SUBSCRIBE-to-session-state-sync pattern in the same file.
  2. binding-mqtt-kafka: KafkaSessionStream.doKafkaData had no kafka != null guard, unlike every sibling method (doKafkaEnd/doKafkaAbort/doKafkaReset/doKafkaWindow). Reachable independently of bug 1 whenever a client negotiates QoS 2 with Will + Clean Start and aborts before the Kafka-side session stream is established — sendWillSignal then NPEs on the unattached stream. This also amplified bug 1's crash into a full JVM crash, since it fired again during the engine's forced teardown.

Both fixes are minimal and follow existing idiomatic patterns in their respective files. Each has a new failing-first k3po spec scenario (confirmed to reproduce the exact production stack traces pre-fix) plus full regression coverage on both modules — see the two commits for details.

Test plan

  • New k3po scenario for bug 1 (session.will.message.header.exceeds.window) fails pre-fix with the reported IndexOutOfBoundsException, passes post-fix
  • New k3po scenario for bug 2 (session.will.message.qos2.abort.before.session.established) fails pre-fix with the reported NPE, passes post-fix
  • Full regression suite green on runtime/binding-mqtt, runtime/binding-mqtt-kafka, specs/binding-mqtt.spec, specs/binding-mqtt-kafka.spec
  • checkstyle:check clean on all four modules

sfr-oc and others added 2 commits August 25, 2026 16:01
…nt session window

When a client sends CONNECT with a Will message, onDecodeConnectWillPayload
computed payloadSize as session.initialBudget() - headerSize with no floor.
The guarding MqttState.initialOpened(session.state) check only proves a
WINDOW frame arrived at least once for the session stream, not that the
granted window is large enough to hold the encoded will-message header. When
it isn't, the subtraction goes negative, producing a negative OctetsFW.wrap()
limit and an uncaught IndexOutOfBoundsException that terminates the engine
worker.

Add a session.hasSessionWindow(headerSize) check before building the header,
mirroring the same pattern already used by the SUBSCRIBE-to-session-state
sync path. When the window is too small, return progress unchanged so the
existing decodeSlot buffering parks the bytes and decodeNetwork retries
automatically on the next session WINDOW.

Fixes aklivity#2428

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…tream

KafkaSessionStream.kafka is only assigned inside a subclass's doKafkaBegin
override, so it stays null until that handshake step runs. Every other
outbound method on the class (doKafkaEnd, doKafkaAbort, doKafkaReset,
doKafkaWindow) already guards on kafka != null, but the three doKafkaData
overloads did not, so any caller reaching them first (e.g. sendWillSignal via
onMqttAbort) hit an NPE in the shared doData helper's unconditional
receiver.accept(...).

This is reachable in normal operation: a client that negotiates
publishQosMax == 2 with Will + Clean Start, then aborts before
doCreateSessionStream has run, leaves the session's kafka field unset when
onMqttAbort fires sendWillSignal.

Add the same kafka != null guard to all three doKafkaData overloads, at the
single shared point that covers sendWillSignal and every other caller.

Fixes aklivity#2428

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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.

MQTT CONNECT with a Will message crashes the whole engine (JVM segfault), not just the connection

1 participant