a9e10b
From 3c22fb00593e78ff27210b2cb708128940077a49 Mon Sep 17 00:00:00 2001
a9e10b
From: Mark Andrews <marka@isc.org>
a9e10b
Date: Tue, 1 Mar 2022 09:48:05 +1100
a9e10b
Subject: [PATCH] Add additional name checks when using a forwarder
a9e10b
a9e10b
When using a forwarder, check that the owner name of response
a9e10b
records are within the bailiwick of the forwarded name space.
a9e10b
a9e10b
(cherry picked from commit e8df2802ac62016ea68585893eb4310fc3329028)
a9e10b
a9e10b
Check that the forward declaration is unchanged and not overridden
a9e10b
a9e10b
If we are using a fowarder, in addition to checking that names to
a9e10b
be cached are subdomains of the forwarded namespace, we must also
a9e10b
check that there are no subsidiary forwarded namespaces which would
a9e10b
take precedence. To be safe, we don't cache any responses if the
a9e10b
forwarding configuration has changed since the query was sent.
a9e10b
a9e10b
(cherry picked from commit 590f8698fc876d6d72f75cf35359e7546c3af972)
a9e10b
a9e10b
Check cached names for possible "forward only" clause
a9e10b
a9e10b
When caching additional and glue data *not* from a forwarder, we must
a9e10b
check that there is no "forward only" clause covering the owner name
a9e10b
that would take precedence.  Such names would normally be allowed by
a9e10b
baliwick rules, but a "forward only" zone introduces a new baliwick
a9e10b
scope.
a9e10b
a9e10b
(cherry picked from commit 4a144fae16e70517be894a971cef1d085ee68ebe)
a9e10b
a9e10b
Look for zones deeper than the current domain or forward name
a9e10b
a9e10b
When caching glue, we need to ensure that there is no closer
a9e10b
source of truth for the name. If the owner name for the glue
a9e10b
record would be answered by a locally configured zone, do not
a9e10b
cache.
a9e10b
a9e10b
(cherry picked from commit 42f8c538d3fb9d075b98d82688aeb71621798754)
a9e10b
a9e10b
Avoid use of compound literals
a9e10b
a9e10b
Compound literals are not used in BIND 9.11, in order to ensure backward
a9e10b
compatibility with ancient compilers.  Rework the relevant parts of the
a9e10b
BIND 9.11 backport of the CVE-2021-25220 fix so that compound literals
a9e10b
are not used.
a9e10b
a9e10b
(cherry picked from commit d4b1efbcbd4dfb8c6ef303968992440c5bdeed15)
a9e10b
---
a9e10b
 lib/dns/resolver.c | 130 +++++++++++++++++++++++++++++++++++++++++++--
a9e10b
 1 file changed, 125 insertions(+), 5 deletions(-)
a9e10b
a9e10b
diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c
a9e10b
index 7231e25..a55b5fd 100644
a9e10b
--- a/lib/dns/resolver.c
a9e10b
+++ b/lib/dns/resolver.c
a9e10b
@@ -66,6 +66,7 @@
a9e10b
 #include <dns/stats.h>
a9e10b
 #include <dns/tsig.h>
a9e10b
 #include <dns/validator.h>
a9e10b
+#include <dns/zone.h>
a9e10b
 
a9e10b
 #ifdef WANT_QUERYTRACE
a9e10b
 #define RTRACE(m)       isc_log_write(dns_lctx, \
