From: Ondřej Surý Date: Fri, 1 Mar 2024 07:26:07 +0000 (+0100) Subject: Add a limit to the number of RRs in RRSets X-Git-Tag: v9.20.0~8^2~18 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=32af7299ebc116146b87e9c2316de6b62d24cec2;p=thirdparty%2Fbind9.git Add a limit to the number of RRs in RRSets Previously, the number of RRs in the RRSets were internally unlimited. As the data structure that holds the RRs is just a linked list, and there are places where we just walk through all of the RRs, adding an RRSet with huge number of RRs inside would slow down processing of said RRSets. Add a configurable limit to cap the number of the RRs in a single RRSet. This is enforced at the database (rbtdb, qpzone, qpcache) level and configured with new max-records-per-type configuration option that can be configured globally, per-view and per-zone. --- diff --git a/bin/named/config.c b/bin/named/config.c index 38ddc7ca238..1943eb18790 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 a17375dbaaa..6bcd0b5d561 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 c45051b42a4..f6646e38198 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 96200d0fd38..a7b09611d1c 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 69de2ca1468..daf31646430 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 60072ce9c20..d637a9c9ed4 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 e161a3e4cff..7aa3757cdb0 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 4c0cc55e581..0decf3a6e03 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 cc9dbaa446b..4238e689f59 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 7c94dcd1804..261d46d0935 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 e3c6ef69d51..6586686300f 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 c0bee863fb5..b389f6eede9 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 26eca8e20a7..4ded7c8e192 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 85c158fbcbd..5f68d83c52f 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 6d7c98cb456..8d0537b1364 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 43821dca176..52d92037d31 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 ad082bbe2be..3f3ca0ede1d 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 72cf80c3f47..738ab4cfe08 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 fe968f3abea..96f9d58a12a 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 ab57716ef5d..4227854669f 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 b5828282664..e97835f8c63 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 2519f979117..bdcff3061c2 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 c4ef39f9d8a..329decbb6f0 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 b2b56749495..da692d2538d 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 f884174673d..779eb143d64 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 43599fa381d..93b71b9a98d 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 0dee744b7a4..71ac5c1951b 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 46da32691a9..fe06b30b139 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 6063c1be9ce..abbb902317d 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 d338c80afa1..15e2e303dbf 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 3c2b2011dc7..6c27dfe3ec0 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 70bf565f191..528a52de05f 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,