From beeef5f8a699b5bf5e9f1cef89fff99792abc803 Mon Sep 17 00:00:00 2001 From: Abtin Keshavarzian Date: Wed, 12 Nov 2025 00:21:29 -0800 Subject: [PATCH] [energy-scan-server] reject scan request with zero channel mask (#12137) This commit validates that the Channel Mask TLVs in a TMF Energy Scan request are non-zero. Additionally, this commit clamps the Count TLV value to the valid range (1, 2, and 3) as required by the Thread specification. The `test_otci` is updated to use count 3 (previously 4). An Energy Scan request with a zero `Channel Mask` is invalid and can cause the device to start a scan that takes a long time or never completes. This change rejects such requests, preventing the device from getting stuck. This was discovered by fuzzer test. --- src/core/thread/energy_scan_server.cpp | 3 +++ src/core/thread/energy_scan_server.hpp | 2 ++ tools/otci/tests/test_otci.py | 4 ++-- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/core/thread/energy_scan_server.cpp b/src/core/thread/energy_scan_server.cpp index 4e3df02d7..c128334f8 100644 --- a/src/core/thread/energy_scan_server.cpp +++ b/src/core/thread/energy_scan_server.cpp @@ -62,10 +62,13 @@ void EnergyScanServer::HandleTmf(Coap::Message &aMessage, const VerifyOrExit(aMessage.IsPostRequest()); SuccessOrExit(Tlv::Find(aMessage, count)); + count = Clamp(count, kMinCount, kMaxCount); + SuccessOrExit(Tlv::Find(aMessage, period)); SuccessOrExit(Tlv::Find(aMessage, scanDuration)); SuccessOrExit(MeshCoP::ChannelMaskTlv::FindIn(aMessage, mask)); + VerifyOrExit(mask != 0); mReportMessage.Reset(Get().NewPriorityConfirmablePostMessage(kUriEnergyReport)); VerifyOrExit(mReportMessage != nullptr); diff --git a/src/core/thread/energy_scan_server.hpp b/src/core/thread/energy_scan_server.hpp index 6278621f2..0fe6413a6 100644 --- a/src/core/thread/energy_scan_server.hpp +++ b/src/core/thread/energy_scan_server.hpp @@ -66,6 +66,8 @@ public: private: static constexpr uint32_t kScanDelay = 1000; // SCAN_DELAY (milliseconds) static constexpr uint32_t kReportDelay = 500; // Delay before sending a report (milliseconds) + static constexpr uint8_t kMinCount = 1; + static constexpr uint8_t kMaxCount = 3; template void HandleTmf(Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo); diff --git a/tools/otci/tests/test_otci.py b/tools/otci/tests/test_otci.py index 334659f5f..40662fe39 100644 --- a/tools/otci/tests/test_otci.py +++ b/tools/otci/tests/test_otci.py @@ -754,10 +754,10 @@ class TestOTCI(unittest.TestCase): rtt: Dict[str, float] = cast(Dict[str, float], statistics['round_trip_time']) self.assertTrue(rtt['min'] - 1e-9 <= rtt['avg'] <= rtt['max'] + 1e-9) - ed_report = commissioner.commissioner_energy_scan(3 << commissioner.get_channel(), 4, 32, 1000, + ed_report = commissioner.commissioner_energy_scan(3 << commissioner.get_channel(), 3, 32, 1000, child1.get_ipaddr_rloc()) comm_chan = commissioner.get_channel() - self.assertEqual({comm_chan: [-30, -30, -30, -30], comm_chan + 1: [-30, -30, -30, -30]}, ed_report) + self.assertEqual({comm_chan: [-30, -30, -30], comm_chan + 1: [-30, -30, -30]}, ed_report) commissioner.commissioner_announce(TEST_CHANNEL_MASK, 1, 32, child1.get_ipaddr_rloc())