Merge pull request #31480 from rpigott/dnssec-maxwork
resolved: limit the number of signature validations in a transaction
This commit is contained in:
@@ -28,8 +28,9 @@ DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EC_KEY*, EC_KEY_free, NULL);
|
||||
/* Permit a maximum clock skew of 1h 10min. This should be enough to deal with DST confusion */
|
||||
#define SKEW_MAX (1*USEC_PER_HOUR + 10*USEC_PER_MINUTE)
|
||||
|
||||
/* Maximum number of NSEC3 iterations we'll do. RFC5155 says 2500 shall be the maximum useful value */
|
||||
#define NSEC3_ITERATIONS_MAX 2500
|
||||
/* Maximum number of NSEC3 iterations we'll do. RFC5155 says 2500 shall be the maximum useful value, but
|
||||
* RFC9276 § 3.2 says that we should reduce the acceptable iteration count */
|
||||
#define NSEC3_ITERATIONS_MAX 100
|
||||
|
||||
/*
|
||||
* The DNSSEC Chain of trust:
|
||||
@@ -1169,6 +1170,7 @@ int dnssec_verify_rrset_search(
|
||||
DnsResourceRecord **ret_rrsig) {
|
||||
|
||||
bool found_rrsig = false, found_invalid = false, found_expired_rrsig = false, found_unsupported_algorithm = false;
|
||||
unsigned nvalidations = 0;
|
||||
DnsResourceRecord *rrsig;
|
||||
int r;
|
||||
|
||||
@@ -1214,6 +1216,14 @@ int dnssec_verify_rrset_search(
|
||||
if (realtime == USEC_INFINITY)
|
||||
realtime = now(CLOCK_REALTIME);
|
||||
|
||||
/* Have we seen an unreasonable number of invalid signaures? */
|
||||
if (nvalidations > DNSSEC_INVALID_MAX) {
|
||||
if (ret_rrsig)
|
||||
*ret_rrsig = NULL;
|
||||
*result = DNSSEC_TOO_MANY_VALIDATIONS;
|
||||
return (int) nvalidations;
|
||||
}
|
||||
|
||||
/* Yay, we found a matching RRSIG with a matching
|
||||
* DNSKEY, awesome. Now let's verify all entries of
|
||||
* the RRSet against the RRSIG and DNSKEY
|
||||
@@ -1223,6 +1233,8 @@ int dnssec_verify_rrset_search(
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
nvalidations++;
|
||||
|
||||
switch (one_result) {
|
||||
|
||||
case DNSSEC_VALIDATED:
|
||||
@@ -1233,7 +1245,7 @@ int dnssec_verify_rrset_search(
|
||||
*ret_rrsig = rrsig;
|
||||
|
||||
*result = one_result;
|
||||
return 0;
|
||||
return (int) nvalidations;
|
||||
|
||||
case DNSSEC_INVALID:
|
||||
/* If the signature is invalid, let's try another
|
||||
@@ -1280,7 +1292,7 @@ int dnssec_verify_rrset_search(
|
||||
if (ret_rrsig)
|
||||
*ret_rrsig = NULL;
|
||||
|
||||
return 0;
|
||||
return (int) nvalidations;
|
||||
}
|
||||
|
||||
int dnssec_has_rrsig(DnsAnswer *a, const DnsResourceKey *key) {
|
||||
@@ -2565,6 +2577,7 @@ static const char* const dnssec_result_table[_DNSSEC_RESULT_MAX] = {
|
||||
[DNSSEC_NSEC_MISMATCH] = "nsec-mismatch",
|
||||
[DNSSEC_INCOMPATIBLE_SERVER] = "incompatible-server",
|
||||
[DNSSEC_UPSTREAM_FAILURE] = "upstream-failure",
|
||||
[DNSSEC_TOO_MANY_VALIDATIONS] = "too-many-validations",
|
||||
};
|
||||
DEFINE_STRING_TABLE_LOOKUP(dnssec_result, DnssecResult);
|
||||
|
||||
|
||||
@@ -9,12 +9,13 @@ typedef enum DnssecVerdict DnssecVerdict;
|
||||
#include "resolved-dns-rr.h"
|
||||
|
||||
enum DnssecResult {
|
||||
/* These five are returned by dnssec_verify_rrset() */
|
||||
/* These six are returned by dnssec_verify_rrset() */
|
||||
DNSSEC_VALIDATED,
|
||||
DNSSEC_VALIDATED_WILDCARD, /* Validated via a wildcard RRSIG, further NSEC/NSEC3 checks necessary */
|
||||
DNSSEC_INVALID,
|
||||
DNSSEC_SIGNATURE_EXPIRED,
|
||||
DNSSEC_UNSUPPORTED_ALGORITHM,
|
||||
DNSSEC_TOO_MANY_VALIDATIONS,
|
||||
|
||||
/* These two are added by dnssec_verify_rrset_search() */
|
||||
DNSSEC_NO_SIGNATURE,
|
||||
@@ -46,6 +47,12 @@ enum DnssecVerdict {
|
||||
/* The longest digest we'll ever generate, of all digest algorithms we support */
|
||||
#define DNSSEC_HASH_SIZE_MAX (MAX(20, 32))
|
||||
|
||||
/* The most invalid signatures we will tolerate for a single rrset */
|
||||
#define DNSSEC_INVALID_MAX 5
|
||||
|
||||
/* The total number of signature validations we will tolerate for a single transaction */
|
||||
#define DNSSEC_VALIDATION_MAX 64
|
||||
|
||||
int dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnskey, bool revoked_ok);
|
||||
int dnssec_key_match_rrsig(const DnsResourceKey *key, DnsResourceRecord *rrsig);
|
||||
|
||||
|
||||
@@ -3232,11 +3232,14 @@ static int dnssec_validate_records(
|
||||
DnsTransaction *t,
|
||||
Phase phase,
|
||||
bool *have_nsec,
|
||||
unsigned *nvalidations,
|
||||
DnsAnswer **validated) {
|
||||
|
||||
DnsResourceRecord *rr;
|
||||
int r;
|
||||
|
||||
assert(nvalidations);
|
||||
|
||||
/* Returns negative on error, 0 if validation failed, 1 to restart validation, 2 when finished. */
|
||||
|
||||
DNS_ANSWER_FOREACH(rr, t->answer) {
|
||||
@@ -3278,6 +3281,7 @@ static int dnssec_validate_records(
|
||||
&rrsig);
|
||||
if (r < 0)
|
||||
return r;
|
||||
*nvalidations += r;
|
||||
|
||||
log_debug("Looking at %s: %s", strna(dns_resource_record_to_string(rr)), dnssec_result_to_string(result));
|
||||
|
||||
@@ -3475,7 +3479,8 @@ static int dnssec_validate_records(
|
||||
DNSSEC_SIGNATURE_EXPIRED,
|
||||
DNSSEC_NO_SIGNATURE))
|
||||
manager_dnssec_verdict(t->scope->manager, DNSSEC_BOGUS, rr->key);
|
||||
else /* DNSSEC_MISSING_KEY or DNSSEC_UNSUPPORTED_ALGORITHM */
|
||||
else /* DNSSEC_MISSING_KEY, DNSSEC_UNSUPPORTED_ALGORITHM,
|
||||
or DNSSEC_TOO_MANY_VALIDATIONS */
|
||||
manager_dnssec_verdict(t->scope->manager, DNSSEC_INDETERMINATE, rr->key);
|
||||
|
||||
/* This is a primary response to our question, and it failed validation.
|
||||
@@ -3568,13 +3573,21 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) {
|
||||
return r;
|
||||
|
||||
phase = DNSSEC_PHASE_DNSKEY;
|
||||
for (;;) {
|
||||
for (unsigned nvalidations = 0;;) {
|
||||
bool have_nsec = false;
|
||||
|
||||
r = dnssec_validate_records(t, phase, &have_nsec, &validated);
|
||||
r = dnssec_validate_records(t, phase, &have_nsec, &nvalidations, &validated);
|
||||
if (r <= 0)
|
||||
return r;
|
||||
|
||||
if (nvalidations > DNSSEC_VALIDATION_MAX) {
|
||||
/* This reply requires an onerous number of signature validations to verify. Let's
|
||||
* not waste our time trying, as this shouldn't happen for well-behaved domains
|
||||
* anyway. */
|
||||
t->answer_dnssec_result = DNSSEC_TOO_MANY_VALIDATIONS;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Try again as long as we managed to achieve something */
|
||||
if (r == 1)
|
||||
continue;
|
||||
|
||||
Reference in New Issue
Block a user