From 55ba863440dc5b1266b79f26ed92bbbdde5a2ebb Mon Sep 17 00:00:00 2001
From: "W.C.A. Wijngaards" <wouter@nlnetlabs.nl>
Date: Tue, 13 Apr 2021 13:52:57 +0200
Subject: [PATCH] - Fix that nxdomain synthesis does not happen above the stub
 or   forward definition.

---
 cachedb/cachedb.c       |  8 +++++++-
 edns-subnet/subnetmod.c |  2 +-
 iterator/iter_utils.c   | 15 ++++++++++++++-
 iterator/iter_utils.h   |  7 ++++++-
 iterator/iterator.c     | 12 +++++++-----
 services/cache/dns.c    |  5 ++++-
 services/cache/dns.h    |  4 +++-
 7 files changed, 42 insertions(+), 11 deletions(-)

diff --git a/cachedb/cachedb.c b/cachedb/cachedb.c
index eed4d5f..1f3ff98 100644
--- a/cachedb/cachedb.c
+++ b/cachedb/cachedb.c
@@ -616,12 +616,18 @@ cachedb_extcache_store(struct module_qstate* qstate, struct cachedb_env* ie)
 static int
 cachedb_intcache_lookup(struct module_qstate* qstate)
 {
+	uint8_t* dpname=NULL;
+	size_t dpnamelen=0;
 	struct dns_msg* msg;
+	if(iter_stub_fwd_no_cache(qstate, &qstate->qinfo,
+		&dpname, &dpnamelen))
+		return 0; /* no cache for these queries */
 	msg = dns_cache_lookup(qstate->env, qstate->qinfo.qname,
 		qstate->qinfo.qname_len, qstate->qinfo.qtype,
 		qstate->qinfo.qclass, qstate->query_flags,
 		qstate->region, qstate->env->scratch,
-		1 /* no partial messages with only a CNAME */
+		1, /* no partial messages with only a CNAME */
+		dpname, dpnamelen
 		);
 	if(!msg && qstate->env->neg_cache &&
 		iter_qname_indicates_dnssec(qstate->env, &qstate->qinfo)) {
diff --git a/edns-subnet/subnetmod.c b/edns-subnet/subnetmod.c
index f1b401b..fcd6dc6 100644
--- a/edns-subnet/subnetmod.c
+++ b/edns-subnet/subnetmod.c
@@ -150,7 +150,7 @@ int ecs_whitelist_check(struct query_info* qinfo,
 
 	/* Cache by default, might be disabled after parsing EDNS option
 	 * received from nameserver. */
-	if(!iter_stub_fwd_no_cache(qstate, &qstate->qinfo)) {
+	if(!iter_stub_fwd_no_cache(qstate, &qstate->qinfo, NULL, NULL)) {
 		qstate->no_cache_store = 0;
 	}
 
diff --git a/iterator/iter_utils.c b/iterator/iter_utils.c
index 7bc67da..260ebe6 100644
--- a/iterator/iter_utils.c
+++ b/iterator/iter_utils.c
@@ -1390,7 +1390,8 @@ int iter_dp_cangodown(struct query_info* qinfo, struct delegpt* dp)
 }
 
 int
-iter_stub_fwd_no_cache(struct module_qstate *qstate, struct query_info *qinf)
+iter_stub_fwd_no_cache(struct module_qstate *qstate, struct query_info *qinf,
+	uint8_t** retdpname, size_t* retdpnamelen)
 {
 	struct iter_hints_stub *stub;
 	struct delegpt *dp;
@@ -1419,6 +1420,10 @@ iter_stub_fwd_no_cache(struct module_qstate *qstate, struct query_info *qinf)
 			dname_str(stub->dp->name, dpname);
 			verbose(VERB_ALGO, "stub for %s %s has no_cache", qname, dpname);
 		}
+		if(retdpname) {
+			*retdpname = stub->dp->name;
+			*retdpnamelen = stub->dp->namelen;
+		}
 		return (stub->dp->no_cache);
 	}
 
@@ -1431,7 +1436,15 @@ iter_stub_fwd_no_cache(struct module_qstate *qstate, struct query_info *qinf)
 			dname_str(dp->name, dpname);
 			verbose(VERB_ALGO, "forward for %s %s has no_cache", qname, dpname);
 		}
+		if(retdpname) {
+			*retdpname = dp->name;
+			*retdpnamelen = dp->namelen;
+		}
 		return (dp->no_cache);
 	}
+	if(retdpname) {
+		*retdpname = NULL;
+		*retdpnamelen = 0;
+	}
 	return 0;
 }
diff --git a/iterator/iter_utils.h b/iterator/iter_utils.h
index f771930..6d4d152 100644
--- a/iterator/iter_utils.h
+++ b/iterator/iter_utils.h
@@ -380,9 +380,14 @@ int iter_dp_cangodown(struct query_info* qinfo, struct delegpt* dp);
  * Lookup if no_cache is set in stub or fwd.
  * @param qstate: query state with env with hints and fwds.
  * @param qinf: query name to lookup for.
+ * @param retdpname: returns NULL or the deepest enclosing name of fwd or stub.
+ * 	This is the name under which the closest lookup is going to happen.
+ * 	Used for NXDOMAIN checks, above that it is an nxdomain from a
+ * 	different server and zone. You can pass NULL to not get it.
+ * @param retdpnamelen: returns the length of the dpname.
  * @return true if no_cache is set in stub or fwd.
  */
 int iter_stub_fwd_no_cache(struct module_qstate *qstate,
-	struct query_info *qinf);
+	struct query_info *qinf, uint8_t** retdpname, size_t* retdpnamelen);
 
 #endif /* ITERATOR_ITER_UTILS_H */
