diff --git a/CHANGES b/CHANGES index daa72deefb..fbd9ba7387 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,6 @@ +5630. [func] Treat DNSSEC responses with NSEC3 iterations greater + than 150 as insecure. [GL #2445] + 5629. [func] Reduce the supported maximum number of iterations that can be configured in an NSEC3 zone to 150. [GL #2642] diff --git a/bin/tests/system/dnssec/tests.sh b/bin/tests/system/dnssec/tests.sh index 728325c794..9e50a0edf1 100644 --- a/bin/tests/system/dnssec/tests.sh +++ b/bin/tests/system/dnssec/tests.sh @@ -4331,5 +4331,56 @@ n=$((n+1)) test "$ret" -eq 0 || echo_i "failed" status=$((status+ret)) +# Check that the validating resolver will fallback to insecure if the answer +# contains NSEC3 records with high iteration count. +echo_i "checking fallback to insecure when NSEC3 iterations is too high (nxdomain) ($n)" +ret=0 +dig_with_opts @10.53.0.2 does-not-exist.too-many-iterations > dig.out.ns2.test$n || ret=1 +dig_with_opts @10.53.0.4 does-not-exist.too-many-iterations > dig.out.ns4.test$n || ret=1 +digcomp dig.out.ns2.test$n dig.out.ns4.test$n || ret=1 +grep "flags: qr rd ra;" dig.out.ns4.test$n >/dev/null || ret=1 +grep "status: NXDOMAIN" dig.out.ns4.test$n >/dev/null || ret=1 +grep "ANSWER: 0, AUTHORITY: 6" dig.out.ns4.test$n > /dev/null || ret=1 +n=$((n+1)) +test "$ret" -eq 0 || echo_i "failed" +status=$((status+ret)) + +echo_i "checking fallback to insecure when NSEC3 iterations is too high (nodata) ($n)" +ret=0 +dig_with_opts @10.53.0.2 a.too-many-iterations txt > dig.out.ns2.test$n || ret=1 +dig_with_opts @10.53.0.4 a.too-many-iterations txt > dig.out.ns4.test$n || ret=1 +digcomp dig.out.ns2.test$n dig.out.ns4.test$n || ret=1 +grep "flags: qr rd ra;" dig.out.ns4.test$n >/dev/null || ret=1 +grep "status: NOERROR" dig.out.ns4.test$n >/dev/null || ret=1 +grep "ANSWER: 0, AUTHORITY: 4" dig.out.ns4.test$n > /dev/null || ret=1 +n=$((n+1)) +test "$ret" -eq 0 || echo_i "failed" +status=$((status+ret)) + +echo_i "checking fallback to insecure when NSEC3 iterations is too high (wildcard) ($n)" +ret=0 +dig_with_opts @10.53.0.2 wild.a.too-many-iterations > dig.out.ns2.test$n || ret=1 +dig_with_opts @10.53.0.4 wild.a.too-many-iterations > dig.out.ns4.test$n || ret=1 +digcomp dig.out.ns2.test$n dig.out.ns4.test$n || ret=1 +grep "flags: qr rd ra;" dig.out.ns4.test$n >/dev/null || ret=1 +grep "status: NOERROR" dig.out.ns4.test$n >/dev/null || ret=1 +grep 'wild\.a\.too-many-iterations\..*A.10\.0\.0\.3' dig.out.ns4.test$n >/dev/null || ret=1 +grep "ANSWER: 2, AUTHORITY: 4" dig.out.ns4.test$n > /dev/null || ret=1 +n=$((n+1)) +test "$ret" -eq 0 || echo_i "failed" +status=$((status+ret)) + +echo_i "checking fallback to insecure when NSEC3 iterations is too high (wildcard nodata) ($n)" +ret=0 +dig_with_opts @10.53.0.2 type100 wild.a.too-many-iterations > dig.out.ns2.test$n || ret=1 +dig_with_opts @10.53.0.4 type100 wild.a.too-many-iterations > dig.out.ns4.test$n || ret=1 +digcomp dig.out.ns2.test$n dig.out.ns4.test$n || ret=1 +grep "flags: qr rd ra;" dig.out.ns4.test$n >/dev/null || ret=1 +grep "status: NOERROR" dig.out.ns4.test$n >/dev/null || ret=1 +grep "ANSWER: 0, AUTHORITY: 8" dig.out.ns4.test$n > /dev/null || ret=1 +n=$((n+1)) +test "$ret" -eq 0 || echo_i "failed" +status=$((status+ret)) + echo_i "exit status: $status" [ $status -eq 0 ] || exit 1 diff --git a/doc/notes/notes-current.rst b/doc/notes/notes-current.rst index ee9d54bde4..2d56a18ca9 100644 --- a/doc/notes/notes-current.rst +++ b/doc/notes/notes-current.rst @@ -40,6 +40,9 @@ Feature Changes - Reduce the supported maximum number of iterations that can be configured in an NSEC3 zones to 150. :gl:`#2642` +- Treat DNSSEC responses with NSEC3 iterations greater than 150 as insecure. + [GL #2445] + Bug Fixes ~~~~~~~~~ diff --git a/lib/dns/include/dns/nsec3.h b/lib/dns/include/dns/nsec3.h index 2f178b680b..0cbcb51108 100644 --- a/lib/dns/include/dns/nsec3.h +++ b/lib/dns/include/dns/nsec3.h @@ -24,7 +24,8 @@ #include #include -#define DNS_NSEC3_SALTSIZE 255 +#define DNS_NSEC3_SALTSIZE 255 +#define DNS_NSEC3_MAXITERATIONS 150U /* * hash = 1, flags =1, iterations = 2, salt length = 1, salt = 255 (max) diff --git a/lib/dns/nsec3.c b/lib/dns/nsec3.c index b5df23eefe..7ff9e8df40 100644 --- a/lib/dns/nsec3.c +++ b/lib/dns/nsec3.c @@ -1880,7 +1880,7 @@ try_private: unsigned int dns_nsec3_maxiterations(void) { - return (150); + return (DNS_NSEC3_MAXITERATIONS); } isc_result_t @@ -2022,6 +2022,13 @@ dns_nsec3_noexistnodata(dns_rdatatype_t type, const dns_name_t *name, first = true; while (qlabels >= zlabels) { + /* + * If there are too many iterations reject the NSEC3 record. + */ + if (nsec3.iterations > DNS_NSEC3_MAXITERATIONS) { + return (DNS_R_NSEC3ITERRANGE); + } + length = isc_iterated_hash(hash, nsec3.hash, nsec3.iterations, nsec3.salt, nsec3.salt_length, qname->ndata, qname->length); diff --git a/lib/dns/validator.c b/lib/dns/validator.c index 27adb5a31f..e54fc70b2e 100644 --- a/lib/dns/validator.c +++ b/lib/dns/validator.c @@ -2245,6 +2245,26 @@ findnsec3proofs(dns_validator_t *val) { if (unknown) { val->attributes |= VALATTR_FOUNDUNKNOWN; } + if (result == DNS_R_NSEC3ITERRANGE) { + /* + * We don't really know which NSEC3 record provides + * which proof. Just populate them. + */ + if (NEEDNOQNAME(val) && + proofs[DNS_VALIDATOR_NOQNAMEPROOF] == NULL) { + proofs[DNS_VALIDATOR_NOQNAMEPROOF] = name; + } else if (setclosest) { + proofs[DNS_VALIDATOR_CLOSESTENCLOSER] = name; + } else if (NEEDNODATA(val) && + proofs[DNS_VALIDATOR_NODATAPROOF] == NULL) { + proofs[DNS_VALIDATOR_NODATAPROOF] = name; + } else if (NEEDNOWILDCARD(val) && + proofs[DNS_VALIDATOR_NOWILDCARDPROOF] == + NULL) { + proofs[DNS_VALIDATOR_NOWILDCARDPROOF] = name; + } + return (result); + } if (result != ISC_R_SUCCESS) { continue; } @@ -2501,7 +2521,13 @@ validate_nx(dns_validator_t *val, bool resume) { */ if (!NEEDNODATA(val) && !NEEDNOWILDCARD(val) && NEEDNOQNAME(val)) { if (!FOUNDNOQNAME(val)) { - findnsec3proofs(val); + result = findnsec3proofs(val); + if (result == DNS_R_NSEC3ITERRANGE) { + validator_log(val, ISC_LOG_DEBUG(3), + "too many iterations"); + markanswer(val, "validate_nx (3)", NULL); + return (ISC_R_SUCCESS); + } } if (FOUNDNOQNAME(val) && FOUNDCLOSEST(val) && !FOUNDOPTOUT(val)) @@ -2531,7 +2557,13 @@ validate_nx(dns_validator_t *val, bool resume) { } if (!FOUNDNOQNAME(val) && !FOUNDNODATA(val)) { - findnsec3proofs(val); + result = findnsec3proofs(val); + if (result == DNS_R_NSEC3ITERRANGE) { + validator_log(val, ISC_LOG_DEBUG(3), + "too many iterations"); + markanswer(val, "validate_nx (4)", NULL); + return (ISC_R_SUCCESS); + } } /*