a9e10b
@@ -318,6 +319,8 @@ struct fetchctx {
a9e10b
 	isc_boolean_t			ns_ttl_ok;
a9e10b
 	isc_uint32_t			ns_ttl;
a9e10b
 	isc_counter_t *			qc;
a9e10b
+	dns_fixedname_t			fwdfname;
a9e10b
+	dns_name_t			*fwdname;
a9e10b
 
a9e10b
 	/*%
a9e10b
 	 * The number of events we're waiting for.
a9e10b
@@ -3331,6 +3334,7 @@ fctx_getaddresses(fetchctx_t *fctx, isc_boolean_t badcache) {
a9e10b
 		if (result == ISC_R_SUCCESS) {
a9e10b
 			fwd = ISC_LIST_HEAD(forwarders->fwdrs);
a9e10b
 			fctx->fwdpolicy = forwarders->fwdpolicy;
a9e10b
+			dns_name_copy(domain, fctx->fwdname, NULL);
a9e10b
 			if (fctx->fwdpolicy == dns_fwdpolicy_only &&
a9e10b
 			    isstrictsubdomain(domain, &fctx->domain)) {
a9e10b
 				fcount_decr(fctx);
a9e10b
@@ -4363,6 +4367,9 @@ fctx_create(dns_resolver_t *res, dns_name_t *name, dns_rdatatype_t type,
a9e10b
 	fctx->restarts = 0;
a9e10b
 	fctx->querysent = 0;
a9e10b
 	fctx->referrals = 0;
a9e10b
+
a9e10b
+	fctx->fwdname = dns_fixedname_initname(&fctx->fwdfname);
a9e10b
+
a9e10b
 	TIME_NOW(&fctx->start);
a9e10b
 	fctx->timeouts = 0;
a9e10b
 	fctx->lamecount = 0;
a9e10b
@@ -4415,8 +4422,10 @@ fctx_create(dns_resolver_t *res, dns_name_t *name, dns_rdatatype_t type,
a9e10b
 		domain = dns_fixedname_initname(&fixed);
a9e10b
 		result = dns_fwdtable_find2(fctx->res->view->fwdtable, fwdname,
a9e10b
 					    domain, &forwarders);
a9e10b
-		if (result == ISC_R_SUCCESS)
a9e10b
+		if (result == ISC_R_SUCCESS) {
a9e10b
 			fctx->fwdpolicy = forwarders->fwdpolicy;
a9e10b
+			dns_name_copy(domain, fctx->fwdname, NULL);
a9e10b
+		}
a9e10b
 
a9e10b
 		if (fctx->fwdpolicy != dns_fwdpolicy_only) {
a9e10b
 			/*
a9e10b
@@ -6165,6 +6174,112 @@ mark_related(dns_name_t *name, dns_rdataset_t *rdataset,
a9e10b
 		rdataset->attributes |= DNS_RDATASETATTR_EXTERNAL;
a9e10b
 }
a9e10b
 
a9e10b
+/*
a9e10b
+ * Returns true if 'name' is external to the namespace for which
a9e10b
+ * the server being queried can answer, either because it's not a
a9e10b
+ * subdomain or because it's below a forward declaration or a
a9e10b
+ * locally served zone.
a9e10b
+ */
a9e10b
+static inline isc_boolean_t
a9e10b
+name_external(dns_name_t *name, dns_rdatatype_t type, fetchctx_t *fctx) {
a9e10b
+	isc_result_t result;
a9e10b
+	dns_forwarders_t *forwarders = NULL;
a9e10b
+	dns_fixedname_t fixed, zfixed;
a9e10b
+	dns_name_t *fname = dns_fixedname_initname(&fixed);
a9e10b
+	dns_name_t *zfname = dns_fixedname_initname(&zfixed);
a9e10b
+	dns_name_t *apex = NULL;
a9e10b
+	dns_name_t suffix;
a9e10b
+	dns_zone_t *zone = NULL;
a9e10b
+	unsigned int labels;
a9e10b
+	dns_namereln_t rel;
a9e10b
+	/*
a9e10b
+	 * The following two variables do not influence code flow; they are
a9e10b
+	 * only necessary for calling dns_name_fullcompare().
a9e10b
+	 */
a9e10b
+	int _orderp = 0;
a9e10b
+	unsigned int _nlabelsp = 0;
a9e10b
+
a9e10b
+	apex = ISFORWARDER(fctx->addrinfo) ? fctx->fwdname : &fctx->domain;
a9e10b
+
a9e10b
+	/*
a9e10b
+	 * The name is outside the queried namespace.
a9e10b
+	 */
a9e10b
+	rel = dns_name_fullcompare(name, apex, &_orderp, &_nlabelsp);
a9e10b
+	if (rel != dns_namereln_subdomain && rel != dns_namereln_equal) {
a9e10b
+		return (ISC_TRUE);
a9e10b
+	}
a9e10b
+
a9e10b
+	/*
a9e10b
+	 * If the record lives in the parent zone, adjust the name so we
a9e10b
+	 * look for the correct zone or forward clause.
a9e10b
+	 */
a9e10b
+	labels = dns_name_countlabels(name);
a9e10b
+	if (dns_rdatatype_atparent(type) && labels > 1U) {
a9e10b
+		dns_name_init(&suffix, NULL);
a9e10b
+		dns_name_getlabelsequence(name, 1, labels - 1, &suffix);
a9e10b
+		name = &suffix;
a9e10b
+	} else if (rel == dns_namereln_equal) {
a9e10b
+		/* If 'name' is 'apex', no further checking is needed. */
a9e10b
+		return (ISC_FALSE);
a9e10b
+	}
a9e10b
+
a9e10b
+	/*
a9e10b
+	 * If there is a locally served zone between 'apex' and 'name'
a9e10b
+	 * then don't cache.
a9e10b
+	 */
a9e10b
+	LOCK(&fctx->res->view->lock);
a9e10b
+	if (fctx->res->view->zonetable != NULL) {
a9e10b
+		unsigned int options = DNS_ZTFIND_NOEXACT;
a9e10b
+		result = dns_zt_find(fctx->res->view->zonetable, name, options,
a9e10b
+				     zfname, &zone);
a9e10b
+		if (zone != NULL) {
a9e10b
+			dns_zone_detach(&zone);
a9e10b
+		}
a9e10b
+		if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) {
a9e10b
+			if (dns_name_fullcompare(zfname, apex, &_orderp,
a9e10b
+						 &_nlabelsp) ==
a9e10b
+			    dns_namereln_subdomain)
a9e10b
+			{
a9e10b
+				UNLOCK(&fctx->res->view->lock);
a9e10b
+				return (ISC_TRUE);
a9e10b
+			}
a9e10b
+		}
a9e10b
+	}
a9e10b
+	UNLOCK(&fctx->res->view->lock);
a9e10b
+
a9e10b
+	/*
a9e10b
+	 * Look for a forward declaration below 'name'.
a9e10b
+	 */
a9e10b
+	result = dns_fwdtable_find2(fctx->res->view->fwdtable, name, fname,
a9e10b
+				    &forwarders);
a9e10b
+
a9e10b
+	if (ISFORWARDER(fctx->addrinfo)) {
a9e10b
+		/*
a9e10b
+		 * See if the forwarder declaration is better.
a9e10b
+		 */
a9e10b
+		if (result == ISC_R_SUCCESS) {
a9e10b
+			return (!dns_name_equal(fname, fctx->fwdname));
a9e10b
+		}
a9e10b
+
a9e10b
+		/*
a9e10b
+		 * If the lookup failed, the configuration must have
a9e10b
+		 * changed: play it safe and don't cache.
a9e10b
+		 */
a9e10b
+		return (ISC_TRUE);
a9e10b
+	} else if (result == ISC_R_SUCCESS &&
a9e10b
+		   forwarders->fwdpolicy == dns_fwdpolicy_only &&
a9e10b
+		   !ISC_LIST_EMPTY(forwarders->fwdrs))
a9e10b
+	{
a9e10b
+		/*
a9e10b
+		 * If 'name' is covered by a 'forward only' clause then we
a9e10b
+		 * can't cache this repsonse.
a9e10b
+		 */
a9e10b
+		return (ISC_TRUE);
a9e10b
+	}
a9e10b
+
a9e10b
+	return (ISC_FALSE);
a9e10b
+}
a9e10b
+
a9e10b
 static isc_result_t
