9abc64
From e2aed3e1885bbc6d94d8845edbd9a8dfb869eb67 Mon Sep 17 00:00:00 2001
9abc64
From: Petr Mensik <pemensik@redhat.com>
9abc64
Date: Fri, 15 May 2020 14:55:26 +0200
9abc64
Subject: [PATCH] CVE-2020-8616
9abc64
9abc64
5395.	[security]	Further limit the number of queries that can be
9abc64
			triggered from a request.  Root and TLD servers
9abc64
			are no longer exempt from max-recursion-queries.
9abc64
			Fetches for missing name server address records
9abc64
			are limited to 4 for any domain. (CVE-2020-8616)
9abc64
			[GL #1388]
9abc64
---
9abc64
 lib/dns/adb.c             | 18 ++++++++--------
9abc64
 lib/dns/include/dns/adb.h |  4 ++++
9abc64
 lib/dns/resolver.c        | 45 ++++++++++++++++++++++++++-------------
9abc64
 3 files changed, 43 insertions(+), 24 deletions(-)
9abc64
9abc64
diff --git a/lib/dns/adb.c b/lib/dns/adb.c
9abc64
index 1eb00c2..ea06a95 100644
9abc64
--- a/lib/dns/adb.c
9abc64
+++ b/lib/dns/adb.c
9abc64
@@ -402,14 +402,13 @@ static void log_quota(dns_adbentry_t *entry, const char *fmt, ...)
9abc64
  */
9abc64
 #define FIND_WANTEVENT(fn)      (((fn)->options & DNS_ADBFIND_WANTEVENT) != 0)
9abc64
 #define FIND_WANTEMPTYEVENT(fn) (((fn)->options & DNS_ADBFIND_EMPTYEVENT) != 0)
9abc64
-#define FIND_AVOIDFETCHES(fn)   (((fn)->options & DNS_ADBFIND_AVOIDFETCHES) \
9abc64
-				 != 0)
9abc64
-#define FIND_STARTATZONE(fn)    (((fn)->options & DNS_ADBFIND_STARTATZONE) \
9abc64
-				 != 0)
9abc64
-#define FIND_HINTOK(fn)         (((fn)->options & DNS_ADBFIND_HINTOK) != 0)
9abc64
-#define FIND_GLUEOK(fn)         (((fn)->options & DNS_ADBFIND_GLUEOK) != 0)
9abc64
-#define FIND_HAS_ADDRS(fn)      (!ISC_LIST_EMPTY((fn)->list))
9abc64
-#define FIND_RETURNLAME(fn)     (((fn)->options & DNS_ADBFIND_RETURNLAME) != 0)
9abc64
+#define FIND_AVOIDFETCHES(fn)	(((fn)->options & DNS_ADBFIND_AVOIDFETCHES) != 0)
9abc64
+#define FIND_STARTATZONE(fn)	(((fn)->options & DNS_ADBFIND_STARTATZONE) != 0)
9abc64
+#define FIND_HINTOK(fn)		(((fn)->options & DNS_ADBFIND_HINTOK) != 0)
9abc64
+#define FIND_GLUEOK(fn)		(((fn)->options & DNS_ADBFIND_GLUEOK) != 0)
9abc64
+#define FIND_HAS_ADDRS(fn)	(!ISC_LIST_EMPTY((fn)->list))
9abc64
+#define FIND_RETURNLAME(fn)	(((fn)->options & DNS_ADBFIND_RETURNLAME) != 0)
9abc64
+#define FIND_NOFETCH(fn)	(((fn)->options & DNS_ADBFIND_NOFETCH) != 0)
9abc64
 
9abc64
 /*
9abc64
  * These are currently used on simple unsigned ints, so they are
9abc64
@@ -3167,7 +3166,8 @@ dns_adb_createfind2(dns_adb_t *adb, isc_task_t *task, isc_taskaction_t action,
9abc64
 	else
9abc64
 		have_address = ISC_FALSE;
9abc64
 	if (wanted_fetches != 0 &&
9abc64
-	    ! (FIND_AVOIDFETCHES(find) && have_address)) {
9abc64
+	    ! (FIND_AVOIDFETCHES(find) && have_address) &&
9abc64
+	    ! FIND_NOFETCH(find)) {
9abc64
 		/*
9abc64
 		 * We're missing at least one address family.  Either the
9abc64
 		 * caller hasn't instructed us to avoid fetches, or we don't
9abc64
diff --git a/lib/dns/include/dns/adb.h b/lib/dns/include/dns/adb.h
9abc64
index bfc8e43..0efaf89 100644
9abc64
--- a/lib/dns/include/dns/adb.h
9abc64
+++ b/lib/dns/include/dns/adb.h
9abc64
@@ -204,6 +204,10 @@ struct dns_adbfind {
9abc64
  *      lame for this query.
9abc64
  */
9abc64
 #define DNS_ADBFIND_OVERQUOTA		0x00000400
9abc64
+/*%
9abc64
+ *	Don't perform a fetch even if there are no address records available.
9abc64
+ */
9abc64
+#define DNS_ADBFIND_NOFETCH		0x00000800
9abc64
 
