Subject: Collected Debian patches for unbound
Author: Debian DNS Team <team+dns@tracker.debian.org>

The unbound package is maintained in Git rather than maintaining patches as
separate files. They are therefore all included in this single Debian patch.

For full commit history and separated commits, see the packaging Git
repository.
--- unbound-1.9.0.orig/iterator/iter_delegpt.c
+++ unbound-1.9.0/iterator/iter_delegpt.c
@@ -84,7 +84,7 @@ struct delegpt* delegpt_copy(struct dele
 	}
 	for(a = dp->target_list; a; a = a->next_target) {
 		if(!delegpt_add_addr(copy, region, &a->addr, a->addrlen, 
-			a->bogus, a->lame, a->tls_auth_name))
+			a->bogus, a->lame, a->tls_auth_name, NULL))
 			return NULL;
 	}
 	return copy;
@@ -161,7 +161,7 @@ delegpt_find_addr(struct delegpt* dp, st
 int 
 delegpt_add_target(struct delegpt* dp, struct regional* region, 
 	uint8_t* name, size_t namelen, struct sockaddr_storage* addr, 
-	socklen_t addrlen, uint8_t bogus, uint8_t lame)
+	socklen_t addrlen, uint8_t bogus, uint8_t lame, int* additions)
 {
 	struct delegpt_ns* ns = delegpt_find_ns(dp, name, namelen);
 	log_assert(!dp->dp_type_mlc);
@@ -176,13 +176,14 @@ delegpt_add_target(struct delegpt* dp, s
 		if(ns->got4 && ns->got6)
 			ns->resolved = 1;
 	}
-	return delegpt_add_addr(dp, region, addr, addrlen, bogus, lame, NULL);
+	return delegpt_add_addr(dp, region, addr, addrlen, bogus, lame, NULL,
+		additions);
 }
 
 int 
 delegpt_add_addr(struct delegpt* dp, struct regional* region, 
 	struct sockaddr_storage* addr, socklen_t addrlen, uint8_t bogus, 
-	uint8_t lame, char* tls_auth_name)
+	uint8_t lame, char* tls_auth_name, int* additions)
 {
 	struct delegpt_addr* a;
 	log_assert(!dp->dp_type_mlc);
@@ -194,6 +195,8 @@ delegpt_add_addr(struct delegpt* dp, str
 			a->lame = 0;
 		return 1;
 	}
+	if(additions)
+		*additions = 1;
 
 	a = (struct delegpt_addr*)regional_alloc(region,
 		sizeof(struct delegpt_addr));
@@ -382,10 +385,10 @@ delegpt_from_message(struct dns_msg* msg
 			continue;
 
 		if(ntohs(s->rk.type) == LDNS_RR_TYPE_A) {
-			if(!delegpt_add_rrset_A(dp, region, s, 0))
+			if(!delegpt_add_rrset_A(dp, region, s, 0, NULL))
 				return NULL;
 		} else if(ntohs(s->rk.type) == LDNS_RR_TYPE_AAAA) {
-			if(!delegpt_add_rrset_AAAA(dp, region, s, 0))
+			if(!delegpt_add_rrset_AAAA(dp, region, s, 0, NULL))
 				return NULL;
 		}
 	}
@@ -416,7 +419,7 @@ delegpt_rrset_add_ns(struct delegpt* dp,
 
 int 
 delegpt_add_rrset_A(struct delegpt* dp, struct regional* region,
-	struct ub_packed_rrset_key* ak, uint8_t lame)
+	struct ub_packed_rrset_key* ak, uint8_t lame, int* additions)
 {
         struct packed_rrset_data* d=(struct packed_rrset_data*)ak->entry.data;
         size_t i;
@@ -432,7 +435,7 @@ delegpt_add_rrset_A(struct delegpt* dp,
                 memmove(&sa.sin_addr, d->rr_data[i]+2, INET_SIZE);
                 if(!delegpt_add_target(dp, region, ak->rk.dname,
                         ak->rk.dname_len, (struct sockaddr_storage*)&sa,
-                        len, (d->security==sec_status_bogus), lame))
+                        len, (d->security==sec_status_bogus), lame, additions))
                         return 0;
         }
         return 1;
@@ -440,7 +443,7 @@ delegpt_add_rrset_A(struct delegpt* dp,
 
 int 
 delegpt_add_rrset_AAAA(struct delegpt* dp, struct regional* region,
-	struct ub_packed_rrset_key* ak, uint8_t lame)
+	struct ub_packed_rrset_key* ak, uint8_t lame, int* additions)
 {
         struct packed_rrset_data* d=(struct packed_rrset_data*)ak->entry.data;
         size_t i;
@@ -456,7 +459,7 @@ delegpt_add_rrset_AAAA(struct delegpt* d
                 memmove(&sa.sin6_addr, d->rr_data[i]+2, INET6_SIZE);
                 if(!delegpt_add_target(dp, region, ak->rk.dname,
                         ak->rk.dname_len, (struct sockaddr_storage*)&sa,
-                        len, (d->security==sec_status_bogus), lame))
+                        len, (d->security==sec_status_bogus), lame, additions))
                         return 0;
         }
         return 1;
@@ -464,20 +467,33 @@ delegpt_add_rrset_AAAA(struct delegpt* d
 
 int 
 delegpt_add_rrset(struct delegpt* dp, struct regional* region,
-        struct ub_packed_rrset_key* rrset, uint8_t lame)
+        struct ub_packed_rrset_key* rrset, uint8_t lame, int* additions)
 {
 	if(!rrset)
 		return 1;
 	if(ntohs(rrset->rk.type) == LDNS_RR_TYPE_NS)
 		return delegpt_rrset_add_ns(dp, region, rrset, lame);
 	else if(ntohs(rrset->rk.type) == LDNS_RR_TYPE_A)
-		return delegpt_add_rrset_A(dp, region, rrset, lame);
+		return delegpt_add_rrset_A(dp, region, rrset, lame, additions);
 	else if(ntohs(rrset->rk.type) == LDNS_RR_TYPE_AAAA)
-		return delegpt_add_rrset_AAAA(dp, region, rrset, lame);
+		return delegpt_add_rrset_AAAA(dp, region, rrset, lame,
+			additions);
 	log_warn("Unknown rrset type added to delegpt");
 	return 1;
 }
 
