diff --git a/bin/named/server.c b/bin/named/server.c index 80c5b5f1bb8..67cafe633f7 100644 --- a/bin/named/server.c +++ b/bin/named/server.c @@ -5455,6 +5455,21 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, cfg_obj_t *config, INSIST(result == ISC_R_SUCCESS); dns_resolver_setmaxqueries(view->resolver, cfg_obj_asuint32(obj)); + obj = NULL; + result = named_config_get(maps, "max-validations-per-fetch", &obj); + if (result == ISC_R_SUCCESS) { + dns_resolver_setmaxvalidations(view->resolver, + cfg_obj_asuint32(obj)); + } + + obj = NULL; + result = named_config_get(maps, "max-validation-failures-per-fetch", + &obj); + if (result == ISC_R_SUCCESS) { + dns_resolver_setmaxvalidationfails(view->resolver, + cfg_obj_asuint32(obj)); + } + obj = NULL; result = named_config_get(maps, "fetches-per-zone", &obj); INSIST(result == ISC_R_SUCCESS); diff --git a/doc/arm/reference.rst b/doc/arm/reference.rst index 8468a785eaf..6fb5937fa22 100644 --- a/doc/arm/reference.rst +++ b/doc/arm/reference.rst @@ -3691,6 +3691,21 @@ system. set to zero, :any:`max-clients-per-query` no longer applies and there is no upper bound, other than that imposed by :any:`recursive-clients`. +.. namedconf:statement:: max-validations-per-fetch + :tags: server + :short: Set the maximum number of DNSSEC validations that can happen in single fetch + + This is an **experimental** setting to set the maximum number of DNSSEC + validations that can happen in a single resolver fetch. The default is 16. + +.. namedconf:statement:: max-validation-failures-per-fetch + :tags: server + :short: Set the maximum number of DNSSEC validation failures that can happen in single fetch + + This is an **experimental** setting to set the maximum number of DNSSEC + validation failures that can happen in a single resolver fetch. The default + is 1. + .. namedconf:statement:: fetches-per-zone :tags: server, query :short: Sets the maximum number of simultaneous iterative queries allowed to any one domain before the server blocks new queries for data in or beneath that zone. diff --git a/doc/misc/options b/doc/misc/options index 297822121af..1c0b47f49a4 100644 --- a/doc/misc/options +++ b/doc/misc/options @@ -188,6 +188,8 @@ options { max-transfer-time-in ; max-transfer-time-out ; max-udp-size ; + max-validation-failures-per-fetch ; // experimental + max-validations-per-fetch ; // experimental max-zone-ttl ( unlimited | ); // deprecated memstatistics ; memstatistics-file ; @@ -469,6 +471,8 @@ view [ ] { max-transfer-time-in ; max-transfer-time-out ; max-udp-size ; + max-validation-failures-per-fetch ; // experimental + max-validations-per-fetch ; // experimental max-zone-ttl ( unlimited | ); // deprecated message-compression ; min-cache-ttl ; diff --git a/lib/dns/dst_api.c b/lib/dns/dst_api.c index af53947ac65..ce41b99e5d8 100644 --- a/lib/dns/dst_api.c +++ b/lib/dns/dst_api.c @@ -164,7 +164,8 @@ computeid(dst_key_t *key); static isc_result_t frombuffer(const dns_name_t *name, unsigned int alg, unsigned int flags, unsigned int protocol, dns_rdataclass_t rdclass, - isc_buffer_t *source, isc_mem_t *mctx, dst_key_t **keyp); + isc_buffer_t *source, isc_mem_t *mctx, bool no_rdata, + dst_key_t **keyp); static isc_result_t algorithm_status(unsigned int alg); @@ -750,6 +751,13 @@ dst_key_todns(const dst_key_t *key, isc_buffer_t *target) { isc_result_t dst_key_fromdns(const dns_name_t *name, dns_rdataclass_t rdclass, isc_buffer_t *source, isc_mem_t *mctx, dst_key_t **keyp) { + return (dst_key_fromdns_ex(name, rdclass, source, mctx, false, keyp)); +} + +isc_result_t +dst_key_fromdns_ex(const dns_name_t *name, dns_rdataclass_t rdclass, + isc_buffer_t *source, isc_mem_t *mctx, bool no_rdata, + dst_key_t **keyp) { uint8_t alg, proto; uint32_t flags, extflags; dst_key_t *key = NULL; @@ -780,7 +788,7 @@ dst_key_fromdns(const dns_name_t *name, dns_rdataclass_t rdclass, } result = frombuffer(name, alg, flags, proto, rdclass, source, mctx, - &key); + no_rdata, &key); if (result != ISC_R_SUCCESS) { return (result); } @@ -801,7 +809,7 @@ dst_key_frombuffer(const dns_name_t *name, unsigned int alg, unsigned int flags, REQUIRE(dst_initialized); result = frombuffer(name, alg, flags, protocol, rdclass, source, mctx, - &key); + false, &key); if (result != ISC_R_SUCCESS) { return (result); } @@ -2302,7 +2310,8 @@ computeid(dst_key_t *key) { static isc_result_t frombuffer(const dns_name_t *name, unsigned int alg, unsigned int flags, unsigned int protocol, dns_rdataclass_t rdclass, - isc_buffer_t *source, isc_mem_t *mctx, dst_key_t **keyp) { + isc_buffer_t *source, isc_mem_t *mctx, bool no_rdata, + dst_key_t **keyp) { dst_key_t *key; isc_result_t ret; @@ -2324,10 +2333,12 @@ frombuffer(const dns_name_t *name, unsigned int alg, unsigned int flags, return (DST_R_UNSUPPORTEDALG); } - ret = key->func->fromdns(key, source); - if (ret != ISC_R_SUCCESS) { - dst_key_free(&key); - return (ret); + if (!no_rdata) { + ret = key->func->fromdns(key, source); + if (ret != ISC_R_SUCCESS) { + dst_key_free(&key); + return (ret); + } } } diff --git a/lib/dns/include/dns/resolver.h b/lib/dns/include/dns/resolver.h index e9258827e47..7f0dde65a55 100644 --- a/lib/dns/include/dns/resolver.h +++ b/lib/dns/include/dns/resolver.h @@ -578,6 +578,14 @@ dns_resolver_printbadcache(dns_resolver_t *resolver, FILE *fp); * \li resolver to be valid. */ +void +dns_resolver_setmaxvalidations(dns_resolver_t *resolver, uint32_t max); +void +dns_resolver_setmaxvalidationfails(dns_resolver_t *resolver, uint32_t max); +/*% + * Set maximum numbers of validations and maximum validation failures per fetch. + */ + void dns_resolver_setmaxdepth(dns_resolver_t *resolver, unsigned int maxdepth); unsigned int diff --git a/lib/dns/include/dns/validator.h b/lib/dns/include/dns/validator.h index d42cdca6376..0b0222c5c6d 100644 --- a/lib/dns/include/dns/validator.h +++ b/lib/dns/include/dns/validator.h @@ -53,6 +53,7 @@ #include #include +#include #include #include /* for dns_rdata_rrsig_t */ #include @@ -144,6 +145,13 @@ struct dns_validator { unsigned int authcount; unsigned int authfail; isc_stdtime_t start; + + bool digest_sha1; + bool supported_algorithm; + dns_rdata_t rdata; + bool resume; + uint32_t *nvalidations; + uint32_t *nfails; }; /*% @@ -161,6 +169,7 @@ dns_validator_create(dns_view_t *view, dns_name_t *name, dns_rdatatype_t type, dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset, dns_message_t *message, unsigned int options, isc_loop_t *loop, isc_job_cb cb, void *arg, + uint32_t *nvalidations, uint32_t *nfails, dns_validator_t **validatorp); /*%< * Start a DNSSEC validation. diff --git a/lib/dns/include/dst/dst.h b/lib/dns/include/dst/dst.h index c0912f3e67a..6c7a4e50db4 100644 --- a/lib/dns/include/dst/dst.h +++ b/lib/dns/include/dst/dst.h @@ -482,6 +482,10 @@ dst_key_tofile(const dst_key_t *key, int type, const char *directory); */ isc_result_t +dst_key_fromdns_ex(const dns_name_t *name, dns_rdataclass_t rdclass, + isc_buffer_t *source, isc_mem_t *mctx, bool no_rdata, + dst_key_t **keyp); +isc_result_t dst_key_fromdns(const dns_name_t *name, dns_rdataclass_t rdclass, isc_buffer_t *source, isc_mem_t *mctx, dst_key_t **keyp); /*%< diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c index f0f48d990df..442dfabce0a 100644 --- a/lib/dns/resolver.c +++ b/lib/dns/resolver.c @@ -178,6 +178,16 @@ */ #define MINIMUM_QUERY_TIMEOUT (MAX_SINGLE_QUERY_TIMEOUT + 1000U) +/* + * The default maximum number of validations and validation failures per-fetch + */ +#ifndef DEFAULT_MAX_VALIDATIONS +#define DEFAULT_MAX_VALIDATIONS 16 +#endif +#ifndef DEFAULT_MAX_VALIDATION_FAILURES +#define DEFAULT_MAX_VALIDATION_FAILURES 1 +#endif + /* The default time in seconds for the whole query to live. */ #ifndef DEFAULT_QUERY_TIMEOUT #define DEFAULT_QUERY_TIMEOUT MINIMUM_QUERY_TIMEOUT @@ -457,6 +467,9 @@ struct fetchctx { dns_adbaddrinfo_t *addrinfo; unsigned int depth; char clientstr[ISC_SOCKADDR_FORMATSIZE]; + + uint32_t nvalidations; + uint32_t nfails; }; #define FCTX_MAGIC ISC_MAGIC('F', '!', '!', '!') @@ -567,6 +580,9 @@ struct dns_resolver { atomic_bool exiting; atomic_bool priming; + atomic_uint_fast32_t maxvalidations; + atomic_uint_fast32_t maxvalidationfails; + /* Locked by lock. */ unsigned int spillat; /* clients-per-query */ @@ -961,7 +977,8 @@ valcreate(fetchctx_t *fctx, dns_message_t *message, dns_adbaddrinfo_t *addrinfo, result = dns_validator_create( fctx->res->view, name, type, rdataset, sigrdataset, message, - valoptions, fctx->loop, validated, valarg, &validator); + valoptions, fctx->loop, validated, valarg, &fctx->nvalidations, + &fctx->nfails, &validator); RUNTIME_CHECK(result == ISC_R_SUCCESS); inc_stats(fctx->res, dns_resstatscounter_val); if ((valoptions & DNS_VALIDATOR_DEFER) == 0) { @@ -4518,6 +4535,8 @@ fctx_create(dns_resolver_t *res, isc_loop_t *loop, const dns_name_t *name, .fwdpolicy = dns_fwdpolicy_none, .result = ISC_R_FAILURE, .loop = loop, + .nvalidations = atomic_load_relaxed(&res->maxvalidations), + .nfails = atomic_load_relaxed(&res->maxvalidationfails), }; isc_mem_attach(mctx, &fctx->mctx); @@ -9960,6 +9979,8 @@ dns_resolver_create(dns_view_t *view, isc_loopmgr_t *loopmgr, isc_nm_t *nm, .maxqueries = DEFAULT_MAX_QUERIES, .alternates = ISC_LIST_INITIALIZER, .nloops = isc_loopmgr_nloops(loopmgr), + .maxvalidations = DEFAULT_MAX_VALIDATIONS, + .maxvalidationfails = DEFAULT_MAX_VALIDATION_FAILURES, }; RTRACE("create"); @@ -10925,6 +10946,18 @@ dns_resolver_settimeout(dns_resolver_t *resolver, unsigned int timeout) { resolver->query_timeout = timeout; } +void +dns_resolver_setmaxvalidations(dns_resolver_t *resolver, uint32_t max) { + REQUIRE(VALID_RESOLVER(resolver)); + atomic_store(&resolver->maxvalidations, max); +} + +void +dns_resolver_setmaxvalidationfails(dns_resolver_t *resolver, uint32_t max) { + REQUIRE(VALID_RESOLVER(resolver)); + atomic_store(&resolver->maxvalidationfails, max); +} + void dns_resolver_setmaxdepth(dns_resolver_t *resolver, unsigned int maxdepth) { REQUIRE(VALID_RESOLVER(resolver)); diff --git a/lib/dns/validator.c b/lib/dns/validator.c index b679392c49e..9afd2ea11fc 100644 --- a/lib/dns/validator.c +++ b/lib/dns/validator.c @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -55,7 +56,7 @@ * validator_start -> proveunsecure * * \li When called with no rdataset or sigrdataset: - * validator_start -> validate_nx-> proveunsecure + * validator_start -> validate_nx -> proveunsecure * * validator_start: determine what type of validation to do. * validate_answer: attempt to perform a positive validation. @@ -66,29 +67,32 @@ #define VALIDATOR_MAGIC ISC_MAGIC('V', 'a', 'l', '?') #define VALID_VALIDATOR(v) ISC_MAGIC_VALID(v, VALIDATOR_MAGIC) -#define VALATTR_CANCELED 0x0002 /*%< Canceled. */ -#define VALATTR_TRIEDVERIFY \ - 0x0004 /*%< We have found a key and \ - * have attempted a verify. */ -#define VALATTR_COMPLETE 0x0008 /*%< Completion event sent. */ -#define VALATTR_INSECURITY 0x0010 /*%< Attempting proveunsecure. */ - -/*! - * NSEC proofs to be looked for. - */ -#define VALATTR_NEEDNOQNAME 0x00000100 -#define VALATTR_NEEDNOWILDCARD 0x00000200 -#define VALATTR_NEEDNODATA 0x00000400 +enum valattr { + VALATTR_CANCELED = 1 << 1, /*%< Canceled. */ + VALATTR_TRIEDVERIFY = 1 << 2, /*%< We have found a key and have + attempted a verify. */ + VALATTR_COMPLETE = 1 << 3, /*%< Completion event sent. */ + VALATTR_INSECURITY = 1 << 4, /*%< Attempting proveunsecure. */ + VALATTR_MAXVALIDATIONS = 1 << 5, /*%< Max validations quota */ + VALATTR_MAXVALIDATIONFAILS = 1 << 6, /*%< Max validation fails quota */ + + /*! + * NSEC proofs to be looked for. + */ + VALATTR_NEEDNOQNAME = 1 << 8, + VALATTR_NEEDNOWILDCARD = 1 << 9, + VALATTR_NEEDNODATA = 1 << 10, -/*! - * NSEC proofs that have been found. - */ -#define VALATTR_FOUNDNOQNAME 0x00001000 -#define VALATTR_FOUNDNOWILDCARD 0x00002000 -#define VALATTR_FOUNDNODATA 0x00004000 -#define VALATTR_FOUNDCLOSEST 0x00008000 -#define VALATTR_FOUNDOPTOUT 0x00010000 -#define VALATTR_FOUNDUNKNOWN 0x00020000 + /*! + * NSEC proofs that have been found. + */ + VALATTR_FOUNDNOQNAME = 1 << 12, + VALATTR_FOUNDNOWILDCARD = 1 << 13, + VALATTR_FOUNDNODATA = 1 << 14, + VALATTR_FOUNDCLOSEST = 1 << 15, + VALATTR_FOUNDOPTOUT = 1 << 16, + VALATTR_FOUNDUNKNOWN = 1 << 17, +}; #define NEEDNODATA(val) ((val->attributes & VALATTR_NEEDNODATA) != 0) #define NEEDNOQNAME(val) ((val->attributes & VALATTR_NEEDNOQNAME) != 0) @@ -105,17 +109,27 @@ #define NEGATIVE(r) (((r)->attributes & DNS_RDATASETATTR_NEGATIVE) != 0) #define NXDOMAIN(r) (((r)->attributes & DNS_RDATASETATTR_NXDOMAIN) != 0) +#define MAXVALIDATIONS(r) (((r)->attributes & VALATTR_MAXVALIDATIONS) != 0) +#define MAXVALIDATIONFAILS(r) \ + (((r)->attributes & VALATTR_MAXVALIDATIONFAILS) != 0) + static void destroy_validator(dns_validator_t *val); static isc_result_t select_signing_key(dns_validator_t *val, dns_rdataset_t *rdataset); +static void +resume_answer(void *arg); +static void +validate_async_done(dns_validator_t *val, isc_result_t result); static isc_result_t -validate_answer(dns_validator_t *val, bool resume); +validate_async_run(dns_validator_t *val, isc_job_cb cb); -static isc_result_t -validate_dnskey(dns_validator_t *val); +static void +validate_dnskey(void *arg); +static void +validate_dnskey_dsset_done(dns_validator_t *val, isc_result_t result); static isc_result_t validate_nx(dns_validator_t *val, bool resume); @@ -204,18 +218,9 @@ marksecure(dns_validator_t *val) { val->secure = true; } -static void -validator_done_cb(void *arg) { - dns_validator_t *val = arg; - val->cb(val); - dns_validator_detach(&val); -} - /* * Validator 'val' is finished; send the completion event to the loop * that called dns_validator_create(), with result `result`. - * - * Caller must be holding the validator lock. */ static void validator_done(dns_validator_t *val, isc_result_t result) { @@ -226,8 +231,7 @@ validator_done(dns_validator_t *val, isc_result_t result) { val->attributes |= VALATTR_COMPLETE; val->result = result; - dns_validator_ref(val); - isc_async_run(val->loop, validator_done_cb, val); + isc_async_run(val->loop, val->cb, val); } /*% @@ -349,6 +353,24 @@ isdelegation(dns_name_t *name, dns_rdataset_t *rdataset, return (found); } +static void +resume_answer_with_key(void *arg) { + dns_validator_t *val = arg; + dns_rdataset_t *rdataset = &val->frdataset; + + isc_result_t result = select_signing_key(val, rdataset); + if (result == ISC_R_SUCCESS) { + val->keyset = &val->frdataset; + } +} + +static void +resume_answer_with_key_done(void *arg) { + dns_validator_t *val = arg; + + resume_answer(val); +} + /*% * We have been asked to look for a key. * If found, resume the validation process. @@ -361,8 +383,6 @@ fetch_callback_dnskey(void *arg) { dns_rdataset_t *rdataset = &val->frdataset; isc_result_t eresult = resp->result; isc_result_t result; - isc_result_t saved_result; - dns_fetch_t *fetch = NULL; INSIST(resp->type == FETCHDONE); @@ -376,14 +396,18 @@ fetch_callback_dnskey(void *arg) { if (dns_rdataset_isassociated(&val->fsigrdataset)) { dns_rdataset_disassociate(&val->fsigrdataset); } - isc_mem_putanddetach(&resp->mctx, resp, sizeof(*resp)); validator_log(val, ISC_LOG_DEBUG(3), "in fetch_callback_dnskey"); - fetch = val->fetch; - val->fetch = NULL; + dns_resolver_destroyfetch(&val->fetch); + if (CANCELED(val)) { - validator_done(val, ISC_R_CANCELED); - } else if (eresult == ISC_R_SUCCESS || eresult == DNS_R_NCACHENXRRSET) { + result = ISC_R_CANCELED; + goto cleanup; + } + + switch (eresult) { + case ISC_R_SUCCESS: + case DNS_R_NCACHENXRRSET: /* * We have an answer to our DNSKEY query. Either the DNSKEY * RRset or a NODATA response. @@ -398,41 +422,23 @@ fetch_callback_dnskey(void *arg) { if (eresult == ISC_R_SUCCESS && rdataset->trust >= dns_trust_secure) { - result = select_signing_key(val, rdataset); - if (result == ISC_R_SUCCESS) { - val->keyset = &val->frdataset; - } - } - result = validate_answer(val, true); - if (result == DNS_R_NOVALIDSIG && - (val->attributes & VALATTR_TRIEDVERIFY) == 0) - { - saved_result = result; - validator_log(val, ISC_LOG_DEBUG(3), - "falling back to insecurity proof"); - result = proveunsecure(val, false, false); - if (result == DNS_R_NOTINSECURE) { - result = saved_result; - } - } - if (result != DNS_R_WAIT) { - validator_done(val, result); + isc_work_enqueue(val->loop, resume_answer_with_key, + resume_answer, val); + result = DNS_R_WAIT; + } else { + result = validate_async_run(val, resume_answer); } - } else { + break; + default: validator_log(val, ISC_LOG_DEBUG(3), "fetch_callback_dnskey: got %s", isc_result_totext(eresult)); - if (eresult == ISC_R_CANCELED) { - validator_done(val, eresult); - } else { - validator_done(val, DNS_R_BROKENCHAIN); - } - } - - if (fetch != NULL) { - dns_resolver_destroyfetch(&fetch); + result = DNS_R_BROKENCHAIN; } +cleanup: + isc_mem_putanddetach(&resp->mctx, resp, sizeof(*resp)); + validate_async_done(val, result); dns_validator_detach(&val); } @@ -447,7 +453,6 @@ fetch_callback_ds(void *arg) { dns_rdataset_t *rdataset = &val->frdataset; isc_result_t eresult = resp->result; isc_result_t result; - dns_fetch_t *fetch = NULL; bool trustchain; INSIST(resp->type == FETCHDONE); @@ -470,28 +475,16 @@ fetch_callback_ds(void *arg) { } validator_log(val, ISC_LOG_DEBUG(3), "in fetch_callback_ds"); - fetch = val->fetch; - val->fetch = NULL; + dns_resolver_destroyfetch(&val->fetch); if (CANCELED(val)) { - validator_done(val, ISC_R_CANCELED); - goto done; + result = ISC_R_CANCELED; + goto cleanup; } - switch (eresult) { - case DNS_R_NXDOMAIN: - case DNS_R_NCACHENXDOMAIN: - /* - * These results only make sense if we're attempting - * an insecurity proof, not when walking a chain of trust. - */ - if (trustchain) { - goto unexpected; - } - - FALLTHROUGH; - case ISC_R_SUCCESS: - if (trustchain) { + if (trustchain) { + switch (eresult) { + case ISC_R_SUCCESS: /* * We looked for a DS record as part of * following a key chain upwards; resume following @@ -501,29 +494,12 @@ fetch_callback_ds(void *arg) { "dsset with trust %s", dns_trust_totext(rdataset->trust)); val->dsset = &val->frdataset; - result = validate_dnskey(val); - if (result != DNS_R_WAIT) { - validator_done(val, result); - } - } else { - /* - * There is a DS which may or may not be a zone cut. - * In either case we are still in a secure zone, - * so keep looking for the break in the chain - * of trust. - */ - result = proveunsecure(val, (eresult == ISC_R_SUCCESS), - true); - if (result != DNS_R_WAIT) { - validator_done(val, result); - } - } - break; - case DNS_R_CNAME: - case DNS_R_NXRRSET: - case DNS_R_NCACHENXRRSET: - case DNS_R_SERVFAIL: /* RFC 1034 parent? */ - if (trustchain) { + result = validate_async_run(val, validate_dnskey); + break; + case DNS_R_CNAME: + case DNS_R_NXRRSET: + case DNS_R_NCACHENXRRSET: + case DNS_R_SERVFAIL: /* RFC 1034 parent? */ /* * Failed to find a DS while following the * chain of trust; now we need to prove insecurity. @@ -532,54 +508,69 @@ fetch_callback_ds(void *arg) { "falling back to insecurity proof (%s)", isc_result_totext(eresult)); result = proveunsecure(val, false, false); - if (result != DNS_R_WAIT) { - validator_done(val, result); - } - } else if (eresult == DNS_R_SERVFAIL) { - goto unexpected; - } else if (eresult != DNS_R_CNAME && - isdelegation(resp->foundname, &val->frdataset, - eresult)) - { + break; + default: + validator_log(val, ISC_LOG_DEBUG(3), + "fetch_callback_ds: got %s", + isc_result_totext(eresult)); + result = DNS_R_BROKENCHAIN; + break; + } + } else { + switch (eresult) { + case DNS_R_NXDOMAIN: + case DNS_R_NCACHENXDOMAIN: /* - * Failed to find a DS while trying to prove - * insecurity. If this is a zone cut, that - * means we're insecure. + * These results only make sense if we're attempting + * an insecurity proof, not when walking a chain of + * trust. */ - result = markanswer(val, "fetch_callback_ds", - "no DS and this is a delegation"); - validator_done(val, result); - } else { + + result = proveunsecure(val, false, true); + break; + case ISC_R_SUCCESS: + /* + * There is a DS which may or may not be a zone cut. + * In either case we are still in a secure zone, + * so keep looking for the break in the chain + * of trust. + */ + result = proveunsecure(val, true, true); + break; + case DNS_R_NXRRSET: + case DNS_R_NCACHENXRRSET: + if (isdelegation(resp->foundname, &val->frdataset, + eresult)) + { + /* + * Failed to find a DS while trying to prove + * insecurity. If this is a zone cut, that + * means we're insecure. + */ + result = markanswer( + val, "fetch_callback_ds", + "no DS and this is a delegation"); + break; + } + FALLTHROUGH; + case DNS_R_CNAME: /* * Not a zone cut, so we have to keep looking for * the break point in the chain of trust. */ result = proveunsecure(val, false, true); - if (result != DNS_R_WAIT) { - validator_done(val, result); - } - } - break; - - default: - unexpected: - validator_log(val, ISC_LOG_DEBUG(3), - "fetch_callback_ds: got %s", - isc_result_totext(eresult)); - if (eresult == ISC_R_CANCELED) { - validator_done(val, eresult); - } else { - validator_done(val, DNS_R_BROKENCHAIN); + break; + default: + validator_log(val, ISC_LOG_DEBUG(3), + "fetch_callback_ds: got %s", + isc_result_totext(eresult)); + result = DNS_R_BROKENCHAIN; } } -done: +cleanup: isc_mem_putanddetach(&resp->mctx, resp, sizeof(*resp)); - - if (fetch != NULL) { - dns_resolver_destroyfetch(&fetch); - } - + validate_async_done(val, result); dns_validator_detach(&val); } @@ -592,52 +583,43 @@ static void validator_callback_dnskey(void *arg) { dns_validator_t *subvalidator = (dns_validator_t *)arg; dns_validator_t *val = subvalidator->parent; - isc_result_t result; - isc_result_t eresult = subvalidator->result; - isc_result_t saved_result; + isc_result_t result = subvalidator->result; val->subvalidator = NULL; - subvalidator->parent = NULL; - validator_log(val, ISC_LOG_DEBUG(3), "in validator_callback_dnskey"); if (CANCELED(val)) { - validator_done(val, ISC_R_CANCELED); - } else if (eresult == ISC_R_SUCCESS) { + result = ISC_R_CANCELED; + goto cleanup; + } + + validator_log(val, ISC_LOG_DEBUG(3), "in validator_callback_dnskey"); + if (result == ISC_R_SUCCESS) { validator_log(val, ISC_LOG_DEBUG(3), "keyset with trust %s", dns_trust_totext(val->frdataset.trust)); /* * Only extract the dst key if the keyset is secure. */ if (val->frdataset.trust >= dns_trust_secure) { - (void)select_signing_key(val, &val->frdataset); - } - result = validate_answer(val, true); - if (result == DNS_R_NOVALIDSIG && - (val->attributes & VALATTR_TRIEDVERIFY) == 0) - { - saved_result = result; - validator_log(val, ISC_LOG_DEBUG(3), - "falling back to insecurity proof"); - result = proveunsecure(val, false, false); - if (result == DNS_R_NOTINSECURE) { - result = saved_result; - } - } - if (result != DNS_R_WAIT) { - validator_done(val, result); + isc_work_enqueue(val->loop, resume_answer_with_key, + resume_answer_with_key_done, val); + result = DNS_R_WAIT; + } else { + result = validate_async_run(val, resume_answer); } } else { - if (eresult != DNS_R_BROKENCHAIN) { + if (result != DNS_R_BROKENCHAIN) { expire_rdatasets(val); } validator_log(val, ISC_LOG_DEBUG(3), "validator_callback_dnskey: got %s", - isc_result_totext(eresult)); - validator_done(val, DNS_R_BROKENCHAIN); + isc_result_totext(result)); + result = DNS_R_BROKENCHAIN; } +cleanup: + dns_validator_detach(&subvalidator->parent); dns_validator_destroy(&subvalidator); - dns_validator_detach(&val); + validate_async_done(val, result); } /*% @@ -653,12 +635,14 @@ validator_callback_ds(void *arg) { isc_result_t eresult = subvalidator->result; val->subvalidator = NULL; - subvalidator->parent = NULL; - validator_log(val, ISC_LOG_DEBUG(3), "in validator_callback_ds"); if (CANCELED(val)) { - validator_done(val, ISC_R_CANCELED); - } else if (eresult == ISC_R_SUCCESS) { + result = ISC_R_CANCELED; + goto cleanup; + } + + validator_log(val, ISC_LOG_DEBUG(3), "in validator_callback_ds"); + if (eresult == ISC_R_SUCCESS) { bool have_dsset; dns_name_t *name; validator_log(val, ISC_LOG_DEBUG(3), "%s with trust %s", @@ -678,10 +662,7 @@ validator_callback_ds(void *arg) { } else if ((val->attributes & VALATTR_INSECURITY) != 0) { result = proveunsecure(val, have_dsset, true); } else { - result = validate_dnskey(val); - } - if (result != DNS_R_WAIT) { - validator_done(val, result); + result = validate_async_run(val, validate_dnskey); } } else { if (eresult != DNS_R_BROKENCHAIN) { @@ -690,11 +671,13 @@ validator_callback_ds(void *arg) { validator_log(val, ISC_LOG_DEBUG(3), "validator_callback_ds: got %s", isc_result_totext(eresult)); - validator_done(val, DNS_R_BROKENCHAIN); + result = DNS_R_BROKENCHAIN; } +cleanup: + dns_validator_detach(&subvalidator->parent); dns_validator_destroy(&subvalidator); - dns_validator_detach(&val); + validate_async_done(val, result); } /*% @@ -713,16 +696,16 @@ validator_callback_cname(void *arg) { val->subvalidator = NULL; - validator_log(val, ISC_LOG_DEBUG(3), "in validator_callback_cname"); if (CANCELED(val)) { - validator_done(val, ISC_R_CANCELED); - } else if (eresult == ISC_R_SUCCESS) { + result = ISC_R_CANCELED; + goto cleanup; + } + + validator_log(val, ISC_LOG_DEBUG(3), "in validator_callback_cname"); + if (eresult == ISC_R_SUCCESS) { validator_log(val, ISC_LOG_DEBUG(3), "cname with trust %s", dns_trust_totext(val->frdataset.trust)); result = proveunsecure(val, false, true); - if (result != DNS_R_WAIT) { - validator_done(val, result); - } } else { if (eresult != DNS_R_BROKENCHAIN) { expire_rdatasets(val); @@ -730,11 +713,13 @@ validator_callback_cname(void *arg) { validator_log(val, ISC_LOG_DEBUG(3), "validator_callback_cname: got %s", isc_result_totext(eresult)); - validator_done(val, DNS_R_BROKENCHAIN); + result = DNS_R_BROKENCHAIN; } +cleanup: + dns_validator_detach(&subvalidator->parent); dns_validator_destroy(&subvalidator); - dns_validator_detach(&val); + validate_async_done(val, result); } /*% @@ -749,30 +734,19 @@ validator_callback_nsec(void *arg) { dns_validator_t *subvalidator = (dns_validator_t *)arg; dns_validator_t *val = subvalidator->parent; dns_rdataset_t *rdataset = subvalidator->rdataset; - isc_result_t result = subvalidator->result; + isc_result_t result; + isc_result_t eresult = subvalidator->result; bool exists, data; val->subvalidator = NULL; - validator_log(val, ISC_LOG_DEBUG(3), "in validator_callback_nsec"); if (CANCELED(val)) { - validator_done(val, ISC_R_CANCELED); - } else if (result != ISC_R_SUCCESS) { - validator_log(val, ISC_LOG_DEBUG(3), - "validator_callback_nsec: got %s", - isc_result_totext(result)); - if (result == DNS_R_BROKENCHAIN) { - val->authfail++; - } - if (result == ISC_R_CANCELED) { - validator_done(val, result); - } else { - result = validate_nx(val, true); - if (result != DNS_R_WAIT) { - validator_done(val, result); - } - } - } else { + result = ISC_R_CANCELED; + goto cleanup; + } + + validator_log(val, ISC_LOG_DEBUG(3), "in validator_callback_nsec"); + if (eresult == ISC_R_SUCCESS) { dns_name_t **proofs = val->proofs; dns_name_t *wild = dns_fixedname_name(&val->wild); @@ -824,13 +798,27 @@ validator_callback_nsec(void *arg) { } result = validate_nx(val, true); - if (result != DNS_R_WAIT) { - validator_done(val, result); + } else { + validator_log(val, ISC_LOG_DEBUG(3), + "validator_callback_nsec: got %s", + isc_result_totext(eresult)); + switch (eresult) { + case ISC_R_CANCELED: + case ISC_R_SHUTTINGDOWN: + result = eresult; + break; + case DNS_R_BROKENCHAIN: + val->authfail++; + FALLTHROUGH; + default: + result = validate_nx(val, true); } } +cleanup: + dns_validator_detach(&subvalidator->parent); dns_validator_destroy(&subvalidator); - dns_validator_detach(&val); + validate_async_done(val, result); } /*% @@ -949,7 +937,6 @@ create_fetch(dns_validator_t *val, dns_name_t *name, dns_rdatatype_t type, validator_logcreate(val, name, type, caller, "fetch"); dns_validator_ref(val); - result = dns_resolver_createfetch( val->view->resolver, name, type, NULL, NULL, NULL, NULL, 0, fopts, 0, NULL, val->loop, callback, val, &val->frdataset, @@ -987,9 +974,9 @@ create_validator(dns_validator_t *val, dns_name_t *name, dns_rdatatype_t type, (DNS_VALIDATOR_NOCDFLAG | DNS_VALIDATOR_NONTA)); validator_logcreate(val, name, type, caller, "validator"); - result = dns_validator_create(val->view, name, type, rdataset, sig, - NULL, vopts, val->loop, cb, val, - &val->subvalidator); + result = dns_validator_create( + val->view, name, type, rdataset, sig, NULL, vopts, val->loop, + cb, val, val->nvalidations, val->nfails, &val->subvalidator); if (result == ISC_R_SUCCESS) { dns_validator_attach(val, &val->subvalidator->parent); val->subvalidator->depth = val->depth + 1; @@ -1016,59 +1003,59 @@ select_signing_key(dns_validator_t *val, dns_rdataset_t *rdataset) { isc_buffer_t b; dns_rdata_t rdata = DNS_RDATA_INIT; dst_key_t *oldkey = val->key; - bool foundold; + bool no_rdata = false; if (oldkey == NULL) { - foundold = true; + result = dns_rdataset_first(rdataset); } else { - foundold = false; + dst_key_free(&oldkey); val->key = NULL; + result = dns_rdataset_next(rdataset); } - - result = dns_rdataset_first(rdataset); if (result != ISC_R_SUCCESS) { - goto failure; + goto done; } + do { dns_rdataset_current(rdataset, &rdata); isc_buffer_init(&b, rdata.data, rdata.length); isc_buffer_add(&b, rdata.length); INSIST(val->key == NULL); - result = dst_key_fromdns(&siginfo->signer, rdata.rdclass, &b, - val->view->mctx, &val->key); + result = dst_key_fromdns_ex(&siginfo->signer, rdata.rdclass, &b, + val->view->mctx, no_rdata, + &val->key); if (result == ISC_R_SUCCESS) { if (siginfo->algorithm == (dns_secalg_t)dst_key_alg(val->key) && siginfo->keyid == (dns_keytag_t)dst_key_id(val->key) && + (dst_key_flags(val->key) & DNS_KEYFLAG_REVOKE) == + 0 && dst_key_iszonekey(val->key)) { - if (foundold) { - /* - * This is the key we're looking for. - */ - return (ISC_R_SUCCESS); - } else if (dst_key_compare(oldkey, val->key)) { - foundold = true; - dst_key_free(&oldkey); + if (no_rdata) { + /* Retry with full key */ + dns_rdata_reset(&rdata); + dst_key_free(&val->key); + no_rdata = false; + continue; } + /* This is the key we're looking for. */ + goto done; } dst_key_free(&val->key); } dns_rdata_reset(&rdata); result = dns_rdataset_next(rdataset); + no_rdata = true; } while (result == ISC_R_SUCCESS); +done: if (result == ISC_R_NOMORE) { result = ISC_R_NOTFOUND; } -failure: - if (oldkey != NULL) { - dst_key_free(&oldkey); - } - return (result); } @@ -1181,15 +1168,22 @@ seek_dnskey(dns_validator_t *val) { validator_log(val, ISC_LOG_DEBUG(3), "keyset with trust %s", dns_trust_totext(val->frdataset.trust)); - result = select_signing_key(val, val->keyset); - if (result != ISC_R_SUCCESS) { - /* - * Either the key we're looking for is not - * in the rrset, or something bad happened. - * Give up. - */ - result = DNS_R_CONTINUE; + + /* + * Cleanup before passing control to the offload thread + */ + if (dns_rdataset_isassociated(&val->frdataset) && + val->keyset != &val->frdataset) + { + dns_rdataset_disassociate(&val->frdataset); + } + if (dns_rdataset_isassociated(&val->fsigrdataset)) { + dns_rdataset_disassociate(&val->fsigrdataset); } + + isc_work_enqueue(val->loop, resume_answer_with_key, + resume_answer_with_key_done, val); + return (DNS_R_WAIT); } break; @@ -1246,20 +1240,47 @@ compute_keytag(dns_rdata_t *rdata) { return (dst_region_computeid(&r)); } +static bool +over_max_validations(dns_validator_t *val) { + if (val->nvalidations == NULL) { + return (false); + } + if (*val->nvalidations > 0) { + (*val->nvalidations)--; + return (false); + } + + val->attributes |= VALATTR_MAXVALIDATIONS; + return (true); +} + +static bool +over_max_fails(dns_validator_t *val) { + if (val->nfails == NULL) { + return (false); + } + if (*val->nfails > 0) { + (*val->nfails)--; + return (false); + } + + val->attributes |= VALATTR_MAXVALIDATIONFAILS; + return (true); +} + /*% * Is the DNSKEY rrset in val->rdataset self-signed? */ -static bool +static isc_result_t selfsigned_dnskey(dns_validator_t *val) { dns_rdataset_t *rdataset = val->rdataset; dns_rdataset_t *sigrdataset = val->sigrdataset; dns_name_t *name = val->name; isc_result_t result; isc_mem_t *mctx = val->view->mctx; - bool answer = false; if (rdataset->type != dns_rdatatype_dnskey) { - return (false); + return (DNS_R_NOKEYMATCH); } for (result = dns_rdataset_first(rdataset); result == ISC_R_SUCCESS; @@ -1301,8 +1322,7 @@ selfsigned_dnskey(dns_validator_t *val) { * This will be verified later. */ if ((key.flags & DNS_KEYFLAG_REVOKE) == 0) { - answer = true; - continue; + return (ISC_R_SUCCESS); } result = dns_dnssec_keyfromrdata(name, &keyrdata, mctx, @@ -1318,6 +1338,10 @@ selfsigned_dnskey(dns_validator_t *val) { if (DNS_TRUST_PENDING(rdataset->trust) && dns_view_istrusted(val->view, name, &key)) { + if (over_max_validations(val)) { + dst_key_free(&dstkey); + return (ISC_R_QUOTA); + } result = dns_dnssec_verify( name, rdataset, dstkey, true, val->view->maxbits, mctx, &sigrdata, @@ -1329,6 +1353,9 @@ selfsigned_dnskey(dns_validator_t *val) { * good. */ dns_view_untrust(val->view, name, &key); + } else if (over_max_fails(val)) { + dst_key_free(&dstkey); + return (ISC_R_QUOTA); } } else if (rdataset->trust >= dns_trust_secure) { /* @@ -1342,7 +1369,7 @@ selfsigned_dnskey(dns_validator_t *val) { } } - return (answer); + return (DNS_R_NOKEYMATCH); } /*% @@ -1365,6 +1392,9 @@ verify(dns_validator_t *val, dst_key_t *key, dns_rdata_t *rdata, val->attributes |= VALATTR_TRIEDVERIFY; wild = dns_fixedname_initname(&fixed); again: + if (over_max_validations(val)) { + return (ISC_R_QUOTA); + } result = dns_dnssec_verify(val->name, val->rdataset, key, ignore, val->view->maxbits, val->view->mctx, rdata, wild); @@ -1408,6 +1438,10 @@ verify(dns_validator_t *val, dst_key_t *key, dns_rdata_t *rdata, } result = ISC_R_SUCCESS; } + + if (result != ISC_R_SUCCESS && over_max_fails(val)) { + result = ISC_R_QUOTA; + } return (result); } @@ -1420,132 +1454,323 @@ verify(dns_validator_t *val, dst_key_t *key, dns_rdata_t *rdata, * for an event. * \li Other return codes are possible and all indicate failure. */ -static isc_result_t -validate_answer(dns_validator_t *val, bool resume) { - isc_result_t result, vresult = DNS_R_NOVALIDSIG; - dns_rdata_t rdata = DNS_RDATA_INIT; + +static void +validate_answer_iter_next(void *arg); +static void +validate_answer_process(void *arg); +static void +validate_answer_iter_done(dns_validator_t *val, isc_result_t result); + +static void +validate_answer_iter_start(dns_validator_t *val) { + isc_result_t result = ISC_R_SUCCESS; /* * Caller must be holding the validator lock. */ - if (resume) { - /* - * We already have a sigrdataset. - */ + if (CANCELED(val)) { + result = ISC_R_CANCELED; + goto cleanup; + } + + if (val->resume) { + /* We already have a sigrdataset. */ result = ISC_R_SUCCESS; validator_log(val, ISC_LOG_DEBUG(3), "resuming validate"); } else { result = dns_rdataset_first(val->sigrdataset); } - for (; result == ISC_R_SUCCESS; - result = dns_rdataset_next(val->sigrdataset)) - { - dns_rdata_reset(&rdata); - dns_rdataset_current(val->sigrdataset, &rdata); - if (val->siginfo == NULL) { - val->siginfo = isc_mem_get(val->view->mctx, - sizeof(*val->siginfo)); - } - result = dns_rdata_tostruct(&rdata, val->siginfo, NULL); - if (result != ISC_R_SUCCESS) { - return (result); - } +cleanup: + if (result != ISC_R_SUCCESS) { + validate_answer_iter_done(val, result); + return; + } - /* - * At this point we could check that the signature algorithm - * was known and "sufficiently good". - */ - if (!dns_resolver_algorithm_supported(val->view->resolver, - val->name, - val->siginfo->algorithm)) - { - resume = false; - continue; - } + result = validate_async_run(val, validate_answer_process); + INSIST(result == DNS_R_WAIT); +} - if (!resume) { - result = seek_dnskey(val); - if (result == DNS_R_CONTINUE) { - continue; /* Try the next SIG RR. */ - } - if (result != ISC_R_SUCCESS) { - return (result); - } - } +static void +validate_answer_iter_next(void *arg) { + dns_validator_t *val = arg; + isc_result_t result; - /* - * There isn't a secure DNSKEY for this signature so move - * onto the next RRSIG. - */ - if (val->key == NULL) { - resume = false; - continue; + if (CANCELED(val)) { + result = ISC_R_CANCELED; + goto cleanup; + } + + val->resume = false; + result = dns_rdataset_next(val->sigrdataset); + +cleanup: + if (result != ISC_R_SUCCESS) { + validate_answer_iter_done(val, result); + return; + } + + (void)validate_async_run(val, validate_answer_process); +} + +static void +validate_answer_finish(void *arg); + +static void +validate_answer_signing_key(void *arg) { + dns_validator_t *val = arg; + isc_result_t result = ISC_R_NOTFOUND; + + if (CANCELED(val)) { + val->result = ISC_R_CANCELED; + } else { + val->result = verify(val, val->key, &val->rdata, + val->siginfo->keyid); + } + + switch (val->result) { + case ISC_R_CANCELED: /* Validation was canceled */ + case ISC_R_SHUTTINGDOWN: /* Server shutting down */ + case ISC_R_QUOTA: /* Validation fails quota reached */ + case ISC_R_SUCCESS: /* We found our valid signature, we are done! */ + if (val->key != NULL) { + dst_key_free(&val->key); + val->key = NULL; } - do { - isc_result_t tresult; - vresult = verify(val, val->key, &rdata, - val->siginfo->keyid); - if (vresult == ISC_R_SUCCESS) { - break; - } + break; + default: + /* Select next signing key */ + result = select_signing_key(val, val->keyset); + break; + } - tresult = select_signing_key(val, val->keyset); - if (tresult != ISC_R_SUCCESS) { - break; - } - } while (1); - if (vresult != ISC_R_SUCCESS) { - validator_log(val, ISC_LOG_DEBUG(3), - "failed to verify rdataset"); - } else { - dns_rdataset_trimttl(val->rdataset, val->sigrdataset, - val->siginfo, val->start, - val->view->acceptexpired); + if (result == ISC_R_SUCCESS) { + INSIST(val->key != NULL); + } else { + INSIST(val->key == NULL); + } +} + +static void +validate_answer_signing_key_done(void *arg) { + dns_validator_t *val = arg; + + if (CANCELED(val)) { + val->result = ISC_R_CANCELED; + } else if (val->key != NULL) { + /* Process with next key if we selected one */ + isc_work_enqueue(val->loop, validate_answer_signing_key, + validate_answer_signing_key_done, val); + return; + } + + validate_answer_finish(val); +} + +static void +validate_answer_process(void *arg) { + dns_validator_t *val = arg; + isc_result_t result; + + if (CANCELED(val)) { + result = ISC_R_CANCELED; + goto cleanup; + } + + dns_rdata_reset(&val->rdata); + + dns_rdataset_current(val->sigrdataset, &val->rdata); + if (val->siginfo == NULL) { + val->siginfo = isc_mem_get(val->view->mctx, + sizeof(*val->siginfo)); + } + result = dns_rdata_tostruct(&val->rdata, val->siginfo, NULL); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + /* + * At this point we could check that the signature algorithm + * was known and "sufficiently good". + */ + if (!dns_resolver_algorithm_supported(val->view->resolver, val->name, + val->siginfo->algorithm)) + { + goto next_key; + } + + if (!val->resume) { + result = seek_dnskey(val); + switch (result) { + case ISC_R_SUCCESS: + break; + case DNS_R_CONTINUE: + goto next_key; + case DNS_R_WAIT: + goto cleanup; + default: + goto cleanup; } + } + + /* + * There isn't a secure DNSKEY for this signature so move + * onto the next RRSIG. + */ + if (val->key == NULL) { + val->resume = false; + goto next_key; + } + + isc_work_enqueue(val->loop, validate_answer_signing_key, + validate_answer_signing_key_done, val); + return; + +next_key: + result = validate_async_run(val, validate_answer_iter_next); + goto cleanup; + +cleanup: + validate_async_done(val, result); +} + +static void +validate_answer_finish(void *arg) { + dns_validator_t *val = arg; + isc_result_t result = ISC_R_UNSET; + + if (val->result != ISC_R_SUCCESS) { + validator_log(val, ISC_LOG_DEBUG(3), + "failed to verify rdataset: %s", + isc_result_totext(val->result)); + } else { + dns_rdataset_trimttl(val->rdataset, val->sigrdataset, + val->siginfo, val->start, + val->view->acceptexpired); + } - if (val->key != NULL) { - dst_key_free(&val->key); - } - if (val->keyset != NULL) { - dns_rdataset_disassociate(val->keyset); - val->keyset = NULL; - } + if (val->key != NULL) { + dst_key_free(&val->key); val->key = NULL; - if (NEEDNOQNAME(val)) { - if (val->message == NULL) { - validator_log(val, ISC_LOG_DEBUG(3), - "no message available " - "for noqname proof"); - return (DNS_R_NOVALIDSIG); - } + } + if (val->keyset != NULL) { + dns_rdataset_disassociate(val->keyset); + val->keyset = NULL; + } + + switch (val->result) { + case ISC_R_CANCELED: + /* The validation was canceled */ + validator_log(val, ISC_LOG_DEBUG(3), "validation was canceled"); + validate_async_done(val, val->result); + return; + case ISC_R_SHUTTINGDOWN: + validator_log(val, ISC_LOG_DEBUG(3), "server is shutting down"); + validate_async_done(val, val->result); + return; + case ISC_R_QUOTA: + if (MAXVALIDATIONS(val)) { validator_log(val, ISC_LOG_DEBUG(3), - "looking for noqname proof"); - return (validate_nx(val, false)); - } else if (vresult == ISC_R_SUCCESS) { - marksecure(val); + "maximum number of validations exceeded"); + } else if (MAXVALIDATIONFAILS(val)) { validator_log(val, ISC_LOG_DEBUG(3), - "marking as secure, " - "noqname proof not needed"); - return (ISC_R_SUCCESS); + "maximum number of validation failures " + "exceeded"); } else { + validator_log( + val, ISC_LOG_DEBUG(3), + "unknown error: validation quota exceeded"); + } + validate_async_done(val, val->result); + return; + default: + break; + } + + if (NEEDNOQNAME(val)) { + if (val->message == NULL) { validator_log(val, ISC_LOG_DEBUG(3), - "verify failure: %s", - isc_result_totext(result)); - resume = false; + "no message available for noqname proof"); + validate_async_done(val, DNS_R_NOVALIDSIG); + return; } + + validator_log(val, ISC_LOG_DEBUG(3), + "looking for noqname proof"); + result = validate_nx(val, false); + validate_async_done(val, result); + return; + } + + if (val->result == ISC_R_SUCCESS) { + marksecure(val); + validator_log(val, ISC_LOG_DEBUG(3), + "marking as secure, noqname proof not needed"); + validate_async_done(val, val->result); + return; } + + validator_log(val, ISC_LOG_DEBUG(3), "verify failure: %s", + isc_result_totext(val->result)); + (void)validate_async_run(val, validate_answer_iter_next); +} + +static void +validate_answer_iter_done(dns_validator_t *val, isc_result_t result) { if (result != ISC_R_NOMORE) { validator_log(val, ISC_LOG_DEBUG(3), "failed to iterate signatures: %s", isc_result_totext(result)); - return (result); + validate_async_done(val, result); + return; } validator_log(val, ISC_LOG_INFO, "no valid signature found"); - return (vresult); + validate_async_done(val, val->result); +} + +static void +resume_answer(void *arg) { + dns_validator_t *val = arg; + val->resume = true; + validate_answer_iter_start(val); +} + +static void +validate_answer(void *arg) { + dns_validator_t *val = arg; + val->resume = false; + validate_answer_iter_start(val); +} + +static isc_result_t +validate_async_run(dns_validator_t *val, isc_job_cb cb) { + isc_async_run(val->loop, cb, val); + return (DNS_R_WAIT); +} + +static void +validate_async_done(dns_validator_t *val, isc_result_t result) { + if (result == DNS_R_NOVALIDSIG && + (val->attributes & VALATTR_TRIEDVERIFY) == 0) + { + isc_result_t saved_result = result; + validator_log(val, ISC_LOG_DEBUG(3), + "falling back to insecurity proof"); + result = proveunsecure(val, false, false); + if (result == DNS_R_NOTINSECURE) { + result = saved_result; + } + } + + if (result != DNS_R_WAIT) { + /* We are still continuing */ + validator_done(val, result); + dns_validator_detach(&val); + } } /*% @@ -1582,7 +1807,7 @@ check_signer(dns_validator_t *val, dns_rdata_t *keyrdata, uint16_t keyid, } } result = verify(val, dstkey, &rdata, sig.keyid); - if (result == ISC_R_SUCCESS) { + if (result == ISC_R_SUCCESS || result == ISC_R_QUOTA) { break; } } @@ -1683,27 +1908,163 @@ get_dsset(dns_validator_t *val, dns_name_t *tname, isc_result_t *resp) { return (DNS_R_CONTINUE); } -/*% - * Attempts positive response validation of an RRset containing zone keys - * (i.e. a DNSKEY rrset). - * - * Caller must be holding the validator lock. - * - * Returns: - * \li ISC_R_SUCCESS Validation completed successfully - * \li DNS_R_WAIT Validation has started but is waiting - * for an event. - * \li Other return codes are possible and all indicate failure. - */ +static void +validate_dnskey_dsset_done(dns_validator_t *val, isc_result_t result) { + if (result == ISC_R_SUCCESS) { + marksecure(val); + validator_log(val, ISC_LOG_DEBUG(3), "marking as secure (DS)"); + } else if (result == ISC_R_NOMORE && !val->supported_algorithm) { + validator_log(val, ISC_LOG_DEBUG(3), + "no supported algorithm/digest (DS)"); + result = markanswer(val, "validate_dnskey (3)", + "no supported algorithm/digest (DS)"); + } else { + validator_log(val, ISC_LOG_INFO, + "no valid signature found (DS)"); + result = DNS_R_NOVALIDSIG; + } + + if (val->dsset == &val->fdsset) { + val->dsset = NULL; + dns_rdataset_disassociate(&val->fdsset); + } + + validate_async_done(val, result); +} + static isc_result_t -validate_dnskey(dns_validator_t *val) { - isc_result_t result; +validate_dnskey_dsset(dns_validator_t *val) { dns_rdata_t dsrdata = DNS_RDATA_INIT; dns_rdata_t keyrdata = DNS_RDATA_INIT; + isc_result_t result; + dns_rdata_ds_t ds; + + dns_rdata_reset(&dsrdata); + dns_rdataset_current(val->dsset, &dsrdata); + result = dns_rdata_tostruct(&dsrdata, &ds, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + if (ds.digest_type == DNS_DSDIGEST_SHA1 && val->digest_sha1 == false) { + return (DNS_R_BADALG); + } + + if (!dns_resolver_ds_digest_supported(val->view->resolver, val->name, + ds.digest_type)) + { + return (DNS_R_BADALG); + } + + if (!dns_resolver_algorithm_supported(val->view->resolver, val->name, + ds.algorithm)) + { + return (DNS_R_BADALG); + } + + val->supported_algorithm = true; + + /* + * Find the DNSKEY matching the DS... + */ + result = dns_dnssec_matchdskey(val->name, &dsrdata, val->rdataset, + &keyrdata); + if (result != ISC_R_SUCCESS) { + validator_log(val, ISC_LOG_DEBUG(3), "no DNSKEY matching DS"); + return (DNS_R_NOKEYMATCH); + } + + /* + * ... and check that it signed the DNSKEY RRset. + */ + result = check_signer(val, &keyrdata, ds.key_tag, ds.algorithm); + if (result != ISC_R_SUCCESS) { + validator_log(val, ISC_LOG_DEBUG(3), + "no RRSIG matching DS key"); + + return (result); + } + + return (ISC_R_SUCCESS); +} + +static void +validate_dnskey_dsset_next(void *arg) { + dns_validator_t *val = arg; + + if (CANCELED(val)) { + val->result = ISC_R_CANCELED; + } else { + val->result = dns_rdataset_next(val->dsset); + } + + if (val->result == ISC_R_SUCCESS) { + /* continue async run */ + val->result = validate_dnskey_dsset(val); + } +} + +static void +validate_dnskey_dsset_next_done(void *arg) { + dns_validator_t *val = arg; + isc_result_t result = val->result; + + if (CANCELED(val)) { + result = ISC_R_CANCELED; + } + + switch (result) { + case ISC_R_CANCELED: + case ISC_R_SHUTTINGDOWN: + /* Abort, abort, abort! */ + break; + case ISC_R_SUCCESS: + case ISC_R_NOMORE: + /* We are done */ + break; + default: + /* Continue validation until we have success or no more data */ + isc_work_enqueue(val->loop, validate_dnskey_dsset_next, + validate_dnskey_dsset_next_done, val); + return; + } + + validate_dnskey_dsset_done(val, result); + return; +} + +static void +validate_dnskey_dsset_first(dns_validator_t *val) { + isc_result_t result; + + if (CANCELED(val)) { + result = ISC_R_CANCELED; + } else { + result = dns_rdataset_first(val->dsset); + } + + if (result == ISC_R_SUCCESS) { + /* continue async run */ + result = validate_dnskey_dsset(val); + if (result != ISC_R_SUCCESS) { + isc_work_enqueue(val->loop, validate_dnskey_dsset_next, + validate_dnskey_dsset_next_done, val); + return; + } + } + + validate_dnskey_dsset_done(val, result); +} + +static void +validate_dnskey(void *arg) { + dns_validator_t *val = arg; + isc_result_t result = ISC_R_SUCCESS; dns_keynode_t *keynode = NULL; dns_rdata_ds_t ds; - bool supported_algorithm; - char digest_types[256]; + + if (CANCELED(val)) { + result = ISC_R_CANCELED; + goto cleanup; + } /* * If we don't already have a DS RRset, check to see if there's @@ -1767,7 +2128,7 @@ validate_dnskey(dns_validator_t *val) { * verification. */ - supported_algorithm = false; + val->supported_algorithm = false; /* * If DNS_DSDIGEST_SHA256 or DNS_DSDIGEST_SHA384 is present we @@ -1775,7 +2136,8 @@ validate_dnskey(dns_validator_t *val) { * practice means that we need to ignore DNS_DSDIGEST_SHA1 if a * DNS_DSDIGEST_SHA256 or DNS_DSDIGEST_SHA384 is present. */ - memset(digest_types, 1, sizeof(digest_types)); + val->digest_sha1 = true; + dns_rdata_t dsrdata = DNS_RDATA_INIT; for (result = dns_rdataset_first(val->dsset); result == ISC_R_SUCCESS; result = dns_rdataset_next(val->dsset)) { @@ -1801,80 +2163,20 @@ validate_dnskey(dns_validator_t *val) { (ds.digest_type == DNS_DSDIGEST_SHA384 && ds.length == ISC_SHA384_DIGESTLENGTH)) { - digest_types[DNS_DSDIGEST_SHA1] = 0; - break; - } - } - - for (result = dns_rdataset_first(val->dsset); result == ISC_R_SUCCESS; - result = dns_rdataset_next(val->dsset)) - { - dns_rdata_reset(&dsrdata); - dns_rdataset_current(val->dsset, &dsrdata); - result = dns_rdata_tostruct(&dsrdata, &ds, NULL); - RUNTIME_CHECK(result == ISC_R_SUCCESS); - - if (digest_types[ds.digest_type] == 0) { - continue; - } - - if (!dns_resolver_ds_digest_supported( - val->view->resolver, val->name, ds.digest_type)) - { - continue; - } - - if (!dns_resolver_algorithm_supported(val->view->resolver, - val->name, ds.algorithm)) - { - continue; - } - - supported_algorithm = true; - - /* - * Find the DNSKEY matching the DS... - */ - result = dns_dnssec_matchdskey(val->name, &dsrdata, - val->rdataset, &keyrdata); - if (result != ISC_R_SUCCESS) { - validator_log(val, ISC_LOG_DEBUG(3), - "no DNSKEY matching DS"); - continue; - } - - /* - * ... and check that it signed the DNSKEY RRset. - */ - result = check_signer(val, &keyrdata, ds.key_tag, ds.algorithm); - if (result == ISC_R_SUCCESS) { + val->digest_sha1 = false; break; } - validator_log(val, ISC_LOG_DEBUG(3), - "no RRSIG matching DS key"); } - if (result == ISC_R_SUCCESS) { - marksecure(val); - validator_log(val, ISC_LOG_DEBUG(3), "marking as secure (DS)"); - } else if (result == ISC_R_NOMORE && !supported_algorithm) { - validator_log(val, ISC_LOG_DEBUG(3), - "no supported algorithm/digest (DS)"); - result = markanswer(val, "validate_dnskey (3)", - "no supported algorithm/digest (DS)"); - } else { - validator_log(val, ISC_LOG_INFO, - "no valid signature found (DS)"); - result = DNS_R_NOVALIDSIG; - } + validate_dnskey_dsset_first(val); + return; cleanup: if (val->dsset == &val->fdsset) { val->dsset = NULL; dns_rdataset_disassociate(&val->fdsset); } - - return (result); + validate_async_done(val, result); } /*% @@ -2521,7 +2823,6 @@ validate_nx(dns_validator_t *val, bool resume) { return (DNS_R_BROKENCHAIN); } - validator_log(val, ISC_LOG_DEBUG(3), "nonexistence proof(s) not found"); return (proveunsecure(val, false, false)); } @@ -2597,10 +2898,10 @@ seek_ds(dns_validator_t *val, isc_result_t *resp) { */ if (val->frdataset.trust >= dns_trust_secure) { if (!check_ds_algs(val, tname, &val->frdataset)) { - validator_log(val, ISC_LOG_DEBUG(3), - "no supported algorithm/" - "digest (%s/DS)", - namebuf); + validator_log( + val, ISC_LOG_DEBUG(3), + "no supported algorithm/digest (%s/DS)", + namebuf); *resp = markanswer(val, "proveunsecure (5)", "no supported " "algorithm/digest (DS)"); @@ -2912,14 +3213,13 @@ validator_start(void *arg) { isc_result_t result = ISC_R_FAILURE; if (CANCELED(val)) { - return; + result = ISC_R_CANCELED; + goto cleanup; } validator_log(val, ISC_LOG_DEBUG(3), "starting"); if (val->rdataset != NULL && val->sigrdataset != NULL) { - isc_result_t saved_result; - /* * This looks like a simple validation. We say "looks like" * because it might end up requiring an insecurity proof. @@ -2929,21 +3229,19 @@ validator_start(void *arg) { INSIST(dns_rdataset_isassociated(val->rdataset)); INSIST(dns_rdataset_isassociated(val->sigrdataset)); - if (selfsigned_dnskey(val)) { - result = validate_dnskey(val); - } else { - result = validate_answer(val, false); - } - if (result == DNS_R_NOVALIDSIG && - (val->attributes & VALATTR_TRIEDVERIFY) == 0) - { - saved_result = result; - validator_log(val, ISC_LOG_DEBUG(3), - "falling back to insecurity proof"); - result = proveunsecure(val, false, false); - if (result == DNS_R_NOTINSECURE) { - result = saved_result; - } + + result = selfsigned_dnskey(val); + switch (result) { + case ISC_R_QUOTA: + goto cleanup; + case ISC_R_SUCCESS: + result = validate_async_run(val, validate_dnskey); + break; + case DNS_R_NOKEYMATCH: + result = validate_async_run(val, validate_answer); + break; + default: + UNREACHABLE(); } } else if (val->rdataset != NULL && val->rdataset->type != 0) { /* @@ -2996,11 +3294,8 @@ validator_start(void *arg) { UNREACHABLE(); } - if (result != DNS_R_WAIT) { - validator_done(val, result); - } - - dns_validator_detach(&val); +cleanup: + validate_async_done(val, result); } isc_result_t @@ -3008,6 +3303,7 @@ dns_validator_create(dns_view_t *view, dns_name_t *name, dns_rdatatype_t type, dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset, dns_message_t *message, unsigned int options, isc_loop_t *loop, isc_job_cb cb, void *arg, + uint32_t *nvalidations, uint32_t *nfails, dns_validator_t **validatorp) { isc_result_t result = ISC_R_FAILURE; dns_validator_t *val = NULL; @@ -3024,18 +3320,23 @@ dns_validator_create(dns_view_t *view, dns_name_t *name, dns_rdatatype_t type, } val = isc_mem_get(view->mctx, sizeof(*val)); - *val = (dns_validator_t){ .tid = isc_tid(), - .result = ISC_R_FAILURE, - .rdataset = rdataset, - .sigrdataset = sigrdataset, - .name = name, - .type = type, - .options = options, - .keytable = kt, - .link = ISC_LINK_INITIALIZER, - .loop = loop, - .cb = cb, - .arg = arg }; + *val = (dns_validator_t){ + .tid = isc_tid(), + .result = DNS_R_NOVALIDSIG, + .rdataset = rdataset, + .sigrdataset = sigrdataset, + .name = name, + .type = type, + .options = options, + .keytable = kt, + .link = ISC_LINK_INITIALIZER, + .loop = loop, + .cb = cb, + .arg = arg, + .rdata = DNS_RDATA_INIT, + .nvalidations = nvalidations, + .nfails = nfails, + }; isc_refcount_init(&val->references, 1); dns_view_attach(view, &val->view); @@ -3054,7 +3355,7 @@ dns_validator_create(dns_view_t *view, dns_name_t *name, dns_rdatatype_t type, if ((options & DNS_VALIDATOR_DEFER) == 0) { dns_validator_ref(val); - isc_async_run(val->loop, validator_start, val); + (void)validate_async_run(val, validator_start); } *validatorp = val; @@ -3063,15 +3364,15 @@ dns_validator_create(dns_view_t *view, dns_name_t *name, dns_rdatatype_t type, } void -dns_validator_send(dns_validator_t *validator) { - REQUIRE(VALID_VALIDATOR(validator)); - REQUIRE(validator->tid == isc_tid()); +dns_validator_send(dns_validator_t *val) { + REQUIRE(VALID_VALIDATOR(val)); + REQUIRE(val->tid == isc_tid()); - INSIST((validator->options & DNS_VALIDATOR_DEFER) != 0); - validator->options &= ~DNS_VALIDATOR_DEFER; + INSIST((val->options & DNS_VALIDATOR_DEFER) != 0); + val->options &= ~DNS_VALIDATOR_DEFER; - dns_validator_ref(validator); - isc_async_run(validator->loop, validator_start, validator); + dns_validator_ref(val); + (void)validate_async_run(val, validator_start); } void @@ -3092,7 +3393,6 @@ dns_validator_cancel(dns_validator_t *validator) { validator->options &= ~DNS_VALIDATOR_DEFER; validator_done(validator, ISC_R_CANCELED); } - validator->attributes |= VALATTR_CANCELED; } } @@ -3111,9 +3411,6 @@ destroy_validator(dns_validator_t *val) { if (val->keytable != NULL) { dns_keytable_detach(&val->keytable); } - if (val->subvalidator != NULL) { - dns_validator_destroy(&val->subvalidator); - } disassociate_rdatasets(val); mctx = val->view->mctx; if (val->siginfo != NULL) { diff --git a/lib/isc/loop.c b/lib/isc/loop.c index e31563b63d5..829cf168a46 100644 --- a/lib/isc/loop.c +++ b/lib/isc/loop.c @@ -138,6 +138,8 @@ static void shutdown_trigger_close_cb(uv_handle_t *handle) { isc_loop_t *loop = uv_handle_get_data(handle); + loop->shuttingdown = true; + isc_loop_detach(&loop); } diff --git a/lib/isccfg/namedconf.c b/lib/isccfg/namedconf.c index 002d4d43a14..e9fc6831092 100644 --- a/lib/isccfg/namedconf.c +++ b/lib/isccfg/namedconf.c @@ -2103,6 +2103,10 @@ static cfg_clausedef_t view_clauses[] = { { "max-recursion-queries", &cfg_type_uint32, 0 }, { "max-stale-ttl", &cfg_type_duration, 0 }, { "max-udp-size", &cfg_type_uint32, 0 }, + { "max-validations-per-fetch", &cfg_type_uint32, + CFG_CLAUSEFLAG_EXPERIMENTAL }, + { "max-validation-failures-per-fetch", &cfg_type_uint32, + CFG_CLAUSEFLAG_EXPERIMENTAL }, { "message-compression", &cfg_type_boolean, 0 }, { "min-cache-ttl", &cfg_type_duration, 0 }, { "min-ncache-ttl", &cfg_type_duration, 0 },