7b3ea0
From 7af485f0fc9926425681ba0280ab6c2c8dd04530 Mon Sep 17 00:00:00 2001
7b3ea0
From: "W.C.A. Wijngaards" <wouter@nlnetlabs.nl>
7b3ea0
Date: Wed, 21 Sep 2022 11:10:38 +0200
7b3ea0
Subject: [PATCH] - Patch for CVE-2022-3204 Non-Responsive Delegation Attack.
7b3ea0
7b3ea0
---
7b3ea0
 unbound-1.16.2/iterator/iter_delegpt.c |  3 +++
7b3ea0
 unbound-1.16.2/iterator/iter_delegpt.h |  2 ++
7b3ea0
 unbound-1.16.2/iterator/iter_utils.c   |  3 +++
7b3ea0
 unbound-1.16.2/iterator/iter_utils.h   |  9 +++++++
7b3ea0
 unbound-1.16.2/iterator/iterator.c     | 36 +++++++++++++++++++++++++-
7b3ea0
 unbound-1.16.2/services/cache/dns.c    |  3 +++
7b3ea0
 unbound-1.16.2/services/mesh.c         |  7 +++++
7b3ea0
 unbound-1.16.2/services/mesh.h         | 11 ++++++++
7b3ea0
 8 files changed, 73 insertions(+), 1 deletion(-)
7b3ea0
7b3ea0
diff --git a/unbound-1.16.2/iterator/iter_delegpt.c b/unbound-1.16.2/iterator/iter_delegpt.c
7b3ea0
index 4bffa1b..fd07aaa 100644
7b3ea0
--- a/unbound-1.16.2/iterator/iter_delegpt.c
7b3ea0
+++ b/unbound-1.16.2/iterator/iter_delegpt.c
7b3ea0
@@ -78,6 +78,7 @@ struct delegpt* delegpt_copy(struct delegpt* dp, struct regional* region)
7b3ea0
 		if(!delegpt_add_ns(copy, region, ns->name, ns->lame,
7b3ea0
 			ns->tls_auth_name, ns->port))
7b3ea0
 			return NULL;
7b3ea0
+		copy->nslist->cache_lookup_count = ns->cache_lookup_count;
7b3ea0
 		copy->nslist->resolved = ns->resolved;
7b3ea0
 		copy->nslist->got4 = ns->got4;
7b3ea0
 		copy->nslist->got6 = ns->got6;
