untrusted comment: verify with openbsd-67-base.pub
RWRmkIA877Io3mVSojvKcxtlE9Mv3uR7hxiuBjxKWzOAHawQe8Kp+5dNgenvvNR/P64NlslfR+nCw/Pd04kc5YoT85YYt9EzHAk=

OpenBSD 6.7 errata 005, May 22, 2020:

Specially crafted queries may crash unbound and unwind.
Both can be tricked into amplifying an incoming query.

Apply by doing:
   signify -Vep /etc/signify/openbsd-67-base.pub -x 005_unbound.patch.sig \
       -m - | (cd /usr/src && patch -p0)

And then rebuild and install unbound and unwind:
   cd /usr/src/usr.sbin/unbound
   make -f Makefile.bsd-wrapper obj
   make -f Makefile.bsd-wrapper clean
   make -f Makefile.bsd-wrapper
   make -f Makefile.bsd-wrapper install

   cd /usr/src/sbin/unwind
   make obj
   make clean
   make
   make install

Index: sbin/unwind/libunbound/iterator/iter_delegpt.c
===================================================================
RCS file: /home/cvs/src/sbin/unwind/libunbound/iterator/iter_delegpt.c,v
retrieving revision 1.1
diff -u -p -r1.1 iter_delegpt.c
--- sbin/unwind/libunbound/iterator/iter_delegpt.c      23 Jan 2019 13:05:27 -0000      1.1
+++ sbin/unwind/libunbound/iterator/iter_delegpt.c      19 May 2020 16:50:57 -0000
@@ -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);
       }
}

Index: sbin/unwind/libunbound/iterator/iter_delegpt.h
===================================================================
RCS file: /home/cvs/src/sbin/unwind/libunbound/iterator/iter_delegpt.h,v
retrieving revision 1.1
diff -u -p -r1.1 iter_delegpt.h
--- sbin/unwind/libunbound/iterator/iter_delegpt.h      23 Jan 2019 13:05:27 -0000      1.1
+++ sbin/unwind/libunbound/iterator/iter_delegpt.h      19 May 2020 16:50:57 -0000
@@ -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.
@@ -340,6 +346,14 @@ size_t delegpt_count_targets(struct dele
 */
struct delegpt* delegpt_from_message(struct dns_msg* msg,
       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.
Index: sbin/unwind/libunbound/iterator/iter_scrub.c
===================================================================
RCS file: /home/cvs/src/sbin/unwind/libunbound/iterator/iter_scrub.c,v
retrieving revision 1.2
diff -u -p -r1.2 iter_scrub.c
--- sbin/unwind/libunbound/iterator/iter_scrub.c        18 Dec 2019 13:03:30 -0000      1.2
+++ sbin/unwind/libunbound/iterator/iter_scrub.c        19 May 2020 16:50:57 -0000
@@ -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;
       if(qnamelen <= dname_rrset->dname_len)
               return 0;
@@ -388,7 +402,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 */
@@ -439,7 +453,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
@@ -460,7 +474,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;
Index: sbin/unwind/libunbound/iterator/iter_utils.c
===================================================================
RCS file: /home/cvs/src/sbin/unwind/libunbound/iterator/iter_utils.c,v
retrieving revision 1.3
diff -u -p -r1.3 iter_utils.c
--- sbin/unwind/libunbound/iterator/iter_utils.c        14 Jul 2019 06:32:35 -0000      1.3
+++ sbin/unwind/libunbound/iterator/iter_utils.c        19 May 2020 16:50:57 -0000
@@ -1142,7 +1142,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);
               }
@@ -1154,7 +1154,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);
               }
Index: sbin/unwind/libunbound/iterator/iterator.c
===================================================================
RCS file: /home/cvs/src/sbin/unwind/libunbound/iterator/iterator.c,v
retrieving revision 1.6
diff -u -p -r1.6 iterator.c
--- sbin/unwind/libunbound/iterator/iterator.c  18 Dec 2019 13:03:30 -0000      1.6
+++ sbin/unwind/libunbound/iterator/iterator.c  19 May 2020 16:50:57 -0000
@@ -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 */
@@ -621,7 +629,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;
@@ -634,6 +642,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;
}

/**
@@ -656,13 +673,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;
@@ -689,11 +708,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) {
@@ -716,6 +747,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);
@@ -759,7 +791,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;
       }
@@ -850,7 +882,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);
@@ -1025,7 +1057,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;
               }
@@ -1069,7 +1101,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;
       }
@@ -1126,7 +1158,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;
@@ -1315,6 +1347,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;
@@ -1693,7 +1726,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 =
@@ -1744,7 +1777,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;
@@ -1783,6 +1816,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);
@@ -1896,7 +1937,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;
@@ -1913,6 +1954,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);
@@ -2078,7 +2120,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);
       }
@@ -2136,6 +2178,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 */
@@ -2240,12 +2289,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) {
@@ -2321,6 +2399,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];
       }
@@ -2366,6 +2446,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;
               }
