diff --git a/CHANGES b/CHANGES index f4c2810650..6d2a8b87c8 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] + 4503. [cleanup] "make uninstall" now removes files installed by BIND. (This currently excludes Python files due to lack of support in setup.py.) [RT #42912] diff --git a/bin/named/config.c b/bin/named/config.c index d8995c6761..7eacf7ecd7 100644 --- a/bin/named/config.c +++ b/bin/named/config.c @@ -221,6 +221,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 5b489e7ebb..fa4bbc63c7 100644 --- a/bin/named/named.conf.docbook +++ b/bin/named/named.conf.docbook @@ -360,6 +360,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; @@ -564,6 +565,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; @@ -665,6 +667,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 c5fbf5a34d..0f46d84328 100644 --- a/bin/named/update.c +++ b/bin/named/update.c @@ -2496,6 +2496,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); @@ -3180,6 +3182,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 508c67f28b..17e24149db 100644 --- a/bin/named/zoneconf.c +++ b/bin/named/zoneconf.c @@ -991,6 +991,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/dyndb/driver/db.c b/bin/tests/system/dyndb/driver/db.c index ef74e66ac9..5c1cfd5f4f 100644 --- a/bin/tests/system/dyndb/driver/db.c +++ b/bin/tests/system/dyndb/driver/db.c @@ -623,7 +623,8 @@ static dns_dbmethods_t sampledb_methods = { findext, setcachestats, hashsize, - NULL + NULL, + NULL, }; /* Auxiliary driver functions. */ diff --git a/bin/tests/system/nsupdate/clean.sh b/bin/tests/system/nsupdate/clean.sh index 73dca2d0bd..5c9d0405b7 100644 --- a/bin/tests/system/nsupdate/clean.sh +++ b/bin/tests/system/nsupdate/clean.sh @@ -31,5 +31,6 @@ rm -f ns3/dsset-* rm -f ns3/example.db rm -f ns3/many.test.bk rm -f ns3/nsec3param.test.db +rm -f ns3/too-big.test.db rm -f nsupdate.out* rm -f typelist.out.* diff --git a/bin/tests/system/nsupdate/ns3/named.conf b/bin/tests/system/nsupdate/ns3/named.conf index 679735401c..2db43de8ed 100644 --- a/bin/tests/system/nsupdate/ns3/named.conf +++ b/bin/tests/system/nsupdate/ns3/named.conf @@ -65,3 +65,10 @@ zone "delegation.test" { allow-update { any; }; file "delegation.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 0000000000..7ff1e4a514 --- /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 09ca4abbb5..c5f0e26fce 100644 --- a/bin/tests/system/nsupdate/setup.sh +++ b/bin/tests/system/nsupdate/setup.sh @@ -18,6 +18,7 @@ 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 @@ -25,6 +26,7 @@ sed 's/example.nil/unixtime.nil/g' ns1/example1.db > ns1/unixtime.db sed 's/example.nil/yyyymmddvv.nil/g' ns1/example1.db > ns1/yyyymmddvv.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 f8d4327d2d..ded835439e 100755 --- a/bin/tests/system/nsupdate/tests.sh +++ b/bin/tests/system/nsupdate/tests.sh @@ -686,5 +686,20 @@ $DIG +tcp @10.53.0.3 -p 5300 ns child.delegation.test > dig.out.ns1.test$n grep "status: NXDOMAIN" dig.out.ns1.test$n > /dev/null 2>&1 || ret=1 [ $ret = 0 ] || { echo I:failed; status=1; } +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" [ $status -eq 0 ] || exit 1 diff --git a/bin/tests/system/xfer/clean.sh b/bin/tests/system/xfer/clean.sh index d043a2c38c..3a5f270072 100644 --- a/bin/tests/system/xfer/clean.sh +++ b/bin/tests/system/xfer/clean.sh @@ -34,3 +34,4 @@ rm -f ns*/named.lock rm -f ns2/mapped.db rm -f ns3/mapped.bk rm -f dig.out.?.* +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 0000000000..d43760d9a8 --- /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 0000000000..318bb772af --- /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 e9fd744c14..b0d46ce1e7 100644 --- a/bin/tests/system/xfer/ns1/named.conf +++ b/bin/tests/system/xfer/ns1/named.conf @@ -40,3 +40,14 @@ zone "edns-expire" { type master; file "edns-expire.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 fccd00d0cc..f9909270cb 100644 --- a/bin/tests/system/xfer/ns6/named.conf +++ b/bin/tests/system/xfer/ns6/named.conf @@ -50,3 +50,17 @@ zone "edns-expire" { masters { 10.53.0.1; }; file "edns-expire.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 04d5aa79c3..6edd02b68c 100644 --- a/bin/tests/system/xfer/setup.sh +++ b/bin/tests/system/xfer/setup.sh @@ -30,3 +30,5 @@ cp ns2/mapped.db.in ns2/mapped.db $PERL -e 'for ($i=0;$i<4096;$i++){ printf("name%u 259200 A 1.2.3.4\nname%u 259200 TXT \"Hello World %u\"\n", $i, $i, $i);}' > ns8/small.db $PERL -e 'printf("large IN TYPE45234 \\# 48000 "); for ($i=0;$i<16*3000;$i++) { printf("%02x", $i % 256); } printf("\n");' > ns8/large.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 bb3ae83ac4..ffd4780bf0 100644 --- a/bin/tests/system/xfer/tests.sh +++ b/bin/tests/system/xfer/tests.sh @@ -414,5 +414,31 @@ $PERL ../digcomp.pl knowngood.mapped dig.out.3.$n || 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 (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" [ $status -eq 0 ] || exit 1 diff --git a/doc/arm/Bv9ARM-book.xml b/doc/arm/Bv9ARM-book.xml index 94c7af65ad..17d8272a47 100644 --- a/doc/arm/Bv9ARM-book.xml +++ b/doc/arm/Bv9ARM-book.xml @@ -4521,6 +4521,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; @@ -8453,6 +8454,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 @@ -12503,6 +12514,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 e02114b93d..a8181b03a2 100644 --- a/doc/arm/notes.xml +++ b/doc/arm/notes.xml @@ -66,6 +66,15 @@
Security Fixes + + + 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] + + It was possible to trigger a assertion when rendering a diff --git a/lib/bind9/check.c b/lib/bind9/check.c index afc38364c6..10c598a320 100644 --- a/lib/bind9/check.c +++ b/lib/bind9/check.c @@ -1756,6 +1756,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 49be6e22af..0be1e22e17 100644 --- a/lib/dns/db.c +++ b/lib/dns/db.c @@ -1003,6 +1003,19 @@ dns_db_getnsec3parameters(dns_db_t *db, dns_dbversion_t *version, return (ISC_R_NOTFOUND); } +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 27135bfedb..23aca666f4 100644 --- a/lib/dns/ecdb.c +++ b/lib/dns/ecdb.c @@ -582,7 +582,8 @@ static dns_dbmethods_t ecdb_methods = { NULL, /* findext */ NULL, /* setcachestats */ NULL, /* hashsize */ - NULL /* nodefullname */ + NULL, /* nodefullname */ + NULL /* getsize */ }; static isc_result_t diff --git a/lib/dns/include/dns/db.h b/lib/dns/include/dns/db.h index c2e1228e6d..59b2dc4c9a 100644 --- a/lib/dns/include/dns/db.h +++ b/lib/dns/include/dns/db.h @@ -188,6 +188,8 @@ typedef struct dns_dbmethods { size_t (*hashsize)(dns_db_t *db); isc_result_t (*nodefullname)(dns_db_t *db, dns_dbnode_t *node, dns_name_t *name); + 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 @@ -1501,6 +1503,24 @@ dns_db_getnsec3parameters(dns_db_t *db, dns_dbversion_t *version, * or this zone does not have NSEC3 records. */ +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 260704ae2a..50e16e82dd 100644 --- a/lib/dns/include/dns/rdataslab.h +++ b/lib/dns/include/dns/rdataslab.h @@ -95,6 +95,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); /*%< @@ -107,6 +108,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 cbba0cd6f3..45b1613c71 100644 --- a/lib/dns/include/dns/result.h +++ b/lib/dns/include/dns/result.h @@ -149,8 +149,9 @@ #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 117 /*%< 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 5043cc59b8..5b58b7b916 100644 --- a/lib/dns/include/dns/zone.h +++ b/lib/dns/include/dns/zone.h @@ -295,6 +295,32 @@ dns_zone_getfile(dns_zone_t *zone); *\li Pointer to null-terminated file name, or NULL. */ +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); /*%< @@ -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 40bed4327c..4f78bc4911 100644 --- a/lib/dns/rbtdb.c +++ b/lib/dns/rbtdb.c @@ -202,6 +202,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 @@ -603,6 +604,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; @@ -1174,6 +1182,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)); } @@ -1437,6 +1446,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; @@ -1469,13 +1479,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; @@ -2760,6 +2785,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)); } @@ -6341,6 +6367,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 @@ -6442,6 +6488,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], @@ -6914,6 +6970,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; @@ -6950,6 +7012,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 @@ -7288,6 +7356,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); @@ -7295,6 +7364,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)); @@ -7910,6 +7982,33 @@ getnsec3parameters(dns_db_t *db, dns_dbversion_t *version, dns_hash_t *hash, return (result); } +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; @@ -8125,7 +8224,8 @@ static dns_dbmethods_t zone_methods = { NULL, NULL, hashsize, - nodefullname + nodefullname, + getsize }; static dns_dbmethods_t cache_methods = { @@ -8172,7 +8272,8 @@ static dns_dbmethods_t cache_methods = { NULL, setcachestats, hashsize, - nodefullname + nodefullname, + NULL }; isc_result_t @@ -8466,6 +8567,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 2642edf52e..cdff86c0dc 100644 --- a/lib/dns/rdataslab.c +++ b/lib/dns/rdataslab.c @@ -516,6 +516,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 5877adcc20..615f6010a4 100644 --- a/lib/dns/result.c +++ b/lib/dns/result.c @@ -161,6 +161,7 @@ static const char *text[DNS_R_NRESULTS] = { "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 *ids[DNS_R_NRESULTS] = { diff --git a/lib/dns/sdb.c b/lib/dns/sdb.c index c86ef96094..651c7dcfd9 100644 --- a/lib/dns/sdb.c +++ b/lib/dns/sdb.c @@ -1290,7 +1290,8 @@ static dns_dbmethods_t sdb_methods = { findext, NULL, /* setcachestats */ NULL, /* hashsize */ - NULL /* nodefullname */ + NULL, /* nodefullname */ + NULL /* getsize */ }; static isc_result_t diff --git a/lib/dns/sdlz.c b/lib/dns/sdlz.c index fb77f9b9d1..60ccc344e4 100644 --- a/lib/dns/sdlz.c +++ b/lib/dns/sdlz.c @@ -1324,7 +1324,8 @@ static dns_dbmethods_t sdlzdb_methods = { findext, NULL, /* setcachestats */ NULL, /* hashsize */ - NULL /* nodefullname */ + NULL, /* nodefullname */ + NULL /* getsize */ }; /* diff --git a/lib/dns/xfrin.c b/lib/dns/xfrin.c index 954fac55f8..2078298aa3 100644 --- a/lib/dns/xfrin.c +++ b/lib/dns/xfrin.c @@ -141,6 +141,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 */ @@ -306,10 +309,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 e48da3e316..2b4e76ddba 100644 --- a/lib/dns/zone.c +++ b/lib/dns/zone.c @@ -245,6 +245,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; @@ -10214,6 +10216,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) @@ -14674,7 +14690,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*/ @@ -14801,6 +14817,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 c79f08ed62..66c4ae2c7c 100644 --- a/lib/isccfg/namedconf.c +++ b/lib/isccfg/namedconf.c @@ -1847,6 +1847,7 @@ zone_clauses[] = { { "masterfile-style", &cfg_type_masterstyle, 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 },