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