diff --git a/src/mms/reporting.cpp b/src/mms/reporting.cpp index 2d67cd21d..ded3947b7 100644 --- a/src/mms/reporting.cpp +++ b/src/mms/reporting.cpp @@ -136,7 +136,13 @@ MmsReportBitField decode_bit_field( result.raw = value.raw_value(); const auto unused = result.raw.front(); if (unused > 7U) throw MmsReportingFormatError("MMS BIT STRING unused-bit count is invalid."); - const auto bit_count = (result.raw.size() - 1U) * 8U - unused; + const auto payload_bits = (result.raw.size() - 1U) * 8U; + // With no payload byte, a nonzero unused count underflows unsigned + // bit_count and would iterate beyond the allocated BIT STRING buffer. + if (unused > payload_bits) { + throw MmsReportingFormatError("MMS BIT STRING unused bits exceed its payload."); + } + const auto bit_count = payload_bits - unused; for (std::size_t bit = 0U; bit < bit_count; ++bit) { const auto byte = result.raw[1U + bit / 8U]; if ((byte & static_cast(0x80U >> (bit % 8U))) == 0U) continue; diff --git a/tests/test_reporting.cpp b/tests/test_reporting.cpp index 8bb3a40ad..24ae95627 100644 --- a/tests/test_reporting.cpp +++ b/tests/test_reporting.cpp @@ -421,6 +421,20 @@ void offline_monitor_enforces_frame_and_stream_limits() { }), "Monitor frame limit was not enforced."); } +void empty_bit_string_unused_bits_fail_closed() { + auto options = realistic_report(); + options.items[1U].value = MmsDataValue::bit_string(1U, {}); + require_throws([&options] { + static_cast(MmsReportFrameMapper::map(options, {})); + }, "Empty OptFlds payload with unused bits was accepted."); + + auto inclusion = realistic_report(); + inclusion.items[8U].value = MmsDataValue::bit_string(7U, {}); + require_throws([&inclusion] { + static_cast(MmsReportFrameMapper::map(inclusion, {})); + }, "Empty inclusion payload with unused bits was accepted."); +} + void malformed_and_bound_paths_are_rejected() { require_throws([] { static_cast(MmsInformationReportCodec::decode(std::array{0xA3U, 0x00U})); @@ -453,6 +467,7 @@ int main() { {"sequence continuity", sequence_tracker_detects_gap_duplicate_and_changes}, {"segmentation continuity", segmentation_continuity_is_tracked}, {"bounded monitor", offline_monitor_enforces_frame_and_stream_limits}, + {"empty BIT STRING unused bits", empty_bit_string_unused_bits_fail_closed}, {"malformed and bounds", malformed_and_bound_paths_are_rejected}, }; std::size_t passed = 0U;