[mdns] add validation for DNS names in mDNS APIs (#12039)

This commit adds `Dns::Name::ValidateName()` and `ValidateLabel()`
helper methods to validate a DNS name or label.

These methods are used at the entry of the mDNS `Register*()` and
`Unregister*()` public APIs to validate the provided host, service,
and key names. This prevents issues with malformed names and improves
the robustness of the mDNS module.

Includes unit tests for the new validation methods.
This commit is contained in:
Abtin Keshavarzian
2025-10-17 21:25:42 -07:00
committed by GitHub
parent ca9a731e4f
commit 99f510a16c
7 changed files with 237 additions and 8 deletions
+1 -1
View File
@@ -52,7 +52,7 @@ extern "C" {
*
* @note This number versions both OpenThread platform and user APIs.
*/
#define OPENTHREAD_API_VERSION (541)
#define OPENTHREAD_API_VERSION (542)
/**
* @addtogroup api-instance
+6
View File
@@ -309,6 +309,7 @@ otError otMdnsSetLocalHostName(otInstance *aInstance, const char *aName);
*
* @retval OT_ERROR_NONE Successfully started registration. @p aCallback will report the outcome.
* @retval OT_ERROR_INVALID_STATE mDNS module is not enabled.
* @retval OT_ERROR_INVALID_ARGS The name in @p aHost is invalid.
*/
otError otMdnsRegisterHost(otInstance *aInstance,
const otMdnsHost *aHost,
@@ -333,6 +334,7 @@ otError otMdnsRegisterHost(otInstance *aInstance,
*
* @retval OT_ERROR_NONE Successfully unregistered host.
* @retval OT_ERROR_INVALID_STATE mDNS module is not enabled.
* @retval OT_ERROR_INVALID_ARGS The name in @p aHost is invalid.
*/
otError otMdnsUnregisterHost(otInstance *aInstance, const otMdnsHost *aHost);
@@ -372,6 +374,7 @@ otError otMdnsUnregisterHost(otInstance *aInstance, const otMdnsHost *aHost);
*
* @retval OT_ERROR_NONE Successfully started registration. @p aCallback will report the outcome.
* @retval OT_ERROR_INVALID_STATE mDNS module is not enabled.
* @retval OT_ERROR_INVALID_ARGS A name in @p aService (instance, service type, sub-types, or host) is not valid.
*/
otError otMdnsRegisterService(otInstance *aInstance,
const otMdnsService *aService,
@@ -399,6 +402,7 @@ otError otMdnsRegisterService(otInstance *aInstance,
*
* @retval OT_ERROR_NONE Successfully unregistered service.
* @retval OT_ERROR_INVALID_STATE mDNS module is not enabled.
* @retval OT_ERROR_INVALID_ARGS A name in @p aService (instance or service type) is not valid.
*/
otError otMdnsUnregisterService(otInstance *aInstance, const otMdnsService *aService);
@@ -429,6 +433,7 @@ otError otMdnsUnregisterService(otInstance *aInstance, const otMdnsService *aSer
*
* @retval OT_ERROR_NONE Successfully started registration. @p aCallback will report the outcome.
* @retval OT_ERROR_INVALID_STATE mDNS module is not enabled.
* @retval OT_ERROR_INVALID_ARGS A name in @p aKey is not valid.
*/
otError otMdnsRegisterKey(otInstance *aInstance,
const otMdnsKey *aKey,
@@ -457,6 +462,7 @@ otError otMdnsRegisterKey(otInstance *aInstance,
*
* @retval OT_ERROR_NONE Successfully unregistered key
* @retval OT_ERROR_INVALID_STATE mDNS module is not enabled.
* @retval OT_ERROR_INVALID_ARGS A name in @p aKey is not valid.
*/
otError otMdnsUnregisterKey(otInstance *aInstance, const otMdnsKey *aKey);
+63
View File
@@ -746,6 +746,69 @@ exit:
return error;
}
Error Name::ValidateLabel(const char *aLabel)
{
Error error = kErrorInvalidArgs;
uint16_t length;
VerifyOrExit(aLabel != nullptr);
length = StringLength(aLabel, kMaxLabelSize);
VerifyOrExit((0 < length) && (length <= kMaxLabelLength));
error = kErrorNone;
exit:
return error;
}
Error Name::ValidateName(const char *aName)
{
Error error = kErrorNone;
uint16_t index = 0;
uint16_t labelStartIndex = 0;
char ch;
uint16_t length;
VerifyOrExit(aName != nullptr, error = kErrorInvalidArgs);
length = StringLength(aName, kMaxNameSize);
VerifyOrExit(length > 0, error = kErrorInvalidArgs);
VerifyOrExit(length <= kMaxNameLength, error = kErrorInvalidArgs);
do
{
ch = aName[index];
if ((ch == kNullChar) || (ch == kLabelSeparatorChar))
{
length = index - labelStartIndex;
VerifyOrExit(length <= kMaxLabelLength, error = kErrorInvalidArgs);
if (length == 0)
{
// Empty label (e.g., consecutive dots) is invalid, but we
// allow for two cases: (1) where `aName` ends with a dot
// (`length` is zero but we are at end of `aName` string
// and `ch` is null char. (2) if `aName` is just "." (we
// see a dot at index 0, and index 1 is null char).
VerifyOrExit((ch == kNullChar) || ((index == 0) && (aName[1] == kNullChar)), error = kErrorInvalidArgs);
error = kErrorNone;
ExitNow();
}
labelStartIndex = index + 1;
}
index++;
} while (ch != kNullChar);
exit:
return error;
}
bool Name::IsSubDomainOf(const char *aName, const char *aDomain)
{
bool match = false;
+31
View File
@@ -1000,6 +1000,37 @@ public:
return ExtractLabels(aName, aSuffixName, aName, kNameBufferSize);
}
/**
* Validates a DNS label.
*
* A label must be between 1 and 63 (`kMaxLabelLength`) characters long.
*
* @param[in] aLabel The string containing the label.
*
* @retval kErrorNone The label is valid.
* @retval kErrorInvalidArgs The label is not valid (e.g., is `nullptr`, empty, or too long).
*/
static Error ValidateLabel(const char *aLabel);
/**
* Validates a DNS name.
*
* A DNS name is a sequence of labels separated by dots.
*
* This method validates the following rules:
* - The name string is not `nullptr`.
* - The total length of the name is between 1 and 254 (`kMaxNameLength`) characters.
* - Each label is at most 63 (`kMaxLabelLength`) characters long.
* - Empty labels (e.g., consecutive dots `..`) are disallowed, except for a single trailing dot `.` at the end of
* the name, or if the name is just ".".
*
* @param[in] aName The string containing the name.
*
* @retval kErrorNone The name is valid.
* @retval kErrorInvalidArgs The name is not valid.
*/
static Error ValidateName(const char *aName);
/**
* Tests if a DNS name is a sub-domain of a given domain.
*
+96 -7
View File
@@ -209,6 +209,52 @@ exit:
#endif // OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
Error Core::ValidateHostName(const Host &aHost) const { return Name::ValidateName(aHost.mHostName); }
Error Core::ValidateServiceNames(const Service &aService, bool aCheckHostAndSubTypeLabels) const
{
Error error;
SuccessOrExit(error = Name::ValidateLabel(aService.mServiceInstance));
SuccessOrExit(error = Name::ValidateName(aService.mServiceType));
if (aCheckHostAndSubTypeLabels && aService.mHostName != nullptr)
{
SuccessOrExit(error = Name::ValidateName(aService.mHostName));
}
if (aCheckHostAndSubTypeLabels && (aService.mSubTypeLabelsLength > 0))
{
VerifyOrExit(aService.mSubTypeLabels != nullptr, error = kErrorInvalidArgs);
for (uint16_t index = 0; index < aService.mSubTypeLabelsLength; index++)
{
SuccessOrExit(error = Name::ValidateLabel(aService.mSubTypeLabels[index]));
}
}
exit:
return error;
}
Error Core::ValidateKeyName(const Key &aKey) const
{
Error error;
if (IsKeyForService(aKey))
{
SuccessOrExit(error = Name::ValidateName(aKey.mServiceType));
error = Name::ValidateLabel(aKey.mName);
}
else
{
error = Name::ValidateName(aKey.mName);
}
exit:
return error;
}
template <typename EntryType, typename ItemInfo>
Error Core::Register(const ItemInfo &aItemInfo, RequestId aRequestId, RegisterCallback aCallback)
{
@@ -252,27 +298,70 @@ exit:
Error Core::RegisterHost(const Host &aHost, RequestId aRequestId, RegisterCallback aCallback)
{
return Register<HostEntry>(aHost, aRequestId, aCallback);
Error error;
SuccessOrExit(error = ValidateHostName(aHost));
error = Register<HostEntry>(aHost, aRequestId, aCallback);
exit:
return error;
}
Error Core::UnregisterHost(const Host &aHost) { return Unregister<HostEntry>(aHost); }
Error Core::UnregisterHost(const Host &aHost)
{
Error error;
SuccessOrExit(error = ValidateHostName(aHost));
error = Unregister<HostEntry>(aHost);
exit:
return error;
}
Error Core::RegisterService(const Service &aService, RequestId aRequestId, RegisterCallback aCallback)
{
return Register<ServiceEntry>(aService, aRequestId, aCallback);
Error error;
SuccessOrExit(error = ValidateServiceNames(aService, /* aCheckHostAndSubTypeLabels */ true));
error = Register<ServiceEntry>(aService, aRequestId, aCallback);
exit:
return error;
}
Error Core::UnregisterService(const Service &aService) { return Unregister<ServiceEntry>(aService); }
Error Core::UnregisterService(const Service &aService)
{
Error error;
SuccessOrExit(error = ValidateServiceNames(aService, /* aCheckHostAndSubTypeLabels */ false));
error = Unregister<ServiceEntry>(aService);
exit:
return error;
}
Error Core::RegisterKey(const Key &aKey, RequestId aRequestId, RegisterCallback aCallback)
{
return IsKeyForService(aKey) ? Register<ServiceEntry>(aKey, aRequestId, aCallback)
: Register<HostEntry>(aKey, aRequestId, aCallback);
Error error;
SuccessOrExit(error = ValidateKeyName(aKey));
error = IsKeyForService(aKey) ? Register<ServiceEntry>(aKey, aRequestId, aCallback)
: Register<HostEntry>(aKey, aRequestId, aCallback);
exit:
return error;
}
Error Core::UnregisterKey(const Key &aKey)
{
return IsKeyForService(aKey) ? Unregister<ServiceEntry>(aKey) : Unregister<HostEntry>(aKey);
Error error;
SuccessOrExit(error = ValidateKeyName(aKey));
error = IsKeyForService(aKey) ? Unregister<ServiceEntry>(aKey) : Unregister<HostEntry>(aKey);
exit:
return error;
}
#if OPENTHREAD_CONFIG_MULTICAST_DNS_ENTRY_ITERATION_API_ENABLE
+9
View File
@@ -313,6 +313,7 @@ public:
*
* @retval kErrorNone Successfully started registration. @p aCallback will report the outcome.
* @retval kErrorInvalidState mDNS module is not enabled.
* @retval kErrorInvalidArgs The host name in @p aHost is not valid.
*/
Error RegisterHost(const Host &aHost, RequestId aRequestId, RegisterCallback aCallback);
@@ -333,6 +334,7 @@ public:
*
* @retval kErrorNone Successfully unregistered host.
* @retval kErrorInvalidState mDNS module is not enabled.
* @retval kErrorInvalidArgs The host name in @p aHost is not valid.
*/
Error UnregisterHost(const Host &aHost);
@@ -370,6 +372,7 @@ public:
*
* @retval kErrorNone Successfully started registration. @p aCallback will report the outcome.
* @retval kErrorInvalidState mDNS module is not enabled.
* @retval kErrorInvalidArgs A name in @p aService (instance, service type, sub-types, or host) is not valid.
*/
Error RegisterService(const Service &aService, RequestId aRequestId, RegisterCallback aCallback);
@@ -393,6 +396,7 @@ public:
*
* @retval kErrorNone Successfully unregistered service.
* @retval kErrorInvalidState mDNS module is not enabled.
* @retval kErrorInvalidArgs A name in @p aService (instance, service type) is not valid.
*/
Error UnregisterService(const Service &aService);
@@ -421,6 +425,7 @@ public:
*
* @retval kErrorNone Successfully started registration. @p aCallback will report the outcome.
* @retval kErrorInvalidState mDNS module is not enabled.
* @retval kErrorInvalidArgs A name in @p aKey is not valid.
*/
Error RegisterKey(const Key &aKey, RequestId aRequestId, RegisterCallback aCallback);
@@ -445,6 +450,7 @@ public:
*
* @retval kErrorNone Successfully unregistered key
* @retval kErrorInvalidState mDNS module is not enabled.
* @retval kErrorInvalidArgs A name in @p aKey is not valid.
*/
Error UnregisterKey(const Key &aKey);
@@ -2313,6 +2319,9 @@ private:
void AfterInstanceInit(void);
Error SetEnabled(bool aEnable, uint32_t aInfraIfIndex, Requester aRequester);
Error ValidateHostName(const Host &aHost) const;
Error ValidateServiceNames(const Service &aService, bool aCheckHostAndSubTypeLabels) const;
Error ValidateKeyName(const Key &aKey) const;
void HandleInfraIfStateChanged(void);
void HandleHostAddressEvent(const Ip6::Address &aAddress, bool aAdded, uint32_t aInfraIfIndex);
void HandleHostAddressRemoveAll(uint32_t aInfraIfIndex);
+31
View File
@@ -591,6 +591,37 @@ void TestDnsName(void)
VerifyOrQuit(!dnsName.Matches("Name.With.Dot", "_srv._tcp", "local."));
VerifyOrQuit(!dnsName.Matches("Name.With.Dot", "_srv._udp", "arpa."));
printf("----------------------------------------------------------------\n");
printf("Name::ValidateLabel\n");
SuccessOrQuit(Dns::Name::ValidateLabel("a"));
SuccessOrQuit(Dns::Name::ValidateLabel("hello"));
SuccessOrQuit(Dns::Name::ValidateLabel("012345678901234567890123456789012345678901234567890123456789012")); // 63
VerifyOrQuit(Dns::Name::ValidateLabel("0123456789012345678901234567890123456789012345678901234567890123") ==
kErrorInvalidArgs);
VerifyOrQuit(Dns::Name::ValidateLabel("") == kErrorInvalidArgs);
SuccessOrQuit(Dns::Name::ValidateName("a"));
SuccessOrQuit(Dns::Name::ValidateName("a.b.c"));
SuccessOrQuit(Dns::Name::ValidateName("a.b.c."));
SuccessOrQuit(Dns::Name::ValidateName("a.b.012345678901234567890123456789012345678901234567890123456789012."));
SuccessOrQuit(Dns::Name::ValidateName("."));
// Empty labels
VerifyOrQuit(Dns::Name::ValidateName("") == kErrorInvalidArgs);
VerifyOrQuit(Dns::Name::ValidateName("a..b") == kErrorInvalidArgs);
VerifyOrQuit(Dns::Name::ValidateName(".a.b") == kErrorInvalidArgs);
VerifyOrQuit(Dns::Name::ValidateName("a.b..") == kErrorInvalidArgs);
// Long labels or names
VerifyOrQuit(Dns::Name::ValidateName("a.b.0123456789012345678901234567890123456789012345678901234567890123.") ==
kErrorInvalidArgs);
VerifyOrQuit(Dns::Name::ValidateName("012345678901234567890123456789012345678901234567890123456789012."
"012345678901234567890123456789012345678901234567890123456789012."
"012345678901234567890123456789012345678901234567890123456789012."
"012345678901234567890123456789012345678901234567890123456789012") ==
kErrorInvalidArgs);
message->Free();
testFreeInstance(instance);
}