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