From 3cc8da40aaf25a38ebc6f9f5a8135567a703659a Mon Sep 17 00:00:00 2001 From: Mark Andrews Date: Thu, 24 Jun 2021 12:20:14 +1000 Subject: [PATCH] Check ZONEMD records when loading or transferring a zone If ZONEMD is present in a zone and zonemd-check is active, check the hash of the zone contents against the ZONEMD record. When loading primary zones only, an expired signature is permitted for the ZONEMD RRset. This will allow the zone to be re-signed by named. When loading or transferring a mirror zone, the zone will now be DNSSEC validated only if there was no signed ZONEMD to check. (Note that ZONEMD is more protective than DNSSEC validation, because it covers glue as well as in-zone data.) --- bin/named/config.c | 1 + bin/tests/system/autosign/ns1/named.conf.in | 1 + bin/tests/system/autosign/ns3/named.conf.in | 5 + bin/tests/system/autosign/tests.sh | 2 +- bin/tests/system/camp/ns1/named.conf.j2 | 1 + bin/tests/system/dnssec/ns2/named.conf.j2 | 1 + bin/tests/system/dnssec/ns3/named.conf.j2 | 30 + bin/tests/system/dnssec/ns6/named.conf.j2 | 1 + bin/tests/system/mirror/ns1/named.conf.in | 1 + bin/tests/system/mirror/ns2/named.conf.in | 2 + bin/tests/system/mirror/ns3/named.conf.in | 1 + .../system/nsupdate/ns3/zonemd.test.db.in | 8 +- bin/tests/system/nsupdate/ns5/named.conf.in | 12 + bin/tests/system/redirect/ns5/named.conf.in | 8 +- bin/tests/system/redirect/ns6/named.conf.in | 8 +- lib/dns/include/dns/zone.h | 41 +- lib/dns/xfrin.c | 26 +- lib/dns/zone.c | 538 +++++++++++++++++- 18 files changed, 655 insertions(+), 32 deletions(-) diff --git a/bin/named/config.c b/bin/named/config.c index 61ce0a6ecc..586fd44eee 100644 --- a/bin/named/config.c +++ b/bin/named/config.c @@ -271,6 +271,7 @@ view \"_bind\" chaos {\n\ allow-new-zones no;\n\ max-cache-size 2M;\n\ provide-zoneversion no;\n\ + check-zonemd no;\n\ \n\ # Prevent use of this zone in DNS amplified reflection DoS attacks\n\ rate-limit {\n\ diff --git a/bin/tests/system/autosign/ns1/named.conf.in b/bin/tests/system/autosign/ns1/named.conf.in index fb76b65dc9..a207a59719 100644 --- a/bin/tests/system/autosign/ns1/named.conf.in +++ b/bin/tests/system/autosign/ns1/named.conf.in @@ -42,6 +42,7 @@ zone "." { allow-transfer { any; }; allow-update { any; }; allow-query { any; }; + check-zonemd no; }; include "trusted.conf"; diff --git a/bin/tests/system/autosign/ns3/named.conf.in b/bin/tests/system/autosign/ns3/named.conf.in index 3707a2c7c9..f305d47e4e 100644 --- a/bin/tests/system/autosign/ns3/named.conf.in +++ b/bin/tests/system/autosign/ns3/named.conf.in @@ -284,6 +284,7 @@ zone "oldsigs.example" { dnssec-policy jitter; sig-signing-nodes 1000; sig-signing-signatures 100; + check-zonemd no; }; zone "prepub.example" { @@ -329,6 +330,7 @@ zone "ttl4.example" { zone "delay.example" { type primary; file "delay.example.db"; + check-zonemd no; }; zone "nozsk.example" { @@ -337,6 +339,7 @@ zone "nozsk.example" { allow-update { any; }; inline-signing no; dnssec-policy autosign; + check-zonemd no; }; zone "inaczsk.example" { @@ -345,6 +348,7 @@ zone "inaczsk.example" { allow-update { any; }; inline-signing no; dnssec-policy autosign; + check-zonemd no; }; zone "noksk.example" { @@ -353,6 +357,7 @@ zone "noksk.example" { allow-update { any; }; inline-signing no; dnssec-policy autosign; + check-zonemd no; }; zone "sync.example" { diff --git a/bin/tests/system/autosign/tests.sh b/bin/tests/system/autosign/tests.sh index 298de9196c..88f524f95f 100755 --- a/bin/tests/system/autosign/tests.sh +++ b/bin/tests/system/autosign/tests.sh @@ -261,7 +261,7 @@ now="$(TZ=UTC date +%Y%m%d%H%M%S)" check_expiry() ( $DIG $DIGOPTS AXFR oldsigs.example @10.53.0.3 >dig.out.test$n || return 1 nearest_expiration="$(awk '$4 == "RRSIG" { print $9 }' diff_running) { + /* This is offloaded. */ + result = dns_zone_checkzonemd(xfr->zone, xfr->db, xfr->ver); + if (result == DNS_R_BADZONE) { + goto failure; + } + if (result != ISC_R_SUCCESS && + dns_zone_gettype(xfr->zone) == dns_zone_mirror) + { + CHECK(dns_zone_verifydb(xfr->zone, xfr->db, xfr->ver)); + } + } + result = ISC_R_SUCCESS; + failure: dns_diff_clear(&xfr->diff); work->result = result; @@ -393,7 +407,6 @@ axfr_apply_done(void *arg) { if (result == ISC_R_SUCCESS) { CHECK(dns_db_endload(xfr->db, &xfr->axfr)); - CHECK(dns_zone_verifydb(xfr->zone, xfr->db, NULL)); CHECK(axfr_finalize(xfr)); } else { (void)dns_db_endload(xfr->db, &xfr->axfr); @@ -512,7 +525,16 @@ static isc_result_t ixfr_end_transaction(dns_xfrin_t *xfr) { isc_result_t result = ISC_R_SUCCESS; - CHECK(dns_zone_verifydb(xfr->zone, xfr->db, xfr->ver)); + /* This is offloaded. */ + result = dns_zone_checkzonemd(xfr->zone, xfr->db, xfr->ver); + if (result == DNS_R_BADZONE) { + goto failure; + } + if (result != ISC_R_SUCCESS && + dns_zone_gettype(xfr->zone) == dns_zone_mirror) + { + CHECK(dns_zone_verifydb(xfr->zone, xfr->db, xfr->ver)); + } /* XXX enter ready-to-commit state here */ if (xfr->ixfr.journal != NULL) { CHECK(dns_journal_commit(xfr->ixfr.journal)); diff --git a/lib/dns/zone.c b/lib/dns/zone.c index 7277d9849e..cae04160a4 100644 --- a/lib/dns/zone.c +++ b/lib/dns/zone.c @@ -91,6 +91,7 @@ #include #include #include +#include #include #include @@ -5196,6 +5197,523 @@ failure: return result; } +static bool +signed_by_dnskey(dns_zone_t *zone, dns_name_t *name, dns_rdataset_t *dnskeyset, + dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset) { + bool answer = false; + bool ignoretime = (zone->type == dns_zone_primary) && + !DNS_ZONE_OPTION(zone, DNS_ZONEOPT_ZONEMD_NOEXPIRED); + + /* + * If rdataset is not associated there is nothing to verify. + */ + if (!dns_rdataset_isassociated(rdataset)) { + return false; + } + + if (dns_rdataset_isassociated(dnskeyset) && + dns_rdataset_isassociated(sigrdataset)) + { + DNS_RDATASET_FOREACH(dnskeyset) { + dns_rdata_t rdata = DNS_RDATA_INIT; + + dns_rdataset_current(dnskeyset, &rdata); + if (dns_dnssec_signs(&rdata, name, rdataset, + sigrdataset, ignoretime, + zone->mctx)) + { + answer = true; + break; + } + } + } + + dns_zone_log(zone, ISC_LOG_DEBUG(3), "signed_by_dnskey(%u) -> %s", + rdataset->type, answer ? "true" : "false"); + return answer; +} + +static bool +self_signed(dns_zone_t *zone, dns_name_t *name, dns_rdataset_t *dsset, + dns_rdataset_t *dnskeyset, dns_rdataset_t *sigrdataset) { + isc_result_t result = ISC_R_NOTFOUND; + dns_rdataset_t *rdataset = dnskeyset; + bool answer = false; + bool ignoretime = (zone->type == dns_zone_primary) && + !DNS_ZONE_OPTION(zone, DNS_ZONEOPT_ZONEMD_NOEXPIRED); + dst_key_t *key = NULL; + + if (!dns_rdataset_isassociated(dnskeyset) || + !dns_rdataset_isassociated(sigrdataset)) + { + goto failure; + } + + DNS_RDATASET_FOREACH(dnskeyset) { + dns_rdata_t rdata = DNS_RDATA_INIT; + + dns_rdataset_current(dnskeyset, &rdata); + if (dns_dnssec_signs(&rdata, name, rdataset, sigrdataset, + ignoretime, zone->mctx)) + { + if (dsset == NULL) { + /* + * There isn't a trust achor to + * check against so we are done. + */ + answer = true; + goto failure; + } + + CHECK(dns_dnssec_keyfromrdata(name, &rdata, zone->mctx, + &key)); + + DNS_RDATASET_FOREACH(dsset) { + dns_rdata_t dsrdata = DNS_RDATA_INIT; + dns_rdata_t newdsrdata = DNS_RDATA_INIT; + unsigned char buf[DNS_DS_BUFFERSIZE]; + dns_rdata_ds_t ds; + + dns_rdata_reset(&dsrdata); + dns_rdataset_current(dsset, &dsrdata); + (void)dns_rdata_tostruct(&dsrdata, &ds, NULL); + + if (ds.key_tag != dst_key_id(key) || + ds.algorithm != dst_key_alg(key)) + { + continue; + } + + result = dns_ds_buildrdata( + name, &rdata, ds.digest_type, buf, + sizeof(buf), &newdsrdata); + if (result != ISC_R_SUCCESS) { + continue; + } + + if (dns_rdata_compare(&dsrdata, &newdsrdata) == + 0) + { + dst_key_free(&key); + answer = true; + goto failure; + } + } + + dst_key_free(&key); + } + } + +failure: + dns_zone_log(zone, ISC_LOG_DEBUG(3), "self_signed(%u) -> %s", + rdataset->type, answer ? "true" : "false"); + return answer; +} + +static bool +haszonemdbit(dns_rdataset_t *set) { + isc_result_t result; + dns_rdata_t rdata = DNS_RDATA_INIT; + + result = dns_rdataset_first(set); + if (result != ISC_R_SUCCESS) { + /* fail true */ + return true; + } + dns_rdataset_current(set, &rdata); + if (set->type == dns_rdatatype_nsec) { + return dns_nsec_typepresent(&rdata, dns_rdatatype_zonemd); + } else { + return dns_nsec3_typepresent(&rdata, dns_rdatatype_zonemd); + } +} + +static isc_result_t +find_origin_nsec3(dns_zone_t *zone, dns_db_t *db, dns_dbnode_t *node, + dns_dbversion_t *version, bool *nsec3paramflagsok, + dns_rdataset_t *nsec3paramset, + dns_rdataset_t *signsec3paramset, dns_fixedname_t *fnsec3name, + dns_dbnode_t **nsec3node, dns_rdataset_t *nsec3set, + dns_rdataset_t *signsec3set) { + unsigned char hash[NSEC3_MAX_HASH_LENGTH]; + size_t hashlen = sizeof(hash); + dns_rdata_nsec3param_t nsec3param; + isc_result_t result; + + result = dns_db_findrdataset( + db, node, version, dns_rdatatype_nsec3param, dns_rdatatype_none, + 0, nsec3paramset, signsec3paramset); + dns_zone_log(zone, ISC_LOG_DEBUG(3), "dns_db_findrdataset(%u) -> %s", + dns_rdatatype_nsec3param, isc_result_totext(result)); + if (result != ISC_R_SUCCESS) { + goto failure; + } + result = dns_rdataset_first(nsec3paramset); + while (result == ISC_R_SUCCESS) { + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdataset_current(nsec3paramset, &rdata); + CHECK(dns_rdata_tostruct(&rdata, &nsec3param, NULL)); + if (nsec3param.flags == 0) { + *nsec3paramflagsok = true; + break; + } + result = dns_rdataset_next(nsec3paramset); + } + CHECK((result == ISC_R_NOMORE) ? ISC_R_NOTFOUND : result); + CHECK(dns_nsec3_hashname(fnsec3name, hash, &hashlen, &zone->origin, + &zone->origin, nsec3param.hash, + nsec3param.iterations, nsec3param.salt, + nsec3param.salt_length)); + CHECK(dns_db_findnsec3node(db, dns_fixedname_name(fnsec3name), false, + nsec3node)); + /* lookup NSEC3 record */ + result = dns_db_findrdataset(db, *nsec3node, version, + dns_rdatatype_nsec3, dns_rdatatype_none, 0, + nsec3set, signsec3set); + dns_zone_log(zone, ISC_LOG_DEBUG(3), "dns_db_findrdataset(%u) -> %s", + dns_rdatatype_nsec3, isc_result_totext(result)); +failure: + return result; +} + +isc_result_t +dns_zone_checkzonemd(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *version) { + enum { unknown = 0, good = 1, bad = 2 } found[256] = { unknown }; + bool nsec3paramflagsok = false; + isc_result_t result; + dns_keynode_t *keynode = NULL; + dns_keytable_t *secroots = NULL; + dns_dbnode_t *node = NULL; + dns_dbnode_t *nsec3node = NULL; + dns_fixedname_t fnsec3name; + dns_name_t *origin; + dns_name_t *nsec3name = dns_fixedname_initname(&fnsec3name); + dns_rdata_soa_t soa; + dns_rdata_t soardata = DNS_RDATA_INIT; + dns_rdataset_t dsset = DNS_RDATASET_INIT, *dssetp = NULL; + dns_rdataset_t dnskeyset = DNS_RDATASET_INIT, + sigdnskeyset = DNS_RDATASET_INIT; + dns_rdataset_t nsec3paramset = DNS_RDATASET_INIT, + signsec3paramset = DNS_RDATASET_INIT; + dns_rdataset_t nsec3set = DNS_RDATASET_INIT, + signsec3set = DNS_RDATASET_INIT; + dns_rdataset_t nsecset = DNS_RDATASET_INIT, + signsecset = DNS_RDATASET_INIT; + dns_rdataset_t soaset = DNS_RDATASET_INIT, + sigsoaset = DNS_RDATASET_INIT; + dns_rdataset_t zonemdset = DNS_RDATASET_INIT, + sigzonemdset = DNS_RDATASET_INIT; + + REQUIRE(DNS_ZONE_VALID(zone)); + REQUIRE(db != NULL); + + if (!DNS_ZONE_OPTION(zone, DNS_ZONEOPT_ZONEMD_CHECK)) { + return ISC_R_NOTFOUND; + } + + origin = &zone->origin; + + if (zone->view != NULL) { + result = dns_view_getsecroots(zone->view, &secroots); + if (result == ISC_R_SUCCESS) { + result = dns_keytable_find(secroots, origin, &keynode); + } + if (result == ISC_R_SUCCESS) { + if (dns_keynode_dsset(keynode, &dsset)) { + dssetp = &dsset; + } + } + } + + dns_zone_log(zone, ISC_LOG_DEBUG(3), "in zone_checkzonemd"); + + CHECK(dns_db_findnode(db, &zone->origin, false, &node)); + + result = dns_db_findrdataset(db, node, version, dns_rdatatype_soa, + dns_rdatatype_none, 0, &soaset, + &sigsoaset); + dns_zone_log(zone, ISC_LOG_DEBUG(3), "dns_db_findrdataset(%u) -> %s", + dns_rdatatype_soa, isc_result_totext(result)); + CHECK((result == ISC_R_NOTFOUND) ? DNS_R_BADZONE : result); + + result = dns_db_findrdataset(db, node, version, dns_rdatatype_dnskey, + dns_rdatatype_none, 0, &dnskeyset, + &sigdnskeyset); + dns_zone_log(zone, ISC_LOG_DEBUG(3), "dns_db_findrdataset(%u) -> %s", + dns_rdatatype_dnskey, isc_result_totext(result)); + if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) { + goto failure; + } + + result = dns_db_findrdataset(db, node, version, dns_rdatatype_zonemd, + dns_rdatatype_none, 0, &zonemdset, + &sigzonemdset); + dns_zone_log(zone, ISC_LOG_DEBUG(3), "dns_db_findrdataset(%u) -> %s", + dns_rdatatype_zonemd, isc_result_totext(result)); + if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) { + goto failure; + } + + /* + * Look for proofs that the zone is fully signed (NSEC or NSEC3PARAM) + * at apex and that ZONEMD does not exist (apex NSEC/NSEC3), + */ + result = dns_db_findrdataset(db, node, version, dns_rdatatype_nsec, + dns_rdatatype_none, 0, &nsecset, + &signsecset); + dns_zone_log(zone, ISC_LOG_DEBUG(3), "dns_db_findrdataset(%u) -> %s", + dns_rdatatype_nsec, isc_result_totext(result)); + if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) { + goto failure; + } + if (result == ISC_R_NOTFOUND) { + result = find_origin_nsec3(zone, db, node, version, + &nsec3paramflagsok, &nsec3paramset, + &signsec3paramset, &fnsec3name, + &nsec3node, &nsec3set, &signsec3set); + if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) { + goto failure; + } + } + + /* + * The zone is signed, check that zonemdset is + * correctly signed, or that it doesn't exist and + * that its nonexistence is not an error. + * + * For a zone to be deemed signed, DNSKEY and one of + * NSEC or NSEC3PARAM (with 0 flags) need to exist. + */ + if (dns_rdataset_isassociated(&dnskeyset) && + (dns_rdataset_isassociated(&nsecset) || + (dns_rdataset_isassociated(&nsec3paramset) && nsec3paramflagsok))) + { + if (!self_signed(zone, origin, dssetp, &dnskeyset, + &sigdnskeyset)) + { + dns_zone_log(zone, ISC_LOG_INFO, + "ZONEMD: self validation failure DNSKEY"); + CHECK(DNS_R_BADZONE); + } + + if (!signed_by_dnskey(zone, origin, &dnskeyset, &soaset, + &sigsoaset)) + { + dns_zone_log(zone, ISC_LOG_INFO, + "ZONEMD: self validation failure SOA"); + CHECK(DNS_R_BADZONE); + } + + if (!dns_rdataset_isassociated(&zonemdset)) { + if (DNS_ZONE_OPTION(zone, DNS_ZONEOPT_ZONEMD_REQUIRED)) + { + dns_zone_log(zone, ISC_LOG_INFO, + "ZONEMD required but not present"); + CHECK(DNS_R_BADZONE); + } + + if (signed_by_dnskey(zone, origin, &dnskeyset, &nsecset, + &signsecset) && + !haszonemdbit(&nsecset)) + { + CHECK(ISC_R_NOTFOUND); + } + + if (signed_by_dnskey(zone, nsec3name, &dnskeyset, + &nsec3set, &signsec3set) && + signed_by_dnskey(zone, origin, &dnskeyset, + &nsec3paramset, + &signsec3paramset) && + !haszonemdbit(&nsec3set)) + { + CHECK(ISC_R_NOTFOUND); + } + + /* + * A validly signed NSEC/NSEC3 indicated that + * ZONEMD should be here, but it isn't. + */ + CHECK(DNS_R_BADZONE); + } else if (!signed_by_dnskey(zone, origin, &dnskeyset, + &zonemdset, &sigzonemdset)) + { + dns_zone_log(zone, ISC_LOG_INFO, + "ZONEMD validation failed"); + CHECK(DNS_R_BADZONE); + } + } else { + if (dssetp != NULL) { + dns_zone_log(zone, ISC_LOG_INFO, + "zone at a trust anchor should be signed " + "but isn't"); + CHECK(DNS_R_BADZONE); + } + + if (dns_rdataset_isassociated(&zonemdset)) { + if (DNS_ZONE_OPTION(zone, DNS_ZONEOPT_ZONEMD_DNSSEC)) { + dns_zone_log(zone, ISC_LOG_INFO, + "ZONEMD found but zone unsigned"); + CHECK(DNS_R_BADZONE); + } + + /* Unsigned ZONEMD is okay */ + } else { + if (DNS_ZONE_OPTION(zone, DNS_ZONEOPT_ZONEMD_REQUIRED)) + { + dns_zone_log(zone, ISC_LOG_INFO, + "ZONEMD required but not present"); + CHECK(DNS_R_BADZONE); + } + + CHECK(ISC_R_NOTFOUND); + } + } + + /* + * Check the ZONEMD records. + */ + CHECK(dns_rdataset_first(&soaset)); + dns_rdataset_current(&soaset, &soardata); + CHECK(dns_rdata_tostruct(&soardata, &soa, NULL)); + + uint32_t max_digest = 0; + DNS_RDATASET_FOREACH(&zonemdset) { + dns_rdata_t rdata = DNS_RDATA_INIT; + + dns_rdataset_current(&zonemdset, &rdata); + if (dns_zonemd_supported(&rdata)) { + dns_rdata_zonemd_t zonemd; + dns_rdata_t check = DNS_RDATA_INIT; + unsigned char buf[DNS_ZONEMD_BUFFERSIZE]; + + (void)dns_rdata_tostruct(&rdata, &zonemd, NULL); + + if (zonemd.digest_type > max_digest) { + max_digest = zonemd.digest_type; + } + + if (soa.serial != zonemd.serial) { + dns_zone_log(zone, ISC_LOG_DEBUG(3), + "zonemd serial mismatch: soa=%u " + "zonemd=%u", + soa.serial, zonemd.serial); + found[zonemd.digest_type] = bad; + continue; + } + + if (found[zonemd.digest_type] != unknown) { + if (found[zonemd.digest_type] == good) { + dns_zone_log(zone, ISC_LOG_DEBUG(3), + "zonemd duplicate: digest " + "type=%u", + zonemd.digest_type); + found[zonemd.digest_type] = bad; + } + } else { + CHECK(dns_zonemd_buildrdata( + &check, db, version, zonemd.scheme, + zonemd.digest_type, zone->mctx, buf, + sizeof(buf))); + if (dns_rdata_compare(&check, &rdata) != 0) { + char tbuf[256]; + isc_buffer_t b; + dns_zone_log(zone, ISC_LOG_DEBUG(3), + "zonemd digest type=%u " + "dns_rdata_compare != 0", + zonemd.digest_type); + found[zonemd.digest_type] = bad; + if (!isc_log_wouldlog( + ISC_LOG_DEBUG(99))) + { + continue; + } + isc_buffer_init(&b, tbuf, sizeof(tbuf)); + (void)dns_rdata_totext(&check, NULL, + &b); + dns_zone_log( + zone, ISC_LOG_DEBUG(99), + "zonemd built: %.*s", + (int)isc_buffer_usedlength(&b), + tbuf); + isc_buffer_init(&b, tbuf, sizeof(tbuf)); + (void)dns_rdata_totext(&rdata, NULL, + &b); + dns_zone_log( + zone, ISC_LOG_DEBUG(99), + "zonemd found: %.*s", + (int)isc_buffer_usedlength(&b), + tbuf); + } else { + found[zonemd.digest_type] = good; + } + } + } + } + + result = DNS_R_BADZONE; + for (size_t i = 0; i <= max_digest; i++) { + if (found[i] == good) { + result = ISC_R_SUCCESS; + } + } + +failure: + if (dns_rdataset_isassociated(&dsset)) { + dns_rdataset_disassociate(&dsset); + } + if (dns_rdataset_isassociated(&soaset)) { + dns_rdataset_disassociate(&soaset); + } + if (dns_rdataset_isassociated(&sigsoaset)) { + dns_rdataset_disassociate(&sigsoaset); + } + if (dns_rdataset_isassociated(&nsecset)) { + dns_rdataset_disassociate(&nsecset); + } + if (dns_rdataset_isassociated(&signsecset)) { + dns_rdataset_disassociate(&signsecset); + } + if (dns_rdataset_isassociated(&nsec3set)) { + dns_rdataset_disassociate(&nsec3set); + } + if (dns_rdataset_isassociated(&signsec3set)) { + dns_rdataset_disassociate(&signsec3set); + } + if (dns_rdataset_isassociated(&dnskeyset)) { + dns_rdataset_disassociate(&dnskeyset); + } + if (dns_rdataset_isassociated(&sigdnskeyset)) { + dns_rdataset_disassociate(&sigdnskeyset); + } + if (dns_rdataset_isassociated(&zonemdset)) { + dns_rdataset_disassociate(&zonemdset); + } + if (dns_rdataset_isassociated(&sigzonemdset)) { + dns_rdataset_disassociate(&sigzonemdset); + } + if (dns_rdataset_isassociated(&nsec3paramset)) { + dns_rdataset_disassociate(&nsec3paramset); + } + if (dns_rdataset_isassociated(&signsec3paramset)) { + dns_rdataset_disassociate(&signsec3paramset); + } + if (node != NULL) { + dns_db_detachnode(&node); + } + if (nsec3node != NULL) { + dns_db_detachnode(&nsec3node); + } + if (keynode != NULL) { + dns_keynode_detach(&keynode); + } + if (secroots != NULL) { + dns_keytable_detach(&secroots); + } + dns_zone_log(zone, ISC_LOG_DEBUG(3), "dns_zone_checkzonemd -> %s", + isc_result_totext(result)); + return result; +} + /* * The zone is presumed to be locked. * If this is a inline_raw zone the secure version is also locked. @@ -5383,10 +5901,13 @@ zone_postload(dns_zone_t *zone, dns_db_t *db, isc_time_t loadtime, */ switch (zone->type) { + case dns_zone_mirror: + dns_zone_setoption(zone, DNS_ZONEOPT_ZONEMD_DNSSEC, true); + FALLTHROUGH; + case dns_zone_dlz: case dns_zone_primary: case dns_zone_secondary: - case dns_zone_mirror: case dns_zone_stub: case dns_zone_redirect: if (soacount != 1) { @@ -5449,11 +5970,18 @@ zone_postload(dns_zone_t *zone, dns_db_t *db, isc_time_t loadtime, goto cleanup; } - result = dns_zone_verifydb(zone, db, NULL); - if (result != ISC_R_SUCCESS) { + result = dns_zone_checkzonemd(zone, db, NULL); + if (result == DNS_R_BADZONE) { goto cleanup; } + if (result != ISC_R_SUCCESS && zone->type == dns_zone_mirror) { + result = dns_zone_verifydb(zone, db, NULL); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + } + if (zone->db != NULL) { unsigned int oldsoacount; @@ -24526,10 +25054,6 @@ dns_zone_verifydb(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver) { ENTER; - if (dns_zone_gettype(zone) != dns_zone_mirror) { - return ISC_R_SUCCESS; - } - if (ver == NULL) { dns_db_currentversion(db, &version); } else {