Skip to content

Commit 2a831b4

Browse files
committed
Fix Zero-Length ClientId Unique Assignment
1 parent 62b0e51 commit 2a831b4

4 files changed

Lines changed: 595 additions & 24 deletions

File tree

src/mqtt_broker.c

Lines changed: 102 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2696,6 +2696,8 @@ static int BrokerHandle_Connect(BrokerClient* bc, int rx_len,
26962696
MqttConnect mc;
26972697
MqttConnectAck ack;
26982698
MqttMessage lwt;
2699+
word16 id_len = 0;
2700+
int auto_assigned = 0;
26992701

27002702
XMEMSET(&mc, 0, sizeof(mc));
27012703
XMEMSET(&ack, 0, sizeof(ack));
@@ -2725,7 +2727,6 @@ static int BrokerHandle_Connect(BrokerClient* bc, int rx_len,
27252727
bc->client_id[0] = '\0';
27262728
#endif
27272729
if (mc.client_id) {
2728-
word16 id_len = 0;
27292730
if (MqttDecode_Num((byte*)mc.client_id - MQTT_DATA_LEN_SIZE,
27302731
&id_len, MQTT_DATA_LEN_SIZE) == MQTT_DATA_LEN_SIZE) {
27312732
#ifdef WOLFMQTT_STATIC_MEMORY
@@ -2747,14 +2748,101 @@ static int BrokerHandle_Connect(BrokerClient* bc, int rx_len,
27472748
goto send_connack;
27482749
}
27492750
#endif
2750-
BROKER_STORE_STR(bc->client_id, mc.client_id, id_len,
2751-
BROKER_MAX_CLIENT_ID_LEN);
2751+
if (id_len > 0) {
2752+
BROKER_STORE_STR(bc->client_id, mc.client_id, id_len,
2753+
BROKER_MAX_CLIENT_ID_LEN);
2754+
}
2755+
}
2756+
}
2757+
2758+
/* Reserve the "auto-" prefix for server-assigned IDs. Without this an
2759+
* attacker could observe their own assigned auto-XXXXXXXX, then reconnect
2760+
* with an explicit client_id matching a predicted future value and
2761+
* hijack a victim's session via the duplicate-takeover path below. */
2762+
if (id_len >= 5 && BROKER_STR_VALID(bc->client_id) &&
2763+
XSTRNCMP(bc->client_id, "auto-", 5) == 0) {
2764+
WBLOG_ERR(broker,
2765+
"broker: client_id with reserved 'auto-' prefix sock=%d",
2766+
(int)bc->sock);
2767+
#ifdef WOLFMQTT_V5
2768+
if (mc.protocol_level >= MQTT_CONNECT_PROTOCOL_LEVEL_5) {
2769+
ack.return_code = MQTT_REASON_CLIENT_ID_NOT_VALID;
2770+
}
2771+
else
2772+
#endif
2773+
{
2774+
ack.return_code = MQTT_CONNECT_ACK_CODE_REFUSED_ID;
27522775
}
2776+
goto send_connack;
2777+
}
2778+
2779+
/* [MQTT-3.1.3-8] v3.1.1: zero-length ClientId requires CleanSession=1.
2780+
* The server MUST respond with CONNACK 0x02 (Identifier rejected) and
2781+
* close the connection. v5 dropped this restriction in favor of Clean
2782+
* Start + Session Expiry Interval, so it is enforced for v3.1.1 only. */
2783+
if (id_len == 0 && !mc.clean_session
2784+
#ifdef WOLFMQTT_V5
2785+
&& mc.protocol_level < MQTT_CONNECT_PROTOCOL_LEVEL_5
2786+
#endif
2787+
) {
2788+
WBLOG_ERR(broker,
2789+
"broker: empty ClientId with clean_session=0 sock=%d "
2790+
"[MQTT-3.1.3-8]", (int)bc->sock);
2791+
ack.return_code = MQTT_CONNECT_ACK_CODE_REFUSED_ID;
2792+
goto send_connack;
27532793
}
27542794

27552795
bc->protocol_level = mc.protocol_level;
27562796
bc->keep_alive_sec = mc.keep_alive_sec;
27572797
bc->last_rx = WOLFMQTT_BROKER_GET_TIME_S();
2798+
2799+
/* [MQTT-3.1.3-6] If we accepted a zero-length ClientId, assign a unique
2800+
* server-generated one before the duplicate-check / session-resume block
2801+
* below so the assigned ID flows through normal handling. v5 also echoes
2802+
* it back to the client via the Assigned Client Identifier property
2803+
* (emitted in the v5 CONNACK construction below); v3.1.1 has no such
2804+
* field, so the assignment is server-internal. */
2805+
if (id_len == 0 && !BROKER_STR_VALID(bc->client_id)) {
2806+
/* "auto-" + at least 8 hex chars + NUL. On a 16-bit-int platform the
2807+
* full 32-bit counter prints as more than 8 nibbles, so size for
2808+
* headroom rather than the minimum. The counter advances on every
2809+
* empty-ID CONNECT that reaches this point — including connects that
2810+
* are later refused (e.g., auth failure below) — so it is not a
2811+
* count of accepted clients. */
2812+
char auto_id[32];
2813+
int auto_len;
2814+
unsigned long id_value = (unsigned long)broker->next_auto_id++;
2815+
if (broker->next_auto_id == 0) {
2816+
/* Skip 0 on wrap for stylistic consistency with next_packet_id;
2817+
* unlike packet IDs, 0 has no protocol significance here. */
2818+
broker->next_auto_id = 1;
2819+
}
2820+
auto_len = XSNPRINTF(auto_id, (int)sizeof(auto_id),
2821+
"auto-%08lx", id_value);
2822+
if (auto_len > 0) {
2823+
BROKER_STORE_STR(bc->client_id, auto_id, (word16)auto_len,
2824+
BROKER_MAX_CLIENT_ID_LEN);
2825+
}
2826+
if (!BROKER_STR_VALID(bc->client_id)) {
2827+
/* Storage failed (e.g., WOLFMQTT_MALLOC returned NULL in the
2828+
* dynamic-memory path). Refuse rather than proceeding with an
2829+
* untracked client. */
2830+
WBLOG_ERR(broker,
2831+
"broker: auto-id store failed sock=%d", (int)bc->sock);
2832+
#ifdef WOLFMQTT_V5
2833+
if (mc.protocol_level >= MQTT_CONNECT_PROTOCOL_LEVEL_5) {
2834+
ack.return_code = MQTT_REASON_SERVER_UNAVAILABLE;
2835+
}
2836+
else
2837+
#endif
2838+
{
2839+
ack.return_code = MQTT_CONNECT_ACK_CODE_REFUSED_UNAVAIL;
2840+
}
2841+
goto send_connack;
2842+
}
2843+
auto_assigned = 1;
2844+
}
2845+
27582846
WBLOG_INFO(broker, "broker: CONNECT proto=%u clean=%d will=%d client_id=%s",
27592847
mc.protocol_level, mc.clean_session, mc.enable_lwt,
27602848
BROKER_STR_VALID(bc->client_id) ? bc->client_id : "(null)");
@@ -2986,25 +3074,16 @@ static int BrokerHandle_Connect(BrokerClient* bc, int rx_len,
29863074
ack.return_code == MQTT_CONNECT_ACK_CODE_ACCEPTED) {
29873075
MqttProp* prop;
29883076

2989-
/* If client sent empty client ID, generate one and inform client */
2990-
if (!BROKER_STR_VALID(bc->client_id)) {
2991-
char auto_id[32];
2992-
int id_len = XSNPRINTF(auto_id, (int)sizeof(auto_id),
2993-
"auto-%04x", broker->next_packet_id++);
2994-
if (broker->next_packet_id == 0) {
2995-
broker->next_packet_id = 1;
2996-
}
2997-
if (id_len > 0) {
2998-
BROKER_STORE_STR(bc->client_id, auto_id, (word16)id_len,
2999-
BROKER_MAX_CLIENT_ID_LEN);
3000-
}
3001-
if (BROKER_STR_VALID(bc->client_id)) {
3002-
prop = MqttProps_Add(&ack.props);
3003-
if (prop != NULL) {
3004-
prop->type = MQTT_PROP_ASSIGNED_CLIENT_ID;
3005-
prop->data_str.str = bc->client_id;
3006-
prop->data_str.len = (word16)XSTRLEN(bc->client_id);
3007-
}
3077+
/* [MQTT-3.1.3-6] Echo any server-assigned ClientId to v5 clients.
3078+
* Keyed off auto_assigned (set in the auto-id branch above) so this
3079+
* stays true to its name even if a future code path populates
3080+
* bc->client_id from another source (e.g., a TLS-cert identity). */
3081+
if (auto_assigned) {
3082+
prop = MqttProps_Add(&ack.props);
3083+
if (prop != NULL) {
3084+
prop->type = MQTT_PROP_ASSIGNED_CLIENT_ID;
3085+
prop->data_str.str = bc->client_id;
3086+
prop->data_str.len = (word16)XSTRLEN(bc->client_id);
30083087
}
30093088
}
30103089

@@ -3035,9 +3114,7 @@ static int BrokerHandle_Connect(BrokerClient* bc, int rx_len,
30353114
}
30363115
#endif
30373116

3038-
#if defined(WOLFMQTT_BROKER_WILL) || defined(WOLFMQTT_STATIC_MEMORY)
30393117
send_connack:
3040-
#endif
30413118
rc = MqttEncode_ConnectAck(bc->tx_buf, BROKER_CLIENT_TX_SZ(bc), &ack);
30423119
if (rc > 0) {
30433120
WBLOG_INFO(broker, "broker: CONNACK send sock=%d code=%d", (int)bc->sock,
@@ -3729,6 +3806,7 @@ int MqttBroker_Init(MqttBroker* broker, MqttBrokerNet* net)
37293806
broker->running = 0;
37303807
broker->log_level = BROKER_LOG_LEVEL_DEFAULT;
37313808
broker->next_packet_id = 1;
3809+
broker->next_auto_id = 1;
37323810

37333811
#if !defined(WOLFMQTT_WOLFIP) && !defined(WOLFMQTT_BROKER_CUSTOM_NET)
37343812
/* For the default POSIX backend, the net callbacks expect ctx to be a

tests/include.am

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,24 @@ tests_unit_tests_DEPENDENCIES = src/libwolfmqtt.la
1515
# Test framework header
1616
noinst_HEADERS += tests/unit_test.h
1717

18+
# Broker CONNECT handler tests. Compiles src/mqtt_broker.c into the test
19+
# binary with WOLFMQTT_BROKER_CUSTOM_NET so the network layer is replaced
20+
# with mock callbacks driven by the harness.
21+
if BUILD_BROKER
22+
check_PROGRAMS += tests/test_broker_connect
23+
tests_test_broker_connect_SOURCES = \
24+
tests/test_broker_connect.c \
25+
src/mqtt_broker.c
26+
tests_test_broker_connect_CFLAGS = -DWOLFMQTT_BROKER -DWOLFMQTT_BROKER_CUSTOM_NET \
27+
-DWOLFMQTT_BROKER_NO_LOG -DNO_MAIN_DRIVER \
28+
'-DWOLFMQTT_BROKER_GET_TIME_S()=((WOLFMQTT_BROKER_TIME_T)0)' \
29+
'-DBROKER_SLEEP_MS(ms)=do{}while(0)' \
30+
$(AM_CFLAGS)
31+
tests_test_broker_connect_CPPFLAGS = -I$(top_srcdir) $(AM_CPPFLAGS)
32+
tests_test_broker_connect_LDADD = src/libwolfmqtt.la $(LIB_STATIC_ADD)
33+
tests_test_broker_connect_DEPENDENCIES = src/libwolfmqtt.la
34+
endif
35+
1836
if BUILD_FUZZ
1937
if BUILD_BROKER
2038
noinst_PROGRAMS += tests/fuzz/broker_fuzz

0 commit comments

Comments
 (0)