untrusted comment: verify with openbsd-66-base.pub RWSvK/c+cFe24EIj2KJX+dqHO9pX01SZrHDq+VQIyUn+sDqCKnPh5ZokDu7T9jBGRqwCCVf3h2CvRfnYLQRMH2/P80BGxs1RmQg= OpenBSD 6.6 errata 028, 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-66-base.pub -x 028_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 20 May 2020 09:54:09 -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 20 May 2020 09:54:09 -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.1 diff -u -p -r1.1 iter_scrub.c --- sbin/unwind/libunbound/iterator/iter_scrub.c 23 Jan 2019 13:05:27 -0000 1.1 +++ sbin/unwind/libunbound/iterator/iter_scrub.c 20 May 2020 09:54:09 -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; log_assert(qnamelen > dname_rrset->dname_len); /* DNAME from com. to net. with qname example.com. -> example.net. */ @@ -384,7 +398,7 @@ scrub_normalize(sldns_buffer* pkt, struc /* check next cname */ uint8_t* t = NULL; size_t tlen = 0; - if(!parse_get_cname_target(nx, &t, &tlen)) + if(!parse_get_cname_target(nx, &t, &tlen, pkt)) return 0; if(dname_pkt_compare(pkt, alias, t) == 0) { /* it's OK and better capitalized */ @@ -435,7 +449,7 @@ scrub_normalize(sldns_buffer* pkt, struc size_t tlen = 0; if(synth_cname(sname, snamelen, nx, alias, &aliaslen, pkt) && - parse_get_cname_target(rrset, &t, &tlen) && + parse_get_cname_target(rrset, &t, &tlen, pkt) && dname_pkt_compare(pkt, alias, t) == 0) { /* the synthesized CNAME equals the * current CNAME. This CNAME is the @@ -456,7 +470,7 @@ scrub_normalize(sldns_buffer* pkt, struc } /* move to next name in CNAME chain */ - if(!parse_get_cname_target(rrset, &sname, &snamelen)) + if(!parse_get_cname_target(rrset, &sname, &snamelen, pkt)) return 0; prev = rrset; rrset = rrset->rrset_all_next; 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 20 May 2020 09:54:09 -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.5 diff -u -p -r1.5 iterator.c --- sbin/unwind/libunbound/iterator/iterator.c 14 Jul 2019 06:32:35 -0000 1.5 +++ sbin/unwind/libunbound/iterator/iterator.c 20 May 2020 09:54:09 -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 */ @@ -617,7 +625,7 @@ static void target_count_create(struct iter_qstate* iq) { if(!iq->target_count) { - iq->target_count = (int*)calloc(2, sizeof(int)); + iq->target_count = (int*)calloc(3, sizeof(int)); /* if calloc fails we simply do not track this number */ if(iq->target_count) iq->target_count[0] = 1; @@ -630,6 +638,15 @@ target_count_increase(struct iter_qstate target_count_create(iq); if(iq->target_count) iq->target_count[1] += num; + iq->dp_target_count++; +} + +static void +target_count_increase_nx(struct iter_qstate* iq, int num) +{ + target_count_create(iq); + if(iq->target_count) + iq->target_count[2] += num; } /** @@ -652,13 +669,15 @@ target_count_increase(struct iter_qstate * @param subq_ret: if newly allocated, the subquerystate, or NULL if it does * not need initialisation. * @param v: if true, validation is done on the subquery. + * @param detached: true if this qstate should not attach to the subquery * @return false on error (malloc). */ static int generate_sub_request(uint8_t* qname, size_t qnamelen, uint16_t qtype, uint16_t qclass, struct module_qstate* qstate, int id, struct iter_qstate* iq, enum iter_state initial_state, - enum iter_state finalstate, struct module_qstate** subq_ret, int v) + enum iter_state finalstate, struct module_qstate** subq_ret, int v, + int detached) { struct module_qstate* subq = NULL; struct iter_qstate* subiq = NULL; @@ -685,11 +704,23 @@ generate_sub_request(uint8_t* qname, siz valrec = 1; } - /* attach subquery, lookup existing or make a new one */ - fptr_ok(fptr_whitelist_modenv_attach_sub(qstate->env->attach_sub)); - if(!(*qstate->env->attach_sub)(qstate, &qinf, qflags, prime, valrec, - &subq)) { - return 0; + if(detached) { + struct mesh_state* sub = NULL; + fptr_ok(fptr_whitelist_modenv_add_sub( + qstate->env->add_sub)); + if(!(*qstate->env->add_sub)(qstate, &qinf, + qflags, prime, valrec, &subq, &sub)){ + return 0; + } + } + else { + /* attach subquery, lookup existing or make a new one */ + fptr_ok(fptr_whitelist_modenv_attach_sub( + qstate->env->attach_sub)); + if(!(*qstate->env->attach_sub)(qstate, &qinf, qflags, prime, + valrec, &subq)) { + return 0; + } } *subq_ret = subq; if(subq) { @@ -712,6 +743,7 @@ generate_sub_request(uint8_t* qname, siz subiq->target_count = iq->target_count; if(iq->target_count) iq->target_count[0] ++; /* extra reference */ + subiq->dp_target_count = 0; subiq->num_current_queries = 0; subiq->depth = iq->depth+1; outbound_list_init(&subiq->outlist); @@ -755,7 +787,7 @@ prime_root(struct module_qstate* qstate, * the normal INIT state logic (which would cause an infloop). */ if(!generate_sub_request((uint8_t*)"\000", 1, LDNS_RR_TYPE_NS, qclass, qstate, id, iq, QUERYTARGETS_STATE, PRIME_RESP_STATE, - &subq, 0)) { + &subq, 0, 0)) { verbose(VERB_ALGO, "could not prime root"); return 0; } @@ -846,7 +878,7 @@ prime_stub(struct module_qstate* qstate, * redundant INIT state processing. */ if(!generate_sub_request(stub_dp->name, stub_dp->namelen, LDNS_RR_TYPE_NS, qclass, qstate, id, iq, - QUERYTARGETS_STATE, PRIME_RESP_STATE, &subq, 0)) { + QUERYTARGETS_STATE, PRIME_RESP_STATE, &subq, 0, 0)) { verbose(VERB_ALGO, "could not prime stub"); errinf(qstate, "could not generate lookup for stub prime"); (void)error_response(qstate, id, LDNS_RCODE_SERVFAIL); @@ -1021,7 +1053,7 @@ generate_a_aaaa_check(struct module_qsta if(!generate_sub_request(s->rk.dname, s->rk.dname_len, ntohs(s->rk.type), ntohs(s->rk.rrset_class), qstate, id, iq, - INIT_REQUEST_STATE, FINISHED_STATE, &subq, 1)) { + INIT_REQUEST_STATE, FINISHED_STATE, &subq, 1, 0)) { verbose(VERB_ALGO, "could not generate addr check"); return; } @@ -1065,7 +1097,7 @@ generate_ns_check(struct module_qstate* iq->dp->name, LDNS_RR_TYPE_NS, iq->qchase.qclass); if(!generate_sub_request(iq->dp->name, iq->dp->namelen, LDNS_RR_TYPE_NS, iq->qchase.qclass, qstate, id, iq, - INIT_REQUEST_STATE, FINISHED_STATE, &subq, 1)) { + INIT_REQUEST_STATE, FINISHED_STATE, &subq, 1, 0)) { verbose(VERB_ALGO, "could not generate ns check"); return; } @@ -1122,7 +1154,7 @@ generate_dnskey_prefetch(struct module_q iq->dp->name, LDNS_RR_TYPE_DNSKEY, iq->qchase.qclass); if(!generate_sub_request(iq->dp->name, iq->dp->namelen, LDNS_RR_TYPE_DNSKEY, iq->qchase.qclass, qstate, id, iq, - INIT_REQUEST_STATE, FINISHED_STATE, &subq, 0)) { + INIT_REQUEST_STATE, FINISHED_STATE, &subq, 0, 0)) { /* we'll be slower, but it'll work */ verbose(VERB_ALGO, "could not generate dnskey prefetch"); return; @@ -1311,6 +1343,7 @@ processInitRequest(struct module_qstate* iq->refetch_glue = 0; iq->query_restart_count++; iq->sent_count = 0; + iq->dp_target_count = 0; sock_list_insert(&qstate->reply_origin, NULL, 0, qstate->region); if(qstate->env->cfg->qname_minimisation) iq->minimisation_state = INIT_MINIMISE_STATE; @@ -1689,7 +1722,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 = @@ -1740,7 +1773,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; @@ -1779,6 +1812,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); @@ -1892,7 +1933,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; @@ -1909,6 +1950,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); @@ -2074,7 +2116,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); } @@ -2132,6 +2174,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 */ @@ -2236,12 +2285,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) { @@ -2317,6 +2395,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]; } @@ -2362,6 +2442,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; } @@ -2449,6 +2530,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; } @@ -2743,7 +2825,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:", @@ -2755,16 +2838,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); @@ -2848,6 +2925,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, @@ -2924,6 +3002,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. */ @@ -3116,7 +3195,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); @@ -3144,6 +3223,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; @@ -3181,7 +3261,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"); } @@ -3192,6 +3272,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, @@ -3203,13 +3284,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); } } @@ -3383,7 +3474,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 20 May 2020 09:54:09 -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.2 diff -u -p -r1.2 dns.c --- sbin/unwind/libunbound/services/cache/dns.c 14 Jul 2019 06:32:35 -0000 1.2 +++ sbin/unwind/libunbound/services/cache/dns.c 20 May 2020 09:54:09 -0000 @@ -272,7 +272,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; } @@ -292,7 +292,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; } @@ -326,7 +326,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; } @@ -346,7 +347,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.1 diff -u -p -r1.1 dname.c --- sbin/unwind/libunbound/util/data/dname.c 23 Jan 2019 13:05:27 -0000 1.1 +++ sbin/unwind/libunbound/util/data/dname.c 20 May 2020 09:54:09 -0000 @@ -231,17 +231,28 @@ int dname_pkt_compare(sldns_buffer* pkt, uint8_t* d1, uint8_t* d2) { uint8_t len1, len2; + int count1 = 0, count2 = 0; log_assert(pkt && d1 && d2); len1 = *d1++; len2 = *d2++; while( len1 != 0 || len2 != 0 ) { /* resolve ptrs */ if(LABEL_IS_PTR(len1)) { + if((size_t)PTR_OFFSET(len1, *d1) + >= sldns_buffer_limit(pkt)) + return -1; + if(count1++ > MAX_COMPRESS_PTRS) + return -1; d1 = sldns_buffer_at(pkt, PTR_OFFSET(len1, *d1)); len1 = *d1++; continue; } if(LABEL_IS_PTR(len2)) { + if((size_t)PTR_OFFSET(len2, *d2) + >= sldns_buffer_limit(pkt)) + return 1; + if(count2++ > MAX_COMPRESS_PTRS) + return 1; d2 = sldns_buffer_at(pkt, PTR_OFFSET(len2, *d2)); len2 = *d2++; continue; @@ -300,12 +311,18 @@ dname_pkt_hash(sldns_buffer* pkt, uint8_ uint8_t labuf[LDNS_MAX_LABELLEN+1]; uint8_t lablen; int i; + int count = 0; /* preserve case of query, make hash label by label */ lablen = *dname++; while(lablen) { if(LABEL_IS_PTR(lablen)) { /* follow pointer */ + if((size_t)PTR_OFFSET(lablen, *dname) + >= sldns_buffer_limit(pkt)) + return h; + if(count++ > MAX_COMPRESS_PTRS) + return h; dname = sldns_buffer_at(pkt, PTR_OFFSET(lablen, *dname)); lablen = *dname++; continue; @@ -333,6 +350,9 @@ void dname_pkt_copy(sldns_buffer* pkt, u while(lablen) { if(LABEL_IS_PTR(lablen)) { /* follow pointer */ + if((size_t)PTR_OFFSET(lablen, *dname) + >= sldns_buffer_limit(pkt)) + return; dname = sldns_buffer_at(pkt, PTR_OFFSET(lablen, *dname)); lablen = *dname++; continue; @@ -357,6 +377,7 @@ void dname_pkt_copy(sldns_buffer* pkt, u void dname_print(FILE* out, struct sldns_buffer* pkt, uint8_t* dname) { uint8_t lablen; + int count = 0; if(!out) out = stdout; if(!dname) return; @@ -367,6 +388,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 20 May 2020 09:54:09 -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 20 May 2020 09:54:09 -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 20 May 2020 09:54:09 -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.9 diff -u -p -r1.9 iter_scrub.c --- usr.sbin/unbound/iterator/iter_scrub.c 4 Dec 2018 12:05:07 -0000 1.9 +++ usr.sbin/unbound/iterator/iter_scrub.c 20 May 2020 09:54:09 -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; log_assert(qnamelen > dname_rrset->dname_len); /* DNAME from com. to net. with qname example.com. -> example.net. */ @@ -384,7 +398,7 @@ scrub_normalize(sldns_buffer* pkt, struc /* check next cname */ uint8_t* t = NULL; size_t tlen = 0; - if(!parse_get_cname_target(nx, &t, &tlen)) + if(!parse_get_cname_target(nx, &t, &tlen, pkt)) return 0; if(dname_pkt_compare(pkt, alias, t) == 0) { /* it's OK and better capitalized */ @@ -435,7 +449,7 @@ scrub_normalize(sldns_buffer* pkt, struc size_t tlen = 0; if(synth_cname(sname, snamelen, nx, alias, &aliaslen, pkt) && - parse_get_cname_target(rrset, &t, &tlen) && + parse_get_cname_target(rrset, &t, &tlen, pkt) && dname_pkt_compare(pkt, alias, t) == 0) { /* the synthesized CNAME equals the * current CNAME. This CNAME is the @@ -456,7 +470,7 @@ scrub_normalize(sldns_buffer* pkt, struc } /* move to next name in CNAME chain */ - if(!parse_get_cname_target(rrset, &sname, &snamelen)) + if(!parse_get_cname_target(rrset, &sname, &snamelen, pkt)) return 0; prev = rrset; rrset = rrset->rrset_all_next; 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 20 May 2020 09:54:09 -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.23 diff -u -p -r1.23 iterator.c --- usr.sbin/unbound/iterator/iterator.c 28 Jun 2019 15:17:16 -0000 1.23 +++ usr.sbin/unbound/iterator/iterator.c 20 May 2020 09:54:09 -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 */ @@ -617,7 +625,7 @@ static void target_count_create(struct iter_qstate* iq) { if(!iq->target_count) { - iq->target_count = (int*)calloc(2, sizeof(int)); + iq->target_count = (int*)calloc(3, sizeof(int)); /* if calloc fails we simply do not track this number */ if(iq->target_count) iq->target_count[0] = 1; @@ -630,6 +638,15 @@ target_count_increase(struct iter_qstate target_count_create(iq); if(iq->target_count) iq->target_count[1] += num; + iq->dp_target_count++; +} + +static void +target_count_increase_nx(struct iter_qstate* iq, int num) +{ + target_count_create(iq); + if(iq->target_count) + iq->target_count[2] += num; } /** @@ -652,13 +669,15 @@ target_count_increase(struct iter_qstate * @param subq_ret: if newly allocated, the subquerystate, or NULL if it does * not need initialisation. * @param v: if true, validation is done on the subquery. + * @param detached: true if this qstate should not attach to the subquery * @return false on error (malloc). */ static int generate_sub_request(uint8_t* qname, size_t qnamelen, uint16_t qtype, uint16_t qclass, struct module_qstate* qstate, int id, struct iter_qstate* iq, enum iter_state initial_state, - enum iter_state finalstate, struct module_qstate** subq_ret, int v) + enum iter_state finalstate, struct module_qstate** subq_ret, int v, + int detached) { struct module_qstate* subq = NULL; struct iter_qstate* subiq = NULL; @@ -685,11 +704,23 @@ generate_sub_request(uint8_t* qname, siz valrec = 1; } - /* attach subquery, lookup existing or make a new one */ - fptr_ok(fptr_whitelist_modenv_attach_sub(qstate->env->attach_sub)); - if(!(*qstate->env->attach_sub)(qstate, &qinf, qflags, prime, valrec, - &subq)) { - return 0; + if(detached) { + struct mesh_state* sub = NULL; + fptr_ok(fptr_whitelist_modenv_add_sub( + qstate->env->add_sub)); + if(!(*qstate->env->add_sub)(qstate, &qinf, + qflags, prime, valrec, &subq, &sub)){ + return 0; + } + } + else { + /* attach subquery, lookup existing or make a new one */ + fptr_ok(fptr_whitelist_modenv_attach_sub( + qstate->env->attach_sub)); + if(!(*qstate->env->attach_sub)(qstate, &qinf, qflags, prime, + valrec, &subq)) { + return 0; + } } *subq_ret = subq; if(subq) { @@ -712,6 +743,7 @@ generate_sub_request(uint8_t* qname, siz subiq->target_count = iq->target_count; if(iq->target_count) iq->target_count[0] ++; /* extra reference */ + subiq->dp_target_count = 0; subiq->num_current_queries = 0; subiq->depth = iq->depth+1; outbound_list_init(&subiq->outlist); @@ -755,7 +787,7 @@ prime_root(struct module_qstate* qstate, * the normal INIT state logic (which would cause an infloop). */ if(!generate_sub_request((uint8_t*)"\000", 1, LDNS_RR_TYPE_NS, qclass, qstate, id, iq, QUERYTARGETS_STATE, PRIME_RESP_STATE, - &subq, 0)) { + &subq, 0, 0)) { verbose(VERB_ALGO, "could not prime root"); return 0; } @@ -846,7 +878,7 @@ prime_stub(struct module_qstate* qstate, * redundant INIT state processing. */ if(!generate_sub_request(stub_dp->name, stub_dp->namelen, LDNS_RR_TYPE_NS, qclass, qstate, id, iq, - QUERYTARGETS_STATE, PRIME_RESP_STATE, &subq, 0)) { + QUERYTARGETS_STATE, PRIME_RESP_STATE, &subq, 0, 0)) { verbose(VERB_ALGO, "could not prime stub"); errinf(qstate, "could not generate lookup for stub prime"); (void)error_response(qstate, id, LDNS_RCODE_SERVFAIL); @@ -1021,7 +1053,7 @@ generate_a_aaaa_check(struct module_qsta if(!generate_sub_request(s->rk.dname, s->rk.dname_len, ntohs(s->rk.type), ntohs(s->rk.rrset_class), qstate, id, iq, - INIT_REQUEST_STATE, FINISHED_STATE, &subq, 1)) { + INIT_REQUEST_STATE, FINISHED_STATE, &subq, 1, 0)) { verbose(VERB_ALGO, "could not generate addr check"); return; } @@ -1065,7 +1097,7 @@ generate_ns_check(struct module_qstate* iq->dp->name, LDNS_RR_TYPE_NS, iq->qchase.qclass); if(!generate_sub_request(iq->dp->name, iq->dp->namelen, LDNS_RR_TYPE_NS, iq->qchase.qclass, qstate, id, iq, - INIT_REQUEST_STATE, FINISHED_STATE, &subq, 1)) { + INIT_REQUEST_STATE, FINISHED_STATE, &subq, 1, 0)) { verbose(VERB_ALGO, "could not generate ns check"); return; } @@ -1122,7 +1154,7 @@ generate_dnskey_prefetch(struct module_q iq->dp->name, LDNS_RR_TYPE_DNSKEY, iq->qchase.qclass); if(!generate_sub_request(iq->dp->name, iq->dp->namelen, LDNS_RR_TYPE_DNSKEY, iq->qchase.qclass, qstate, id, iq, - INIT_REQUEST_STATE, FINISHED_STATE, &subq, 0)) { + INIT_REQUEST_STATE, FINISHED_STATE, &subq, 0, 0)) { /* we'll be slower, but it'll work */ verbose(VERB_ALGO, "could not generate dnskey prefetch"); return; @@ -1311,6 +1343,7 @@ processInitRequest(struct module_qstate* iq->refetch_glue = 0; iq->query_restart_count++; iq->sent_count = 0; + iq->dp_target_count = 0; sock_list_insert(&qstate->reply_origin, NULL, 0, qstate->region); if(qstate->env->cfg->qname_minimisation) iq->minimisation_state = INIT_MINIMISE_STATE; @@ -1689,7 +1722,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 = @@ -1740,7 +1773,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; @@ -1779,6 +1812,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); @@ -1892,7 +1933,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; @@ -1909,6 +1950,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); @@ -2074,7 +2116,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); } @@ -2132,6 +2174,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 */ @@ -2236,12 +2285,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) { @@ -2317,6 +2395,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]; } @@ -2362,6 +2442,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; } @@ -2449,6 +2530,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; } @@ -2743,7 +2825,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:", @@ -2755,16 +2838,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); @@ -2848,6 +2925,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, @@ -2924,6 +3002,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. */ @@ -3116,7 +3195,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); @@ -3144,6 +3223,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; @@ -3181,7 +3261,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"); } @@ -3192,6 +3272,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, @@ -3203,13 +3284,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); } } @@ -3383,7 +3474,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 20 May 2020 09:54:09 -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.13 diff -u -p -r1.13 dns.c --- usr.sbin/unbound/services/cache/dns.c 28 Jun 2019 15:17:16 -0000 1.13 +++ usr.sbin/unbound/services/cache/dns.c 20 May 2020 09:54:09 -0000 @@ -272,7 +272,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; } @@ -292,7 +292,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; } @@ -326,7 +326,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; } @@ -346,7 +347,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.4 diff -u -p -r1.4 dname.c --- usr.sbin/unbound/util/data/dname.c 17 Sep 2018 09:46:12 -0000 1.4 +++ usr.sbin/unbound/util/data/dname.c 20 May 2020 09:54:09 -0000 @@ -231,17 +231,28 @@ int dname_pkt_compare(sldns_buffer* pkt, uint8_t* d1, uint8_t* d2) { uint8_t len1, len2; + int count1 = 0, count2 = 0; log_assert(pkt && d1 && d2); len1 = *d1++; len2 = *d2++; while( len1 != 0 || len2 != 0 ) { /* resolve ptrs */ if(LABEL_IS_PTR(len1)) { + if((size_t)PTR_OFFSET(len1, *d1) + >= sldns_buffer_limit(pkt)) + return -1; + if(count1++ > MAX_COMPRESS_PTRS) + return -1; d1 = sldns_buffer_at(pkt, PTR_OFFSET(len1, *d1)); len1 = *d1++; continue; } if(LABEL_IS_PTR(len2)) { + if((size_t)PTR_OFFSET(len2, *d2) + >= sldns_buffer_limit(pkt)) + return 1; + if(count2++ > MAX_COMPRESS_PTRS) + return 1; d2 = sldns_buffer_at(pkt, PTR_OFFSET(len2, *d2)); len2 = *d2++; continue; @@ -300,12 +311,18 @@ dname_pkt_hash(sldns_buffer* pkt, uint8_ uint8_t labuf[LDNS_MAX_LABELLEN+1]; uint8_t lablen; int i; + int count = 0; /* preserve case of query, make hash label by label */ lablen = *dname++; while(lablen) { if(LABEL_IS_PTR(lablen)) { /* follow pointer */ + if((size_t)PTR_OFFSET(lablen, *dname) + >= sldns_buffer_limit(pkt)) + return h; + if(count++ > MAX_COMPRESS_PTRS) + return h; dname = sldns_buffer_at(pkt, PTR_OFFSET(lablen, *dname)); lablen = *dname++; continue; @@ -333,6 +350,9 @@ void dname_pkt_copy(sldns_buffer* pkt, u while(lablen) { if(LABEL_IS_PTR(lablen)) { /* follow pointer */ + if((size_t)PTR_OFFSET(lablen, *dname) + >= sldns_buffer_limit(pkt)) + return; dname = sldns_buffer_at(pkt, PTR_OFFSET(lablen, *dname)); lablen = *dname++; continue; @@ -357,6 +377,7 @@ void dname_pkt_copy(sldns_buffer* pkt, u void dname_print(FILE* out, struct sldns_buffer* pkt, uint8_t* dname) { uint8_t lablen; + int count = 0; if(!out) out = stdout; if(!dname) return; @@ -367,6 +388,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 20 May 2020 09:54:09 -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. */