Skip to content

validate reserved flags in the broker QUIC acceptor and wasm broker decode paths #167

Description

@fabracht

mqtt5-protocol 0.15.2 made Packet::decode_from_body_with_version check fixed-header reserved flags for every packet type (MQTT-2.1.3-1). Two decode paths still use the unchecked decode_from_body, so they accept packets with invalid reserved flags:

  • the broker QUIC acceptor (crates/mqtt5/src/broker/quic_acceptor.rs, around lines 502 and 756)
  • the wasm broker's decoder path (crates/mqtt5-wasm)

The TCP, TLS and WebSocket broker paths reject these packets as malformed, so behaviour depends on the transport.

Fix: route both paths through the version-aware checked decode. Add a test per transport sending, for example, PINGREQ with flags 0x1, expecting the connection to close as malformed.

Found in the quorum review of #164.

Activity

  1. fabracht commented on Oct 2, 2026

    @fabracht
    ContributorAuthor

    Closing: the QUIC acceptor and the wasm broker decoder already reject packets with invalid reserved flags. Packet::decode_from_body has called validate_flags() before decoding since a4dd456 (February), so it is not an unchecked path. A runtime check against the broker confirms it: PINGREQ with flags 0x1 and SUBSCRIBE with flags 0x0 sent over QUIC are rejected as malformed and the connection is closed, the same as over TCP.

    Two related problems turned up while checking, and they'll be tracked separately: a decode error on a QUIC data stream or datagram does not close the connection, and the wasm broker and the QUIC data-stream/datagram paths always decode as MQTT v5 regardless of the negotiated protocol version.

  2. fabracht commented on Oct 2, 2026

    @fabracht
    ContributorAuthor

    Correction to the above: over QUIC the broker rejects the malformed packet, ends the MQTT session and closes the control stream, but it does not close the QUIC connection itself, which stays open until the client drops it or the idle timeout expires. Over TCP the socket is closed. The reserved-flag check itself works on every transport, so the conclusion stands.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions