[netdata] add validation for incoming network data TLVs (#12040)

Introduces a new method `ValidateTlvs()` on `NetworkData` to perform
structural validation of all TLVs within the network data.

This new validation is invoked from `Leader::SetNetworkData()` when
receiving new network data. If the new data fails validation, it is
rejected, and the previous network data is restored. This prevents a
device from accepting and propagating malformed network data, which
could lead to parsing errors or undefined behavior on devices.

The validation checks include:
- All TLVs and sub-TLVs are within the network data buffer bounds.
- Known TLV types like `PrefixTlv` and `ServiceTlv` are well-formed
  by calling their respective `IsValid()` methods.
- Container TLVs like `BorderRouterTlv` and `HasRouteTlv` have a
  length that is an exact multiple of their entry size.
This commit is contained in:
Abtin Keshavarzian
2025-10-16 09:13:27 +02:00
committed by GitHub
parent d23da65a48
commit 0b0930f1b8
3 changed files with 101 additions and 5 deletions
+67
View File
@@ -43,6 +43,73 @@ RegisterLogModule("NetworkData");
//---------------------------------------------------------------------------------------------------------------------
// NetworkData
Error NetworkData::ValidateTlvs(void) const
{
Error error = kErrorParse;
const NetworkDataTlv *end = GetTlvsEnd();
const NetworkDataTlv *tlv;
const NetworkDataTlv *subTlv;
const NetworkDataTlv *tlvEnd;
for (tlv = GetTlvsStart(); tlv < end; tlv = tlv->GetNext())
{
VerifyOrExit(tlv + 1 <= end);
VerifyOrExit(tlv->GetNext() <= end);
tlvEnd = tlv->GetNext();
subTlv = nullptr;
switch (tlv->GetType())
{
case NetworkDataTlv::kTypePrefix:
VerifyOrExit(As<PrefixTlv>(tlv)->IsValid());
subTlv = As<PrefixTlv>(tlv)->GetSubTlvs();
break;
case NetworkDataTlv::kTypeService:
VerifyOrExit(As<ServiceTlv>(tlv)->IsValid());
subTlv = As<ServiceTlv>(tlv)->GetSubTlvs();
break;
case NetworkDataTlv::kTypeCommissioningData:
default:
break;
}
if (subTlv == nullptr)
{
continue;
}
for (; subTlv < tlvEnd; subTlv = subTlv->GetNext())
{
VerifyOrExit(subTlv + 1 <= tlvEnd);
VerifyOrExit(subTlv->GetNext() <= tlvEnd);
switch (subTlv->GetType())
{
case NetworkDataTlv::kTypeContext:
VerifyOrExit(As<ContextTlv>(subTlv)->IsValid());
break;
case NetworkDataTlv::kTypeServer:
VerifyOrExit(As<ServerTlv>(subTlv)->IsValid());
break;
case NetworkDataTlv::kTypeBorderRouter:
VerifyOrExit((As<BorderRouterTlv>(subTlv)->GetLength() % sizeof(BorderRouterEntry)) == 0);
break;
case NetworkDataTlv::kTypeHasRoute:
VerifyOrExit((As<HasRouteTlv>(subTlv)->GetLength() % sizeof(HasRouteEntry)) == 0);
break;
default:
break;
}
}
}
error = kErrorNone;
exit:
return error;
}
Error NetworkData::CopyNetworkData(Type aType, uint8_t *aData, uint8_t &aDataLength) const
{
Error error;
+12
View File
@@ -159,6 +159,18 @@ public:
*/
const uint8_t *GetBytes(void) const { return mTlvs; }
/**
* Parses and validates all TLVs contained within the Network Data.
*
* Performs the following checks on all TLVs in the Network Data.
* - Ensures correct TLV format and expected minimum length for known TLV types that can appear in Network Data.
* - Validates sub-TLVs included in the known TLVs.
*
* @retval kErrorNone Successfully validated all the TLVs in the Network Data.
* @retval kErrorParse Network Data TLVs are not well-formed.
*/
Error ValidateTlvs(void) const;
/**
* Provides full or stable copy of the Thread Network Data.
*
+22 -5
View File
@@ -453,13 +453,30 @@ Error Leader::SetNetworkData(uint8_t aVersion,
const Message &aMessage,
const OffsetRange &aOffsetRange)
{
Error error = kErrorNone;
uint16_t length = aOffsetRange.GetLength();
Error error = kErrorNone;
uint8_t oldData[kMaxSize];
uint8_t oldLength;
VerifyOrExit(length <= kMaxSize, error = kErrorParse);
SuccessOrExit(error = aMessage.Read(aOffsetRange.GetOffset(), GetBytes(), length));
VerifyOrExit(aOffsetRange.GetLength() <= kMaxSize, error = kErrorParse);
VerifyOrExit(aOffsetRange.GetEndOffset() <= aMessage.GetLength(), error = kErrorParse);
oldLength = sizeof(oldData);
IgnoreError(CopyNetworkData(kFullSet, oldData, oldLength));
aMessage.ReadBytes(aOffsetRange, GetBytes());
SetLength(static_cast<uint8_t>(aOffsetRange.GetLength()));
error = ValidateTlvs();
if (error != kErrorNone)
{
// Restores the old data back
memcpy(GetBytes(), oldData, oldLength);
SetLength(oldLength);
ExitNow();
}
SetLength(static_cast<uint8_t>(length));
mVersion = aVersion;
mStableVersion = aStableVersion;