@@ -616,6 +616,77 @@ TEST(decode_publish_v5_content_type_property)
616616
617617 MqttProps_Free (pub .props );
618618}
619+
620+ /* [MQTT-1.5.4-2]: an embedded NUL in a v5 STRING property must be
621+ * rejected. Uses a PUBLISH packet with a Content Type property whose
622+ * value contains 0x00. MqttDecode_Props propagates MALFORMED_DATA from
623+ * MqttDecode_String distinctly from generic MQTT_CODE_ERROR_PROPERTY. */
624+ TEST (decode_publish_v5_rejects_nul_in_string_property )
625+ {
626+ /* Wire: PUBLISH QoS 0, remain_len=13, topic "a/b", props_len=7,
627+ * prop 0x03 CONTENT_TYPE, str_len=4, "t\0xt". No payload. */
628+ byte buf [] = {
629+ 0x30 , 13 ,
630+ 0x00 , 0x03 , 'a' , '/' , 'b' ,
631+ 0x07 ,
632+ 0x03 , 0x00 , 0x04 , 't' , 0x00 , 'x' , 't'
633+ };
634+ MqttPublish pub ;
635+ int rc ;
636+
637+ XMEMSET (& pub , 0 , sizeof (pub ));
638+ pub .protocol_level = MQTT_CONNECT_PROTOCOL_LEVEL_5 ;
639+ rc = MqttDecode_Publish (buf , (int )sizeof (buf ), & pub );
640+ ASSERT_EQ (MQTT_CODE_ERROR_MALFORMED_DATA , rc );
641+ MqttProps_Free (pub .props );
642+ }
643+
644+ /* STRING_PAIR (USER_PROPERTY) NUL rejection — first string of pair. The
645+ * MqttDecode_Props path runs MqttDecode_String twice for STRING_PAIR;
646+ * this pins coverage on the first sub-decode propagating MALFORMED_DATA. */
647+ TEST (decode_publish_v5_rejects_nul_in_user_prop_key )
648+ {
649+ /* Wire: PUBLISH QoS 0, remain_len=14, topic "a/b", props_len=8,
650+ * prop 0x26 USER_PROPERTY, key_len=2 "k\0", val_len=1 "v". */
651+ byte buf [] = {
652+ 0x30 , 14 ,
653+ 0x00 , 0x03 , 'a' , '/' , 'b' ,
654+ 0x08 ,
655+ 0x26 , 0x00 , 0x02 , 'k' , 0x00 ,
656+ 0x00 , 0x01 , 'v'
657+ };
658+ MqttPublish pub ;
659+ int rc ;
660+
661+ XMEMSET (& pub , 0 , sizeof (pub ));
662+ pub .protocol_level = MQTT_CONNECT_PROTOCOL_LEVEL_5 ;
663+ rc = MqttDecode_Publish (buf , (int )sizeof (buf ), & pub );
664+ ASSERT_EQ (MQTT_CODE_ERROR_MALFORMED_DATA , rc );
665+ MqttProps_Free (pub .props );
666+ }
667+
668+ /* STRING_PAIR (USER_PROPERTY) NUL rejection — second string of pair.
669+ * Pins coverage on the second sub-decode propagating MALFORMED_DATA. */
670+ TEST (decode_publish_v5_rejects_nul_in_user_prop_value )
671+ {
672+ /* Wire: PUBLISH QoS 0, remain_len=14, topic "a/b", props_len=8,
673+ * prop 0x26 USER_PROPERTY, key_len=1 "k", val_len=2 "v\0". */
674+ byte buf [] = {
675+ 0x30 , 14 ,
676+ 0x00 , 0x03 , 'a' , '/' , 'b' ,
677+ 0x08 ,
678+ 0x26 , 0x00 , 0x01 , 'k' ,
679+ 0x00 , 0x02 , 'v' , 0x00
680+ };
681+ MqttPublish pub ;
682+ int rc ;
683+
684+ XMEMSET (& pub , 0 , sizeof (pub ));
685+ pub .protocol_level = MQTT_CONNECT_PROTOCOL_LEVEL_5 ;
686+ rc = MqttDecode_Publish (buf , (int )sizeof (buf ), & pub );
687+ ASSERT_EQ (MQTT_CODE_ERROR_MALFORMED_DATA , rc );
688+ MqttProps_Free (pub .props );
689+ }
619690#endif /* WOLFMQTT_V5 */
620691
621692/* ============================================================================
@@ -1610,6 +1681,34 @@ TEST(decode_connect_rejects_nul_in_will_topic)
16101681 rc = MqttDecode_Connect (buf , (int )sizeof (buf ), & dec );
16111682 ASSERT_EQ (MQTT_CODE_ERROR_MALFORMED_DATA , rc );
16121683}
1684+
1685+ #ifdef WOLFMQTT_V5
1686+ /* MQTT v5 has a different decode path than v3.1.1 (properties walked
1687+ * before client_id, separate LWT properties), but the NUL rejection
1688+ * lives inside MqttDecode_String / MqttDecode_Password and so applies
1689+ * uniformly. This pins coverage on the v5 branch so a future refactor
1690+ * cannot quietly bypass the check on one protocol level. */
1691+ TEST (decode_connect_v5_rejects_nul_in_client_id )
1692+ {
1693+ byte buf [] = {
1694+ 0x10 , 0x13 , /* CONNECT, remain_len = 19 */
1695+ 0x00 , 0x04 , 'M' , 'Q' , 'T' , 'T' ,
1696+ 0x05 , /* protocol level v5 */
1697+ 0x02 , /* flags: clean_session */
1698+ 0x00 , 0x3C , /* keep alive */
1699+ 0x00 , /* props_len = 0 (VBI) */
1700+ 0x00 , 0x06 , 'a' , 'd' , 0x00 , 'm' , 'i' , 'n' /* client_id with NUL */
1701+ };
1702+ MqttConnect dec ;
1703+ int rc ;
1704+
1705+ XMEMSET (& dec , 0 , sizeof (dec ));
1706+ dec .protocol_level = MQTT_CONNECT_PROTOCOL_LEVEL_5 ;
1707+ rc = MqttDecode_Connect (buf , (int )sizeof (buf ), & dec );
1708+ ASSERT_EQ (MQTT_CODE_ERROR_MALFORMED_DATA , rc );
1709+ MqttProps_Free (dec .props );
1710+ }
1711+ #endif /* WOLFMQTT_V5 */
16131712#endif /* WOLFMQTT_BROKER */
16141713
16151714/* ============================================================================
@@ -1726,31 +1825,6 @@ TEST(decode_subscribe_v5_options_byte_qos_extracted)
17261825 ASSERT_EQ (1 , sub .topic_count );
17271826 ASSERT_EQ (MQTT_QOS_1 , topic_arr [0 ].qos );
17281827}
1729-
1730- /* [MQTT-1.5.4-2]: an embedded NUL in a v5 STRING property must be
1731- * rejected. Uses a PUBLISH packet with a Content Type property whose
1732- * value contains 0x00. The MqttDecode_Props path now propagates the
1733- * underlying MALFORMED_DATA from MqttDecode_String instead of masking
1734- * it as MQTT_CODE_ERROR_PROPERTY. */
1735- TEST (decode_publish_v5_rejects_nul_in_string_property )
1736- {
1737- /* Wire: PUBLISH QoS 0, remain_len=13, topic "a/b", props_len=7,
1738- * prop 0x03 CONTENT_TYPE, str_len=4, "t\0xt". No payload. */
1739- byte buf [] = {
1740- 0x30 , 13 ,
1741- 0x00 , 0x03 , 'a' , '/' , 'b' ,
1742- 0x07 ,
1743- 0x03 , 0x00 , 0x04 , 't' , 0x00 , 'x' , 't'
1744- };
1745- MqttPublish pub ;
1746- int rc ;
1747-
1748- XMEMSET (& pub , 0 , sizeof (pub ));
1749- pub .protocol_level = MQTT_CONNECT_PROTOCOL_LEVEL_5 ;
1750- rc = MqttDecode_Publish (buf , (int )sizeof (buf ), & pub );
1751- ASSERT_EQ (MQTT_CODE_ERROR_MALFORMED_DATA , rc );
1752- MqttProps_Free (pub .props );
1753- }
17541828#endif /* WOLFMQTT_V5 */
17551829
17561830/* ============================================================================
@@ -2311,6 +2385,9 @@ void run_mqtt_packet_tests(void)
23112385 RUN_TEST (decode_publish_rejects_nul_in_topic );
23122386#ifdef WOLFMQTT_V5
23132387 RUN_TEST (decode_publish_v5_content_type_property );
2388+ RUN_TEST (decode_publish_v5_rejects_nul_in_string_property );
2389+ RUN_TEST (decode_publish_v5_rejects_nul_in_user_prop_key );
2390+ RUN_TEST (decode_publish_v5_rejects_nul_in_user_prop_value );
23142391#endif
23152392
23162393 /* MqttDecode_ConnectAck */
@@ -2364,14 +2441,16 @@ void run_mqtt_packet_tests(void)
23642441 RUN_TEST (decode_connect_rejects_nul_in_username );
23652442 RUN_TEST (decode_connect_rejects_nul_in_password );
23662443 RUN_TEST (decode_connect_rejects_nul_in_will_topic );
2444+ #ifdef WOLFMQTT_V5
2445+ RUN_TEST (decode_connect_v5_rejects_nul_in_client_id );
2446+ #endif
23672447
23682448 /* MqttDecode_Subscribe */
23692449 RUN_TEST (decode_subscribe_v311_single_topic );
23702450 RUN_TEST (decode_subscribe_v311_qos3_reserved );
23712451 RUN_TEST (decode_subscribe_rejects_nul_in_filter );
23722452#ifdef WOLFMQTT_V5
23732453 RUN_TEST (decode_subscribe_v5_options_byte_qos_extracted );
2374- RUN_TEST (decode_publish_v5_rejects_nul_in_string_property );
23752454#endif
23762455
23772456 /* MqttDecode_Unsubscribe */
0 commit comments