From 14abd767349bc868ca59838f1af3aaf17dfe4350 Mon Sep 17 00:00:00 2001 From: Yi Zhao Date: Thu, 13 Apr 2017 13:48:13 +0800 Subject: bind: Security fix CVE-2016-6170 CVE-2016-6170: ISC BIND through 9.9.9-P1, 9.10.x through 9.10.4-P1, and 9.11.x through 9.11.0b1 allows primary DNS servers to cause a denial of service (secondary DNS server crash) via a large AXFR response, and possibly allows IXFR servers to cause a denial of service (IXFR client crash) via a large IXFR response and allows remote authenticated users to cause a denial of service (primary DNS server crash) via a large UPDATE message. External References: https://nvd.nist.gov/vuln/detail/CVE-2016-6170 Patch from: https://source.isc.org/cgi-bin/gitweb.cgi?p=bind9.git;a=commit;h=1bbcfe2fc84f57b1e4e075fb3bc2a1dd0a3a851f Signed-off-by: Yi Zhao Signed-off-by: Ross Burton --- .../bind/bind/CVE-2016-6170.patch | 1090 ++++++++++++++++++++ 1 file changed, 1090 insertions(+) create mode 100644 meta/recipes-connectivity/bind/bind/CVE-2016-6170.patch (limited to 'meta/recipes-connectivity/bind/bind/CVE-2016-6170.patch') diff --git a/meta/recipes-connectivity/bind/bind/CVE-2016-6170.patch b/meta/recipes-connectivity/bind/bind/CVE-2016-6170.patch new file mode 100644 index 0000000000..75bc211cb6 --- /dev/null +++ b/meta/recipes-connectivity/bind/bind/CVE-2016-6170.patch @@ -0,0 +1,1090 @@ +From 1bbcfe2fc84f57b1e4e075fb3bc2a1dd0a3a851f Mon Sep 17 00:00:00 2001 +From: Mark Andrews +Date: Wed, 2 Nov 2016 17:31:27 +1100 +Subject: [PATCH] 4504. [security] Allow the maximum number of records in a + zone to be specified. This provides a control for issues raised in + CVE-2016-6170. [RT #42143] + +(cherry picked from commit 5f8412a4cb5ee14a0e8cddd4107854b40ee3291e) + +Upstream-Status: Backport +[https://source.isc.org/cgi-bin/gitweb.cgi?p=bind9.git;a=commit;h=1bbcfe2fc84f57b1e4e075fb3bc2a1dd0a3a851f] + +CVE: CVE-2016-6170 + +Signed-off-by: Yi Zhao +--- + CHANGES | 4 + + bin/named/config.c | 1 + + bin/named/named.conf.docbook | 3 + + bin/named/update.c | 16 +++ + bin/named/zoneconf.c | 7 ++ + bin/tests/system/nsupdate/clean.sh | 1 + + bin/tests/system/nsupdate/ns3/named.conf | 7 ++ + bin/tests/system/nsupdate/ns3/too-big.test.db.in | 10 ++ + bin/tests/system/nsupdate/setup.sh | 2 + + bin/tests/system/nsupdate/tests.sh | 15 +++ + bin/tests/system/xfer/clean.sh | 1 + + bin/tests/system/xfer/ns1/axfr-too-big.db | 10 ++ + bin/tests/system/xfer/ns1/ixfr-too-big.db.in | 13 +++ + bin/tests/system/xfer/ns1/named.conf | 11 ++ + bin/tests/system/xfer/ns6/named.conf | 14 +++ + bin/tests/system/xfer/setup.sh | 2 + + bin/tests/system/xfer/tests.sh | 26 +++++ + doc/arm/Bv9ARM-book.xml | 21 ++++ + doc/arm/notes.xml | 9 ++ + lib/bind9/check.c | 2 + + lib/dns/db.c | 13 +++ + lib/dns/ecdb.c | 3 +- + lib/dns/include/dns/db.h | 20 ++++ + lib/dns/include/dns/rdataslab.h | 13 +++ + lib/dns/include/dns/result.h | 6 +- + lib/dns/include/dns/zone.h | 28 ++++- + lib/dns/rbtdb.c | 127 +++++++++++++++++++++-- + lib/dns/rdataslab.c | 13 +++ + lib/dns/result.c | 9 +- + lib/dns/sdb.c | 3 +- + lib/dns/sdlz.c | 3 +- + lib/dns/xfrin.c | 22 +++- + lib/dns/zone.c | 23 +++- + lib/isccfg/namedconf.c | 1 + + 34 files changed, 444 insertions(+), 15 deletions(-) + create mode 100644 bin/tests/system/nsupdate/ns3/too-big.test.db.in + create mode 100644 bin/tests/system/xfer/ns1/axfr-too-big.db + create mode 100644 bin/tests/system/xfer/ns1/ixfr-too-big.db.in + +diff --git a/CHANGES b/CHANGES +index 41cfce5..97d2e60 100644 +--- a/CHANGES ++++ b/CHANGES +@@ -1,3 +1,7 @@ ++4504. [security] Allow the maximum number of records in a zone to ++ be specified. This provides a control for issues ++ raised in CVE-2016-6170. [RT #42143] ++ + 4489. [security] It was possible to trigger assertions when processing + a response. (CVE-2016-8864) [RT #43465] + +diff --git a/bin/named/config.c b/bin/named/config.c +index f06348c..c24e334 100644 +--- a/bin/named/config.c ++++ b/bin/named/config.c +@@ -209,6 +209,7 @@ options {\n\ + max-transfer-time-out 120;\n\ + max-transfer-idle-in 60;\n\ + max-transfer-idle-out 60;\n\ ++ max-records 0;\n\ + max-retry-time 1209600; /* 2 weeks */\n\ + min-retry-time 500;\n\ + max-refresh-time 2419200; /* 4 weeks */\n\ +diff --git a/bin/named/named.conf.docbook b/bin/named/named.conf.docbook +index 4c99a61..c2d173a 100644 +--- a/bin/named/named.conf.docbook ++++ b/bin/named/named.conf.docbook +@@ -338,6 +338,7 @@ options { + }; + + max-journal-size size_no_default; ++ max-records integer; + max-transfer-time-in integer; + max-transfer-time-out integer; + max-transfer-idle-in integer; +@@ -527,6 +528,7 @@ view string optional_class + }; + + max-journal-size size_no_default; ++ max-records integer; + max-transfer-time-in integer; + max-transfer-time-out integer; + max-transfer-idle-in integer; +@@ -624,6 +626,7 @@ zone string optional_class + }; + + max-journal-size size_no_default; ++ max-records integer; + max-transfer-time-in integer; + max-transfer-time-out integer; + max-transfer-idle-in integer; +diff --git a/bin/named/update.c b/bin/named/update.c +index 83b1a05..cc2a611 100644 +--- a/bin/named/update.c ++++ b/bin/named/update.c +@@ -2455,6 +2455,8 @@ update_action(isc_task_t *task, isc_event_t *event) { + isc_boolean_t had_dnskey; + dns_rdatatype_t privatetype = dns_zone_getprivatetype(zone); + dns_ttl_t maxttl = 0; ++ isc_uint32_t maxrecords; ++ isc_uint64_t records; + + INSIST(event->ev_type == DNS_EVENT_UPDATE); + +@@ -3138,6 +3140,20 @@ update_action(isc_task_t *task, isc_event_t *event) { + } + } + ++ maxrecords = dns_zone_getmaxrecords(zone); ++ if (maxrecords != 0U) { ++ result = dns_db_getsize(db, ver, &records, NULL); ++ if (result == ISC_R_SUCCESS && records > maxrecords) { ++ update_log(client, zone, ISC_LOG_ERROR, ++ "records in zone (%" ++ ISC_PRINT_QUADFORMAT ++ "u) exceeds max-records (%u)", ++ records, maxrecords); ++ result = DNS_R_TOOMANYRECORDS; ++ goto failure; ++ } ++ } ++ + journalfile = dns_zone_getjournal(zone); + if (journalfile != NULL) { + update_log(client, zone, LOGLEVEL_DEBUG, +diff --git a/bin/named/zoneconf.c b/bin/named/zoneconf.c +index 4ee3dfe..14dd8ce 100644 +--- a/bin/named/zoneconf.c ++++ b/bin/named/zoneconf.c +@@ -978,6 +978,13 @@ ns_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig, + dns_zone_setmaxttl(raw, maxttl); + } + ++ obj = NULL; ++ result = ns_config_get(maps, "max-records", &obj); ++ INSIST(result == ISC_R_SUCCESS && obj != NULL); ++ dns_zone_setmaxrecords(mayberaw, cfg_obj_asuint32(obj)); ++ if (zone != mayberaw) ++ dns_zone_setmaxrecords(zone, 0); ++ + if (raw != NULL && filename != NULL) { + #define SIGNED ".signed" + size_t signedlen = strlen(filename) + sizeof(SIGNED); +diff --git a/bin/tests/system/nsupdate/clean.sh b/bin/tests/system/nsupdate/clean.sh +index aaefc02..ea25545 100644 +--- a/bin/tests/system/nsupdate/clean.sh ++++ b/bin/tests/system/nsupdate/clean.sh +@@ -32,6 +32,7 @@ rm -f ns3/example.db.jnl ns3/example.db + rm -f ns3/nsec3param.test.db.signed.jnl ns3/nsec3param.test.db ns3/nsec3param.test.db.signed ns3/dsset-nsec3param.test. + rm -f ns3/dnskey.test.db.signed.jnl ns3/dnskey.test.db ns3/dnskey.test.db.signed ns3/dsset-dnskey.test. + rm -f ns3/K* ++rm -f ns3/too-big.test.db + rm -f dig.out.* + rm -f jp.out.ns3.* + rm -f Kxxx.* +diff --git a/bin/tests/system/nsupdate/ns3/named.conf b/bin/tests/system/nsupdate/ns3/named.conf +index 2abd522..68ff27a 100644 +--- a/bin/tests/system/nsupdate/ns3/named.conf ++++ b/bin/tests/system/nsupdate/ns3/named.conf +@@ -60,3 +60,10 @@ zone "dnskey.test" { + allow-update { any; }; + file "dnskey.test.db.signed"; + }; ++ ++zone "too-big.test" { ++ type master; ++ allow-update { any; }; ++ max-records 3; ++ file "too-big.test.db"; ++}; +diff --git a/bin/tests/system/nsupdate/ns3/too-big.test.db.in b/bin/tests/system/nsupdate/ns3/too-big.test.db.in +new file mode 100644 +index 0000000..7ff1e4a +--- /dev/null ++++ b/bin/tests/system/nsupdate/ns3/too-big.test.db.in +@@ -0,0 +1,10 @@ ++; Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC") ++; ++; This Source Code Form is subject to the terms of the Mozilla Public ++; License, v. 2.0. If a copy of the MPL was not distributed with this ++; file, You can obtain one at http://mozilla.org/MPL/2.0/. ++ ++$TTL 10 ++too-big.test. IN SOA too-big.test. hostmaster.too-big.test. 1 3600 900 2419200 3600 ++too-big.test. IN NS too-big.test. ++too-big.test. IN A 10.53.0.3 +diff --git a/bin/tests/system/nsupdate/setup.sh b/bin/tests/system/nsupdate/setup.sh +index 828255e..43c4094 100644 +--- a/bin/tests/system/nsupdate/setup.sh ++++ b/bin/tests/system/nsupdate/setup.sh +@@ -27,12 +27,14 @@ test -r $RANDFILE || $GENRANDOM 400 $RANDFILE + rm -f ns1/*.jnl ns1/example.db ns2/*.jnl ns2/example.bk + rm -f ns2/update.bk ns2/update.alt.bk + rm -f ns3/example.db.jnl ++rm -f ns3/too-big.test.db.jnl + + cp -f ns1/example1.db ns1/example.db + sed 's/example.nil/other.nil/g' ns1/example1.db > ns1/other.db + sed 's/example.nil/unixtime.nil/g' ns1/example1.db > ns1/unixtime.db + sed 's/example.nil/keytests.nil/g' ns1/example1.db > ns1/keytests.db + cp -f ns3/example.db.in ns3/example.db ++cp -f ns3/too-big.test.db.in ns3/too-big.test.db + + # update_test.pl has its own zone file because it + # requires a specific NS record set. +diff --git a/bin/tests/system/nsupdate/tests.sh b/bin/tests/system/nsupdate/tests.sh +index 78d501e..0a6bbd3 100755 +--- a/bin/tests/system/nsupdate/tests.sh ++++ b/bin/tests/system/nsupdate/tests.sh +@@ -581,5 +581,20 @@ if [ $ret -ne 0 ]; then + status=1 + fi + ++n=`expr $n + 1` ++echo "I:check that adding too many records is blocked ($n)" ++ret=0 ++$NSUPDATE -v << EOF > nsupdate.out-$n 2>&1 && ret=1 ++server 10.53.0.3 5300 ++zone too-big.test. ++update add r1.too-big.test 3600 IN TXT r1.too-big.test ++send ++EOF ++grep "update failed: SERVFAIL" nsupdate.out-$n > /dev/null || ret=1 ++DIG +tcp @10.53.0.3 -p 5300 r1.too-big.test TXT > dig.out.ns3.test$n ++grep "status: NXDOMAIN" dig.out.ns3.test$n > /dev/null || ret=1 ++grep "records in zone (4) exceeds max-records (3)" ns3/named.run > /dev/null || ret=1 ++[ $ret = 0 ] || { echo I:failed; status=1; } ++ + echo "I:exit status: $status" + exit $status +diff --git a/bin/tests/system/xfer/clean.sh b/bin/tests/system/xfer/clean.sh +index 48aa159..da62a33 100644 +--- a/bin/tests/system/xfer/clean.sh ++++ b/bin/tests/system/xfer/clean.sh +@@ -36,3 +36,4 @@ rm -f ns7/*.db ns7/*.bk ns7/*.jnl + rm -f */named.memstats + rm -f */named.run + rm -f */ans.run ++rm -f ns1/ixfr-too-big.db ns1/ixfr-too-big.db.jnl +diff --git a/bin/tests/system/xfer/ns1/axfr-too-big.db b/bin/tests/system/xfer/ns1/axfr-too-big.db +new file mode 100644 +index 0000000..d43760d +--- /dev/null ++++ b/bin/tests/system/xfer/ns1/axfr-too-big.db +@@ -0,0 +1,10 @@ ++; Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC") ++; ++; This Source Code Form is subject to the terms of the Mozilla Public ++; License, v. 2.0. If a copy of the MPL was not distributed with this ++; file, You can obtain one at http://mozilla.org/MPL/2.0/. ++ ++$TTL 3600 ++@ IN SOA . . 0 0 0 0 0 ++@ IN NS . ++$GENERATE 1-29 host$ A 1.2.3.$ +diff --git a/bin/tests/system/xfer/ns1/ixfr-too-big.db.in b/bin/tests/system/xfer/ns1/ixfr-too-big.db.in +new file mode 100644 +index 0000000..318bb77 +--- /dev/null ++++ b/bin/tests/system/xfer/ns1/ixfr-too-big.db.in +@@ -0,0 +1,13 @@ ++; Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC") ++; ++; This Source Code Form is subject to the terms of the Mozilla Public ++; License, v. 2.0. If a copy of the MPL was not distributed with this ++; file, You can obtain one at http://mozilla.org/MPL/2.0/. ++ ++$TTL 3600 ++@ IN SOA . . 0 0 0 0 0 ++@ IN NS ns1 ++@ IN NS ns6 ++ns1 IN A 10.53.0.1 ++ns6 IN A 10.53.0.6 ++$GENERATE 1-25 host$ A 1.2.3.$ +diff --git a/bin/tests/system/xfer/ns1/named.conf b/bin/tests/system/xfer/ns1/named.conf +index 07dad85..1d29292 100644 +--- a/bin/tests/system/xfer/ns1/named.conf ++++ b/bin/tests/system/xfer/ns1/named.conf +@@ -44,3 +44,14 @@ zone "slave" { + type master; + file "slave.db"; + }; ++ ++zone "axfr-too-big" { ++ type master; ++ file "axfr-too-big.db"; ++}; ++ ++zone "ixfr-too-big" { ++ type master; ++ allow-update { any; }; ++ file "ixfr-too-big.db"; ++}; +diff --git a/bin/tests/system/xfer/ns6/named.conf b/bin/tests/system/xfer/ns6/named.conf +index c9421b1..a12a92c 100644 +--- a/bin/tests/system/xfer/ns6/named.conf ++++ b/bin/tests/system/xfer/ns6/named.conf +@@ -52,3 +52,17 @@ zone "slave" { + masters { 10.53.0.1; }; + file "slave.bk"; + }; ++ ++zone "axfr-too-big" { ++ type slave; ++ max-records 30; ++ masters { 10.53.0.1; }; ++ file "axfr-too-big.bk"; ++}; ++ ++zone "ixfr-too-big" { ++ type slave; ++ max-records 30; ++ masters { 10.53.0.1; }; ++ file "ixfr-too-big.bk"; ++}; +diff --git a/bin/tests/system/xfer/setup.sh b/bin/tests/system/xfer/setup.sh +index 56ca901..c55abf8 100644 +--- a/bin/tests/system/xfer/setup.sh ++++ b/bin/tests/system/xfer/setup.sh +@@ -33,3 +33,5 @@ cp -f ns4/named.conf.base ns4/named.conf + + cp ns2/slave.db.in ns2/slave.db + touch -t 200101010000 ns2/slave.db ++ ++cp -f ns1/ixfr-too-big.db.in ns1/ixfr-too-big.db +diff --git a/bin/tests/system/xfer/tests.sh b/bin/tests/system/xfer/tests.sh +index 67b2a1a..fe33f0a 100644 +--- a/bin/tests/system/xfer/tests.sh ++++ b/bin/tests/system/xfer/tests.sh +@@ -368,5 +368,31 @@ $DIGCMD nil. TXT | grep 'incorrect key AXFR' >/dev/null && { + status=1 + } + ++n=`expr $n + 1` ++echo "I:test that a zone with too many records is rejected (AXFR) ($n)" ++tmp=0 ++grep "'axfr-too-big/IN'.*: too many records" ns6/named.run >/dev/null || tmp=1 ++if test $tmp != 0 ; then echo "I:failed"; fi ++status=`expr $status + $tmp` ++ ++n=`expr $n + 1` ++echo "I:test that a zone with too many records is rejected (IXFR) ($n)" ++tmp=0 ++grep "'ixfr-too-big./IN.*: too many records" ns6/named.run >/dev/null && tmp=1 ++$NSUPDATE << EOF ++zone ixfr-too-big ++server 10.53.0.1 5300 ++update add the-31st-record.ixfr-too-big 0 TXT this is it ++send ++EOF ++for i in 1 2 3 4 5 6 7 8 ++do ++ grep "'ixfr-too-big/IN'.*: too many records" ns6/named.run >/dev/null && break ++ sleep 1 ++done ++grep "'ixfr-too-big/IN'.*: too many records" ns6/named.run >/dev/null || tmp=1 ++if test $tmp != 0 ; then echo "I:failed"; fi ++status=`expr $status + $tmp` ++ + echo "I:exit status: $status" + exit $status +diff --git a/doc/arm/Bv9ARM-book.xml b/doc/arm/Bv9ARM-book.xml +index 848b582..0369505 100644 +--- a/doc/arm/Bv9ARM-book.xml ++++ b/doc/arm/Bv9ARM-book.xml +@@ -4858,6 +4858,7 @@ badresp:1,adberr:0,findfail:0,valfail:0] + use-queryport-pool yes_or_no; + queryport-pool-ports number; + queryport-pool-updateinterval number; ++ max-records number; + max-transfer-time-in number; + max-transfer-time-out number; + max-transfer-idle-in number; +@@ -8164,6 +8165,16 @@ avoid-v6-udp-ports { 40000; range 50000 60000; }; + + + ++ max-records ++ ++ ++ The maximum number of records permitted in a zone. ++ The default is zero which means unlimited. ++ ++ ++ ++ ++ + host-statistics-max + + +@@ -12056,6 +12067,16 @@ zone zone_name class + + ++ max-records ++ ++ ++ See the description of ++ max-records in . ++ ++ ++ ++ ++ + max-transfer-time-in + + +diff --git a/doc/arm/notes.xml b/doc/arm/notes.xml +index 095eb5b..36495e7 100644 +--- a/doc/arm/notes.xml ++++ b/doc/arm/notes.xml +@@ -52,6 +52,15 @@ + + + ++ Added the ability to specify the maximum number of records ++ permitted in a zone (max-records #;). This provides a mechanism ++ to block overly large zone transfers, which is a potential risk ++ with slave zones from other parties, as described in CVE-2016-6170. ++ [RT #42143] ++ ++ ++ ++ + Duplicate EDNS COOKIE options in a response could trigger + an assertion failure. This flaw is disclosed in CVE-2016-2088. + [RT #41809] +diff --git a/lib/bind9/check.c b/lib/bind9/check.c +index b8c05dd..edb7534 100644 +--- a/lib/bind9/check.c ++++ b/lib/bind9/check.c +@@ -1510,6 +1510,8 @@ check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions, + REDIRECTZONE }, + { "masters", SLAVEZONE | STUBZONE | REDIRECTZONE }, + { "max-ixfr-log-size", MASTERZONE | SLAVEZONE | STREDIRECTZONE }, ++ { "max-records", MASTERZONE | SLAVEZONE | STUBZONE | STREDIRECTZONE | ++ STATICSTUBZONE | REDIRECTZONE }, + { "max-refresh-time", SLAVEZONE | STUBZONE | STREDIRECTZONE }, + { "max-retry-time", SLAVEZONE | STUBZONE | STREDIRECTZONE }, + { "max-transfer-idle-in", SLAVEZONE | STUBZONE | STREDIRECTZONE }, +diff --git a/lib/dns/db.c b/lib/dns/db.c +index 7e4f357..ced94a5 100644 +--- a/lib/dns/db.c ++++ b/lib/dns/db.c +@@ -999,6 +999,19 @@ dns_db_getnsec3parameters(dns_db_t *db, dns_dbversion_t *version, + } + + isc_result_t ++dns_db_getsize(dns_db_t *db, dns_dbversion_t *version, isc_uint64_t *records, ++ isc_uint64_t *bytes) ++{ ++ REQUIRE(DNS_DB_VALID(db)); ++ REQUIRE(dns_db_iszone(db) == ISC_TRUE); ++ ++ if (db->methods->getsize != NULL) ++ return ((db->methods->getsize)(db, version, records, bytes)); ++ ++ return (ISC_R_NOTFOUND); ++} ++ ++isc_result_t + dns_db_setsigningtime(dns_db_t *db, dns_rdataset_t *rdataset, + isc_stdtime_t resign) + { +diff --git a/lib/dns/ecdb.c b/lib/dns/ecdb.c +index 553a339..b5d04d2 100644 +--- a/lib/dns/ecdb.c ++++ b/lib/dns/ecdb.c +@@ -587,7 +587,8 @@ static dns_dbmethods_t ecdb_methods = { + NULL, /* findnodeext */ + NULL, /* findext */ + NULL, /* setcachestats */ +- NULL /* hashsize */ ++ NULL, /* hashsize */ ++ NULL /* getsize */ + }; + + static isc_result_t +diff --git a/lib/dns/include/dns/db.h b/lib/dns/include/dns/db.h +index a4a4482..aff42d6 100644 +--- a/lib/dns/include/dns/db.h ++++ b/lib/dns/include/dns/db.h +@@ -195,6 +195,8 @@ typedef struct dns_dbmethods { + dns_rdataset_t *sigrdataset); + isc_result_t (*setcachestats)(dns_db_t *db, isc_stats_t *stats); + unsigned int (*hashsize)(dns_db_t *db); ++ isc_result_t (*getsize)(dns_db_t *db, dns_dbversion_t *version, ++ isc_uint64_t *records, isc_uint64_t *bytes); + } dns_dbmethods_t; + + typedef isc_result_t +@@ -1485,6 +1487,24 @@ dns_db_getnsec3parameters(dns_db_t *db, dns_dbversion_t *version, + */ + + isc_result_t ++dns_db_getsize(dns_db_t *db, dns_dbversion_t *version, isc_uint64_t *records, ++ isc_uint64_t *bytes); ++/*%< ++ * Get the number of records in the given version of the database as well ++ * as the number bytes used to store those records. ++ * ++ * Requires: ++ * \li 'db' is a valid zone database. ++ * \li 'version' is NULL or a valid version. ++ * \li 'records' is NULL or a pointer to return the record count in. ++ * \li 'bytes' is NULL or a pointer to return the byte count in. ++ * ++ * Returns: ++ * \li #ISC_R_SUCCESS ++ * \li #ISC_R_NOTIMPLEMENTED ++ */ ++ ++isc_result_t + dns_db_findnsec3node(dns_db_t *db, dns_name_t *name, + isc_boolean_t create, dns_dbnode_t **nodep); + /*%< +diff --git a/lib/dns/include/dns/rdataslab.h b/lib/dns/include/dns/rdataslab.h +index 3ac44b8..2e1e759 100644 +--- a/lib/dns/include/dns/rdataslab.h ++++ b/lib/dns/include/dns/rdataslab.h +@@ -104,6 +104,7 @@ dns_rdataslab_tordataset(unsigned char *slab, unsigned int reservelen, + * Ensures: + *\li 'rdataset' is associated and points to a valid rdataest. + */ ++ + unsigned int + dns_rdataslab_size(unsigned char *slab, unsigned int reservelen); + /*%< +@@ -116,6 +117,18 @@ dns_rdataslab_size(unsigned char *slab, unsigned int reservelen); + *\li The number of bytes in the slab, including the reservelen. + */ + ++unsigned int ++dns_rdataslab_count(unsigned char *slab, unsigned int reservelen); ++/*%< ++ * Return the number of records in the rdataslab ++ * ++ * Requires: ++ *\li 'slab' points to a slab. ++ * ++ * Returns: ++ *\li The number of records in the slab. ++ */ ++ + isc_result_t + dns_rdataslab_merge(unsigned char *oslab, unsigned char *nslab, + unsigned int reservelen, isc_mem_t *mctx, +diff --git a/lib/dns/include/dns/result.h b/lib/dns/include/dns/result.h +index 7d11c2b..93d1fd5 100644 +--- a/lib/dns/include/dns/result.h ++++ b/lib/dns/include/dns/result.h +@@ -157,8 +157,12 @@ + #define DNS_R_BADCDS (ISC_RESULTCLASS_DNS + 111) + #define DNS_R_BADCDNSKEY (ISC_RESULTCLASS_DNS + 112) + #define DNS_R_OPTERR (ISC_RESULTCLASS_DNS + 113) ++#define DNS_R_BADDNSTAP (ISC_RESULTCLASS_DNS + 114) ++#define DNS_R_BADTSIG (ISC_RESULTCLASS_DNS + 115) ++#define DNS_R_BADSIG0 (ISC_RESULTCLASS_DNS + 116) ++#define DNS_R_TOOMANYRECORDS (ISC_RESULTCLASS_DNS + 117) + +-#define DNS_R_NRESULTS 114 /*%< Number of results */ ++#define DNS_R_NRESULTS 118 /*%< Number of results */ + + /* + * DNS wire format rcodes. +diff --git a/lib/dns/include/dns/zone.h b/lib/dns/include/dns/zone.h +index a9367f1..227540b 100644 +--- a/lib/dns/include/dns/zone.h ++++ b/lib/dns/include/dns/zone.h +@@ -296,6 +296,32 @@ dns_zone_getfile(dns_zone_t *zone); + */ + + void ++dns_zone_setmaxrecords(dns_zone_t *zone, isc_uint32_t records); ++/*%< ++ * Sets the maximim number of records permitted in a zone. ++ * 0 implies unlimited. ++ * ++ * Requires: ++ *\li 'zone' to be valid initialised zone. ++ * ++ * Returns: ++ *\li void ++ */ ++ ++isc_uint32_t ++dns_zone_getmaxrecords(dns_zone_t *zone); ++/*%< ++ * Gets the maximim number of records permitted in a zone. ++ * 0 implies unlimited. ++ * ++ * Requires: ++ *\li 'zone' to be valid initialised zone. ++ * ++ * Returns: ++ *\li isc_uint32_t maxrecords. ++ */ ++ ++void + dns_zone_setmaxttl(dns_zone_t *zone, isc_uint32_t maxttl); + /*%< + * Sets the max ttl of the zone. +@@ -316,7 +342,7 @@ dns_zone_getmaxttl(dns_zone_t *zone); + *\li 'zone' to be valid initialised zone. + * + * Returns: +- *\li isc_uint32_t maxttl. ++ *\li dns_ttl_t maxttl. + */ + + isc_result_t +diff --git a/lib/dns/rbtdb.c b/lib/dns/rbtdb.c +index 62becfc..72d722f 100644 +--- a/lib/dns/rbtdb.c ++++ b/lib/dns/rbtdb.c +@@ -209,6 +209,7 @@ typedef isc_uint64_t rbtdb_serial_t; + #define free_rbtdb_callback free_rbtdb_callback64 + #define free_rdataset free_rdataset64 + #define getnsec3parameters getnsec3parameters64 ++#define getsize getsize64 + #define getoriginnode getoriginnode64 + #define getrrsetstats getrrsetstats64 + #define getsigningtime getsigningtime64 +@@ -589,6 +590,13 @@ typedef struct rbtdb_version { + isc_uint16_t iterations; + isc_uint8_t salt_length; + unsigned char salt[DNS_NSEC3_SALTSIZE]; ++ ++ /* ++ * records and bytes are covered by rwlock. ++ */ ++ isc_rwlock_t rwlock; ++ isc_uint64_t records; ++ isc_uint64_t bytes; + } rbtdb_version_t; + + typedef ISC_LIST(rbtdb_version_t) rbtdb_versionlist_t; +@@ -1130,6 +1138,7 @@ free_rbtdb(dns_rbtdb_t *rbtdb, isc_boolean_t log, isc_event_t *event) { + INSIST(refs == 0); + UNLINK(rbtdb->open_versions, rbtdb->current_version, link); + isc_refcount_destroy(&rbtdb->current_version->references); ++ isc_rwlock_destroy(&rbtdb->current_version->rwlock); + isc_mem_put(rbtdb->common.mctx, rbtdb->current_version, + sizeof(rbtdb_version_t)); + } +@@ -1383,6 +1392,7 @@ allocate_version(isc_mem_t *mctx, rbtdb_serial_t serial, + + static isc_result_t + newversion(dns_db_t *db, dns_dbversion_t **versionp) { ++ isc_result_t result; + dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db; + rbtdb_version_t *version; + +@@ -1415,13 +1425,28 @@ newversion(dns_db_t *db, dns_dbversion_t **versionp) { + version->salt_length = 0; + memset(version->salt, 0, sizeof(version->salt)); + } +- rbtdb->next_serial++; +- rbtdb->future_version = version; +- } ++ result = isc_rwlock_init(&version->rwlock, 0, 0); ++ if (result != ISC_R_SUCCESS) { ++ isc_refcount_destroy(&version->references); ++ isc_mem_put(rbtdb->common.mctx, version, ++ sizeof(*version)); ++ version = NULL; ++ } else { ++ RWLOCK(&rbtdb->current_version->rwlock, ++ isc_rwlocktype_read); ++ version->records = rbtdb->current_version->records; ++ version->bytes = rbtdb->current_version->bytes; ++ RWUNLOCK(&rbtdb->current_version->rwlock, ++ isc_rwlocktype_read); ++ rbtdb->next_serial++; ++ rbtdb->future_version = version; ++ } ++ } else ++ result = ISC_R_NOMEMORY; + RBTDB_UNLOCK(&rbtdb->lock, isc_rwlocktype_write); + + if (version == NULL) +- return (ISC_R_NOMEMORY); ++ return (result); + + *versionp = version; + +@@ -2681,6 +2706,7 @@ closeversion(dns_db_t *db, dns_dbversion_t **versionp, isc_boolean_t commit) { + + if (cleanup_version != NULL) { + INSIST(EMPTY(cleanup_version->changed_list)); ++ isc_rwlock_destroy(&cleanup_version->rwlock); + isc_mem_put(rbtdb->common.mctx, cleanup_version, + sizeof(*cleanup_version)); + } +@@ -6254,6 +6280,26 @@ add32(dns_rbtdb_t *rbtdb, dns_rbtnode_t *rbtnode, rbtdb_version_t *rbtversion, + else + rbtnode->data = newheader; + newheader->next = topheader->next; ++ if (rbtversion != NULL) ++ RWLOCK(&rbtversion->rwlock, isc_rwlocktype_write); ++ if (rbtversion != NULL && !header_nx) { ++ rbtversion->records -= ++ dns_rdataslab_count((unsigned char *)header, ++ sizeof(*header)); ++ rbtversion->bytes -= ++ dns_rdataslab_size((unsigned char *)header, ++ sizeof(*header)); ++ } ++ if (rbtversion != NULL && !newheader_nx) { ++ rbtversion->records += ++ dns_rdataslab_count((unsigned char *)newheader, ++ sizeof(*newheader)); ++ rbtversion->bytes += ++ dns_rdataslab_size((unsigned char *)newheader, ++ sizeof(*newheader)); ++ } ++ if (rbtversion != NULL) ++ RWUNLOCK(&rbtversion->rwlock, isc_rwlocktype_write); + if (loading) { + /* + * There are no other references to 'header' when +@@ -6355,6 +6401,16 @@ add32(dns_rbtdb_t *rbtdb, dns_rbtnode_t *rbtnode, rbtdb_version_t *rbtversion, + newheader->down = NULL; + rbtnode->data = newheader; + } ++ if (rbtversion != NULL && !newheader_nx) { ++ RWLOCK(&rbtversion->rwlock, isc_rwlocktype_write); ++ rbtversion->records += ++ dns_rdataslab_count((unsigned char *)newheader, ++ sizeof(*newheader)); ++ rbtversion->bytes += ++ dns_rdataslab_size((unsigned char *)newheader, ++ sizeof(*newheader)); ++ RWUNLOCK(&rbtversion->rwlock, isc_rwlocktype_write); ++ } + idx = newheader->node->locknum; + if (IS_CACHE(rbtdb)) { + ISC_LIST_PREPEND(rbtdb->rdatasets[idx], +@@ -6811,6 +6867,12 @@ subtractrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version, + */ + newheader->additional_auth = NULL; + newheader->additional_glue = NULL; ++ rbtversion->records += ++ dns_rdataslab_count((unsigned char *)newheader, ++ sizeof(*newheader)); ++ rbtversion->bytes += ++ dns_rdataslab_size((unsigned char *)newheader, ++ sizeof(*newheader)); + } else if (result == DNS_R_NXRRSET) { + /* + * This subtraction would remove all of the rdata; +@@ -6846,6 +6908,12 @@ subtractrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version, + * topheader. + */ + INSIST(rbtversion->serial >= topheader->serial); ++ rbtversion->records -= ++ dns_rdataslab_count((unsigned char *)header, ++ sizeof(*header)); ++ rbtversion->bytes -= ++ dns_rdataslab_size((unsigned char *)header, ++ sizeof(*header)); + if (topheader_prev != NULL) + topheader_prev->next = newheader; + else +@@ -7172,6 +7240,7 @@ rbt_datafixer(dns_rbtnode_t *rbtnode, void *base, size_t filesize, + unsigned char *limit = ((unsigned char *) base) + filesize; + unsigned char *p; + size_t size; ++ unsigned int count; + + REQUIRE(rbtnode != NULL); + +@@ -7179,6 +7248,9 @@ rbt_datafixer(dns_rbtnode_t *rbtnode, void *base, size_t filesize, + p = (unsigned char *) header; + + size = dns_rdataslab_size(p, sizeof(*header)); ++ count = dns_rdataslab_count(p, sizeof(*header));; ++ rbtdb->current_version->records += count; ++ rbtdb->current_version->bytes += size; + isc_crc64_update(crc, p, size); + #ifdef DEBUG + hexdump("hashing header", p, sizeof(rdatasetheader_t)); +@@ -7777,6 +7849,33 @@ getnsec3parameters(dns_db_t *db, dns_dbversion_t *version, dns_hash_t *hash, + } + + static isc_result_t ++getsize(dns_db_t *db, dns_dbversion_t *version, isc_uint64_t *records, ++ isc_uint64_t *bytes) ++{ ++ dns_rbtdb_t *rbtdb; ++ isc_result_t result = ISC_R_SUCCESS; ++ rbtdb_version_t *rbtversion = version; ++ ++ rbtdb = (dns_rbtdb_t *)db; ++ ++ REQUIRE(VALID_RBTDB(rbtdb)); ++ INSIST(rbtversion == NULL || rbtversion->rbtdb == rbtdb); ++ ++ if (rbtversion == NULL) ++ rbtversion = rbtdb->current_version; ++ ++ RWLOCK(&rbtversion->rwlock, isc_rwlocktype_read); ++ if (records != NULL) ++ *records = rbtversion->records; ++ ++ if (bytes != NULL) ++ *bytes = rbtversion->bytes; ++ RWUNLOCK(&rbtversion->rwlock, isc_rwlocktype_read); ++ ++ return (result); ++} ++ ++static isc_result_t + setsigningtime(dns_db_t *db, dns_rdataset_t *rdataset, isc_stdtime_t resign) { + dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db; + isc_stdtime_t oldresign; +@@ -7972,7 +8071,8 @@ static dns_dbmethods_t zone_methods = { + NULL, + NULL, + NULL, +- hashsize ++ hashsize, ++ getsize + }; + + static dns_dbmethods_t cache_methods = { +@@ -8018,7 +8118,8 @@ static dns_dbmethods_t cache_methods = { + NULL, + NULL, + setcachestats, +- hashsize ++ hashsize, ++ NULL + }; + + isc_result_t +@@ -8310,6 +8411,20 @@ dns_rbtdb_create + rbtdb->current_version->salt_length = 0; + memset(rbtdb->current_version->salt, 0, + sizeof(rbtdb->current_version->salt)); ++ result = isc_rwlock_init(&rbtdb->current_version->rwlock, 0, 0); ++ if (result != ISC_R_SUCCESS) { ++ isc_refcount_destroy(&rbtdb->current_version->references); ++ isc_mem_put(mctx, rbtdb->current_version, ++ sizeof(*rbtdb->current_version)); ++ rbtdb->current_version = NULL; ++ isc_refcount_decrement(&rbtdb->references, NULL); ++ isc_refcount_destroy(&rbtdb->references); ++ free_rbtdb(rbtdb, ISC_FALSE, NULL); ++ return (result); ++ } ++ ++ rbtdb->current_version->records = 0; ++ rbtdb->current_version->bytes = 0; + rbtdb->future_version = NULL; + ISC_LIST_INIT(rbtdb->open_versions); + /* +diff --git a/lib/dns/rdataslab.c b/lib/dns/rdataslab.c +index e29dc84..63e3728 100644 +--- a/lib/dns/rdataslab.c ++++ b/lib/dns/rdataslab.c +@@ -523,6 +523,19 @@ dns_rdataslab_size(unsigned char *slab, unsigned int reservelen) { + return ((unsigned int)(current - slab)); + } + ++unsigned int ++dns_rdataslab_count(unsigned char *slab, unsigned int reservelen) { ++ unsigned int count; ++ unsigned char *current; ++ ++ REQUIRE(slab != NULL); ++ ++ current = slab + reservelen; ++ count = *current++ * 256; ++ count += *current++; ++ return (count); ++} ++ + /* + * Make the dns_rdata_t 'rdata' refer to the slab item + * beginning at '*current', which is part of a slab of type +diff --git a/lib/dns/result.c b/lib/dns/result.c +index 7be4f57..a621909 100644 +--- a/lib/dns/result.c ++++ b/lib/dns/result.c +@@ -167,11 +167,16 @@ static const char *text[DNS_R_NRESULTS] = { + "covered by negative trust anchor", /*%< 110 DNS_R_NTACOVERED */ + "bad CDS", /*%< 111 DNS_R_BADCSD */ + "bad CDNSKEY", /*%< 112 DNS_R_BADCDNSKEY */ +- "malformed OPT option" /*%< 113 DNS_R_OPTERR */ ++ "malformed OPT option", /*%< 113 DNS_R_OPTERR */ ++ "malformed DNSTAP data", /*%< 114 DNS_R_BADDNSTAP */ ++ ++ "TSIG in wrong location", /*%< 115 DNS_R_BADTSIG */ ++ "SIG(0) in wrong location", /*%< 116 DNS_R_BADSIG0 */ ++ "too many records", /*%< 117 DNS_R_TOOMANYRECORDS */ + }; + + static const char *rcode_text[DNS_R_NRCODERESULTS] = { +- "NOERROR", /*%< 0 DNS_R_NOEROR */ ++ "NOERROR", /*%< 0 DNS_R_NOERROR */ + "FORMERR", /*%< 1 DNS_R_FORMERR */ + "SERVFAIL", /*%< 2 DNS_R_SERVFAIL */ + "NXDOMAIN", /*%< 3 DNS_R_NXDOMAIN */ +diff --git a/lib/dns/sdb.c b/lib/dns/sdb.c +index abfeeb0..19397e0 100644 +--- a/lib/dns/sdb.c ++++ b/lib/dns/sdb.c +@@ -1298,7 +1298,8 @@ static dns_dbmethods_t sdb_methods = { + findnodeext, + findext, + NULL, /* setcachestats */ +- NULL /* hashsize */ ++ NULL, /* hashsize */ ++ NULL /* getsize */ + }; + + static isc_result_t +diff --git a/lib/dns/sdlz.c b/lib/dns/sdlz.c +index b1198a4..0e3163d 100644 +--- a/lib/dns/sdlz.c ++++ b/lib/dns/sdlz.c +@@ -1269,7 +1269,8 @@ static dns_dbmethods_t sdlzdb_methods = { + findnodeext, + findext, + NULL, /* setcachestats */ +- NULL /* hashsize */ ++ NULL, /* hashsize */ ++ NULL /* getsize */ + }; + + /* +diff --git a/lib/dns/xfrin.c b/lib/dns/xfrin.c +index 2a6c1b4..ac566e1 100644 +--- a/lib/dns/xfrin.c ++++ b/lib/dns/xfrin.c +@@ -149,6 +149,9 @@ struct dns_xfrin_ctx { + unsigned int nrecs; /*%< Number of records recvd */ + isc_uint64_t nbytes; /*%< Number of bytes received */ + ++ unsigned int maxrecords; /*%< The maximum number of ++ records set for the zone */ ++ + isc_time_t start; /*%< Start time of the transfer */ + isc_time_t end; /*%< End time of the transfer */ + +@@ -309,10 +312,18 @@ axfr_putdata(dns_xfrin_ctx_t *xfr, dns_diffop_t op, + static isc_result_t + axfr_apply(dns_xfrin_ctx_t *xfr) { + isc_result_t result; ++ isc_uint64_t records; + + CHECK(dns_diff_load(&xfr->diff, xfr->axfr.add, xfr->axfr.add_private)); + xfr->difflen = 0; + dns_diff_clear(&xfr->diff); ++ if (xfr->maxrecords != 0U) { ++ result = dns_db_getsize(xfr->db, xfr->ver, &records, NULL); ++ if (result == ISC_R_SUCCESS && records > xfr->maxrecords) { ++ result = DNS_R_TOOMANYRECORDS; ++ goto failure; ++ } ++ } + result = ISC_R_SUCCESS; + failure: + return (result); +@@ -396,6 +407,7 @@ ixfr_putdata(dns_xfrin_ctx_t *xfr, dns_diffop_t op, + static isc_result_t + ixfr_apply(dns_xfrin_ctx_t *xfr) { + isc_result_t result; ++ isc_uint64_t records; + + if (xfr->ver == NULL) { + CHECK(dns_db_newversion(xfr->db, &xfr->ver)); +@@ -403,6 +415,13 @@ ixfr_apply(dns_xfrin_ctx_t *xfr) { + CHECK(dns_journal_begin_transaction(xfr->ixfr.journal)); + } + CHECK(dns_diff_apply(&xfr->diff, xfr->db, xfr->ver)); ++ if (xfr->maxrecords != 0U) { ++ result = dns_db_getsize(xfr->db, xfr->ver, &records, NULL); ++ if (result == ISC_R_SUCCESS && records > xfr->maxrecords) { ++ result = DNS_R_TOOMANYRECORDS; ++ goto failure; ++ } ++ } + if (xfr->ixfr.journal != NULL) { + result = dns_journal_writediff(xfr->ixfr.journal, &xfr->diff); + if (result != ISC_R_SUCCESS) +@@ -759,7 +778,7 @@ xfrin_reset(dns_xfrin_ctx_t *xfr) { + + static void + xfrin_fail(dns_xfrin_ctx_t *xfr, isc_result_t result, const char *msg) { +- if (result != DNS_R_UPTODATE) { ++ if (result != DNS_R_UPTODATE && result != DNS_R_TOOMANYRECORDS) { + xfrin_log(xfr, ISC_LOG_ERROR, "%s: %s", + msg, isc_result_totext(result)); + if (xfr->is_ixfr) +@@ -852,6 +871,7 @@ xfrin_create(isc_mem_t *mctx, + xfr->nmsg = 0; + xfr->nrecs = 0; + xfr->nbytes = 0; ++ xfr->maxrecords = dns_zone_getmaxrecords(zone); + isc_time_now(&xfr->start); + + xfr->tsigkey = NULL; +diff --git a/lib/dns/zone.c b/lib/dns/zone.c +index 90e558d..2b0d8e4 100644 +--- a/lib/dns/zone.c ++++ b/lib/dns/zone.c +@@ -253,6 +253,8 @@ struct dns_zone { + isc_uint32_t maxretry; + isc_uint32_t minretry; + ++ isc_uint32_t maxrecords; ++ + isc_sockaddr_t *masters; + isc_dscp_t *masterdscps; + dns_name_t **masterkeynames; +@@ -10088,6 +10090,20 @@ dns_zone_setmaxretrytime(dns_zone_t *zone, isc_uint32_t val) { + zone->maxretry = val; + } + ++isc_uint32_t ++dns_zone_getmaxrecords(dns_zone_t *zone) { ++ REQUIRE(DNS_ZONE_VALID(zone)); ++ ++ return (zone->maxrecords); ++} ++ ++void ++dns_zone_setmaxrecords(dns_zone_t *zone, isc_uint32_t val) { ++ REQUIRE(DNS_ZONE_VALID(zone)); ++ ++ zone->maxrecords = val; ++} ++ + static isc_boolean_t + notify_isqueued(dns_zone_t *zone, unsigned int flags, dns_name_t *name, + isc_sockaddr_t *addr, dns_tsigkey_t *key) +@@ -14431,7 +14447,7 @@ zone_xfrdone(dns_zone_t *zone, isc_result_t result) { + DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_SOABEFOREAXFR); + + TIME_NOW(&now); +- switch (result) { ++ switch (xfrresult) { + case ISC_R_SUCCESS: + DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NEEDNOTIFY); + /*FALLTHROUGH*/ +@@ -14558,6 +14574,11 @@ zone_xfrdone(dns_zone_t *zone, isc_result_t result) { + DNS_ZONE_SETFLAG(zone, DNS_ZONEFLAG_NOIXFR); + goto same_master; + ++ case DNS_R_TOOMANYRECORDS: ++ DNS_ZONE_JITTER_ADD(&now, zone->refresh, &zone->refreshtime); ++ inc_stats(zone, dns_zonestatscounter_xfrfail); ++ break; ++ + default: + next_master: + /* +diff --git a/lib/isccfg/namedconf.c b/lib/isccfg/namedconf.c +index 780ab46..e7ff1cc 100644 +--- a/lib/isccfg/namedconf.c ++++ b/lib/isccfg/namedconf.c +@@ -1679,6 +1679,7 @@ zone_clauses[] = { + { "masterfile-format", &cfg_type_masterformat, 0 }, + { "max-ixfr-log-size", &cfg_type_size, CFG_CLAUSEFLAG_OBSOLETE }, + { "max-journal-size", &cfg_type_sizenodefault, 0 }, ++ { "max-records", &cfg_type_uint32, 0 }, + { "max-refresh-time", &cfg_type_uint32, 0 }, + { "max-retry-time", &cfg_type_uint32, 0 }, + { "max-transfer-idle-in", &cfg_type_uint32, 0 }, +-- +2.7.4 + -- cgit 1.2.3-korg