diff --git a/src/mqtt_packet.c b/src/mqtt_packet.c index 66eba6ce..9cfec878 100644 --- a/src/mqtt_packet.c +++ b/src/mqtt_packet.c @@ -2152,6 +2152,11 @@ int MqttDecode_PublishResp(byte* rx_buf, int rx_buf_len, byte type, if (header_len < 0) { return header_len; } + /* Remaining Length is attacker-controlled; bound it by the bytes the + * caller actually supplied before any remain_len-gated read below. */ + if (rx_buf_len < header_len + remain_len) { + return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_OUT_OF_BUFFER); + } /* MQTT 3.1.1 sections 3.4-3.7: PUBACK/PUBREC/PUBREL/PUBCOMP variable header is * exactly the two-byte Packet Identifier and there is no payload - @@ -3186,6 +3191,11 @@ int MqttDecode_Disconnect(byte *rx_buf, int rx_buf_len, MqttDisconnect *disc) if (header_len < 0) { return header_len; } + /* Remaining Length is attacker-controlled; bound it by the bytes the + * caller actually supplied before the reason-code read below. */ + if (rx_buf_len < header_len + remain_len) { + return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_OUT_OF_BUFFER); + } rx_payload = &rx_buf[header_len]; disc->props = NULL; @@ -3320,6 +3330,11 @@ int MqttDecode_Auth(byte *rx_buf, int rx_buf_len, MqttAuth *auth) if (header_len < 0) { return header_len; } + /* Remaining Length is attacker-controlled; bound it by the bytes the + * caller actually supplied before the reason-code read below. */ + if (rx_buf_len < header_len + remain_len) { + return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_OUT_OF_BUFFER); + } rx_payload = &rx_buf[header_len]; auth->props = NULL; diff --git a/tests/test_mqtt_packet.c b/tests/test_mqtt_packet.c index f873e94e..495977c0 100644 --- a/tests/test_mqtt_packet.c +++ b/tests/test_mqtt_packet.c @@ -3801,6 +3801,22 @@ TEST(decode_puback_v5_with_reason_code_accepted) ASSERT_EQ(0, resp.reason_code); MqttProps_Free(resp.props); } + +/* Remaining Length claims a Reason Code (3) but the buffer ends right after + * the Packet Identifier, so the reason-code read must be rejected rather + * than stepping past rx_buf. */ +TEST(decode_puback_v5_truncated_reason_code_oob) +{ + byte buf[4] = { 0x40, 0x03, 0x00, 0x01 }; + MqttPublishResp resp; + int rc; + + XMEMSET(&resp, 0, sizeof(resp)); + resp.protocol_level = MQTT_CONNECT_PROTOCOL_LEVEL_5; + rc = MqttDecode_PublishResp(buf, (int)sizeof(buf), + MQTT_PACKET_TYPE_PUBLISH_ACK, &resp); + ASSERT_EQ(MQTT_CODE_ERROR_OUT_OF_BUFFER, rc); +} #endif /* WOLFMQTT_V5 */ /* ============================================================================ @@ -4042,6 +4058,20 @@ TEST(decode_disconnect_v5_with_reason_code_accepted) ASSERT_EQ(0, disc.reason_code); MqttProps_Free(disc.props); } + +/* Remaining Length claims a Reason Code (1) but only the fixed header is + * present, so the reason-code read must be rejected rather than stepping + * past rx_buf. */ +TEST(decode_disconnect_v5_truncated_reason_code_oob) +{ + byte buf[2] = { 0xE0, 0x01 }; + MqttDisconnect disc; + int rc; + + XMEMSET(&disc, 0, sizeof(disc)); + rc = MqttDecode_Disconnect(buf, (int)sizeof(buf), &disc); + ASSERT_EQ(MQTT_CODE_ERROR_OUT_OF_BUFFER, rc); +} #endif /* WOLFMQTT_V5 */ /* ============================================================================ @@ -4493,6 +4523,23 @@ TEST(auth_v5_invalid_reason_code_rejected) dec_len = MqttDecode_Auth(buf, 4, &dec); ASSERT_EQ(MQTT_CODE_ERROR_MALFORMED_DATA, dec_len); } + +/* Remaining Length claims a Reason Code (1) but only the fixed header is + * present, so the reason-code read must be rejected rather than stepping + * past rx_buf. */ +TEST(decode_auth_truncated_reason_code_oob) +{ + byte buf[2]; + MqttAuth dec; + int dec_len; + + buf[0] = (byte)(MQTT_PACKET_TYPE_AUTH << 4); + buf[1] = 0x01; /* Remaining Length claims one payload byte */ + + XMEMSET(&dec, 0, sizeof(dec)); + dec_len = MqttDecode_Auth(buf, (int)sizeof(buf), &dec); + ASSERT_EQ(MQTT_CODE_ERROR_OUT_OF_BUFFER, dec_len); +} #endif /* WOLFMQTT_V5 */ /* ============================================================================ @@ -4760,6 +4807,7 @@ void run_mqtt_packet_tests(void) RUN_TEST(decode_puback_null_resp_extra_payload_rejected); #ifdef WOLFMQTT_V5 RUN_TEST(decode_puback_v5_with_reason_code_accepted); + RUN_TEST(decode_puback_v5_truncated_reason_code_oob); #endif /* MqttEncode_PublishResp fixed-header QoS bits */ @@ -4786,6 +4834,7 @@ void run_mqtt_packet_tests(void) #ifdef WOLFMQTT_V5 RUN_TEST(decode_disconnect_v5_invalid_fixed_header_flags_rejected); RUN_TEST(decode_disconnect_v5_with_reason_code_accepted); + RUN_TEST(decode_disconnect_v5_truncated_reason_code_oob); #endif /* Fixed-header reserved-flag validation [MQTT-2.2.2-2] */ @@ -4814,6 +4863,7 @@ void run_mqtt_packet_tests(void) RUN_TEST(auth_v5_reauth_decodes_without_error); RUN_TEST(auth_v5_success_remaining_length_zero); RUN_TEST(auth_v5_invalid_reason_code_rejected); + RUN_TEST(decode_auth_truncated_reason_code_oob); #endif TEST_SUITE_END();