Skip to content

Commit 7886eea

Browse files
rpigottbluca
authored andcommitted
resolved: limit the number of signature validations in a transaction
It has been demonstrated that tolerating an unbounded number of dnssec signature validations is a bad idea. It is easy for a maliciously crafted DNS reply to contain as many keytag collisions as desired, causing us to iterate every dnskey and signature combination in vain. The solution is to impose a maximum number of validations we will tolerate. While collisions are not hard to craft, I still expect they are unlikely in the wild so it should be safe to pick fairly small values. Here two limits are imposed: one on the maximum number of invalid signatures encountered per rrset, and another on the total number of validations performed per transaction. (cherry picked from commit 67d0ce8) (cherry picked from commit 1ebdb19) (cherry picked from commit 2f5edff)
1 parent e31a074 commit 7886eea

3 files changed

Lines changed: 38 additions & 6 deletions

File tree

src/resolve/resolved-dns-dnssec.c

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1176,6 +1176,7 @@ int dnssec_verify_rrset_search(
11761176
DnsResourceRecord **ret_rrsig) {
11771177

11781178
bool found_rrsig = false, found_invalid = false, found_expired_rrsig = false, found_unsupported_algorithm = false;
1179+
unsigned nvalidations = 0;
11791180
DnsResourceRecord *rrsig;
11801181
int r;
11811182

@@ -1221,6 +1222,14 @@ int dnssec_verify_rrset_search(
12211222
if (realtime == USEC_INFINITY)
12221223
realtime = now(CLOCK_REALTIME);
12231224

1225+
/* Have we seen an unreasonable number of invalid signaures? */
1226+
if (nvalidations > DNSSEC_INVALID_MAX) {
1227+
if (ret_rrsig)
1228+
*ret_rrsig = NULL;
1229+
*result = DNSSEC_TOO_MANY_VALIDATIONS;
1230+
return (int) nvalidations;
1231+
}
1232+
12241233
/* Yay, we found a matching RRSIG with a matching
12251234
* DNSKEY, awesome. Now let's verify all entries of
12261235
* the RRSet against the RRSIG and DNSKEY
@@ -1230,6 +1239,8 @@ int dnssec_verify_rrset_search(
12301239
if (r < 0)
12311240
return r;
12321241

1242+
nvalidations++;
1243+
12331244
switch (one_result) {
12341245

12351246
case DNSSEC_VALIDATED:
@@ -1240,7 +1251,7 @@ int dnssec_verify_rrset_search(
12401251
*ret_rrsig = rrsig;
12411252

12421253
*result = one_result;
1243-
return 0;
1254+
return (int) nvalidations;
12441255

12451256
case DNSSEC_INVALID:
12461257
/* If the signature is invalid, let's try another
@@ -1287,7 +1298,7 @@ int dnssec_verify_rrset_search(
12871298
if (ret_rrsig)
12881299
*ret_rrsig = NULL;
12891300

1290-
return 0;
1301+
return (int) nvalidations;
12911302
}
12921303

12931304
int dnssec_has_rrsig(DnsAnswer *a, const DnsResourceKey *key) {
@@ -2571,6 +2582,7 @@ static const char* const dnssec_result_table[_DNSSEC_RESULT_MAX] = {
25712582
[DNSSEC_FAILED_AUXILIARY] = "failed-auxiliary",
25722583
[DNSSEC_NSEC_MISMATCH] = "nsec-mismatch",
25732584
[DNSSEC_INCOMPATIBLE_SERVER] = "incompatible-server",
2585+
[DNSSEC_TOO_MANY_VALIDATIONS] = "too-many-validations",
25742586
};
25752587
DEFINE_STRING_TABLE_LOOKUP(dnssec_result, DnssecResult);
25762588

src/resolve/resolved-dns-dnssec.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,13 @@ typedef enum DnssecVerdict DnssecVerdict;
99
#include "resolved-dns-rr.h"
1010

1111
enum DnssecResult {
12-
/* These five are returned by dnssec_verify_rrset() */
12+
/* These six are returned by dnssec_verify_rrset() */
1313
DNSSEC_VALIDATED,
1414
DNSSEC_VALIDATED_WILDCARD, /* Validated via a wildcard RRSIG, further NSEC/NSEC3 checks necessary */
1515
DNSSEC_INVALID,
1616
DNSSEC_SIGNATURE_EXPIRED,
1717
DNSSEC_UNSUPPORTED_ALGORITHM,
18+
DNSSEC_TOO_MANY_VALIDATIONS,
1819

1920
/* These two are added by dnssec_verify_rrset_search() */
2021
DNSSEC_NO_SIGNATURE,
@@ -45,6 +46,12 @@ enum DnssecVerdict {
4546
/* The longest digest we'll ever generate, of all digest algorithms we support */
4647
#define DNSSEC_HASH_SIZE_MAX (MAX(20, 32))
4748

49+
/* The most invalid signatures we will tolerate for a single rrset */
50+
#define DNSSEC_INVALID_MAX 5
51+
52+
/* The total number of signature validations we will tolerate for a single transaction */
53+
#define DNSSEC_VALIDATION_MAX 64
54+
4855
int dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnskey, bool revoked_ok);
4956
int dnssec_key_match_rrsig(const DnsResourceKey *key, DnsResourceRecord *rrsig);
5057

src/resolve/resolved-dns-transaction.c

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3172,11 +3172,14 @@ static int dnssec_validate_records(
31723172
DnsTransaction *t,
31733173
Phase phase,
31743174
bool *have_nsec,
3175+
unsigned *nvalidations,
31753176
DnsAnswer **validated) {
31763177

31773178
DnsResourceRecord *rr;
31783179
int r;
31793180

3181+
assert(nvalidations);
3182+
31803183
/* Returns negative on error, 0 if validation failed, 1 to restart validation, 2 when finished. */
31813184

31823185
DNS_ANSWER_FOREACH(rr, t->answer) {
@@ -3218,6 +3221,7 @@ static int dnssec_validate_records(
32183221
&rrsig);
32193222
if (r < 0)
32203223
return r;
3224+
*nvalidations += r;
32213225

32223226
log_debug("Looking at %s: %s", strna(dns_resource_record_to_string(rr)), dnssec_result_to_string(result));
32233227

@@ -3415,7 +3419,8 @@ static int dnssec_validate_records(
34153419
DNSSEC_SIGNATURE_EXPIRED,
34163420
DNSSEC_NO_SIGNATURE))
34173421
manager_dnssec_verdict(t->scope->manager, DNSSEC_BOGUS, rr->key);
3418-
else /* DNSSEC_MISSING_KEY or DNSSEC_UNSUPPORTED_ALGORITHM */
3422+
else /* DNSSEC_MISSING_KEY, DNSSEC_UNSUPPORTED_ALGORITHM,
3423+
or DNSSEC_TOO_MANY_VALIDATIONS */
34193424
manager_dnssec_verdict(t->scope->manager, DNSSEC_INDETERMINATE, rr->key);
34203425

34213426
/* This is a primary response to our question, and it failed validation.
@@ -3508,13 +3513,21 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) {
35083513
return r;
35093514

35103515
phase = DNSSEC_PHASE_DNSKEY;
3511-
for (;;) {
3516+
for (unsigned nvalidations = 0;;) {
35123517
bool have_nsec = false;
35133518

3514-
r = dnssec_validate_records(t, phase, &have_nsec, &validated);
3519+
r = dnssec_validate_records(t, phase, &have_nsec, &nvalidations, &validated);
35153520
if (r <= 0)
35163521
return r;
35173522

3523+
if (nvalidations > DNSSEC_VALIDATION_MAX) {
3524+
/* This reply requires an onerous number of signature validations to verify. Let's
3525+
* not waste our time trying, as this shouldn't happen for well-behaved domains
3526+
* anyway. */
3527+
t->answer_dnssec_result = DNSSEC_TOO_MANY_VALIDATIONS;
3528+
return 0;
3529+
}
3530+
35183531
/* Try again as long as we managed to achieve something */
35193532
if (r == 1)
35203533
continue;

0 commit comments

Comments
 (0)