]> git.ipfire.org Git - thirdparty/unbound.git/commitdiff
- Fix CVE-2023-50387, DNSSEC verification complexity can be exploited to
authorW.C.A. Wijngaards <wouter@nlnetlabs.nl>
Tue, 13 Feb 2024 12:02:08 +0000 (13:02 +0100)
committerW.C.A. Wijngaards <wouter@nlnetlabs.nl>
Tue, 13 Feb 2024 12:02:08 +0000 (13:02 +0100)
  exhaust CPU resources and stall DNS resolvers.

14 files changed:
services/authzone.c
testcode/unitverify.c
testdata/val_any.rpl
testdata/val_any_dname.rpl
testdata/val_any_negcache.rpl
util/fptr_wlist.c
validator/val_nsec.c
validator/val_nsec3.c
validator/val_sigcrypt.c
validator/val_sigcrypt.h
validator/val_utils.c
validator/val_utils.h
validator/validator.c
validator/validator.h

index a1b3d22787d22e01a21dcaa0465bde541a70678e..9d02cfbffe4c8d1baae2e048b5548f9dcc5ae1bb 100644 (file)
@@ -7774,6 +7774,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 "
@@ -7797,7 +7798,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;
        }
index ff069a1bb03d1283dd320fb76ed3dff73288fcb8..fb7d84467a52d88c425486adaaada2e4c567379c 100644 (file)
@@ -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:"");
index ee249ffb68434e3109525ff9ed0cc7c036414fe1..301f28425b0de33afac4106c21a4c2a239b18da8 100644 (file)
@@ -162,6 +162,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
index 005d29606980218d52957780e79810cc1020d1fc..a88d7c22c76bd706c4b7625f183f188e3cc1a3fd 100644 (file)
@@ -164,6 +164,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
index 77aacba8cc130913a9a4eb6ddc0e975697f49daa..8800a2140219b448aa33e12542ad06264a5b5fae 100644 (file)
@@ -199,6 +199,9 @@ SECTION QUESTION
 example.com. IN ANY
 ENTRY_END
 
+; Allow validation resuming for the RRSIGs
+STEP 21 TIME_PASSES ELAPSE 0.05
+
 ; recursion happens here.
 STEP 30 CHECK_ANSWER
 ENTRY_BEGIN
index 43d38dc3797ddd7316ea90bebc35a7aa9ba70ee1..de128168abfe10d7317e1c3cdd0fbdec83fd5860 100644 (file)
@@ -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_msg_signatures_timer_cb) return 1;
 #ifdef UB_ON_WINDOWS
        else if(fptr == &wsvc_cron_cb) return 1;
 #endif