7b3ea0
@@ -121,6 +122,7 @@ delegpt_add_ns(struct delegpt* dp, struct regional* region, uint8_t* name,
7b3ea0
 	ns->namelen = len;
7b3ea0
 	dp->nslist = ns;
7b3ea0
 	ns->name = regional_alloc_init(region, name, ns->namelen);
7b3ea0
+	ns->cache_lookup_count = 0;
7b3ea0
 	ns->resolved = 0;
7b3ea0
 	ns->got4 = 0;
7b3ea0
 	ns->got6 = 0;
7b3ea0
@@ -620,6 +622,7 @@ int delegpt_add_ns_mlc(struct delegpt* dp, uint8_t* name, uint8_t lame,
7b3ea0
 	}
7b3ea0
 	ns->next = dp->nslist;
7b3ea0
 	dp->nslist = ns;
7b3ea0
+	ns->cache_lookup_count = 0;
7b3ea0
 	ns->resolved = 0;
7b3ea0
 	ns->got4 = 0;
7b3ea0
 	ns->got6 = 0;
7b3ea0
diff --git a/unbound-1.16.2/iterator/iter_delegpt.h b/unbound-1.16.2/iterator/iter_delegpt.h
7b3ea0
index 62c8edc..586597a 100644
7b3ea0
--- a/unbound-1.16.2/iterator/iter_delegpt.h
7b3ea0
+++ b/unbound-1.16.2/iterator/iter_delegpt.h
7b3ea0
@@ -101,6 +101,8 @@ struct delegpt_ns {
7b3ea0
 	uint8_t* name;
7b3ea0
 	/** length of name */
7b3ea0
 	size_t namelen;
7b3ea0
+	/** number of cache lookups for the name */
7b3ea0
+	int cache_lookup_count;
7b3ea0
 	/** 
7b3ea0
 	 * If the name has been resolved. false if not queried for yet.
7b3ea0
 	 * true if the A, AAAA queries have been generated.
7b3ea0
diff --git a/unbound-1.16.2/iterator/iter_utils.c b/unbound-1.16.2/iterator/iter_utils.c
7b3ea0
index 3e13e59..56b184a 100644
7b3ea0
--- a/unbound-1.16.2/iterator/iter_utils.c
7b3ea0
+++ b/unbound-1.16.2/iterator/iter_utils.c
7b3ea0
@@ -1209,6 +1209,9 @@ int iter_lookup_parent_glue_from_cache(struct module_env* env,
7b3ea0
 	struct delegpt_ns* ns;
7b3ea0
 	size_t num = delegpt_count_targets(dp);
7b3ea0
 	for(ns = dp->nslist; ns; ns = ns->next) {
7b3ea0
+		if(ns->cache_lookup_count > ITERATOR_NAME_CACHELOOKUP_MAX_PSIDE)
7b3ea0
+			continue;
7b3ea0
+		ns->cache_lookup_count++;
7b3ea0
 		/* get cached parentside A */
7b3ea0
 		akey = rrset_cache_lookup(env->rrset_cache, ns->name,
7b3ea0
 			ns->namelen, LDNS_RR_TYPE_A, qinfo->qclass,
7b3ea0
diff --git a/unbound-1.16.2/iterator/iter_utils.h b/unbound-1.16.2/iterator/iter_utils.h
7b3ea0
index 8583fde..850be96 100644
7b3ea0
--- a/unbound-1.16.2/iterator/iter_utils.h
7b3ea0
+++ b/unbound-1.16.2/iterator/iter_utils.h
7b3ea0
@@ -62,6 +62,15 @@ struct ub_packed_rrset_key;
7b3ea0
 struct module_stack;
7b3ea0
 struct outside_network;
7b3ea0
 
7b3ea0
+/* max number of lookups in the cache for target nameserver names.
7b3ea0
+ * This stops, for large delegations, N*N lookups in the cache. */
7b3ea0
+#define ITERATOR_NAME_CACHELOOKUP_MAX	3
7b3ea0
+/* max number of lookups in the cache for parentside glue for nameserver names
7b3ea0
+ * This stops, for larger delegations, N*N lookups in the cache.
7b3ea0
+ * It is a little larger than the nonpside max, so it allows a couple extra
7b3ea0
+ * lookups of parent side glue. */
7b3ea0
+#define ITERATOR_NAME_CACHELOOKUP_MAX_PSIDE	5
7b3ea0
+
7b3ea0
 /**
7b3ea0
  * Process config options and set iterator module state.
7b3ea0
  * Sets default values if no config is found.
7b3ea0
diff --git a/unbound-1.16.2/iterator/iterator.c b/unbound-1.16.2/iterator/iterator.c
7b3ea0
index 25e5cfe..da9b799 100644
7b3ea0
--- a/unbound-1.16.2/iterator/iterator.c
7b3ea0
+++ b/unbound-1.16.2/iterator/iterator.c
7b3ea0
@@ -1218,6 +1218,15 @@ generate_dnskey_prefetch(struct module_qstate* qstate,
7b3ea0
 		(qstate->query_flags&BIT_RD) && !(qstate->query_flags&BIT_CD)){
7b3ea0
 		return;
7b3ea0
 	}
7b3ea0
+	/* we do not generate this prefetch when the query list is full,
7b3ea0
+	 * the query is fetched, if needed, when the validator wants it.
7b3ea0
+	 * At that time the validator waits for it, after spawning it.
7b3ea0
+	 * This means there is one state that uses cpu and a socket, the
7b3ea0
+	 * spawned while this one waits, and not several at the same time,
7b3ea0
+	 * if we had created the lookup here. And this helps to keep
7b3ea0
+	 * the total load down, but the query still succeeds to resolve. */
7b3ea0
+	if(mesh_jostle_exceeded(qstate->env->mesh))
7b3ea0
+		return;
7b3ea0
 
7b3ea0
 	/* if the DNSKEY is in the cache this lookup will stop quickly */
7b3ea0
 	log_nametypeclass(VERB_ALGO, "schedule dnskey prefetch", 
7b3ea0
@@ -1911,6 +1920,14 @@ query_for_targets(struct module_qstate* qstate, struct iter_qstate* iq,
7b3ea0
 				return 0;
7b3ea0
 			}
7b3ea0
 			query_count++;
7b3ea0
+			/* If the mesh query list is full, exit the loop here.
7b3ea0
+			 * This makes the routine spawn one query at a time,
7b3ea0
+			 * and this means there is no query state load
7b3ea0
+			 * increase, because the spawned state uses cpu and a
7b3ea0
+			 * socket while this state waits for that spawned
7b3ea0
+			 * state. Next time we can look up further targets */
7b3ea0
+			if(mesh_jostle_exceeded(qstate->env->mesh))
7b3ea0
+				break;
7b3ea0
 		}
7b3ea0
 		/* Send the A request. */
7b3ea0
 		if(ie->supports_ipv4 &&
7b3ea0
@@ -1925,6 +1942,9 @@ query_for_targets(struct module_qstate* qstate, struct iter_qstate* iq,
7b3ea0
 				return 0;
7b3ea0
 			}
7b3ea0
 			query_count++;
7b3ea0
+			/* If the mesh query list is full, exit the loop. */
7b3ea0
+			if(mesh_jostle_exceeded(qstate->env->mesh))
7b3ea0
+				break;
7b3ea0
 		}
7b3ea0
 
7b3ea0
 		/* mark this target as in progress. */
7b3ea0
@@ -2085,6 +2105,15 @@ processLastResort(struct module_qstate* qstate, struct iter_qstate* iq,
7b3ea0
 			}
7b3ea0
 			ns->done_pside6 = 1;
7b3ea0
 			query_count++;
7b3ea0
+			if(mesh_jostle_exceeded(qstate->env->mesh)) {
7b3ea0
+				/* Wait for the lookup; do not spawn multiple
7b3ea0
+				 * lookups at a time. */
7b3ea0
+				verbose(VERB_ALGO, "try parent-side glue lookup");
7b3ea0
+				iq->num_target_queries += query_count;
7b3ea0
+				target_count_increase(iq, query_count);
7b3ea0
+				qstate->ext_state[id] = module_wait_subquery;
7b3ea0
+				return 0;
7b3ea0
+			}
7b3ea0
 		}
7b3ea0
 		if(ie->supports_ipv4 && !ns->done_pside4) {
7b3ea0
 			/* Send the A request. */
7b3ea0
@@ -2560,7 +2589,12 @@ processQueryTargets(struct module_qstate* qstate, struct iter_qstate* iq,
7b3ea0
 	if(iq->depth < ie->max_dependency_depth
7b3ea0
 		&& iq->num_target_queries == 0
7b3ea0
 		&& (!iq->target_count || iq->target_count[TARGET_COUNT_NX]==0)
7b3ea0
-		&& iq->sent_count < TARGET_FETCH_STOP) {
7b3ea0
+		&& iq->sent_count < TARGET_FETCH_STOP
7b3ea0
+		/* if the mesh query list is full, then do not waste cpu
7b3ea0
+		 * and sockets to fetch promiscuous targets. They can be
7b3ea0
+		 * looked up when needed. */
7b3ea0
+		&& !mesh_jostle_exceeded(qstate->env->mesh)
7b3ea0
+		) {
7b3ea0
 		tf_policy = ie->target_fetch_policy[iq->depth];
7b3ea0
 	}
7b3ea0
 
7b3ea0
diff --git a/unbound-1.16.2/services/cache/dns.c b/unbound-1.16.2/services/cache/dns.c
7b3ea0
index 6bca8d8..b6e5697 100644
7b3ea0
--- a/unbound-1.16.2/services/cache/dns.c
7b3ea0
+++ b/unbound-1.16.2/services/cache/dns.c
7b3ea0
@@ -404,6 +404,9 @@ cache_fill_missing(struct module_env* env, uint16_t qclass,
7b3ea0
 	struct ub_packed_rrset_key* akey;
7b3ea0
 	time_t now = *env->now;
7b3ea0
 	for(ns = dp->nslist; ns; ns = ns->next) {
7b3ea0
+		if(ns->cache_lookup_count > ITERATOR_NAME_CACHELOOKUP_MAX)
7b3ea0
+			continue;
7b3ea0
+		ns->cache_lookup_count++;
7b3ea0
 		akey = rrset_cache_lookup(env->rrset_cache, ns->name, 
7b3ea0
 			ns->namelen, LDNS_RR_TYPE_A, qclass, 0, now, 0);
7b3ea0
 		if(akey) {
7b3ea0
diff --git a/unbound-1.16.2/services/mesh.c b/unbound-1.16.2/services/mesh.c
7b3ea0
index 30bcf7c..2a41194 100644
7b3ea0
--- a/unbound-1.16.2/services/mesh.c
7b3ea0
+++ b/unbound-1.16.2/services/mesh.c
7b3ea0
@@ -2240,3 +2240,10 @@ mesh_serve_expired_callback(void* arg)
7b3ea0
 		mesh_do_callback(mstate, LDNS_RCODE_NOERROR, msg->rep, c, &tv;;
7b3ea0
 	}
7b3ea0
 }
7b3ea0
+
7b3ea0
+int mesh_jostle_exceeded(struct mesh_area* mesh)
7b3ea0
+{
7b3ea0
+	if(mesh->all.count < mesh->max_reply_states)
7b3ea0
+		return 0;
7b3ea0
+	return 1;
7b3ea0
+}
7b3ea0
diff --git a/unbound-1.16.2/services/mesh.h b/unbound-1.16.2/services/mesh.h
7b3ea0
index 3be9b63..25121a6 100644
7b3ea0
--- a/unbound-1.16.2/services/mesh.h
7b3ea0
+++ b/unbound-1.16.2/services/mesh.h
7b3ea0
@@ -685,4 +685,15 @@ struct dns_msg*
7b3ea0
 mesh_serve_expired_lookup(struct module_qstate* qstate,
7b3ea0
 	struct query_info* lookup_qinfo);
7b3ea0
 
7b3ea0
+/**
7b3ea0
+ * See if the mesh has space for more queries. You can allocate queries
7b3ea0
+ * anyway, but this checks for the allocated space.
7b3ea0
+ * @param mesh: mesh area.
7b3ea0
+ * @return true if the query list is full.
7b3ea0
+ * 	It checks the number of all queries, not just number of reply states,
7b3ea0
+ * 	that have a client address. So that spawned queries count too,
7b3ea0
+ * 	that were created by the iterator, or other modules.
7b3ea0
+ */
7b3ea0
+int mesh_jostle_exceeded(struct mesh_area* mesh);
7b3ea0
+
7b3ea0
 #endif /* SERVICES_MESH_H */
7b3ea0
-- 
7b3ea0
2.37.3
7b3ea0