diff --git a/bin/named/config.c b/bin/named/config.c index 38ddc7ca23..1943eb1879 100644 --- a/bin/named/config.c +++ b/bin/named/config.c @@ -222,6 +222,7 @@ options {\n\ ixfr-from-differences false;\n\ max-journal-size default;\n\ max-records 0;\n\ + max-records-per-type 100;\n\ max-refresh-time 2419200; /* 4 weeks */\n\ max-retry-time 1209600; /* 2 weeks */\n\ max-transfer-idle-in 60;\n\ diff --git a/bin/named/server.c b/bin/named/server.c index a17375dbaa..6bcd0b5d56 100644 --- a/bin/named/server.c +++ b/bin/named/server.c @@ -5454,6 +5454,15 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, cfg_obj_t *config, dns_resolver_setclientsperquery(view->resolver, cfg_obj_asuint32(obj), max_clients_per_query); + /* + * This is used for the cache and also as a default value + * for zone databases. + */ + obj = NULL; + result = named_config_get(maps, "max-records-per-type", &obj); + INSIST(result == ISC_R_SUCCESS); + dns_view_setmaxrrperset(view, cfg_obj_asuint32(obj)); + obj = NULL; result = named_config_get(maps, "max-recursion-depth", &obj); INSIST(result == ISC_R_SUCCESS); diff --git a/bin/named/zoneconf.c b/bin/named/zoneconf.c index c45051b42a..f6646e3819 100644 --- a/bin/named/zoneconf.c +++ b/bin/named/zoneconf.c @@ -1074,6 +1074,14 @@ named_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig, dns_zone_setmaxrecords(zone, 0); } + obj = NULL; + result = named_config_get(maps, "max-records-per-type", &obj); + INSIST(result == ISC_R_SUCCESS && obj != NULL); + dns_zone_setmaxrrperset(mayberaw, cfg_obj_asuint32(obj)); + if (zone != mayberaw) { + dns_zone_setmaxrrperset(zone, 0); + } + if (raw != NULL && filename != NULL) { #define SIGNED ".signed" size_t signedlen = strlen(filename) + sizeof(SIGNED); diff --git a/bin/tests/system/doth/ns2/named.conf.in b/bin/tests/system/doth/ns2/named.conf.in index 96200d0fd3..a7b09611d1 100644 --- a/bin/tests/system/doth/ns2/named.conf.in +++ b/bin/tests/system/doth/ns2/named.conf.in @@ -52,6 +52,7 @@ options { ixfr-from-differences yes; check-integrity no; dnssec-validation yes; + max-records-per-type 0; transfers-in 100; transfers-out 100; }; diff --git a/bin/tests/system/doth/ns3/named.conf.in b/bin/tests/system/doth/ns3/named.conf.in index 69de2ca146..daf3164643 100644 --- a/bin/tests/system/doth/ns3/named.conf.in +++ b/bin/tests/system/doth/ns3/named.conf.in @@ -44,6 +44,7 @@ options { ixfr-from-differences yes; check-integrity no; dnssec-validation yes; + max-records-per-type 0; }; trust-anchors { }; diff --git a/bin/tests/system/doth/ns4/named.conf.in b/bin/tests/system/doth/ns4/named.conf.in index 60072ce9c2..d637a9c9ed 100644 --- a/bin/tests/system/doth/ns4/named.conf.in +++ b/bin/tests/system/doth/ns4/named.conf.in @@ -52,6 +52,7 @@ options { ixfr-from-differences yes; check-integrity no; dnssec-validation yes; + max-records-per-type 0; }; trust-anchors { }; diff --git a/bin/tests/system/doth/ns5/named.conf.in b/bin/tests/system/doth/ns5/named.conf.in index e161a3e4cf..7aa3757cdb 100644 --- a/bin/tests/system/doth/ns5/named.conf.in +++ b/bin/tests/system/doth/ns5/named.conf.in @@ -40,6 +40,7 @@ options { ixfr-from-differences yes; check-integrity no; dnssec-validation yes; + max-records-per-type 0; }; trust-anchors { }; diff --git a/doc/arm/reference.rst b/doc/arm/reference.rst index 4c0cc55e58..0decf3a6e0 100644 --- a/doc/arm/reference.rst +++ b/doc/arm/reference.rst @@ -3681,6 +3681,21 @@ system. This sets the maximum number of records permitted in a zone. The default is zero, which means the maximum is unlimited. +.. namedconf:statement:: max-records-per-type + :tags: server + :short: Sets the maximum number of records that can be stored in an RRset + + This sets the maximum number of resource records that can be stored + in an RRset in a database. When configured in :namedconf:ref:`options` + or :namedconf:ref:`view`, it controls the cache database; it also sets + the default value for zone databases, which can be overridden by setting + it at the :namedconf:ref:`zone` level. + + If set to a positive value, any attempt to cache or to add to a zone + an RRset with more than the specified number of records will result in + a failure. If set to 0, there is no cap on RRset size. The default is + 100. + .. namedconf:statement:: recursive-clients :tags: query :short: Specifies the maximum number of concurrent recursive queries the server can perform. diff --git a/doc/misc/mirror.zoneopt b/doc/misc/mirror.zoneopt index cc9dbaa446..4238e689f5 100644 --- a/doc/misc/mirror.zoneopt +++ b/doc/misc/mirror.zoneopt @@ -16,6 +16,7 @@ zone [ ] { max-ixfr-ratio ( unlimited | ); max-journal-size ( default | unlimited | ); max-records ; + max-records-per-type ; max-refresh-time ; max-retry-time ; max-transfer-idle-in ; diff --git a/doc/misc/options b/doc/misc/options index 7c94dcd180..261d46d093 100644 --- a/doc/misc/options +++ b/doc/misc/options @@ -183,6 +183,7 @@ options { max-journal-size ( default | unlimited | ); max-ncache-ttl ; max-records ; + max-records-per-type ; max-recursion-depth ; max-recursion-queries ; max-refresh-time ; @@ -468,6 +469,7 @@ view [ ] { max-journal-size ( default | unlimited | ); max-ncache-ttl ; max-records ; + max-records-per-type ; max-recursion-depth ; max-recursion-queries ; max-refresh-time ; diff --git a/doc/misc/primary.zoneopt b/doc/misc/primary.zoneopt index e3c6ef69d5..6586686300 100644 --- a/doc/misc/primary.zoneopt +++ b/doc/misc/primary.zoneopt @@ -37,6 +37,7 @@ zone [ ] { max-ixfr-ratio ( unlimited | ); max-journal-size ( default | unlimited | ); max-records ; + max-records-per-type ; max-transfer-idle-out ; max-transfer-time-out ; max-zone-ttl ( unlimited | ); // deprecated diff --git a/doc/misc/redirect.zoneopt b/doc/misc/redirect.zoneopt index c0bee863fb..b389f6eede 100644 --- a/doc/misc/redirect.zoneopt +++ b/doc/misc/redirect.zoneopt @@ -7,6 +7,7 @@ zone [ ] { masterfile-format ( raw | text ); masterfile-style ( full | relative ); max-records ; + max-records-per-type ; max-zone-ttl ( unlimited | ); // deprecated primaries [ port ] [ source ( | * ) ] [ source-v6 ( | * ) ] { ( | [ port ] | [ port ] ) [ key ] [ tls ]; ... }; zone-statistics ( full | terse | none | ); diff --git a/doc/misc/secondary.zoneopt b/doc/misc/secondary.zoneopt index 26eca8e20a..4ded7c8e19 100644 --- a/doc/misc/secondary.zoneopt +++ b/doc/misc/secondary.zoneopt @@ -28,6 +28,7 @@ zone [ ] { max-ixfr-ratio ( unlimited | ); max-journal-size ( default | unlimited | ); max-records ; + max-records-per-type ; max-refresh-time ; max-retry-time ; max-transfer-idle-in ; diff --git a/doc/misc/static-stub.zoneopt b/doc/misc/static-stub.zoneopt index 85c158fbcb..5f68d83c52 100644 --- a/doc/misc/static-stub.zoneopt +++ b/doc/misc/static-stub.zoneopt @@ -5,6 +5,7 @@ zone [ ] { forward ( first | only ); forwarders [ port ] [ tls ] { ( | ) [ port ] [ tls ]; ... }; max-records ; + max-records-per-type ; server-addresses { ( | ); ... }; server-names { ; ... }; zone-statistics ( full | terse | none | ); diff --git a/doc/misc/stub.zoneopt b/doc/misc/stub.zoneopt index 6d7c98cb45..8d0537b136 100644 --- a/doc/misc/stub.zoneopt +++ b/doc/misc/stub.zoneopt @@ -11,6 +11,7 @@ zone [ ] { masterfile-format ( raw | text ); masterfile-style ( full | relative ); max-records ; + max-records-per-type ; max-refresh-time ; max-retry-time ; max-transfer-idle-in ; diff --git a/lib/dns/cache.c b/lib/dns/cache.c index 43821dca17..52d92037d3 100644 --- a/lib/dns/cache.c +++ b/lib/dns/cache.c @@ -80,6 +80,7 @@ struct dns_cache { dns_ttl_t serve_stale_ttl; dns_ttl_t serve_stale_refresh; isc_stats_t *stats; + uint32_t maxrrperset; }; /*** @@ -128,6 +129,7 @@ cache_create_db(dns_cache_t *cache, dns_db_t **dbp, isc_mem_t **tmctxp, dns_db_setservestalettl(db, cache->serve_stale_ttl); dns_db_setservestalerefresh(db, cache->serve_stale_refresh); + dns_db_setmaxrrperset(db, cache->maxrrperset); /* * XXX this is only used by the RBT cache, and can @@ -546,6 +548,16 @@ dns_cache_updatestats(dns_cache_t *cache, isc_result_t result) { } } +void +dns_cache_setmaxrrperset(dns_cache_t *cache, uint32_t value) { + REQUIRE(VALID_CACHE(cache)); + + cache->maxrrperset = value; + if (cache->db != NULL) { + dns_db_setmaxrrperset(cache->db, value); + } +} + /* * XXX: Much of the following code has been copied in from statschannel.c. * We should refactor this into a generic function in stats.c that can be diff --git a/lib/dns/db.c b/lib/dns/db.c index ad082bbe2b..3f3ca0ede1 100644 --- a/lib/dns/db.c +++ b/lib/dns/db.c @@ -1170,3 +1170,12 @@ dns_db_nodefullname(dns_db_t *db, dns_dbnode_t *node, dns_name_t *name) { } return (ISC_R_NOTIMPLEMENTED); } + +void +dns_db_setmaxrrperset(dns_db_t *db, uint32_t value) { + REQUIRE(DNS_DB_VALID(db)); + + if (db->methods->setmaxrrperset != NULL) { + (db->methods->setmaxrrperset)(db, value); + } +} diff --git a/lib/dns/include/dns/cache.h b/lib/dns/include/dns/cache.h index 72cf80c3f4..738ab4cfe0 100644 --- a/lib/dns/include/dns/cache.h +++ b/lib/dns/include/dns/cache.h @@ -246,6 +246,12 @@ dns_cache_updatestats(dns_cache_t *cache, isc_result_t result); * Update cache statistics based on result code in 'result' */ +void +dns_cache_setmaxrrperset(dns_cache_t *cache, uint32_t value); +/*%< + * Set the maximum resource records per RRSet that can be cached. + */ + #ifdef HAVE_LIBXML2 int dns_cache_renderxml(dns_cache_t *cache, void *writer0); diff --git a/lib/dns/include/dns/db.h b/lib/dns/include/dns/db.h index fe968f3abe..96f9d58a12 100644 --- a/lib/dns/include/dns/db.h +++ b/lib/dns/include/dns/db.h @@ -183,6 +183,7 @@ typedef struct dns_dbmethods { void (*deletedata)(dns_db_t *db, dns_dbnode_t *node, void *data); isc_result_t (*nodefullname)(dns_db_t *db, dns_dbnode_t *node, dns_name_t *name); + void (*setmaxrrperset)(dns_db_t *db, uint32_t value); } dns_dbmethods_t; typedef isc_result_t (*dns_dbcreatefunc_t)(isc_mem_t *mctx, @@ -1800,4 +1801,12 @@ dns_db_nodefullname(dns_db_t *db, dns_dbnode_t *node, dns_name_t *name); * \li 'db' is a valid database * \li 'node' and 'name' are not NULL */ + +void +dns_db_setmaxrrperset(dns_db_t *db, uint32_t value); +/*%< + * Set the maximum permissible number of RRs per RRset. If 'value' + * is nonzero, then any subsequent attempt to add an rdataset with + * more than 'value' RRs will return ISC_R_TOOMANYRECORDS. + */ ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/rdataslab.h b/lib/dns/include/dns/rdataslab.h index ab57716ef5..4227854669 100644 --- a/lib/dns/include/dns/rdataslab.h +++ b/lib/dns/include/dns/rdataslab.h @@ -169,7 +169,8 @@ extern dns_rdatasetmethods_t dns_rdataslab_rdatasetmethods; isc_result_t dns_rdataslab_fromrdataset(dns_rdataset_t *rdataset, isc_mem_t *mctx, - isc_region_t *region, unsigned int reservelen); + isc_region_t *region, unsigned int reservelen, + uint32_t limit); /*%< * Slabify a rdataset. The slab area will be allocated and returned * in 'region'. @@ -225,7 +226,8 @@ isc_result_t dns_rdataslab_merge(unsigned char *oslab, unsigned char *nslab, unsigned int reservelen, isc_mem_t *mctx, dns_rdataclass_t rdclass, dns_rdatatype_t type, - unsigned int flags, unsigned char **tslabp); + unsigned int flags, uint32_t maxrrperset, + unsigned char **tslabp); /*%< * Merge 'oslab' and 'nslab'. */ diff --git a/lib/dns/include/dns/view.h b/lib/dns/include/dns/view.h index b582828266..e97835f8c6 100644 --- a/lib/dns/include/dns/view.h +++ b/lib/dns/include/dns/view.h @@ -183,6 +183,7 @@ struct dns_view { uint32_t fail_ttl; dns_badcache_t *failcache; unsigned int udpsize; + uint32_t maxrrperset; /* * Configurable data for server use only, @@ -1242,6 +1243,12 @@ dns_view_getresolver(dns_view_t *view, dns_resolver_t **resolverp); * Return the resolver associated with the view. */ +void +dns_view_setmaxrrperset(dns_view_t *view, uint32_t value); +/*%< + * Set the maximum resource records per RRSet that can be cached. + */ + void dns_view_setudpsize(dns_view_t *view, uint16_t udpsize); /*%< diff --git a/lib/dns/include/dns/zone.h b/lib/dns/include/dns/zone.h index 2519f97911..bdcff3061c 100644 --- a/lib/dns/include/dns/zone.h +++ b/lib/dns/include/dns/zone.h @@ -366,6 +366,19 @@ dns_zone_getmaxrecords(dns_zone_t *zone); *\li uint32_t maxrecords. */ +void +dns_zone_setmaxrrperset(dns_zone_t *zone, uint32_t maxrrperset); +/*%< + * Sets the maximum number of records per rrset permitted in a zone. + * 0 implies unlimited. + * + * Requires: + *\li 'zone' to be valid initialised zone. + * + * Returns: + *\li void + */ + void dns_zone_setmaxttl(dns_zone_t *zone, uint32_t maxttl); /*%< diff --git a/lib/dns/qpcache.c b/lib/dns/qpcache.c index c4ef39f9d8..329decbb6f 100644 --- a/lib/dns/qpcache.c +++ b/lib/dns/qpcache.c @@ -217,6 +217,8 @@ struct qpcache { /* Locked by lock. */ unsigned int active; + uint32_t maxrrperset; /* Maximum RRs per RRset */ + /* * The time after a failed lookup, where stale answers from cache * may be used directly in a DNS response without attempting a @@ -3280,7 +3282,7 @@ find_header: } static isc_result_t -addnoqname(isc_mem_t *mctx, dns_slabheader_t *newheader, +addnoqname(isc_mem_t *mctx, dns_slabheader_t *newheader, uint32_t maxrrperset, dns_rdataset_t *rdataset) { isc_result_t result; dns_slabheader_proof_t *noqname = NULL; @@ -3291,12 +3293,12 @@ addnoqname(isc_mem_t *mctx, dns_slabheader_t *newheader, result = dns_rdataset_getnoqname(rdataset, &name, &neg, &negsig); RUNTIME_CHECK(result == ISC_R_SUCCESS); - result = dns_rdataslab_fromrdataset(&neg, mctx, &r1, 0); + result = dns_rdataslab_fromrdataset(&neg, mctx, &r1, 0, maxrrperset); if (result != ISC_R_SUCCESS) { goto cleanup; } - result = dns_rdataslab_fromrdataset(&negsig, mctx, &r2, 0); + result = dns_rdataslab_fromrdataset(&negsig, mctx, &r2, 0, maxrrperset); if (result != ISC_R_SUCCESS) { goto cleanup; } @@ -3319,7 +3321,7 @@ cleanup: } static isc_result_t -addclosest(isc_mem_t *mctx, dns_slabheader_t *newheader, +addclosest(isc_mem_t *mctx, dns_slabheader_t *newheader, uint32_t maxrrperset, dns_rdataset_t *rdataset) { isc_result_t result; dns_slabheader_proof_t *closest = NULL; @@ -3330,12 +3332,12 @@ addclosest(isc_mem_t *mctx, dns_slabheader_t *newheader, result = dns_rdataset_getclosest(rdataset, &name, &neg, &negsig); RUNTIME_CHECK(result == ISC_R_SUCCESS); - result = dns_rdataslab_fromrdataset(&neg, mctx, &r1, 0); + result = dns_rdataslab_fromrdataset(&neg, mctx, &r1, 0, maxrrperset); if (result != ISC_R_SUCCESS) { goto cleanup; } - result = dns_rdataslab_fromrdataset(&negsig, mctx, &r2, 0); + result = dns_rdataslab_fromrdataset(&negsig, mctx, &r2, 0, maxrrperset); if (result != ISC_R_SUCCESS) { goto cleanup; } @@ -3386,7 +3388,8 @@ addrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version, } result = dns_rdataslab_fromrdataset(rdataset, qpdb->common.mctx, - ®ion, sizeof(dns_slabheader_t)); + ®ion, sizeof(dns_slabheader_t), + qpdb->maxrrperset); if (result != ISC_R_SUCCESS) { return (result); } @@ -3423,14 +3426,16 @@ addrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version, DNS_SLABHEADER_SETATTR(newheader, DNS_SLABHEADERATTR_OPTOUT); } if ((rdataset->attributes & DNS_RDATASETATTR_NOQNAME) != 0) { - result = addnoqname(qpdb->common.mctx, newheader, rdataset); + result = addnoqname(qpdb->common.mctx, newheader, + qpdb->maxrrperset, rdataset); if (result != ISC_R_SUCCESS) { dns_slabheader_destroy(&newheader); return (result); } } if ((rdataset->attributes & DNS_RDATASETATTR_CLOSEST) != 0) { - result = addclosest(qpdb->common.mctx, newheader, rdataset); + result = addclosest(qpdb->common.mctx, newheader, + qpdb->maxrrperset, rdataset); if (result != ISC_R_SUCCESS) { dns_slabheader_destroy(&newheader); return (result); @@ -4330,6 +4335,15 @@ expire_ttl_headers(qpcache_t *qpdb, unsigned int locknum, } } +static void +setmaxrrperset(dns_db_t *db, uint32_t value) { + qpcache_t *qpdb = (qpcache_t *)db; + + REQUIRE(VALID_QPDB(qpdb)); + + qpdb->maxrrperset = value; +} + static dns_dbmethods_t qpdb_cachemethods = { .destroy = qpdb_destroy, .findnode = findnode, @@ -4354,6 +4368,7 @@ static dns_dbmethods_t qpdb_cachemethods = { .unlocknode = unlocknode, .expiredata = expiredata, .deletedata = deletedata, + .setmaxrrperset = setmaxrrperset, }; static void diff --git a/lib/dns/qpzone.c b/lib/dns/qpzone.c index b2b5674949..da692d2538 100644 --- a/lib/dns/qpzone.c +++ b/lib/dns/qpzone.c @@ -178,6 +178,7 @@ struct qpzonedb { uint32_t current_serial; uint32_t least_serial; uint32_t next_serial; + uint32_t maxrrperset; qpz_version_t *current_version; qpz_version_t *future_version; qpz_versionlist_t open_versions; @@ -1898,7 +1899,7 @@ add(qpzonedb_t *qpdb, qpznode_t *node, const dns_name_t *nodename, (unsigned int)(sizeof(*newheader)), qpdb->common.mctx, qpdb->common.rdclass, (dns_rdatatype_t)header->type, flags, - &merged); + qpdb->maxrrperset, &merged); } if (result == ISC_R_SUCCESS) { /* @@ -2147,7 +2148,8 @@ loading_addrdataset(void *arg, const dns_name_t *name, loading_addnode(loadctx, name, rdataset->type, rdataset->covers, &node); result = dns_rdataslab_fromrdataset(rdataset, qpdb->common.mctx, - ®ion, sizeof(dns_slabheader_t)); + ®ion, sizeof(dns_slabheader_t), + qpdb->maxrrperset); if (result != ISC_R_SUCCESS) { return (result); } @@ -4648,7 +4650,8 @@ addrdataset(dns_db_t *db, dns_dbnode_t *dbnode, dns_dbversion_t *dbversion, rdataset->covers != dns_rdatatype_nsec3))); result = dns_rdataslab_fromrdataset(rdataset, qpdb->common.mctx, - ®ion, sizeof(dns_slabheader_t)); + ®ion, sizeof(dns_slabheader_t), + qpdb->maxrrperset); if (result != ISC_R_SUCCESS) { return (result); } @@ -4767,7 +4770,8 @@ subtractrdataset(dns_db_t *db, dns_dbnode_t *dbnode, dns_dbversion_t *dbversion, dns_name_copy(&node->name, nodename); result = dns_rdataslab_fromrdataset(rdataset, qpdb->common.mctx, - ®ion, sizeof(dns_slabheader_t)); + ®ion, sizeof(dns_slabheader_t), + 0); if (result != ISC_R_SUCCESS) { return (result); } @@ -5277,6 +5281,15 @@ addglue(dns_db_t *db, dns_dbversion_t *dbversion, dns_rdataset_t *rdataset, return (ISC_R_SUCCESS); } +static void +setmaxrrperset(dns_db_t *db, uint32_t value) { + qpzonedb_t *qpdb = (qpzonedb_t *)db; + + REQUIRE(VALID_QPZONE(qpdb)); + + qpdb->maxrrperset = value; +} + static dns_dbmethods_t qpdb_zonemethods = { .destroy = qpdb_destroy, .beginload = beginload, @@ -5310,6 +5323,7 @@ static dns_dbmethods_t qpdb_zonemethods = { .addglue = addglue, .deletedata = deletedata, .nodefullname = nodefullname, + .setmaxrrperset = setmaxrrperset, }; static void diff --git a/lib/dns/rbt-cachedb.c b/lib/dns/rbt-cachedb.c index f884174673..779eb143d6 100644 --- a/lib/dns/rbt-cachedb.c +++ b/lib/dns/rbt-cachedb.c @@ -1582,6 +1582,7 @@ dns_dbmethods_t dns__rbtdb_cachemethods = { .unlocknode = dns__rbtdb_unlocknode, .expiredata = expiredata, .deletedata = dns__rbtdb_deletedata, + .setmaxrrperset = dns__rbtdb_setmaxrrperset, }; /* diff --git a/lib/dns/rbt-zonedb.c b/lib/dns/rbt-zonedb.c index 43599fa381..93b71b9a98 100644 --- a/lib/dns/rbt-zonedb.c +++ b/lib/dns/rbt-zonedb.c @@ -1749,7 +1749,8 @@ loading_addrdataset(void *arg, const dns_name_t *name, } result = dns_rdataslab_fromrdataset(rdataset, rbtdb->common.mctx, - ®ion, sizeof(dns_slabheader_t)); + ®ion, sizeof(dns_slabheader_t), + rbtdb->maxrrperset); if (result != ISC_R_SUCCESS) { return (result); } @@ -2418,6 +2419,7 @@ dns_dbmethods_t dns__rbtdb_zonemethods = { .addglue = addglue, .deletedata = dns__rbtdb_deletedata, .nodefullname = dns__rbtdb_nodefullname, + .setmaxrrperset = dns__rbtdb_setmaxrrperset, }; void diff --git a/lib/dns/rbtdb.c b/lib/dns/rbtdb.c index 0dee744b7a..71ac5c1951 100644 --- a/lib/dns/rbtdb.c +++ b/lib/dns/rbtdb.c @@ -2780,7 +2780,7 @@ find_header: rbtdb->common.mctx, rbtdb->common.rdclass, (dns_rdatatype_t)header->type, flags, - &merged); + rbtdb->maxrrperset, &merged); } if (result == ISC_R_SUCCESS) { /* @@ -3141,7 +3141,7 @@ delegating_type(dns_rbtdb_t *rbtdb, dns_rbtnode_t *node, dns_typepair_t type) { } static isc_result_t -addnoqname(isc_mem_t *mctx, dns_slabheader_t *newheader, +addnoqname(isc_mem_t *mctx, dns_slabheader_t *newheader, uint32_t maxrrperset, dns_rdataset_t *rdataset) { isc_result_t result; dns_slabheader_proof_t *noqname = NULL; @@ -3152,12 +3152,12 @@ addnoqname(isc_mem_t *mctx, dns_slabheader_t *newheader, result = dns_rdataset_getnoqname(rdataset, &name, &neg, &negsig); RUNTIME_CHECK(result == ISC_R_SUCCESS); - result = dns_rdataslab_fromrdataset(&neg, mctx, &r1, 0); + result = dns_rdataslab_fromrdataset(&neg, mctx, &r1, 0, maxrrperset); if (result != ISC_R_SUCCESS) { goto cleanup; } - result = dns_rdataslab_fromrdataset(&negsig, mctx, &r2, 0); + result = dns_rdataslab_fromrdataset(&negsig, mctx, &r2, 0, maxrrperset); if (result != ISC_R_SUCCESS) { goto cleanup; } @@ -3180,7 +3180,7 @@ cleanup: } static isc_result_t -addclosest(isc_mem_t *mctx, dns_slabheader_t *newheader, +addclosest(isc_mem_t *mctx, dns_slabheader_t *newheader, uint32_t maxrrperset, dns_rdataset_t *rdataset) { isc_result_t result; dns_slabheader_proof_t *closest = NULL; @@ -3191,12 +3191,12 @@ addclosest(isc_mem_t *mctx, dns_slabheader_t *newheader, result = dns_rdataset_getclosest(rdataset, &name, &neg, &negsig); RUNTIME_CHECK(result == ISC_R_SUCCESS); - result = dns_rdataslab_fromrdataset(&neg, mctx, &r1, 0); + result = dns_rdataslab_fromrdataset(&neg, mctx, &r1, 0, maxrrperset); if (result != ISC_R_SUCCESS) { goto cleanup; } - result = dns_rdataslab_fromrdataset(&negsig, mctx, &r2, 0); + result = dns_rdataslab_fromrdataset(&negsig, mctx, &r2, 0, maxrrperset); if (result != ISC_R_SUCCESS) { goto cleanup; } @@ -3272,7 +3272,8 @@ dns__rbtdb_addrdataset(dns_db_t *db, dns_dbnode_t *node, } result = dns_rdataslab_fromrdataset(rdataset, rbtdb->common.mctx, - ®ion, sizeof(dns_slabheader_t)); + ®ion, sizeof(dns_slabheader_t), + rbtdb->maxrrperset); if (result != ISC_R_SUCCESS) { return (result); } @@ -3329,7 +3330,7 @@ dns__rbtdb_addrdataset(dns_db_t *db, dns_dbnode_t *node, } if ((rdataset->attributes & DNS_RDATASETATTR_NOQNAME) != 0) { result = addnoqname(rbtdb->common.mctx, newheader, - rdataset); + rbtdb->maxrrperset, rdataset); if (result != ISC_R_SUCCESS) { dns_slabheader_destroy(&newheader); return (result); @@ -3337,7 +3338,7 @@ dns__rbtdb_addrdataset(dns_db_t *db, dns_dbnode_t *node, } if ((rdataset->attributes & DNS_RDATASETATTR_CLOSEST) != 0) { result = addclosest(rbtdb->common.mctx, newheader, - rdataset); + rbtdb->maxrrperset, rdataset); if (result != ISC_R_SUCCESS) { dns_slabheader_destroy(&newheader); return (result); @@ -3487,7 +3488,8 @@ dns__rbtdb_subtractrdataset(dns_db_t *db, dns_dbnode_t *node, dns__rbtdb_nodefullname(db, node, nodename); result = dns_rdataslab_fromrdataset(rdataset, rbtdb->common.mctx, - ®ion, sizeof(dns_slabheader_t)); + ®ion, sizeof(dns_slabheader_t), + 0); if (result != ISC_R_SUCCESS) { return (result); } @@ -4957,3 +4959,12 @@ expire_ttl_headers(dns_rbtdb_t *rbtdb, unsigned int locknum, dns_expire_ttl DNS__DB_FLARG_PASS); } } + +void +dns__rbtdb_setmaxrrperset(dns_db_t *db, uint32_t value) { + dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db; + + REQUIRE(VALID_RBTDB(rbtdb)); + + rbtdb->maxrrperset = value; +} diff --git a/lib/dns/rbtdb_p.h b/lib/dns/rbtdb_p.h index 46da32691a..fe06b30b13 100644 --- a/lib/dns/rbtdb_p.h +++ b/lib/dns/rbtdb_p.h @@ -114,6 +114,7 @@ struct dns_rbtdb { uint32_t current_serial; uint32_t least_serial; uint32_t next_serial; + uint32_t maxrrperset; dns_rbtdb_version_t *current_version; dns_rbtdb_version_t *future_version; rbtdb_versionlist_t open_versions; @@ -427,6 +428,12 @@ dns__rbtdb_setttl(dns_slabheader_t *header, dns_ttl_t newttl); * also update the TTL heap accordingly. */ +void +dns__rbtdb_setmaxrrperset(dns_db_t *db, uint32_t value); +/*%< + * Set the max RRs per RRset limit. + */ + /* * Functions specific to zone databases that are also called from rbtdb.c. */ diff --git a/lib/dns/rdataslab.c b/lib/dns/rdataslab.c index 6063c1be9c..abbb902317 100644 --- a/lib/dns/rdataslab.c +++ b/lib/dns/rdataslab.c @@ -168,7 +168,8 @@ fillin_offsets(unsigned char *offsetbase, unsigned int *offsettable, isc_result_t dns_rdataslab_fromrdataset(dns_rdataset_t *rdataset, isc_mem_t *mctx, - isc_region_t *region, unsigned int reservelen) { + isc_region_t *region, unsigned int reservelen, + uint32_t maxrrperset) { /* * Use &removed as a sentinel pointer for duplicate * rdata as rdata.data == NULL is valid. @@ -208,6 +209,10 @@ dns_rdataslab_fromrdataset(dns_rdataset_t *rdataset, isc_mem_t *mctx, return (ISC_R_SUCCESS); } + if (maxrrperset > 0 && nitems > maxrrperset) { + return (DNS_R_TOOMANYRECORDS); + } + if (nitems > 0xffff) { return (ISC_R_NOSPACE); } @@ -515,7 +520,8 @@ isc_result_t dns_rdataslab_merge(unsigned char *oslab, unsigned char *nslab, unsigned int reservelen, isc_mem_t *mctx, dns_rdataclass_t rdclass, dns_rdatatype_t type, - unsigned int flags, unsigned char **tslabp) { + unsigned int flags, uint32_t maxrrperset, + unsigned char **tslabp) { unsigned char *ocurrent = NULL, *ostart = NULL, *ncurrent = NULL; unsigned char *tstart = NULL, *tcurrent = NULL, *data = NULL; unsigned int ocount, ncount, count, olength, tlength, tcount, length; @@ -554,6 +560,10 @@ dns_rdataslab_merge(unsigned char *oslab, unsigned char *nslab, #endif /* if DNS_RDATASET_FIXED */ INSIST(ocount > 0 && ncount > 0); + if (maxrrperset > 0 && ocount + ncount > maxrrperset) { + return (DNS_R_TOOMANYRECORDS); + } + #if DNS_RDATASET_FIXED oncount = ncount; #endif /* if DNS_RDATASET_FIXED */ diff --git a/lib/dns/view.c b/lib/dns/view.c index d338c80afa..15e2e303db 100644 --- a/lib/dns/view.c +++ b/lib/dns/view.c @@ -643,6 +643,8 @@ dns_view_setcache(dns_view_t *view, dns_cache_t *cache, bool shared) { dns_cache_attach(cache, &view->cache); dns_cache_attachdb(cache, &view->cachedb); INSIST(DNS_DB_VALID(view->cachedb)); + + dns_cache_setmaxrrperset(view->cache, view->maxrrperset); } bool @@ -2336,6 +2338,15 @@ dns_view_getresolver(dns_view_t *view, dns_resolver_t **resolverp) { return (result); } +void +dns_view_setmaxrrperset(dns_view_t *view, uint32_t value) { + REQUIRE(DNS_VIEW_VALID(view)); + view->maxrrperset = value; + if (view->cache != NULL) { + dns_cache_setmaxrrperset(view->cache, value); + } +} + void dns_view_setudpsize(dns_view_t *view, uint16_t udpsize) { REQUIRE(DNS_VIEW_VALID(view)); diff --git a/lib/dns/zone.c b/lib/dns/zone.c index 3c2b2011dc..6c27dfe3ec 100644 --- a/lib/dns/zone.c +++ b/lib/dns/zone.c @@ -318,6 +318,7 @@ struct dns_zone { uint32_t minretry; uint32_t maxrecords; + uint32_t maxrrperset; dns_remote_t primaries; @@ -12057,6 +12058,16 @@ dns_zone_setmaxrecords(dns_zone_t *zone, uint32_t val) { zone->maxrecords = val; } +void +dns_zone_setmaxrrperset(dns_zone_t *zone, uint32_t val) { + REQUIRE(DNS_ZONE_VALID(zone)); + + zone->maxrrperset = val; + if (zone->db != NULL) { + dns_db_setmaxrrperset(zone->db, val); + } +} + static bool notify_isqueued(dns_zone_t *zone, unsigned int flags, dns_name_t *name, isc_sockaddr_t *addr, dns_tsigkey_t *key, @@ -14458,6 +14469,7 @@ ns_query(dns_zone_t *zone, dns_rdataset_t *soardataset, dns_stub_t *stub) { goto cleanup; } dns_db_setloop(stub->db, zone->loop); + dns_db_setmaxrrperset(stub->db, zone->maxrrperset); } result = dns_db_newversion(stub->db, &stub->version); @@ -17514,6 +17526,7 @@ zone_replacedb(dns_zone_t *zone, dns_db_t *db, bool dump) { } zone_attachdb(zone, db); dns_db_setloop(zone->db, zone->loop); + dns_db_setmaxrrperset(zone->db, zone->maxrrperset); DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_LOADED | DNS_ZONEFLG_NEEDNOTIFY); return (ISC_R_SUCCESS); @@ -24153,6 +24166,7 @@ dns_zone_makedb(dns_zone_t *zone, dns_db_t **dbp) { } dns_db_setloop(db, zone->loop); + dns_db_setmaxrrperset(db, zone->maxrrperset); *dbp = db; diff --git a/lib/isccfg/namedconf.c b/lib/isccfg/namedconf.c index 70bf565f19..528a52de05 100644 --- a/lib/isccfg/namedconf.c +++ b/lib/isccfg/namedconf.c @@ -2372,6 +2372,9 @@ static cfg_clausedef_t zone_clauses[] = { { "max-records", &cfg_type_uint32, CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR | CFG_ZONE_STUB | CFG_ZONE_STATICSTUB | CFG_ZONE_REDIRECT }, + { "max-records-per-type", &cfg_type_uint32, + CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR | + CFG_ZONE_STUB | CFG_ZONE_STATICSTUB | CFG_ZONE_REDIRECT }, { "max-refresh-time", &cfg_type_uint32, CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR | CFG_ZONE_STUB }, { "max-retry-time", &cfg_type_uint32,