From 717e53ebd63da59820f6929a473ece3043cb143e Mon Sep 17 00:00:00 2001 From: aidan garske Date: Thu, 9 Jul 2026 22:38:08 -0700 Subject: [PATCH 01/54] F-6046 - Reset the SN shared object instead of the generic message object --- src/mqtt_sn_client.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mqtt_sn_client.c b/src/mqtt_sn_client.c index 77b75443e..3c2c68e9a 100644 --- a/src/mqtt_sn_client.c +++ b/src/mqtt_sn_client.c @@ -672,7 +672,7 @@ static int SN_Client_WaitType(MqttClient *client, void* packet_obj, } /* Clear shared union for next call */ - MqttSNClient_PacketReset(packet_type, &client->msg); + MqttSNClient_PacketReset(packet_type, &client->msgSN); #ifdef WOLFMQTT_DEBUG_CLIENT PRINTF("Read Packet: Len %d, Type %d, ID %d", From 623b7af1d4b03852fb74da1ecbea884592f2bb41 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Thu, 9 Jul 2026 22:38:20 -0700 Subject: [PATCH 02/54] F-6219 - Validate exact length in SN_Decode_PublishResp --- src/mqtt_sn_packet.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/mqtt_sn_packet.c b/src/mqtt_sn_packet.c index 4236c3961..9fdb3746e 100644 --- a/src/mqtt_sn_packet.c +++ b/src/mqtt_sn_packet.c @@ -1423,6 +1423,11 @@ int SN_Decode_PublishResp(byte* rx_buf, int rx_buf_len, byte type, return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_OUT_OF_BUFFER); } + /* Fixed size: PUBACK is 7 bytes, PUBREC/PUBREL/PUBCOMP are 4 */ + if (total_len != ((type == SN_MSG_TYPE_PUBACK) ? 7 : 4)) { + return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_MALFORMED_DATA); + } + /* Validate packet type */ rec_type = *rx_payload++; if (rec_type != type) { From 6bcdacee8e0e39a5a8875cd4e096c6d2728e6d0c Mon Sep 17 00:00:00 2001 From: aidan garske Date: Thu, 9 Jul 2026 22:38:35 -0700 Subject: [PATCH 03/54] F-6483 - Bound the SN PINGREQ client id to prevent length narrowing --- src/mqtt_sn_packet.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/mqtt_sn_packet.c b/src/mqtt_sn_packet.c index 9fdb3746e..0e8202a86 100644 --- a/src/mqtt_sn_packet.c +++ b/src/mqtt_sn_packet.c @@ -1659,7 +1659,11 @@ int SN_Encode_Ping(byte *tx_buf, int tx_buf_len, SN_PingReq *ping, byte type) if ((type == SN_MSG_TYPE_PING_REQ) && (ping != NULL) && (ping->clientId != NULL)) { - total_len += clientId_len = (int)XSTRLEN(ping->clientId); + clientId_len = (int)XSTRLEN(ping->clientId); + if (clientId_len > SN_CLIENTID_MAX_LEN) { + clientId_len = SN_CLIENTID_MAX_LEN; + } + total_len += clientId_len; } if (total_len > tx_buf_len) { From 8a18f2a796de7769a7e5b16996ee2758b209cfdb Mon Sep 17 00:00:00 2001 From: aidan garske Date: Thu, 9 Jul 2026 22:38:51 -0700 Subject: [PATCH 04/54] F-6482 - Echo the received PUBLISH TopicId in the MQTT-SN PUBACK --- src/mqtt_sn_client.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/mqtt_sn_client.c b/src/mqtt_sn_client.c index 3c2c68e9a..0c45b66b0 100644 --- a/src/mqtt_sn_client.c +++ b/src/mqtt_sn_client.c @@ -223,6 +223,11 @@ static int SN_Client_HandlePacket(MqttClient* client, SN_MsgType packet_type, type = (p_pub->qos == MQTT_QOS_1) ? SN_MSG_TYPE_PUBACK : SN_MSG_TYPE_PUBREC; + /* [MQTT-SN-1.2 5.4.4] The response MUST echo the received + * PUBLISH TopicId; topic_name holds the 2-byte TopicId. */ + (void)MqttDecode_Num((byte*)p_pub->topic_name, + &p_pub->resp.topicId, MQTT_DATA_LEN_SIZE); + p_pub->resp.return_code = SN_RC_ACCEPTED; p_pub->resp.packet_id = packet_id; #ifdef WOLFMQTT_MULTITHREAD From 5205fca40adc1247591a5ce727352a0c05302d5f Mon Sep 17 00:00:00 2001 From: aidan garske Date: Thu, 9 Jul 2026 22:39:04 -0700 Subject: [PATCH 05/54] F-3389 - Skip v5 property-stack shutdown when MqttClient_DeInit gets NULL --- src/mqtt_client.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mqtt_client.c b/src/mqtt_client.c index ec39f455b..56c397cbd 100644 --- a/src/mqtt_client.c +++ b/src/mqtt_client.c @@ -1738,10 +1738,10 @@ void MqttClient_DeInit(MqttClient *client) (void)wm_SemFree(&client->lockCURL); #endif #endif - } #ifdef WOLFMQTT_V5 - (void)MqttProps_ShutDown(); + (void)MqttProps_ShutDown(); #endif + } } #ifdef WOLFMQTT_DISCONNECT_CB From b8357bab6189e12ea21be30d86fc97218dbafd67 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Thu, 9 Jul 2026 22:39:13 -0700 Subject: [PATCH 06/54] F-6653 - Zero-fill the encoded data field when the payload pointer is NULL --- src/mqtt_packet.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/mqtt_packet.c b/src/mqtt_packet.c index fc541e495..fa640ca60 100644 --- a/src/mqtt_packet.c +++ b/src/mqtt_packet.c @@ -675,9 +675,15 @@ int MqttEncode_Data(byte *buf, const byte *data, word16 data_len) { int len = MqttEncode_Num(buf, data_len); - if ((buf != NULL) && (data != NULL)) { + if (buf != NULL) { buf += len; - XMEMCPY(buf, data, data_len); + if (data != NULL) { + XMEMCPY(buf, data, data_len); + } + else { + /* never emit stale tx_buf for a declared-length field */ + XMEMSET(buf, 0, data_len); + } } return len + data_len; } From 31ff000110e61463c2ada9d6bf3dde8cd97cf03e Mon Sep 17 00:00:00 2001 From: aidan garske Date: Thu, 9 Jul 2026 22:39:25 -0700 Subject: [PATCH 07/54] F-4774 - Bound assigned client id copy by decoded length in mqttclient --- examples/mqttclient/mqttclient.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/examples/mqttclient/mqttclient.c b/examples/mqttclient/mqttclient.c index 6b2d421b2..215379a32 100644 --- a/examples/mqttclient/mqttclient.c +++ b/examples/mqttclient/mqttclient.c @@ -143,11 +143,15 @@ static int mqtt_property_cb(MqttClient *client, MqttProp *head, void *ctx) /* Store client ID in global */ mqttCtx->client_id = &gClientId[0]; - /* Store assigned client ID from CONNACK*/ - XSTRNCPY((char*)mqttCtx->client_id, prop->data_str.str, - MAX_CLIENT_ID_LEN - 1); - /* should use strlcpy() semantics, but non-portable */ - ((char*)mqttCtx->client_id)[MAX_CLIENT_ID_LEN - 1] = '\0'; + /* copy bounded by decoded len; data_str.str is not NUL-terminated */ + { + word16 idLen = prop->data_str.len; + if (idLen >= MAX_CLIENT_ID_LEN) { + idLen = MAX_CLIENT_ID_LEN - 1; + } + XMEMCPY((char*)mqttCtx->client_id, prop->data_str.str, idLen); + ((char*)mqttCtx->client_id)[idLen] = '\0'; + } break; case MQTT_PROP_SUBSCRIPTION_ID_AVAIL: From 6e763713763ba6986adf574d7fbcf7acd0cfed13 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Thu, 9 Jul 2026 22:39:36 -0700 Subject: [PATCH 08/54] F-4930 - Bound assigned client id copy by decoded length in nbclient --- examples/nbclient/nbclient.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/examples/nbclient/nbclient.c b/examples/nbclient/nbclient.c index 1efbd3cbf..dbd1ef9e8 100644 --- a/examples/nbclient/nbclient.c +++ b/examples/nbclient/nbclient.c @@ -132,11 +132,15 @@ static int mqtt_property_cb(MqttClient *client, MqttProp *head, void *ctx) /* Store client ID in global */ mqttCtx->client_id = &gClientId[0]; - /* Store assigned client ID from CONNACK*/ - XSTRNCPY((char*)mqttCtx->client_id, prop->data_str.str, - MAX_CLIENT_ID_LEN - 1); - /* should use strlcpy() semantics, but non-portable */ - ((char*)mqttCtx->client_id)[MAX_CLIENT_ID_LEN - 1] = '\0'; + /* copy bounded by decoded len; data_str.str is not NUL-terminated */ + { + word16 idLen = prop->data_str.len; + if (idLen >= MAX_CLIENT_ID_LEN) { + idLen = MAX_CLIENT_ID_LEN - 1; + } + XMEMCPY((char*)mqttCtx->client_id, prop->data_str.str, idLen); + ((char*)mqttCtx->client_id)[idLen] = '\0'; + } break; case MQTT_PROP_SUBSCRIPTION_ID_AVAIL: From af8123cb5952bc432ac9f948d712875a00854e16 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Thu, 9 Jul 2026 22:39:46 -0700 Subject: [PATCH 09/54] F-4998 - Bound assigned client id copy by decoded length in mqtt-pub --- examples/pub-sub/mqtt-pub.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/examples/pub-sub/mqtt-pub.c b/examples/pub-sub/mqtt-pub.c index 6c92a77db..abf313022 100644 --- a/examples/pub-sub/mqtt-pub.c +++ b/examples/pub-sub/mqtt-pub.c @@ -75,11 +75,15 @@ static int mqtt_property_cb(MqttClient *client, MqttProp *head, void *ctx) /* Store client ID in global */ mqttCtx->client_id = &gClientId[0]; - /* Store assigned client ID from CONNACK*/ - XSTRNCPY((char*)mqttCtx->client_id, prop->data_str.str, - MAX_CLIENT_ID_LEN - 1); - /* should use strlcpy() semantics, but non-portable */ - ((char*)mqttCtx->client_id)[MAX_CLIENT_ID_LEN - 1] = '\0'; + /* copy bounded by decoded len; data_str.str is not NUL-terminated */ + { + word16 idLen = prop->data_str.len; + if (idLen >= MAX_CLIENT_ID_LEN) { + idLen = MAX_CLIENT_ID_LEN - 1; + } + XMEMCPY((char*)mqttCtx->client_id, prop->data_str.str, idLen); + ((char*)mqttCtx->client_id)[idLen] = '\0'; + } break; case MQTT_PROP_SUBSCRIPTION_ID_AVAIL: From 0b772686f5c5cd9e0575766f2a760502f2029f63 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Thu, 9 Jul 2026 22:39:55 -0700 Subject: [PATCH 10/54] F-4999 - Bound assigned client id copy by decoded length in mqtt-sub --- examples/pub-sub/mqtt-sub.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/examples/pub-sub/mqtt-sub.c b/examples/pub-sub/mqtt-sub.c index c2905a1a4..dcffc628f 100644 --- a/examples/pub-sub/mqtt-sub.c +++ b/examples/pub-sub/mqtt-sub.c @@ -145,11 +145,15 @@ static int mqtt_property_cb(MqttClient *client, MqttProp *head, void *ctx) /* Store client ID in global */ mqttCtx->client_id = &gClientId[0]; - /* Store assigned client ID from CONNACK*/ - XSTRNCPY((char*)mqttCtx->client_id, prop->data_str.str, - MAX_CLIENT_ID_LEN - 1); - /* should use strlcpy() semantics, but non-portable */ - ((char*)mqttCtx->client_id)[MAX_CLIENT_ID_LEN - 1] = '\0'; + /* copy bounded by decoded len; data_str.str is not NUL-terminated */ + { + word16 idLen = prop->data_str.len; + if (idLen >= MAX_CLIENT_ID_LEN) { + idLen = MAX_CLIENT_ID_LEN - 1; + } + XMEMCPY((char*)mqttCtx->client_id, prop->data_str.str, idLen); + ((char*)mqttCtx->client_id)[idLen] = '\0'; + } break; case MQTT_PROP_SUBSCRIPTION_ID_AVAIL: From f5326a4888e90b0c460c39602fae80a046dd7915 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Thu, 9 Jul 2026 22:40:15 -0700 Subject: [PATCH 11/54] F-2025 - Force-zero broker client credentials on static-memory free --- src/mqtt_broker.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mqtt_broker.c b/src/mqtt_broker.c index 917a4420e..c37fb1049 100644 --- a/src/mqtt_broker.c +++ b/src/mqtt_broker.c @@ -2033,8 +2033,8 @@ static void BrokerClient_Free(BrokerClient* bc) (void)BrokerNetDisconnect(bc); MqttClient_DeInit(&bc->client); #ifdef WOLFMQTT_STATIC_MEMORY - XMEMSET(bc, 0, sizeof(*bc)); - /* in_use is now 0 after memset */ + BROKER_FORCE_ZERO(bc, sizeof(*bc)); + /* in_use is now 0 after zeroization */ #else if (bc->client_id) { WOLFMQTT_FREE(bc->client_id); From ab5f47e1542121da2387ce611e9207d668f9a76c Mon Sep 17 00:00:00 2001 From: aidan garske Date: Thu, 9 Jul 2026 22:40:47 -0700 Subject: [PATCH 12/54] F-6049 - Wipe the persist AES key cache unconditionally on broker free --- src/mqtt_broker.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/mqtt_broker.c b/src/mqtt_broker.c index c37fb1049..06dccf90a 100644 --- a/src/mqtt_broker.c +++ b/src/mqtt_broker.c @@ -6523,12 +6523,11 @@ int MqttBroker_Free(MqttBroker* broker) defined(WOLFMQTT_BROKER_PERSIST_ENCRYPT) /* Zero the cached AES key on teardown. ForceZero so the compiler * cannot elide the wipe (plain XMEMSET on a value that becomes - * dead-on-return is at the compiler's discretion). */ - if (broker->persist_key_loaded) { - BROKER_FORCE_ZERO(broker->persist_key_cache, - sizeof(broker->persist_key_cache)); - broker->persist_key_loaded = 0; - } + * dead-on-return is at the compiler's discretion). Unconditional: a + * failed/partial derive_key can leave bytes without setting the flag. */ + BROKER_FORCE_ZERO(broker->persist_key_cache, + sizeof(broker->persist_key_cache)); + broker->persist_key_loaded = 0; #endif return MQTT_CODE_SUCCESS; From 392a99dc513e0b389e764d32ea694f8b4b4c4bf5 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Thu, 9 Jul 2026 22:41:10 -0700 Subject: [PATCH 13/54] F-2024 - Force-zero broker will buffers on static-memory ClearWill --- src/mqtt_broker.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/mqtt_broker.c b/src/mqtt_broker.c index 06dccf90a..bdd047ed8 100644 --- a/src/mqtt_broker.c +++ b/src/mqtt_broker.c @@ -3476,8 +3476,9 @@ static void BrokerClient_ClearWill(BrokerClient* bc) bc->will_retain = 0; bc->will_delay_sec = 0; #ifdef WOLFMQTT_STATIC_MEMORY + BROKER_FORCE_ZERO(bc->will_topic, sizeof(bc->will_topic)); + BROKER_FORCE_ZERO(bc->will_payload, sizeof(bc->will_payload)); bc->will_payload_len = 0; - bc->will_topic[0] = '\0'; #else if (bc->will_topic) { BROKER_FORCE_ZERO(bc->will_topic, XSTRLEN(bc->will_topic) + 1); From ef1ddfd6a825bdbaa762b27b455033c35f810f3c Mon Sep 17 00:00:00 2001 From: aidan garske Date: Thu, 9 Jul 2026 22:41:28 -0700 Subject: [PATCH 14/54] F-2026 - Force-zero retained message on static-memory delete --- src/mqtt_broker.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mqtt_broker.c b/src/mqtt_broker.c index bdd047ed8..a5c46b1f5 100644 --- a/src/mqtt_broker.c +++ b/src/mqtt_broker.c @@ -3377,7 +3377,7 @@ static void BrokerRetained_Delete(MqttBroker* broker, const char* topic) XSTRCMP(broker->retained[i].topic, topic) == 0) { WBLOG_DBG(broker, "broker: retained delete topic=%s", BrokerLog_Sanitize(topic)); - XMEMSET(&broker->retained[i], 0, sizeof(BrokerRetainedMsg)); + BROKER_FORCE_ZERO(&broker->retained[i], sizeof(BrokerRetainedMsg)); found = 1; break; } From ff9c8e95100c244d0264c2316e26afb5c59836b9 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Thu, 9 Jul 2026 22:41:46 -0700 Subject: [PATCH 15/54] F-2027 - Force-zero retained messages on static-memory free-all --- src/mqtt_broker.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mqtt_broker.c b/src/mqtt_broker.c index a5c46b1f5..72af8a456 100644 --- a/src/mqtt_broker.c +++ b/src/mqtt_broker.c @@ -3439,7 +3439,7 @@ static void BrokerRetained_FreeAll(MqttBroker* broker) #ifdef WOLFMQTT_STATIC_MEMORY for (i = 0; i < BROKER_MAX_RETAINED; i++) { - XMEMSET(&broker->retained[i], 0, sizeof(BrokerRetainedMsg)); + BROKER_FORCE_ZERO(&broker->retained[i], sizeof(BrokerRetainedMsg)); } #else while (cur) { From 0fef1f26bb4d8aaf37405bb5d0526e9692ffd075 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Thu, 9 Jul 2026 22:42:27 -0700 Subject: [PATCH 16/54] F-3830 - Force-zero pending wills on static-memory cleanup --- src/mqtt_broker.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/mqtt_broker.c b/src/mqtt_broker.c index 72af8a456..e53682f84 100644 --- a/src/mqtt_broker.c +++ b/src/mqtt_broker.c @@ -3632,7 +3632,7 @@ static void BrokerPendingWill_Cancel(MqttBroker* broker, XSTRCMP(broker->pending_wills[i].client_id, client_id) == 0) { WBLOG_DBG(broker, "broker: will cancelled client_id=%s", BrokerLog_Sanitize(client_id)); - XMEMSET(&broker->pending_wills[i], 0, + BROKER_FORCE_ZERO(&broker->pending_wills[i], sizeof(BrokerPendingWill)); return; } @@ -3679,7 +3679,7 @@ static void BrokerPendingWill_FreeAll(MqttBroker* broker) return; } #ifdef WOLFMQTT_STATIC_MEMORY - XMEMSET(broker->pending_wills, 0, sizeof(broker->pending_wills)); + BROKER_FORCE_ZERO(broker->pending_wills, sizeof(broker->pending_wills)); #else pw = broker->pending_wills; while (pw) { @@ -3733,7 +3733,7 @@ static int BrokerPendingWill_Process(MqttBroker* broker) (unsigned)pw->payload_len); BrokerClient_PublishWillImmediate(broker, pw->topic, pw->payload, pw->payload_len, pw->qos, pw->retain); - XMEMSET(pw, 0, sizeof(BrokerPendingWill)); + BROKER_FORCE_ZERO(pw, sizeof(BrokerPendingWill)); activity = 1; } } From 9519460e2a46dd3708cff75e28ed75307afb8a15 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Thu, 9 Jul 2026 22:42:44 -0700 Subject: [PATCH 17/54] F-4524 - Scrub each subscriber tx_buf after forwarding an immediate will --- src/mqtt_broker.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/mqtt_broker.c b/src/mqtt_broker.c index e53682f84..940c89185 100644 --- a/src/mqtt_broker.c +++ b/src/mqtt_broker.c @@ -4065,6 +4065,7 @@ static void BrokerClient_PublishWillImmediate(MqttBroker* broker, if (enc_rc > 0) { (void)MqttPacket_Write(&sub->client->client, sub->client->tx_buf, enc_rc); + BROKER_FORCE_ZERO(sub->client->tx_buf, enc_rc); } } #ifndef WOLFMQTT_STATIC_MEMORY From db2b3aaed56d082bbdb0d6c34b1193177bd9c7e4 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Thu, 9 Jul 2026 22:43:05 -0700 Subject: [PATCH 18/54] F-6051 - Force-zero retained payloads before free on dynamic-memory paths --- src/mqtt_broker.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/mqtt_broker.c b/src/mqtt_broker.c index 940c89185..d1405a9a5 100644 --- a/src/mqtt_broker.c +++ b/src/mqtt_broker.c @@ -3404,8 +3404,10 @@ static void BrokerRetained_Delete(MqttBroker* broker, const char* topic) else { broker->retained = next; } + BROKER_FORCE_ZERO(cur->topic, XSTRLEN(cur->topic) + 1); WOLFMQTT_FREE(cur->topic); if (cur->payload) { + BROKER_FORCE_ZERO(cur->payload, cur->payload_len); WOLFMQTT_FREE(cur->payload); } WOLFMQTT_FREE(cur); @@ -3445,9 +3447,11 @@ static void BrokerRetained_FreeAll(MqttBroker* broker) while (cur) { BrokerRetainedMsg* next = cur->next; if (cur->topic) { + BROKER_FORCE_ZERO(cur->topic, XSTRLEN(cur->topic) + 1); WOLFMQTT_FREE(cur->topic); } if (cur->payload) { + BROKER_FORCE_ZERO(cur->payload, cur->payload_len); WOLFMQTT_FREE(cur->payload); } WOLFMQTT_FREE(cur); From a71f43be7deffda312344a472a31e2cdb4e6f08d Mon Sep 17 00:00:00 2001 From: aidan garske Date: Thu, 9 Jul 2026 22:43:24 -0700 Subject: [PATCH 19/54] F-6657 - Force-zero broker out-queue payloads before free --- src/mqtt_broker.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/mqtt_broker.c b/src/mqtt_broker.c index d1405a9a5..5a32b0469 100644 --- a/src/mqtt_broker.c +++ b/src/mqtt_broker.c @@ -1595,10 +1595,12 @@ static void BrokerOutPub_Free(BrokerOutPub* e) return; } if (e->topic != NULL) { + BROKER_FORCE_ZERO(e->topic, XSTRLEN(e->topic) + 1); WOLFMQTT_FREE(e->topic); e->topic = NULL; } if (e->payload != NULL) { + BROKER_FORCE_ZERO(e->payload, e->payload_len); WOLFMQTT_FREE(e->payload); e->payload = NULL; } From bdc73ecfaa4ccbbaa48a8e101ec76bfc644c2e1a Mon Sep 17 00:00:00 2001 From: aidan garske Date: Thu, 9 Jul 2026 22:44:04 -0700 Subject: [PATCH 20/54] F-6226 - Force-zero decrypted persist payloads before free --- src/mqtt_broker_persist.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/mqtt_broker_persist.c b/src/mqtt_broker_persist.c index f25e722c3..431d5e6d8 100644 --- a/src/mqtt_broker_persist.c +++ b/src/mqtt_broker_persist.c @@ -242,6 +242,19 @@ static int wmqb_get_key(MqttBroker* broker) * rekeying only when persist_key_cache changes would amortize the AES * key schedule. Requires deciding the threading model first - today * the broker is single-threaded so a single shared context is safe. */ +/* Volatile wipe the compiler cannot elide, for plaintext about to be freed. */ +static void wmqb_force_zero(void* mem, word32 len) +{ + volatile byte* p = (volatile byte*)mem; + if (p == NULL) { + return; + } + while (len > 0) { + *p++ = 0; + len--; + } +} + static int wmqb_encrypt_blob(MqttBroker* broker, const byte* plain, word32 plain_len, byte** ct_out, word32* ct_out_len) { @@ -445,6 +458,7 @@ static int wmqb_iter_decrypt_cb(const byte* key, word16 key_len, return d->inner_cb(key, key_len, blob, 0, d->inner_ctx); } stop = d->inner_cb(key, key_len, plain, plain_len, d->inner_ctx); + wmqb_force_zero(plain, plain_len); WOLFMQTT_FREE(plain); return stop; } @@ -508,11 +522,13 @@ static int wmqb_kv_get_decrypt_meta(MqttBroker* broker, byte ns, return rc; } if (plain_len > *inout_len) { + wmqb_force_zero(plain, plain_len); WOLFMQTT_FREE(plain); return MQTT_CODE_ERROR_OUT_OF_BUFFER; } XMEMCPY(out, plain, plain_len); *inout_len = plain_len; + wmqb_force_zero(plain, plain_len); WOLFMQTT_FREE(plain); return 0; } From 9cdcc1e9595e02fdaf686bae90769b2dc3a3884f Mon Sep 17 00:00:00 2001 From: aidan garske Date: Thu, 9 Jul 2026 22:44:49 -0700 Subject: [PATCH 21/54] F-6490 - Force-zero persist serialization buffers before free --- src/mqtt_broker_persist.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/mqtt_broker_persist.c b/src/mqtt_broker_persist.c index 431d5e6d8..de22f1f0e 100644 --- a/src/mqtt_broker_persist.c +++ b/src/mqtt_broker_persist.c @@ -615,6 +615,9 @@ static int wmqb_put_session_record(MqttBroker* broker, const char* cid, rc = wmqb_kv_put_commit(broker, BROKER_PERSIST_NS_SESSION, (const byte*)cid, cid_len, buf, total_len); +#ifdef WOLFMQTT_BROKER_PERSIST_ENCRYPT + wmqb_force_zero(buf, total_len); +#endif WOLFMQTT_FREE(buf); return rc; } @@ -798,6 +801,9 @@ int BrokerPersist_PutSubs(MqttBroker* broker, const char* client_id) rc = wmqb_kv_put_commit(broker, BROKER_PERSIST_NS_SUBS, (const byte*)client_id, cid_len, buf, total_len); +#ifdef WOLFMQTT_BROKER_PERSIST_ENCRYPT + wmqb_force_zero(buf, total_len); +#endif WOLFMQTT_FREE(buf); return rc; } @@ -871,6 +877,9 @@ int BrokerPersist_PutRetained(MqttBroker* broker, rc = wmqb_kv_put_commit(broker, BROKER_PERSIST_NS_RETAINED, (const byte*)topic, topic_len, buf, total_len); +#ifdef WOLFMQTT_BROKER_PERSIST_ENCRYPT + wmqb_force_zero(buf, total_len); +#endif WOLFMQTT_FREE(buf); return rc; } @@ -991,6 +1000,9 @@ int BrokerPersist_PutOutPub(MqttBroker* broker, const char* client_id, rc = wmqb_kv_put_commit(broker, BROKER_PERSIST_NS_OUTQ, key, key_len, buf, total_len); +#ifdef WOLFMQTT_BROKER_PERSIST_ENCRYPT + wmqb_force_zero(buf, total_len); +#endif WOLFMQTT_FREE(buf); return rc; #endif /* WOLFMQTT_STATIC_MEMORY */ From 80a00b27f026fd1df3ffb3d13487642d206ca89d Mon Sep 17 00:00:00 2001 From: aidan garske Date: Thu, 9 Jul 2026 22:45:35 -0700 Subject: [PATCH 22/54] F-3822 - Reject reserved QoS in MqttEncode_Publish --- src/mqtt_packet.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/mqtt_packet.c b/src/mqtt_packet.c index fa640ca60..4fc87471a 100644 --- a/src/mqtt_packet.c +++ b/src/mqtt_packet.c @@ -1830,6 +1830,9 @@ int MqttEncode_Publish(byte *tx_buf, int tx_buf_len, MqttPublish *publish, if (tx_buf == NULL || publish == NULL) { return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_BAD_ARG); } + if (publish->qos > MQTT_QOS_2) { + return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_BAD_ARG); + } /* MQTT UTF-8 strings are limited to 65535 bytes [MQTT-1.5.3]. Check here * before writing the fixed header so a later MqttEncode_String failure * cannot corrupt tx_payload via `tx_payload += -1`. NULL topic_name From 1e812414133417fcf8f249bacce11dde47e99adf Mon Sep 17 00:00:00 2001 From: aidan garske Date: Thu, 9 Jul 2026 22:45:54 -0700 Subject: [PATCH 23/54] F-3828 - Reject per-topic QoS above 2 in MqttEncode_Subscribe --- src/mqtt_packet.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/mqtt_packet.c b/src/mqtt_packet.c index 4fc87471a..c2b4793f7 100644 --- a/src/mqtt_packet.c +++ b/src/mqtt_packet.c @@ -2361,6 +2361,9 @@ int MqttEncode_Subscribe(byte *tx_buf, int tx_buf_len, if (str_len > (size_t)0xFFFF) { return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_BAD_ARG); } + if (topic->qos > MQTT_QOS_2) { + return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_BAD_ARG); + } remain_len += (int)str_len + MQTT_DATA_LEN_SIZE; remain_len++; /* For QoS */ } From ce373d8c79d7d9bfe2b42074ee50814eb42eb842 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Thu, 9 Jul 2026 22:46:27 -0700 Subject: [PATCH 24/54] F-2753 - Reject zero-topic SUBSCRIBE and UNSUBSCRIBE encode --- src/mqtt_packet.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/mqtt_packet.c b/src/mqtt_packet.c index c2b4793f7..4137e6044 100644 --- a/src/mqtt_packet.c +++ b/src/mqtt_packet.c @@ -2351,6 +2351,11 @@ int MqttEncode_Subscribe(byte *tx_buf, int tx_buf_len, return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_PACKET_ID); } + /* [MQTT-3.8.3-3] SUBSCRIBE must carry at least one topic filter */ + if (subscribe->topic_count <= 0) { + return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_BAD_ARG); + } + /* Determine packet length */ remain_len = MQTT_DATA_LEN_SIZE; /* For packet_id */ for (i = 0; i < subscribe->topic_count; i++) { @@ -2736,6 +2741,11 @@ int MqttEncode_Unsubscribe(byte *tx_buf, int tx_buf_len, return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_PACKET_ID); } + /* [MQTT-3.10.3-2] UNSUBSCRIBE must carry at least one topic filter */ + if (unsubscribe->topic_count <= 0) { + return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_BAD_ARG); + } + /* Determine packet length */ remain_len = MQTT_DATA_LEN_SIZE; /* For packet_id */ for (i = 0; i < unsubscribe->topic_count; i++) { From 2ae015994aa6a4bd932c0f2fe0b351a1b782a670 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Thu, 9 Jul 2026 22:46:43 -0700 Subject: [PATCH 25/54] F-4055 - Require a non-zero packet id in MqttEncode_PublishResp --- src/mqtt_packet.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/mqtt_packet.c b/src/mqtt_packet.c index 4137e6044..9943a7840 100644 --- a/src/mqtt_packet.c +++ b/src/mqtt_packet.c @@ -2141,6 +2141,11 @@ int MqttEncode_PublishResp(byte* tx_buf, int tx_buf_len, byte type, return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_BAD_ARG); } + /* [MQTT-2.3.1-1] PUBACK/PUBREC/PUBREL/PUBCOMP require a non-zero packet id */ + if (publish_resp->packet_id == 0) { + return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_PACKET_ID); + } + /* Determine packet length */ remain_len = MQTT_DATA_LEN_SIZE; /* For packet_id */ From 9a753bb60ca6664907e44c9567e20ded38aa42ea Mon Sep 17 00:00:00 2001 From: aidan garske Date: Thu, 9 Jul 2026 22:47:13 -0700 Subject: [PATCH 26/54] F-4526 - Validate the Will Topic name in MqttEncode_Connect --- src/mqtt_packet.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/mqtt_packet.c b/src/mqtt_packet.c index 9943a7840..a0cfac1d9 100644 --- a/src/mqtt_packet.c +++ b/src/mqtt_packet.c @@ -1186,6 +1186,11 @@ int MqttEncode_Connect(byte *tx_buf, int tx_buf_len, MqttConnect *mc_connect) if (str_len > (size_t)0xFFFF) { return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_BAD_ARG); } + /* [MQTT-3.1.3.3] Will Topic is a Topic Name: no wildcards */ + if (!MqttPacket_TopicNameValid(mc_connect->lwt_msg->topic_name, + (word16)str_len, mc_connect->protocol_level)) { + return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_MALFORMED_DATA); + } remain_len += (int)str_len; remain_len += MQTT_DATA_LEN_SIZE; From 55e67e1d48212b68cf1753d7433712590e1c521a Mon Sep 17 00:00:00 2001 From: aidan garske Date: Thu, 9 Jul 2026 22:47:24 -0700 Subject: [PATCH 27/54] F-4522 - Reject reserved Will QoS in MqttEncode_Connect --- src/mqtt_packet.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/mqtt_packet.c b/src/mqtt_packet.c index a0cfac1d9..43a340572 100644 --- a/src/mqtt_packet.c +++ b/src/mqtt_packet.c @@ -1191,6 +1191,9 @@ int MqttEncode_Connect(byte *tx_buf, int tx_buf_len, MqttConnect *mc_connect) (word16)str_len, mc_connect->protocol_level)) { return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_MALFORMED_DATA); } + if (mc_connect->lwt_msg->qos > MQTT_QOS_2) { + return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_BAD_ARG); + } remain_len += (int)str_len; remain_len += MQTT_DATA_LEN_SIZE; From 58860cd93fd5fee60ea59a309ae6cec66aa76323 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Thu, 9 Jul 2026 22:47:35 -0700 Subject: [PATCH 28/54] F-4525 - Normalize protocol level 0 before encoding CONNECT --- src/mqtt_packet.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/mqtt_packet.c b/src/mqtt_packet.c index 43a340572..b3fffb197 100644 --- a/src/mqtt_packet.c +++ b/src/mqtt_packet.c @@ -1132,6 +1132,11 @@ int MqttEncode_Connect(byte *tx_buf, int tx_buf_len, MqttConnect *mc_connect) return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_BAD_ARG); } + /* 0 means use build default; normalize so wire level and v5 prop gating agree */ + if (mc_connect->protocol_level == 0) { + mc_connect->protocol_level = MQTT_CONNECT_PROTOCOL_LEVEL; + } + /* [MQTT-3.1.2-22]: If the User Name Flag is set to 0, the Password Flag * MUST be set to 0 */ if (mc_connect->password != NULL && mc_connect->username == NULL) { From 0ade8fc984f218008c2405e6238da1d754e09262 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Thu, 9 Jul 2026 22:47:49 -0700 Subject: [PATCH 29/54] F-6489 - Reject reserved ConnectAck flag bits --- src/mqtt_packet.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/mqtt_packet.c b/src/mqtt_packet.c index b3fffb197..293acaa2b 100644 --- a/src/mqtt_packet.c +++ b/src/mqtt_packet.c @@ -1767,6 +1767,11 @@ int MqttEncode_ConnectAck(byte *tx_buf, int tx_buf_len, return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_BAD_ARG); } + /* [MQTT-3.2.2-1] Connect Acknowledge Flags bits 1-7 are reserved (0) */ + if (connect_ack->flags & 0xFE) { + return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_BAD_ARG); + } + /* Determine packet length */ remain_len = 2; /* flags + return code */ #ifdef WOLFMQTT_V5 From 74c30c0cc33cc883cf07e98ad95fa944870b6793 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Thu, 9 Jul 2026 22:48:17 -0700 Subject: [PATCH 30/54] F-3629 - Require an Authentication Method property when encoding AUTH --- src/mqtt_packet.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/mqtt_packet.c b/src/mqtt_packet.c index 293acaa2b..5347905f9 100644 --- a/src/mqtt_packet.c +++ b/src/mqtt_packet.c @@ -3412,12 +3412,26 @@ int MqttEncode_Auth(byte *tx_buf, int tx_buf_len, MqttAuth *auth) int header_len, remain_len = 0; byte* tx_payload; int props_len = 0; + MqttProp* auth_prop; /* Validate required arguments */ if ((tx_buf == NULL) || (tx_buf_len <= 0) || (auth == NULL)) { return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_BAD_ARG); } + /* [MQTT-4.12] AUTH (REAUTH/CONT_AUTH) must carry an Authentication Method + * property; reject if the list is empty or lacks it. */ + if ((auth->reason_code == MQTT_REASON_CONT_AUTH) || + (auth->reason_code == MQTT_REASON_REAUTH)) { + auth_prop = auth->props; + while (auth_prop != NULL && auth_prop->type != MQTT_PROP_AUTH_METHOD) { + auth_prop = auth_prop->next; + } + if (auth_prop == NULL) { + return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_BAD_ARG); + } + } + /* Length of Reason Code */ remain_len++; From 5c80dc04044fbf5a400883d82d35e5dbd1286373 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Thu, 9 Jul 2026 22:48:34 -0700 Subject: [PATCH 31/54] F-2758 - Default v5 DISCONNECT reason code when remaining length is zero --- src/mqtt_packet.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/mqtt_packet.c b/src/mqtt_packet.c index 5347905f9..ebbb636a1 100644 --- a/src/mqtt_packet.c +++ b/src/mqtt_packet.c @@ -3400,6 +3400,10 @@ int MqttDecode_Disconnect(byte *rx_buf, int rx_buf_len, MqttDisconnect *disc) } } } + else { + /* [MQTT-3.14.2.1] Remaining Length < 1 means Normal Disconnection */ + disc->reason_code = MQTT_REASON_SUCCESS; + } (void)rx_payload; From b2f37144cbf5586a7c93acba97d7ddb01bcfdd64 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Thu, 9 Jul 2026 22:49:17 -0700 Subject: [PATCH 32/54] F-6048 - Validate v5 UNSUBACK reason codes --- src/mqtt_packet.c | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/mqtt_packet.c b/src/mqtt_packet.c index ebbb636a1..319a97547 100644 --- a/src/mqtt_packet.c +++ b/src/mqtt_packet.c @@ -250,6 +250,27 @@ int MqttPacket_SubAckReturnCodeValid(byte code, byte protocol_level) return 0; } +#ifdef WOLFMQTT_V5 +/* [MQTT-4.8.0-1] Validate a v5 UNSUBACK Reason Code against the fixed set in + * MQTT 5.0 section 3.11.3. Returns 1 if allowed, 0 if reserved. */ +static int MqttPacket_UnsubAckReasonCodeValid(byte code) +{ + switch (code) { + case MQTT_REASON_SUCCESS: /* 0x00 */ + case MQTT_REASON_NO_SUB_EXIST: /* 0x11 */ + case MQTT_REASON_UNSPECIFIED_ERR: /* 0x80 */ + case MQTT_REASON_IMPL_SPECIFIC_ERR: /* 0x83 */ + case MQTT_REASON_NOT_AUTHORIZED: /* 0x87 */ + case MQTT_REASON_TOPIC_FILTER_INVALID: /* 0x8F */ + case MQTT_REASON_PACKET_ID_IN_USE: /* 0x91 */ + return 1; + default: + break; + } + return 0; +} +#endif + /* Validate an MQTT Topic Filter against the syntax rules from * [MQTT-4.7.3-1] (minimum length one character), [MQTT-4.7.1-2] * (multi-level wildcard '#' must be either the whole filter or directly @@ -3027,6 +3048,7 @@ int MqttDecode_UnsubscribeAck(byte *rx_buf, int rx_buf_len, * here would push rx_payload past this packet and underflow * reason_code_count to a huge word16. */ byte* packet_end = &rx_buf[header_len + remain_len]; + word16 rc_idx; if (remain_len > MQTT_DATA_LEN_SIZE) { word32 props_len = 0; @@ -3063,6 +3085,20 @@ int MqttDecode_UnsubscribeAck(byte *rx_buf, int rx_buf_len, unsubscribe_ack->reason_codes = rx_payload; unsubscribe_ack->reason_code_count = (word16)(packet_end - rx_payload); + + /* [MQTT-4.8.0-1] Reject reserved UNSUBACK reason codes, mirroring + * the SUBACK path; free any decoded v5 props before returning. */ + for (rc_idx = 0; rc_idx < unsubscribe_ack->reason_code_count; + rc_idx++) { + if (!MqttPacket_UnsubAckReasonCodeValid( + unsubscribe_ack->reason_codes[rc_idx])) { + if (unsubscribe_ack->props != NULL) { + (void)MqttProps_Free(unsubscribe_ack->props); + unsubscribe_ack->props = NULL; + } + return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_MALFORMED_DATA); + } + } } #endif } From 4c2aad2abd113afc802e28cbfcce1158b07d46c4 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Thu, 9 Jul 2026 22:49:37 -0700 Subject: [PATCH 33/54] F-6228 - Validate v5 property values in MqttEncode_Props --- src/mqtt_packet.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/mqtt_packet.c b/src/mqtt_packet.c index 319a97547..f3659e298 100644 --- a/src/mqtt_packet.c +++ b/src/mqtt_packet.c @@ -760,6 +760,11 @@ int MqttEncode_Props(MqttPacketType packet, MqttProp* props, byte* buf) } case MQTT_DATA_TYPE_SHORT: { + /* [MQTT-3.3.2-7] A Topic Alias of 0 is a Protocol Error. */ + if (cur_prop->type == MQTT_PROP_TOPIC_ALIAS && + cur_prop->data_short == 0) { + return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_PROPERTY); + } tmp = MqttEncode_Num(buf, cur_prop->data_short); rc += tmp; if (buf != NULL) { @@ -789,6 +794,12 @@ int MqttEncode_Props(MqttPacketType packet, MqttProp* props, byte* buf) } case MQTT_DATA_TYPE_VAR_INT: { + /* [MQTT-3.8.2.1.2] A Subscription Identifier of 0 is a + * Protocol Error. */ + if (cur_prop->type == MQTT_PROP_SUBSCRIPTION_ID && + cur_prop->data_int == 0) { + return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_PROPERTY); + } tmp = MqttEncode_Vbi(buf, cur_prop->data_int); if (tmp < 0) { return tmp; From f001cf6ef459a94515d03563fe6de52964d7a7ac Mon Sep 17 00:00:00 2001 From: aidan garske Date: Thu, 9 Jul 2026 22:50:11 -0700 Subject: [PATCH 34/54] F-5507 - Validate topic filters in SUBSCRIBE and UNSUBSCRIBE encode --- src/mqtt_packet.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/mqtt_packet.c b/src/mqtt_packet.c index f3659e298..fad0b6fbe 100644 --- a/src/mqtt_packet.c +++ b/src/mqtt_packet.c @@ -2424,6 +2424,12 @@ int MqttEncode_Subscribe(byte *tx_buf, int tx_buf_len, if (topic->qos > MQTT_QOS_2) { return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_BAD_ARG); } + /* Reject filters the decoder would treat as malformed so the + * encoder cannot emit an undecodable SUBSCRIBE [MQTT-4.7]. */ + if (!MqttPacket_TopicFilterValid(topic->topic_filter, + (word16)str_len)) { + return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_MALFORMED_DATA); + } remain_len += (int)str_len + MQTT_DATA_LEN_SIZE; remain_len++; /* For QoS */ } @@ -2811,6 +2817,12 @@ int MqttEncode_Unsubscribe(byte *tx_buf, int tx_buf_len, if (str_len > (size_t)0xFFFF) { return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_BAD_ARG); } + /* Reject filters the decoder would treat as malformed so the + * encoder cannot emit an undecodable UNSUBSCRIBE [MQTT-4.7]. */ + if (!MqttPacket_TopicFilterValid(topic->topic_filter, + (word16)str_len)) { + return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_MALFORMED_DATA); + } remain_len += (int)str_len + MQTT_DATA_LEN_SIZE; } else { From bd1612d6ed4af07513defd35806e04ac78739a73 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Thu, 9 Jul 2026 22:50:44 -0700 Subject: [PATCH 35/54] F-5667 - Reject v5 request-info property values above 1 --- src/mqtt_packet.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/mqtt_packet.c b/src/mqtt_packet.c index fad0b6fbe..602cb61a3 100644 --- a/src/mqtt_packet.c +++ b/src/mqtt_packet.c @@ -955,6 +955,13 @@ int MqttDecode_Props(MqttPacketType packet, MqttProp** props, byte* pbuf, tmp++; total++; prop_len--; + /* [MQTT-3.1.2-28/29] Request Response/Problem Information + * MUST be 0 or 1; any other value is a Protocol Error. */ + if ((cur_prop->type == MQTT_PROP_REQ_RESP_INFO || + cur_prop->type == MQTT_PROP_REQ_PROB_INFO) && + cur_prop->data_byte > 1) { + rc = MQTT_TRACE_ERROR(MQTT_CODE_ERROR_PROPERTY); + } break; } case MQTT_DATA_TYPE_SHORT: From df9001beb1355b10f2cddd2002d489bc9356859b Mon Sep 17 00:00:00 2001 From: aidan garske Date: Thu, 9 Jul 2026 22:51:50 -0700 Subject: [PATCH 36/54] F-6652 - Reject an empty v5 Topic Name without a Topic Alias --- src/mqtt_packet.c | 20 ++++++++++++++++++++ tests/test_mqtt_packet.c | 23 +++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/src/mqtt_packet.c b/src/mqtt_packet.c index 602cb61a3..1c6c919b1 100644 --- a/src/mqtt_packet.c +++ b/src/mqtt_packet.c @@ -1898,6 +1898,10 @@ int MqttEncode_Publish(byte *tx_buf, int tx_buf_len, MqttPublish *publish, { size_t str_len; byte level = 0; + #ifdef WOLFMQTT_V5 + MqttProp* prop_iter; + int has_topic_alias = 0; + #endif if (publish->topic_name == NULL) { return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_BAD_ARG); } @@ -1915,6 +1919,22 @@ int MqttEncode_Publish(byte *tx_buf, int tx_buf_len, MqttPublish *publish, (word16)str_len, level)) { return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_MALFORMED_DATA); } + #ifdef WOLFMQTT_V5 + /* [MQTT-3.3.2-8] An empty v5 Topic Name requires a Topic Alias so the + * decoder (and any conformant broker) can resolve the topic. */ + if (level >= MQTT_CONNECT_PROTOCOL_LEVEL_5 && str_len == 0) { + for (prop_iter = publish->props; prop_iter != NULL; + prop_iter = prop_iter->next) { + if (prop_iter->type == MQTT_PROP_TOPIC_ALIAS) { + has_topic_alias = 1; + break; + } + } + if (!has_topic_alias) { + return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_BAD_ARG); + } + } + #endif /* Determine packet length */ variable_len = (int)str_len + MQTT_DATA_LEN_SIZE; diff --git a/tests/test_mqtt_packet.c b/tests/test_mqtt_packet.c index 18245ccf7..7bb1ce2d1 100644 --- a/tests/test_mqtt_packet.c +++ b/tests/test_mqtt_packet.c @@ -1053,15 +1053,37 @@ TEST(encode_publish_v5_empty_topic_accepted) { byte tx_buf[64]; MqttPublish pub; + MqttProp prop; int rc; + /* An empty v5 Topic Name is only valid when a Topic Alias is supplied; + * the encoder now mirrors the decoder and rejects it otherwise. */ XMEMSET(&pub, 0, sizeof(pub)); + XMEMSET(&prop, 0, sizeof(prop)); + prop.type = MQTT_PROP_TOPIC_ALIAS; + prop.data_short = 1; + prop.next = NULL; pub.protocol_level = MQTT_CONNECT_PROTOCOL_LEVEL_5; pub.topic_name = ""; pub.qos = MQTT_QOS_0; + pub.props = ∝ rc = MqttEncode_Publish(tx_buf, (int)sizeof(tx_buf), &pub, 0); ASSERT_TRUE(rc > 0); } + +TEST(encode_publish_v5_empty_topic_no_alias_rejected) +{ + byte tx_buf[64]; + MqttPublish pub; + int rc; + + XMEMSET(&pub, 0, sizeof(pub)); + pub.protocol_level = MQTT_CONNECT_PROTOCOL_LEVEL_5; + pub.topic_name = ""; + pub.qos = MQTT_QOS_0; + rc = MqttEncode_Publish(tx_buf, (int)sizeof(tx_buf), &pub, 0); + ASSERT_TRUE(rc < 0); +} #endif /* WOLFMQTT_V5 */ /* ============================================================================ @@ -4891,6 +4913,7 @@ void run_mqtt_packet_tests(void) RUN_TEST(encode_publish_null_topic_returns_bad_arg); #ifdef WOLFMQTT_V5 RUN_TEST(encode_publish_v5_empty_topic_accepted); + RUN_TEST(encode_publish_v5_empty_topic_no_alias_rejected); #endif /* MqttDecode_Publish */ From 6f36226a356e91067ffb120fc71144c9797d54a3 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Thu, 9 Jul 2026 22:52:01 -0700 Subject: [PATCH 37/54] F-6218 - Treat retain as truthy in the retain-availability guard --- src/mqtt_client.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mqtt_client.c b/src/mqtt_client.c index 56c397cbd..52dcbb392 100644 --- a/src/mqtt_client.c +++ b/src/mqtt_client.c @@ -2235,7 +2235,7 @@ static int MqttPublishMsg(MqttClient *client, MqttPublish *publish, /* Validate publish request against server properties */ if ((publish->qos > client->max_qos) || - ((publish->retain == 1) && (client->retain_avail == 0))) + ((publish->retain != 0) && (client->retain_avail == 0))) { return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_SERVER_PROP); } From 75bb05fd84e605b4a67b8fe0873e26478a17a296 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Thu, 9 Jul 2026 22:54:18 -0700 Subject: [PATCH 38/54] F-3139 - Guard property length underflow in MqttDecode_Props --- src/mqtt_packet.c | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/mqtt_packet.c b/src/mqtt_packet.c index 1c6c919b1..06da95205 100644 --- a/src/mqtt_packet.c +++ b/src/mqtt_packet.c @@ -976,6 +976,10 @@ int MqttDecode_Props(MqttPacketType packet, MqttProp** props, byte* pbuf, rc = tmp; break; } + if ((word32)tmp > prop_len) { + rc = MQTT_TRACE_ERROR(MQTT_CODE_ERROR_MALFORMED_DATA); + break; + } buf += tmp; total += tmp; prop_len -= (word32)tmp; @@ -1021,6 +1025,10 @@ int MqttDecode_Props(MqttPacketType packet, MqttProp** props, byte* pbuf, rc = tmp; } else if ((word32)tmp <= (buf_len - (buf - pbuf))) { + if ((word32)tmp > prop_len) { + rc = MQTT_TRACE_ERROR(MQTT_CODE_ERROR_MALFORMED_DATA); + break; + } buf += tmp; total += tmp; prop_len -= (word32)tmp; @@ -1051,6 +1059,10 @@ int MqttDecode_Props(MqttPacketType packet, MqttProp** props, byte* pbuf, rc = tmp; break; } + if ((word32)tmp > prop_len) { + rc = MQTT_TRACE_ERROR(MQTT_CODE_ERROR_MALFORMED_DATA); + break; + } buf += tmp; total += tmp; prop_len -= (word32)tmp; @@ -1072,11 +1084,19 @@ int MqttDecode_Props(MqttPacketType packet, MqttProp** props, byte* pbuf, rc = tmp; break; } + if ((word32)tmp > prop_len) { + rc = MQTT_TRACE_ERROR(MQTT_CODE_ERROR_MALFORMED_DATA); + break; + } buf += tmp; total += tmp; prop_len -= tmp; if (cur_prop->data_bin.len <= (buf_len - (buf - pbuf))) { + if (cur_prop->data_bin.len > prop_len) { + rc = MQTT_TRACE_ERROR(MQTT_CODE_ERROR_MALFORMED_DATA); + break; + } cur_prop->data_bin.data = buf; buf += cur_prop->data_bin.len; total += (int)cur_prop->data_bin.len; @@ -1100,6 +1120,10 @@ int MqttDecode_Props(MqttPacketType packet, MqttProp** props, byte* pbuf, rc = tmp; } else if ((word32)tmp <= (buf_len - (buf - pbuf))) { + if ((word32)tmp > prop_len) { + rc = MQTT_TRACE_ERROR(MQTT_CODE_ERROR_MALFORMED_DATA); + break; + } buf += tmp; total += tmp; prop_len -= (word32)tmp; @@ -1113,6 +1137,11 @@ int MqttDecode_Props(MqttPacketType packet, MqttProp** props, byte* pbuf, } else if ((word32)tmp <= (buf_len - (buf - pbuf))) { + if ((word32)tmp > prop_len) { + rc = MQTT_TRACE_ERROR( + MQTT_CODE_ERROR_MALFORMED_DATA); + break; + } buf += tmp; total += tmp; prop_len -= (word32)tmp; From b396b96af019718eb36a05464f6cae5d885e2c65 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Thu, 9 Jul 2026 22:54:55 -0700 Subject: [PATCH 39/54] F-3824 - Reject an over-long remaining-length VBI without an extra socket read --- src/mqtt_packet.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/mqtt_packet.c b/src/mqtt_packet.c index 06da95205..4c7a51484 100644 --- a/src/mqtt_packet.c +++ b/src/mqtt_packet.c @@ -3913,6 +3913,14 @@ int MqttPacket_Read(MqttClient *client, byte* rx_buf, int rx_buf_len, break; } + /* [MQTT-1.5.5-1] A VBI is at most 4 bytes: if the last allowed + * byte still has the continuation bit set, reject without + * reading a spurious 5th byte from the socket. */ + if (i == MQTT_PACKET_MAX_LEN_BYTES - 1) { + return MqttPacket_HandleNetError(client, + MQTT_TRACE_ERROR(MQTT_CODE_ERROR_MALFORMED_DATA)); + } + /* Read next byte and try decode again */ len = 1; rc = MqttSocket_Read(client, &rx_buf[client->packet.header_len], From 1bcd0bbc9ffc40ff396cd574de2bce2fbad6744a Mon Sep 17 00:00:00 2001 From: aidan garske Date: Thu, 9 Jul 2026 22:55:31 -0700 Subject: [PATCH 40/54] F-2759 - Compare broker credentials with a bounded constant-time length --- src/mqtt_broker.c | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/src/mqtt_broker.c b/src/mqtt_broker.c index 5a32b0469..058ba7036 100644 --- a/src/mqtt_broker.c +++ b/src/mqtt_broker.c @@ -245,13 +245,36 @@ static int BrokerBufCompare(const byte* a, int len_a, return result; } +/* Length of a NUL-terminated string bounded to max_len, computed with a + * fixed number of iterations so an attacker cannot infer a server secret's + * length from timing. Never reads past the terminating NUL (the read index + * is clamped once NUL is seen), so it is safe on strings shorter than + * max_len. Returns max_len for strings that reach max_len without a NUL. */ +static int BrokerStrLenBounded(const char* s, int max_len) +{ + int len = 0; + int idx = 0; + int done = 0; + int i; + for (i = 0; i < max_len; i++) { + int atNul = ((byte)s[idx] == 0); + done |= atNul; + len += (done == 0); + idx += (done == 0); + } + return len; +} + /* Constant-time C-string comparison wrapper. Both inputs are assumed to * be NUL-terminated UTF-8 (e.g. configured username, decoded username - * field which is rejected if it contains U+0000). */ + * field which is rejected if it contains U+0000). Lengths are computed with + * BrokerStrLenBounded so the server secret's length is not leaked by a + * variable-time XSTRLEN. */ static int BrokerStrCompare(const char* a, const char* b, int cmp_len) { - return BrokerBufCompare((const byte*)a, (int)XSTRLEN(a), - (const byte*)b, (int)XSTRLEN(b), cmp_len); + return BrokerBufCompare((const byte*)a, BrokerStrLenBounded(a, cmp_len), + (const byte*)b, BrokerStrLenBounded(b, cmp_len), + cmp_len); } #endif /* WOLFMQTT_BROKER_AUTH */ @@ -4616,7 +4639,7 @@ static int BrokerHandle_Connect(BrokerClient* bc, int rx_len, #endif bc->password_len == 0 || BrokerBufCompare((const byte*)broker->auth_pass, - (int)XSTRLEN(broker->auth_pass), + BrokerStrLenBounded(broker->auth_pass, BROKER_MAX_PASSWORD_LEN), (const byte*)bc->password, (int)bc->password_len, BROKER_MAX_PASSWORD_LEN) != 0)) { auth_ok = 0; From 455707f0eeff783d0676d362f3d99fb1b50582a9 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Thu, 9 Jul 2026 22:56:06 -0700 Subject: [PATCH 41/54] F-4306 - Return Packet-Identifier-Not-Found for untracked QoS2 PUBREL --- src/mqtt_broker.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/mqtt_broker.c b/src/mqtt_broker.c index 058ba7036..3e6114a76 100644 --- a/src/mqtt_broker.c +++ b/src/mqtt_broker.c @@ -5604,6 +5604,9 @@ static int BrokerHandle_PublishRel(BrokerClient* bc, int rx_len) { int rc; MqttPublishResp resp; +#ifdef WOLFMQTT_V5 + int was_tracked; +#endif XMEMSET(&resp, 0, sizeof(resp)); #ifdef WOLFMQTT_V5 @@ -5617,6 +5620,9 @@ static int BrokerHandle_PublishRel(BrokerClient* bc, int rx_len) return rc; } +#ifdef WOLFMQTT_V5 + was_tracked = BrokerInboundQos2_Contains(bc, resp.packet_id); +#endif /* [MQTT-4.3.3] QoS 2 step 3: discard the stored Packet Identifier so a * later PUBLISH with the same ID is treated as a fresh delivery. PUBREL * for an unknown ID is idempotent - we still PUBCOMP it. */ @@ -5626,7 +5632,9 @@ static int BrokerHandle_PublishRel(BrokerClient* bc, int rx_len) if (resp.props) { (void)MqttProps_Free(resp.props); } - resp.reason_code = MQTT_REASON_SUCCESS; + /* [MQTT-3.7.2.1] Report 0x92 when the broker had no record of this ID. */ + resp.reason_code = was_tracked ? + MQTT_REASON_SUCCESS : MQTT_REASON_PACKET_ID_NOT_FOUND; resp.props = NULL; #endif rc = MqttEncode_PublishResp(bc->tx_buf, BROKER_CLIENT_TX_SZ(bc), From ad5b0b3ab99e6cbfb99245d350692f4e528ee048 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Thu, 9 Jul 2026 22:56:34 -0700 Subject: [PATCH 42/54] F-4519 - Deliver retained messages using the full subscription filter --- src/mqtt_broker.c | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/src/mqtt_broker.c b/src/mqtt_broker.c index 3e6114a76..9f555ef32 100644 --- a/src/mqtt_broker.c +++ b/src/mqtt_broker.c @@ -5049,16 +5049,32 @@ static int BrokerHandle_Subscribe(BrokerClient* bc, int rx_len, } #ifdef WOLFMQTT_BROKER_RETAINED else { - /* Deliver retained messages matching this filter */ + /* Deliver retained messages matching this filter. */ char filter_z[BROKER_MAX_FILTER_LEN]; word16 copy_len = flen; - if (copy_len >= BROKER_MAX_FILTER_LEN) { - copy_len = BROKER_MAX_FILTER_LEN - 1; + #ifndef WOLFMQTT_STATIC_MEMORY + /* Dynamic builds store filters longer than the stack buffer in + * full; use a heap copy so retained matching uses the same + * filter as future PUBLISH fan-out instead of a truncation. + * Static builds reject over-length filters at subscribe time. */ + if (flen >= BROKER_MAX_FILTER_LEN) { + char* filter_dyn = (char*)WOLFMQTT_MALLOC(flen + 1); + if (filter_dyn != NULL) { + XMEMCPY(filter_dyn, f, flen); + filter_dyn[flen] = '\0'; + BrokerRetained_DeliverToClient(broker, bc, filter_dyn, + topic_qos); + WOLFMQTT_FREE(filter_dyn); + } + } + else + #endif + if (copy_len < BROKER_MAX_FILTER_LEN) { + XMEMCPY(filter_z, f, copy_len); + filter_z[copy_len] = '\0'; + BrokerRetained_DeliverToClient(broker, bc, filter_z, + topic_qos); } - XMEMCPY(filter_z, f, copy_len); - filter_z[copy_len] = '\0'; - BrokerRetained_DeliverToClient(broker, bc, filter_z, - topic_qos); } #endif } From fbfae69d1d876e752611e73b33022c2a15dc2389 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Thu, 9 Jul 2026 22:57:29 -0700 Subject: [PATCH 43/54] F-5666 - Force-zero tx_buf after encoding v5 DISCONNECT --- src/mqtt_client.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/mqtt_client.c b/src/mqtt_client.c index 52dcbb392..a3a43f0a3 100644 --- a/src/mqtt_client.c +++ b/src/mqtt_client.c @@ -2833,6 +2833,10 @@ int MqttClient_Disconnect_ex(MqttClient *client, MqttDisconnect *p_disconnect) MQTT_PACKET_TYPE_DISCONNECT, 0, 0); #endif if (rc <= 0) { + /* Encode failed: tx_buf may hold partial v5 DISCONNECT property + * data. Zero the full buffer before MqttWriteStop releases + * lockSend so no other thread can see residual data. */ + CLIENT_FORCE_ZERO(client->tx_buf, client->tx_buf_len); MqttWriteStop(client, &disconnect->stat); return rc; } @@ -2851,10 +2855,15 @@ int MqttClient_Disconnect_ex(MqttClient *client, MqttDisconnect *p_disconnect) && client->write.total > 0 #endif ) { - /* keep send locked and return early */ + /* keep send locked and return early (tx_buf still holds property + * data until the write completes) */ return rc; } #endif + /* Clear tx_buf to remove any v5 DISCONNECT property data BEFORE + * MqttWriteStop releases lockSend, so another thread cannot race in and + * populate tx_buf before it is scrubbed. */ + CLIENT_FORCE_ZERO(client->tx_buf, xfer); MqttWriteStop(client, &disconnect->stat); if (rc == xfer) { rc = MQTT_CODE_SUCCESS; From 0a658514ea1203e0228c30b6803611de6ac53b55 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Thu, 9 Jul 2026 22:57:51 -0700 Subject: [PATCH 44/54] F-5504 - Scrub tx_buf in MqttClient_CancelMessage --- src/mqtt_client.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/mqtt_client.c b/src/mqtt_client.c index a3a43f0a3..46a3fbf57 100644 --- a/src/mqtt_client.c +++ b/src/mqtt_client.c @@ -3108,6 +3108,10 @@ int MqttClient_CancelMessage(MqttClient *client, MqttObject* msg) #ifdef WOLFMQTT_DEBUG_CLIENT PRINTF("Cancel Write Lock"); #endif + /* An abandoned write (e.g. a partial nonblocking CONNECT) leaves the + * encoded packet - possibly plaintext credentials - in tx_buf. Scrub + * it before MqttWriteStop releases lockSend. */ + CLIENT_FORCE_ZERO(client->tx_buf, client->tx_buf_len); MqttWriteStop(client, mms_stat); } From 06be735031223219a2bb15dcc6631580711eff21 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Thu, 9 Jul 2026 22:58:22 -0700 Subject: [PATCH 45/54] F-2361 - Decode fixed-header remaining length via a typed word32 temporary --- src/mqtt_packet.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/mqtt_packet.c b/src/mqtt_packet.c index 4c7a51484..81abf9aba 100644 --- a/src/mqtt_packet.c +++ b/src/mqtt_packet.c @@ -388,6 +388,7 @@ static int MqttDecode_FixedHeader(byte *rx_buf, int rx_buf_len, int *remain_len, byte type, MqttQoS *p_qos, byte *p_retain, byte *p_duplicate) { int header_len; + word32 remain_len_u32 = 0; MqttPacket* header = (MqttPacket*)rx_buf; /* Every MQTT packet is at least 2 bytes: the type/flags byte plus at @@ -398,11 +399,15 @@ static int MqttDecode_FixedHeader(byte *rx_buf, int rx_buf_len, int *remain_len, if (rx_buf_len < 2) { return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_OUT_OF_BUFFER); } - header_len = MqttDecode_Vbi(header->len, (word32*)remain_len, + header_len = MqttDecode_Vbi(header->len, &remain_len_u32, (word32)(rx_buf_len - 1)); if (header_len < 0) { return header_len; } + if (remain_len_u32 > MQTT_PACKET_MAX_REMAIN_LEN) { + return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_MALFORMED_DATA); + } + *remain_len = (int)remain_len_u32; /* Validate packet type */ if (MQTT_PACKET_TYPE_GET(header->type_flags) != type) { From 763e3ba24cfbb304b15089808b7f1fe680ea8553 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Thu, 9 Jul 2026 22:58:58 -0700 Subject: [PATCH 46/54] F-2364 - Decode packet-read remaining length via a typed word32 temporary --- src/mqtt_packet.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/mqtt_packet.c b/src/mqtt_packet.c index 81abf9aba..f0820a7c8 100644 --- a/src/mqtt_packet.c +++ b/src/mqtt_packet.c @@ -3907,6 +3907,7 @@ int MqttPacket_Read(MqttClient *client, byte* rx_buf, int rx_buf_len, case MQTT_PK_READ_HEAD: { int i; + word32 remain_len_u32 = 0; client->packet.stat = MQTT_PK_READ_HEAD; for (i = (client->packet.header_len - MQTT_PACKET_HEADER_MIN_SIZE); @@ -3950,13 +3951,18 @@ int MqttPacket_Read(MqttClient *client, byte* rx_buf, int rx_buf_len, return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_OUT_OF_BUFFER); } rc = MqttDecode_Vbi(header->len, - (word32*)&client->packet.remain_len, + &remain_len_u32, rx_buf_len - (client->packet.header_len - (i + 1))); if (rc < 0) { /* Indicates error */ return MqttPacket_HandleNetError(client, rc); } /* Indicates decode success and rc is len of header */ else { + if (remain_len_u32 > MQTT_PACKET_MAX_REMAIN_LEN) { + return MqttPacket_HandleNetError(client, + MQTT_TRACE_ERROR(MQTT_CODE_ERROR_MALFORMED_DATA)); + } + client->packet.remain_len = (int)remain_len_u32; /* Add size of type and flags */ rc += sizeof(header->type_flags); client->packet.header_len = rc; From 6e24875efb8e9be1511f6743f02923301a3b3d47 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Thu, 9 Jul 2026 22:59:33 -0700 Subject: [PATCH 47/54] F-2365 - Decode property identifier via a typed word32 temporary --- src/mqtt_packet.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/mqtt_packet.c b/src/mqtt_packet.c index f0820a7c8..2cbe9b09a 100644 --- a/src/mqtt_packet.c +++ b/src/mqtt_packet.c @@ -877,6 +877,7 @@ int MqttDecode_Props(MqttPacketType packet, MqttProp** props, byte* pbuf, int total, tmp; int prop_count = 0; word32 seen_lo = 0, seen_hi = 0; /* singleton-property duplicate guard */ + word32 prop_type; MqttProp* cur_prop; byte* buf = pbuf; @@ -903,11 +904,13 @@ int MqttDecode_Props(MqttPacketType packet, MqttProp** props, byte* pbuf, break; } /* Decode the Identifier */ - rc = MqttDecode_Vbi(buf, (word32*)&cur_prop->type, + prop_type = 0; + rc = MqttDecode_Vbi(buf, &prop_type, (word32)(buf_len - (buf - pbuf))); if (rc < 0) { break; } + cur_prop->type = (MqttPropertyType)prop_type; tmp = rc; buf += tmp; total += tmp; From 32b8a5ea0269333c3be681cfe3f0d3836f0bccb0 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Thu, 9 Jul 2026 22:59:52 -0700 Subject: [PATCH 48/54] F-4521 - Reject a negative cmd_timeout_ms in MqttClient_Init --- src/mqtt_client.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/mqtt_client.c b/src/mqtt_client.c index 46a3fbf57..74c1723ea 100644 --- a/src/mqtt_client.c +++ b/src/mqtt_client.c @@ -1674,7 +1674,8 @@ int MqttClient_Init(MqttClient *client, MqttNet* net, /* Check arguments */ if (client == NULL || tx_buf == NULL || tx_buf_len <= 0 || - rx_buf == NULL || rx_buf_len <= 0) { + rx_buf == NULL || rx_buf_len <= 0 || + cmd_timeout_ms < 0) { return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_BAD_ARG); } From 6f846d922f62c526dd15fd1c72d581d1507bb846 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Thu, 9 Jul 2026 23:00:14 -0700 Subject: [PATCH 49/54] F-4520 - Emit the plaintext-credentials warning in production builds --- src/mqtt_client.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mqtt_client.c b/src/mqtt_client.c index 74c1723ea..7d9f557ac 100644 --- a/src/mqtt_client.c +++ b/src/mqtt_client.c @@ -1783,8 +1783,8 @@ int MqttClient_Connect(MqttClient *client, MqttConnect *mc_connect) } if (mc_connect->stat.write == MQTT_MSG_BEGIN) { - #ifdef DEBUG_WOLFMQTT /* Warn if credentials are being sent without TLS */ + #ifdef WOLFMQTT_DEBUG_CLIENT if ((mc_connect->username != NULL || mc_connect->password != NULL) && !(MqttClient_Flags(client, 0, 0) & MQTT_CLIENT_FLAG_IS_TLS)) { PRINTF("Warning: MQTT credentials are being sent without TLS"); From 0c14280d698a385c4ebdc71a7c74fe7726e9b4d9 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Thu, 9 Jul 2026 23:01:23 -0700 Subject: [PATCH 50/54] F-6040 - Reject a SUBACK whose return-code count differs from topic count --- src/mqtt_client.c | 23 +++++++++++++++-------- src/mqtt_packet.c | 2 ++ tests/test_mqtt_packet.c | 1 + wolfmqtt/mqtt_packet.h | 1 + 4 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/mqtt_client.c b/src/mqtt_client.c index 7d9f557ac..1b8a91b49 100644 --- a/src/mqtt_client.c +++ b/src/mqtt_client.c @@ -2553,15 +2553,22 @@ int MqttClient_Subscribe(MqttClient *client, MqttSubscribe *subscribe) * that filter. */ if (rc == MQTT_CODE_SUCCESS) { byte any_rejected = 0; - for (i = 0; i < subscribe->topic_count && i < MAX_MQTT_TOPICS; i++) { - topic = &subscribe->topics[i]; - topic->return_code = subscribe->ack.return_codes[i]; - if (topic->return_code & MQTT_SUBSCRIBE_ACK_CODE_FAILURE) { - any_rejected = 1; - } + /* [MQTT-3.9.3-1] a SUBACK carries exactly one reason code per + * requested topic; too few would be read as granted QoS 0 (fail-open). */ + if (subscribe->ack.return_code_count != subscribe->topic_count) { + rc = MQTT_TRACE_ERROR(MQTT_CODE_ERROR_MALFORMED_DATA); } - if (any_rejected) { - rc = MQTT_TRACE_ERROR(MQTT_CODE_ERROR_SUBSCRIBE_REJECTED); + else { + for (i = 0; i < subscribe->topic_count && i < MAX_MQTT_TOPICS; i++) { + topic = &subscribe->topics[i]; + topic->return_code = subscribe->ack.return_codes[i]; + if (topic->return_code & MQTT_SUBSCRIBE_ACK_CODE_FAILURE) { + any_rejected = 1; + } + } + if (any_rejected) { + rc = MQTT_TRACE_ERROR(MQTT_CODE_ERROR_SUBSCRIBE_REJECTED); + } } } diff --git a/src/mqtt_packet.c b/src/mqtt_packet.c index 2cbe9b09a..1f44a808e 100644 --- a/src/mqtt_packet.c +++ b/src/mqtt_packet.c @@ -2815,6 +2815,8 @@ int MqttDecode_SubscribeAck(byte* rx_buf, int rx_buf_len, if (payload_len > buf_remain) { return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_OUT_OF_BUFFER); } + subscribe_ack->return_code_count = + (word16)((payload_len > 0xFFFF) ? 0xFFFF : payload_len); if (payload_len > MAX_MQTT_TOPICS) payload_len = MAX_MQTT_TOPICS; #ifdef WOLFMQTT_V5 diff --git a/tests/test_mqtt_packet.c b/tests/test_mqtt_packet.c index 7bb1ce2d1..ff63f828f 100644 --- a/tests/test_mqtt_packet.c +++ b/tests/test_mqtt_packet.c @@ -3700,6 +3700,7 @@ TEST(decode_suback_multiple_return_codes) ASSERT_EQ(MQTT_QOS_1, ack.return_codes[0]); ASSERT_EQ(MQTT_QOS_2, ack.return_codes[1]); ASSERT_EQ(MQTT_SUBSCRIBE_ACK_CODE_FAILURE, ack.return_codes[2]); + ASSERT_EQ(3, ack.return_code_count); } TEST(decode_suback_malformed_remain_len_zero) diff --git a/wolfmqtt/mqtt_packet.h b/wolfmqtt/mqtt_packet.h index fdda86772..04452d967 100644 --- a/wolfmqtt/mqtt_packet.h +++ b/wolfmqtt/mqtt_packet.h @@ -547,6 +547,7 @@ typedef struct _MqttSubscribeAck { word16 packet_id; byte return_codes[MAX_MQTT_TOPICS]; + word16 return_code_count; /* number of return codes the broker sent */ #ifdef WOLFMQTT_V5 MqttProp* props; byte protocol_level; From 539709a6010087f902158433b50300cadd5bd5a4 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Thu, 9 Jul 2026 23:01:51 -0700 Subject: [PATCH 51/54] F-6041 - Reject a publish with buffer set but zero total length --- src/mqtt_packet.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/mqtt_packet.c b/src/mqtt_packet.c index 1f44a808e..2da2d7696 100644 --- a/src/mqtt_packet.c +++ b/src/mqtt_packet.c @@ -2009,6 +2009,15 @@ int MqttEncode_Publish(byte *tx_buf, int tx_buf_len, MqttPublish *publish, } #endif + /* Reject inconsistent length fields: a non-empty payload buffer paired + * with a zero total_len would silently emit an empty PUBLISH (which on a + * retained topic deletes the stored message). An intentional empty + * publish leaves buffer NULL or buffer_len 0. */ + if ((publish->buffer != NULL) && (publish->buffer_len > 0) && + (publish->total_len == 0)) { + return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_BAD_ARG); + } + if (((publish->buffer != NULL) || (use_cb == 1)) && (publish->total_len > 0)) { payload_len = publish->total_len; From cead46f287dac09636fab973c2869f2054a3c37c Mon Sep 17 00:00:00 2001 From: aidan garske Date: Thu, 9 Jul 2026 23:03:15 -0700 Subject: [PATCH 52/54] F-5772 - Stream signed firmware from memory to close fwpush reopen TOCTOU --- examples/firmware/fwpush.c | 73 +++++++++++++++++++++++--------------- examples/firmware/fwpush.h | 4 +++ 2 files changed, 49 insertions(+), 28 deletions(-) diff --git a/examples/firmware/fwpush.c b/examples/firmware/fwpush.c index 31c09b26b..a4caebbc1 100644 --- a/examples/firmware/fwpush.c +++ b/examples/firmware/fwpush.c @@ -84,51 +84,58 @@ static int mqtt_message_cb(MqttClient *client, MqttMessage *msg, static int mqtt_publish_cb(MqttPublish *publish) { int ret = -1; #if !defined(NO_FILESYSTEM) - size_t bytes_read; FwpushCBdata *cbData; FirmwareHeader *header; word32 headerSize; + int avail, chunk; /* Structure was stored in ctx pointer */ if (publish != NULL) { cbData = (FwpushCBdata*)publish->ctx; - if (cbData != NULL) { + if (cbData != NULL && cbData->fwBuf != NULL) { header = (FirmwareHeader*)cbData->data; + /* Guard a corrupt offset so avail/chunk can never go negative and + * feed a negative length back into the publish state machine. */ + if (cbData->fwOffset < 0 || cbData->fwOffset > cbData->fwLen) { + return -1; + } + avail = cbData->fwLen - cbData->fwOffset; - /* Check for first iteration of callback */ - if (cbData->fp == NULL) { - /* Get FW size from FW header struct */ + /* Stream from the in-memory signed firmware rather than reopening + * the file, so the bytes sent are exactly the bytes that were + * signed (no file-swap TOCTOU between build and send). */ + if (!cbData->headerSent) { headerSize = sizeof(FirmwareHeader) + header->sigLen + header->pubKeyLen; if (headerSize > publish->buffer_len) { PRINTF("Error: Firmware Header %d larger than max buffer %d", - headerSize, publish->buffer_len); + (int)headerSize, publish->buffer_len); return -1; } - - /* Copy header to buffer */ XMEMCPY(publish->buffer, header, headerSize); - - /* Open file */ - cbData->fp = fopen(cbData->filename, "rb"); - if (cbData->fp != NULL) { - /* read a buffer of data from the file */ - bytes_read = fread(&publish->buffer[headerSize], - 1, publish->buffer_len - headerSize, cbData->fp); - if (bytes_read != 0) { - ret = (int)bytes_read + headerSize; - } + chunk = publish->buffer_len - (int)headerSize; + if (chunk > avail) { + chunk = avail; } + if (chunk > 0) { + XMEMCPY(&publish->buffer[headerSize], + &cbData->fwBuf[cbData->fwOffset], chunk); + cbData->fwOffset += chunk; + } + cbData->headerSent = 1; + ret = (int)headerSize + chunk; } else { - /* read a buffer of data from the file */ - bytes_read = fread(publish->buffer, 1, publish->buffer_len, - cbData->fp); - ret = (int)bytes_read; - } - if (cbData->fp && feof(cbData->fp)) { - fclose(cbData->fp); - cbData->fp = NULL; + chunk = publish->buffer_len; + if (chunk > avail) { + chunk = avail; + } + if (chunk > 0) { + XMEMCPY(publish->buffer, + &cbData->fwBuf[cbData->fwOffset], chunk); + cbData->fwOffset += chunk; + } + ret = chunk; } } } @@ -139,7 +146,7 @@ static int mqtt_publish_cb(MqttPublish *publish) { } static int fw_message_build(MQTTCtx *mqttCtx, const char* fwFile, - byte **p_msgBuf, int *p_msgLen) + byte **p_msgBuf, int *p_msgLen, byte **p_fwBuf, int *p_fwLen) { int rc; byte *msgBuf = NULL, *sigBuf = NULL, *keyBuf = NULL, *fwBuf = NULL; @@ -265,6 +272,14 @@ static int fw_message_build(MQTTCtx *mqttCtx, const char* fwFile, } if (p_msgLen) *p_msgLen = msgLen; + + /* Transfer ownership of the signed firmware buffer so the callback + * streams the exact signed bytes instead of re-reading the file. */ + if (p_fwBuf) { + *p_fwBuf = fwBuf; + fwBuf = NULL; + } + if (p_fwLen) *p_fwLen = fwLen; } else { if (msgBuf) WOLFMQTT_FREE(msgBuf); @@ -445,7 +460,8 @@ int fwpush_test(MQTTCtx *mqttCtx) cbData->filename = mqttCtx->pub_file; rc = fw_message_build(mqttCtx, cbData->filename, &cbData->data, - (int*)&mqttCtx->publish.total_len); + (int*)&mqttCtx->publish.total_len, + &cbData->fwBuf, &cbData->fwLen); /* The publish->ctx is available for use by the application to pass * data to the callback routine. */ @@ -539,6 +555,7 @@ int fwpush_test(MQTTCtx *mqttCtx) if (cbData) { if (cbData->fp) fclose(cbData->fp); if (cbData->data) WOLFMQTT_FREE(cbData->data); + if (cbData->fwBuf) WOLFMQTT_FREE(cbData->fwBuf); WOLFMQTT_FREE(cbData); } if (mqttCtx->publish.buffer) WOLFMQTT_FREE(mqttCtx->publish.buffer); diff --git a/examples/firmware/fwpush.h b/examples/firmware/fwpush.h index f837f9c26..b5898ac29 100644 --- a/examples/firmware/fwpush.h +++ b/examples/firmware/fwpush.h @@ -33,6 +33,10 @@ typedef struct FwpushCBdata_s { const char *filename; byte *data; FILE *fp; + byte *fwBuf; /* signed firmware held in memory, streamed by the callback */ + int fwLen; + int fwOffset; + int headerSent; } FwpushCBdata; /* Exposed functions */ From 7575b762de4ab8e94c154d3cf60e04b3a09322f4 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Fri, 10 Jul 2026 10:59:02 -0700 Subject: [PATCH 53/54] F-3396 - Encode AUTH with the Success reason code --- src/mqtt_packet.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/mqtt_packet.c b/src/mqtt_packet.c index 2da2d7696..8b5ea3156 100644 --- a/src/mqtt_packet.c +++ b/src/mqtt_packet.c @@ -3600,8 +3600,10 @@ int MqttEncode_Auth(byte *tx_buf, int tx_buf_len, MqttAuth *auth) } tx_payload = &tx_buf[header_len]; - /* Encode variable header */ - if ((auth->reason_code == MQTT_REASON_CONT_AUTH) || + /* Encode variable header. [MQTT-3.15.2.1] Success (0x00) is valid too and + * is accepted by MqttDecode_Auth, so encode it to keep the roundtrip. */ + if ((auth->reason_code == MQTT_REASON_SUCCESS) || + (auth->reason_code == MQTT_REASON_CONT_AUTH) || (auth->reason_code == MQTT_REASON_REAUTH)) { *tx_payload++ = auth->reason_code; From 13699b208e1aeb9c8398bc06972b78c330135107 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Fri, 10 Jul 2026 10:59:02 -0700 Subject: [PATCH 54/54] F-5770 - Guard firmware message length against integer overflow --- examples/firmware/fwpush.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/examples/firmware/fwpush.c b/examples/firmware/fwpush.c index a4caebbc1..841ea8865 100644 --- a/examples/firmware/fwpush.c +++ b/examples/firmware/fwpush.c @@ -50,6 +50,7 @@ #include #endif +#include #include "fwpush.h" #include "firmware.h" #include "examples/mqttexample.h" @@ -239,6 +240,12 @@ static int fw_message_build(MQTTCtx *mqttCtx, const char* fwFile, #endif /* Assemble message */ + /* guard the int msgLen sum against overflow on huge firmware */ + if (fwLen > INT_MAX - (int)(sizeof(FirmwareHeader) + sigLen + keyLen)) { + PRINTF("Firmware file too large: %d bytes", fwLen); + rc = EXIT_FAILURE; + goto exit; + } msgLen = sizeof(FirmwareHeader) + sigLen + keyLen + fwLen; /* The firmware will be copied by the callback */