diff --git a/backport-CVE-2023-50387_CVE-2023-50868.patch b/backport-CVE-2023-50387_CVE-2023-50868.patch new file mode 100644 index 0000000000000000000000000000000000000000..8e0ff38d0cc0a2b9fdfea5d83bdc2767f9b4c9fc --- /dev/null +++ b/backport-CVE-2023-50387_CVE-2023-50868.patch @@ -0,0 +1,2329 @@ +From 882903f2fa800c4cb6f5e225b728e2887bb7b9ae Mon Sep 17 00:00:00 2001 +From: "W.C.A. Wijngaards" +Date: Tue, 13 Feb 2024 13:02:08 +0100 +Subject: [PATCH] - Fix CVE-2023-50387, CVE-2023-50868 + +CVE-2023-50387: +DNSSEC verification complexity can be exploited to exhaust CPU resources and stall DNS resolvers. +CVE-2023-50868: +NSEC3 closest encloser proof can exhaust CPU. + +--- + services/authzone.c | 3 +- + services/cache/dns.c | 18 + + services/cache/dns.h | 9 + + testcode/unitverify.c | 7 +- + testdata/val_any.rpl | 3 + + testdata/val_any_dname.rpl | 3 + + testdata/val_nx_nsec3_collision.rpl | 3 + + util/fptr_wlist.c | 1 + + validator/val_nsec.c | 3 +- + validator/val_nsec3.c | 316 ++++++++++++++---- + validator/val_nsec3.h | 60 +++- + validator/val_sigcrypt.c | 37 +- + validator/val_sigcrypt.h | 3 +- + validator/val_utils.c | 22 +- + validator/val_utils.h | 4 +- + validator/validator.c | 500 ++++++++++++++++++++++++---- + validator/validator.h | 18 + + 17 files changed, 839 insertions(+), 171 deletions(-) + +diff --git a/services/authzone.c b/services/authzone.c +index 3898767c..4c63b2e0 100644 +--- a/services/authzone.c ++++ b/services/authzone.c +@@ -7767,6 +7767,7 @@ static int zonemd_dnssec_verify_rrset(struct auth_zone* z, + enum sec_status sec; + struct val_env* ve; + int m; ++ int verified = 0; + m = modstack_find(mods, "validator"); + if(m == -1) { + auth_zone_log(z->name, VERB_ALGO, "zonemd dnssec verify: have " +@@ -7790,7 +7791,7 @@ static int zonemd_dnssec_verify_rrset(struct auth_zone* z, + "zonemd: verify %s RRset with DNSKEY", typestr); + } + sec = dnskeyset_verify_rrset(env, ve, &pk, dnskey, sigalg, why_bogus, NULL, +- LDNS_SECTION_ANSWER, NULL); ++ LDNS_SECTION_ANSWER, NULL, &verified); + if(sec == sec_status_secure) { + return 1; + } +diff --git a/services/cache/dns.c b/services/cache/dns.c +index 6fc9919e..1ced1143 100644 +--- a/services/cache/dns.c ++++ b/services/cache/dns.c +@@ -703,6 +703,24 @@ tomsg(struct module_env* env, struct query_info* q, struct reply_info* r, + return msg; + } + ++struct dns_msg* ++dns_msg_deepcopy_region(struct dns_msg* origin, struct regional* region) ++{ ++ size_t i; ++ struct dns_msg* res = NULL; ++ res = gen_dns_msg(region, &origin->qinfo, origin->rep->rrset_count); ++ if(!res) return NULL; ++ *res->rep = *origin->rep; ++ for(i=0; irep->rrset_count; i++) { ++ res->rep->rrsets[i] = packed_rrset_copy_region( ++ origin->rep->rrsets[i], region, 0); ++ if(!res->rep->rrsets[i]) { ++ return NULL; ++ } ++ } ++ return res; ++} ++ + /** synthesize RRset-only response from cached RRset item */ + static struct dns_msg* + rrset_msg(struct ub_packed_rrset_key* rrset, struct regional* region, +diff --git a/services/cache/dns.h b/services/cache/dns.h +index 147f992c..c2bf23c6 100644 +--- a/services/cache/dns.h ++++ b/services/cache/dns.h +@@ -164,6 +164,15 @@ struct dns_msg* tomsg(struct module_env* env, struct query_info* q, + struct reply_info* r, struct regional* region, time_t now, + int allow_expired, struct regional* scratch); + ++/** ++ * Deep copy a dns_msg to a region. ++ * @param origin: the dns_msg to copy. ++ * @param region: the region to copy all the data to. ++ * @return the new dns_msg or NULL on malloc error. ++ */ ++struct dns_msg* dns_msg_deepcopy_region(struct dns_msg* origin, ++ struct regional* region); ++ + /** + * Find cached message + * @param env: module environment with the DNS cache. +diff --git a/testcode/unitverify.c b/testcode/unitverify.c +index ff069a1b..395b4c25 100644 +--- a/testcode/unitverify.c ++++ b/testcode/unitverify.c +@@ -180,6 +180,7 @@ verifytest_rrset(struct module_env* env, struct val_env* ve, + enum sec_status sec; + char* reason = NULL; + uint8_t sigalg[ALGO_NEEDS_MAX+1]; ++ int verified = 0; + if(vsig) { + log_nametypeclass(VERB_QUERY, "verify of rrset", + rrset->rk.dname, ntohs(rrset->rk.type), +@@ -188,7 +189,7 @@ verifytest_rrset(struct module_env* env, struct val_env* ve, + setup_sigalg(dnskey, sigalg); /* check all algorithms in the dnskey */ + /* ok to give null as qstate here, won't be used for answer section. */ + sec = dnskeyset_verify_rrset(env, ve, rrset, dnskey, sigalg, &reason, NULL, +- LDNS_SECTION_ANSWER, NULL); ++ LDNS_SECTION_ANSWER, NULL, &verified); + if(vsig) { + printf("verify outcome is: %s %s\n", sec_status_to_string(sec), + reason?reason:""); +@@ -442,9 +443,9 @@ nsec3_hash_test_entry(struct entry* e, rbtree_type* ct, + + ret = nsec3_hash_name(ct, region, buf, nsec3, 0, qname, + qinfo.qname_len, &hash); +- if(ret != 1) { ++ if(ret < 1) { + printf("Bad nsec3_hash_name retcode %d\n", ret); +- unit_assert(ret == 1); ++ unit_assert(ret == 1 || ret == 2); + } + unit_assert(hash->dname && hash->hash && hash->hash_len && + hash->b32 && hash->b32_len); +diff --git a/testdata/val_any.rpl b/testdata/val_any.rpl +index 4ce19513..90263af8 100644 +--- a/testdata/val_any.rpl ++++ b/testdata/val_any.rpl +@@ -161,6 +161,9 @@ SECTION QUESTION + example.com. IN ANY + ENTRY_END + ++; Allow validation resuming for the RRSIGs ++STEP 2 TIME_PASSES ELAPSE 0.05 ++ + ; recursion happens here. + STEP 10 CHECK_ANSWER + ENTRY_BEGIN +diff --git a/testdata/val_any_dname.rpl b/testdata/val_any_dname.rpl +index 6ab3cded..dd65e97b 100644 +--- a/testdata/val_any_dname.rpl ++++ b/testdata/val_any_dname.rpl +@@ -163,6 +163,9 @@ SECTION QUESTION + example.com. IN ANY + ENTRY_END + ++; Allow validation resuming for the RRSIGs ++STEP 2 TIME_PASSES ELAPSE 0.05 ++ + ; recursion happens here. + STEP 10 CHECK_ANSWER + ENTRY_BEGIN +diff --git a/testdata/val_nx_nsec3_collision.rpl b/testdata/val_nx_nsec3_collision.rpl +index 8ff7e4b0..87a55f56 100644 +--- a/testdata/val_nx_nsec3_collision.rpl ++++ b/testdata/val_nx_nsec3_collision.rpl +@@ -156,6 +156,9 @@ SECTION QUESTION + www.example.com. IN A + ENTRY_END + ++; Allow validation resuming for NSEC3 hash calculations ++STEP 2 TIME_PASSES ELAPSE 0.05 ++ + ; recursion happens here. + STEP 10 CHECK_ANSWER + ENTRY_BEGIN +diff --git a/util/fptr_wlist.c b/util/fptr_wlist.c +index dc8ab669..00b73253 100644 +--- a/util/fptr_wlist.c ++++ b/util/fptr_wlist.c +@@ -131,6 +131,7 @@ fptr_whitelist_comm_timer(void (*fptr)(void*)) + else if(fptr == &pending_udp_timer_delay_cb) return 1; + else if(fptr == &worker_stat_timer_cb) return 1; + else if(fptr == &worker_probe_timer_cb) return 1; ++ else if(fptr == &validate_suspend_timer_cb) return 1; + #ifdef UB_ON_WINDOWS + else if(fptr == &wsvc_cron_cb) return 1; + #endif +diff --git a/validator/val_nsec.c b/validator/val_nsec.c +index 876bfab6..5871db90 100644 +--- a/validator/val_nsec.c ++++ b/validator/val_nsec.c +@@ -180,6 +180,7 @@ nsec_verify_rrset(struct module_env* env, struct val_env* ve, + { + struct packed_rrset_data* d = (struct packed_rrset_data*) + nsec->entry.data; ++ int verified = 0; + if(!d) return 0; + if(d->security == sec_status_secure) + return 1; +@@ -187,7 +188,7 @@ nsec_verify_rrset(struct module_env* env, struct val_env* ve, + if(d->security == sec_status_secure) + return 1; + d->security = val_verify_rrset_entry(env, ve, nsec, kkey, reason, +- NULL, LDNS_SECTION_AUTHORITY, qstate); ++ NULL, LDNS_SECTION_AUTHORITY, qstate, &verified); + if(d->security == sec_status_secure) { + rrset_update_sec_status(env->rrset_cache, nsec, *env->now); + return 1; +diff --git a/validator/val_nsec3.c b/validator/val_nsec3.c +index a2b3794f..95d1e4d7 100644 +--- a/validator/val_nsec3.c ++++ b/validator/val_nsec3.c +@@ -57,6 +57,19 @@ + /* we include nsec.h for the bitmap_has_type function */ + #include "validator/val_nsec.h" + #include "sldns/sbuffer.h" ++#include "util/config_file.h" ++ ++/** ++ * Max number of NSEC3 calculations at once, suspend query for later. ++ * 8 is low enough and allows for cases where multiple proofs are needed. ++ */ ++#define MAX_NSEC3_CALCULATIONS 8 ++/** ++ * When all allowed NSEC3 calculations at once resulted in error treat as ++ * bogus. NSEC3 hash errors are not cached and this helps breaks loops with ++ * erroneous data. ++ */ ++#define MAX_NSEC3_ERRORS -1 + + /** + * This function we get from ldns-compat or from base system +@@ -532,6 +545,17 @@ nsec3_hash_cmp(const void* c1, const void* c2) + return memcmp(s1, s2, s1len); + } + ++int ++nsec3_cache_table_init(struct nsec3_cache_table* ct, struct regional* region) ++{ ++ if(ct->ct) return 1; ++ ct->ct = (rbtree_type*)regional_alloc(region, sizeof(*ct->ct)); ++ if(!ct->ct) return 0; ++ ct->region = region; ++ rbtree_init(ct->ct, &nsec3_hash_cmp); ++ return 1; ++} ++ + size_t + nsec3_get_hashed(sldns_buffer* buf, uint8_t* nm, size_t nmlen, int algo, + size_t iter, uint8_t* salt, size_t saltlen, uint8_t* res, size_t max) +@@ -646,7 +670,7 @@ nsec3_hash_name(rbtree_type* table, struct regional* region, sldns_buffer* buf, + c = (struct nsec3_cached_hash*)rbtree_search(table, &looki); + if(c) { + *hash = c; +- return 1; ++ return 2; + } + /* create a new entry */ + c = (struct nsec3_cached_hash*)regional_alloc(region, sizeof(*c)); +@@ -658,10 +682,10 @@ nsec3_hash_name(rbtree_type* table, struct regional* region, sldns_buffer* buf, + c->dname_len = dname_len; + r = nsec3_calc_hash(region, buf, c); + if(r != 1) +- return r; ++ return r; /* returns -1 or 0 */ + r = nsec3_calc_b32(region, buf, c); + if(r != 1) +- return r; ++ return r; /* returns 0 */ + #ifdef UNBOUND_DEBUG + n = + #else +@@ -704,6 +728,7 @@ nsec3_hash_matches_owner(struct nsec3_filter* flt, + struct nsec3_cached_hash* hash, struct ub_packed_rrset_key* s) + { + uint8_t* nm = s->rk.dname; ++ if(!hash) return 0; /* please clang */ + /* compare, does hash of name based on params in this NSEC3 + * match the owner name of this NSEC3? + * name must be: base32 . zone name +@@ -730,34 +755,50 @@ nsec3_hash_matches_owner(struct nsec3_filter* flt, + * @param nmlen: length of name. + * @param rrset: nsec3 that matches is returned here. + * @param rr: rr number in nsec3 rrset that matches. ++ * @param calculations: current hash calculations. + * @return true if a matching NSEC3 is found, false if not. + */ + static int + find_matching_nsec3(struct module_env* env, struct nsec3_filter* flt, +- rbtree_type* ct, uint8_t* nm, size_t nmlen, +- struct ub_packed_rrset_key** rrset, int* rr) ++ struct nsec3_cache_table* ct, uint8_t* nm, size_t nmlen, ++ struct ub_packed_rrset_key** rrset, int* rr, ++ int* calculations) + { + size_t i_rs; + int i_rr; + struct ub_packed_rrset_key* s; + struct nsec3_cached_hash* hash = NULL; + int r; ++ int calc_errors = 0; + + /* this loop skips other-zone and unknown NSEC3s, also non-NSEC3 RRs */ + for(s=filter_first(flt, &i_rs, &i_rr); s; + s=filter_next(flt, &i_rs, &i_rr)) { ++ /* check if we are allowed more calculations */ ++ if(*calculations >= MAX_NSEC3_CALCULATIONS) { ++ if(calc_errors == *calculations) { ++ *calculations = MAX_NSEC3_ERRORS; ++ } ++ break; ++ } + /* get name hashed for this NSEC3 RR */ +- r = nsec3_hash_name(ct, env->scratch, env->scratch_buffer, ++ r = nsec3_hash_name(ct->ct, ct->region, env->scratch_buffer, + s, i_rr, nm, nmlen, &hash); + if(r == 0) { + log_err("nsec3: malloc failure"); + break; /* alloc failure */ +- } else if(r != 1) +- continue; /* malformed NSEC3 */ +- else if(nsec3_hash_matches_owner(flt, hash, s)) { +- *rrset = s; /* rrset with this name */ +- *rr = i_rr; /* matches hash with these parameters */ +- return 1; ++ } else if(r < 0) { ++ /* malformed NSEC3 */ ++ calc_errors++; ++ (*calculations)++; ++ continue; ++ } else { ++ if(r == 1) (*calculations)++; ++ if(nsec3_hash_matches_owner(flt, hash, s)) { ++ *rrset = s; /* rrset with this name */ ++ *rr = i_rr; /* matches hash with these parameters */ ++ return 1; ++ } + } + } + *rrset = NULL; +@@ -775,6 +816,7 @@ nsec3_covers(uint8_t* zone, struct nsec3_cached_hash* hash, + if(!nsec3_get_nextowner(rrset, rr, &next, &nextlen)) + return 0; /* malformed RR proves nothing */ + ++ if(!hash) return 0; /* please clang */ + /* check the owner name is a hashed value . apex + * base32 encoded values must have equal length. + * hash_value and next hash value must have equal length. */ +@@ -823,35 +865,51 @@ nsec3_covers(uint8_t* zone, struct nsec3_cached_hash* hash, + * @param nmlen: length of name. + * @param rrset: covering NSEC3 rrset is returned here. + * @param rr: rr of cover is returned here. ++ * @param calculations: current hash calculations. + * @return true if a covering NSEC3 is found, false if not. + */ + static int + find_covering_nsec3(struct module_env* env, struct nsec3_filter* flt, +- rbtree_type* ct, uint8_t* nm, size_t nmlen, +- struct ub_packed_rrset_key** rrset, int* rr) ++ struct nsec3_cache_table* ct, uint8_t* nm, size_t nmlen, ++ struct ub_packed_rrset_key** rrset, int* rr, ++ int* calculations) + { + size_t i_rs; + int i_rr; + struct ub_packed_rrset_key* s; + struct nsec3_cached_hash* hash = NULL; + int r; ++ int calc_errors = 0; + + /* this loop skips other-zone and unknown NSEC3s, also non-NSEC3 RRs */ + for(s=filter_first(flt, &i_rs, &i_rr); s; + s=filter_next(flt, &i_rs, &i_rr)) { ++ /* check if we are allowed more calculations */ ++ if(*calculations >= MAX_NSEC3_CALCULATIONS) { ++ if(calc_errors == *calculations) { ++ *calculations = MAX_NSEC3_ERRORS; ++ } ++ break; ++ } + /* get name hashed for this NSEC3 RR */ +- r = nsec3_hash_name(ct, env->scratch, env->scratch_buffer, ++ r = nsec3_hash_name(ct->ct, ct->region, env->scratch_buffer, + s, i_rr, nm, nmlen, &hash); + if(r == 0) { + log_err("nsec3: malloc failure"); + break; /* alloc failure */ +- } else if(r != 1) +- continue; /* malformed NSEC3 */ +- else if(nsec3_covers(flt->zone, hash, s, i_rr, +- env->scratch_buffer)) { +- *rrset = s; /* rrset with this name */ +- *rr = i_rr; /* covers hash with these parameters */ +- return 1; ++ } else if(r < 0) { ++ /* malformed NSEC3 */ ++ calc_errors++; ++ (*calculations)++; ++ continue; ++ } else { ++ if(r == 1) (*calculations)++; ++ if(nsec3_covers(flt->zone, hash, s, i_rr, ++ env->scratch_buffer)) { ++ *rrset = s; /* rrset with this name */ ++ *rr = i_rr; /* covers hash with these parameters */ ++ return 1; ++ } + } + } + *rrset = NULL; +@@ -869,11 +927,13 @@ find_covering_nsec3(struct module_env* env, struct nsec3_filter* flt, + * @param ct: cached hashes table. + * @param qinfo: query that is verified for. + * @param ce: closest encloser information is returned in here. ++ * @param calculations: current hash calculations. + * @return true if a closest encloser candidate is found, false if not. + */ + static int +-nsec3_find_closest_encloser(struct module_env* env, struct nsec3_filter* flt, +- rbtree_type* ct, struct query_info* qinfo, struct ce_response* ce) ++nsec3_find_closest_encloser(struct module_env* env, struct nsec3_filter* flt, ++ struct nsec3_cache_table* ct, struct query_info* qinfo, ++ struct ce_response* ce, int* calculations) + { + uint8_t* nm = qinfo->qname; + size_t nmlen = qinfo->qname_len; +@@ -888,8 +948,12 @@ nsec3_find_closest_encloser(struct module_env* env, struct nsec3_filter* flt, + * may be the case. */ + + while(dname_subdomain_c(nm, flt->zone)) { ++ if(*calculations >= MAX_NSEC3_CALCULATIONS || ++ *calculations == MAX_NSEC3_ERRORS) { ++ return 0; ++ } + if(find_matching_nsec3(env, flt, ct, nm, nmlen, +- &ce->ce_rrset, &ce->ce_rr)) { ++ &ce->ce_rrset, &ce->ce_rr, calculations)) { + ce->ce = nm; + ce->ce_len = nmlen; + return 1; +@@ -933,22 +997,38 @@ next_closer(uint8_t* qname, size_t qnamelen, uint8_t* ce, + * If set true, and the return value is true, then you can be + * certain that the ce.nc_rrset and ce.nc_rr are set properly. + * @param ce: closest encloser information is returned in here. ++ * @param calculations: pointer to the current NSEC3 hash calculations. + * @return bogus if no closest encloser could be proven. + * secure if a closest encloser could be proven, ce is set. + * insecure if the closest-encloser candidate turns out to prove + * that an insecure delegation exists above the qname. ++ * unchecked if no more hash calculations are allowed at this point. + */ + static enum sec_status +-nsec3_prove_closest_encloser(struct module_env* env, struct nsec3_filter* flt, +- rbtree_type* ct, struct query_info* qinfo, int prove_does_not_exist, +- struct ce_response* ce) ++nsec3_prove_closest_encloser(struct module_env* env, struct nsec3_filter* flt, ++ struct nsec3_cache_table* ct, struct query_info* qinfo, ++ int prove_does_not_exist, struct ce_response* ce, int* calculations) + { + uint8_t* nc; + size_t nc_len; + /* robust: clean out ce, in case it gets abused later */ + memset(ce, 0, sizeof(*ce)); + +- if(!nsec3_find_closest_encloser(env, flt, ct, qinfo, ce)) { ++ if(!nsec3_find_closest_encloser(env, flt, ct, qinfo, ce, calculations)) { ++ if(*calculations == MAX_NSEC3_ERRORS) { ++ verbose(VERB_ALGO, "nsec3 proveClosestEncloser: could " ++ "not find a candidate for the closest " ++ "encloser; all attempted hash calculations " ++ "were erroneous; bogus"); ++ return sec_status_bogus; ++ } else if(*calculations >= MAX_NSEC3_CALCULATIONS) { ++ verbose(VERB_ALGO, "nsec3 proveClosestEncloser: could " ++ "not find a candidate for the closest " ++ "encloser; reached MAX_NSEC3_CALCULATIONS " ++ "(%d); unchecked still", ++ MAX_NSEC3_CALCULATIONS); ++ return sec_status_unchecked; ++ } + verbose(VERB_ALGO, "nsec3 proveClosestEncloser: could " + "not find a candidate for the closest encloser."); + return sec_status_bogus; +@@ -989,9 +1069,23 @@ nsec3_prove_closest_encloser(struct module_env* env, struct nsec3_filter* flt, + /* Otherwise, we need to show that the next closer name is covered. */ + next_closer(qinfo->qname, qinfo->qname_len, ce->ce, &nc, &nc_len); + if(!find_covering_nsec3(env, flt, ct, nc, nc_len, +- &ce->nc_rrset, &ce->nc_rr)) { ++ &ce->nc_rrset, &ce->nc_rr, calculations)) { ++ if(*calculations == MAX_NSEC3_ERRORS) { ++ verbose(VERB_ALGO, "nsec3: Could not find proof that the " ++ "candidate encloser was the closest encloser; " ++ "all attempted hash calculations were " ++ "erroneous; bogus"); ++ return sec_status_bogus; ++ } else if(*calculations >= MAX_NSEC3_CALCULATIONS) { ++ verbose(VERB_ALGO, "nsec3: Could not find proof that the " ++ "candidate encloser was the closest encloser; " ++ "reached MAX_NSEC3_CALCULATIONS (%d); " ++ "unchecked still", ++ MAX_NSEC3_CALCULATIONS); ++ return sec_status_unchecked; ++ } + verbose(VERB_ALGO, "nsec3: Could not find proof that the " +- "candidate encloser was the closest encloser"); ++ "candidate encloser was the closest encloser"); + return sec_status_bogus; + } + return sec_status_secure; +@@ -1019,8 +1113,8 @@ nsec3_ce_wildcard(struct regional* region, uint8_t* ce, size_t celen, + + /** Do the name error proof */ + static enum sec_status +-nsec3_do_prove_nameerror(struct module_env* env, struct nsec3_filter* flt, +- rbtree_type* ct, struct query_info* qinfo) ++nsec3_do_prove_nameerror(struct module_env* env, struct nsec3_filter* flt, ++ struct nsec3_cache_table* ct, struct query_info* qinfo, int* calc) + { + struct ce_response ce; + uint8_t* wc; +@@ -1032,11 +1126,15 @@ nsec3_do_prove_nameerror(struct module_env* env, struct nsec3_filter* flt, + /* First locate and prove the closest encloser to qname. We will + * use the variant that fails if the closest encloser turns out + * to be qname. */ +- sec = nsec3_prove_closest_encloser(env, flt, ct, qinfo, 1, &ce); ++ sec = nsec3_prove_closest_encloser(env, flt, ct, qinfo, 1, &ce, calc); + if(sec != sec_status_secure) { + if(sec == sec_status_bogus) + verbose(VERB_ALGO, "nsec3 nameerror proof: failed " + "to prove a closest encloser"); ++ else if(sec == sec_status_unchecked) ++ verbose(VERB_ALGO, "nsec3 nameerror proof: will " ++ "continue proving closest encloser after " ++ "suspend"); + else verbose(VERB_ALGO, "nsec3 nameerror proof: closest " + "nsec3 is an insecure delegation"); + return sec; +@@ -1046,9 +1144,27 @@ nsec3_do_prove_nameerror(struct module_env* env, struct nsec3_filter* flt, + /* At this point, we know that qname does not exist. Now we need + * to prove that the wildcard does not exist. */ + log_assert(ce.ce); +- wc = nsec3_ce_wildcard(env->scratch, ce.ce, ce.ce_len, &wclen); +- if(!wc || !find_covering_nsec3(env, flt, ct, wc, wclen, +- &wc_rrset, &wc_rr)) { ++ wc = nsec3_ce_wildcard(ct->region, ce.ce, ce.ce_len, &wclen); ++ if(!wc) { ++ verbose(VERB_ALGO, "nsec3 nameerror proof: could not prove " ++ "that the applicable wildcard did not exist."); ++ return sec_status_bogus; ++ } ++ if(!find_covering_nsec3(env, flt, ct, wc, wclen, &wc_rrset, &wc_rr, calc)) { ++ if(*calc == MAX_NSEC3_ERRORS) { ++ verbose(VERB_ALGO, "nsec3 nameerror proof: could not prove " ++ "that the applicable wildcard did not exist; " ++ "all attempted hash calculations were " ++ "erroneous; bogus"); ++ return sec_status_bogus; ++ } else if(*calc >= MAX_NSEC3_CALCULATIONS) { ++ verbose(VERB_ALGO, "nsec3 nameerror proof: could not prove " ++ "that the applicable wildcard did not exist; " ++ "reached MAX_NSEC3_CALCULATIONS (%d); " ++ "unchecked still", ++ MAX_NSEC3_CALCULATIONS); ++ return sec_status_unchecked; ++ } + verbose(VERB_ALGO, "nsec3 nameerror proof: could not prove " + "that the applicable wildcard did not exist."); + return sec_status_bogus; +@@ -1064,14 +1180,13 @@ nsec3_do_prove_nameerror(struct module_env* env, struct nsec3_filter* flt, + enum sec_status + nsec3_prove_nameerror(struct module_env* env, struct val_env* ve, + struct ub_packed_rrset_key** list, size_t num, +- struct query_info* qinfo, struct key_entry_key* kkey) ++ struct query_info* qinfo, struct key_entry_key* kkey, ++ struct nsec3_cache_table* ct, int* calc) + { +- rbtree_type ct; + struct nsec3_filter flt; + + if(!list || num == 0 || !kkey || !key_entry_isgood(kkey)) + return sec_status_bogus; /* no valid NSEC3s, bogus */ +- rbtree_init(&ct, &nsec3_hash_cmp); /* init names-to-hash cache */ + filter_init(&flt, list, num, qinfo); /* init RR iterator */ + if(!flt.zone) + return sec_status_bogus; /* no RRs */ +@@ -1079,7 +1194,7 @@ nsec3_prove_nameerror(struct module_env* env, struct val_env* ve, + return sec_status_insecure; /* iteration count too high */ + log_nametypeclass(VERB_ALGO, "start nsec3 nameerror proof, zone", + flt.zone, 0, 0); +- return nsec3_do_prove_nameerror(env, &flt, &ct, qinfo); ++ return nsec3_do_prove_nameerror(env, &flt, ct, qinfo, calc); + } + + /* +@@ -1089,8 +1204,9 @@ nsec3_prove_nameerror(struct module_env* env, struct val_env* ve, + + /** Do the nodata proof */ + static enum sec_status +-nsec3_do_prove_nodata(struct module_env* env, struct nsec3_filter* flt, +- rbtree_type* ct, struct query_info* qinfo) ++nsec3_do_prove_nodata(struct module_env* env, struct nsec3_filter* flt, ++ struct nsec3_cache_table* ct, struct query_info* qinfo, ++ int* calc) + { + struct ce_response ce; + uint8_t* wc; +@@ -1100,7 +1216,7 @@ nsec3_do_prove_nodata(struct module_env* env, struct nsec3_filter* flt, + enum sec_status sec; + + if(find_matching_nsec3(env, flt, ct, qinfo->qname, qinfo->qname_len, +- &rrset, &rr)) { ++ &rrset, &rr, calc)) { + /* cases 1 and 2 */ + if(nsec3_has_type(rrset, rr, qinfo->qtype)) { + verbose(VERB_ALGO, "proveNodata: Matching NSEC3 " +@@ -1144,11 +1260,23 @@ nsec3_do_prove_nodata(struct module_env* env, struct nsec3_filter* flt, + } + return sec_status_secure; + } ++ if(*calc == MAX_NSEC3_ERRORS) { ++ verbose(VERB_ALGO, "proveNodata: all attempted hash " ++ "calculations were erroneous while finding a matching " ++ "NSEC3, bogus"); ++ return sec_status_bogus; ++ } else if(*calc >= MAX_NSEC3_CALCULATIONS) { ++ verbose(VERB_ALGO, "proveNodata: reached " ++ "MAX_NSEC3_CALCULATIONS (%d) while finding a " ++ "matching NSEC3; unchecked still", ++ MAX_NSEC3_CALCULATIONS); ++ return sec_status_unchecked; ++ } + + /* For cases 3 - 5, we need the proven closest encloser, and it + * can't match qname. Although, at this point, we know that it + * won't since we just checked that. */ +- sec = nsec3_prove_closest_encloser(env, flt, ct, qinfo, 1, &ce); ++ sec = nsec3_prove_closest_encloser(env, flt, ct, qinfo, 1, &ce, calc); + if(sec == sec_status_bogus) { + verbose(VERB_ALGO, "proveNodata: did not match qname, " + "nor found a proven closest encloser."); +@@ -1157,14 +1285,17 @@ nsec3_do_prove_nodata(struct module_env* env, struct nsec3_filter* flt, + verbose(VERB_ALGO, "proveNodata: closest nsec3 is insecure " + "delegation."); + return sec_status_insecure; ++ } else if(sec==sec_status_unchecked) { ++ return sec_status_unchecked; + } + + /* Case 3: removed */ + + /* Case 4: */ + log_assert(ce.ce); +- wc = nsec3_ce_wildcard(env->scratch, ce.ce, ce.ce_len, &wclen); +- if(wc && find_matching_nsec3(env, flt, ct, wc, wclen, &rrset, &rr)) { ++ wc = nsec3_ce_wildcard(ct->region, ce.ce, ce.ce_len, &wclen); ++ if(wc && find_matching_nsec3(env, flt, ct, wc, wclen, &rrset, &rr, ++ calc)) { + /* found wildcard */ + if(nsec3_has_type(rrset, rr, qinfo->qtype)) { + verbose(VERB_ALGO, "nsec3 nodata proof: matching " +@@ -1195,6 +1326,18 @@ nsec3_do_prove_nodata(struct module_env* env, struct nsec3_filter* flt, + } + return sec_status_secure; + } ++ if(*calc == MAX_NSEC3_ERRORS) { ++ verbose(VERB_ALGO, "nsec3 nodata proof: all attempted hash " ++ "calculations were erroneous while matching " ++ "wildcard, bogus"); ++ return sec_status_bogus; ++ } else if(*calc >= MAX_NSEC3_CALCULATIONS) { ++ verbose(VERB_ALGO, "nsec3 nodata proof: reached " ++ "MAX_NSEC3_CALCULATIONS (%d) while matching " ++ "wildcard, unchecked still", ++ MAX_NSEC3_CALCULATIONS); ++ return sec_status_unchecked; ++ } + + /* Case 5: */ + /* Due to forwarders, cnames, and other collating effects, we +@@ -1223,28 +1366,27 @@ nsec3_do_prove_nodata(struct module_env* env, struct nsec3_filter* flt, + enum sec_status + nsec3_prove_nodata(struct module_env* env, struct val_env* ve, + struct ub_packed_rrset_key** list, size_t num, +- struct query_info* qinfo, struct key_entry_key* kkey) ++ struct query_info* qinfo, struct key_entry_key* kkey, ++ struct nsec3_cache_table* ct, int* calc) + { +- rbtree_type ct; + struct nsec3_filter flt; + + if(!list || num == 0 || !kkey || !key_entry_isgood(kkey)) + return sec_status_bogus; /* no valid NSEC3s, bogus */ +- rbtree_init(&ct, &nsec3_hash_cmp); /* init names-to-hash cache */ + filter_init(&flt, list, num, qinfo); /* init RR iterator */ + if(!flt.zone) + return sec_status_bogus; /* no RRs */ + if(nsec3_iteration_count_high(ve, &flt, kkey)) + return sec_status_insecure; /* iteration count too high */ +- return nsec3_do_prove_nodata(env, &flt, &ct, qinfo); ++ return nsec3_do_prove_nodata(env, &flt, ct, qinfo, calc); + } + + enum sec_status + nsec3_prove_wildcard(struct module_env* env, struct val_env* ve, + struct ub_packed_rrset_key** list, size_t num, +- struct query_info* qinfo, struct key_entry_key* kkey, uint8_t* wc) ++ struct query_info* qinfo, struct key_entry_key* kkey, uint8_t* wc, ++ struct nsec3_cache_table* ct, int* calc) + { +- rbtree_type ct; + struct nsec3_filter flt; + struct ce_response ce; + uint8_t* nc; +@@ -1254,7 +1396,6 @@ nsec3_prove_wildcard(struct module_env* env, struct val_env* ve, + + if(!list || num == 0 || !kkey || !key_entry_isgood(kkey)) + return sec_status_bogus; /* no valid NSEC3s, bogus */ +- rbtree_init(&ct, &nsec3_hash_cmp); /* init names-to-hash cache */ + filter_init(&flt, list, num, qinfo); /* init RR iterator */ + if(!flt.zone) + return sec_status_bogus; /* no RRs */ +@@ -1272,8 +1413,22 @@ nsec3_prove_wildcard(struct module_env* env, struct val_env* ve, + /* Now we still need to prove that the original data did not exist. + * Otherwise, we need to show that the next closer name is covered. */ + next_closer(qinfo->qname, qinfo->qname_len, ce.ce, &nc, &nc_len); +- if(!find_covering_nsec3(env, &flt, &ct, nc, nc_len, +- &ce.nc_rrset, &ce.nc_rr)) { ++ if(!find_covering_nsec3(env, &flt, ct, nc, nc_len, ++ &ce.nc_rrset, &ce.nc_rr, calc)) { ++ if(*calc == MAX_NSEC3_ERRORS) { ++ verbose(VERB_ALGO, "proveWildcard: did not find a " ++ "covering NSEC3 that covered the next closer " ++ "name; all attempted hash calculations were " ++ "erroneous; bogus"); ++ return sec_status_bogus; ++ } else if(*calc >= MAX_NSEC3_CALCULATIONS) { ++ verbose(VERB_ALGO, "proveWildcard: did not find a " ++ "covering NSEC3 that covered the next closer " ++ "name; reached MAX_NSEC3_CALCULATIONS " ++ "(%d); unchecked still", ++ MAX_NSEC3_CALCULATIONS); ++ return sec_status_unchecked; ++ } + verbose(VERB_ALGO, "proveWildcard: did not find a covering " + "NSEC3 that covered the next closer name."); + return sec_status_bogus; +@@ -1294,6 +1449,7 @@ list_is_secure(struct module_env* env, struct val_env* ve, + { + struct packed_rrset_data* d; + size_t i; ++ int verified = 0; + for(i=0; ientry.data; + if(list[i]->rk.type != htons(LDNS_RR_TYPE_NSEC3)) +@@ -1304,7 +1460,8 @@ list_is_secure(struct module_env* env, struct val_env* ve, + if(d->security == sec_status_secure) + continue; + d->security = val_verify_rrset_entry(env, ve, list[i], kkey, +- reason, reason_bogus, LDNS_SECTION_AUTHORITY, qstate); ++ reason, reason_bogus, LDNS_SECTION_AUTHORITY, qstate, ++ &verified); + if(d->security != sec_status_secure) { + verbose(VERB_ALGO, "NSEC3 did not verify"); + return 0; +@@ -1318,13 +1475,16 @@ enum sec_status + nsec3_prove_nods(struct module_env* env, struct val_env* ve, + struct ub_packed_rrset_key** list, size_t num, + struct query_info* qinfo, struct key_entry_key* kkey, char** reason, +- sldns_ede_code* reason_bogus, struct module_qstate* qstate) ++ sldns_ede_code* reason_bogus, struct module_qstate* qstate, ++ struct nsec3_cache_table* ct) + { +- rbtree_type ct; + struct nsec3_filter flt; + struct ce_response ce; + struct ub_packed_rrset_key* rrset; + int rr; ++ int calc = 0; ++ enum sec_status sec; ++ + log_assert(qinfo->qtype == LDNS_RR_TYPE_DS); + + if(!list || num == 0 || !kkey || !key_entry_isgood(kkey)) { +@@ -1335,7 +1495,6 @@ nsec3_prove_nods(struct module_env* env, struct val_env* ve, + *reason = "not all NSEC3 records secure"; + return sec_status_bogus; /* not all NSEC3 records secure */ + } +- rbtree_init(&ct, &nsec3_hash_cmp); /* init names-to-hash cache */ + filter_init(&flt, list, num, qinfo); /* init RR iterator */ + if(!flt.zone) { + *reason = "no NSEC3 records"; +@@ -1346,8 +1505,8 @@ nsec3_prove_nods(struct module_env* env, struct val_env* ve, + + /* Look for a matching NSEC3 to qname -- this is the normal + * NODATA case. */ +- if(find_matching_nsec3(env, &flt, &ct, qinfo->qname, qinfo->qname_len, +- &rrset, &rr)) { ++ if(find_matching_nsec3(env, &flt, ct, qinfo->qname, qinfo->qname_len, ++ &rrset, &rr, &calc)) { + /* If the matching NSEC3 has the SOA bit set, it is from + * the wrong zone (the child instead of the parent). If + * it has the DS bit set, then we were lied to. */ +@@ -1370,10 +1529,24 @@ nsec3_prove_nods(struct module_env* env, struct val_env* ve, + /* Otherwise, this proves no DS. */ + return sec_status_secure; + } ++ if(calc == MAX_NSEC3_ERRORS) { ++ verbose(VERB_ALGO, "nsec3 provenods: all attempted hash " ++ "calculations were erroneous while finding a matching " ++ "NSEC3, bogus"); ++ return sec_status_bogus; ++ } else if(calc >= MAX_NSEC3_CALCULATIONS) { ++ verbose(VERB_ALGO, "nsec3 provenods: reached " ++ "MAX_NSEC3_CALCULATIONS (%d) while finding a " ++ "matching NSEC3, unchecked still", ++ MAX_NSEC3_CALCULATIONS); ++ return sec_status_unchecked; ++ } + + /* Otherwise, we are probably in the opt-out case. */ +- if(nsec3_prove_closest_encloser(env, &flt, &ct, qinfo, 1, &ce) +- != sec_status_secure) { ++ sec = nsec3_prove_closest_encloser(env, &flt, ct, qinfo, 1, &ce, &calc); ++ if(sec == sec_status_unchecked) { ++ return sec_status_unchecked; ++ } else if(sec != sec_status_secure) { + /* an insecure delegation *above* the qname does not prove + * anything about this qname exactly, and bogus is bogus */ + verbose(VERB_ALGO, "nsec3 provenods: did not match qname, " +@@ -1407,17 +1580,16 @@ nsec3_prove_nods(struct module_env* env, struct val_env* ve, + + enum sec_status + nsec3_prove_nxornodata(struct module_env* env, struct val_env* ve, +- struct ub_packed_rrset_key** list, size_t num, +- struct query_info* qinfo, struct key_entry_key* kkey, int* nodata) ++ struct ub_packed_rrset_key** list, size_t num, ++ struct query_info* qinfo, struct key_entry_key* kkey, int* nodata, ++ struct nsec3_cache_table* ct, int* calc) + { + enum sec_status sec, secnx; +- rbtree_type ct; + struct nsec3_filter flt; + *nodata = 0; + + if(!list || num == 0 || !kkey || !key_entry_isgood(kkey)) + return sec_status_bogus; /* no valid NSEC3s, bogus */ +- rbtree_init(&ct, &nsec3_hash_cmp); /* init names-to-hash cache */ + filter_init(&flt, list, num, qinfo); /* init RR iterator */ + if(!flt.zone) + return sec_status_bogus; /* no RRs */ +@@ -1427,16 +1599,20 @@ nsec3_prove_nxornodata(struct module_env* env, struct val_env* ve, + /* try nxdomain and nodata after another, while keeping the + * hash cache intact */ + +- secnx = nsec3_do_prove_nameerror(env, &flt, &ct, qinfo); ++ secnx = nsec3_do_prove_nameerror(env, &flt, ct, qinfo, calc); + if(secnx==sec_status_secure) + return sec_status_secure; +- sec = nsec3_do_prove_nodata(env, &flt, &ct, qinfo); ++ else if(secnx == sec_status_unchecked) ++ return sec_status_unchecked; ++ sec = nsec3_do_prove_nodata(env, &flt, ct, qinfo, calc); + if(sec==sec_status_secure) { + *nodata = 1; + } else if(sec == sec_status_insecure) { + *nodata = 1; + } else if(secnx == sec_status_insecure) { + sec = sec_status_insecure; ++ } else if(sec == sec_status_unchecked) { ++ return sec_status_unchecked; + } + return sec; + } +diff --git a/validator/val_nsec3.h b/validator/val_nsec3.h +index 7676fc8b..8ca91293 100644 +--- a/validator/val_nsec3.h ++++ b/validator/val_nsec3.h +@@ -98,6 +98,15 @@ struct sldns_buffer; + /** The SHA1 hash algorithm for NSEC3 */ + #define NSEC3_HASH_SHA1 0x01 + ++/** ++* Cache table for NSEC3 hashes. ++* It keeps a *pointer* to the region its items are allocated. ++*/ ++struct nsec3_cache_table { ++ rbtree_type* ct; ++ struct regional* region; ++}; ++ + /** + * Determine if the set of NSEC3 records provided with a response prove NAME + * ERROR. This means that the NSEC3s prove a) the closest encloser exists, +@@ -110,14 +119,18 @@ struct sldns_buffer; + * @param num: number of RRsets in the array to examine. + * @param qinfo: query that is verified for. + * @param kkey: key entry that signed the NSEC3s. ++ * @param ct: cached hashes table. ++ * @param calc: current hash calculations. + * @return: + * sec_status SECURE of the Name Error is proven by the NSEC3 RRs, +- * BOGUS if not, INSECURE if all of the NSEC3s could be validly ignored. ++ * BOGUS if not, INSECURE if all of the NSEC3s could be validly ignored, ++ * UNCHECKED if no more hash calculations are allowed at this point. + */ + enum sec_status + nsec3_prove_nameerror(struct module_env* env, struct val_env* ve, + struct ub_packed_rrset_key** list, size_t num, +- struct query_info* qinfo, struct key_entry_key* kkey); ++ struct query_info* qinfo, struct key_entry_key* kkey, ++ struct nsec3_cache_table* ct, int* calc); + + /** + * Determine if the NSEC3s provided in a response prove the NOERROR/NODATA +@@ -144,15 +157,18 @@ nsec3_prove_nameerror(struct module_env* env, struct val_env* ve, + * @param num: number of RRsets in the array to examine. + * @param qinfo: query that is verified for. + * @param kkey: key entry that signed the NSEC3s. ++ * @param ct: cached hashes table. ++ * @param calc: current hash calculations. + * @return: + * sec_status SECURE of the proposition is proven by the NSEC3 RRs, +- * BOGUS if not, INSECURE if all of the NSEC3s could be validly ignored. ++ * BOGUS if not, INSECURE if all of the NSEC3s could be validly ignored, ++ * UNCHECKED if no more hash calculations are allowed at this point. + */ + enum sec_status + nsec3_prove_nodata(struct module_env* env, struct val_env* ve, + struct ub_packed_rrset_key** list, size_t num, +- struct query_info* qinfo, struct key_entry_key* kkey); +- ++ struct query_info* qinfo, struct key_entry_key* kkey, ++ struct nsec3_cache_table* ct, int* calc); + + /** + * Prove that a positive wildcard match was appropriate (no direct match +@@ -166,14 +182,18 @@ nsec3_prove_nodata(struct module_env* env, struct val_env* ve, + * @param kkey: key entry that signed the NSEC3s. + * @param wc: The purported wildcard that matched. This is the wildcard name + * as *.wildcard.name., with the *. label already removed. ++ * @param ct: cached hashes table. ++ * @param calc: current hash calculations. + * @return: + * sec_status SECURE of the proposition is proven by the NSEC3 RRs, +- * BOGUS if not, INSECURE if all of the NSEC3s could be validly ignored. ++ * BOGUS if not, INSECURE if all of the NSEC3s could be validly ignored, ++ * UNCHECKED if no more hash calculations are allowed at this point. + */ + enum sec_status + nsec3_prove_wildcard(struct module_env* env, struct val_env* ve, + struct ub_packed_rrset_key** list, size_t num, +- struct query_info* qinfo, struct key_entry_key* kkey, uint8_t* wc); ++ struct query_info* qinfo, struct key_entry_key* kkey, uint8_t* wc, ++ struct nsec3_cache_table* ct, int* calc); + + /** + * Prove that a DS response either had no DS, or wasn't a delegation point. +@@ -189,17 +209,20 @@ nsec3_prove_wildcard(struct module_env* env, struct val_env* ve, + * @param reason: string for bogus result. + * @param reason_bogus: EDE (RFC8914) code paired with the reason of failure. + * @param qstate: qstate with region. ++ * @param ct: cached hashes table. + * @return: + * sec_status SECURE of the proposition is proven by the NSEC3 RRs, + * BOGUS if not, INSECURE if all of the NSEC3s could be validly ignored. + * or if there was no DS in an insecure (i.e., opt-in) way, +- * INDETERMINATE if it was clear that this wasn't a delegation point. ++ * INDETERMINATE if it was clear that this wasn't a delegation point, ++ * UNCHECKED if no more hash calculations are allowed at this point. + */ + enum sec_status + nsec3_prove_nods(struct module_env* env, struct val_env* ve, + struct ub_packed_rrset_key** list, size_t num, + struct query_info* qinfo, struct key_entry_key* kkey, char** reason, +- sldns_ede_code* reason_bogus, struct module_qstate* qstate); ++ sldns_ede_code* reason_bogus, struct module_qstate* qstate, ++ struct nsec3_cache_table* ct); + + /** + * Prove NXDOMAIN or NODATA. +@@ -212,14 +235,18 @@ nsec3_prove_nods(struct module_env* env, struct val_env* ve, + * @param kkey: key entry that signed the NSEC3s. + * @param nodata: if return value is secure, this indicates if nodata or + * nxdomain was proven. ++ * @param ct: cached hashes table. ++ * @param calc: current hash calculations. + * @return: + * sec_status SECURE of the proposition is proven by the NSEC3 RRs, +- * BOGUS if not, INSECURE if all of the NSEC3s could be validly ignored. ++ * BOGUS if not, INSECURE if all of the NSEC3s could be validly ignored, ++ * UNCHECKED if no more hash calculations are allowed at this point. + */ + enum sec_status + nsec3_prove_nxornodata(struct module_env* env, struct val_env* ve, + struct ub_packed_rrset_key** list, size_t num, +- struct query_info* qinfo, struct key_entry_key* kkey, int* nodata); ++ struct query_info* qinfo, struct key_entry_key* kkey, int* nodata, ++ struct nsec3_cache_table* ct, int* calc); + + /** + * The NSEC3 hash result storage. +@@ -256,6 +283,14 @@ struct nsec3_cached_hash { + */ + int nsec3_hash_cmp(const void* c1, const void* c2); + ++/** ++ * Initialise the NSEC3 cache table. ++ * @param ct: the nsec3 cache table. ++ * @param region: the region where allocations for the table will happen. ++ * @return true on success, false on malloc error. ++ */ ++int nsec3_cache_table_init(struct nsec3_cache_table* ct, struct regional* region); ++ + /** + * Obtain the hash of an owner name. + * Used internally by the nsec3 proof functions in this file. +@@ -272,7 +307,8 @@ int nsec3_hash_cmp(const void* c1, const void* c2); + * @param dname_len: the length of the name. + * @param hash: the hash node is returned on success. + * @return: +- * 1 on success, either from cache or newly hashed hash is returned. ++ * 2 on success, hash from cache is returned. ++ * 1 on success, newly computed hash is returned. + * 0 on a malloc failure. + * -1 if the NSEC3 rr was badly formatted (i.e. formerr). + */ +diff --git a/validator/val_sigcrypt.c b/validator/val_sigcrypt.c +index 5ab21e20..8600a682 100644 +--- a/validator/val_sigcrypt.c ++++ b/validator/val_sigcrypt.c +@@ -78,6 +78,9 @@ + #include + #endif + ++/** Maximum number of RRSIG validations for an RRset. */ ++#define MAX_VALIDATE_RRSIGS 8 ++ + /** return number of rrs in an rrset */ + static size_t + rrset_get_count(struct ub_packed_rrset_key* rrset) +@@ -541,6 +544,8 @@ int algo_needs_missing(struct algo_needs* n) + * @param reason_bogus: EDE (RFC8914) code paired with the reason of failure. + * @param section: section of packet where this rrset comes from. + * @param qstate: qstate with region. ++ * @param numverified: incremented when the number of RRSIG validations ++ * increases. + * @return secure if any key signs *this* signature. bogus if no key signs it, + * unchecked on error, or indeterminate if all keys are not supported by + * the crypto library (openssl3+ only). +@@ -551,7 +556,8 @@ dnskeyset_verify_rrset_sig(struct module_env* env, struct val_env* ve, + struct ub_packed_rrset_key* dnskey, size_t sig_idx, + struct rbtree_type** sortree, + char** reason, sldns_ede_code *reason_bogus, +- sldns_pkt_section section, struct module_qstate* qstate) ++ sldns_pkt_section section, struct module_qstate* qstate, ++ int* numverified) + { + /* find matching keys and check them */ + enum sec_status sec = sec_status_bogus; +@@ -575,6 +581,7 @@ dnskeyset_verify_rrset_sig(struct module_env* env, struct val_env* ve, + tag != dnskey_calc_keytag(dnskey, i)) + continue; + numchecked ++; ++ (*numverified)++; + + /* see if key verifies */ + sec = dnskey_verify_rrset_sig(env->scratch, +@@ -585,6 +592,13 @@ dnskeyset_verify_rrset_sig(struct module_env* env, struct val_env* ve, + return sec; + else if(sec == sec_status_indeterminate) + numindeterminate ++; ++ if(*numverified > MAX_VALIDATE_RRSIGS) { ++ *reason = "too many RRSIG validations"; ++ if(reason_bogus) ++ *reason_bogus = LDNS_EDE_DNSSEC_BOGUS; ++ verbose(VERB_ALGO, "verify sig: too many RRSIG validations"); ++ return sec_status_bogus; ++ } + } + if(numchecked == 0) { + *reason = "signatures from unknown keys"; +@@ -608,7 +622,7 @@ enum sec_status + dnskeyset_verify_rrset(struct module_env* env, struct val_env* ve, + struct ub_packed_rrset_key* rrset, struct ub_packed_rrset_key* dnskey, + uint8_t* sigalg, char** reason, sldns_ede_code *reason_bogus, +- sldns_pkt_section section, struct module_qstate* qstate) ++ sldns_pkt_section section, struct module_qstate* qstate, int* verified) + { + enum sec_status sec; + size_t i, num; +@@ -616,6 +630,7 @@ dnskeyset_verify_rrset(struct module_env* env, struct val_env* ve, + /* make sure that for all DNSKEY algorithms there are valid sigs */ + struct algo_needs needs; + int alg; ++ *verified = 0; + + num = rrset_get_sigcount(rrset); + if(num == 0) { +@@ -640,7 +655,7 @@ dnskeyset_verify_rrset(struct module_env* env, struct val_env* ve, + for(i=0; inow, rrset, + dnskey, i, &sortree, reason, reason_bogus, +- section, qstate); ++ section, qstate, verified); + /* see which algorithm has been fixed up */ + if(sec == sec_status_secure) { + if(!sigalg) +@@ -652,6 +667,13 @@ dnskeyset_verify_rrset(struct module_env* env, struct val_env* ve, + algo_needs_set_bogus(&needs, + (uint8_t)rrset_get_sig_algo(rrset, i)); + } ++ if(*verified > MAX_VALIDATE_RRSIGS) { ++ verbose(VERB_QUERY, "rrset failed to verify, too many RRSIG validations"); ++ *reason = "too many RRSIG validations"; ++ if(reason_bogus) ++ *reason_bogus = LDNS_EDE_DNSSEC_BOGUS; ++ return sec_status_bogus; ++ } + } + if(sigalg && (alg=algo_needs_missing(&needs)) != 0) { + verbose(VERB_ALGO, "rrset failed to verify: " +@@ -690,6 +712,7 @@ dnskey_verify_rrset(struct module_env* env, struct val_env* ve, + int buf_canon = 0; + uint16_t tag = dnskey_calc_keytag(dnskey, dnskey_idx); + int algo = dnskey_get_algo(dnskey, dnskey_idx); ++ int numverified = 0; + + num = rrset_get_sigcount(rrset); + if(num == 0) { +@@ -713,8 +736,16 @@ dnskey_verify_rrset(struct module_env* env, struct val_env* ve, + if(sec == sec_status_secure) + return sec; + numchecked ++; ++ numverified ++; + if(sec == sec_status_indeterminate) + numindeterminate ++; ++ if(numverified > MAX_VALIDATE_RRSIGS) { ++ verbose(VERB_QUERY, "rrset failed to verify, too many RRSIG validations"); ++ *reason = "too many RRSIG validations"; ++ if(reason_bogus) ++ *reason_bogus = LDNS_EDE_DNSSEC_BOGUS; ++ return sec_status_bogus; ++ } + } + verbose(VERB_ALGO, "rrset failed to verify: all signatures are bogus"); + if(!numchecked) { +diff --git a/validator/val_sigcrypt.h b/validator/val_sigcrypt.h +index 7f52b71e..1a3d8fcb 100644 +--- a/validator/val_sigcrypt.h ++++ b/validator/val_sigcrypt.h +@@ -260,6 +260,7 @@ uint16_t dnskey_get_flags(struct ub_packed_rrset_key* k, size_t idx); + * @param reason_bogus: EDE (RFC8914) code paired with the reason of failure. + * @param section: section of packet where this rrset comes from. + * @param qstate: qstate with region. ++ * @param verified: if not NULL the number of RRSIG validations is returned. + * @return SECURE if one key in the set verifies one rrsig. + * UNCHECKED on allocation errors, unsupported algorithms, malformed data, + * and BOGUS on verification failures (no keys match any signatures). +@@ -268,7 +269,7 @@ enum sec_status dnskeyset_verify_rrset(struct module_env* env, + struct val_env* ve, struct ub_packed_rrset_key* rrset, + struct ub_packed_rrset_key* dnskey, uint8_t* sigalg, + char** reason, sldns_ede_code *reason_bogus, +- sldns_pkt_section section, struct module_qstate* qstate); ++ sldns_pkt_section section, struct module_qstate* qstate, int* verified); + + + /** +diff --git a/validator/val_utils.c b/validator/val_utils.c +index e2319ee2..cb37ea00 100644 +--- a/validator/val_utils.c ++++ b/validator/val_utils.c +@@ -58,6 +58,10 @@ + #include "sldns/wire2str.h" + #include "sldns/parseutil.h" + ++/** Maximum allowed digest match failures per DS, for DNSKEYs with the same ++ * properties */ ++#define MAX_DS_MATCH_FAILURES 4 ++ + enum val_classification + val_classify_response(uint16_t query_flags, struct query_info* origqinf, + struct query_info* qinf, struct reply_info* rep, size_t skip) +@@ -336,7 +340,8 @@ static enum sec_status + val_verify_rrset(struct module_env* env, struct val_env* ve, + struct ub_packed_rrset_key* rrset, struct ub_packed_rrset_key* keys, + uint8_t* sigalg, char** reason, sldns_ede_code *reason_bogus, +- sldns_pkt_section section, struct module_qstate* qstate) ++ sldns_pkt_section section, struct module_qstate* qstate, ++ int *verified) + { + enum sec_status sec; + struct packed_rrset_data* d = (struct packed_rrset_data*)rrset-> +@@ -346,6 +351,7 @@ val_verify_rrset(struct module_env* env, struct val_env* ve, + log_nametypeclass(VERB_ALGO, "verify rrset cached", + rrset->rk.dname, ntohs(rrset->rk.type), + ntohs(rrset->rk.rrset_class)); ++ *verified = 0; + return d->security; + } + /* check in the cache if verification has already been done */ +@@ -354,12 +360,13 @@ val_verify_rrset(struct module_env* env, struct val_env* ve, + log_nametypeclass(VERB_ALGO, "verify rrset from cache", + rrset->rk.dname, ntohs(rrset->rk.type), + ntohs(rrset->rk.rrset_class)); ++ *verified = 0; + return d->security; + } + log_nametypeclass(VERB_ALGO, "verify rrset", rrset->rk.dname, + ntohs(rrset->rk.type), ntohs(rrset->rk.rrset_class)); + sec = dnskeyset_verify_rrset(env, ve, rrset, keys, sigalg, reason, +- reason_bogus, section, qstate); ++ reason_bogus, section, qstate, verified); + verbose(VERB_ALGO, "verify result: %s", sec_status_to_string(sec)); + regional_free_all(env->scratch); + +@@ -393,7 +400,8 @@ enum sec_status + val_verify_rrset_entry(struct module_env* env, struct val_env* ve, + struct ub_packed_rrset_key* rrset, struct key_entry_key* kkey, + char** reason, sldns_ede_code *reason_bogus, +- sldns_pkt_section section, struct module_qstate* qstate) ++ sldns_pkt_section section, struct module_qstate* qstate, ++ int* verified) + { + /* temporary dnskey rrset-key */ + struct ub_packed_rrset_key dnskey; +@@ -407,7 +415,7 @@ val_verify_rrset_entry(struct module_env* env, struct val_env* ve, + dnskey.entry.key = &dnskey; + dnskey.entry.data = kd->rrset_data; + sec = val_verify_rrset(env, ve, rrset, &dnskey, kd->algo, reason, +- reason_bogus, section, qstate); ++ reason_bogus, section, qstate, verified); + return sec; + } + +@@ -439,6 +447,12 @@ verify_dnskeys_with_ds_rr(struct module_env* env, struct val_env* ve, + if(!ds_digest_match_dnskey(env, dnskey_rrset, i, ds_rrset, + ds_idx)) { + verbose(VERB_ALGO, "DS match attempt failed"); ++ if(numchecked > numhashok + MAX_DS_MATCH_FAILURES) { ++ verbose(VERB_ALGO, "DS match attempt reached " ++ "MAX_DS_MATCH_FAILURES (%d); bogus", ++ MAX_DS_MATCH_FAILURES); ++ return sec_status_bogus; ++ } + continue; + } + numhashok++; +diff --git a/validator/val_utils.h b/validator/val_utils.h +index 83e3d0ad..e8cdcefa 100644 +--- a/validator/val_utils.h ++++ b/validator/val_utils.h +@@ -124,12 +124,14 @@ void val_find_signer(enum val_classification subtype, + * @param reason_bogus: EDE (RFC8914) code paired with the reason of failure. + * @param section: section of packet where this rrset comes from. + * @param qstate: qstate with region. ++ * @param verified: if not NULL, the number of RRSIG validations is returned. + * @return security status of verification. + */ + enum sec_status val_verify_rrset_entry(struct module_env* env, + struct val_env* ve, struct ub_packed_rrset_key* rrset, + struct key_entry_key* kkey, char** reason, sldns_ede_code *reason_bogus, +- sldns_pkt_section section, struct module_qstate* qstate); ++ sldns_pkt_section section, struct module_qstate* qstate, ++ int* verified); + + /** + * Verify DNSKEYs with DS rrset. Like val_verify_new_DNSKEYs but +diff --git a/validator/validator.c b/validator/validator.c +index 1723afef..f1f7be34 100644 +--- a/validator/validator.c ++++ b/validator/validator.c +@@ -64,10 +64,15 @@ + #include "sldns/wire2str.h" + #include "sldns/str2wire.h" + ++/** Max number of RRSIGs to validate at once, suspend query for later. */ ++#define MAX_VALIDATE_AT_ONCE 8 ++/** Max number of validation suspends allowed, error out otherwise. */ ++#define MAX_VALIDATION_SUSPENDS 16 ++ + /* forward decl for cache response and normal super inform calls of a DS */ + static void process_ds_response(struct module_qstate* qstate, + struct val_qstate* vq, int id, int rcode, struct dns_msg* msg, +- struct query_info* qinfo, struct sock_list* origin); ++ struct query_info* qinfo, struct sock_list* origin, int* suspend); + + + /* Updates the suplied EDE (RFC8914) code selectively so we don't loose +@@ -281,6 +286,21 @@ val_new(struct module_qstate* qstate, int id) + return val_new_getmsg(qstate, vq); + } + ++/** reset validator query state for query restart */ ++static void ++val_restart(struct val_qstate* vq) ++{ ++ struct comm_timer* temp_timer; ++ int restart_count; ++ if(!vq) return; ++ temp_timer = vq->suspend_timer; ++ restart_count = vq->restart_count+1; ++ memset(vq, 0, sizeof(*vq)); ++ vq->suspend_timer = temp_timer; ++ vq->restart_count = restart_count; ++ vq->state = VAL_INIT_STATE; ++} ++ + /** + * Exit validation with an error status + * +@@ -587,30 +607,42 @@ prime_trust_anchor(struct module_qstate* qstate, struct val_qstate* vq, + * completed. + * + * @param qstate: query state. ++ * @param vq: validator query state. + * @param env: module env for verify. + * @param ve: validator env for verify. + * @param qchase: query that was made. + * @param chase_reply: answer to validate. + * @param key_entry: the key entry, which is trusted, and which matches + * the signer of the answer. The key entry isgood(). ++ * @param suspend: returned true if the task takes too long and needs to ++ * suspend to continue the effort later. + * @return false if any of the rrsets in the an or ns sections of the message + * fail to verify. The message is then set to bogus. + */ + static int +-validate_msg_signatures(struct module_qstate* qstate, struct module_env* env, +- struct val_env* ve, struct query_info* qchase, +- struct reply_info* chase_reply, struct key_entry_key* key_entry) ++validate_msg_signatures(struct module_qstate* qstate, struct val_qstate* vq, ++ struct module_env* env, struct val_env* ve, struct query_info* qchase, ++ struct reply_info* chase_reply, struct key_entry_key* key_entry, ++ int* suspend) + { + uint8_t* sname; + size_t i, slen; + struct ub_packed_rrset_key* s; + enum sec_status sec; +- int dname_seen = 0; ++ int dname_seen = 0, num_verifies = 0, verified, have_state = 0; + char* reason = NULL; + sldns_ede_code reason_bogus = LDNS_EDE_DNSSEC_BOGUS; ++ *suspend = 0; ++ if(vq->msg_signatures_state) { ++ /* Pick up the state, and reset it, may not be needed now. */ ++ vq->msg_signatures_state = 0; ++ have_state = 1; ++ } + + /* validate the ANSWER section */ + for(i=0; ian_numrrsets; i++) { ++ if(have_state && i <= vq->msg_signatures_index) ++ continue; + s = chase_reply->rrsets[i]; + /* Skip the CNAME following a (validated) DNAME. + * Because of the normalization routines in the iterator, +@@ -629,7 +661,7 @@ validate_msg_signatures(struct module_qstate* qstate, struct module_env* env, + + /* Verify the answer rrset */ + sec = val_verify_rrset_entry(env, ve, s, key_entry, &reason, +- &reason_bogus, LDNS_SECTION_ANSWER, qstate); ++ &reason_bogus, LDNS_SECTION_ANSWER, qstate, &verified); + /* If the (answer) rrset failed to validate, then this + * message is BAD. */ + if(sec != sec_status_secure) { +@@ -654,14 +686,33 @@ validate_msg_signatures(struct module_qstate* qstate, struct module_env* env, + ntohs(s->rk.type) == LDNS_RR_TYPE_DNAME) { + dname_seen = 1; + } ++ num_verifies += verified; ++ if(num_verifies > MAX_VALIDATE_AT_ONCE && ++ i+1 < (env->cfg->val_clean_additional? ++ chase_reply->an_numrrsets+chase_reply->ns_numrrsets: ++ chase_reply->rrset_count)) { ++ /* If the number of RRSIGs exceeds the maximum in ++ * one go, suspend. Only suspend if there is a next ++ * rrset to verify, i+1msg_signatures_state = 1; ++ vq->msg_signatures_index = i; ++ verbose(VERB_ALGO, "msg signature validation " ++ "suspended"); ++ return 0; ++ } + } + + /* validate the AUTHORITY section */ + for(i=chase_reply->an_numrrsets; ian_numrrsets+ + chase_reply->ns_numrrsets; i++) { ++ if(have_state && i <= vq->msg_signatures_index) ++ continue; + s = chase_reply->rrsets[i]; + sec = val_verify_rrset_entry(env, ve, s, key_entry, &reason, +- &reason_bogus, LDNS_SECTION_AUTHORITY, qstate); ++ &reason_bogus, LDNS_SECTION_AUTHORITY, qstate, ++ &verified); + /* If anything in the authority section fails to be secure, + * we have a bad message. */ + if(sec != sec_status_secure) { +@@ -675,6 +726,18 @@ validate_msg_signatures(struct module_qstate* qstate, struct module_env* env, + update_reason_bogus(chase_reply, reason_bogus); + return 0; + } ++ num_verifies += verified; ++ if(num_verifies > MAX_VALIDATE_AT_ONCE && ++ i+1 < (env->cfg->val_clean_additional? ++ chase_reply->an_numrrsets+chase_reply->ns_numrrsets: ++ chase_reply->rrset_count)) { ++ *suspend = 1; ++ vq->msg_signatures_state = 1; ++ vq->msg_signatures_index = i; ++ verbose(VERB_ALGO, "msg signature validation " ++ "suspended"); ++ return 0; ++ } + } + + /* If set, the validator should clean the additional section of +@@ -684,22 +747,103 @@ validate_msg_signatures(struct module_qstate* qstate, struct module_env* env, + /* attempt to validate the ADDITIONAL section rrsets */ + for(i=chase_reply->an_numrrsets+chase_reply->ns_numrrsets; + irrset_count; i++) { ++ if(have_state && i <= vq->msg_signatures_index) ++ continue; + s = chase_reply->rrsets[i]; + /* only validate rrs that have signatures with the key */ + /* leave others unchecked, those get removed later on too */ + val_find_rrset_signer(s, &sname, &slen); + ++ verified = 0; + if(sname && query_dname_compare(sname, key_entry->name)==0) + (void)val_verify_rrset_entry(env, ve, s, key_entry, +- &reason, NULL, LDNS_SECTION_ADDITIONAL, qstate); ++ &reason, NULL, LDNS_SECTION_ADDITIONAL, qstate, ++ &verified); + /* the additional section can fail to be secure, + * it is optional, check signature in case we need + * to clean the additional section later. */ ++ num_verifies += verified; ++ if(num_verifies > MAX_VALIDATE_AT_ONCE && ++ i+1 < chase_reply->rrset_count) { ++ *suspend = 1; ++ vq->msg_signatures_state = 1; ++ vq->msg_signatures_index = i; ++ verbose(VERB_ALGO, "msg signature validation " ++ "suspended"); ++ return 0; ++ } + } + + return 1; + } + ++void ++validate_suspend_timer_cb(void* arg) ++{ ++ struct module_qstate* qstate = (struct module_qstate*)arg; ++ verbose(VERB_ALGO, "validate_suspend timer, continue"); ++ mesh_run(qstate->env->mesh, qstate->mesh_info, module_event_pass, ++ NULL); ++} ++ ++/** Setup timer to continue validation of msg signatures later */ ++static int ++validate_suspend_setup_timer(struct module_qstate* qstate, ++ struct val_qstate* vq, int id, enum val_state resume_state) ++{ ++ struct timeval tv; ++ int usec, slack, base; ++ if(vq->suspend_count >= MAX_VALIDATION_SUSPENDS) { ++ verbose(VERB_ALGO, "validate_suspend timer: " ++ "reached MAX_VALIDATION_SUSPENDS (%d); error out", ++ MAX_VALIDATION_SUSPENDS); ++ errinf(qstate, "max validation suspends reached, " ++ "too many RRSIG validations"); ++ return 0; ++ } ++ verbose(VERB_ALGO, "validate_suspend timer, set for suspend"); ++ vq->state = resume_state; ++ qstate->ext_state[id] = module_wait_reply; ++ if(!vq->suspend_timer) { ++ vq->suspend_timer = comm_timer_create( ++ qstate->env->worker_base, ++ validate_suspend_timer_cb, qstate); ++ if(!vq->suspend_timer) { ++ log_err("validate_suspend_setup_timer: " ++ "out of memory for comm_timer_create"); ++ return 0; ++ } ++ } ++ /* The timer is activated later, after other events in the event ++ * loop have been processed. The query state can also be deleted, ++ * when the list is full and query states are dropped. */ ++ /* Extend wait time if there are a lot of queries or if this one ++ * is taking long, to keep around cpu time for ordinary queries. */ ++ usec = 50000; /* 50 msec */ ++ slack = 0; ++ if(qstate->env->mesh->all.count >= qstate->env->mesh->max_reply_states) ++ slack += 3; ++ else if(qstate->env->mesh->all.count >= qstate->env->mesh->max_reply_states/2) ++ slack += 2; ++ else if(qstate->env->mesh->all.count >= qstate->env->mesh->max_reply_states/4) ++ slack += 1; ++ if(vq->suspend_count > 3) ++ slack += 3; ++ else if(vq->suspend_count > 0) ++ slack += vq->suspend_count; ++ if(slack != 0 && slack <= 12 /* No numeric overflow. */) { ++ usec = usec << slack; ++ } ++ /* Spread such timeouts within 90%-100% of the original timer. */ ++ base = usec * 9/10; ++ usec = base + ub_random_max(qstate->env->rnd, usec-base); ++ tv.tv_usec = (usec % 1000000); ++ tv.tv_sec = (usec / 1000000); ++ vq->suspend_count ++; ++ comm_timer_set(vq->suspend_timer, &tv); ++ return 1; ++} ++ + /** + * Detect wrong truncated response (say from BIND 9.6.1 that is forwarding + * and saw the NS record without signatures from a referral). +@@ -798,11 +942,17 @@ remove_spurious_authority(struct reply_info* chase_reply, + * @param chase_reply: answer to that query to validate. + * @param kkey: the key entry, which is trusted, and which matches + * the signer of the answer. The key entry isgood(). ++ * @param qstate: query state for the region. ++ * @param vq: validator state for the nsec3 cache table. ++ * @param nsec3_calculations: current nsec3 hash calculations. ++ * @param suspend: returned true if the task takes too long and needs to ++ * suspend to continue the effort later. + */ + static void + validate_positive_response(struct module_env* env, struct val_env* ve, + struct query_info* qchase, struct reply_info* chase_reply, +- struct key_entry_key* kkey) ++ struct key_entry_key* kkey, struct module_qstate* qstate, ++ struct val_qstate* vq, int* nsec3_calculations, int* suspend) + { + uint8_t* wc = NULL; + size_t wl; +@@ -811,6 +961,7 @@ validate_positive_response(struct module_env* env, struct val_env* ve, + int nsec3s_seen = 0; + size_t i; + struct ub_packed_rrset_key* s; ++ *suspend = 0; + + /* validate the ANSWER section - this will be the answer itself */ + for(i=0; ian_numrrsets; i++) { +@@ -862,17 +1013,23 @@ validate_positive_response(struct module_env* env, struct val_env* ve, + /* If this was a positive wildcard response that we haven't already + * proven, and we have NSEC3 records, try to prove it using the NSEC3 + * records. */ +- if(wc != NULL && !wc_NSEC_ok && nsec3s_seen) { +- enum sec_status sec = nsec3_prove_wildcard(env, ve, ++ if(wc != NULL && !wc_NSEC_ok && nsec3s_seen && ++ nsec3_cache_table_init(&vq->nsec3_cache_table, qstate->region)) { ++ enum sec_status sec = nsec3_prove_wildcard(env, ve, + chase_reply->rrsets+chase_reply->an_numrrsets, +- chase_reply->ns_numrrsets, qchase, kkey, wc); ++ chase_reply->ns_numrrsets, qchase, kkey, wc, ++ &vq->nsec3_cache_table, nsec3_calculations); + if(sec == sec_status_insecure) { + verbose(VERB_ALGO, "Positive wildcard response is " + "insecure"); + chase_reply->security = sec_status_insecure; + return; +- } else if(sec == sec_status_secure) ++ } else if(sec == sec_status_secure) { + wc_NSEC_ok = 1; ++ } else if(sec == sec_status_unchecked) { ++ *suspend = 1; ++ return; ++ } + } + + /* If after all this, we still haven't proven the positive wildcard +@@ -904,11 +1061,17 @@ validate_positive_response(struct module_env* env, struct val_env* ve, + * @param chase_reply: answer to that query to validate. + * @param kkey: the key entry, which is trusted, and which matches + * the signer of the answer. The key entry isgood(). ++ * @param qstate: query state for the region. ++ * @param vq: validator state for the nsec3 cache table. ++ * @param nsec3_calculations: current nsec3 hash calculations. ++ * @param suspend: returned true if the task takes too long and needs to ++ * suspend to continue the effort later. + */ + static void + validate_nodata_response(struct module_env* env, struct val_env* ve, + struct query_info* qchase, struct reply_info* chase_reply, +- struct key_entry_key* kkey) ++ struct key_entry_key* kkey, struct module_qstate* qstate, ++ struct val_qstate* vq, int* nsec3_calculations, int* suspend) + { + /* Since we are here, there must be nothing in the ANSWER section to + * validate. */ +@@ -925,6 +1088,7 @@ validate_nodata_response(struct module_env* env, struct val_env* ve, + int nsec3s_seen = 0; /* nsec3s seen */ + struct ub_packed_rrset_key* s; + size_t i; ++ *suspend = 0; + + for(i=chase_reply->an_numrrsets; ian_numrrsets+ + chase_reply->ns_numrrsets; i++) { +@@ -963,16 +1127,23 @@ validate_nodata_response(struct module_env* env, struct val_env* ve, + } + } + +- if(!has_valid_nsec && nsec3s_seen) { ++ if(!has_valid_nsec && nsec3s_seen && ++ nsec3_cache_table_init(&vq->nsec3_cache_table, qstate->region)) { + enum sec_status sec = nsec3_prove_nodata(env, ve, + chase_reply->rrsets+chase_reply->an_numrrsets, +- chase_reply->ns_numrrsets, qchase, kkey); ++ chase_reply->ns_numrrsets, qchase, kkey, ++ &vq->nsec3_cache_table, nsec3_calculations); + if(sec == sec_status_insecure) { + verbose(VERB_ALGO, "NODATA response is insecure"); + chase_reply->security = sec_status_insecure; + return; +- } else if(sec == sec_status_secure) ++ } else if(sec == sec_status_secure) { + has_valid_nsec = 1; ++ } else if(sec == sec_status_unchecked) { ++ /* check is incomplete; suspend */ ++ *suspend = 1; ++ return; ++ } + } + + if(!has_valid_nsec) { +@@ -1004,11 +1175,18 @@ validate_nodata_response(struct module_env* env, struct val_env* ve, + * @param kkey: the key entry, which is trusted, and which matches + * the signer of the answer. The key entry isgood(). + * @param rcode: adjusted RCODE, in case of RCODE/proof mismatch leniency. ++ * @param qstate: query state for the region. ++ * @param vq: validator state for the nsec3 cache table. ++ * @param nsec3_calculations: current nsec3 hash calculations. ++ * @param suspend: returned true if the task takes too long and needs to ++ * suspend to continue the effort later. + */ + static void + validate_nameerror_response(struct module_env* env, struct val_env* ve, + struct query_info* qchase, struct reply_info* chase_reply, +- struct key_entry_key* kkey, int* rcode) ++ struct key_entry_key* kkey, int* rcode, ++ struct module_qstate* qstate, struct val_qstate* vq, ++ int* nsec3_calculations, int* suspend) + { + int has_valid_nsec = 0; + int has_valid_wnsec = 0; +@@ -1018,6 +1196,7 @@ validate_nameerror_response(struct module_env* env, struct val_env* ve, + uint8_t* ce; + int ce_labs = 0; + int prev_ce_labs = 0; ++ *suspend = 0; + + for(i=chase_reply->an_numrrsets; ian_numrrsets+ + chase_reply->ns_numrrsets; i++) { +@@ -1047,13 +1226,18 @@ validate_nameerror_response(struct module_env* env, struct val_env* ve, + nsec3s_seen = 1; + } + +- if((!has_valid_nsec || !has_valid_wnsec) && nsec3s_seen) { ++ if((!has_valid_nsec || !has_valid_wnsec) && nsec3s_seen && ++ nsec3_cache_table_init(&vq->nsec3_cache_table, qstate->region)) { + /* use NSEC3 proof, both answer and auth rrsets, in case + * NSEC3s end up in the answer (due to qtype=NSEC3 or so) */ + chase_reply->security = nsec3_prove_nameerror(env, ve, + chase_reply->rrsets, chase_reply->an_numrrsets+ +- chase_reply->ns_numrrsets, qchase, kkey); +- if(chase_reply->security != sec_status_secure) { ++ chase_reply->ns_numrrsets, qchase, kkey, ++ &vq->nsec3_cache_table, nsec3_calculations); ++ if(chase_reply->security == sec_status_unchecked) { ++ *suspend = 1; ++ return; ++ } else if(chase_reply->security != sec_status_secure) { + verbose(VERB_QUERY, "NameError response failed nsec, " + "nsec3 proof was %s", sec_status_to_string( + chase_reply->security)); +@@ -1065,26 +1249,34 @@ validate_nameerror_response(struct module_env* env, struct val_env* ve, + + /* If the message fails to prove either condition, it is bogus. */ + if(!has_valid_nsec) { ++ validate_nodata_response(env, ve, qchase, chase_reply, kkey, ++ qstate, vq, nsec3_calculations, suspend); ++ if(*suspend) return; + verbose(VERB_QUERY, "NameError response has failed to prove: " + "qname does not exist"); +- chase_reply->security = sec_status_bogus; +- update_reason_bogus(chase_reply, LDNS_EDE_DNSSEC_BOGUS); + /* Be lenient with RCODE in NSEC NameError responses */ +- validate_nodata_response(env, ve, qchase, chase_reply, kkey); +- if (chase_reply->security == sec_status_secure) ++ if(chase_reply->security == sec_status_secure) { + *rcode = LDNS_RCODE_NOERROR; ++ } else { ++ chase_reply->security = sec_status_bogus; ++ update_reason_bogus(chase_reply, LDNS_EDE_DNSSEC_BOGUS); ++ } + return; + } + + if(!has_valid_wnsec) { ++ validate_nodata_response(env, ve, qchase, chase_reply, kkey, ++ qstate, vq, nsec3_calculations, suspend); ++ if(*suspend) return; + verbose(VERB_QUERY, "NameError response has failed to prove: " + "covering wildcard does not exist"); +- chase_reply->security = sec_status_bogus; +- update_reason_bogus(chase_reply, LDNS_EDE_DNSSEC_BOGUS); + /* Be lenient with RCODE in NSEC NameError responses */ +- validate_nodata_response(env, ve, qchase, chase_reply, kkey); +- if (chase_reply->security == sec_status_secure) ++ if (chase_reply->security == sec_status_secure) { + *rcode = LDNS_RCODE_NOERROR; ++ } else { ++ chase_reply->security = sec_status_bogus; ++ update_reason_bogus(chase_reply, LDNS_EDE_DNSSEC_BOGUS); ++ } + return; + } + +@@ -1144,11 +1336,17 @@ validate_referral_response(struct reply_info* chase_reply) + * @param chase_reply: answer to that query to validate. + * @param kkey: the key entry, which is trusted, and which matches + * the signer of the answer. The key entry isgood(). ++ * @param qstate: query state for the region. ++ * @param vq: validator state for the nsec3 cache table. ++ * @param nsec3_calculations: current nsec3 hash calculations. ++ * @param suspend: returned true if the task takes too long and needs to ++ * suspend to continue the effort later. + */ + static void + validate_any_response(struct module_env* env, struct val_env* ve, + struct query_info* qchase, struct reply_info* chase_reply, +- struct key_entry_key* kkey) ++ struct key_entry_key* kkey, struct module_qstate* qstate, ++ struct val_qstate* vq, int* nsec3_calculations, int* suspend) + { + /* all answer and auth rrsets already verified */ + /* but check if a wildcard response is given, then check NSEC/NSEC3 +@@ -1159,6 +1357,7 @@ validate_any_response(struct module_env* env, struct val_env* ve, + int nsec3s_seen = 0; + size_t i; + struct ub_packed_rrset_key* s; ++ *suspend = 0; + + if(qchase->qtype != LDNS_RR_TYPE_ANY) { + log_err("internal error: ANY validation called for non-ANY"); +@@ -1213,19 +1412,25 @@ validate_any_response(struct module_env* env, struct val_env* ve, + /* If this was a positive wildcard response that we haven't already + * proven, and we have NSEC3 records, try to prove it using the NSEC3 + * records. */ +- if(wc != NULL && !wc_NSEC_ok && nsec3s_seen) { ++ if(wc != NULL && !wc_NSEC_ok && nsec3s_seen && ++ nsec3_cache_table_init(&vq->nsec3_cache_table, qstate->region)) { + /* look both in answer and auth section for NSEC3s */ +- enum sec_status sec = nsec3_prove_wildcard(env, ve, ++ enum sec_status sec = nsec3_prove_wildcard(env, ve, + chase_reply->rrsets, +- chase_reply->an_numrrsets+chase_reply->ns_numrrsets, +- qchase, kkey, wc); ++ chase_reply->an_numrrsets+chase_reply->ns_numrrsets, ++ qchase, kkey, wc, &vq->nsec3_cache_table, ++ nsec3_calculations); + if(sec == sec_status_insecure) { + verbose(VERB_ALGO, "Positive ANY wildcard response is " + "insecure"); + chase_reply->security = sec_status_insecure; + return; +- } else if(sec == sec_status_secure) ++ } else if(sec == sec_status_secure) { + wc_NSEC_ok = 1; ++ } else if(sec == sec_status_unchecked) { ++ *suspend = 1; ++ return; ++ } + } + + /* If after all this, we still haven't proven the positive wildcard +@@ -1258,11 +1463,17 @@ validate_any_response(struct module_env* env, struct val_env* ve, + * @param chase_reply: answer to that query to validate. + * @param kkey: the key entry, which is trusted, and which matches + * the signer of the answer. The key entry isgood(). ++ * @param qstate: query state for the region. ++ * @param vq: validator state for the nsec3 cache table. ++ * @param nsec3_calculations: current nsec3 hash calculations. ++ * @param suspend: returned true if the task takes too long and needs to ++ * suspend to continue the effort later. + */ + static void + validate_cname_response(struct module_env* env, struct val_env* ve, + struct query_info* qchase, struct reply_info* chase_reply, +- struct key_entry_key* kkey) ++ struct key_entry_key* kkey, struct module_qstate* qstate, ++ struct val_qstate* vq, int* nsec3_calculations, int* suspend) + { + uint8_t* wc = NULL; + size_t wl; +@@ -1270,6 +1481,7 @@ validate_cname_response(struct module_env* env, struct val_env* ve, + int nsec3s_seen = 0; + size_t i; + struct ub_packed_rrset_key* s; ++ *suspend = 0; + + /* validate the ANSWER section - this will be the CNAME (+DNAME) */ + for(i=0; ian_numrrsets; i++) { +@@ -1334,17 +1546,23 @@ validate_cname_response(struct module_env* env, struct val_env* ve, + /* If this was a positive wildcard response that we haven't already + * proven, and we have NSEC3 records, try to prove it using the NSEC3 + * records. */ +- if(wc != NULL && !wc_NSEC_ok && nsec3s_seen) { +- enum sec_status sec = nsec3_prove_wildcard(env, ve, ++ if(wc != NULL && !wc_NSEC_ok && nsec3s_seen && ++ nsec3_cache_table_init(&vq->nsec3_cache_table, qstate->region)) { ++ enum sec_status sec = nsec3_prove_wildcard(env, ve, + chase_reply->rrsets+chase_reply->an_numrrsets, +- chase_reply->ns_numrrsets, qchase, kkey, wc); ++ chase_reply->ns_numrrsets, qchase, kkey, wc, ++ &vq->nsec3_cache_table, nsec3_calculations); + if(sec == sec_status_insecure) { + verbose(VERB_ALGO, "wildcard CNAME response is " + "insecure"); + chase_reply->security = sec_status_insecure; + return; +- } else if(sec == sec_status_secure) ++ } else if(sec == sec_status_secure) { + wc_NSEC_ok = 1; ++ } else if(sec == sec_status_unchecked) { ++ *suspend = 1; ++ return; ++ } + } + + /* If after all this, we still haven't proven the positive wildcard +@@ -1375,11 +1593,17 @@ validate_cname_response(struct module_env* env, struct val_env* ve, + * @param chase_reply: answer to that query to validate. + * @param kkey: the key entry, which is trusted, and which matches + * the signer of the answer. The key entry isgood(). ++ * @param qstate: query state for the region. ++ * @param vq: validator state for the nsec3 cache table. ++ * @param nsec3_calculations: current nsec3 hash calculations. ++ * @param suspend: returned true if the task takes too long and needs to ++ * suspend to continue the effort later. + */ + static void + validate_cname_noanswer_response(struct module_env* env, struct val_env* ve, + struct query_info* qchase, struct reply_info* chase_reply, +- struct key_entry_key* kkey) ++ struct key_entry_key* kkey, struct module_qstate* qstate, ++ struct val_qstate* vq, int* nsec3_calculations, int* suspend) + { + int nodata_valid_nsec = 0; /* If true, then NODATA has been proven.*/ + uint8_t* ce = NULL; /* for wildcard nodata responses. This is the +@@ -1393,6 +1617,7 @@ validate_cname_noanswer_response(struct module_env* env, struct val_env* ve, + uint8_t* nsec_ce; /* Used to find the NSEC with the longest ce */ + int ce_labs = 0; + int prev_ce_labs = 0; ++ *suspend = 0; + + /* the AUTHORITY section */ + for(i=chase_reply->an_numrrsets; ian_numrrsets+ +@@ -1458,11 +1683,13 @@ validate_cname_noanswer_response(struct module_env* env, struct val_env* ve, + update_reason_bogus(chase_reply, LDNS_EDE_DNSSEC_BOGUS); + return; + } +- if(!nodata_valid_nsec && !nxdomain_valid_nsec && nsec3s_seen) { ++ if(!nodata_valid_nsec && !nxdomain_valid_nsec && nsec3s_seen && ++ nsec3_cache_table_init(&vq->nsec3_cache_table, qstate->region)) { + int nodata; + enum sec_status sec = nsec3_prove_nxornodata(env, ve, + chase_reply->rrsets+chase_reply->an_numrrsets, +- chase_reply->ns_numrrsets, qchase, kkey, &nodata); ++ chase_reply->ns_numrrsets, qchase, kkey, &nodata, ++ &vq->nsec3_cache_table, nsec3_calculations); + if(sec == sec_status_insecure) { + verbose(VERB_ALGO, "CNAMEchain to noanswer response " + "is insecure"); +@@ -1472,6 +1699,9 @@ validate_cname_noanswer_response(struct module_env* env, struct val_env* ve, + if(nodata) + nodata_valid_nsec = 1; + else nxdomain_valid_nsec = 1; ++ } else if(sec == sec_status_unchecked) { ++ *suspend = 1; ++ return; + } + } + +@@ -1822,13 +2052,37 @@ processFindKey(struct module_qstate* qstate, struct val_qstate* vq, int id) + * Uses negative cache for NSEC3 lookup of DS responses. */ + /* only if cache not blacklisted, of course */ + struct dns_msg* msg; +- if(!qstate->blacklist && !vq->chain_blacklist && ++ int suspend; ++ if(vq->sub_ds_msg) { ++ /* We have a suspended DS reply from a sub-query; ++ * process it. */ ++ verbose(VERB_ALGO, "Process suspended sub DS response"); ++ msg = vq->sub_ds_msg; ++ process_ds_response(qstate, vq, id, LDNS_RCODE_NOERROR, ++ msg, &msg->qinfo, NULL, &suspend); ++ if(suspend) { ++ /* we'll come back here later to continue */ ++ if(!validate_suspend_setup_timer(qstate, vq, ++ id, VAL_FINDKEY_STATE)) ++ return val_error(qstate, id); ++ return 0; ++ } ++ vq->sub_ds_msg = NULL; ++ return 1; /* continue processing ds-response results */ ++ } else if(!qstate->blacklist && !vq->chain_blacklist && + (msg=val_find_DS(qstate->env, target_key_name, + target_key_len, vq->qchase.qclass, qstate->region, + vq->key_entry->name)) ) { + verbose(VERB_ALGO, "Process cached DS response"); + process_ds_response(qstate, vq, id, LDNS_RCODE_NOERROR, +- msg, &msg->qinfo, NULL); ++ msg, &msg->qinfo, NULL, &suspend); ++ if(suspend) { ++ /* we'll come back here later to continue */ ++ if(!validate_suspend_setup_timer(qstate, vq, ++ id, VAL_FINDKEY_STATE)) ++ return val_error(qstate, id); ++ return 0; ++ } + return 1; /* continue processing ds-response results */ + } + if(!generate_request(qstate, id, target_key_name, +@@ -1871,7 +2125,7 @@ processValidate(struct module_qstate* qstate, struct val_qstate* vq, + struct val_env* ve, int id) + { + enum val_classification subtype; +- int rcode; ++ int rcode, suspend, nsec3_calculations = 0; + + if(!vq->key_entry) { + verbose(VERB_ALGO, "validate: no key entry, failed"); +@@ -1926,8 +2180,14 @@ processValidate(struct module_qstate* qstate, struct val_qstate* vq, + + /* check signatures in the message; + * answer and authority must be valid, additional is only checked. */ +- if(!validate_msg_signatures(qstate, qstate->env, ve, &vq->qchase, +- vq->chase_reply, vq->key_entry)) { ++ if(!validate_msg_signatures(qstate, vq, qstate->env, ve, &vq->qchase, ++ vq->chase_reply, vq->key_entry, &suspend)) { ++ if(suspend) { ++ if(!validate_suspend_setup_timer(qstate, vq, ++ id, VAL_VALIDATE_STATE)) ++ return val_error(qstate, id); ++ return 0; ++ } + /* workaround bad recursor out there that truncates (even + * with EDNS4k) to 512 by removing RRSIG from auth section + * for positive replies*/ +@@ -1956,7 +2216,14 @@ processValidate(struct module_qstate* qstate, struct val_qstate* vq, + case VAL_CLASS_POSITIVE: + verbose(VERB_ALGO, "Validating a positive response"); + validate_positive_response(qstate->env, ve, +- &vq->qchase, vq->chase_reply, vq->key_entry); ++ &vq->qchase, vq->chase_reply, vq->key_entry, ++ qstate, vq, &nsec3_calculations, &suspend); ++ if(suspend) { ++ if(!validate_suspend_setup_timer(qstate, ++ vq, id, VAL_VALIDATE_STATE)) ++ return val_error(qstate, id); ++ return 0; ++ } + verbose(VERB_DETAIL, "validate(positive): %s", + sec_status_to_string( + vq->chase_reply->security)); +@@ -1965,7 +2232,14 @@ processValidate(struct module_qstate* qstate, struct val_qstate* vq, + case VAL_CLASS_NODATA: + verbose(VERB_ALGO, "Validating a nodata response"); + validate_nodata_response(qstate->env, ve, +- &vq->qchase, vq->chase_reply, vq->key_entry); ++ &vq->qchase, vq->chase_reply, vq->key_entry, ++ qstate, vq, &nsec3_calculations, &suspend); ++ if(suspend) { ++ if(!validate_suspend_setup_timer(qstate, ++ vq, id, VAL_VALIDATE_STATE)) ++ return val_error(qstate, id); ++ return 0; ++ } + verbose(VERB_DETAIL, "validate(nodata): %s", + sec_status_to_string( + vq->chase_reply->security)); +@@ -1975,7 +2249,14 @@ processValidate(struct module_qstate* qstate, struct val_qstate* vq, + rcode = (int)FLAGS_GET_RCODE(vq->orig_msg->rep->flags); + verbose(VERB_ALGO, "Validating a nxdomain response"); + validate_nameerror_response(qstate->env, ve, +- &vq->qchase, vq->chase_reply, vq->key_entry, &rcode); ++ &vq->qchase, vq->chase_reply, vq->key_entry, &rcode, ++ qstate, vq, &nsec3_calculations, &suspend); ++ if(suspend) { ++ if(!validate_suspend_setup_timer(qstate, ++ vq, id, VAL_VALIDATE_STATE)) ++ return val_error(qstate, id); ++ return 0; ++ } + verbose(VERB_DETAIL, "validate(nxdomain): %s", + sec_status_to_string( + vq->chase_reply->security)); +@@ -1986,7 +2267,14 @@ processValidate(struct module_qstate* qstate, struct val_qstate* vq, + case VAL_CLASS_CNAME: + verbose(VERB_ALGO, "Validating a cname response"); + validate_cname_response(qstate->env, ve, +- &vq->qchase, vq->chase_reply, vq->key_entry); ++ &vq->qchase, vq->chase_reply, vq->key_entry, ++ qstate, vq, &nsec3_calculations, &suspend); ++ if(suspend) { ++ if(!validate_suspend_setup_timer(qstate, ++ vq, id, VAL_VALIDATE_STATE)) ++ return val_error(qstate, id); ++ return 0; ++ } + verbose(VERB_DETAIL, "validate(cname): %s", + sec_status_to_string( + vq->chase_reply->security)); +@@ -1996,7 +2284,14 @@ processValidate(struct module_qstate* qstate, struct val_qstate* vq, + verbose(VERB_ALGO, "Validating a cname noanswer " + "response"); + validate_cname_noanswer_response(qstate->env, ve, +- &vq->qchase, vq->chase_reply, vq->key_entry); ++ &vq->qchase, vq->chase_reply, vq->key_entry, ++ qstate, vq, &nsec3_calculations, &suspend); ++ if(suspend) { ++ if(!validate_suspend_setup_timer(qstate, ++ vq, id, VAL_VALIDATE_STATE)) ++ return val_error(qstate, id); ++ return 0; ++ } + verbose(VERB_DETAIL, "validate(cname_noanswer): %s", + sec_status_to_string( + vq->chase_reply->security)); +@@ -2013,8 +2308,15 @@ processValidate(struct module_qstate* qstate, struct val_qstate* vq, + case VAL_CLASS_ANY: + verbose(VERB_ALGO, "Validating a positive ANY " + "response"); +- validate_any_response(qstate->env, ve, &vq->qchase, +- vq->chase_reply, vq->key_entry); ++ validate_any_response(qstate->env, ve, &vq->qchase, ++ vq->chase_reply, vq->key_entry, qstate, vq, ++ &nsec3_calculations, &suspend); ++ if(suspend) { ++ if(!validate_suspend_setup_timer(qstate, ++ vq, id, VAL_VALIDATE_STATE)) ++ return val_error(qstate, id); ++ return 0; ++ } + verbose(VERB_DETAIL, "validate(positive_any): %s", + sec_status_to_string( + vq->chase_reply->security)); +@@ -2123,16 +2425,13 @@ processFinished(struct module_qstate* qstate, struct val_qstate* vq, + if(vq->orig_msg->rep->security == sec_status_bogus) { + /* see if we can try again to fetch data */ + if(vq->restart_count < ve->max_restart) { +- int restart_count = vq->restart_count+1; + verbose(VERB_ALGO, "validation failed, " + "blacklist and retry to fetch data"); + val_blacklist(&qstate->blacklist, qstate->region, + qstate->reply_origin, 0); + qstate->reply_origin = NULL; + qstate->errinf = NULL; +- memset(vq, 0, sizeof(*vq)); +- vq->restart_count = restart_count; +- vq->state = VAL_INIT_STATE; ++ val_restart(vq); + verbose(VERB_ALGO, "pass back to next module"); + qstate->ext_state[id] = module_restart_next; + return 0; +@@ -2440,7 +2739,10 @@ primeResponseToKE(struct ub_packed_rrset_key* dnskey_rrset, + * DS response indicated an end to secure space, is_good if the DS + * validated. It returns ke=NULL if the DS response indicated that the + * request wasn't a delegation point. +- * @return 0 on servfail error (malloc failure). ++ * @return ++ * 0 on success, ++ * 1 on servfail error (malloc failure), ++ * 2 on NSEC3 suspend. + */ + static int + ds_response_to_ke(struct module_qstate* qstate, struct val_qstate* vq, +@@ -2451,6 +2753,7 @@ ds_response_to_ke(struct module_qstate* qstate, struct val_qstate* vq, + char* reason = NULL; + sldns_ede_code reason_bogus = LDNS_EDE_DNSSEC_BOGUS; + enum val_classification subtype; ++ int verified; + if(rcode != LDNS_RCODE_NOERROR) { + char rc[16]; + rc[0]=0; +@@ -2479,7 +2782,7 @@ ds_response_to_ke(struct module_qstate* qstate, struct val_qstate* vq, + /* Verify only returns BOGUS or SECURE. If the rrset is + * bogus, then we are done. */ + sec = val_verify_rrset_entry(qstate->env, ve, ds, +- vq->key_entry, &reason, &reason_bogus, LDNS_SECTION_ANSWER, qstate); ++ vq->key_entry, &reason, &reason_bogus, LDNS_SECTION_ANSWER, qstate, &verified); + if(sec != sec_status_secure) { + verbose(VERB_DETAIL, "DS rrset in DS response did " + "not verify"); +@@ -2499,7 +2802,7 @@ ds_response_to_ke(struct module_qstate* qstate, struct val_qstate* vq, + *ke = key_entry_create_null(qstate->region, + qinfo->qname, qinfo->qname_len, qinfo->qclass, + ub_packed_rrset_ttl(ds), *qstate->env->now); +- return (*ke) != NULL; ++ return (*ke) == NULL; + } + + /* Otherwise, we return the positive response. */ +@@ -2507,7 +2810,7 @@ ds_response_to_ke(struct module_qstate* qstate, struct val_qstate* vq, + *ke = key_entry_create_rrset(qstate->region, + qinfo->qname, qinfo->qname_len, qinfo->qclass, ds, + NULL, *qstate->env->now); +- return (*ke) != NULL; ++ return (*ke) == NULL; + } else if(subtype == VAL_CLASS_NODATA || + subtype == VAL_CLASS_NAMEERROR) { + /* NODATA means that the qname exists, but that there was +@@ -2539,12 +2842,12 @@ ds_response_to_ke(struct module_qstate* qstate, struct val_qstate* vq, + qinfo->qname, qinfo->qname_len, + qinfo->qclass, proof_ttl, + *qstate->env->now); +- return (*ke) != NULL; ++ return (*ke) == NULL; + case sec_status_insecure: + verbose(VERB_DETAIL, "NSEC RRset for the " + "referral proved not a delegation point"); + *ke = NULL; +- return 1; ++ return 0; + case sec_status_bogus: + verbose(VERB_DETAIL, "NSEC RRset for the " + "referral did not prove no DS."); +@@ -2556,10 +2859,17 @@ ds_response_to_ke(struct module_qstate* qstate, struct val_qstate* vq, + break; + } + ++ if(!nsec3_cache_table_init(&vq->nsec3_cache_table, qstate->region)) { ++ log_err("malloc failure in ds_response_to_ke for " ++ "NSEC3 cache"); ++ reason = "malloc failure"; ++ errinf_ede(qstate, reason, 0); ++ goto return_bogus; ++ } + sec = nsec3_prove_nods(qstate->env, ve, + msg->rep->rrsets + msg->rep->an_numrrsets, + msg->rep->ns_numrrsets, qinfo, vq->key_entry, &reason, +- &reason_bogus, qstate); ++ &reason_bogus, qstate, &vq->nsec3_cache_table); + switch(sec) { + case sec_status_insecure: + /* case insecure also continues to unsigned +@@ -2572,18 +2882,19 @@ ds_response_to_ke(struct module_qstate* qstate, struct val_qstate* vq, + qinfo->qname, qinfo->qname_len, + qinfo->qclass, proof_ttl, + *qstate->env->now); +- return (*ke) != NULL; ++ return (*ke) == NULL; + case sec_status_indeterminate: + verbose(VERB_DETAIL, "NSEC3s for the " + "referral proved no delegation"); + *ke = NULL; +- return 1; ++ return 0; + case sec_status_bogus: + verbose(VERB_DETAIL, "NSEC3s for the " + "referral did not prove no DS."); + errinf_ede(qstate, reason, reason_bogus); + goto return_bogus; + case sec_status_unchecked: ++ return 2; + default: + /* NSEC3 proof did not work */ + break; +@@ -2620,13 +2931,14 @@ ds_response_to_ke(struct module_qstate* qstate, struct val_qstate* vq, + goto return_bogus; + } + sec = val_verify_rrset_entry(qstate->env, ve, cname, +- vq->key_entry, &reason, NULL, LDNS_SECTION_ANSWER, qstate); ++ vq->key_entry, &reason, NULL, LDNS_SECTION_ANSWER, ++ qstate, &verified); + if(sec == sec_status_secure) { + verbose(VERB_ALGO, "CNAME validated, " + "proof that DS does not exist"); + /* and that it is not a referral point */ + *ke = NULL; +- return 1; ++ return 0; + } + errinf(qstate, "CNAME in DS response was not secure."); + errinf(qstate, reason); +@@ -2649,7 +2961,7 @@ return_bogus: + *ke = key_entry_create_bad(qstate->region, qinfo->qname, + qinfo->qname_len, qinfo->qclass, + BOGUS_KEY_TTL, *qstate->env->now); +- return (*ke) != NULL; ++ return (*ke) == NULL; + } + + /** +@@ -2670,17 +2982,31 @@ return_bogus: + static void + process_ds_response(struct module_qstate* qstate, struct val_qstate* vq, + int id, int rcode, struct dns_msg* msg, struct query_info* qinfo, +- struct sock_list* origin) ++ struct sock_list* origin, int* suspend) + { + struct val_env* ve = (struct val_env*)qstate->env->modinfo[id]; + struct key_entry_key* dske = NULL; + uint8_t* olds = vq->empty_DS_name; ++ int ret; ++ *suspend = 0; + vq->empty_DS_name = NULL; +- if(!ds_response_to_ke(qstate, vq, id, rcode, msg, qinfo, &dske)) { ++ ret = ds_response_to_ke(qstate, vq, id, rcode, msg, qinfo, &dske); ++ if(ret != 0) { ++ switch(ret) { ++ case 1: + log_err("malloc failure in process_ds_response"); + vq->key_entry = NULL; /* make it error */ + vq->state = VAL_VALIDATE_STATE; + return; ++ case 2: ++ *suspend = 1; ++ return; ++ default: ++ log_err("unhandled error value for ds_response_to_ke"); ++ vq->key_entry = NULL; /* make it error */ ++ vq->state = VAL_VALIDATE_STATE; ++ return; ++ } + } + if(dske == NULL) { + vq->empty_DS_name = regional_alloc_init(qstate->region, +@@ -2927,9 +3253,26 @@ val_inform_super(struct module_qstate* qstate, int id, + return; + } + if(qstate->qinfo.qtype == LDNS_RR_TYPE_DS) { ++ int suspend; + process_ds_response(super, vq, id, qstate->return_rcode, +- qstate->return_msg, &qstate->qinfo, +- qstate->reply_origin); ++ qstate->return_msg, &qstate->qinfo, ++ qstate->reply_origin, &suspend); ++ /* If NSEC3 was needed during validation, NULL the NSEC3 cache; ++ * it will be re-initiated if needed later on. ++ * Validation (and the cache table) are happening/allocated in ++ * the super qstate whilst the RRs are allocated (and pointed ++ * to) in this sub qstate. */ ++ if(vq->nsec3_cache_table.ct) { ++ vq->nsec3_cache_table.ct = NULL; ++ } ++ if(suspend) { ++ /* deep copy the return_msg to vq->sub_ds_msg; it will ++ * be resumed later in the super state with the caveat ++ * that the initial calculations will be re-caclulated ++ * and re-suspended there before continuing. */ ++ vq->sub_ds_msg = dns_msg_deepcopy_region( ++ qstate->return_msg, super->region); ++ } + return; + } else if(qstate->qinfo.qtype == LDNS_RR_TYPE_DNSKEY) { + process_dnskey_response(super, vq, id, qstate->return_rcode, +@@ -2943,8 +3286,15 @@ val_inform_super(struct module_qstate* qstate, int id, + void + val_clear(struct module_qstate* qstate, int id) + { ++ struct val_qstate* vq; + if(!qstate) + return; ++ vq = (struct val_qstate*)qstate->minfo[id]; ++ if(vq) { ++ if(vq->suspend_timer) { ++ comm_timer_delete(vq->suspend_timer); ++ } ++ } + /* everything is allocated in the region, so assign NULL */ + qstate->minfo[id] = NULL; + } +diff --git a/validator/validator.h b/validator/validator.h +index 694e4c89..72f44b16 100644 +--- a/validator/validator.h ++++ b/validator/validator.h +@@ -45,11 +45,13 @@ + #include "util/module.h" + #include "util/data/msgreply.h" + #include "validator/val_utils.h" ++#include "validator/val_nsec3.h" + struct val_anchors; + struct key_cache; + struct key_entry_key; + struct val_neg_cache; + struct config_strlist; ++struct comm_timer; + + /** + * This is the TTL to use when a trust anchor fails to prime. A trust anchor +@@ -215,6 +217,19 @@ struct val_qstate { + + /** true if this state is waiting to prime a trust anchor */ + int wait_prime_ta; ++ ++ /** State to continue with RRSIG validation in a message later */ ++ int msg_signatures_state; ++ /** The rrset index for the msg signatures to continue from */ ++ size_t msg_signatures_index; ++ /** Cache table for NSEC3 hashes */ ++ struct nsec3_cache_table nsec3_cache_table; ++ /** DS message from sub if it got suspended from NSEC3 calculations */ ++ struct dns_msg* sub_ds_msg; ++ /** The timer to resume processing msg signatures */ ++ struct comm_timer* suspend_timer; ++ /** Number of suspends */ ++ int suspend_count; + }; + + /** +@@ -262,4 +277,7 @@ void val_clear(struct module_qstate* qstate, int id); + */ + size_t val_get_mem(struct module_env* env, int id); + ++/** Timer callback for msg signatures continue timer */ ++void validate_suspend_timer_cb(void* arg); ++ + #endif /* VALIDATOR_VALIDATOR_H */ diff --git a/unbound.spec b/unbound.spec index e2ebea355a071524e601fb3e216df2e6e7580734..fa57e17059e618bf6ae452e899d57672e1958b9f 100644 --- a/unbound.spec +++ b/unbound.spec @@ -2,7 +2,7 @@ Name: unbound Version: 1.17.1 -Release: 1 +Release: 2 Summary: Unbound is a validating, recursive, caching DNS resolver License: BSD-3-Clause Url: https://nlnetlabs.nl/projects/unbound/about/ @@ -22,6 +22,7 @@ Source12: unbound-anchor.timer Source13: unbound-anchor.service Patch1: unbound-remove-buildin-key.patch +Patch2: backport-CVE-2023-50387_CVE-2023-50868.patch BuildRequires: make flex swig pkgconfig systemd BuildRequires: libevent-devel expat-devel openssl-devel python3-devel @@ -234,6 +235,12 @@ popd %{_mandir}/man* %changelog +* Fri Feb 23 2024 gaihuiying - 1.17.1-2 +- Type:bugfix +- CVE:NA +- SUG:NA +- DESC:fix CVE-2023-50387 CVE-2023-50868 + * Tue Mar 07 2023 gaihuiying - 1.17.1-1 - Type:requirement - CVE:NA