bcfc7f
From 165181b794e185af8621300e2a68777a04af8358 Mon Sep 17 00:00:00 2001
bcfc7f
From: Petr Mensik <pemensik@redhat.com>
bcfc7f
Date: Fri, 15 May 2020 14:55:26 +0200
bcfc7f
Subject: [PATCH] CVE-2020-8616
bcfc7f
bcfc7f
5395.	[security]	Further limit the number of queries that can be
bcfc7f
			triggered from a request.  Root and TLD servers
bcfc7f
			are no longer exempt from max-recursion-queries.
bcfc7f
			Fetches for missing name server address records
bcfc7f
			are limited to 4 for any domain. (CVE-2020-8616)
bcfc7f
			[GL #1388]
bcfc7f
---
bcfc7f
 lib/dns/adb.c             | 33 +++++++++++++----------
bcfc7f
 lib/dns/include/dns/adb.h |  4 +++
bcfc7f
 lib/dns/resolver.c        | 55 ++++++++++++++++++++++++++-------------
bcfc7f
 3 files changed, 60 insertions(+), 32 deletions(-)
bcfc7f
bcfc7f
diff --git a/lib/dns/adb.c b/lib/dns/adb.c
bcfc7f
index 3d12221..ec183d0 100644
bcfc7f
--- a/lib/dns/adb.c
bcfc7f
+++ b/lib/dns/adb.c
bcfc7f
@@ -404,14 +404,13 @@ static void log_quota(dns_adbentry_t *entry, const char *fmt, ...)
bcfc7f
  */
bcfc7f
 #define FIND_WANTEVENT(fn)      (((fn)->options & DNS_ADBFIND_WANTEVENT) != 0)
bcfc7f
 #define FIND_WANTEMPTYEVENT(fn) (((fn)->options & DNS_ADBFIND_EMPTYEVENT) != 0)
bcfc7f
-#define FIND_AVOIDFETCHES(fn)   (((fn)->options & DNS_ADBFIND_AVOIDFETCHES) \
bcfc7f
-				 != 0)
bcfc7f
-#define FIND_STARTATZONE(fn)    (((fn)->options & DNS_ADBFIND_STARTATZONE) \
bcfc7f
-				 != 0)
bcfc7f
-#define FIND_HINTOK(fn)         (((fn)->options & DNS_ADBFIND_HINTOK) != 0)
bcfc7f
-#define FIND_GLUEOK(fn)         (((fn)->options & DNS_ADBFIND_GLUEOK) != 0)
bcfc7f
-#define FIND_HAS_ADDRS(fn)      (!ISC_LIST_EMPTY((fn)->list))
bcfc7f
-#define FIND_RETURNLAME(fn)     (((fn)->options & DNS_ADBFIND_RETURNLAME) != 0)
bcfc7f
+#define FIND_AVOIDFETCHES(fn)	(((fn)->options & DNS_ADBFIND_AVOIDFETCHES) != 0)
bcfc7f
+#define FIND_STARTATZONE(fn)	(((fn)->options & DNS_ADBFIND_STARTATZONE) != 0)
bcfc7f
+#define FIND_HINTOK(fn)		(((fn)->options & DNS_ADBFIND_HINTOK) != 0)
bcfc7f
+#define FIND_GLUEOK(fn)		(((fn)->options & DNS_ADBFIND_GLUEOK) != 0)
bcfc7f
+#define FIND_HAS_ADDRS(fn)	(!ISC_LIST_EMPTY((fn)->list))
bcfc7f
+#define FIND_RETURNLAME(fn)	(((fn)->options & DNS_ADBFIND_RETURNLAME) != 0)
bcfc7f
+#define FIND_NOFETCH(fn)	(((fn)->options & DNS_ADBFIND_NOFETCH) != 0)
bcfc7f
 
bcfc7f
 /*
bcfc7f
  * These are currently used on simple unsigned ints, so they are
bcfc7f
@@ -3155,21 +3154,26 @@ dns_adb_createfind2(dns_adb_t *adb, isc_task_t *task, isc_taskaction_t action,
bcfc7f
 		 * Listen to negative cache hints, and don't start
bcfc7f
 		 * another query.
bcfc7f
 		 */
bcfc7f
-		if (NCACHE_RESULT(result) || AUTH_NX(result))
bcfc7f
+		if (NCACHE_RESULT(result) || AUTH_NX(result)) {
bcfc7f
 			goto fetch;
bcfc7f
+		}
bcfc7f
 
bcfc7f
-		if (!NAME_FETCH_V6(adbname))
bcfc7f
+		if (!NAME_FETCH_V6(adbname)) {
bcfc7f
 			wanted_fetches |= DNS_ADBFIND_INET6;
bcfc7f
+		}
bcfc7f
 	}
bcfc7f
 
