Fix error handling in Darwin DNS-SD implementation to clean up properly. (#35725)
We had several codepaths where we could create a service ref but then do some
fallible operations before calling Add() on the context. If those failed, they
would call Finalize() on the context, which would delete the context, but _not_
DNSServiceRefDeallocate() the service ref.
The fix is to make sure that:
1) All cases where we create a service ref successfully immediately set it on
the context.
2) All context deletion goes through MdnsContexts::Delete, so that we make sure
to clean up the service ref if there is one.
diff --git a/src/platform/Darwin/DnssdContexts.cpp b/src/platform/Darwin/DnssdContexts.cpp
index ff86991..9d293c0 100644
--- a/src/platform/Darwin/DnssdContexts.cpp
+++ b/src/platform/Darwin/DnssdContexts.cpp
@@ -135,7 +135,8 @@
}
else
{
- chip::Platform::Delete(this);
+ // Ensure that we clean up our service ref, if any, correctly.
+ MdnsContexts::GetInstance().Delete(this);
}
return err;
@@ -161,33 +162,22 @@
}
}
-CHIP_ERROR MdnsContexts::Add(GenericContext * context, DNSServiceRef sdRef)
+CHIP_ERROR MdnsContexts::Add(GenericContext * context)
{
- VerifyOrReturnError(context != nullptr || sdRef != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
-
- if (context == nullptr)
+ VerifyOrReturnError(context != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
+ if (!context->serviceRef)
{
- DNSServiceRefDeallocate(sdRef);
+ Delete(context);
return CHIP_ERROR_INVALID_ARGUMENT;
}
- if (sdRef == nullptr)
- {
- chip::Platform::Delete(context);
- return CHIP_ERROR_INVALID_ARGUMENT;
- }
-
- auto err = DNSServiceSetDispatchQueue(sdRef, chip::DeviceLayer::PlatformMgrImpl().GetWorkQueue());
+ auto err = DNSServiceSetDispatchQueue(context->serviceRef, chip::DeviceLayer::PlatformMgrImpl().GetWorkQueue());
if (kDNSServiceErr_NoError != err)
{
- // We can't just use our Delete to deallocate the service ref here,
- // because our context may not have its serviceRef set yet.
- DNSServiceRefDeallocate(sdRef);
- chip::Platform::Delete(context);
+ Delete(context);
return Error::ToChipError(err);
}
- context->serviceRef = sdRef;
mContexts.push_back(context);
return CHIP_NO_ERROR;
@@ -242,6 +232,8 @@
return found ? CHIP_NO_ERROR : CHIP_ERROR_KEY_NOT_FOUND;
}
+// TODO: Perhaps this cleanup code should just move into ~GenericContext, and
+// the places that call this method should just Platform::Delete() the context?
void MdnsContexts::Delete(GenericContext * context)
{
if (context->serviceRef != nullptr)
diff --git a/src/platform/Darwin/DnssdImpl.cpp b/src/platform/Darwin/DnssdImpl.cpp
index 3740600..5cd3a2d 100644
--- a/src/platform/Darwin/DnssdImpl.cpp
+++ b/src/platform/Darwin/DnssdImpl.cpp
@@ -188,12 +188,17 @@
auto err = sdCtx->mHostNameRegistrar.Init(hostname, addressType, interfaceId);
VerifyOrReturnError(kDNSServiceErr_NoError == err, sdCtx->Finalize(err));
- DNSServiceRef sdRef;
- err = DNSServiceRegister(&sdRef, registerFlags, interfaceId, name, type, kLocalDot, hostname, htons(port), record.size(),
- record.data(), OnRegister, sdCtx);
- VerifyOrReturnError(kDNSServiceErr_NoError == err, sdCtx->Finalize(err));
+ err = DNSServiceRegister(&sdCtx->serviceRef, registerFlags, interfaceId, name, type, kLocalDot, hostname, htons(port),
+ record.size(), record.data(), OnRegister, sdCtx);
+ if (err != kDNSServiceErr_NoError)
+ {
+ // Just in case DNSServiceCreateConnection put garbage in the outparam
+ // on failure.
+ sdCtx->serviceRef = nullptr;
+ return sdCtx->Finalize(err);
+ }
- return MdnsContexts::GetInstance().Add(sdCtx, sdRef);
+ return MdnsContexts::GetInstance().Add(sdCtx);
}
static void OnBrowse(DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceId, DNSServiceErrorType err, const char * name,
@@ -216,16 +221,23 @@
CHIP_ERROR Browse(BrowseHandler * sdCtx, uint32_t interfaceId, const char * type)
{
auto err = DNSServiceCreateConnection(&sdCtx->serviceRef);
- VerifyOrReturnError(kDNSServiceErr_NoError == err, sdCtx->Finalize(err));
+ if (err != kDNSServiceErr_NoError)
+ {
+ // Just in case DNSServiceCreateConnection put garbage in the outparam
+ // on failure.
+ sdCtx->serviceRef = nullptr;
+ return sdCtx->Finalize(err);
+ }
// We will browse on both the local domain and the SRP domain.
+ // BrowseOnDomain guarantees it will Finalize() on failure.
ChipLogProgress(Discovery, "Browsing for: %s on local domain", StringOrNullMarker(type));
ReturnErrorOnFailure(BrowseOnDomain(sdCtx, interfaceId, type, kLocalDot));
ChipLogProgress(Discovery, "Browsing for: %s on %s domain", StringOrNullMarker(type), kSRPDot);
ReturnErrorOnFailure(BrowseOnDomain(sdCtx, interfaceId, type, kSRPDot));
- return MdnsContexts::GetInstance().Add(sdCtx, sdCtx->serviceRef);
+ return MdnsContexts::GetInstance().Add(sdCtx);
}
CHIP_ERROR Browse(void * context, DnssdBrowseCallback callback, uint32_t interfaceId, const char * type,
@@ -380,10 +392,17 @@
StringOrNullMarker(name), StringOrNullMarker(domain), interfaceId);
auto err = DNSServiceCreateConnection(&sdCtx->serviceRef);
- VerifyOrReturnError(kDNSServiceErr_NoError == err, sdCtx->Finalize(err));
+ if (err != kDNSServiceErr_NoError)
+ {
+ // Just in case DNSServiceCreateConnection put garbage in the outparam
+ // on failure.
+ sdCtx->serviceRef = nullptr;
+ return sdCtx->Finalize(err);
+ }
// If we have a single domain from a browse, we will use that for the Resolve.
// Otherwise we will try to resolve using both the local domain and the SRP domain.
+ // ResolveWithContext guarantees it will Finalize() on failure.
if (domain != nullptr)
{
ReturnErrorOnFailure(ResolveWithContext(sdCtx, interfaceId, type, name, domain, &sdCtx->resolveContextWithNonSRPType));
@@ -400,7 +419,7 @@
sdCtx->shouldStartSRPTimerForResolve = true;
}
- auto retval = MdnsContexts::GetInstance().Add(sdCtx, sdCtx->serviceRef);
+ auto retval = MdnsContexts::GetInstance().Add(sdCtx);
if (retval == CHIP_NO_ERROR)
{
(*(sdCtx->consumerCounter))++;
diff --git a/src/platform/Darwin/DnssdImpl.h b/src/platform/Darwin/DnssdImpl.h
index 2304407..d4cd05f 100644
--- a/src/platform/Darwin/DnssdImpl.h
+++ b/src/platform/Darwin/DnssdImpl.h
@@ -43,7 +43,13 @@
{
ContextType type;
void * context;
- DNSServiceRef serviceRef;
+ // When using a GenericContext, if a DNSServiceRef is created successfully
+ // API consumers must ensure that it gets set as serviceRef on the context
+ // immediately, before any other operations that might fail can happen.
+ //
+ // In all cases, once a context has been created, Finalize() must be called
+ // on it to clean it up properly.
+ DNSServiceRef serviceRef = nullptr;
virtual ~GenericContext() {}
@@ -69,7 +75,8 @@
~MdnsContexts();
static MdnsContexts & GetInstance() { return sInstance.get(); }
- CHIP_ERROR Add(GenericContext * context, DNSServiceRef sdRef);
+ // The context being added is expected to have a valid serviceRef.
+ CHIP_ERROR Add(GenericContext * context);
CHIP_ERROR Remove(GenericContext * context);
CHIP_ERROR RemoveAllOfType(ContextType type);
CHIP_ERROR Has(GenericContext * context);