Skip to content

Commit dec01d1

Browse files
committed
Fix Unsupported Protocol Level Is Not Rejected with CONNACK 0x01
1 parent f4f1bca commit dec01d1

3 files changed

Lines changed: 119 additions & 2 deletions

File tree

src/mqtt_broker.c

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2722,6 +2722,31 @@ static int BrokerHandle_Connect(BrokerClient* bc, int rx_len,
27222722
ack.protocol_level = mc.protocol_level;
27232723
#endif
27242724

2725+
/* [MQTT-3.1.2-2] Reject unsupported Protocol Level. Per spec, the server
2726+
* MUST respond with CONNACK 0x01 (unacceptable protocol level) and then
2727+
* disconnect. v3.1.1 (level 4) is always supported; v5 (level 5) only
2728+
* when WOLFMQTT_V5 is compiled in. Other values reach this branch and
2729+
* are refused before any session/will/auth processing. */
2730+
if (mc.protocol_level != MQTT_CONNECT_PROTOCOL_LEVEL_4
2731+
#ifdef WOLFMQTT_V5
2732+
&& mc.protocol_level != MQTT_CONNECT_PROTOCOL_LEVEL_5
2733+
#endif
2734+
) {
2735+
WBLOG_ERR(broker,
2736+
"broker: unsupported protocol level %u sock=%d [MQTT-3.1.2-2]",
2737+
(unsigned)mc.protocol_level, (int)bc->sock);
2738+
ack.return_code = MQTT_CONNECT_ACK_CODE_REFUSED_PROTO;
2739+
#ifdef WOLFMQTT_V5
2740+
/* The client claimed an unknown protocol; we don't know what wire
2741+
* format they expect for the CONNACK. Fall back to the v3.1.1
2742+
* shape (no properties), which is what [MQTT-3.1.2-2] specifies
2743+
* verbatim and is the simplest format any reasonable client can
2744+
* parse. */
2745+
ack.protocol_level = MQTT_CONNECT_PROTOCOL_LEVEL_4;
2746+
#endif
2747+
goto send_connack;
2748+
}
2749+
27252750
/* Store client ID */
27262751
#ifdef WOLFMQTT_STATIC_MEMORY
27272752
bc->client_id[0] = '\0';

src/mqtt_packet.c

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1172,7 +1172,13 @@ int MqttDecode_Connect(byte *rx_buf, int rx_buf_len, MqttConnect *mc_connect)
11721172

11731173
#ifdef WOLFMQTT_V5
11741174
mc_connect->props = NULL;
1175-
if (mc_connect->protocol_level >= MQTT_CONNECT_PROTOCOL_LEVEL_5) {
1175+
/* Only decode v5 properties when the level is exactly 5. Treating any
1176+
* level >= 5 as v5 incorrectly consumes a properties-length byte for
1177+
* unsupported levels (e.g., 6) — the broker's [MQTT-3.1.2-2] rejection
1178+
* runs after this function, so we must let the wire decode under the
1179+
* level the spec actually defines for it (here: nothing, fall through
1180+
* to the v3.1.1-shape payload). */
1181+
if (mc_connect->protocol_level == MQTT_CONNECT_PROTOCOL_LEVEL_5) {
11761182
/* Decode Length of Properties */
11771183
if (rx_buf_len < (rx_payload - rx_buf)) {
11781184
return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_OUT_OF_BUFFER);
@@ -1219,7 +1225,8 @@ int MqttDecode_Connect(byte *rx_buf, int rx_buf_len, MqttConnect *mc_connect)
12191225

12201226
#ifdef WOLFMQTT_V5
12211227
mc_connect->lwt_msg->props = NULL;
1222-
if (mc_connect->protocol_level >= MQTT_CONNECT_PROTOCOL_LEVEL_5) {
1228+
/* See note above: only level 5 carries v5 LWT properties on the wire. */
1229+
if (mc_connect->protocol_level == MQTT_CONNECT_PROTOCOL_LEVEL_5) {
12231230
word32 lwt_props_len = 0;
12241231
int lwt_tmp;
12251232
/* Decode Length of LWT Properties */

tests/test_broker_connect.c

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,88 @@ TEST(connect_v311_explicit_auto_prefix_refused)
360360
MqttBroker_Free(&broker);
361361
}
362362

363+
/* [MQTT-3.1.2-2] Unsupported Protocol Level must be refused with CONNACK
364+
* 0x01 (REFUSED_PROTO) followed by disconnect. The CONNACK MUST come back
365+
* in v3.1.1 wire shape (4 bytes: type, remain=2, flags, code) regardless
366+
* of the level the client claimed — we don't know what their wire format
367+
* actually is, and the spec text specifies "CONNACK return code 0x01"
368+
* verbatim.
369+
*
370+
* Cases below mirror the dynamic test evidence in the issue report:
371+
* level 0x03 -> refused
372+
* level 0x06 -> refused (and not silently accepted as v5 just because
373+
* the encoder uses level >= 5 as a mode switch).
374+
*/
375+
static void run_unsupported_level(byte level)
376+
{
377+
MqttBroker broker;
378+
MqttBrokerNet net;
379+
/* Wire matches the reporter's case for level X with ClientId "A":
380+
* 10 0d 00 04 4d 51 54 54 LL 02 00 3c 00 01 41
381+
* (15 bytes total: fixed header 2 + remain 13 = type+nameLen+name+
382+
* level+flags+keepalive+idLen+id) */
383+
byte connect[] = {
384+
0x10, 13,
385+
0x00, 0x04, 'M', 'Q', 'T', 'T',
386+
0x00, /* protocol_level placeholder */
387+
0x02, /* CleanSession=1 */
388+
0x00, 0x3C,
389+
0x00, 0x01, 'A'
390+
};
391+
connect[8] = level;
392+
393+
install_mock_net(&net);
394+
XMEMSET(&broker, 0, sizeof(broker));
395+
ASSERT_EQ(MQTT_CODE_SUCCESS, MqttBroker_Init(&broker, &net));
396+
ASSERT_EQ(MQTT_CODE_SUCCESS, MqttBroker_Start(&broker));
397+
398+
reset_mock_state(connect, sizeof(connect));
399+
run_broker_one_connect(&broker);
400+
401+
/* Expect a v3.1.1-shaped CONNACK: 0x20, 0x02, flags, REFUSED_PROTO.
402+
* [MQTT-3.1.2-2] mandates 0x01 for any unsupported level regardless of
403+
* what the client claimed they spoke. */
404+
ASSERT_TRUE(g_out_len >= 4);
405+
ASSERT_EQ(0x20, g_out_buf[0]);
406+
ASSERT_EQ(0x02, g_out_buf[1]);
407+
ASSERT_EQ(0x00, g_out_buf[2]);
408+
ASSERT_EQ(MQTT_CONNECT_ACK_CODE_REFUSED_PROTO, g_out_buf[3]);
409+
ASSERT_TRUE(g_client_closed);
410+
411+
MqttBroker_Stop(&broker);
412+
MqttBroker_Free(&broker);
413+
}
414+
415+
TEST(connect_unsupported_level_3_refused)
416+
{
417+
run_unsupported_level(0x03);
418+
}
419+
420+
TEST(connect_unsupported_level_6_refused)
421+
{
422+
/* Catches the secondary issue the reporter flagged in section 3 of #496:
423+
* pre-fix MqttDecode_Connect treated `protocol_level >= 5` as v5, so for
424+
* level 6 the decoder consumed the byte at the v5 props_len position
425+
* (0x00 here, parsed as zero-length props), then read the next two bytes
426+
* (0x00 0x01) as the ClientId length prefix and 'A' as the start of a
427+
* 1-byte ClientId. With WOLFMQTT_V5 the decoder still returned success
428+
* for this particular wire, but it produced a misaligned MqttConnect with
429+
* ClientId="" — so the test below also fails on the post-decode path
430+
* unless the broker's [MQTT-3.1.2-2] check rejects the level. (For wires
431+
* without enough trailing bytes, the pre-fix decoder instead returned
432+
* OUT_OF_BUFFER and never emitted a CONNACK at all, which would also
433+
* fail the `g_out_len >= 4` assertion.) Either way, this test pins the
434+
* fix at both layers. */
435+
run_unsupported_level(0x06);
436+
}
437+
438+
TEST(connect_unsupported_level_127_refused)
439+
{
440+
/* Top of the byte range — guards against a future "treat high values
441+
* as latest known" mutation. */
442+
run_unsupported_level(0x7F);
443+
}
444+
363445
#ifdef WOLFMQTT_V5
364446
/* v5 dropped the [MQTT-3.1.3-8] CleanSession=1-only restriction; an empty
365447
* ClientId is acceptable with any Clean Start value. The broker MUST emit
@@ -463,6 +545,9 @@ int main(int argc, char** argv)
463545
RUN_TEST(connect_v311_emptyid_clean1_accepted);
464546
RUN_TEST(connect_v311_nonempty_clean0_accepted);
465547
RUN_TEST(connect_v311_explicit_auto_prefix_refused);
548+
RUN_TEST(connect_unsupported_level_3_refused);
549+
RUN_TEST(connect_unsupported_level_6_refused);
550+
RUN_TEST(connect_unsupported_level_127_refused);
466551
#ifdef WOLFMQTT_V5
467552
RUN_TEST(connect_v5_emptyid_assigned_id_emitted);
468553
RUN_TEST(connect_v5_emptyid_clean0_accepted);

0 commit comments

Comments
 (0)