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