From 8ff2c133b554332e6b954152783e2e8c11e6384c Mon Sep 17 00:00:00 2001 From: Mark Andrews Date: Sat, 27 Nov 2021 09:12:08 +1100 Subject: [PATCH] Add dns_nsec_requiredtypespresent checks an NSEC rdataset to ensure that both NSEC and RRSIG are present in the type map. These types are required for the NSEC to be valid --- .../system/synthfromdnssec/ns1/minimal.db.in | 6 ++- bin/tests/system/synthfromdnssec/tests.sh | 45 +++++++++++++++++++ lib/dns/include/dns/nsec.h | 10 +++++ lib/dns/nsec.c | 29 ++++++++++++ lib/dns/resolver.c | 9 ++++ 5 files changed, 98 insertions(+), 1 deletion(-) diff --git a/bin/tests/system/synthfromdnssec/ns1/minimal.db.in b/bin/tests/system/synthfromdnssec/ns1/minimal.db.in index 6dd356a3cf..195062ed56 100644 --- a/bin/tests/system/synthfromdnssec/ns1/minimal.db.in +++ b/bin/tests/system/synthfromdnssec/ns1/minimal.db.in @@ -7,7 +7,11 @@ minimal. 3600 SOA ns1.minimal. hostmaster.minimal. ( 3600 ; minimum (1 hour) ) 3600 NS ns1.minimal. - 3600 NSEC black.minimal. NS SOA RRSIG NSEC DNSKEY + 3600 NSEC badtypemap.minimal. NS SOA RRSIG NSEC DNSKEY +; bad NSEC type map without RRSIG or NSEC +badtypemap.minimal. 3600 NSEC black.minimal. A +badtypemap.minimal. 3600 A 1.2.3.4 +badtypemap.minimal. 3600 AAAA 2002::1 ; cloudflare black lie black.minimal. 3600 NSEC \000.black.minimal. RRSIG NSEC ; diff --git a/bin/tests/system/synthfromdnssec/tests.sh b/bin/tests/system/synthfromdnssec/tests.sh index 125daf5f41..5a61409294 100644 --- a/bin/tests/system/synthfromdnssec/tests.sh +++ b/bin/tests/system/synthfromdnssec/tests.sh @@ -67,6 +67,19 @@ check_nosynth_a() ( return 0 ) +check_synth_aaaa() ( + name=$(echo "$1" | sed 's/\./\\./g') + grep "^${name}.*[0-9]*.IN.AAAA" ${2} > /dev/null || return 1 + grep "^${name}.*3600.IN.A" ${2} > /dev/null && return 1 + return 0 +) + +check_nosynth_aaaa() ( + name=$(echo "$1" | sed 's/\./\\./g') + grep "^${name}.*3600.IN.AAAA" ${2} > /dev/null || return 1 + return 0 +) + check_synth_cname() ( name=$(echo "$1" | sed 's/\./\\./g') grep "^${name}.*[0-9]*.IN.CNAME" ${2} > /dev/null || return 1 @@ -200,6 +213,17 @@ do n=$((n+1)) if [ $ret != 0 ]; then echo_i "failed"; fi status=$((status+ret)) + + echo_i "prime bad type map NODATA response (synth-from-dnssec ${description};) ($n)" + ret=0 + dig_with_opts badtypemap.minimal. @10.53.0.${ns} TXT > dig.out.ns${ns}.test$n || ret=1 + check_ad_flag $ad dig.out.ns${ns}.test$n || ret=1 + check_status NOERROR dig.out.ns${ns}.test$n || ret=1 + check_nosynth_soa minimal. dig.out.ns${ns}.test$n || ret=1 + grep 'badtypemap.minimal.*3600.IN.NSEC.black.minimal. A$' dig.out.ns${ns}.test$n > /dev/null || ret=1 + n=$((n+1)) + if [ $ret != 0 ]; then echo_i "failed"; fi + status=$((status+ret)) done echo_i "prime redirect response (+nodnssec) (synth-from-dnssec ;) ($n)" @@ -388,6 +412,27 @@ do if [ $ret != 0 ]; then echo_i "failed"; fi status=$((status+ret)) + echo_i "check bad type map NODATA response (synth-from-dnssec ${description};) ($n)" + ret=0 + dig_with_opts badtypemap.minimal. @10.53.0.${ns} HINFO > dig.out.ns${ns}.test$n || ret=1 + check_ad_flag $ad dig.out.ns${ns}.test$n || ret=1 + check_status NOERROR dig.out.ns${ns}.test$n || ret=1 + check_nosynth_soa minimal. dig.out.ns${ns}.test$n || ret=1 + grep 'badtypemap.minimal.*3600.IN.NSEC.black.minimal. A$' dig.out.ns${ns}.test$n > /dev/null || ret=1 + n=$((n+1)) + if [ $ret != 0 ]; then echo_i "failed"; fi + status=$((status+ret)) + + echo_i "check bad type map NODATA response with existent data (synth-from-dnssec ${description};) ($n)" + ret=0 + dig_with_opts badtypemap.minimal. @10.53.0.${ns} AAAA > dig.out.ns${ns}.test$n || ret=1 + check_ad_flag $ad dig.out.ns${ns}.test$n || ret=1 + check_status NOERROR dig.out.ns${ns}.test$n || ret=1 + check_nosynth_aaaa badtypemap.minimal. dig.out.ns${ns}.test$n || ret=1 + n=$((n+1)) + if [ $ret != 0 ]; then echo_i "failed"; fi + status=$((status+ret)) + echo_i "check 'rndc stats' output for 'covering nsec returned' (synth-from-dnssec ${description};) ($n)" ret=0 ${RNDCCMD} 10.53.0.${ns} stats 2>&1 | sed 's/^/ns6 /' | cat_i diff --git a/lib/dns/include/dns/nsec.h b/lib/dns/include/dns/nsec.h index f6844cf82c..7aae7e8ccb 100644 --- a/lib/dns/include/dns/nsec.h +++ b/lib/dns/include/dns/nsec.h @@ -106,4 +106,14 @@ dns_nsec_noexistnodata(dns_rdatatype_t type, const dns_name_t *name, * Return ISC_R_IGNORE when the NSEC is not the appropriate one. */ +bool +dns_nsec_requiredtypespresent(dns_rdataset_t *rdataset); +/* + * Return true if all the NSEC records in rdataset have both + * NSEC and RRSIG present. + * + * Requires: + * \li rdataset to be a NSEC rdataset. + */ + ISC_LANG_ENDDECLS diff --git a/lib/dns/nsec.c b/lib/dns/nsec.c index cbb27d6062..95af49c3a2 100644 --- a/lib/dns/nsec.c +++ b/lib/dns/nsec.c @@ -460,3 +460,32 @@ dns_nsec_noexistnodata(dns_rdatatype_t type, const dns_name_t *name, *exists = false; return (ISC_R_SUCCESS); } + +bool +dns_nsec_requiredtypespresent(dns_rdataset_t *nsecset) { + dns_rdataset_t rdataset; + isc_result_t result; + bool found = false; + + REQUIRE(DNS_RDATASET_VALID(nsecset)); + REQUIRE(nsecset->type == dns_rdatatype_nsec); + + dns_rdataset_init(&rdataset); + dns_rdataset_clone(nsecset, &rdataset); + + for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(&rdataset)) + { + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdataset_current(&rdataset, &rdata); + if (!dns_nsec_typepresent(&rdata, dns_rdatatype_nsec) || + !dns_nsec_typepresent(&rdata, dns_rdatatype_rrsig)) + { + dns_rdataset_disassociate(&rdataset); + return (false); + } + found = true; + } + dns_rdataset_disassociate(&rdataset); + return (found); +} diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c index 6c6c67d5aa..f98295bf12 100644 --- a/lib/dns/resolver.c +++ b/lib/dns/resolver.c @@ -5572,6 +5572,15 @@ answer_response: continue; } + /* + * Don't cache NSEC if missing NSEC or RRSIG types. + */ + if (rdataset->type == dns_rdatatype_nsec && + !dns_nsec_requiredtypespresent(rdataset)) + { + continue; + } + /* * Don't cache "white lies" but do cache * "black lies".