@@ -2453,6 +2534,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;
                               }
@@ -2747,7 +2829,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:",
@@ -2759,16 +2842,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);
@@ -2852,6 +2929,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,
@@ -2928,6 +3006,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. */
@@ -3120,7 +3199,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);
@@ -3148,6 +3227,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;
@@ -3185,7 +3265,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");
       }

@@ -3196,6 +3276,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,
@@ -3207,13 +3288,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);
       }
}

@@ -3387,7 +3478,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,
Index: sbin/unwind/libunbound/iterator/iterator.h
===================================================================
RCS file: /home/cvs/src/sbin/unwind/libunbound/iterator/iterator.h,v
retrieving revision 1.1
diff -u -p -r1.1 iterator.h
--- sbin/unwind/libunbound/iterator/iterator.h  23 Jan 2019 13:05:27 -0000      1.1
+++ sbin/unwind/libunbound/iterator/iterator.h  19 May 2020 16:50:57 -0000
@@ -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,8 +310,13 @@ 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;
Index: sbin/unwind/libunbound/services/cache/dns.c
===================================================================
RCS file: /home/cvs/src/sbin/unwind/libunbound/services/cache/dns.c,v
retrieving revision 1.3
diff -u -p -r1.3 dns.c
--- sbin/unwind/libunbound/services/cache/dns.c 23 Mar 2020 08:37:27 -0000      1.3
+++ sbin/unwind/libunbound/services/cache/dns.c 19 May 2020 16:50:57 -0000
@@ -273,7 +273,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;
                       }
@@ -293,7 +293,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;
                       }
@@ -327,7 +327,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;
                       }
@@ -347,7 +348,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;
                       }
Index: sbin/unwind/libunbound/util/data/dname.c
===================================================================
RCS file: /home/cvs/src/sbin/unwind/libunbound/util/data/dname.c,v
retrieving revision 1.3
diff -u -p -r1.3 dname.c
--- sbin/unwind/libunbound/util/data/dname.c    23 Mar 2020 08:37:28 -0000      1.3
+++ sbin/unwind/libunbound/util/data/dname.c    19 May 2020 16:50:57 -0000
@@ -233,17 +233,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;
@@ -302,12 +313,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;
@@ -341,6 +358,9 @@ void dname_pkt_copy(sldns_buffer* pkt, u
                               return;
                       }
                       /* 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;
@@ -369,6 +389,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;

@@ -379,6 +400,15 @@ void dname_print(FILE* out, struct sldns
               if(LABEL_IS_PTR(lablen)) {
                       /* follow pointer */
                       if(!pkt) {
+                               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;
                       }
Index: sbin/unwind/libunbound/util/data/msgparse.c
===================================================================
RCS file: /home/cvs/src/sbin/unwind/libunbound/util/data/msgparse.c,v
retrieving revision 1.2
diff -u -p -r1.2 msgparse.c
--- sbin/unwind/libunbound/util/data/msgparse.c 3 Oct 2019 13:32:27 -0000       1.2
+++ sbin/unwind/libunbound/util/data/msgparse.c 19 May 2020 16:50:57 -0000
@@ -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. */
Index: usr.sbin/unbound/iterator/iter_delegpt.c
===================================================================
RCS file: /home/cvs/src/usr.sbin/unbound/iterator/iter_delegpt.c,v
retrieving revision 1.3
diff -u -p -r1.3 iter_delegpt.c
--- usr.sbin/unbound/iterator/iter_delegpt.c    17 Sep 2018 09:46:12 -0000      1.3
+++ usr.sbin/unbound/iterator/iter_delegpt.c    19 May 2020 16:49:57 -0000
@@ -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);
       }
}

Index: usr.sbin/unbound/iterator/iter_delegpt.h
===================================================================
RCS file: /home/cvs/src/usr.sbin/unbound/iterator/iter_delegpt.h,v
retrieving revision 1.5
diff -u -p -r1.5 iter_delegpt.h
--- usr.sbin/unbound/iterator/iter_delegpt.h    20 Sep 2018 23:15:39 -0000      1.5
+++ usr.sbin/unbound/iterator/iter_delegpt.h    19 May 2020 16:49:57 -0000
@@ -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.
@@ -340,6 +346,14 @@ size_t delegpt_count_targets(struct dele
 */
struct delegpt* delegpt_from_message(struct dns_msg* msg,
       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.
Index: usr.sbin/unbound/iterator/iter_scrub.c
===================================================================
RCS file: /home/cvs/src/usr.sbin/unbound/iterator/iter_scrub.c,v
retrieving revision 1.10
diff -u -p -r1.10 iter_scrub.c
--- usr.sbin/unbound/iterator/iter_scrub.c      18 Dec 2019 11:04:13 -0000      1.10
+++ usr.sbin/unbound/iterator/iter_scrub.c      19 May 2020 16:49:57 -0000
@@ -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;
       if(qnamelen <= dname_rrset->dname_len)
               return 0;
@@ -388,7 +402,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 */
@@ -439,7 +453,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
@@ -460,7 +474,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;
Index: usr.sbin/unbound/iterator/iter_utils.c
===================================================================
RCS file: /home/cvs/src/usr.sbin/unbound/iterator/iter_utils.c,v
retrieving revision 1.13
diff -u -p -r1.13 iter_utils.c
--- usr.sbin/unbound/iterator/iter_utils.c      28 Jun 2019 15:17:16 -0000      1.13
+++ usr.sbin/unbound/iterator/iter_utils.c      19 May 2020 16:49:57 -0000
@@ -1142,7 +1142,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);
               }
