From 15096aefdf544d8f71c4d1ad84f4b8558c8756ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Sur=C3=BD?= Date: Mon, 11 Dec 2023 16:50:12 +0100 Subject: [PATCH] Make the dns_validator validations asynchronous and limit it Instead of running all the cryptographic validation in a tight loop, spread it out into multiple event loop "ticks", but moving every single validation into own isc_async_run() asynchronous event. Move the cryptographic operations - both verification and DNSKEY selection - to the offloaded threads (isc_work_enqueue), this further limits the time we spend doing expensive operations on the event loops that should be fast. Limit the impact of invalid or malicious RRSets that contain crafted records causing the dns_validator to do many validations per single fetch by adding a cap on the maximum number of validations and maximum number of validation failures that can happen before the resolving fails. --- bin/named/server.c | 15 + doc/arm/reference.rst | 15 + doc/misc/options | 4 + lib/dns/dst_api.c | 27 +- lib/dns/include/dns/resolver.h | 8 + lib/dns/include/dns/validator.h | 9 + lib/dns/include/dst/dst.h | 4 + lib/dns/resolver.c | 35 +- lib/dns/validator.c | 1277 +++++++++++++++++++------------ lib/isc/loop.c | 2 + lib/isccfg/namedconf.c | 4 + 11 files changed, 901 insertions(+), 499 deletions(-) diff --git a/bin/named/server.c b/bin/named/server.c index 80c5b5f1bb..67cafe633f 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 8468a785ea..6fb5937fa2 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 ac5dd66794..5fe4f32685 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 af53947ac6..ce41b99e5d 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 e9258827e4..7f0dde65a5 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 d42cdca637..0b0222c5c6 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 c0912f3e67..6c7a4e50db 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 f0f48d990d..442dfabce0 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 b679392c49..9afd2ea11f 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. */ +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. - */ -#define VALATTR_NEEDNOQNAME 0x00000100 -#define VALATTR_NEEDNOWILDCARD 0x00000200 -#define VALATTR_NEEDNODATA 0x00000400 + /*! + * 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 @@ trynsec3: 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; - } + isc_work_enqueue(val->loop, resume_answer_with_key, + resume_answer, val); + result = DNS_R_WAIT; + } else { + result = validate_async_run(val, resume_answer); } - 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); - } - } 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; + + if (CANCELED(val)) { + result = ISC_R_CANCELED; + goto cleanup; + } 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) { + 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; + + if (CANCELED(val)) { + result = ISC_R_CANCELED; + goto cleanup; + } 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) { + 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); + } + if (result != ISC_R_SUCCESS) { + goto done; } - result = dns_rdataset_first(rdataset); - if (result != ISC_R_SUCCESS) { - goto failure; - } 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 @@ again: } result = ISC_R_SUCCESS; } + + if (result != ISC_R_SUCCESS && over_max_fails(val)) { + result = ISC_R_QUOTA; + } return (result); } @@ -1420,132 +1454,323 @@ again: * 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; + } - do { - isc_result_t tresult; - vresult = verify(val, val->key, &rdata, - val->siginfo->keyid); - if (vresult == ISC_R_SUCCESS) { - break; - } + val->resume = false; + result = dns_rdataset_next(val->sigrdataset); - 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); - } +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; } - if (val->keyset != NULL) { - dns_rdataset_disassociate(val->keyset); - val->keyset = NULL; - } - 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); - } - validator_log(val, ISC_LOG_DEBUG(3), - "looking for noqname proof"); - return (validate_nx(val, false)); - } else if (vresult == ISC_R_SUCCESS) { - marksecure(val); - validator_log(val, ISC_LOG_DEBUG(3), - "marking as secure, " - "noqname proof not needed"); - return (ISC_R_SUCCESS); - } else { - validator_log(val, ISC_LOG_DEBUG(3), - "verify failure: %s", - isc_result_totext(result)); - resume = false; + + break; + default: + /* Select next signing key */ + result = select_signing_key(val, val->keyset); + break; + } + + 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); + val->key = NULL; + } + 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), + "maximum number of validations exceeded"); + } else if (MAXVALIDATIONFAILS(val)) { + validator_log(val, ISC_LOG_DEBUG(3), + "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), + "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; + val->digest_sha1 = false; 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) { - 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 e31563b63d..829cf168a4 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 10aa92ee22..3ffa2c95c4 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 },