+void delegpt_mark_neg(struct delegpt_ns* ns, uint16_t qtype)
+{
+	if(ns) {
+		if(qtype == LDNS_RR_TYPE_A)
+			ns->got4 = 2;
+		else if(qtype == LDNS_RR_TYPE_AAAA)
+			ns->got6 = 2;
+		if(ns->got4 && ns->got6)
+			ns->resolved = 1;
+	}
+}
+
 void delegpt_add_neg_msg(struct delegpt* dp, struct msgreply_entry* msg)
 {
 	struct reply_info* rep = (struct reply_info*)msg->entry.data;
@@ -487,14 +503,7 @@ void delegpt_add_neg_msg(struct delegpt*
 	if(FLAGS_GET_RCODE(rep->flags) != 0 || rep->an_numrrsets == 0) {
 		struct delegpt_ns* ns = delegpt_find_ns(dp, msg->key.qname, 
 			msg->key.qname_len);
-		if(ns) {
-			if(msg->key.qtype == LDNS_RR_TYPE_A)
-				ns->got4 = 1;
-			else if(msg->key.qtype == LDNS_RR_TYPE_AAAA)
-				ns->got6 = 1;
-			if(ns->got4 && ns->got6)
-				ns->resolved = 1;
-		}
+		delegpt_mark_neg(ns, msg->key.qtype);
 	}
 }
 
--- unbound-1.9.0.orig/iterator/iter_delegpt.h
+++ unbound-1.9.0/iterator/iter_delegpt.h
@@ -106,9 +106,10 @@ struct delegpt_ns {
 	 * and marked true if got4 and got6 are both true.
 	 */
 	int resolved;
-	/** if the ipv4 address is in the delegpt */
+	/** if the ipv4 address is in the delegpt, 0=not, 1=yes 2=negative,
+	 * negative means it was done, but no content. */
 	uint8_t got4;
-	/** if the ipv6 address is in the delegpt */
+	/** if the ipv6 address is in the delegpt, 0=not, 1=yes 2=negative */
 	uint8_t got6;
 	/**
 	 * If the name is parent-side only and thus dispreferred.
@@ -215,11 +216,12 @@ int delegpt_rrset_add_ns(struct delegpt*
  * @param addrlen: the length of addr.
  * @param bogus: security status for the address, pass true if bogus.
  * @param lame: address is lame.
+ * @param additions: will be set to 1 if a new address is added
  * @return false on error.
  */
 int delegpt_add_target(struct delegpt* dp, struct regional* regional, 
 	uint8_t* name, size_t namelen, struct sockaddr_storage* addr, 
-	socklen_t addrlen, uint8_t bogus, uint8_t lame);
+	socklen_t addrlen, uint8_t bogus, uint8_t lame, int* additions);
 
 /**
  * Add A RRset to delegpt.
@@ -227,10 +229,11 @@ int delegpt_add_target(struct delegpt* d
  * @param regional: where to allocate the info.
  * @param rrset: RRset A to add.
  * @param lame: rrset is lame, disprefer it.
+ * @param additions: will be set to 1 if a new address is added
  * @return 0 on alloc error.
  */
 int delegpt_add_rrset_A(struct delegpt* dp, struct regional* regional, 
-	struct ub_packed_rrset_key* rrset, uint8_t lame);
+	struct ub_packed_rrset_key* rrset, uint8_t lame, int* additions);
 
 /**
  * Add AAAA RRset to delegpt.
@@ -238,10 +241,11 @@ int delegpt_add_rrset_A(struct delegpt*
  * @param regional: where to allocate the info.
  * @param rrset: RRset AAAA to add.
  * @param lame: rrset is lame, disprefer it.
+ * @param additions: will be set to 1 if a new address is added
  * @return 0 on alloc error.
  */
 int delegpt_add_rrset_AAAA(struct delegpt* dp, struct regional* regional, 
-	struct ub_packed_rrset_key* rrset, uint8_t lame);
+	struct ub_packed_rrset_key* rrset, uint8_t lame, int* additions);
 
 /**
  * Add any RRset to delegpt.
@@ -250,10 +254,11 @@ int delegpt_add_rrset_AAAA(struct delegp
  * @param regional: where to allocate the info.
  * @param rrset: RRset to add, NS, A, AAAA.
  * @param lame: rrset is lame, disprefer it.
+ * @param additions: will be set to 1 if a new address is added
  * @return 0 on alloc error.
  */
 int delegpt_add_rrset(struct delegpt* dp, struct regional* regional, 
-	struct ub_packed_rrset_key* rrset, uint8_t lame);
+	struct ub_packed_rrset_key* rrset, uint8_t lame, int* additions);
 
 /**
  * Add address to the delegation point. No servername is associated or checked.
@@ -264,11 +269,12 @@ int delegpt_add_rrset(struct delegpt* dp
  * @param bogus: if address is bogus.
  * @param lame: if address is lame.
  * @param tls_auth_name: TLS authentication name (or NULL).
+ * @param additions: will be set to 1 if a new address is added
  * @return false on error.
  */
 int delegpt_add_addr(struct delegpt* dp, struct regional* regional, 
 	struct sockaddr_storage* addr, socklen_t addrlen,
-	uint8_t bogus, uint8_t lame, char* tls_auth_name);
+	uint8_t bogus, uint8_t lame, char* tls_auth_name, int* additions);
 
 /** 
  * Find NS record in name list of delegation point.
@@ -342,6 +348,14 @@ struct delegpt* delegpt_from_message(str
 	struct regional* regional);
 
 /**
+ * Mark negative return in delegation point for specific nameserver.
+ * sets the got4 or got6 to negative, updates the ns->resolved.
+ * @param ns: the nameserver in the delegpt.
+ * @param qtype: A or AAAA (host order).
+ */
+void delegpt_mark_neg(struct delegpt_ns* ns, uint16_t qtype);
+
+/**
  * Add negative message to delegation point.
  * @param dp: delegation point.
  * @param msg: the message added, marks off A or AAAA from an NS entry.
--- unbound-1.9.0.orig/iterator/iter_scrub.c
+++ unbound-1.9.0/iterator/iter_scrub.c
@@ -185,8 +185,9 @@ mark_additional_rrset(sldns_buffer* pkt,
 /** Get target name of a CNAME */
 static int
 parse_get_cname_target(struct rrset_parse* rrset, uint8_t** sname, 
-	size_t* snamelen)
+	size_t* snamelen, sldns_buffer* pkt)
 {
+	size_t oldpos, dlen;
 	if(rrset->rr_count != 1) {
 		struct rr_parse* sig;
 		verbose(VERB_ALGO, "Found CNAME rrset with "
@@ -204,6 +205,19 @@ parse_get_cname_target(struct rrset_pars
 	*sname = rrset->rr_first->ttl_data + sizeof(uint32_t)
 		+ sizeof(uint16_t); /* skip ttl, rdatalen */
 	*snamelen = rrset->rr_first->size - sizeof(uint16_t);
+
+	if(rrset->rr_first->outside_packet) {
+		if(!dname_valid(*sname, *snamelen))
+			return 0;
+		return 1;
+	}
+	oldpos = sldns_buffer_position(pkt);
+	sldns_buffer_set_position(pkt, (size_t)(*sname - sldns_buffer_begin(pkt)));
+	dlen = pkt_dname_len(pkt);
+	sldns_buffer_set_position(pkt, oldpos);
+	if(dlen == 0)
+		return 0; /* parse fail on the rdata name */
+	*snamelen = dlen;
 	return 1;
 }
 
@@ -215,7 +229,7 @@ synth_cname(uint8_t* qname, size_t qname
 	/* we already know that sname is a strict subdomain of DNAME owner */
 	uint8_t* dtarg = NULL;
 	size_t dtarglen;
-	if(!parse_get_cname_target(dname_rrset, &dtarg, &dtarglen))
+	if(!parse_get_cname_target(dname_rrset, &dtarg, &dtarglen, pkt))
 		return 0; 
 	log_assert(qnamelen > dname_rrset->dname_len);
 	/* DNAME from com. to net. with qname example.com. -> example.net. */
@@ -384,7 +398,7 @@ scrub_normalize(sldns_buffer* pkt, struc
 				/* check next cname */
 				uint8_t* t = NULL;
 				size_t tlen = 0;
-				if(!parse_get_cname_target(nx, &t, &tlen))
+				if(!parse_get_cname_target(nx, &t, &tlen, pkt))
 					return 0;
 				if(dname_pkt_compare(pkt, alias, t) == 0) {
 					/* it's OK and better capitalized */
@@ -435,7 +449,7 @@ scrub_normalize(sldns_buffer* pkt, struc
 				size_t tlen = 0;
 				if(synth_cname(sname, snamelen, nx, alias,
 					&aliaslen, pkt) &&
-					parse_get_cname_target(rrset, &t, &tlen) &&
+					parse_get_cname_target(rrset, &t, &tlen, pkt) &&
 			   		dname_pkt_compare(pkt, alias, t) == 0) {
 					/* the synthesized CNAME equals the
 					 * current CNAME.  This CNAME is the
@@ -456,7 +470,7 @@ scrub_normalize(sldns_buffer* pkt, struc
 			}
 
 			/* move to next name in CNAME chain */
-			if(!parse_get_cname_target(rrset, &sname, &snamelen))
+			if(!parse_get_cname_target(rrset, &sname, &snamelen, pkt))
 				return 0;
 			prev = rrset;
 			rrset = rrset->rrset_all_next;
--- unbound-1.9.0.orig/iterator/iter_utils.c
+++ unbound-1.9.0/iterator/iter_utils.c
@@ -1090,7 +1090,7 @@ int iter_lookup_parent_glue_from_cache(s
 			log_rrset_key(VERB_ALGO, "found parent-side", akey);
 			ns->done_pside4 = 1;
 			/* a negative-cache-element has no addresses it adds */
-			if(!delegpt_add_rrset_A(dp, region, akey, 1))
+			if(!delegpt_add_rrset_A(dp, region, akey, 1, NULL))
 				log_err("malloc failure in lookup_parent_glue");
 			lock_rw_unlock(&akey->entry.lock);
 		}
@@ -1102,7 +1102,7 @@ int iter_lookup_parent_glue_from_cache(s
 			log_rrset_key(VERB_ALGO, "found parent-side", akey);
 			ns->done_pside6 = 1;
 			/* a negative-cache-element has no addresses it adds */
-			if(!delegpt_add_rrset_AAAA(dp, region, akey, 1))
+			if(!delegpt_add_rrset_AAAA(dp, region, akey, 1, NULL))
 				log_err("malloc failure in lookup_parent_glue");
 			lock_rw_unlock(&akey->entry.lock);
 		}
--- unbound-1.9.0.orig/iterator/iterator.c
+++ unbound-1.9.0/iterator/iterator.c
@@ -72,6 +72,8 @@
 /* in msec */
 int UNKNOWN_SERVER_NICENESS = 376;
 
+static void target_count_increase_nx(struct iter_qstate* iq, int num);
+
 int 
 iter_init(struct module_env* env, int id)
 {
@@ -150,6 +152,7 @@ iter_new(struct module_qstate* qstate, i
 	iq->sent_count = 0;
 	iq->ratelimit_ok = 0;
 	iq->target_count = NULL;
+	iq->dp_target_count = 0;
 	iq->wait_priming_stub = 0;
 	iq->refetch_glue = 0;
 	iq->dnssec_expected = 0;
@@ -221,6 +224,7 @@ final_state(struct iter_qstate* iq)
 static void
 error_supers(struct module_qstate* qstate, int id, struct module_qstate* super)
 {
+	struct iter_env* ie = (struct iter_env*)qstate->env->modinfo[id];
 	struct iter_qstate* super_iq = (struct iter_qstate*)super->minfo[id];
 
 	if(qstate->qinfo.qtype == LDNS_RR_TYPE_A ||
@@ -246,7 +250,11 @@ error_supers(struct module_qstate* qstat
 				super->region, super_iq->dp))
 				log_err("out of memory adding missing");
 		}
+		delegpt_mark_neg(dpns, qstate->qinfo.qtype);
 		dpns->resolved = 1; /* mark as failed */
+		if((dpns->got4 == 2 || !ie->supports_ipv4) &&
+			(dpns->got6 == 2 || !ie->supports_ipv6))
+			target_count_increase_nx(super_iq, 1);
 	}
 	if(qstate->qinfo.qtype == LDNS_RR_TYPE_NS) {
 		/* prime failed to get delegation */
@@ -617,7 +625,7 @@ static void
 target_count_create(struct iter_qstate* iq)
 {
 	if(!iq->target_count) {
-		iq->target_count = (int*)calloc(2, sizeof(int));
+		iq->target_count = (int*)calloc(3, sizeof(int));
 		/* if calloc fails we simply do not track this number */
 		if(iq->target_count)
 			iq->target_count[0] = 1;
@@ -630,6 +638,15 @@ target_count_increase(struct iter_qstate
 	target_count_create(iq);
 	if(iq->target_count)
 		iq->target_count[1] += num;
+	iq->dp_target_count++;
+}
+
+static void
+target_count_increase_nx(struct iter_qstate* iq, int num)
+{
+	target_count_create(iq);
+	if(iq->target_count)
+		iq->target_count[2] += num;
 }
 
 /**
@@ -652,13 +669,15 @@ target_count_increase(struct iter_qstate
  * @param subq_ret: if newly allocated, the subquerystate, or NULL if it does
  * 	not need initialisation.
  * @param v: if true, validation is done on the subquery.
+ * @param detached: true if this qstate should not attach to the subquery
  * @return false on error (malloc).
  */
 static int
 generate_sub_request(uint8_t* qname, size_t qnamelen, uint16_t qtype, 
 	uint16_t qclass, struct module_qstate* qstate, int id,
 	struct iter_qstate* iq, enum iter_state initial_state, 
-	enum iter_state finalstate, struct module_qstate** subq_ret, int v)
+	enum iter_state finalstate, struct module_qstate** subq_ret, int v,
+	int detached)
 {
 	struct module_qstate* subq = NULL;
 	struct iter_qstate* subiq = NULL;
@@ -685,11 +704,23 @@ generate_sub_request(uint8_t* qname, siz
 		valrec = 1;
 	}
 	
-	/* attach subquery, lookup existing or make a new one */
-	fptr_ok(fptr_whitelist_modenv_attach_sub(qstate->env->attach_sub));
-	if(!(*qstate->env->attach_sub)(qstate, &qinf, qflags, prime, valrec,
-		&subq)) {
-		return 0;
+	if(detached) {
+		struct mesh_state* sub = NULL;
+		fptr_ok(fptr_whitelist_modenv_add_sub(
+			qstate->env->add_sub));
+		if(!(*qstate->env->add_sub)(qstate, &qinf,
+			qflags, prime, valrec, &subq, &sub)){
+			return 0;
+		}
+	}
+	else {
+		/* attach subquery, lookup existing or make a new one */
+		fptr_ok(fptr_whitelist_modenv_attach_sub(
+			qstate->env->attach_sub));
+		if(!(*qstate->env->attach_sub)(qstate, &qinf, qflags, prime,
+			valrec, &subq)) {
+			return 0;
+		}
 	}
 	*subq_ret = subq;
 	if(subq) {
@@ -712,6 +743,7 @@ generate_sub_request(uint8_t* qname, siz
 		subiq->target_count = iq->target_count;
 		if(iq->target_count)
 			iq->target_count[0] ++; /* extra reference */
+		subiq->dp_target_count = 0;
 		subiq->num_current_queries = 0;
 		subiq->depth = iq->depth+1;
 		outbound_list_init(&subiq->outlist);
@@ -755,7 +787,7 @@ prime_root(struct module_qstate* qstate,
 	 * the normal INIT state logic (which would cause an infloop). */
 	if(!generate_sub_request((uint8_t*)"\000", 1, LDNS_RR_TYPE_NS, 
 		qclass, qstate, id, iq, QUERYTARGETS_STATE, PRIME_RESP_STATE,
-		&subq, 0)) {
+		&subq, 0, 0)) {
 		verbose(VERB_ALGO, "could not prime root");
 		return 0;
 	}
@@ -846,7 +878,7 @@ prime_stub(struct module_qstate* qstate,
 	 * redundant INIT state processing. */
 	if(!generate_sub_request(stub_dp->name, stub_dp->namelen, 
 		LDNS_RR_TYPE_NS, qclass, qstate, id, iq,
-		QUERYTARGETS_STATE, PRIME_RESP_STATE, &subq, 0)) {
+		QUERYTARGETS_STATE, PRIME_RESP_STATE, &subq, 0, 0)) {
 		verbose(VERB_ALGO, "could not prime stub");
 		errinf(qstate, "could not generate lookup for stub prime");
 		(void)error_response(qstate, id, LDNS_RCODE_SERVFAIL);
@@ -1021,7 +1053,7 @@ generate_a_aaaa_check(struct module_qsta
 		if(!generate_sub_request(s->rk.dname, s->rk.dname_len, 
 			ntohs(s->rk.type), ntohs(s->rk.rrset_class),
 			qstate, id, iq,
-			INIT_REQUEST_STATE, FINISHED_STATE, &subq, 1)) {
+			INIT_REQUEST_STATE, FINISHED_STATE, &subq, 1, 0)) {
 			verbose(VERB_ALGO, "could not generate addr check");
 			return;
 		}
@@ -1065,7 +1097,7 @@ generate_ns_check(struct module_qstate*
 		iq->dp->name, LDNS_RR_TYPE_NS, iq->qchase.qclass);
 	if(!generate_sub_request(iq->dp->name, iq->dp->namelen, 
 		LDNS_RR_TYPE_NS, iq->qchase.qclass, qstate, id, iq,
-		INIT_REQUEST_STATE, FINISHED_STATE, &subq, 1)) {
+		INIT_REQUEST_STATE, FINISHED_STATE, &subq, 1, 0)) {
 		verbose(VERB_ALGO, "could not generate ns check");
 		return;
 	}
@@ -1122,7 +1154,7 @@ generate_dnskey_prefetch(struct module_q
 		iq->dp->name, LDNS_RR_TYPE_DNSKEY, iq->qchase.qclass);
 	if(!generate_sub_request(iq->dp->name, iq->dp->namelen, 
 		LDNS_RR_TYPE_DNSKEY, iq->qchase.qclass, qstate, id, iq,
-		INIT_REQUEST_STATE, FINISHED_STATE, &subq, 0)) {
+		INIT_REQUEST_STATE, FINISHED_STATE, &subq, 0, 0)) {
 		/* we'll be slower, but it'll work */
 		verbose(VERB_ALGO, "could not generate dnskey prefetch");
 		return;
@@ -1311,6 +1343,7 @@ processInitRequest(struct module_qstate*
 			iq->refetch_glue = 0;
 			iq->query_restart_count++;
 			iq->sent_count = 0;
+			iq->dp_target_count = 0;
 			sock_list_insert(&qstate->reply_origin, NULL, 0, qstate->region);
 			if(qstate->env->cfg->qname_minimisation)
 				iq->minimisation_state = INIT_MINIMISE_STATE;
@@ -1688,7 +1721,7 @@ generate_parentside_target_query(struct
 {
 	struct module_qstate* subq;
 	if(!generate_sub_request(name, namelen, qtype, qclass, qstate, 
-		id, iq, INIT_REQUEST_STATE, FINISHED_STATE, &subq, 0))
+		id, iq, INIT_REQUEST_STATE, FINISHED_STATE, &subq, 0, 0))
 		return 0;
 	if(subq) {
 		struct iter_qstate* subiq = 
@@ -1739,7 +1772,7 @@ generate_target_query(struct module_qsta
 {
 	struct module_qstate* subq;
 	if(!generate_sub_request(name, namelen, qtype, qclass, qstate, 
-		id, iq, INIT_REQUEST_STATE, FINISHED_STATE, &subq, 0))
+		id, iq, INIT_REQUEST_STATE, FINISHED_STATE, &subq, 0, 0))
 		return 0;
 	log_nametypeclass(VERB_QUERY, "new target", name, qtype, qclass);
 	return 1;
@@ -1778,6 +1811,14 @@ query_for_targets(struct module_qstate*
 			"number of glue fetches %d", s, iq->target_count[1]);
 		return 0;
 	}
+	if(iq->dp_target_count > MAX_DP_TARGET_COUNT) {
+		char s[LDNS_MAX_DOMAINLEN+1];
+		dname_str(qstate->qinfo.qname, s);
+		verbose(VERB_QUERY, "request %s has exceeded the maximum "
+			"number of glue fetches %d to a single delegation point",
+			s, iq->dp_target_count);
+		return 0;
+	}
 
 	iter_mark_cycle_targets(qstate, iq->dp);
 	missing = (int)delegpt_count_missing_targets(iq->dp);
@@ -1891,7 +1932,7 @@ processLastResort(struct module_qstate*
 			for(a = p->target_list; a; a=a->next_target) {
 				(void)delegpt_add_addr(iq->dp, qstate->region,
 					&a->addr, a->addrlen, a->bogus,
-					a->lame, a->tls_auth_name);
+					a->lame, a->tls_auth_name, NULL);
 			}
 		}
 		iq->dp->has_parent_side_NS = 1;
@@ -1908,6 +1949,7 @@ processLastResort(struct module_qstate*
 			iq->refetch_glue = 1;
 			iq->query_restart_count++;
 			iq->sent_count = 0;
+			iq->dp_target_count = 0;
 			if(qstate->env->cfg->qname_minimisation)
 				iq->minimisation_state = INIT_MINIMISE_STATE;
 			return next_state(iq, INIT_REQUEST_STATE);
@@ -2073,7 +2115,7 @@ processDSNSFind(struct module_qstate* qs
 		iq->dsns_point, LDNS_RR_TYPE_NS, iq->qchase.qclass);
 	if(!generate_sub_request(iq->dsns_point, iq->dsns_point_len, 
 		LDNS_RR_TYPE_NS, iq->qchase.qclass, qstate, id, iq,
-		INIT_REQUEST_STATE, FINISHED_STATE, &subq, 0)) {
+		INIT_REQUEST_STATE, FINISHED_STATE, &subq, 0, 0)) {
 		errinf_dname(qstate, "for DS query parent-child nameserver search, could not generate NS lookup for", iq->dsns_point);
 		return error_response_cache(qstate, id, LDNS_RCODE_SERVFAIL);
 	}
@@ -2129,6 +2171,13 @@ processQueryTargets(struct module_qstate
 		errinf(qstate, "exceeded the maximum number of sends");
 		return error_response(qstate, id, LDNS_RCODE_SERVFAIL);
 	}
+	if(iq->target_count && iq->target_count[2] > MAX_TARGET_NX) {
+		verbose(VERB_QUERY, "request has exceeded the maximum "
+			" number of nxdomain nameserver lookups with %d",
+			iq->target_count[2]);
+		errinf(qstate, "exceeded the maximum nameserver nxdomains");
+		return error_response(qstate, id, LDNS_RCODE_SERVFAIL);
+	}
 	
 	/* Make sure we have a delegation point, otherwise priming failed
 	 * or another failure occurred */
@@ -2231,12 +2280,41 @@ processQueryTargets(struct module_qstate
 				iq->qinfo_out.qtype, iq->qinfo_out.qclass, 
 				qstate->query_flags, qstate->region, 
 				qstate->env->scratch, 0);
-			if(msg && msg->rep->an_numrrsets == 0
-				&& FLAGS_GET_RCODE(msg->rep->flags) == 
+			if(msg && FLAGS_GET_RCODE(msg->rep->flags) ==
 				LDNS_RCODE_NOERROR)
 				/* no need to send query if it is already 
-				 * cached as NOERROR/NODATA */
+				 * cached as NOERROR */
 				return 1;
+			if(msg && FLAGS_GET_RCODE(msg->rep->flags) ==
+				LDNS_RCODE_NXDOMAIN &&
+				qstate->env->need_to_validate &&
+				qstate->env->cfg->harden_below_nxdomain) {
+				if(msg->rep->security == sec_status_secure) {
+					iq->response = msg;
+					return final_state(iq);
+				}
+				if(msg->rep->security == sec_status_unchecked) {
+					struct module_qstate* subq = NULL;
+					if(!generate_sub_request(
+						iq->qinfo_out.qname,
+						iq->qinfo_out.qname_len,
+						iq->qinfo_out.qtype,
+						iq->qinfo_out.qclass,
+						qstate, id, iq,
+						INIT_REQUEST_STATE,
+						FINISHED_STATE, &subq, 1, 1))
+						verbose(VERB_ALGO,
+						"could not validate NXDOMAIN "
+						"response");
+				}
+			}
+			if(msg && FLAGS_GET_RCODE(msg->rep->flags) ==
+				LDNS_RCODE_NXDOMAIN) {
+				/* return and add a label in the next
+				 * minimisation iteration.
+				 */
+				return 1;
+			}
 		}
 	}
 	if(iq->minimisation_state == SKIP_MINIMISE_STATE) {
@@ -2312,6 +2390,8 @@ processQueryTargets(struct module_qstate
 	 * generated query will immediately be discarded due to depth and
 	 * that servfail is cached, which is not good as opportunism goes. */
 	if(iq->depth < ie->max_dependency_depth
+		&& iq->num_target_queries == 0
+		&& (!iq->target_count || iq->target_count[2]==0)
 		&& iq->sent_count < TARGET_FETCH_STOP) {
 		tf_policy = ie->target_fetch_policy[iq->depth];
 	}
@@ -2350,6 +2430,7 @@ processQueryTargets(struct module_qstate
 			iq->num_current_queries++; /* RespState decrements it*/
 			iq->referral_count++; /* make sure we don't loop */
 			iq->sent_count = 0;
+			iq->dp_target_count = 0;
 			iq->state = QUERY_RESP_STATE;
 			return 1;
 		}
@@ -2437,6 +2518,7 @@ processQueryTargets(struct module_qstate
 					iq->num_current_queries++; /* RespState decrements it*/
 					iq->referral_count++; /* make sure we don't loop */
 					iq->sent_count = 0;
+					iq->dp_target_count = 0;
 					iq->state = QUERY_RESP_STATE;
 					return 1;
 				}
@@ -2716,7 +2798,8 @@ processQueryResponse(struct module_qstat
 				/* Make subrequest to validate intermediate
 				 * NXDOMAIN if harden-below-nxdomain is
 				 * enabled. */
-				if(qstate->env->cfg->harden_below_nxdomain) {
+				if(qstate->env->cfg->harden_below_nxdomain &&
+					qstate->env->need_to_validate) {
 					struct module_qstate* subq = NULL;
 					log_query_info(VERB_QUERY,
 						"schedule NXDOMAIN validation:",
@@ -2728,16 +2811,10 @@ processQueryResponse(struct module_qstat
 						iq->response->qinfo.qclass,
 						qstate, id, iq,
 						INIT_REQUEST_STATE,
-						FINISHED_STATE, &subq, 1))
+						FINISHED_STATE, &subq, 1, 1))
 						verbose(VERB_ALGO,
 						"could not validate NXDOMAIN "
 						"response");
-					outbound_list_clear(&iq->outlist);
-					iq->num_current_queries = 0;
-					fptr_ok(fptr_whitelist_modenv_detach_subs(
-						qstate->env->detach_subs));
-					(*qstate->env->detach_subs)(qstate);
-					iq->num_target_queries = 0;
 				}
 			}
 			return next_state(iq, QUERYTARGETS_STATE);
@@ -2821,6 +2898,7 @@ processQueryResponse(struct module_qstat
 		/* Count this as a referral. */
 		iq->referral_count++;
 		iq->sent_count = 0;
+		iq->dp_target_count = 0;
 		/* see if the next dp is a trust anchor, or a DS was sent
 		 * along, indicating dnssec is expected for next zone */
 		iq->dnssec_expected = iter_indicates_dnssec(qstate->env, 
@@ -2897,6 +2975,7 @@ processQueryResponse(struct module_qstat
 		iq->dsns_point = NULL;
 		iq->auth_zone_response = 0;
 		iq->sent_count = 0;
+		iq->dp_target_count = 0;
 		if(iq->minimisation_state != MINIMISE_STATE)
 			/* Only count as query restart when it is not an extra
 			 * query as result of qname minimisation. */
@@ -3089,7 +3168,7 @@ processPrimeResponse(struct module_qstat
 		if(!generate_sub_request(qstate->qinfo.qname, 
 			qstate->qinfo.qname_len, qstate->qinfo.qtype,
 			qstate->qinfo.qclass, qstate, id, iq,
-			INIT_REQUEST_STATE, FINISHED_STATE, &subq, 1)) {
+			INIT_REQUEST_STATE, FINISHED_STATE, &subq, 1, 0)) {
 			verbose(VERB_ALGO, "could not generate prime check");
 		}
 		generate_a_aaaa_check(qstate, iq, id);
@@ -3117,6 +3196,7 @@ static void
 processTargetResponse(struct module_qstate* qstate, int id,
 	struct module_qstate* forq)
 {
+	struct iter_env* ie = (struct iter_env*)qstate->env->modinfo[id];
 	struct iter_qstate* iq = (struct iter_qstate*)qstate->minfo[id];
 	struct iter_qstate* foriq = (struct iter_qstate*)forq->minfo[id];
 	struct ub_packed_rrset_key* rrset;
@@ -3154,7 +3234,7 @@ processTargetResponse(struct module_qsta
 		log_rrset_key(VERB_ALGO, "add parentside glue to dp", 
 			iq->pside_glue);
 		if(!delegpt_add_rrset(foriq->dp, forq->region, 
-			iq->pside_glue, 1))
+			iq->pside_glue, 1, NULL))
 			log_err("out of memory adding pside glue");
 	}
 
@@ -3165,6 +3245,7 @@ processTargetResponse(struct module_qsta
 	 * response type was ANSWER. */
 	rrset = reply_find_answer_rrset(&iq->qchase, qstate->return_msg->rep);
 	if(rrset) {
+		int additions = 0;
 		/* if CNAMEs have been followed - add new NS to delegpt. */
 		/* BTW. RFC 1918 says NS should not have got CNAMEs. Robust. */
 		if(!delegpt_find_ns(foriq->dp, rrset->rk.dname, 
@@ -3176,13 +3257,23 @@ processTargetResponse(struct module_qsta
 		}
 		/* if dpns->lame then set the address(es) lame too */
 		if(!delegpt_add_rrset(foriq->dp, forq->region, rrset, 
-			dpns->lame))
+			dpns->lame, &additions))
 			log_err("out of memory adding targets");
+		if(!additions) {
+			/* no new addresses, increase the nxns counter, like
+			 * this could be a list of wildcards with no new
+			 * addresses */
+			target_count_increase_nx(foriq, 1);
+		}
 		verbose(VERB_ALGO, "added target response");
 		delegpt_log(VERB_ALGO, foriq->dp);
 	} else {
 		verbose(VERB_ALGO, "iterator TargetResponse failed");
+		delegpt_mark_neg(dpns, qstate->qinfo.qtype);
 		dpns->resolved = 1; /* fail the target */
+		if((dpns->got4 == 2 || !ie->supports_ipv4) &&
+			(dpns->got6 == 2 || !ie->supports_ipv6))
+			target_count_increase_nx(foriq, 1);
 	}
 }
 
@@ -3356,7 +3447,7 @@ processCollectClass(struct module_qstate
 				qstate->qinfo.qname_len, qstate->qinfo.qtype,
 				c, qstate, id, iq, INIT_REQUEST_STATE,
 				FINISHED_STATE, &subq, 
-				(int)!(qstate->query_flags&BIT_CD))) {
+				(int)!(qstate->query_flags&BIT_CD), 0)) {
 				errinf(qstate, "could not generate class ANY"
 					" lookup query");
 				return error_response(qstate, id, 
--- unbound-1.9.0.orig/iterator/iterator.h
+++ unbound-1.9.0/iterator/iterator.h
@@ -55,6 +55,11 @@ struct rbtree_type;
 
 /** max number of targets spawned for a query and its subqueries */
 #define MAX_TARGET_COUNT	64
+/** max number of target lookups per qstate, per delegation point */
+#define MAX_DP_TARGET_COUNT	16
+/** max number of nxdomains allowed for target lookups for a query and
+ * its subqueries */
+#define MAX_TARGET_NX		5
 /** max number of query restarts. Determines max number of CNAME chain. */
 #define MAX_RESTART_COUNT       8
 /** max number of referrals. Makes sure resolver does not run away */
@@ -305,9 +310,14 @@ struct iter_qstate {
 	int sent_count;
 	
 	/** number of target queries spawned in [1], for this query and its
-	 * subqueries, the malloced-array is shared, [0] refcount. */
+	 * subqueries, the malloced-array is shared, [0] refcount.
+	 * in [2] the number of nxdomains is counted. */
 	int* target_count;
 
+	/** number of target lookups per delegation point. Reset to 0 after
+	 * receiving referral answer. Not shared with subqueries. */
+	int dp_target_count;
+
 	/** if true, already tested for ratelimiting and passed the test */
 	int ratelimit_ok;
 
--- unbound-1.9.0.orig/services/cache/dns.c
+++ unbound-1.9.0/services/cache/dns.c
@@ -271,7 +271,7 @@ find_add_addrs(struct module_env* env, u
 		akey = rrset_cache_lookup(env->rrset_cache, ns->name, 
 			ns->namelen, LDNS_RR_TYPE_A, qclass, 0, now, 0);
 		if(akey) {
-			if(!delegpt_add_rrset_A(dp, region, akey, 0)) {
+			if(!delegpt_add_rrset_A(dp, region, akey, 0, NULL)) {
 				lock_rw_unlock(&akey->entry.lock);
 				return 0;
 			}
@@ -291,7 +291,7 @@ find_add_addrs(struct module_env* env, u
 		akey = rrset_cache_lookup(env->rrset_cache, ns->name, 
 			ns->namelen, LDNS_RR_TYPE_AAAA, qclass, 0, now, 0);
 		if(akey) {
-			if(!delegpt_add_rrset_AAAA(dp, region, akey, 0)) {
+			if(!delegpt_add_rrset_AAAA(dp, region, akey, 0, NULL)) {
 				lock_rw_unlock(&akey->entry.lock);
 				return 0;
 			}
@@ -325,7 +325,8 @@ cache_fill_missing(struct module_env* en
 		akey = rrset_cache_lookup(env->rrset_cache, ns->name, 
 			ns->namelen, LDNS_RR_TYPE_A, qclass, 0, now, 0);
 		if(akey) {
-			if(!delegpt_add_rrset_A(dp, region, akey, ns->lame)) {
+			if(!delegpt_add_rrset_A(dp, region, akey, ns->lame,
+				NULL)) {
 				lock_rw_unlock(&akey->entry.lock);
 				return 0;
 			}
@@ -345,7 +346,8 @@ cache_fill_missing(struct module_env* en
 		akey = rrset_cache_lookup(env->rrset_cache, ns->name, 
 			ns->namelen, LDNS_RR_TYPE_AAAA, qclass, 0, now, 0);
 		if(akey) {
-			if(!delegpt_add_rrset_AAAA(dp, region, akey, ns->lame)) {
+			if(!delegpt_add_rrset_AAAA(dp, region, akey, ns->lame,
+				NULL)) {
 				lock_rw_unlock(&akey->entry.lock);
 				return 0;
 			}
--- unbound-1.9.0.orig/util/data/dname.c
+++ unbound-1.9.0/util/data/dname.c
@@ -231,17 +231,28 @@ int
 dname_pkt_compare(sldns_buffer* pkt, uint8_t* d1, uint8_t* d2)
 {
 	uint8_t len1, len2;
+	int count1 = 0, count2 = 0;
 	log_assert(pkt && d1 && d2);
 	len1 = *d1++;
 	len2 = *d2++;
 	while( len1 != 0 || len2 != 0 ) {
 		/* resolve ptrs */
 		if(LABEL_IS_PTR(len1)) {
+			if((size_t)PTR_OFFSET(len1, *d1)
+				>= sldns_buffer_limit(pkt))
+				return -1;
+			if(count1++ > MAX_COMPRESS_PTRS)
+				return -1;
 			d1 = sldns_buffer_at(pkt, PTR_OFFSET(len1, *d1));
 			len1 = *d1++;
 			continue;
 		}
 		if(LABEL_IS_PTR(len2)) {
+			if((size_t)PTR_OFFSET(len2, *d2)
+				>= sldns_buffer_limit(pkt))
+				return 1;
+			if(count2++ > MAX_COMPRESS_PTRS)
+				return 1;
 			d2 = sldns_buffer_at(pkt, PTR_OFFSET(len2, *d2));
 			len2 = *d2++;
 			continue;
@@ -300,12 +311,18 @@ dname_pkt_hash(sldns_buffer* pkt, uint8_
 	uint8_t labuf[LDNS_MAX_LABELLEN+1];
 	uint8_t lablen;
 	int i;
+	int count = 0;
 
 	/* preserve case of query, make hash label by label */
 	lablen = *dname++;
 	while(lablen) {
 		if(LABEL_IS_PTR(lablen)) {
 			/* follow pointer */
+			if((size_t)PTR_OFFSET(lablen, *dname)
+				>= sldns_buffer_limit(pkt))
+				return h;
+			if(count++ > MAX_COMPRESS_PTRS)
+				return h;
 			dname = sldns_buffer_at(pkt, PTR_OFFSET(lablen, *dname));
 			lablen = *dname++;
 			continue;
@@ -333,6 +350,9 @@ void dname_pkt_copy(sldns_buffer* pkt, u
 	while(lablen) {
 		if(LABEL_IS_PTR(lablen)) {
 			/* follow pointer */
+			if((size_t)PTR_OFFSET(lablen, *dname)
+				>= sldns_buffer_limit(pkt))
+				return;
 			dname = sldns_buffer_at(pkt, PTR_OFFSET(lablen, *dname));
 			lablen = *dname++;
 			continue;
@@ -357,6 +377,7 @@ void dname_pkt_copy(sldns_buffer* pkt, u
 void dname_print(FILE* out, struct sldns_buffer* pkt, uint8_t* dname)
 {
 	uint8_t lablen;
+	int count = 0;
 	if(!out) out = stdout;
 	if(!dname) return;
 
@@ -370,6 +391,15 @@ void dname_print(FILE* out, struct sldns
 				fputs("??compressionptr??", out);
 				return;
 			}
+			if((size_t)PTR_OFFSET(lablen, *dname)
+				>= sldns_buffer_limit(pkt)) {
+				fputs("??compressionptr??", out);
+				return;
+			}
+			if(count++ > MAX_COMPRESS_PTRS) {
+				fputs("??compressionptr??", out);
+				return;
+			}
 			dname = sldns_buffer_at(pkt, PTR_OFFSET(lablen, *dname));
 			lablen = *dname++;
 			continue;
--- unbound-1.9.0.orig/util/data/msgparse.c
+++ unbound-1.9.0/util/data/msgparse.c
@@ -55,7 +55,11 @@ smart_compare(sldns_buffer* pkt, uint8_t
 {
 	if(LABEL_IS_PTR(*dnow)) {
 		/* ptr points to a previous dname */
-		uint8_t* p = sldns_buffer_at(pkt, PTR_OFFSET(dnow[0], dnow[1]));
+		uint8_t* p;
+		if((size_t)PTR_OFFSET(dnow[0], dnow[1])
+			>= sldns_buffer_limit(pkt))
+			return -1;
+		p = sldns_buffer_at(pkt, PTR_OFFSET(dnow[0], dnow[1]));
 		if( p == dprfirst || p == dprlast )
 			return 0;
 		/* prev dname is also a ptr, both ptrs are the same. */
@@ -1061,18 +1065,18 @@ parse_edns_from_pkt(sldns_buffer* pkt, s
 	size_t rdata_len;
 	uint8_t* rdata_ptr;
 	log_assert(LDNS_QDCOUNT(sldns_buffer_begin(pkt)) == 1);
+	memset(edns, 0, sizeof(*edns));
 	if(LDNS_ANCOUNT(sldns_buffer_begin(pkt)) != 0 ||
 		LDNS_NSCOUNT(sldns_buffer_begin(pkt)) != 0) {
 		if(!skip_pkt_rrs(pkt, ((int)LDNS_ANCOUNT(sldns_buffer_begin(pkt)))+
 			((int)LDNS_NSCOUNT(sldns_buffer_begin(pkt)))))
-			return 0;
+			return LDNS_RCODE_FORMERR;
 	}
 	/* check edns section is present */
 	if(LDNS_ARCOUNT(sldns_buffer_begin(pkt)) > 1) {
 		return LDNS_RCODE_FORMERR;
 	}
 	if(LDNS_ARCOUNT(sldns_buffer_begin(pkt)) == 0) {
-		memset(edns, 0, sizeof(*edns));
 		edns->udp_size = 512;
 		return 0;
 	}