@@ -1154,7 +1154,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);
               }
Index: usr.sbin/unbound/iterator/iterator.c
===================================================================
RCS file: /home/cvs/src/usr.sbin/unbound/iterator/iterator.c,v
retrieving revision 1.24
diff -u -p -r1.24 iterator.c
--- usr.sbin/unbound/iterator/iterator.c        18 Dec 2019 11:04:13 -0000      1.24
+++ usr.sbin/unbound/iterator/iterator.c        19 May 2020 16:49:57 -0000
@@ -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 */
@@ -621,7 +629,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;
@@ -634,6 +642,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;
}

/**
@@ -656,13 +673,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;
@@ -689,11 +708,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) {
@@ -716,6 +747,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);
@@ -759,7 +791,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;
       }
@@ -850,7 +882,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);
@@ -1025,7 +1057,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;
               }
@@ -1069,7 +1101,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;
       }
@@ -1126,7 +1158,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;
@@ -1315,6 +1347,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;
@@ -1693,7 +1726,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 =
@@ -1744,7 +1777,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;
@@ -1783,6 +1816,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);
@@ -1896,7 +1937,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;
@@ -1913,6 +1954,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);
@@ -2078,7 +2120,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);
       }
@@ -2136,6 +2178,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 */
@@ -2240,12 +2289,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) {
@@ -2321,6 +2399,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];
       }
@@ -2366,6 +2446,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;
               }
@@ -2453,6 +2534,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;
                               }
@@ -2747,7 +2829,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:",
@@ -2759,16 +2842,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);
@@ -2852,6 +2929,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,
@@ -2928,6 +3006,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. */
@@ -3120,7 +3199,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);
@@ -3148,6 +3227,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;
@@ -3185,7 +3265,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");
       }

@@ -3196,6 +3276,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,
@@ -3207,13 +3288,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);
       }
}

@@ -3387,7 +3478,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,
Index: usr.sbin/unbound/iterator/iterator.h
===================================================================
RCS file: /home/cvs/src/usr.sbin/unbound/iterator/iterator.h,v
retrieving revision 1.13
diff -u -p -r1.13 iterator.h
--- usr.sbin/unbound/iterator/iterator.h        4 Dec 2018 12:05:07 -0000       1.13
+++ usr.sbin/unbound/iterator/iterator.h        19 May 2020 16:49:57 -0000
@@ -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,8 +310,13 @@ 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;
Index: usr.sbin/unbound/services/cache/dns.c
===================================================================
RCS file: /home/cvs/src/usr.sbin/unbound/services/cache/dns.c,v
retrieving revision 1.14
diff -u -p -r1.14 dns.c
--- usr.sbin/unbound/services/cache/dns.c       19 Mar 2020 17:48:08 -0000      1.14
+++ usr.sbin/unbound/services/cache/dns.c       19 May 2020 16:49:57 -0000
@@ -273,7 +273,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;
                       }
@@ -293,7 +293,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;
                       }
@@ -327,7 +327,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;
                       }
@@ -347,7 +348,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;
                       }
Index: usr.sbin/unbound/util/data/dname.c
===================================================================
RCS file: /home/cvs/src/usr.sbin/unbound/util/data/dname.c,v
retrieving revision 1.6
diff -u -p -r1.6 dname.c
--- usr.sbin/unbound/util/data/dname.c  19 Mar 2020 17:48:08 -0000      1.6
+++ usr.sbin/unbound/util/data/dname.c  19 May 2020 16:49:57 -0000
@@ -233,17 +233,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;
@@ -302,12 +313,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;
@@ -341,6 +358,9 @@ void dname_pkt_copy(sldns_buffer* pkt, u
                               return;
                       }
                       /* 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;
@@ -369,6 +389,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;

@@ -379,6 +400,15 @@ void dname_print(FILE* out, struct sldns
               if(LABEL_IS_PTR(lablen)) {
                       /* follow pointer */
                       if(!pkt) {
+                               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;
                       }
Index: usr.sbin/unbound/util/data/msgparse.c
===================================================================
RCS file: /home/cvs/src/usr.sbin/unbound/util/data/msgparse.c,v
retrieving revision 1.6
diff -u -p -r1.6 msgparse.c
--- usr.sbin/unbound/util/data/msgparse.c       3 Oct 2019 13:32:27 -0000       1.6
+++ usr.sbin/unbound/util/data/msgparse.c       19 May 2020 16:49:57 -0000
@@ -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. */