a9e10b
 check_section(void *arg, dns_name_t *addname, dns_rdatatype_t type,
a9e10b
 	      dns_section_t section)
a9e10b
@@ -6191,7 +6306,7 @@ check_section(void *arg, dns_name_t *addname, dns_rdatatype_t type,
a9e10b
 	result = dns_message_findname(fctx->rmessage, section, addname,
a9e10b
 				      dns_rdatatype_any, 0, &name, NULL);
a9e10b
 	if (result == ISC_R_SUCCESS) {
a9e10b
-		external = ISC_TF(!dns_name_issubdomain(name, &fctx->domain));
a9e10b
+		external = ISC_TF(name_external(name, type, fctx));
a9e10b
 		if (type == dns_rdatatype_a) {
a9e10b
 			for (rdataset = ISC_LIST_HEAD(name->list);
a9e10b
 			     rdataset != NULL;
a9e10b
@@ -7041,6 +7156,13 @@ answer_response(fetchctx_t *fctx) {
a9e10b
 			break;
a9e10b
 
a9e10b
 		case dns_namereln_subdomain:
a9e10b
+			/*
a9e10b
+			 * Don't accept DNAME from parent namespace.
a9e10b
+			 */
a9e10b
+			if (name_external(name, dns_rdatatype_dname, fctx)) {
a9e10b
+				continue;
a9e10b
+			}
a9e10b
+
a9e10b
 			/*
a9e10b
 			 * In-scope DNAME records must have at least
a9e10b
 			 * as many labels as the domain being queried.
a9e10b
@@ -7269,11 +7391,9 @@ answer_response(fetchctx_t *fctx) {
a9e10b
 	 */
a9e10b
 	result = dns_message_firstname(message, DNS_SECTION_AUTHORITY);
a9e10b
 	while (!done && result == ISC_R_SUCCESS) {
a9e10b
-		isc_boolean_t external;
a9e10b
 		name = NULL;
a9e10b
 		dns_message_currentname(message, DNS_SECTION_AUTHORITY, &name);
a9e10b
-		external = ISC_TF(!dns_name_issubdomain(name, &fctx->domain));
a9e10b
-		if (!external) {
a9e10b
+		if (!name_external(name, dns_rdatatype_ns, fctx)) {
a9e10b
 			/*
a9e10b
 			 * We expect to find NS or SIG NS rdatasets, and
a9e10b
 			 * nothing else.
a9e10b
-- 
a9e10b
2.38.1
a9e10b