From 13b6717f1716810523ffb60f971a6c9c5e8ce127 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Wed, 16 Sep 2026 09:30:25 +0200 Subject: [PATCH] - Fix CVE-2026-85501, Retrap: Novel Vulnerabilities to launch Algorithmic Complexity Attacks on DNSSEC. Thanks to Zuyao Xu and Xiang Li from Nankai University, AOSP Lab for the report. In addition, thanks to Qifan Zhang from Palo Alto Networks for a complimentary report. --- daemon/remote.c | 2 + daemon/worker.c | 2 +- doc/example.conf.in | 8 +- doc/unbound.conf.rst | 22 +++- iterator/iter_scrub.c | 11 ++ services/authzone.c | 9 +- services/cache/dns.c | 2 +- services/mesh.c | 2 +- testcode/unitverify.c | 2 +- testdata/03-testbound.tdir/03-testbound.test | 9 ++ testdata/val_refer_unsignadd.rpl | 1 + testdata/val_referglue.rpl | 1 + util/config_file.c | 8 +- util/config_file.h | 4 + util/configlexer.lex | 2 + util/configparser.y | 22 +++- util/data/msgreply.c | 11 ++ util/data/msgreply.h | 3 + validator/autotrust.c | 22 ++-- validator/autotrust.h | 4 +- validator/val_nsec.c | 11 +- validator/val_nsec.h | 5 +- validator/val_nsec3.c | 10 +- validator/val_nsec3.h | 5 +- validator/val_sigcrypt.c | 73 ++++++++++++- validator/val_sigcrypt.h | 12 +- validator/val_utils.c | 105 ++++++++++++++---- validator/val_utils.h | 21 +++- validator/validator.c | 109 +++++++++++++------ validator/validator.h | 13 +++ 30 files changed, 411 insertions(+), 100 deletions(-) diff --git a/daemon/remote.c b/daemon/remote.c index 8e6ba19b1..23e62d2ec 100644 --- a/daemon/remote.c +++ b/daemon/remote.c @@ -6590,6 +6590,8 @@ fr_atomic_copy_cfg(struct config_file* oldcfg, struct config_file* cfg, COPY_VAR_ptr(ipset_name_v6); #endif COPY_VAR_int(ede); + COPY_VAR_int(val_validation_attempts); + COPY_VAR_int(val_hash_attempts); COPY_VAR_int(iter_scrub_ns); COPY_VAR_int(iter_scrub_cname); COPY_VAR_int(iter_scrub_rrsig); diff --git a/daemon/worker.c b/daemon/worker.c index 5cc586442..8cfc776c6 100644 --- a/daemon/worker.c +++ b/daemon/worker.c @@ -863,7 +863,7 @@ answer_from_cache(struct worker* worker, struct query_info* qinfo, "validation"); goto bail_out; /* need to validate cache entry first */ } else if(rep->security == sec_status_secure) { - if(reply_all_rrsets_secure(rep)) { + if(reply_an_ns_rrsets_secure(rep)) { *is_secure_answer = 1; } else { if(must_validate) { diff --git a/doc/example.conf.in b/doc/example.conf.in index c5e4e242e..4687dc64f 100644 --- a/doc/example.conf.in +++ b/doc/example.conf.in @@ -203,6 +203,12 @@ server: # protects against poison attempts. # iter-scrub-promiscuous: yes + # Limit on number of DNSSEC validation attempts for a query. + # val-validation-attempts: 32 + + # Limit on number of DNSSEC hash attempts for a query. + # val-hash-attempts: 32 + # msec for waiting for an unknown server to reply. Increase if you # are behind a slow satellite link, to eg. 1128. # unknown-server-time-limit: 376 @@ -728,7 +734,7 @@ server: # non-secure data. Useful to shield the users of this validator from # potential bogus data in the additional section. All unsigned data # in the additional section is removed from secure messages. - # val-clean-additional: yes + # val-clean-additional: no # Turn permissive mode on to permit bogus messages. Thus, messages # for which security checks failed will be returned to clients, diff --git a/doc/unbound.conf.rst b/doc/unbound.conf.rst index e341c516f..838840d13 100644 --- a/doc/unbound.conf.rst +++ b/doc/unbound.conf.rst @@ -2320,7 +2320,7 @@ These options are part of the ``server:`` section. Use this setting to protect the users that rely on this validator for authentication from potentially bad data in the additional section. - Default: yes + Default: no @@UAHL@unbound.conf@val-log-level@@: ** @@ -3358,6 +3358,26 @@ These options are part of the ``server:`` section. Default: yes +@@UAHL@unbound.conf@val-validation-attempts@@: ** + Limit on the number of DNSSEC validation attempts for a query. + This protects against too large numbers of cryptographic operations, + like for a deep delegation chain. + This counts attempts to validate RRSIGs. + When it is exceeded, the query fails. + + Default: 32 + + +@@UAHL@unbound.conf@val-hash-attempts@@: ** + Limit on the number of DNSSEC hash attempts for a query. + This protects against too large numbers of cryptographic operations, + like for a deep delegation chain. + This counts DS hash attempts to match DNSKEYs. + When it is exceeded, the query fails. + + Default: 32 + + @@UAHL@unbound.conf@fast-server-permil@@: ** Specify how many times out of 1000 to pick from the set of fastest servers. 0 turns the feature off. diff --git a/iterator/iter_scrub.c b/iterator/iter_scrub.c index 1e3e01330..d1f3896bf 100644 --- a/iterator/iter_scrub.c +++ b/iterator/iter_scrub.c @@ -655,6 +655,9 @@ scrub_normalize(sldns_buffer* pkt, struct msg_parse* msg, if(rrset->type == LDNS_RR_TYPE_NS && rrset->rr_count > env->cfg->iter_scrub_ns) { shorten_rrset(pkt, rrset, env->cfg->iter_scrub_ns); + } else if(rrset->type == LDNS_RR_TYPE_DS && + rrset->rr_count > env->cfg->iter_scrub_ns) { + shorten_rrset(pkt, rrset, env->cfg->iter_scrub_ns); } prev = rrset; rrset = rrset->rrset_all_next; @@ -674,6 +677,9 @@ scrub_normalize(sldns_buffer* pkt, struct msg_parse* msg, if(rrset->type == LDNS_RR_TYPE_NS && rrset->rr_count > env->cfg->iter_scrub_ns) { shorten_rrset(pkt, rrset, env->cfg->iter_scrub_ns); + } else if(rrset->type == LDNS_RR_TYPE_DS && + rrset->rr_count > env->cfg->iter_scrub_ns) { + shorten_rrset(pkt, rrset, env->cfg->iter_scrub_ns); } /* Mark the additional names from relevant rrset as OK. */ @@ -798,6 +804,11 @@ scrub_normalize(sldns_buffer* pkt, struct msg_parse* msg, shorten_rrset(pkt, rrset, env->cfg->iter_scrub_ns); } } + } else if(rrset->type==LDNS_RR_TYPE_DS) { + if(rrset->rr_count > env->cfg->iter_scrub_ns) { + shorten_rrset(pkt, rrset, + env->cfg->iter_scrub_ns); + } } /* if this is type DS and we query for type DS we just got * a referral answer for our type DS query, fix packet */ diff --git a/services/authzone.c b/services/authzone.c index b3fb09e64..3fe2cec53 100644 --- a/services/authzone.c +++ b/services/authzone.c @@ -8234,7 +8234,8 @@ 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, &verified, reasonbuf, reasonlen); + LDNS_SECTION_ANSWER, NULL, NULL, &verified, reasonbuf, + reasonlen); if(sec == sec_status_secure) { return 1; } @@ -8583,8 +8584,8 @@ zonemd_get_dnskey_from_anchor(struct auth_zone* z, struct module_env* env, auth_zone_log(z->name, VERB_QUERY, "zonemd: verify DNSKEY RRset with trust anchor"); sec = val_verify_DNSKEY_with_TA(env, ve, keystorage, anchor->ds_rrset, - anchor->dnskey_rrset, NULL, why_bogus, NULL, NULL, reasonbuf, - reasonlen); + anchor->dnskey_rrset, NULL, why_bogus, NULL, NULL, NULL, + reasonbuf, reasonlen); regional_free_all(env->scratch); if(sec == sec_status_secure) { /* success */ @@ -8644,7 +8645,7 @@ auth_zone_verify_zonemd_key_with_ds(struct auth_zone* z, keystorage->rk.rrset_class = htons(z->dclass); auth_zone_log(z->name, VERB_QUERY, "zonemd: verify zone DNSKEY with DS"); sec = val_verify_DNSKEY_with_DS(env, ve, keystorage, ds, sigalg, - why_bogus, NULL, NULL, reasonbuf, reasonlen); + why_bogus, NULL, NULL, NULL, reasonbuf, reasonlen); regional_free_all(env->scratch); if(sec == sec_status_secure) { /* success */ diff --git a/services/cache/dns.c b/services/cache/dns.c index 04d5ae2d6..e83b57c11 100644 --- a/services/cache/dns.c +++ b/services/cache/dns.c @@ -677,7 +677,7 @@ tomsg(struct module_env* env, struct query_info* q, struct reply_info* r, rrset_array_unlock(r->ref, r->rrset_count); return NULL; } - if(r->security == sec_status_secure && !reply_all_rrsets_secure(r)) { + if(r->security == sec_status_secure && !reply_an_ns_rrsets_secure(r)) { /* message rrsets have changed status, revalidate */ rrset_array_unlock(r->ref, r->rrset_count); return NULL; diff --git a/services/mesh.c b/services/mesh.c index 0360a0a86..04dedd329 100644 --- a/services/mesh.c +++ b/services/mesh.c @@ -373,7 +373,7 @@ mesh_serve_expired_lookup(struct module_qstate* qstate, "validation"); goto bail_out; /* need to validate cache entry first */ } else if(msg->rep->security == sec_status_secure && - !reply_all_rrsets_secure(msg->rep) && must_validate) { + !reply_an_ns_rrsets_secure(msg->rep) && must_validate) { verbose(VERB_ALGO, "Serve expired: secure entry" " changed status"); goto bail_out; /* rrset changed, re-verify */ diff --git a/testcode/unitverify.c b/testcode/unitverify.c index db0a1a383..ea991faac 100644 --- a/testcode/unitverify.c +++ b/testcode/unitverify.c @@ -196,7 +196,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, &verified, reasonbuf, + NULL, LDNS_SECTION_ANSWER, NULL, NULL, &verified, reasonbuf, sizeof(reasonbuf)); if(vsig) { printf("verify outcome is: %s %s\n", sec_status_to_string(sec), diff --git a/testdata/03-testbound.tdir/03-testbound.test b/testdata/03-testbound.tdir/03-testbound.test index b9fdf214d..dd75b061d 100644 --- a/testdata/03-testbound.tdir/03-testbound.test +++ b/testdata/03-testbound.tdir/03-testbound.test @@ -85,6 +85,15 @@ for input in $PRE/testdata/*.rpl $PRE/testdata/*.crpl; do fi fi + # detect if ECDSA is needed + if echo $cleaninput | grep ecdsa >/dev/null 2>&1; then + if $PRE/testbound -e >/dev/null 2>&1; then + : # ECDSA is supported + else + continue + fi + fi + # detect if CLIENT_SUBNET is needed if echo $cleaninput | grep subnet >/dev/null 2>&1; then if $PRE/testbound -c >/dev/null 2>&1; then diff --git a/testdata/val_refer_unsignadd.rpl b/testdata/val_refer_unsignadd.rpl index 22f15d21a..6e3418ceb 100644 --- a/testdata/val_refer_unsignadd.rpl +++ b/testdata/val_refer_unsignadd.rpl @@ -11,6 +11,7 @@ server: trust-anchor-signaling: no iter-scrub-promiscuous: no rrset-roundrobin: no + val-clean-additional: yes stub-zone: name: "." diff --git a/testdata/val_referglue.rpl b/testdata/val_referglue.rpl index 3ca0c0e80..c14bad17f 100644 --- a/testdata/val_referglue.rpl +++ b/testdata/val_referglue.rpl @@ -12,6 +12,7 @@ server: minimal-responses: no iter-scrub-promiscuous: no rrset-roundrobin: no + val-clean-additional: yes stub-zone: name: "." diff --git a/util/config_file.c b/util/config_file.c index ab209a818..623f02ad3 100644 --- a/util/config_file.c +++ b/util/config_file.c @@ -278,7 +278,7 @@ config_create(void) cfg->val_sig_skew_min = 3600; /* at least daylight savings trouble */ cfg->val_sig_skew_max = 86400; /* at most timezone settings trouble */ cfg->val_max_restart = 5; - cfg->val_clean_additional = 1; + cfg->val_clean_additional = 0; /* off to protect against much data. */ cfg->val_log_level = 0; cfg->val_log_squelch = 0; cfg->val_permissive_mode = 0; @@ -429,6 +429,8 @@ config_create(void) cfg->iter_scrub_rrsig = 8; cfg->iter_scrub_promiscuous = 1; cfg->max_global_quota = 200; + cfg->val_validation_attempts = 32; + cfg->val_hash_attempts = 32; return cfg; error_exit: config_delete(cfg); @@ -785,6 +787,8 @@ int config_set_option(struct config_file* cfg, const char* opt, else S_NUMBER_OR_ZERO("iter-scrub-rrsig:", iter_scrub_rrsig) else S_YNO("iter-scrub-promiscuous:", iter_scrub_promiscuous) else S_NUMBER_OR_ZERO("max-global-quota:", max_global_quota) + else S_NUMBER_OR_ZERO("val-validation-attempts:", val_validation_attempts) + else S_NUMBER_OR_ZERO("val-hash-attempts:", val_hash_attempts) else S_YNO("serve-original-ttl:", serve_original_ttl) else S_STR("val-nsec3-keysize-iterations:", val_nsec3_key_iterations) else S_YNO("zonemd-permissive-mode:", zonemd_permissive_mode) @@ -1265,6 +1269,8 @@ config_get_option(struct config_file* cfg, const char* opt, else O_DEC(opt, "iter-scrub-rrsig", iter_scrub_rrsig) else O_YNO(opt, "iter-scrub-promiscuous", iter_scrub_promiscuous) else O_DEC(opt, "max-global-quota", max_global_quota) + else O_DEC(opt, "val-validation-attempts", val_validation_attempts) + else O_DEC(opt, "val-hash-attempts", val_hash_attempts) else O_YNO(opt, "serve-original-ttl", serve_original_ttl) else O_STR(opt, "val-nsec3-keysize-iterations",val_nsec3_key_iterations) else O_YNO(opt, "zonemd-permissive-mode", zonemd_permissive_mode) diff --git a/util/config_file.h b/util/config_file.h index c2b45759f..ae7eb6436 100644 --- a/util/config_file.h +++ b/util/config_file.h @@ -798,6 +798,10 @@ struct config_file { int iter_scrub_rrsig; /** limit on upstream queries for an incoming query and subqueries. */ int max_global_quota; + /** limit on validator validation attempts. */ + int val_validation_attempts; + /** limit on validator hash attempts. */ + int val_hash_attempts; /** Should the iterator scrub promiscuous NS rrsets, from positive * answers. */ int iter_scrub_promiscuous; diff --git a/util/configlexer.lex b/util/configlexer.lex index 196575bdb..84e353d5e 100644 --- a/util/configlexer.lex +++ b/util/configlexer.lex @@ -608,6 +608,8 @@ iter-scrub-ns{COLON} { YDVAR(1, VAR_ITER_SCRUB_NS) } iter-scrub-cname{COLON} { YDVAR(1, VAR_ITER_SCRUB_CNAME) } iter-scrub-rrsig{COLON} { YDVAR(1, VAR_ITER_SCRUB_RRSIG) } max-global-quota{COLON} { YDVAR(1, VAR_MAX_GLOBAL_QUOTA) } +val-validation-attempts{COLON} { YDVAR(1, VAR_VAL_VALIDATION_ATTEMPTS) } +val-hash-attempts{COLON} { YDVAR(1, VAR_VAL_HASH_ATTEMPTS) } max-transfer-size{COLON} { YDVAR(1, VAR_MAX_TRANSFER_SIZE) } max-transfer-time{COLON} { YDVAR(1, VAR_MAX_TRANSFER_TIME) } iter-scrub-promiscuous{COLON} { YDVAR(1, VAR_ITER_SCRUB_PROMISCUOUS) } diff --git a/util/configparser.y b/util/configparser.y index 490bba0ff..1f877df0b 100644 --- a/util/configparser.y +++ b/util/configparser.y @@ -218,6 +218,7 @@ extern struct config_parser_state* cfg_parser; %token VAR_ITER_SCRUB_RRSIG %token VAR_MAX_TRANSFER_SIZE VAR_MAX_TRANSFER_TIME %token VAR_MAX_GLOBAL_QUOTA VAR_HARDEN_UNVERIFIED_GLUE VAR_LOG_TIME_ISO +%token VAR_VAL_VALIDATION_ATTEMPTS VAR_VAL_HASH_ATTEMPTS %token VAR_ITER_SCRUB_PROMISCUOUS VAR_LOG_THREAD_ID %% @@ -360,7 +361,8 @@ content_server: server_num_threads | server_verbosity | server_port | server_harden_unknown_additional | server_disable_edns_do | server_log_destaddr | server_cookie_secret_file | server_iter_scrub_ns | server_iter_scrub_cname | server_max_global_quota | - server_iter_scrub_rrsig | + server_val_validation_attempts | + server_val_hash_attempts | server_iter_scrub_rrsig | server_harden_unverified_glue | server_log_time_iso | server_iter_scrub_promiscuous ; stub_clause: stubstart contents_stub @@ -4317,6 +4319,24 @@ server_iter_scrub_promiscuous: VAR_ITER_SCRUB_PROMISCUOUS STRING_ARG free($2); } ; +server_val_validation_attempts: VAR_VAL_VALIDATION_ATTEMPTS STRING_ARG + { + OUTYY(("P(server_val_validation_attempts:%s)\n", $2)); + if(atoi($2) == 0 && strcmp($2, "0") != 0) + yyerror("number expected"); + else cfg_parser->cfg->val_validation_attempts = atoi($2); + free($2); + } + ; +server_val_hash_attempts: VAR_VAL_HASH_ATTEMPTS STRING_ARG + { + OUTYY(("P(server_val_hash_attempts:%s)\n", $2)); + if(atoi($2) == 0 && strcmp($2, "0") != 0) + yyerror("number expected"); + else cfg_parser->cfg->val_hash_attempts = atoi($2); + free($2); + } + ; ipsetstart: VAR_IPSET { OUTYY(("\nP(ipset:)\n")); diff --git a/util/data/msgreply.c b/util/data/msgreply.c index 71cab7d74..86193a671 100644 --- a/util/data/msgreply.c +++ b/util/data/msgreply.c @@ -1139,6 +1139,17 @@ reply_all_rrsets_secure(struct reply_info* rep) return 1; } +int reply_an_ns_rrsets_secure(struct reply_info* rep) +{ + size_t i; + for(i=0; ian_numrrsets+rep->ns_numrrsets; i++) { + if( ((struct packed_rrset_data*)rep->rrsets[i]->entry.data) + ->security != sec_status_secure ) + return 0; + } + return 1; +} + struct reply_info* parse_reply_in_temp_region(sldns_buffer* pkt, struct regional* region, struct query_info* qi) diff --git a/util/data/msgreply.h b/util/data/msgreply.h index e7c688e97..e4476660d 100644 --- a/util/data/msgreply.h +++ b/util/data/msgreply.h @@ -494,6 +494,9 @@ int reply_check_cname_chain(struct query_info* qinfo, struct reply_info* rep); */ int reply_all_rrsets_secure(struct reply_info* rep); +/** Check status of answer and authority section RRs. */ +int reply_an_ns_rrsets_secure(struct reply_info* rep); + /** * Find answer rrset in reply, the one matching qinfo. Follows CNAMEs, so the * result may have a different owner name. diff --git a/validator/autotrust.c b/validator/autotrust.c index fc7897b71..bfe0319af 100644 --- a/validator/autotrust.c +++ b/validator/autotrust.c @@ -1297,12 +1297,13 @@ void autr_write_file(struct module_env* env, struct trust_anchor* tp) * @param tp: trust point to verify with * @param rrset: DNSKEY rrset to verify. * @param qstate: qstate with region. + * @param vq: validator query state. * @return false on failure, true if verification successful. */ static int verify_dnskey(struct module_env* env, struct val_env* ve, struct trust_anchor* tp, struct ub_packed_rrset_key* rrset, - struct module_qstate* qstate) + struct module_qstate* qstate, struct val_qstate* vq) { char reasonbuf[256]; char* reason = NULL; @@ -1310,7 +1311,7 @@ verify_dnskey(struct module_env* env, struct val_env* ve, int downprot = env->cfg->harden_algo_downgrade; enum sec_status sec = val_verify_DNSKEY_with_TA(env, ve, rrset, tp->ds_rrset, tp->dnskey_rrset, downprot?sigalg:NULL, &reason, - NULL, qstate, reasonbuf, sizeof(reasonbuf)); + NULL, qstate, vq, reasonbuf, sizeof(reasonbuf)); /* sigalg is ignored, it returns algorithms signalled to exist, but * in 5011 there are no other rrsets to check. if downprot is * enabled, then it checks that the DNSKEY is signed with all @@ -1350,16 +1351,18 @@ min_expiry(struct module_env* env, struct packed_rrset_data* dd) static int rr_is_selfsigned_revoked(struct module_env* env, struct val_env* ve, struct ub_packed_rrset_key* dnskey_rrset, size_t i, - struct module_qstate* qstate) + struct module_qstate* qstate, struct val_qstate* vq) { enum sec_status sec; char* reason = NULL; + size_t num_tagmatches = 0; verbose(VERB_ALGO, "seen REVOKE flag, check self-signed, rr %d", (int)i); /* no algorithm downgrade protection necessary, if it is selfsigned * revoked it can be removed. */ sec = dnskey_verify_rrset(env, ve, dnskey_rrset, dnskey_rrset, i, - &reason, NULL, LDNS_SECTION_ANSWER, qstate); + &reason, NULL, LDNS_SECTION_ANSWER, qstate, vq, + &num_tagmatches); return (sec == sec_status_secure); } @@ -1575,7 +1578,7 @@ init_events(struct trust_anchor* tp) static void check_contains_revoked(struct module_env* env, struct val_env* ve, struct trust_anchor* tp, struct ub_packed_rrset_key* dnskey_rrset, - int* changed, struct module_qstate* qstate) + int* changed, struct module_qstate* qstate, struct val_qstate* vq) { struct packed_rrset_data* dd = (struct packed_rrset_data*) dnskey_rrset->entry.data; @@ -1595,7 +1598,8 @@ check_contains_revoked(struct module_env* env, struct val_env* ve, } if(!ta) continue; /* key not found */ - if(rr_is_selfsigned_revoked(env, ve, dnskey_rrset, i, qstate)) { + if(rr_is_selfsigned_revoked(env, ve, dnskey_rrset, i, qstate, + vq)) { /* checked if there is an rrsig signed by this key. */ /* same keytag, but stored can be revoked already, so * compare keytags, with +0 or +128(REVOKE flag) */ @@ -2209,7 +2213,7 @@ autr_tp_remove(struct module_env* env, struct trust_anchor* tp, int autr_process_prime(struct module_env* env, struct val_env* ve, struct trust_anchor* tp, struct ub_packed_rrset_key* dnskey_rrset, - struct module_qstate* qstate) + struct module_qstate* qstate, struct val_qstate* vq) { int changed = 0; log_assert(tp && tp->autr); @@ -2250,7 +2254,7 @@ int autr_process_prime(struct module_env* env, struct val_env* ve, return 1; /* trust point exists */ } /* check for revoked keys to remove immediately */ - check_contains_revoked(env, ve, tp, dnskey_rrset, &changed, qstate); + check_contains_revoked(env, ve, tp, dnskey_rrset, &changed, qstate, vq); if(changed) { verbose(VERB_ALGO, "autotrust: revokedkeys, reassemble"); if(!autr_assemble(tp)) { @@ -2266,7 +2270,7 @@ int autr_process_prime(struct module_env* env, struct val_env* ve, } } /* verify the dnskey rrset and see if it is valid. */ - if(!verify_dnskey(env, ve, tp, dnskey_rrset, qstate)) { + if(!verify_dnskey(env, ve, tp, dnskey_rrset, qstate, vq)) { verbose(VERB_ALGO, "autotrust: dnskey did not verify."); /* only increase failure count if this is not the first prime, * this means there was a previous successful probe */ diff --git a/validator/autotrust.h b/validator/autotrust.h index 057f2b68a..2e2f92668 100644 --- a/validator/autotrust.h +++ b/validator/autotrust.h @@ -50,6 +50,7 @@ struct module_env; struct module_qstate; struct val_env; struct sldns_buffer; +struct val_qstate; /** Autotrust anchor states */ typedef enum { @@ -190,13 +191,14 @@ void autr_point_delete(struct trust_anchor* tp); * @param dnskey_rrset: DNSKEY rrset probed (can be NULL if bad prime result). * allocated in a region. Has not been validated yet. * @param qstate: qstate with region. + * @param vq: validator query state. * @return false if trust anchor was revoked completely. * Otherwise logs errors to log, does not change return value. * On errors, likely the trust point has been unchanged. */ int autr_process_prime(struct module_env* env, struct val_env* ve, struct trust_anchor* tp, struct ub_packed_rrset_key* dnskey_rrset, - struct module_qstate* qstate); + struct module_qstate* qstate, struct val_qstate* vq); /** * Debug printout of rfc5011 tracked anchors diff --git a/validator/val_nsec.c b/validator/val_nsec.c index becd42b4d..a7f49d6e1 100644 --- a/validator/val_nsec.c +++ b/validator/val_nsec.c @@ -177,7 +177,8 @@ static int nsec_verify_rrset(struct module_env* env, struct val_env* ve, struct ub_packed_rrset_key* nsec, struct key_entry_key* kkey, char** reason, sldns_ede_code* reason_bogus, - struct module_qstate* qstate, char* reasonbuf, size_t reasonlen) + struct module_qstate* qstate, struct val_qstate* vq, char* reasonbuf, + size_t reasonlen) { struct packed_rrset_data* d = (struct packed_rrset_data*) nsec->entry.data; @@ -189,7 +190,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, - reason_bogus, LDNS_SECTION_AUTHORITY, qstate, &verified, + reason_bogus, LDNS_SECTION_AUTHORITY, qstate, vq, &verified, reasonbuf, reasonlen); if(d->security == sec_status_secure) { rrset_update_sec_status(env->rrset_cache, nsec, *env->now); @@ -203,7 +204,7 @@ val_nsec_prove_nodata_dsreply(struct module_env* env, struct val_env* ve, struct query_info* qinfo, struct reply_info* rep, struct key_entry_key* kkey, time_t* proof_ttl, char** reason, sldns_ede_code* reason_bogus, struct module_qstate* qstate, - char* reasonbuf, size_t reasonlen) + struct val_qstate* vq, char* reasonbuf, size_t reasonlen) { struct ub_packed_rrset_key* nsec = reply_find_rrset_section_ns( rep, qinfo->qname, qinfo->qname_len, LDNS_RR_TYPE_NSEC, @@ -221,7 +222,7 @@ val_nsec_prove_nodata_dsreply(struct module_env* env, struct val_env* ve, * 2) this is not a delegation point */ if(nsec) { if(!nsec_verify_rrset(env, ve, nsec, kkey, reason, - reason_bogus, qstate, reasonbuf, reasonlen)) { + reason_bogus, qstate, vq, reasonbuf, reasonlen)) { verbose(VERB_ALGO, "NSEC RRset for the " "referral did not verify."); return sec_status_bogus; @@ -258,7 +259,7 @@ val_nsec_prove_nodata_dsreply(struct module_env* env, struct val_env* ve, if(rep->rrsets[i]->rk.type != htons(LDNS_RR_TYPE_NSEC)) continue; if(!nsec_verify_rrset(env, ve, rep->rrsets[i], kkey, reason, - reason_bogus, qstate, reasonbuf, reasonlen)) { + reason_bogus, qstate, vq, reasonbuf, reasonlen)) { verbose(VERB_ALGO, "NSEC for empty non-terminal " "did not verify."); *reason = "NSEC for empty non-terminal " diff --git a/validator/val_nsec.h b/validator/val_nsec.h index c1d45314a..a9002b78a 100644 --- a/validator/val_nsec.h +++ b/validator/val_nsec.h @@ -52,6 +52,7 @@ struct ub_packed_rrset_key; struct reply_info; struct query_info; struct key_entry_key; +struct val_qstate; /** * Check DS absence. @@ -68,6 +69,7 @@ struct key_entry_key; * @param reason: string explaining why bogus. * @param reason_bogus: relevant EDE code for validation failure. * @param qstate: qstate with region. + * @param vq: validator qstate. * @param reasonbuf: buffer to use for fail reason string print. * @param reasonlen: length of reasonbuf. * @return security status. @@ -80,7 +82,8 @@ enum sec_status val_nsec_prove_nodata_dsreply(struct module_env* env, struct val_env* ve, struct query_info* qinfo, struct reply_info* rep, struct key_entry_key* kkey, time_t* proof_ttl, char** reason, sldns_ede_code* reason_bogus, - struct module_qstate* qstate, char* reasonbuf, size_t reasonlen); + struct module_qstate* qstate, struct val_qstate* vq, char* reasonbuf, + size_t reasonlen); /** * nsec typemap check, takes an NSEC-type bitmap as argument, checks for type. diff --git a/validator/val_nsec3.c b/validator/val_nsec3.c index d0385be68..8314f2db9 100644 --- a/validator/val_nsec3.c +++ b/validator/val_nsec3.c @@ -1521,7 +1521,8 @@ static int list_is_secure(struct module_env* env, struct val_env* ve, struct ub_packed_rrset_key** list, size_t num, struct key_entry_key* kkey, char** reason, sldns_ede_code *reason_bogus, - struct module_qstate* qstate, char* reasonbuf, size_t reasonlen) + struct module_qstate* qstate, struct val_qstate* vq, char* reasonbuf, + size_t reasonlen) { struct packed_rrset_data* d; size_t i; @@ -1537,7 +1538,7 @@ list_is_secure(struct module_env* env, struct val_env* ve, continue; d->security = val_verify_rrset_entry(env, ve, list[i], kkey, reason, reason_bogus, LDNS_SECTION_AUTHORITY, qstate, - &verified, reasonbuf, reasonlen); + vq, &verified, reasonbuf, reasonlen); if(d->security != sec_status_secure) { verbose(VERB_ALGO, "NSEC3 did not verify"); return 0; @@ -1552,7 +1553,8 @@ 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, - struct nsec3_cache_table* ct, char* reasonbuf, size_t reasonlen) + struct val_qstate* vq, struct nsec3_cache_table* ct, char* reasonbuf, + size_t reasonlen) { struct nsec3_filter flt; struct ce_response ce; @@ -1568,7 +1570,7 @@ nsec3_prove_nods(struct module_env* env, struct val_env* ve, return sec_status_bogus; /* no valid NSEC3s, bogus */ } if(!list_is_secure(env, ve, list, num, kkey, reason, reason_bogus, - qstate, reasonbuf, reasonlen)) { + qstate, vq, reasonbuf, reasonlen)) { *reason = "not all NSEC3 records secure"; return sec_status_bogus; /* not all NSEC3 records secure */ } diff --git a/validator/val_nsec3.h b/validator/val_nsec3.h index a13e92991..b6f56741f 100644 --- a/validator/val_nsec3.h +++ b/validator/val_nsec3.h @@ -78,6 +78,7 @@ struct reply_info; struct query_info; struct key_entry_key; struct sldns_buffer; +struct val_qstate; /** * 0 1 2 3 4 5 6 7 @@ -215,6 +216,7 @@ 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 vq: validator qstate. * @param ct: cached hashes table. * @param reasonbuf: buffer to use for fail reason string print. * @param reasonlen: length of reasonbuf. @@ -230,7 +232,8 @@ 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, - struct nsec3_cache_table* ct, char* reasonbuf, size_t reasonlen); + struct val_qstate* vq, struct nsec3_cache_table* ct, char* reasonbuf, + size_t reasonlen); /** * Prove NXDOMAIN or NODATA. diff --git a/validator/val_sigcrypt.c b/validator/val_sigcrypt.c index 8c0568c9f..612754cf3 100644 --- a/validator/val_sigcrypt.c +++ b/validator/val_sigcrypt.c @@ -82,6 +82,8 @@ /** Maximum number of RRSIG validations for an RRset. */ #define MAX_VALIDATE_RRSIGS 8 +/** Maximum number of NSEC validations for a message. */ +#define MAX_VALIDATE_NSECS 8 /** return number of rrs in an rrset */ static size_t @@ -548,8 +550,10 @@ 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 vq: validator qstate with attempt counts. * @param numverified: incremented when the number of RRSIG validations * increases. + * @param num_tagmatches: incremented for tag matches. * @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). @@ -561,7 +565,7 @@ dnskeyset_verify_rrset_sig(struct module_env* env, struct val_env* ve, struct rbtree_type** sortree, char** reason, sldns_ede_code *reason_bogus, sldns_pkt_section section, struct module_qstate* qstate, - int* numverified) + struct val_qstate* vq, int* numverified, size_t* num_tagmatches) { /* find matching keys and check them */ enum sec_status sec = sec_status_bogus; @@ -580,6 +584,14 @@ dnskeyset_verify_rrset_sig(struct module_env* env, struct val_env* ve, } for(i=0; i MAX_TAG_MATCHES) { + *reason = "too many tag matches"; + if(reason_bogus) + *reason_bogus = LDNS_EDE_DNSSEC_BOGUS; + verbose(VERB_ALGO, "verify sig: too many tag matches, " + "MAX_TAG_MATCHES (%d); bogus", MAX_TAG_MATCHES); + return sec_status_bogus; + } /* see if key matches keytag and algo */ if(algo != dnskey_get_algo(dnskey, i) || tag != dnskey_calc_keytag(dnskey, i)) @@ -587,6 +599,26 @@ dnskeyset_verify_rrset_sig(struct module_env* env, struct val_env* ve, numchecked ++; (*numverified)++; + if(vq && vq->num_validation_attempts++ > env->cfg->val_validation_attempts) { + *reason = "too many validation attempts"; + if(reason_bogus) + *reason_bogus = LDNS_EDE_DNSSEC_BOGUS; + verbose(VERB_ALGO, "verify sig: too many validation attempts, " + "val-validation-attempts (%d); bogus", env->cfg->val_validation_attempts); + return sec_status_bogus; + } + if(vq && (ntohs(rrset->rk.type) == LDNS_RR_TYPE_NSEC || + ntohs(rrset->rk.type) == LDNS_RR_TYPE_NSEC3) && + vq->num_nsec_attempts++ > MAX_VALIDATE_NSECS) { + *reason = "too many NSEC or NSEC3 validation attempts"; + if(reason_bogus) + *reason_bogus = LDNS_EDE_DNSSEC_BOGUS; + verbose(VERB_ALGO, "verify sig: too many NSEC or NSEC3 validation attempts, " + "(%d); bogus", MAX_VALIDATE_NSECS); + vq->num_nsec_attempts_exceeded = 1; + return sec_status_bogus; + } + /* see if key verifies */ sec = dnskey_verify_rrset_sig(env->scratch, env->scratch_buffer, ve, now, rrset, dnskey, i, @@ -626,11 +658,12 @@ 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, int* verified, - char* reasonbuf, size_t reasonlen) + sldns_pkt_section section, struct module_qstate* qstate, + struct val_qstate* vq, int* verified, char* reasonbuf, + size_t reasonlen) { enum sec_status sec; - size_t i, num; + size_t i, num, num_tagmatches = 0; rbtree_type* sortree = NULL; /* make sure that for all DNSKEY algorithms there are valid sigs */ struct algo_needs needs; @@ -658,9 +691,19 @@ dnskeyset_verify_rrset(struct module_env* env, struct val_env* ve, } } for(i=0; i MAX_TAG_MATCHES) { + *reason = "too many tag matches"; + if(reason_bogus) + *reason_bogus = LDNS_EDE_DNSSEC_BOGUS; + verbose(VERB_ALGO, "rrset failed to verify: too many tag matches, " + "MAX_TAG_MATCHES (%d)", MAX_TAG_MATCHES); + if(reason_bogus) + *reason_bogus = LDNS_EDE_DNSSEC_BOGUS; + return sec_status_bogus; + } sec = dnskeyset_verify_rrset_sig(env, ve, *env->now, rrset, dnskey, i, &sortree, reason, reason_bogus, - section, qstate, verified); + section, qstate, vq, verified, &num_tagmatches); /* see which algorithm has been fixed up */ if(sec == sec_status_secure) { if(!sigalg) @@ -709,7 +752,8 @@ enum sec_status dnskey_verify_rrset(struct module_env* env, struct val_env* ve, struct ub_packed_rrset_key* rrset, struct ub_packed_rrset_key* dnskey, size_t dnskey_idx, char** reason, sldns_ede_code *reason_bogus, - sldns_pkt_section section, struct module_qstate* qstate) + sldns_pkt_section section, struct module_qstate* qstate, + struct val_qstate* vq, size_t* num_tagmatches) { enum sec_status sec; size_t i, num, numchecked = 0, numindeterminate = 0; @@ -730,9 +774,26 @@ dnskey_verify_rrset(struct module_env* env, struct val_env* ve, } for(i=0; i MAX_TAG_MATCHES) { + *reason = "too many tag matches"; + if(reason_bogus) + *reason_bogus = LDNS_EDE_DNSSEC_BOGUS; + verbose(VERB_ALGO, "rrset failed to verify: too many tag matches, " + "MAX_TAG_MATCHES (%d); bogus", MAX_TAG_MATCHES); + return sec_status_bogus; + } if(algo != rrset_get_sig_algo(rrset, i) || tag != rrset_get_sig_keytag(rrset, i)) continue; + if(vq && vq->num_validation_attempts++ > env->cfg->val_validation_attempts) { + *reason = "too many validation attempts"; + if(reason_bogus) + *reason_bogus = LDNS_EDE_DNSSEC_BOGUS; + verbose(VERB_ALGO, "rrset failed to verify: too many validation attempts, " + "val-validation-attempts (%d); bogus", env->cfg->val_validation_attempts); + return sec_status_bogus; + } + buf_canon = 0; sec = dnskey_verify_rrset_sig(env->scratch, env->scratch_buffer, ve, *env->now, rrset, diff --git a/validator/val_sigcrypt.h b/validator/val_sigcrypt.h index c1e2658e4..b2cfae96d 100644 --- a/validator/val_sigcrypt.h +++ b/validator/val_sigcrypt.h @@ -53,6 +53,7 @@ struct ub_packed_rrset_key; struct rbtree_type; struct regional; struct sldns_buffer; +struct val_qstate; /** number of entries in algorithm needs array */ #define ALGO_NEEDS_MAX 256 @@ -262,6 +263,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 vq: validator qstate with attempt counts. * @param verified: if not NULL the number of RRSIG validations is returned. * @param reasonbuf: buffer to use for fail reason string print. * @param reasonlen: length of reasonbuf. @@ -273,8 +275,9 @@ 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, int* verified, - char* reasonbuf, size_t reasonlen); + sldns_pkt_section section, struct module_qstate* qstate, + struct val_qstate* vq, int* verified, char* reasonbuf, + size_t reasonlen); /** * verify rrset against one specific dnskey (from rrset) @@ -287,13 +290,16 @@ enum sec_status dnskeyset_verify_rrset(struct module_env* env, * @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 vq: validator qstate with attempt counts. + * @param num_tagmatches: incremented to keep track of tag matches. * @return secure if *this* key signs any of the signatures on rrset. * unchecked on error or and bogus on bad signature. */ enum sec_status dnskey_verify_rrset(struct module_env* env, struct val_env* ve, struct ub_packed_rrset_key* rrset, struct ub_packed_rrset_key* dnskey, size_t dnskey_idx, char** reason, sldns_ede_code *reason_bogus, - sldns_pkt_section section, struct module_qstate* qstate); + sldns_pkt_section section, struct module_qstate* qstate, + struct val_qstate* vq, size_t* num_tagmatches); /** * verify rrset, with specific dnskey(from set), for a specific rrsig diff --git a/validator/val_utils.c b/validator/val_utils.c index e77f93f5a..c5fb7f325 100644 --- a/validator/val_utils.c +++ b/validator/val_utils.c @@ -406,7 +406,8 @@ 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, - int *verified, char* reasonbuf, size_t reasonlen) + struct val_qstate* vq, int *verified, char* reasonbuf, + size_t reasonlen) { enum sec_status sec; struct packed_rrset_data* d = (struct packed_rrset_data*)rrset-> @@ -431,7 +432,8 @@ val_verify_rrset(struct module_env* env, struct val_env* ve, 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, verified, reasonbuf, reasonlen); + reason_bogus, section, qstate, vq, verified, reasonbuf, + reasonlen); verbose(VERB_ALGO, "verify result: %s", sec_status_to_string(sec)); regional_free_all(env->scratch); @@ -475,7 +477,8 @@ 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, - int* verified, char* reasonbuf, size_t reasonlen) + struct val_qstate* vq, int* verified, char* reasonbuf, + size_t reasonlen) { /* temporary dnskey rrset-key */ struct ub_packed_rrset_key dnskey; @@ -489,7 +492,8 @@ 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, verified, reasonbuf, reasonlen); + reason_bogus, section, qstate, vq, verified, reasonbuf, + reasonlen); return sec; } @@ -499,13 +503,20 @@ verify_dnskeys_with_ds_rr(struct module_env* env, struct val_env* ve, struct ub_packed_rrset_key* dnskey_rrset, struct ub_packed_rrset_key* ds_rrset, size_t ds_idx, char** reason, sldns_ede_code *reason_bogus, struct module_qstate* qstate, - int *nonechecked, char* reasonbuf, size_t reasonlen) + struct val_qstate* vq, int *nonechecked, char* reasonbuf, + size_t reasonlen, size_t* num_tagmatches, + size_t* num_tagmatches_dnskeysig) { enum sec_status sec = sec_status_bogus; size_t i, num, numchecked = 0, numhashok = 0, numsizesupp = 0; num = rrset_get_count(dnskey_rrset); *nonechecked = 0; for(i=0; i MAX_TAG_MATCHES) { + verbose(VERB_ALGO, "DS match attempt reached " + "MAX_TAG_MATCHES (%d); bogus", MAX_TAG_MATCHES); + return sec_status_bogus; + } /* Skip DNSKEYs that don't match the basic criteria. */ if(ds_get_key_algo(ds_rrset, ds_idx) != dnskey_get_algo(dnskey_rrset, i) @@ -518,6 +529,15 @@ verify_dnskeys_with_ds_rr(struct module_env* env, struct val_env* ve, ds_get_key_algo(ds_rrset, ds_idx), ds_get_keytag(ds_rrset, ds_idx)); + if(vq && vq->num_hash_attempts++ > env->cfg->val_hash_attempts) { + *reason = "too many hash attempts"; + if(reason_bogus) + *reason_bogus = LDNS_EDE_DNSSEC_BOGUS; + verbose(VERB_ALGO, "rrset failed to verify: too many hash attempts, " + "val-hash-attempts (%d); bogus", env->cfg->val_hash_attempts); + return sec_status_bogus; + } + /* Convert the candidate DNSKEY into a hash using the * same DS hash algorithm. */ if(!ds_digest_match_dnskey(env, dnskey_rrset, i, ds_rrset, @@ -541,8 +561,14 @@ verify_dnskeys_with_ds_rr(struct module_env* env, struct val_env* ve, /* Otherwise, we have a match! Make sure that the DNSKEY * verifies *with this key* */ + if(*num_tagmatches_dnskeysig > MAX_TAG_MATCHES) { + verbose(VERB_ALGO, "DS that matched has too many DNSKEY to RRSIG tag matches " + "MAX_TAG_MATCHES (%d); bogus", MAX_TAG_MATCHES); + return sec_status_bogus; + } sec = dnskey_verify_rrset(env, ve, dnskey_rrset, dnskey_rrset, - i, reason, reason_bogus, LDNS_SECTION_ANSWER, qstate); + i, reason, reason_bogus, LDNS_SECTION_ANSWER, qstate, + vq, num_tagmatches_dnskeysig); if(sec == sec_status_secure) { return sec; } @@ -586,14 +612,14 @@ val_verify_DNSKEY_with_DS(struct module_env* env, struct val_env* ve, struct ub_packed_rrset_key* dnskey_rrset, struct ub_packed_rrset_key* ds_rrset, uint8_t* sigalg, char** reason, sldns_ede_code *reason_bogus, struct module_qstate* qstate, - char* reasonbuf, size_t reasonlen) + struct val_qstate* vq, char* reasonbuf, size_t reasonlen) { /* as long as this is false, we can consider this DS rrset to be * equivalent to no DS rrset. */ int has_useful_ds = 0, digest_algo, alg, has_algo_refusal = 0, nonechecked, has_checked_ds = 0; struct algo_needs needs; - size_t i, num; + size_t i, num, num_tagmatches = 0, num_tagmatches_dnskeysig = 0; enum sec_status sec; if(dnskey_rrset->rk.dname_len != ds_rrset->rk.dname_len || @@ -615,6 +641,13 @@ val_verify_DNSKEY_with_DS(struct module_env* env, struct val_env* ve, } num = rrset_get_count(ds_rrset); for(i=0; i MAX_TAG_MATCHES) { + verbose(VERB_ALGO, "DS verify attempt reached " + "MAX_TAG_MATCHES (%d); bogus", MAX_TAG_MATCHES); + *reason = "DS verify has too many tag matches"; + return sec_status_bogus; + } + /* Check to see if we can understand this DS. * And check it is the strongest digest */ if(!ds_digest_algo_is_supported(ds_rrset, i) || @@ -623,9 +656,16 @@ val_verify_DNSKEY_with_DS(struct module_env* env, struct val_env* ve, continue; } + if(num_tagmatches_dnskeysig > MAX_TAG_MATCHES) { + verbose(VERB_ALGO, "DS verify attempt reached " + "DNSKEY to RRSIG MAX_TAG_MATCHES (%d); bogus", MAX_TAG_MATCHES); + *reason = "DS verify has too many DNSKEY to RRSIG tag matches"; + return sec_status_bogus; + } sec = verify_dnskeys_with_ds_rr(env, ve, dnskey_rrset, - ds_rrset, i, reason, reason_bogus, qstate, - &nonechecked, reasonbuf, reasonlen); + ds_rrset, i, reason, reason_bogus, qstate, vq, + &nonechecked, reasonbuf, reasonlen, &num_tagmatches, + &num_tagmatches_dnskeysig); if(sec == sec_status_insecure) { /* DNSKEY too large unsupported or algo refused by * crypto lib. */ @@ -687,12 +727,12 @@ val_verify_new_DNSKEYs(struct regional* region, struct module_env* env, struct val_env* ve, struct ub_packed_rrset_key* dnskey_rrset, struct ub_packed_rrset_key* ds_rrset, int downprot, char** reason, sldns_ede_code *reason_bogus, struct module_qstate* qstate, - char* reasonbuf, size_t reasonlen) + struct val_qstate* vq, char* reasonbuf, size_t reasonlen) { uint8_t sigalg[ALGO_NEEDS_MAX+1]; enum sec_status sec = val_verify_DNSKEY_with_DS(env, ve, dnskey_rrset, ds_rrset, downprot?sigalg:NULL, reason, - reason_bogus, qstate, reasonbuf, reasonlen); + reason_bogus, qstate, vq, reasonbuf, reasonlen); if(sec == sec_status_secure) { return key_entry_create_rrset(region, @@ -718,14 +758,14 @@ val_verify_DNSKEY_with_TA(struct module_env* env, struct val_env* ve, struct ub_packed_rrset_key* ta_ds, struct ub_packed_rrset_key* ta_dnskey, uint8_t* sigalg, char** reason, sldns_ede_code *reason_bogus, struct module_qstate* qstate, - char* reasonbuf, size_t reasonlen) + struct val_qstate* vq, char* reasonbuf, size_t reasonlen) { /* as long as this is false, we can consider this anchor to be * equivalent to no anchor. */ int has_useful_ta = 0, digest_algo = 0, alg, has_algo_refusal = 0, nonechecked, has_checked_ds = 0; struct algo_needs needs; - size_t i, num; + size_t i, num, num_tagmatches = 0, num_tagmatches_dnskeysig = 0; enum sec_status sec; if(ta_ds && (dnskey_rrset->rk.dname_len != ta_ds->rk.dname_len || @@ -761,6 +801,15 @@ val_verify_DNSKEY_with_TA(struct module_env* env, struct val_env* ve, if(ta_ds) { num = rrset_get_count(ta_ds); for(i=0; i MAX_TAG_MATCHES) { + verbose(VERB_ALGO, "anchor DS verify attempt reached " + "MAX_TAG_MATCHES (%d); bogus", MAX_TAG_MATCHES); + *reason = "anchor DS verify has too many tag matches"; + if(reason_bogus) + *reason_bogus = LDNS_EDE_DNSSEC_BOGUS; + return sec_status_bogus; + } + /* Check to see if we can understand this DS. * And check it is the strongest digest */ if(!ds_digest_algo_is_supported(ta_ds, i) || @@ -768,9 +817,18 @@ val_verify_DNSKEY_with_TA(struct module_env* env, struct val_env* ve, ds_get_digest_algo(ta_ds, i) != digest_algo) continue; + if(num_tagmatches_dnskeysig > MAX_TAG_MATCHES) { + verbose(VERB_ALGO, "anchor DS verify has too many DNSKEY to RRSIG tag matches " + "MAX_TAG_MATCHES (%d); bogus", MAX_TAG_MATCHES); + *reason = "anchor DS verify has too many DNSKEY to RRSIG tag matches"; + if(reason_bogus) + *reason_bogus = LDNS_EDE_DNSSEC_BOGUS; + return sec_status_bogus; + } sec = verify_dnskeys_with_ds_rr(env, ve, dnskey_rrset, - ta_ds, i, reason, reason_bogus, qstate, &nonechecked, - reasonbuf, reasonlen); + ta_ds, i, reason, reason_bogus, qstate, vq, + &nonechecked, reasonbuf, reasonlen, &num_tagmatches, + &num_tagmatches_dnskeysig); if(sec == sec_status_insecure) { has_algo_refusal = 1; continue; @@ -813,8 +871,16 @@ val_verify_DNSKEY_with_TA(struct module_env* env, struct val_env* ve, /* we saw a useful TA */ has_useful_ta = 1; + if(num_tagmatches_dnskeysig > MAX_TAG_MATCHES) { + verbose(VERB_ALGO, "anchor DS that matched has too many DNSKEY to RRSIG tag matches " + "MAX_TAG_MATCHES (%d); bogus", MAX_TAG_MATCHES); + *reason = "anchor DS that matched has too many DNSKEY to RRSIG tag matches"; + if(reason_bogus) + *reason_bogus = LDNS_EDE_DNSSEC_BOGUS; + return sec_status_bogus; + } sec = dnskey_verify_rrset(env, ve, dnskey_rrset, - ta_dnskey, i, reason, reason_bogus, LDNS_SECTION_ANSWER, qstate); + ta_dnskey, i, reason, reason_bogus, LDNS_SECTION_ANSWER, qstate, vq, &num_tagmatches_dnskeysig); if(sec == sec_status_secure) { if(!sigalg || algo_needs_set_secure(&needs, (uint8_t)dnskey_get_algo(ta_dnskey, i))) { @@ -862,12 +928,13 @@ val_verify_new_DNSKEYs_with_ta(struct regional* region, struct module_env* env, struct ub_packed_rrset_key* ta_ds_rrset, struct ub_packed_rrset_key* ta_dnskey_rrset, int downprot, char** reason, sldns_ede_code *reason_bogus, - struct module_qstate* qstate, char* reasonbuf, size_t reasonlen) + struct module_qstate* qstate, struct val_qstate* vq, char* reasonbuf, + size_t reasonlen) { uint8_t sigalg[ALGO_NEEDS_MAX+1]; enum sec_status sec = val_verify_DNSKEY_with_TA(env, ve, dnskey_rrset, ta_ds_rrset, ta_dnskey_rrset, - downprot?sigalg:NULL, reason, reason_bogus, qstate, + downprot?sigalg:NULL, reason, reason_bogus, qstate, vq, reasonbuf, reasonlen); if(sec == sec_status_secure) { diff --git a/validator/val_utils.h b/validator/val_utils.h index 43386edbf..e5e3194a9 100644 --- a/validator/val_utils.h +++ b/validator/val_utils.h @@ -55,6 +55,11 @@ struct regional; struct val_anchors; struct rrset_cache; struct sock_list; +struct val_qstate; + +/** Maximum number of matches with key tag and algorithm, for DNSKEY to + * RRSIG and DS to DNSKEY. Since the number is O(N*N), there is a limit. */ +#define MAX_TAG_MATCHES 256 /** * Response classifications for the validator. The different types of proofs. @@ -124,6 +129,7 @@ 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 vq: validator qstate with attempt counts. * @param verified: if not NULL, the number of RRSIG validations is returned. * @param reasonbuf: buffer to use for fail reason string print. * @param reasonlen: length of reasonbuf. @@ -133,7 +139,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, - int* verified, char* reasonbuf, size_t reasonlen); + struct val_qstate* vq, int* verified, char* reasonbuf, + size_t reasonlen); /** * Verify DNSKEYs with DS rrset. Like val_verify_new_DNSKEYs but @@ -148,6 +155,7 @@ enum sec_status val_verify_rrset_entry(struct module_env* env, * @param reason: reason of failure. Fixed string or alloced in scratch. * @param reason_bogus: EDE (RFC8914) code paired with the reason of failure. * @param qstate: qstate with region. + * @param vq: validator qstate with attempt counts. * @param reasonbuf: buffer to use for fail reason string print. * @param reasonlen: length of reasonbuf. * @return: sec_status_secure if a DS matches. @@ -158,7 +166,7 @@ enum sec_status val_verify_DNSKEY_with_DS(struct module_env* env, struct val_env* ve, struct ub_packed_rrset_key* dnskey_rrset, struct ub_packed_rrset_key* ds_rrset, uint8_t* sigalg, char** reason, sldns_ede_code *reason_bogus, struct module_qstate* qstate, - char* reasonbuf, size_t reasonlen); + struct val_qstate* vq, char* reasonbuf, size_t reasonlen); /** * Verify DNSKEYs with DS and DNSKEY rrset. Like val_verify_DNSKEY_with_DS @@ -174,6 +182,7 @@ enum sec_status val_verify_DNSKEY_with_DS(struct module_env* env, * @param reason: reason of failure. Fixed string or alloced in scratch. * @param reason_bogus: EDE (RFC8914) code paired with the reason of failure. * @param qstate: qstate with region. + * @param vq: validator qstate with attempt counts. * @param reasonbuf: buffer to use for fail reason string print. * @param reasonlen: length of reasonbuf. * @return: sec_status_secure if a DS matches. @@ -185,7 +194,7 @@ enum sec_status val_verify_DNSKEY_with_TA(struct module_env* env, struct ub_packed_rrset_key* ta_ds, struct ub_packed_rrset_key* ta_dnskey, uint8_t* sigalg, char** reason, sldns_ede_code *reason_bogus, struct module_qstate* qstate, - char* reasonbuf, size_t reasonlen); + struct val_qstate* vq, char* reasonbuf, size_t reasonlen); /** * Verify new DNSKEYs with DS rrset. The DS contains hash values that should @@ -202,6 +211,7 @@ enum sec_status val_verify_DNSKEY_with_TA(struct module_env* env, * @param reason: reason of failure. Fixed string or alloced in scratch. * @param reason_bogus: EDE (RFC8914) code paired with the reason of failure. * @param qstate: qstate with region. + * @param vq: validator qstate with attempt counts. * @param reasonbuf: buffer to use for fail reason string print. * @param reasonlen: length of reasonbuf. * @return a KeyEntry. This will either contain the now trusted @@ -219,7 +229,7 @@ struct key_entry_key* val_verify_new_DNSKEYs(struct regional* region, struct ub_packed_rrset_key* dnskey_rrset, struct ub_packed_rrset_key* ds_rrset, int downprot, char** reason, sldns_ede_code *reason_bogus, struct module_qstate* qstate, - char* reasonbuf, size_t reasonlen); + struct val_qstate* vq, char* reasonbuf, size_t reasonlen); /** * Verify rrset with trust anchor: DS and DNSKEY rrset. @@ -235,6 +245,7 @@ struct key_entry_key* val_verify_new_DNSKEYs(struct regional* region, * @param reason: reason of failure. Fixed string or alloced in scratch. * @param reason_bogus: EDE (RFC8914) code paired with the reason of failure. * @param qstate: qstate with region. + * @param vq: validator qstate with attempt counts. * @param reasonbuf: buffer to use for fail reason string print. * @param reasonlen: length of reasonbuf. * @return a KeyEntry. This will either contain the now trusted @@ -253,7 +264,7 @@ struct key_entry_key* val_verify_new_DNSKEYs_with_ta(struct regional* region, struct ub_packed_rrset_key* ta_ds_rrset, struct ub_packed_rrset_key* ta_dnskey_rrset, int downprot, char** reason, sldns_ede_code *reason_bogus, struct module_qstate* qstate, - char* reasonbuf, size_t reasonlen); + struct val_qstate* vq, char* reasonbuf, size_t reasonlen); /** * Determine if DS rrset is usable for validator or not. diff --git a/validator/validator.c b/validator/validator.c index d3ed8be3e..4958a2242 100644 --- a/validator/validator.c +++ b/validator/validator.c @@ -350,13 +350,17 @@ static void val_restart(struct val_qstate* vq) { struct comm_timer* temp_timer; - int restart_count; + int restart_count, num_validation_attempts, num_hash_attempts; if(!vq) return; temp_timer = vq->suspend_timer; restart_count = vq->restart_count+1; + num_validation_attempts = vq->num_validation_attempts; + num_hash_attempts = vq->num_hash_attempts; memset(vq, 0, sizeof(*vq)); vq->suspend_timer = temp_timer; vq->restart_count = restart_count; + vq->num_validation_attempts = num_validation_attempts; + vq->num_hash_attempts = num_hash_attempts; vq->state = VAL_INIT_STATE; } @@ -455,6 +459,24 @@ already_validated(struct dns_msg* ret_msg) return 0; } +/** If it is possible to restart the validation state */ +static int +val_can_restart(struct module_qstate* qstate, struct val_qstate* vq, + struct val_env* ve) +{ + /* For validation failures that are limits exceeded on the amount + * of work that the DNSSEC validator is willing to do, the restart + * is not allowed. A restart would increase the amount of effort + * spent even further. */ + if(vq->restart_count < ve->max_restart && + vq->num_validation_attempts <= qstate->env->cfg->val_validation_attempts && + vq->num_hash_attempts <= qstate->env->cfg->val_hash_attempts && + !vq->num_nsec_attempts_exceeded) + return 1; + (void)qstate; + return 0; +} + /** * Generate a request for DNS data. * @@ -763,8 +785,8 @@ validate_msg_signatures(struct module_qstate* qstate, struct val_qstate* vq, /* Verify the answer rrset */ sec = val_verify_rrset_entry(env, ve, s, key_entry, &reason, - &reason_bogus, LDNS_SECTION_ANSWER, qstate, &verified, - reasonbuf, sizeof(reasonbuf)); + &reason_bogus, LDNS_SECTION_ANSWER, qstate, vq, + &verified, reasonbuf, sizeof(reasonbuf)); /* If the (answer) rrset failed to validate, then this * message is BAD. */ if(sec != sec_status_secure) { @@ -808,7 +830,7 @@ validate_msg_signatures(struct module_qstate* qstate, struct val_qstate* vq, 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, vq, &verified, reasonbuf, sizeof(reasonbuf)); /* If anything in the authority section fails to be secure, * we have a bad message. */ @@ -855,7 +877,7 @@ validate_msg_signatures(struct module_qstate* qstate, struct val_qstate* vq, 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, - &verified, reasonbuf, sizeof(reasonbuf)); + vq, &verified, reasonbuf, sizeof(reasonbuf)); /* the additional section can fail to be secure, * it is optional, check signature in case we need * to clean the additional section later. */ @@ -1433,16 +1455,20 @@ validate_nameerror_response(struct module_env* env, struct val_env* ve, * trusted DNSKEY rrset that signs this response must already have been * completed. * + * @param env: module env. * @param chase_reply: answer to validate. */ static void -validate_referral_response(struct reply_info* chase_reply) +validate_referral_response(struct module_env* env, struct reply_info* chase_reply) { - size_t i; + size_t i, count; enum sec_status s; /* message security equals lowest rrset security */ chase_reply->security = sec_status_secure; - for(i=0; irrset_count; i++) { + if(env->cfg->val_clean_additional) + count = chase_reply->rrset_count; + else count = chase_reply->an_numrrsets+chase_reply->ns_numrrsets; + for(i=0; irrsets[i] ->entry.data)->security; if(s < chase_reply->security) @@ -2309,7 +2335,7 @@ processValidate(struct module_qstate* qstate, struct val_qstate* vq, key_entry_get_reason_bogus(vq->key_entry)); errinf_ede(qstate, "while building chain of trust", key_entry_get_reason_bogus(vq->key_entry)); - if(vq->restart_count >= ve->max_restart) + if(!val_can_restart(qstate, vq, ve)) key_cache_insert(ve->kcache, vq->key_entry, qstate->env->cfg->val_log_level >= 2); return 1; @@ -2457,7 +2483,7 @@ processValidate(struct module_qstate* qstate, struct val_qstate* vq, case VAL_CLASS_REFERRAL: verbose(VERB_ALGO, "Validating a referral response"); - validate_referral_response(vq->chase_reply); + validate_referral_response(qstate->env, vq->chase_reply); verbose(VERB_DETAIL, "validate(referral): %s", sec_status_to_string( vq->chase_reply->security)); @@ -2531,15 +2557,17 @@ processFinished(struct module_qstate* qstate, struct val_qstate* vq, } if(subtype == VAL_CLASS_REFERRAL) { - /* for a referral, move to next unchecked rrset and check it*/ - vq->rrset_skip = val_next_unchecked(vq->orig_msg->rep, - vq->rrset_skip); - if(vq->rrset_skip < vq->orig_msg->rep->rrset_count) { - /* and restart for this rrset */ - verbose(VERB_ALGO, "validator: go to next rrset"); - vq->chase_reply->security = sec_status_unchecked; - vq->state = VAL_INIT_STATE; - return 1; + if(qstate->env->cfg->val_clean_additional) { + /* for a referral, move to next unchecked rrset and check it*/ + vq->rrset_skip = val_next_unchecked(vq->orig_msg->rep, + vq->rrset_skip); + if(vq->rrset_skip < vq->orig_msg->rep->rrset_count) { + /* and restart for this rrset */ + verbose(VERB_ALGO, "validator: go to next rrset"); + vq->chase_reply->security = sec_status_unchecked; + vq->state = VAL_INIT_STATE; + return 1; + } } /* referral chase is done */ } @@ -2584,7 +2612,7 @@ processFinished(struct module_qstate* qstate, struct val_qstate* vq, struct msgreply_entry* e; /* see if we can try again to fetch data */ - if(vq->restart_count < ve->max_restart) { + if(val_can_restart(qstate, vq, ve)) { verbose(VERB_ALGO, "validation failed, " "blacklist and retry to fetch data"); val_blacklist(&qstate->blacklist, qstate->region, @@ -2876,6 +2904,7 @@ val_operate(struct module_qstate* qstate, enum module_ev event, int id, * (this rrset is allocated in the wrong region, not the qstate). * @param ta: trust anchor. * @param qstate: qstate that needs key. + * @param vq: validator qstate. * @param id: module id. * @param sub_qstate: the sub query state, that is the lookup that fetched * the trust anchor data, it contains error information for the answer. @@ -2886,8 +2915,8 @@ val_operate(struct module_qstate* qstate, enum module_ev event, int id, */ static struct key_entry_key* primeResponseToKE(struct ub_packed_rrset_key* dnskey_rrset, - struct trust_anchor* ta, struct module_qstate* qstate, int id, - struct module_qstate* sub_qstate) + struct trust_anchor* ta, struct module_qstate* qstate, + struct val_qstate* vq, int id, struct module_qstate* sub_qstate) { struct val_env* ve = (struct val_env*)qstate->env->modinfo[id]; struct key_entry_key* kkey = NULL; @@ -2927,7 +2956,8 @@ primeResponseToKE(struct ub_packed_rrset_key* dnskey_rrset, /* attempt to verify with trust anchor DS and DNSKEY */ kkey = val_verify_new_DNSKEYs_with_ta(qstate->region, qstate->env, ve, dnskey_rrset, ta->ds_rrset, ta->dnskey_rrset, downprot, - &reason, &reason_bogus, qstate, reasonbuf, sizeof(reasonbuf)); + &reason, &reason_bogus, qstate, vq, reasonbuf, + sizeof(reasonbuf)); if(!kkey) { log_err("out of memory: verifying prime TA"); return NULL; @@ -3040,7 +3070,7 @@ ds_response_to_ke(struct module_qstate* qstate, struct val_qstate* vq, * bogus, then we are done. */ sec = val_verify_rrset_entry(qstate->env, ve, ds, vq->key_entry, &reason, &reason_bogus, - LDNS_SECTION_ANSWER, qstate, &verified, reasonbuf, + LDNS_SECTION_ANSWER, qstate, vq, &verified, reasonbuf, sizeof(reasonbuf)); if(sec != sec_status_secure) { verbose(VERB_DETAIL, "DS rrset in DS response did " @@ -3091,7 +3121,7 @@ ds_response_to_ke(struct module_qstate* qstate, struct val_qstate* vq, /* Try to prove absence of the DS with NSEC */ sec = val_nsec_prove_nodata_dsreply( qstate->env, ve, qinfo, msg->rep, vq->key_entry, - &proof_ttl, &reason, &reason_bogus, qstate, + &proof_ttl, &reason, &reason_bogus, qstate, vq, reasonbuf, sizeof(reasonbuf)); switch(sec) { case sec_status_secure: @@ -3129,7 +3159,7 @@ ds_response_to_ke(struct module_qstate* qstate, struct val_qstate* vq, 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, &vq->nsec3_cache_table, + &reason_bogus, qstate, vq, &vq->nsec3_cache_table, reasonbuf, sizeof(reasonbuf)); switch(sec) { case sec_status_insecure: @@ -3197,7 +3227,7 @@ ds_response_to_ke(struct module_qstate* qstate, struct val_qstate* vq, } sec = val_verify_rrset_entry(qstate->env, ve, cname, vq->key_entry, &reason, &reason_bogus, - LDNS_SECTION_ANSWER, qstate, &verified, reasonbuf, + LDNS_SECTION_ANSWER, qstate, vq, &verified, reasonbuf, sizeof(reasonbuf)); if(sec == sec_status_secure) { /* Check for wildcard expansion */ @@ -3318,6 +3348,7 @@ process_ds_response(struct module_qstate* qstate, struct val_qstate* vq, uint8_t* olds = vq->empty_DS_name; int ret; *suspend = 0; + vq->num_nsec_attempts = 0; vq->empty_DS_name = NULL; if(sub_qstate && sub_qstate->rpz_applied) { verbose(VERB_ALGO, "rpz was applied to the DS lookup, " @@ -3329,6 +3360,8 @@ process_ds_response(struct module_qstate* qstate, struct val_qstate* vq, } ret = ds_response_to_ke(qstate, vq, id, rcode, msg, qinfo, &dske, sub_qstate); + /* New NSEC attempt count for next message validation. */ + vq->num_nsec_attempts = 0; if(ret != 0) { switch(ret) { case 1: @@ -3370,7 +3403,7 @@ process_ds_response(struct module_qstate* qstate, struct val_qstate* vq, vq->chain_blacklist = NULL; /* fresh blacklist for next part*/ /* Keep the forState.state on FINDKEY. */ } else if(key_entry_isbad(dske) - && vq->restart_count < ve->max_restart) { + && val_can_restart(qstate, vq, ve)) { vq->empty_DS_name = olds; val_blacklist(&vq->chain_blacklist, qstate->region, origin, 1); qstate->errinf = NULL; @@ -3420,6 +3453,7 @@ process_dnskey_response(struct module_qstate* qstate, struct val_qstate* vq, char* reason = NULL; sldns_ede_code reason_bogus = LDNS_EDE_DNSSEC_BOGUS; + vq->num_nsec_attempts = 0; if(sub_qstate && sub_qstate->rpz_applied) { verbose(VERB_ALGO, "rpz was applied to the DNSKEY lookup, " "make it insecure"); @@ -3439,7 +3473,7 @@ process_dnskey_response(struct module_qstate* qstate, struct val_qstate* vq, verbose(VERB_DETAIL, "Missing DNSKEY RRset in response to " "DNSKEY query."); - if(vq->restart_count < ve->max_restart) { + if(val_can_restart(qstate, vq, ve)) { val_blacklist(&vq->chain_blacklist, qstate->region, origin, 1); qstate->errinf = NULL; @@ -3476,7 +3510,9 @@ process_dnskey_response(struct module_qstate* qstate, struct val_qstate* vq, downprot = qstate->env->cfg->harden_algo_downgrade; vq->key_entry = val_verify_new_DNSKEYs(qstate->region, qstate->env, ve, dnskey, vq->ds_rrset, downprot, &reason, &reason_bogus, - qstate, reasonbuf, sizeof(reasonbuf)); + qstate, vq, reasonbuf, sizeof(reasonbuf)); + /* New NSEC attempt count for next message validation. */ + vq->num_nsec_attempts = 0; if(!vq->key_entry) { log_err("out of memory in verify new DNSKEYs"); @@ -3487,7 +3523,7 @@ process_dnskey_response(struct module_qstate* qstate, struct val_qstate* vq, * state. */ if(!key_entry_isgood(vq->key_entry)) { if(key_entry_isbad(vq->key_entry)) { - if(vq->restart_count < ve->max_restart) { + if(val_can_restart(qstate, vq, ve)) { val_blacklist(&vq->chain_blacklist, qstate->region, origin, 1); qstate->errinf = NULL; @@ -3539,6 +3575,7 @@ process_prime_response(struct module_qstate* qstate, struct val_qstate* vq, struct trust_anchor* ta = anchor_find(qstate->env->anchors, vq->trust_anchor_name, vq->trust_anchor_labs, vq->trust_anchor_len, vq->qchase.qclass); + vq->num_nsec_attempts = 0; if(!ta) { /* trust anchor revoked, restart with less anchors */ vq->state = VAL_INIT_STATE; @@ -3557,19 +3594,23 @@ process_prime_response(struct module_qstate* qstate, struct val_qstate* vq, if(ta->autr) { if(!autr_process_prime(qstate->env, ve, ta, dnskey_rrset, - qstate)) { + qstate, vq)) { + /* New NSEC attempt count for next message validation. */ + vq->num_nsec_attempts = 0; /* trust anchor revoked, restart with less anchors */ vq->state = VAL_INIT_STATE; vq->trust_anchor_name = NULL; return; } } - vq->key_entry = primeResponseToKE(dnskey_rrset, ta, qstate, id, + vq->key_entry = primeResponseToKE(dnskey_rrset, ta, qstate, vq, id, sub_qstate); lock_basic_unlock(&ta->lock); + /* New NSEC attempt count for next message validation. */ + vq->num_nsec_attempts = 0; if(vq->key_entry) { if(key_entry_isbad(vq->key_entry) - && vq->restart_count < ve->max_restart) { + && val_can_restart(qstate, vq, ve)) { val_blacklist(&vq->chain_blacklist, qstate->region, origin, 1); qstate->errinf = NULL; diff --git a/validator/validator.h b/validator/validator.h index e04fad572..f7bc3550a 100644 --- a/validator/validator.h +++ b/validator/validator.h @@ -231,6 +231,19 @@ struct val_qstate { struct comm_timer* suspend_timer; /** Number of suspends */ int suspend_count; + + /** Number of DNSKEY RRSIG validation attempts. This is the number of + * cryptographic operations done for the mesh state. */ + int num_validation_attempts; + /** Number of DS hash verification attempts. This is the number of + * hash operations done for the mesh state. + * It does not count NSEC3 hashes. */ + int num_hash_attempts; + /** Number of NSEC validations. And NSEC3 too. This is reset per + * answer. */ + int num_nsec_attempts; + /** The nsec attempts have been exceeded. */ + int num_nsec_attempts_exceeded; }; /**