9abc64
 /*%
9abc64
  * The answers to queries come back as a list of these.
9abc64
diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c
9abc64
index 9df33c7..e13d684 100644
9abc64
--- a/lib/dns/resolver.c
9abc64
+++ b/lib/dns/resolver.c
9abc64
@@ -175,6 +175,14 @@
9abc64
 #define DEFAULT_MAX_QUERIES 75
9abc64
 #endif
9abc64
 
9abc64
+/*
9abc64
+ * After NS_FAIL_LIMIT attempts to fetch a name server address,
9abc64
+ * if the number of addresses in the NS RRset exceeds NS_RR_LIMIT,
9abc64
+ * stop trying to fetch, in order to avoid wasting resources.
9abc64
+ */
9abc64
+#define NS_FAIL_LIMIT 4
9abc64
+#define NS_RR_LIMIT   5
9abc64
+
9abc64
 /* Number of hash buckets for zone counters */
9abc64
 #ifndef RES_DOMAIN_BUCKETS
9abc64
 #define RES_DOMAIN_BUCKETS	523
9abc64
@@ -3086,8 +3094,8 @@ sort_finds(dns_adbfindlist_t *findlist, unsigned int bias) {
9abc64
 static void
9abc64
 findname(fetchctx_t *fctx, dns_name_t *name, in_port_t port,
9abc64
 	 unsigned int options, unsigned int flags, isc_stdtime_t now,
9abc64
-	 isc_boolean_t *overquota, isc_boolean_t *need_alternate)
9abc64
-{
9abc64
+	 isc_boolean_t *overquota, isc_boolean_t *need_alternate,
9abc64
+	 unsigned int *no_addresses) {
9abc64
 	dns_adbaddrinfo_t *ai;
9abc64
 	dns_adbfind_t *find;
9abc64
 	dns_resolver_t *res;
9abc64
@@ -3176,6 +3184,9 @@ findname(fetchctx_t *fctx, dns_name_t *name, in_port_t port,
9abc64
 			     (res->dispatches6 == NULL &&
9abc64
 			      find->result_v4 != DNS_R_NXDOMAIN)))
9abc64
 				*need_alternate = ISC_TRUE;
9abc64
+			if (no_addresses != NULL) {
9abc64
+				(*no_addresses)++;
9abc64
+			}
9abc64
 		} else {
9abc64
 			if ((find->options & DNS_ADBFIND_OVERQUOTA) != 0) {
9abc64
 				if (overquota != NULL)
9abc64
@@ -3226,6 +3237,7 @@ fctx_getaddresses(fetchctx_t *fctx, isc_boolean_t badcache) {
9abc64
 	dns_rdata_ns_t ns;
9abc64
 	isc_boolean_t need_alternate = ISC_FALSE;
9abc64
 	isc_boolean_t all_spilled = ISC_TRUE;
9abc64
+	unsigned int no_addresses = 0;
9abc64
 
9abc64
 	FCTXTRACE5("getaddresses", "fctx->depth=", fctx->depth);
9abc64
 
9abc64
@@ -3384,8 +3396,13 @@ fctx_getaddresses(fetchctx_t *fctx, isc_boolean_t badcache) {
9abc64
 		if (result != ISC_R_SUCCESS)
9abc64
 			continue;
9abc64
 
9abc64
-		findname(fctx, &ns.name, 0, stdoptions, 0, now,
9abc64
-			 &overquota, &need_alternate);
9abc64
+		if (no_addresses > NS_FAIL_LIMIT &&
9abc64
+		    dns_rdataset_count(&fctx->nameservers) > NS_RR_LIMIT)
9abc64
+		{
9abc64
+			stdoptions |= DNS_ADBFIND_NOFETCH;
9abc64
+		}
9abc64
+		findname(fctx, &ns.name, 0, stdoptions, 0, now, &overquota,
9abc64
+			 &need_alternate, &no_addresses);
9abc64
 
9abc64
 		if (!overquota)
9abc64
 			all_spilled = ISC_FALSE;
9abc64
@@ -3409,7 +3426,7 @@ fctx_getaddresses(fetchctx_t *fctx, isc_boolean_t badcache) {
9abc64
 			if (!a->isaddress) {
9abc64
 				findname(fctx, &a->_u._n.name, a->_u._n.port,
9abc64
 					 stdoptions, FCTX_ADDRINFO_FORWARDER,
9abc64
-					 now, NULL, NULL);
9abc64
+					 now, NULL, NULL, NULL);
9abc64
 				continue;
9abc64
 			}
9abc64
 			if (isc_sockaddr_pf(&a->_u.addr) != family)
9abc64
@@ -3771,16 +3788,14 @@ fctx_try(fetchctx_t *fctx, isc_boolean_t retrying, isc_boolean_t badcache) {
9abc64
 		}
9abc64
 	}
9abc64
 
9abc64
-	if (dns_name_countlabels(&fctx->domain) > 2) {
9abc64
-		result = isc_counter_increment(fctx->qc);
9abc64
-		if (result != ISC_R_SUCCESS) {
9abc64
-			isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER,
9abc64
-				      DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3),
9abc64
-				      "exceeded max queries resolving '%s'",
9abc64
-				      fctx->info);
9abc64
-			fctx_done(fctx, DNS_R_SERVFAIL, __LINE__);
9abc64
-			return;
9abc64
-		}
9abc64
+	result = isc_counter_increment(fctx->qc);
9abc64
+	if (result != ISC_R_SUCCESS) {
9abc64
+		isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER,
9abc64
+			      DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3),
9abc64
+			      "exceeded max queries resolving '%s'",
9abc64
+			      fctx->info);
9abc64
+		fctx_done(fctx, DNS_R_SERVFAIL, __LINE__);
9abc64
+		return;
9abc64
 	}
9abc64
 
9abc64
 	bucketnum = fctx->bucketnum;
9abc64
-- 
9abc64
2.21.1
9abc64