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.
This commit is contained in:
Ondřej Surý 2023-12-11 16:50:12 +01:00 committed by Michał Kępień
parent 1e40c0b124
commit 15096aefdf
No known key found for this signature in database
11 changed files with 901 additions and 499 deletions

View file

@ -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);

View file

@ -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.

View file

@ -188,6 +188,8 @@ options {
max-transfer-time-in <integer>;
max-transfer-time-out <integer>;
max-udp-size <integer>;
max-validation-failures-per-fetch <integer>; // experimental
max-validations-per-fetch <integer>; // experimental
max-zone-ttl ( unlimited | <duration> ); // deprecated
memstatistics <boolean>;
memstatistics-file <quoted_string>;
@ -469,6 +471,8 @@ view <string> [ <class> ] {
max-transfer-time-in <integer>;
max-transfer-time-out <integer>;
max-udp-size <integer>;
max-validation-failures-per-fetch <integer>; // experimental
max-validations-per-fetch <integer>; // experimental
max-zone-ttl ( unlimited | <duration> ); // deprecated
message-compression <boolean>;
min-cache-ttl <duration>;

View file

@ -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);
}
}
}

View file

@ -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

View file

@ -53,6 +53,7 @@
#include <isc/refcount.h>
#include <dns/fixedname.h>
#include <dns/rdata.h>
#include <dns/rdataset.h>
#include <dns/rdatastruct.h> /* for dns_rdata_rrsig_t */
#include <dns/types.h>
@ -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.

View file

@ -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);
/*%<

View file

@ -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));

File diff suppressed because it is too large Load diff

View file

@ -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);
}

View file

@ -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 },