[dataset] add additional validation for dataset range and formatting (#13518)

This commit is contained in:
sarveshkumarv3
2026-08-24 08:59:34 -07:00
committed by GitHub
parent 1736567a08
commit 3e54d9cf37
8 changed files with 173 additions and 25 deletions
+6
View File
@@ -578,6 +578,12 @@ otError otNetworkNameFromString(otNetworkName *aNetworkName, const char *aNameSt
*
* This method also checks whether there are duplicated TLVs or the TLVs are not well-formed in the @p aDatasetTlvs.
*
* In addition to the TLV lengths, the values of the following TLVs are validated: Channel and Wake-up Channel (the
* channel page must be supported and the channel within range), Channel Mask (well-formed entries), PAN ID (must not
* be the broadcast PAN ID 0xffff), Extended PAN ID (must not be all-zeros or all-ones), Mesh-Local Prefix (must be a
* locally assigned ULA prefix, i.e., `fd00::/8`), Network Name (1 to 16 bytes, valid UTF-8, no control characters),
* and Security Policy.
*
* @param[in] aDatasetTlvs A pointer to dataset TLVs.
* @param[in] aActive TRUE for Active Dataset, FALSE for Pending Dataset.
*
+1 -1
View File
@@ -52,7 +52,7 @@ extern "C" {
*
* @note This number versions both OpenThread platform and user APIs.
*/
#define OPENTHREAD_API_VERSION (617)
#define OPENTHREAD_API_VERSION (618)
/**
* @addtogroup api-instance
+46 -23
View File
@@ -128,6 +128,10 @@ bool Dataset::IsTlvValid(const Tlv &aTlv)
bool isValid = true;
uint8_t minLength = 0;
// Validate the TLV format, i.e., that the value is long enough for
// the TLV type. TLV types whose `IsValid()` does its own length
// checking are not included here.
switch (aTlv.GetType())
{
case Tlv::kActiveTimestamp:
@@ -139,41 +143,25 @@ bool Dataset::IsTlvValid(const Tlv &aTlv)
case Tlv::kDelayTimer:
minLength = sizeof(DelayTimerTlv::UintValueType);
break;
case Tlv::kPanId:
minLength = sizeof(PanIdTlv::UintValueType);
break;
case Tlv::kExtendedPanId:
minLength = sizeof(ExtendedPanIdTlv::ValueType);
break;
case Tlv::kPskc:
minLength = sizeof(PskcTlv::ValueType);
break;
case Tlv::kNetworkKey:
minLength = sizeof(NetworkKeyTlv::ValueType);
break;
case Tlv::kPanId:
minLength = sizeof(PanIdTlv::UintValueType);
break;
case Tlv::kExtendedPanId:
minLength = sizeof(ExtendedPanIdTlv::ValueType);
break;
case Tlv::kMeshLocalPrefix:
minLength = sizeof(MeshLocalPrefixTlv::ValueType);
break;
case Tlv::kChannel:
VerifyOrExit(aTlv.GetLength() >= sizeof(ChannelTlvValue), isValid = false);
isValid = aTlv.ReadValueAs<ChannelTlv>().IsValid();
break;
case Tlv::kWakeupChannel:
VerifyOrExit(aTlv.GetLength() >= sizeof(ChannelTlvValue), isValid = false);
isValid = aTlv.ReadValueAs<WakeupChannelTlv>().IsValid();
minLength = sizeof(ChannelTlvValue);
break;
case Tlv::kNetworkName:
isValid = As<NetworkNameTlv>(aTlv).IsValid();
break;
case Tlv::kSecurityPolicy:
isValid = As<SecurityPolicyTlv>(aTlv).IsValid();
break;
case Tlv::kChannelMask:
isValid = As<ChannelMaskTlv>(aTlv).IsValid();
break;
default:
break;
}
@@ -181,6 +169,41 @@ bool Dataset::IsTlvValid(const Tlv &aTlv)
if (minLength > 0)
{
isValid = (aTlv.GetLength() >= minLength);
VerifyOrExit(isValid);
}
// Validate the TLV value.
switch (aTlv.GetType())
{
case Tlv::kPanId:
// The broadcast PAN ID does not identify a network.
isValid = (aTlv.ReadValueAs<PanIdTlv>() != Mac::kPanIdBroadcast);
break;
case Tlv::kExtendedPanId:
isValid = aTlv.ReadValueAs<ExtendedPanIdTlv>().IsValid();
break;
case Tlv::kMeshLocalPrefix:
// The Mesh-Local Prefix is required to be a locally assigned ULA prefix.
isValid = aTlv.ReadValueAs<MeshLocalPrefixTlv>().IsLocallyAssignedUla();
break;
case Tlv::kChannel:
isValid = aTlv.ReadValueAs<ChannelTlv>().IsValid();
break;
case Tlv::kWakeupChannel:
isValid = aTlv.ReadValueAs<WakeupChannelTlv>().IsValid();
break;
case Tlv::kNetworkName:
isValid = As<NetworkNameTlv>(aTlv).IsValid();
break;
case Tlv::kSecurityPolicy:
isValid = As<SecurityPolicyTlv>(aTlv).IsValid();
break;
case Tlv::kChannelMask:
isValid = As<ChannelMaskTlv>(aTlv).IsValid();
break;
default:
break;
}
exit:
+18
View File
@@ -49,5 +49,23 @@ ExtendedPanId::InfoString ExtendedPanId::ToString(void) const
Error ExtendedPanId::GenerateRandom(void) { return Random::Crypto::Fill(*this); }
bool ExtendedPanId::IsValid(void) const
{
// The all-zeros and all-ones Extended PAN IDs are disallowed by
// the Thread specification. Determine both cases in a single pass
// by OR-ing and AND-ing all bytes together.
uint8_t oredBytes = 0x00;
uint8_t andedBytes = 0xff;
for (uint8_t byte : m8)
{
oredBytes |= byte;
andedBytes &= byte;
}
return (oredBytes != 0x00) && (andedBytes != 0xff);
}
} // namespace MeshCoP
} // namespace ot
+10
View File
@@ -75,6 +75,16 @@ public:
*/
Error GenerateRandom(void);
/**
* Indicates whether or not the Extended PAN Identifier is valid.
*
* The all-zeros and all-ones values are reserved and disallowed by the Thread specification.
*
* @retval TRUE If the Extended PAN Identifier is valid.
* @retval FALSE If the Extended PAN Identifier is not valid.
*/
bool IsValid(void) const;
} OT_TOOL_PACKED_END;
} // namespace MeshCoP
+18 -1
View File
@@ -58,7 +58,24 @@ void NetworkNameTlv::SetNetworkName(const NameData &aNameData)
SetLength(len);
}
bool NetworkNameTlv::IsValid(void) const { return IsValidUtf8String(mNetworkName, GetLength()); }
bool NetworkNameTlv::IsValid(void) const
{
bool isValid = false;
#if OPENTHREAD_CONFIG_ALLOW_EMPTY_NETWORK_NAME
if (GetLength() == 0)
{
ExitNow(isValid = true);
}
#endif
VerifyOrExit(IsValueInRange<uint8_t>(GetLength(), 1, NetworkName::kMaxSize));
VerifyOrExit(IsValidUtf8String(mNetworkName, GetLength()));
isValid = true;
exit:
return isValid;
}
Error SteeringDataTlv::CopyTo(SteeringData &aSteeringData) const
{
+16
View File
@@ -93,6 +93,22 @@ public:
*/
Error InitFrom(const Prefix &aPrefix);
/**
* Indicates whether or not the Network Prefix is a locally assigned Unique Local Address (ULA) prefix, i.e., a
* `fd00::/8` prefix.
*
* RFC 4193 defines a ULA prefix as `fc00::/7` followed by the L bit, which is set to one for a locally assigned
* prefix. Section 3.2 of RFC 4193 defines a Global ID generation process for locally assigned prefixes only, so
* `fd00::/8` is the only form a conformant generator can produce. This is what `GenerateRandomUla()` produces.
*
* Note that this is intentionally stricter than `Prefix::IsUniqueLocal()`, which matches the entire `fc00::/7`
* ULA range and is used to recognize prefixes advertised by other devices.
*
* @retval TRUE If the Network Prefix is a locally assigned ULA prefix.
* @retval FALSE If the Network Prefix is not a locally assigned ULA prefix.
*/
bool IsLocallyAssignedUla(void) const { return m8[0] == 0xfd; }
} OT_TOOL_PACKED_END;
/**
+58
View File
@@ -36,6 +36,19 @@
namespace ot {
namespace MeshCoP {
template <size_t kSize> Error SetFromTlvsAndValidate(const uint8_t (&aTlvs)[kSize])
{
Dataset dataset;
// `SetFrom()` takes the length as `uint8_t`, so guard against an array
// that would be silently narrowed when passed to it.
static_assert(kSize <= Dataset::kMaxLength, "aTlvs is too long for a Dataset");
SuccessOrQuit(dataset.SetFrom(aTlvs, kSize));
return dataset.ValidateTlvs();
}
void TestDataset(void)
{
static const uint8_t kTlvBytes[] = {
@@ -57,6 +70,29 @@ void TestDataset(void)
0x00, 0x03, 0x00, 0x00, 0x1a, 0x00, 0x03, 0x00, 0x00, 0x1a,
};
// PAN ID TLV - the broadcast PAN ID is not allowed.
static const uint8_t kInvalidPanId[] = {0x01, 0x02, 0xff, 0xff};
static const uint8_t kValidPanId[] = {0x01, 0x02, 0xff, 0xfe};
// Extended PAN ID TLV - all-zeros and all-ones are disallowed.
static const uint8_t kInvalidExtPanIdAllZeros[] = {0x02, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
static const uint8_t kInvalidExtPanIdAllOnes[] = {0x02, 0x08, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
static const uint8_t kValidExtPanId[] = {0x02, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01};
// Mesh-Local Prefix TLV - must be a locally assigned ULA prefix (`fd00::/8`).
static const uint8_t kInvalidMeshLocalPrefix[] = {0x07, 0x08, 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00};
static const uint8_t kInvalidMeshLocalPrefixLBit[] = {0x07, 0x08, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
static const uint8_t kValidMeshLocalPrefix[] = {0x07, 0x08, 0xfd, 0xde, 0xad, 0x00, 0xbe, 0xef, 0x00, 0x00};
// Network Name TLV - 1 to 16 bytes of valid UTF-8 without control characters.
// A zero-length name is valid only when `ALLOW_EMPTY_NETWORK_NAME` is enabled.
static const uint8_t kEmptyNetworkName[] = {0x03, 0x00};
static const uint8_t kInvalidTooLongNetworkName[] = {0x03, 0x11, 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a',
'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a'};
static const uint8_t kInvalidUtf8NetworkName[] = {0x03, 0x03, 'a', 0x00, 'b'};
static const uint8_t kValidMaxLenNetworkName[] = {0x03, 0x10, 'a', 'a', 'a', 'a', 'a', 'a', 'a',
'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a'};
static const Tlv::Type kDatasetTlvTypes[] = {
Tlv::kChannel, Tlv::kPanId, Tlv::kExtendedPanId, Tlv::kNetworkName, Tlv::kPskc,
Tlv::kNetworkKey, Tlv::kMeshLocalPrefix, Tlv::kSecurityPolicy, Tlv::kActiveTimestamp,
@@ -173,6 +209,28 @@ void TestDataset(void)
SuccessOrQuit(dataset.SetFrom(kDuplicateChannels, sizeof(kDuplicateChannels) / 2));
SuccessOrQuit(dataset.ValidateTlvs());
// Invalid TLV values
VerifyOrQuit(SetFromTlvsAndValidate(kInvalidPanId) == kErrorParse);
VerifyOrQuit(SetFromTlvsAndValidate(kInvalidExtPanIdAllZeros) == kErrorParse);
VerifyOrQuit(SetFromTlvsAndValidate(kInvalidExtPanIdAllOnes) == kErrorParse);
VerifyOrQuit(SetFromTlvsAndValidate(kInvalidMeshLocalPrefix) == kErrorParse);
VerifyOrQuit(SetFromTlvsAndValidate(kInvalidMeshLocalPrefixLBit) == kErrorParse);
#if OPENTHREAD_CONFIG_ALLOW_EMPTY_NETWORK_NAME
SuccessOrQuit(SetFromTlvsAndValidate(kEmptyNetworkName));
#else
VerifyOrQuit(SetFromTlvsAndValidate(kEmptyNetworkName) == kErrorParse);
#endif
VerifyOrQuit(SetFromTlvsAndValidate(kInvalidTooLongNetworkName) == kErrorParse);
VerifyOrQuit(SetFromTlvsAndValidate(kInvalidUtf8NetworkName) == kErrorParse);
// Valid variants of the same TLVs
SuccessOrQuit(SetFromTlvsAndValidate(kValidPanId));
SuccessOrQuit(SetFromTlvsAndValidate(kValidExtPanId));
SuccessOrQuit(SetFromTlvsAndValidate(kValidMeshLocalPrefix));
SuccessOrQuit(SetFromTlvsAndValidate(kValidMaxLenNetworkName));
// Combining/Merging TLVs from two Datasets.
SuccessOrQuit(dataset.SetFrom(kTlvBytes, sizeof(kTlvBytes)));