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