diff --git a/CHANGES b/CHANGES index 51ac2ec601..02ba72f801 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,6 @@ +5351. [bug] CDS / CDNSKEY consistency checks failed to handle + removal records. [GL #1554] + 5350. [bug] When a view was configured with class CHAOS, the server could crash while processing a query for a non-existent record. [GL #1540] diff --git a/bin/tests/system/dnssec/clean.sh b/bin/tests/system/dnssec/clean.sh index bee3bbfd62..550b5a7f7f 100644 --- a/bin/tests/system/dnssec/clean.sh +++ b/bin/tests/system/dnssec/clean.sh @@ -24,9 +24,9 @@ rm -f ./canonical?.* rm -f ./delv.out* rm -f ./delve.out* rm -f ./dig.out.* +rm -f ./dnssectools.out* rm -f ./dsfromkey.out.* rm -f ./keygen.err -rm -f ./dnssectools.out* rm -f ./named.secroots.test* rm -f ./nosign.before rm -f ./ns*/*.nta @@ -37,11 +37,13 @@ rm -f ./ns1/root.db ./ns2/example.db ./ns2/managed.db ./ns2/trusted.db rm -f ./ns2/algroll.db rm -f ./ns2/badparam.db ./ns2/badparam.db.bad rm -f ./ns2/cdnskey-kskonly.secure.db +rm -f ./ns2/cdnskey-kskonly.secure.id rm -f ./ns2/cdnskey-update.secure.db rm -f ./ns2/cdnskey-x.secure.db rm -f ./ns2/cdnskey.secure.db rm -f ./ns2/cds-auto.secure.db ./ns2/cds-auto.secure.db.jnl rm -f ./ns2/cds-kskonly.secure.db +rm -f ./ns2/cds-kskonly.secure.id rm -f ./ns2/cds-update.secure.db ./ns2/cds-update.secure.db.jnl rm -f ./ns2/cds.secure.db ./ns2/cds-x.secure.db rm -f ./ns2/dlv.db @@ -50,8 +52,6 @@ rm -f ./ns2/nsec3chain-test.db rm -f ./ns2/private.secure.example.db rm -f ./ns2/single-nsec3.db rm -f ./ns2/updatecheck-kskonly.secure.* -rm -f ./ns3/secure.example.db ./ns3/*.managed.db ./ns3/*.trusted.db -rm -f ./ns3/unsupported.managed.db.tmp ./ns3/unsupported.trusted.db.tmp rm -f ./ns3/auto-nsec.example.db ./ns3/auto-nsec3.example.db rm -f ./ns3/badds.example.db rm -f ./ns3/dname-at-apex-nsec3.example.db @@ -59,10 +59,10 @@ rm -f ./ns3/dnskey-nsec3-unknown.example.db rm -f ./ns3/dnskey-nsec3-unknown.example.db.tmp rm -f ./ns3/dnskey-unknown.example.db rm -f ./ns3/dnskey-unknown.example.db.tmp -rm -f ./ns3/dnskey-unsupported.example.db -rm -f ./ns3/dnskey-unsupported.example.db.tmp rm -f ./ns3/dnskey-unsupported-2.example.db rm -f ./ns3/dnskey-unsupported-2.example.db.tmp +rm -f ./ns3/dnskey-unsupported.example.db +rm -f ./ns3/dnskey-unsupported.example.db.tmp rm -f ./ns3/dynamic.example.db ./ns3/dynamic.example.db.signed.jnl rm -f ./ns3/expired.example.db ./ns3/update-nsec3.example.db rm -f ./ns3/expiring.example.db ./ns3/nosign.example.db @@ -82,6 +82,7 @@ rm -f ./ns3/publish-inactive.example.db rm -f ./ns3/revkey.example.db rm -f ./ns3/rsasha256.example.db ./ns3/rsasha512.example.db rm -f ./ns3/secure.below-cname.example.db +rm -f ./ns3/secure.example.db ./ns3/*.managed.db ./ns3/*.trusted.db rm -f ./ns3/secure.nsec3.example.db rm -f ./ns3/secure.optout.example.db rm -f ./ns3/siginterval.conf @@ -91,6 +92,7 @@ rm -f ./ns3/split-smart.example.db rm -f ./ns3/ttlpatch.example.db ./ns3/ttlpatch.example.db.signed rm -f ./ns3/ttlpatch.example.db.patched rm -f ./ns3/unsecure.example.db ./ns3/bogus.example.db ./ns3/keyless.example.db +rm -f ./ns3/unsupported.managed.db.tmp ./ns3/unsupported.trusted.db.tmp rm -f ./ns4/managed-keys.bind* rm -f ./ns4/named_dump.db* rm -f ./ns6/optout-tld.db diff --git a/bin/tests/system/dnssec/ns2/sign.sh b/bin/tests/system/dnssec/ns2/sign.sh index 23964b9818..0c588f2e54 100644 --- a/bin/tests/system/dnssec/ns2/sign.sh +++ b/bin/tests/system/dnssec/ns2/sign.sh @@ -263,6 +263,7 @@ key1=$("$KEYGEN" -q -a "$DEFAULT_ALGORITHM" -b "$DEFAULT_BITS" -n zone -f KSK "$ key2=$("$KEYGEN" -q -a "$DEFAULT_ALGORITHM" -b "$DEFAULT_BITS" -n zone "$zone") cat "$infile" "$key1.key" "$key2.key" > "$zonefile" "$SIGNER" -P -g -o "$zone" "$zonefile" > /dev/null 2>&1 +keyfile_to_key_id "$key1" > cds-kskonly.secure.id zone=cds-auto.secure infile=cds-auto.secure.db.in @@ -306,6 +307,7 @@ key1=$("$KEYGEN" -q -a "$DEFAULT_ALGORITHM" -b "$DEFAULT_BITS" -n zone -f KSK "$ key2=$("$KEYGEN" -q -a "$DEFAULT_ALGORITHM" -b "$DEFAULT_BITS" -n zone "$zone") cat "$infile" "$key1.key" "$key2.key" > "$zonefile" "$SIGNER" -P -g -o "$zone" "$zonefile" > /dev/null 2>&1 +keyfile_to_key_id "$key1" > cdnskey-kskonly.secure.id zone=cdnskey-auto.secure infile=cdnskey-auto.secure.db.in diff --git a/bin/tests/system/dnssec/tests.sh b/bin/tests/system/dnssec/tests.sh index b9493b9f27..7fa7e2335a 100644 --- a/bin/tests/system/dnssec/tests.sh +++ b/bin/tests/system/dnssec/tests.sh @@ -3377,6 +3377,24 @@ n=$((n+1)) test "$ret" -eq 0 || echo_i "failed" status=$((status+ret)) +echo_i "check that a CDS deletion record is accepted ($n)" +ret=0 +( +echo zone cds-update.secure +echo server 10.53.0.2 "$PORT" +echo update delete cds-update.secure CDS +echo update add cds-update.secure 0 CDS 0 0 0 00 +echo send +) | $NSUPDATE > nsupdate.out.test$n 2>&1 +dig_with_opts +noall +answer @10.53.0.2 cds cds-update.secure > dig.out.test$n +lines=$(awk '$4 == "CDS" {print}' dig.out.test$n | wc -l) +test "${lines:-10}" -eq 1 || ret=1 +lines=$(awk '$4 == "CDS" && $5 == "0" && $6 == "0" && $7 == "0" && $8 == "00" {print}' dig.out.test$n | wc -l) +test "$lines" -eq 1 || ret=1 +n=$((n+1)) +test "$ret" -eq 0 || echo_i "failed" +status=$((status+ret)) + echo_i "check that CDS records are signed using KSK when added by nsupdate ($n)" ret=0 ( @@ -3402,6 +3420,7 @@ status=$((status+ret)) echo_i "check that CDS records are signed only using KSK when added by" echo_i " nsupdate when dnssec-dnskey-kskonly is yes ($n)" ret=0 +keyid=$(cat ns2/cds-kskonly.secure.id) ( echo zone cds-kskonly.secure echo server 10.53.0.2 "$PORT" @@ -3416,12 +3435,38 @@ echo send dig_with_opts +noall +answer @10.53.0.2 cds cds-kskonly.secure > dig.out.test$n lines=$(awk '$4 == "RRSIG" && $5 == "CDS" {print}' dig.out.test$n | wc -l) test "$lines" -eq 1 || ret=1 +lines=$(awk -v id="${keyid}" '$4 == "RRSIG" && $5 == "CDS" && $11 == id {print}' dig.out.test$n | wc -l) +test "$lines" -eq 1 || ret=1 lines=$(awk '$4 == "CDS" {print}' dig.out.test$n | wc -l) test "$lines" -eq 2 || ret=1 n=$((n+1)) test "$ret" -eq 0 || echo_i "failed" status=$((status+ret)) +echo_i "check that CDS deletion records are signed only using KSK when added by" +echo_i " nsupdate when dnssec-dnskey-kskonly is yes ($n)" +ret=0 +keyid=$(cat ns2/cds-kskonly.secure.id) +( +echo zone cds-kskonly.secure +echo server 10.53.0.2 "$PORT" +echo update delete cds-kskonly.secure CDS +echo update add cds-kskonly.secure 0 CDS 0 0 0 00 +echo send +) | $NSUPDATE +dig_with_opts +noall +answer @10.53.0.2 cds cds-kskonly.secure > dig.out.test$n +lines=$(awk '$4 == "RRSIG" && $5 == "CDS" {print}' dig.out.test$n | wc -l) +test "$lines" -eq 1 || ret=1 +lines=$(awk -v id="${keyid}" '$4 == "RRSIG" && $5 == "CDS" && $11 == id {print}' dig.out.test$n | wc -l) +test "$lines" -eq 1 || ret=1 +lines=$(awk '$4 == "CDS" {print}' dig.out.test$n | wc -l) +test "$lines" -eq 1 || ret=1 +lines=$(awk '$4 == "CDS" && $5 == "0" && $6 == "0" && $7 == "0" && $8 == "00" {print}' dig.out.test$n | wc -l) +test "$lines" -eq 1 || ret=1 +n=$((n+1)) +test "$ret" -eq 0 || echo_i "failed" +status=$((status+ret)) + echo_i "checking that positive unknown NSEC3 hash algorithm with OPTOUT does validate ($n)" ret=0 dig_with_opts +noauth +noadd +nodnssec +adflag @10.53.0.3 optout-unknown.example SOA > dig.out.ns3.test$n @@ -3557,6 +3602,24 @@ n=$((n+1)) test "$ret" -eq 0 || echo_i "failed" status=$((status+ret)) +echo_i "check that a CDNSKEY deletion record is accepted ($n)" +ret=0 +( +echo zone cdnskey-update.secure +echo server 10.53.0.2 "$PORT" +echo update delete cdnskey-update.secure CDNSKEY +echo update add cdnskey-update.secure 0 CDNSKEY 0 3 0 AA== +echo send +) | $NSUPDATE > nsupdate.out.test$n 2>&1 +dig_with_opts +noall +answer @10.53.0.2 cdnskey cdnskey-update.secure > dig.out.test$n +lines=$(awk '$4 == "CDNSKEY" {print}' dig.out.test$n | wc -l) +test "${lines:-10}" -eq 1 || ret=1 +lines=$(awk '$4 == "CDNSKEY" && $5 == "0" && $6 == "3" && $7 == "0" && $8 == "AA==" {print}' dig.out.test$n | wc -l) +test "${lines:-10}" -eq 1 || ret=1 +n=$((n+1)) +test "$ret" -eq 0 || echo_i "failed" +status=$((status+ret)) + echo_i "checking that unknown DNSKEY algorithm + unknown NSEC3 has algorithm validates as insecure ($n)" ret=0 dig_with_opts +noauth +noadd +nodnssec +adflag @10.53.0.3 dnskey-nsec3-unknown.example A > dig.out.ns3.test$n @@ -3590,6 +3653,7 @@ status=$((status+ret)) echo_i "check that CDNSKEY records are signed only using KSK when added by" echo_i " nsupdate when dnssec-dnskey-kskonly is yes ($n)" ret=0 +keyid=$(cat ns2/cdnskey-kskonly.secure.id) ( echo zone cdnskey-kskonly.secure echo server 10.53.0.2 "$PORT" @@ -3601,12 +3665,38 @@ echo send dig_with_opts +noall +answer @10.53.0.2 cdnskey cdnskey-kskonly.secure > dig.out.test$n lines=$(awk '$4 == "RRSIG" && $5 == "CDNSKEY" {print}' dig.out.test$n | wc -l) test "$lines" -eq 1 || ret=1 +lines=$(awk -v id="${keyid}" '$4 == "RRSIG" && $5 == "CDNSKEY" && $11 == id {print}' dig.out.test$n | wc -l) +test "$lines" -eq 1 || ret=1 lines=$(awk '$4 == "CDNSKEY" {print}' dig.out.test$n | wc -l) test "$lines" -eq 1 || ret=1 n=$((n+1)) test "$ret" -eq 0 || echo_i "failed" status=$((status+ret)) +echo_i "check that CDNSKEY deletion records are signed only using KSK when added by" +echo_i " nsupdate when dnssec-dnskey-kskonly is yes ($n)" +ret=0 +keyid=$(cat ns2/cdnskey-kskonly.secure.id) +( +echo zone cdnskey-kskonly.secure +echo server 10.53.0.2 "$PORT" +echo update delete cdnskey-kskonly.secure CDNSKEY +echo update add cdnskey-kskonly.secure 0 CDNSKEY 0 3 0 AA== +echo send +) | $NSUPDATE +dig_with_opts +noall +answer @10.53.0.2 cdnskey cdnskey-kskonly.secure > dig.out.test$n +lines=$(awk '$4 == "RRSIG" && $5 == "CDNSKEY" {print}' dig.out.test$n | wc -l) +test "$lines" -eq 1 || ret=1 +lines=$(awk -v id="${keyid}" '$4 == "RRSIG" && $5 == "CDNSKEY" && $11 == id {print}' dig.out.test$n | wc -l) +test "$lines" -eq 1 || ret=1 +lines=$(awk '$4 == "CDNSKEY" {print}' dig.out.test$n | wc -l) +test "$lines" -eq 1 || ret=1 +lines=$(awk '$4 == "CDNSKEY" && $5 == "0" && $6 == "3" && $7 == "0" && $8 == "AA==" {print}' dig.out.test$n | wc -l) +test "${lines:-10}" -eq 1 || ret=1 +n=$((n+1)) +test "$ret" -eq 0 || echo_i "failed" +status=$((status+ret)) + echo_i "checking initialization with a revoked managed key ($n)" ret=0 copy_setports ns5/named2.conf.in ns5/named.conf diff --git a/lib/dns/zone.c b/lib/dns/zone.c index edfddc8ac5..76b270feef 100644 --- a/lib/dns/zone.c +++ b/lib/dns/zone.c @@ -18985,11 +18985,14 @@ dns_zone_cdscheck(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *version) { unsigned char algorithms[256]; unsigned int i; + enum { notexpected = 0, expected = 1, found = 2 }; + REQUIRE(DNS_ZONE_VALID(zone)); result = dns_db_getoriginnode(db, &node); - if (result != ISC_R_SUCCESS) + if (result != ISC_R_SUCCESS) { return (result); + } dns_rdataset_init(&cds); dns_rdataset_init(&dnskey); @@ -18997,16 +19000,19 @@ dns_zone_cdscheck(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *version) { result = dns_db_findrdataset(db, node, version, dns_rdatatype_cds, dns_rdatatype_none, 0, &cds, NULL); - if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) + if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) { goto failure; + } result = dns_db_findrdataset(db, node, version, dns_rdatatype_cdnskey, dns_rdatatype_none, 0, &cdnskey, NULL); - if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) + if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) { goto failure; + } if (!dns_rdataset_isassociated(&cds) && - !dns_rdataset_isassociated(&cdnskey)) { + !dns_rdataset_isassociated(&cdnskey)) + { result = ISC_R_SUCCESS; goto failure; } @@ -19014,21 +19020,25 @@ dns_zone_cdscheck(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *version) { result = dns_db_findrdataset(db, node, version, dns_rdatatype_dnskey, dns_rdatatype_none, 0, &dnskey, NULL); if (result == ISC_R_NOTFOUND) { - if (dns_rdataset_isassociated(&cds)) + if (dns_rdataset_isassociated(&cds)) { result = DNS_R_BADCDS; - else + } else { result = DNS_R_BADCDNSKEY; + } goto failure; } - if (result != ISC_R_SUCCESS) + if (result != ISC_R_SUCCESS) { goto failure; + } /* * For each DNSSEC algorithm in the CDS RRset there must be - * a matching DNSKEY record. + * a matching DNSKEY record with the exception of a CDS deletion + * record which must be by itself. */ if (dns_rdataset_isassociated(&cds)) { - memset(algorithms, 0, sizeof(algorithms)); + bool delete = false; + memset(algorithms, notexpected, sizeof(algorithms)); for (result = dns_rdataset_first(&cds); result == ISC_R_SUCCESS; result = dns_rdataset_next(&cds)) { @@ -19036,9 +19046,21 @@ dns_zone_cdscheck(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *version) { dns_rdata_cds_t structcds; dns_rdataset_current(&cds, &crdata); + /* + * CDS deletion record has this form "0 0 0 00" which + * is 5 zero octets. + */ + if (crdata.length == 5U && + memcmp(crdata.data, + (unsigned char[5]){ 0, 0, 0, 0, 0 }, 5) == 0) + { + delete = true; + continue; + } CHECK(dns_rdata_tostruct(&crdata, &structcds, NULL)); - if (algorithms[structcds.algorithm] == 0) - algorithms[structcds.algorithm] = 1; + if (algorithms[structcds.algorithm] == 0) { + algorithms[structcds.algorithm] = expected; + } for (result = dns_rdataset_first(&dnskey); result == ISC_R_SUCCESS; result = dns_rdataset_next(&dnskey)) { @@ -19051,16 +19073,23 @@ dns_zone_cdscheck(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *version) { buffer, &dsrdata)); if (crdata.length == dsrdata.length && memcmp(crdata.data, dsrdata.data, - dsrdata.length) == 0) { - algorithms[structcds.algorithm] = 2; + dsrdata.length) == 0) + { + algorithms[structcds.algorithm] = found; } } - if (result != ISC_R_NOMORE) + if (result != ISC_R_NOMORE) { goto failure; + } } for (i = 0; i < sizeof(algorithms); i++) { - if (algorithms[i] == 1) { - result = DNS_R_BADCDNSKEY; + if (delete) { + if (algorithms[i] != notexpected) { + result = DNS_R_BADCDS; + goto failure; + } + } else if (algorithms[i] == expected) { + result = DNS_R_BADCDS; goto failure; } } @@ -19068,10 +19097,12 @@ dns_zone_cdscheck(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *version) { /* * For each DNSSEC algorithm in the CDNSKEY RRset there must be - * a matching DNSKEY record. + * a matching DNSKEY record with the exception of a CDNSKEY deletion + * record which must be by itself. */ if (dns_rdataset_isassociated(&cdnskey)) { - memset(algorithms, 0, sizeof(algorithms)); + bool delete = false; + memset(algorithms, notexpected, sizeof(algorithms)); for (result = dns_rdataset_first(&cdnskey); result == ISC_R_SUCCESS; result = dns_rdataset_next(&cdnskey)) { @@ -19079,10 +19110,23 @@ dns_zone_cdscheck(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *version) { dns_rdata_cdnskey_t structcdnskey; dns_rdataset_current(&cdnskey, &crdata); + /* + * CDNSKEY deletion record has this form + * "0 3 0 AA==" which is 2 zero octets, a 3, + * and 2 zero octets. + */ + if (crdata.length == 5U && + memcmp(crdata.data, + (unsigned char[5]){ 0, 0, 3, 0, 0 }, 5) == 0) + { + delete = true; + continue; + } CHECK(dns_rdata_tostruct(&crdata, &structcdnskey, NULL)); - if (algorithms[structcdnskey.algorithm] == 0) - algorithms[structcdnskey.algorithm] = 1; + if (algorithms[structcdnskey.algorithm] == 0) { + algorithms[structcdnskey.algorithm] = expected; + } for (result = dns_rdataset_first(&dnskey); result == ISC_R_SUCCESS; result = dns_rdataset_next(&dnskey)) { @@ -19091,16 +19135,24 @@ dns_zone_cdscheck(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *version) { dns_rdataset_current(&dnskey, &rdata); if (crdata.length == rdata.length && memcmp(crdata.data, rdata.data, - rdata.length) == 0) { - algorithms[structcdnskey.algorithm] = 2; + rdata.length) == 0) + { + algorithms[structcdnskey.algorithm] = + found; } } - if (result != ISC_R_NOMORE) + if (result != ISC_R_NOMORE) { goto failure; + } } for (i = 0; i < sizeof(algorithms); i++) { - if (algorithms[i] == 1) { - result = DNS_R_BADCDS; + if (delete) { + if (algorithms[i] != notexpected) { + result = DNS_R_BADCDNSKEY; + goto failure; + } + } else if (algorithms[i] == expected) { + result = DNS_R_BADCDNSKEY; goto failure; } } @@ -19108,12 +19160,15 @@ dns_zone_cdscheck(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *version) { result = ISC_R_SUCCESS; failure: - if (dns_rdataset_isassociated(&cds)) + if (dns_rdataset_isassociated(&cds)) { dns_rdataset_disassociate(&cds); - if (dns_rdataset_isassociated(&dnskey)) + } + if (dns_rdataset_isassociated(&dnskey)) { dns_rdataset_disassociate(&dnskey); - if (dns_rdataset_isassociated(&cdnskey)) + } + if (dns_rdataset_isassociated(&cdnskey)) { dns_rdataset_disassociate(&cdnskey); + } dns_db_detachnode(db, &node); return (result); }