From 54323a2953773edc1d18e90c44fb4f19b7f82ee4 Mon Sep 17 00:00:00 2001 From: Luc1fer Date: Thu, 18 Jun 2026 23:25:50 +0800 Subject: [PATCH] [mle] reject malformed Network Data in Child ID Response (#13213) This change makes MLE Child ID Response handling fail safely when the Network Data TLV is present but cannot be parsed or applied. The attach path requires Network Data in a Child ID Response. This patch checks the result of applying the Network Data TLV and avoids completing attach if the required Network Data cannot be applied. Signed-off-by: OneThing4101 --- src/core/thread/mle.cpp | 7 +- src/core/thread/mle.hpp | 2 + tests/unit/test_mle.cpp | 286 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 294 insertions(+), 1 deletion(-) diff --git a/src/core/thread/mle.cpp b/src/core/thread/mle.cpp index 343b11ede..b8844b551 100644 --- a/src/core/thread/mle.cpp +++ b/src/core/thread/mle.cpp @@ -5480,7 +5480,12 @@ void Mle::Attacher::HandleChildIdResponse(RxInfo &aRxInfo) Get().mParent.SetRloc16(sourceAddress); - IgnoreError(aRxInfo.mMessage.ReadAndSetNetworkDataTlv(leaderData)); + error = aRxInfo.mMessage.ReadAndSetNetworkDataTlv(leaderData); + if (error != kErrorNone) + { + IgnoreError(Get().BecomeDetached()); + ExitNow(); + } Get().SetStateChild(shortAddress); diff --git a/src/core/thread/mle.hpp b/src/core/thread/mle.hpp index c4f2d5dd9..1fb5fe5e5 100644 --- a/src/core/thread/mle.hpp +++ b/src/core/thread/mle.hpp @@ -1939,6 +1939,8 @@ private: class Attacher : public InstanceLocator { + friend class ot::UnitTester; + public: explicit Attacher(Instance &aInstance); diff --git a/tests/unit/test_mle.cpp b/tests/unit/test_mle.cpp index 8e5a34b9d..6bef15c81 100644 --- a/tests/unit/test_mle.cpp +++ b/tests/unit/test_mle.cpp @@ -32,7 +32,11 @@ #include "test_util.hpp" #include "common/num_utils.hpp" +#include "thread/lowpan.hpp" +#include "thread/mle.hpp" +#include "thread/mle_tlvs.hpp" #include "thread/mle_types.hpp" +#include "thread/network_data_leader.hpp" namespace ot { @@ -188,6 +192,287 @@ void TestDeviceMode(void) printf("TestDeviceMode passed\n"); } +namespace { + +constexpr uint16_t kOldParentRloc16 = 0x5400; +constexpr uint16_t kOldChildRloc16 = 0x5401; +constexpr uint16_t kNewParentRloc16 = 0x5c00; +constexpr uint16_t kNewChildRloc16 = 0x5c01; + +constexpr uint8_t kOldDataVersion = 4; +constexpr uint8_t kOldStableVersion = 3; +constexpr uint8_t kNewDataVersion = 8; +constexpr uint8_t kNewStableVersion = 7; +constexpr uint8_t kMalformedDataLen = 31; + +const uint8_t kOldNetworkData[] = { + 0x03, 0x0e, 0x00, 0x40, 0x20, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x07, 0x02, 0x11, 0x40, + 0x03, 0x0e, 0x00, 0x40, 0x20, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x07, 0x02, 0x02, 0x40, +}; + +const uint8_t kNewNetworkData[] = { + 0x03, 0x0e, 0x00, 0x40, 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x03, 0x07, 0x02, 0x13, 0x40, + 0x03, 0x0e, 0x00, 0x40, 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x04, 0x07, 0x02, 0x04, 0x40, +}; + +Ip6::Prefix PrefixFromString(const char *aString, uint8_t aLength) +{ + Ip6::Prefix prefix; + + SuccessOrQuit(AsCoreType(&prefix.mPrefix).FromString(aString)); + prefix.mLength = aLength; + + return prefix; +} + +Mac::ExtAddress ExtAddressFromSeed(uint8_t aSeed) +{ + Mac::ExtAddress extAddress; + + for (size_t index = 0; index < sizeof(extAddress.m8); index++) + { + extAddress.m8[index] = static_cast(aSeed + index); + } + + return extAddress; +} + +void VerifyContext(Instance &aInstance, uint8_t aContextId, const Ip6::Prefix &aExpectedPrefix, bool aShouldBeValid) +{ + Lowpan::Context context; + + aInstance.Get().FindContextForId(aContextId, context); + VerifyOrQuit(context.IsValid() == aShouldBeValid); + + if (aShouldBeValid) + { + VerifyOrQuit(context.GetContextId() == aContextId); + VerifyOrQuit(context.GetPrefix() == aExpectedPrefix); + } +} + +} // namespace + +class UnitTester +{ +public: + static void TestChildIdResponseNetworkDataHandling(void) + { + TestValidNetworkDataControl(); + TestMissingNetworkDataControl(); + TestMalformedNetworkDataControl(); + + printf("TestChildIdResponseNetworkDataHandling passed\n"); + } + +private: + static void SetNetworkData(Instance &aInstance, + uint8_t aDataVersion, + uint8_t aStableVersion, + const uint8_t *aNetworkData, + uint8_t aNetworkDataLength) + { + Message *message = aInstance.Get().Allocate(Message::kTypeIp6); + OffsetRange offsetRange; + + VerifyOrQuit(message != nullptr); + + SuccessOrQuit(message->AppendBytes(aNetworkData, aNetworkDataLength)); + offsetRange.Init(0, aNetworkDataLength); + + SuccessOrQuit(aInstance.Get().SetNetworkData( + aDataVersion, aStableVersion, NetworkData::kFullSet, *message, offsetRange)); + + message->Free(); + } + + static Message *NewChildIdResponseMessage(Instance &aInstance, + uint16_t aSourceAddress, + uint16_t aChildAddress, + const Mle::LeaderData &aLeaderData, + const uint8_t *aNetworkData, + uint8_t aNetworkDataLength, + bool aIncludeNetworkData) + { + Message *message = aInstance.Get().Allocate(Message::kTypeIp6); + const Mle::LeaderDataTlvValue leaderDataTlv(aLeaderData); + + VerifyOrQuit(message != nullptr); + message->SetSubType(Message::kSubTypeMle); + + SuccessOrQuit(Tlv::Append(*message, aSourceAddress)); + SuccessOrQuit(Tlv::Append(*message, aChildAddress)); + SuccessOrQuit(Tlv::Append(*message, leaderDataTlv)); + + if (aIncludeNetworkData) + { + SuccessOrQuit(Tlv::Append(*message, aNetworkData, aNetworkDataLength)); + } + + return message; + } + + static void PrepareChildIdResponse(Mle::Mle &aMle, const Mac::ExtAddress &aParentExtAddress, uint16_t aParentRloc16) + { + Parent &parentCandidate = aMle.GetParentCandidate(); + + aMle.SetStateDetached(); + aMle.mParent.SetState(Neighbor::kStateInvalid); + aMle.SetRloc16(Mle::kInvalidRloc16); + aMle.Get().Up(); + aMle.Get().AddUnicastAddress(aMle.mMeshLocalEid); + + parentCandidate.Clear(); + parentCandidate.GetExtAddress() = aParentExtAddress; + parentCandidate.SetRloc16(aParentRloc16); + parentCandidate.SetVersion(kThreadVersion); + parentCandidate.SetDeviceMode(Mle::DeviceMode(Mle::DeviceMode::kModeFullThreadDevice | + Mle::DeviceMode::kModeRxOnWhenIdle | + Mle::DeviceMode::kModeFullNetworkData)); + parentCandidate.SetState(Neighbor::kStateValid); + aMle.mAttacher.mState = Mle::Mle::Attacher::kStateChildIdRequest; + } + + static void HandleChildIdResponse(Mle::Mle &aMle, Message &aMessage, const Mac::ExtAddress &aParentExtAddress) + { + Ip6::Address peerAddress; + Ip6::MessageInfo messageInfo; + Mle::Mle::RxInfo rxInfo(aMessage, messageInfo); + + peerAddress.InitAsLinkLocalAddress(aParentExtAddress); + messageInfo.SetPeerAddr(peerAddress); + messageInfo.SetSockAddr(aMle.GetLinkLocalAddress()); + + aMessage.SetOffset(0); + rxInfo.mNeighbor = &aMle.mAttacher.mParentCandidate; + + aMle.mAttacher.HandleChildIdResponse(rxInfo); + } + + static void VerifyDataVersions(Instance &aInstance, uint8_t aDataVersion, uint8_t aStableVersion) + { + VerifyOrQuit(aInstance.Get().GetVersion(NetworkData::kFullSet) == aDataVersion); + VerifyOrQuit(aInstance.Get().GetVersion(NetworkData::kStableSubset) == aStableVersion); + } + + static Mle::LeaderData NewLeaderData(uint32_t aPartitionId, + uint8_t aWeighting, + uint8_t aLeaderRouterId, + uint8_t aDataVersion, + uint8_t aStableVersion) + { + Mle::LeaderData leaderData; + + leaderData.SetPartitionId(aPartitionId); + leaderData.SetWeighting(aWeighting); + leaderData.SetLeaderRouterId(aLeaderRouterId); + leaderData.SetDataVersion(aDataVersion); + leaderData.SetStableDataVersion(aStableVersion); + + return leaderData; + } + + static void TestValidNetworkDataControl(void) + { + Instance *instance = static_cast(testInitInstance()); + Mle::Mle &mle = instance->Get(); + const Mac::ExtAddress parentExtAddress = ExtAddressFromSeed(0x30); + const Ip6::Prefix oldPrefix1 = PrefixFromString("2001:2:0:1::", 64); + const Ip6::Prefix oldPrefix2 = PrefixFromString("2001:2:0:2::", 64); + const Ip6::Prefix newPrefix1 = PrefixFromString("2001:db8:0:3::", 64); + const Ip6::Prefix newPrefix2 = PrefixFromString("2001:db8:0:4::", 64); + Message *message; + Mle::LeaderData leaderData = NewLeaderData(0x11111111, 64, Mle::RouterIdFromRloc16(kNewParentRloc16), + kNewDataVersion, kNewStableVersion); + + printf("valid-network-data-control\n"); + + SetNetworkData(*instance, kOldDataVersion, kOldStableVersion, kOldNetworkData, sizeof(kOldNetworkData)); + PrepareChildIdResponse(mle, parentExtAddress, kNewParentRloc16); + + message = NewChildIdResponseMessage(*instance, kNewParentRloc16, kNewChildRloc16, leaderData, kNewNetworkData, + sizeof(kNewNetworkData), true); + + HandleChildIdResponse(mle, *message, parentExtAddress); + + VerifyOrQuit(mle.IsChild()); + VerifyOrQuit(mle.GetRloc16() == kNewChildRloc16); + VerifyOrQuit(mle.GetParent().GetRloc16() == kNewParentRloc16); + VerifyDataVersions(*instance, kNewDataVersion, kNewStableVersion); + VerifyContext(*instance, 1, oldPrefix1, false); + VerifyContext(*instance, 2, oldPrefix2, false); + VerifyContext(*instance, 3, newPrefix1, true); + VerifyContext(*instance, 4, newPrefix2, true); + + message->Free(); + testFreeInstance(instance); + } + + static void TestMissingNetworkDataControl(void) + { + Instance *instance = static_cast(testInitInstance()); + Mle::Mle &mle = instance->Get(); + const Mac::ExtAddress parentExtAddress = ExtAddressFromSeed(0x40); + const Ip6::Prefix oldPrefix1 = PrefixFromString("2001:2:0:1::", 64); + const Ip6::Prefix oldPrefix2 = PrefixFromString("2001:2:0:2::", 64); + Message *message; + Mle::LeaderData leaderData = NewLeaderData(0x22222222, 64, Mle::RouterIdFromRloc16(kOldParentRloc16), 9, 9); + + printf("missing-network-data-control\n"); + + SetNetworkData(*instance, kOldDataVersion, kOldStableVersion, kOldNetworkData, sizeof(kOldNetworkData)); + PrepareChildIdResponse(mle, parentExtAddress, kOldParentRloc16); + + message = + NewChildIdResponseMessage(*instance, kOldParentRloc16, kOldChildRloc16, leaderData, nullptr, 0, false); + + HandleChildIdResponse(mle, *message, parentExtAddress); + + VerifyOrQuit(!mle.IsChild()); + VerifyDataVersions(*instance, kOldDataVersion, kOldStableVersion); + VerifyContext(*instance, 1, oldPrefix1, true); + VerifyContext(*instance, 2, oldPrefix2, true); + + message->Free(); + testFreeInstance(instance); + } + + static void TestMalformedNetworkDataControl(void) + { + Instance *instance = static_cast(testInitInstance()); + Mle::Mle &mle = instance->Get(); + const Mac::ExtAddress parentExtAddress = ExtAddressFromSeed(0x50); + const Ip6::Prefix oldPrefix1 = PrefixFromString("2001:2:0:1::", 64); + const Ip6::Prefix oldPrefix2 = PrefixFromString("2001:2:0:2::", 64); + const Ip6::Prefix newPrefix1 = PrefixFromString("2001:db8:0:3::", 64); + Message *message; + Mle::LeaderData leaderData = NewLeaderData(0x33333333, 64, Mle::RouterIdFromRloc16(kNewParentRloc16), + kNewDataVersion, kNewStableVersion); + + printf("malformed-network-data-control\n"); + + SetNetworkData(*instance, kOldDataVersion, kOldStableVersion, kOldNetworkData, sizeof(kOldNetworkData)); + PrepareChildIdResponse(mle, parentExtAddress, kNewParentRloc16); + + message = NewChildIdResponseMessage(*instance, kNewParentRloc16, kNewChildRloc16, leaderData, kNewNetworkData, + kMalformedDataLen, true); + + HandleChildIdResponse(mle, *message, parentExtAddress); + + VerifyOrQuit(mle.IsDetached()); + VerifyOrQuit(mle.GetParent().IsStateInvalid()); + VerifyOrQuit(mle.mAttacher.mState == Mle::Mle::Attacher::kStateStart); + VerifyOrQuit(mle.mAttacher.mTimer.IsRunning()); + VerifyDataVersions(*instance, kOldDataVersion, kOldStableVersion); + VerifyContext(*instance, 1, oldPrefix1, true); + VerifyContext(*instance, 2, oldPrefix2, true); + VerifyContext(*instance, 3, newPrefix1, false); + + message->Free(); + testFreeInstance(instance); + } +}; + #if OPENTHREAD_FTD && OPENTHREAD_CONFIG_MLE_DEVICE_PROPERTY_LEADER_WEIGHT_ENABLE void TestDefaultDeviceProperties(void) @@ -329,6 +614,7 @@ void TestLeaderWeightCalculation(void) int main(void) { ot::TestDeviceMode(); + ot::UnitTester::TestChildIdResponseNetworkDataHandling(); #if OPENTHREAD_FTD && OPENTHREAD_CONFIG_MLE_DEVICE_PROPERTY_LEADER_WEIGHT_ENABLE ot::TestDefaultDeviceProperties();