diff --git a/iterator/iterator.c b/iterator/iterator.c
index 23b07ea..3772235 100644
--- a/iterator/iterator.c
+++ b/iterator/iterator.c
@@ -1228,8 +1228,8 @@ static int
 processInitRequest(struct module_qstate* qstate, struct iter_qstate* iq,
 	struct iter_env* ie, int id)
 {
-	uint8_t* delname;
-	size_t delnamelen;
+	uint8_t* delname, *dpname=NULL;
+	size_t delnamelen, dpnamelen=0;
 	struct dns_msg* msg = NULL;
 
 	log_query_info(VERB_DETAIL, "resolving", &qstate->qinfo);
@@ -1283,7 +1283,7 @@ processInitRequest(struct module_qstate* qstate, struct iter_qstate* iq,
 	/* This either results in a query restart (CNAME cache response), a
 	 * terminating response (ANSWER), or a cache miss (null). */
 	
-	if (iter_stub_fwd_no_cache(qstate, &iq->qchase)) {
+	if (iter_stub_fwd_no_cache(qstate, &iq->qchase, &dpname, &dpnamelen)) {	
 		/* Asked to not query cache. */
 		verbose(VERB_ALGO, "no-cache set, going to the network");
 		qstate->no_cache_lookup = 1;
@@ -1298,7 +1298,8 @@ processInitRequest(struct module_qstate* qstate, struct iter_qstate* iq,
 		msg = dns_cache_lookup(qstate->env, iq->qchase.qname, 
 			iq->qchase.qname_len, iq->qchase.qtype, 
 			iq->qchase.qclass, qstate->query_flags,
-			qstate->region, qstate->env->scratch, 0);
+			qstate->region, qstate->env->scratch, 0, dpname,
+			dpnamelen);	
 		if(!msg && qstate->env->neg_cache &&
 			iter_qname_indicates_dnssec(qstate->env, &iq->qchase)) {
 			/* lookup in negative cache; may result in
@@ -2288,7 +2289,8 @@ processQueryTargets(struct module_qstate* qstate, struct iter_qstate* iq,
 				iq->qinfo_out.qname, iq->qinfo_out.qname_len, 
 				iq->qinfo_out.qtype, iq->qinfo_out.qclass, 
 				qstate->query_flags, qstate->region, 
-				qstate->env->scratch, 0);
+				qstate->env->scratch, 0, iq->dp->name,
+				iq->dp->namelen);	
 			if(msg && FLAGS_GET_RCODE(msg->rep->flags) ==
 				LDNS_RCODE_NOERROR)
 				/* no need to send query if it is already 
diff --git a/services/cache/dns.c b/services/cache/dns.c
index 7b6e142..8160b31 100644
--- a/services/cache/dns.c
+++ b/services/cache/dns.c
@@ -801,7 +801,7 @@ struct dns_msg*
 dns_cache_lookup(struct module_env* env,
 	uint8_t* qname, size_t qnamelen, uint16_t qtype, uint16_t qclass,
 	uint16_t flags, struct regional* region, struct regional* scratch,
-	int no_partial)
+	int no_partial, uint8_t* dpname, size_t dpnamelen)
 {
 	struct lruhash_entry* e;
 	struct query_info k;
@@ -924,6 +924,9 @@ dns_cache_lookup(struct module_env* env,
 	 * the same.  We search upwards for NXDOMAINs. */
 	if(env->cfg->harden_below_nxdomain) {
 		while(!dname_is_root(k.qname)) {
+			if(dpname && dpnamelen
+				&& !dname_subdomain_c(k.qname, dpname))
+				break; /* no synth nxdomain above the stub */
 			dname_remove_label(&k.qname, &k.qname_len);
 			h = query_info_hash(&k, flags);
 			e = slabhash_lookup(env->msg_cache, h, &k, 0);
diff --git a/services/cache/dns.h b/services/cache/dns.h
index f1b77fb..bece837 100644
--- a/services/cache/dns.h
+++ b/services/cache/dns.h
@@ -164,6 +164,8 @@ struct dns_msg* tomsg(struct module_env* env, struct query_info* q,
  * @param scratch: where to allocate temporary data.
  * @param no_partial: if true, only complete messages and not a partial
  *	one (with only the start of the CNAME chain and not the rest).
+ * @param dpname: if not NULL, do not return NXDOMAIN above this name.
+ * @param dpnamelen: length of dpname.
  * @return new response message (alloced in region, rrsets do not have IDs).
  * 	or NULL on error or if not found in cache.
  *	TTLs are made relative to the current time.
@@ -171,7 +173,7 @@ struct dns_msg* tomsg(struct module_env* env, struct query_info* q,
 struct dns_msg* dns_cache_lookup(struct module_env* env,
 	uint8_t* qname, size_t qnamelen, uint16_t qtype, uint16_t qclass,
 	uint16_t flags, struct regional* region, struct regional* scratch,
-	int no_partial);
+	int no_partial, uint8_t* dpname, size_t dpnamelen);
 
 /** 
  * find and add A and AAAA records for missing nameservers in delegpt 
-- 
2.33.0