index 17c90d83f59430edf4ce76a4dd07bff5e66f4588..d0cc67ff5d0bcaddf62c0aee715014db06ef5a23 100644 (file)
@@ -181,6 +181,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;
@@ -188,7 +189,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);
+               reason_bogus, LDNS_SECTION_AUTHORITY, qstate, &verified);
        if(d->security == sec_status_secure) {
                rrset_update_sec_status(env->rrset_cache, nsec, *env->now);
                return 1;
index a2b3794f6019a02ce5b2a22b9024df63983f88af..f4b9b2bca377041070dc35fd68d3ce2b570deb31 100644 (file)
@@ -1294,6 +1294,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; i<num; i++) {
                d = (struct packed_rrset_data*)list[i]->entry.data;
                if(list[i]->rk.type != htons(LDNS_RR_TYPE_NSEC3))
@@ -1304,7 +1305,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;
index 37730f17967623de276b2d40c910c8aa8b1c3aef..f4b866366f3c73da10064d135ce92ef84d2ed606 100644 (file)
@@ -79,6 +79,9 @@
 #include <openssl/engine.h>
 #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)
@@ -542,6 +545,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).
@@ -552,7 +557,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;
@@ -576,6 +582,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,
@@ -586,6 +593,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";
@@ -609,7 +623,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;
@@ -617,6 +631,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) {
@@ -641,7 +656,7 @@ dnskeyset_verify_rrset(struct module_env* env, struct val_env* ve,
        for(i=0; i<num; i++) {
                sec = dnskeyset_verify_rrset_sig(env, ve, *env->now, 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)
@@ -653,6 +668,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: "
@@ -691,6 +713,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) {
@@ -714,8 +737,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) {
index 7f52b71e41fa047f412bd10b0f9a9f6925047412..1a3d8fcb22debc3c05d6c1b119b85ea63cd25be5 100644 (file)
@@ -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);
 
 
 /** 
index 8b388882b82a6acac93aa684cfb270f15cf21f6e..67a958ae2ade5e9380c1614b5078529421da3a4d 100644 (file)
 #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++;
index 83e3d0ad824e2346873a20ebc1957da3d8bb1b99..e8cdcefa6923fa93e1dc11e6b1c3d619694aaeb6 100644 (file)
@@ -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
index 6cd15cfc1c7c01a52c15d64bf450e75989824c48..b01f388ca593641752f8d58a0098c433040e3575 100644 (file)
 #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, 
@@ -292,6 +297,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->msg_signatures_timer;
+       restart_count = vq->restart_count+1;
+       memset(vq, 0, sizeof(*vq));
+       vq->msg_signatures_timer = temp_timer;
+       vq->restart_count = restart_count;
+       vq->state = VAL_INIT_STATE;
+}
+
 /**
  * Exit validation with an error status
  * 
@@ -598,30 +618,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 to 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; i<chase_reply->an_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, 
@@ -640,7 +672,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) {
@@ -665,14 +697,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+1<loopmax. Store where to
+                        * continue later. */
+                       *suspend = 1;
+                       vq->msg_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; i<chase_reply->an_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) {
@@ -686,6 +737,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
@@ -695,22 +758,102 @@ 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; 
                i<chase_reply->rrset_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_msg_signatures_timer_cb(void* arg)
+{
+       struct module_qstate* qstate = (struct module_qstate*)arg;
+       verbose(VERB_ALGO, "validate_msg_signatures 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_msg_signatures_setup_timer(struct module_qstate* qstate,
+       struct val_qstate* vq, int id)
+{
+       struct timeval tv;
+       int usec, slack, base;
+       if(vq->suspend_count >= MAX_VALIDATION_SUSPENDS) {
+               verbose(VERB_ALGO, "validate_msg_signatures_setup_timer: "
+                       "reached MAX_VALIDATION_SUSPENDS (%d); error out",
+                       MAX_VALIDATION_SUSPENDS);
+               errinf(qstate, "max validation suspends reached, "
+                       "too many RRSIG validations");
+               return 0;
+       }
+       vq->state = VAL_VALIDATE_STATE;
+       qstate->ext_state[id] = module_wait_reply;
+       if(!vq->msg_signatures_timer) {
+               vq->msg_signatures_timer = comm_timer_create(
+                       qstate->env->worker_base,
+                       validate_msg_signatures_timer_cb, qstate);
+               if(!vq->msg_signatures_timer) {
+                       log_err("validate_msg_signatures_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->msg_signatures_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).
@@ -1875,7 +2018,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;
 
        if(!vq->key_entry) {
                verbose(VERB_ALGO, "validate: no key entry, failed");
@@ -1932,8 +2075,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_msg_signatures_setup_timer(qstate, vq,
+                               id))
+                               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*/
@@ -2129,16 +2278,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;
@@ -2476,6 +2622,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;
@@ -2506,7 +2653,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");
@@ -2652,7 +2799,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);
+                       LDNS_SECTION_ANSWER, qstate, &verified);
                if(sec == sec_status_secure) {
                        verbose(VERB_ALGO, "CNAME validated, "
                                "proof that DS does not exist");
@@ -2981,8 +3128,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->msg_signatures_timer) {
+                       comm_timer_delete(vq->msg_signatures_timer);
+               }
+       }
        /* everything is allocated in the region, so assign NULL */
        qstate->minfo[id] = NULL;
 }
index 694e4c89529efbdd830d5cc01d914d71ccdfd953..a997ca88f876258597385a168b3fa3e3343162f7 100644 (file)
@@ -50,6 +50,7 @@ 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 +216,15 @@ 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;
+       /** The timer to resume processing msg signatures */
+       struct comm_timer* msg_signatures_timer;
+       /** number of suspends */
+        int suspend_count;
 };
 
 /**
@@ -262,4 +272,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_msg_signatures_timer_cb(void* arg);
+
 #endif /* VALIDATOR_VALIDATOR_H */