bcfc7f
  fetch:
bcfc7f
 	if ((WANT_INET(wanted_addresses) && NAME_HAS_V4(adbname)) ||
bcfc7f
 	    (WANT_INET6(wanted_addresses) && NAME_HAS_V6(adbname)))
bcfc7f
+	{
bcfc7f
 		have_address = true;
bcfc7f
-	else
bcfc7f
+	} else {
bcfc7f
 		have_address = false;
bcfc7f
-	if (wanted_fetches != 0 &&
bcfc7f
-	    ! (FIND_AVOIDFETCHES(find) && have_address)) {
bcfc7f
+	}
bcfc7f
+	if (wanted_fetches != 0 && !(FIND_AVOIDFETCHES(find) && have_address) &&
bcfc7f
+	    !FIND_NOFETCH(find))
bcfc7f
+	{
bcfc7f
 		/*
bcfc7f
 		 * We're missing at least one address family.  Either the
bcfc7f
 		 * caller hasn't instructed us to avoid fetches, or we don't
bcfc7f
@@ -3177,8 +3181,9 @@ dns_adb_createfind2(dns_adb_t *adb, isc_task_t *task, isc_taskaction_t action,
bcfc7f
 		 * be acceptable so we have to launch fetches.
bcfc7f
 		 */
bcfc7f
 
bcfc7f
-		if (FIND_STARTATZONE(find))
bcfc7f
+		if (FIND_STARTATZONE(find)) {
bcfc7f
 			start_at_zone = true;
bcfc7f
+		}
bcfc7f
 
bcfc7f
 		/*
bcfc7f
 		 * Start V4.
bcfc7f
diff --git a/lib/dns/include/dns/adb.h b/lib/dns/include/dns/adb.h
bcfc7f
index ca35bac..3e27c9e 100644
bcfc7f
--- a/lib/dns/include/dns/adb.h
bcfc7f
+++ b/lib/dns/include/dns/adb.h
bcfc7f
@@ -207,6 +207,10 @@ struct dns_adbfind {
bcfc7f
  *      lame for this query.
bcfc7f
  */
bcfc7f
 #define DNS_ADBFIND_OVERQUOTA		0x00000400
bcfc7f
+/*%
bcfc7f
+ *	Don't perform a fetch even if there are no address records available.
bcfc7f
+ */
bcfc7f
+#define DNS_ADBFIND_NOFETCH		0x00000800
bcfc7f
 
bcfc7f
 /*%
bcfc7f
  * The answers to queries come back as a list of these.
bcfc7f
diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c
bcfc7f
index 164fc01..79ad212 100644
bcfc7f
--- a/lib/dns/resolver.c
bcfc7f
+++ b/lib/dns/resolver.c
bcfc7f
@@ -173,6 +173,14 @@
bcfc7f
 #define DEFAULT_MAX_QUERIES 75
bcfc7f
 #endif
bcfc7f
 
bcfc7f
+/*
bcfc7f
+ * After NS_FAIL_LIMIT attempts to fetch a name server address,
bcfc7f
+ * if the number of addresses in the NS RRset exceeds NS_RR_LIMIT,
bcfc7f
+ * stop trying to fetch, in order to avoid wasting resources.
bcfc7f
+ */
bcfc7f
+#define NS_FAIL_LIMIT 4
bcfc7f
+#define NS_RR_LIMIT   5
bcfc7f
+
bcfc7f
 /* Number of hash buckets for zone counters */
bcfc7f
 #ifndef RES_DOMAIN_BUCKETS
bcfc7f
 #define RES_DOMAIN_BUCKETS	523
bcfc7f
@@ -3121,8 +3129,7 @@ sort_finds(dns_adbfindlist_t *findlist, unsigned int bias) {
bcfc7f
 static void
bcfc7f
 findname(fetchctx_t *fctx, dns_name_t *name, in_port_t port,
bcfc7f
 	 unsigned int options, unsigned int flags, isc_stdtime_t now,
bcfc7f
-	 bool *overquota, bool *need_alternate)
bcfc7f
-{
bcfc7f
+	 bool *overquota, bool *need_alternate, unsigned int *no_addresses) {
bcfc7f
 	dns_adbaddrinfo_t *ai;
bcfc7f
 	dns_adbfind_t *find;
bcfc7f
 	dns_resolver_t *res;
bcfc7f
@@ -3210,7 +3217,12 @@ findname(fetchctx_t *fctx, dns_name_t *name, in_port_t port,
bcfc7f
 			      find->result_v6 != DNS_R_NXDOMAIN) ||
bcfc7f
 			     (res->dispatches6 == NULL &&
bcfc7f
 			      find->result_v4 != DNS_R_NXDOMAIN)))
bcfc7f
+			{
bcfc7f
 				*need_alternate = true;
bcfc7f
+			}
bcfc7f
+			if (no_addresses != NULL) {
bcfc7f
+				(*no_addresses)++;
bcfc7f
+			}
bcfc7f
 		} else {
bcfc7f
 			if ((find->options & DNS_ADBFIND_OVERQUOTA) != 0) {
bcfc7f
 				if (overquota != NULL)
bcfc7f
@@ -3261,6 +3273,7 @@ fctx_getaddresses(fetchctx_t *fctx, bool badcache) {
bcfc7f
 	dns_rdata_ns_t ns;
bcfc7f
 	bool need_alternate = false;
bcfc7f
 	bool all_spilled = true;
bcfc7f
+	unsigned int no_addresses = 0;
bcfc7f
 
bcfc7f
 	FCTXTRACE5("getaddresses", "fctx->depth=", fctx->depth);
bcfc7f
 
bcfc7f
@@ -3428,20 +3441,28 @@ fctx_getaddresses(fetchctx_t *fctx, bool badcache) {
bcfc7f
 		 * Extract the name from the NS record.
bcfc7f
 		 */
bcfc7f
 		result = dns_rdata_tostruct(&rdata, &ns, NULL);
bcfc7f
-		if (result != ISC_R_SUCCESS)
bcfc7f
+		if (result != ISC_R_SUCCESS) {
bcfc7f
 			continue;
bcfc7f
+		}
bcfc7f
 
bcfc7f
-		findname(fctx, &ns.name, 0, stdoptions, 0, now,
bcfc7f
-			 &overquota, &need_alternate);
bcfc7f
+		if (no_addresses > NS_FAIL_LIMIT &&
bcfc7f
+		    dns_rdataset_count(&fctx->nameservers) > NS_RR_LIMIT)
bcfc7f
+		{
bcfc7f
+			stdoptions |= DNS_ADBFIND_NOFETCH;
bcfc7f
+		}
bcfc7f
+		findname(fctx, &ns.name, 0, stdoptions, 0, now, &overquota,
bcfc7f
+			 &need_alternate, &no_addresses);
bcfc7f
 
bcfc7f
-		if (!overquota)
bcfc7f
+		if (!overquota) {
bcfc7f
 			all_spilled = false;
bcfc7f
+		}
bcfc7f
 
bcfc7f
 		dns_rdata_reset(&rdata);
bcfc7f
 		dns_rdata_freestruct(&ns);
bcfc7f
 	}
bcfc7f
-	if (result != ISC_R_NOMORE)
bcfc7f
+	if (result != ISC_R_NOMORE) {
bcfc7f
 		return (result);
bcfc7f
+	}
bcfc7f
 
bcfc7f
 	/*
bcfc7f
 	 * Do we need to use 6 to 4?
bcfc7f
@@ -3456,7 +3477,7 @@ fctx_getaddresses(fetchctx_t *fctx, bool badcache) {
bcfc7f
 			if (!a->isaddress) {
bcfc7f
 				findname(fctx, &a->_u._n.name, a->_u._n.port,
bcfc7f
 					 stdoptions, FCTX_ADDRINFO_FORWARDER,
bcfc7f
-					 now, NULL, NULL);
bcfc7f
+					 now, NULL, NULL, NULL);
bcfc7f
 				continue;
bcfc7f
 			}
bcfc7f
 			if (isc_sockaddr_pf(&a->_u.addr) != family)
bcfc7f
@@ -3818,16 +3839,14 @@ fctx_try(fetchctx_t *fctx, bool retrying, bool badcache) {
bcfc7f
 		}
bcfc7f
 	}
bcfc7f
 
bcfc7f
-	if (dns_name_countlabels(&fctx->domain) > 2) {
bcfc7f
-		result = isc_counter_increment(fctx->qc);
bcfc7f
-		if (result != ISC_R_SUCCESS) {
bcfc7f
-			isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER,
bcfc7f
-				      DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3),
bcfc7f
-				      "exceeded max queries resolving '%s'",
bcfc7f
-				      fctx->info);
bcfc7f
-			fctx_done(fctx, DNS_R_SERVFAIL, __LINE__);
bcfc7f
-			return;
bcfc7f
-		}
bcfc7f
+	result = isc_counter_increment(fctx->qc);
bcfc7f
+	if (result != ISC_R_SUCCESS) {
bcfc7f
+		isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER,
bcfc7f
+			      DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3),
bcfc7f
+			      "exceeded max queries resolving '%s'",
bcfc7f
+			      fctx->info);
bcfc7f
+		fctx_done(fctx, DNS_R_SERVFAIL, __LINE__);
bcfc7f
+		return;
bcfc7f
 	}
bcfc7f
 
bcfc7f
 	bucketnum = fctx->bucketnum;
bcfc7f
-- 
bcfc7f
2.21.1
bcfc7f