From 32af7299ebc116146b87e9c2316de6b62d24cec2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Sur=C3=BD?= Date: Fri, 1 Mar 2024 08:26:07 +0100 Subject: [PATCH] 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. --- bin/named/config.c | 1 + bin/named/server.c | 9 +++++++ bin/named/zoneconf.c | 8 ++++++ bin/tests/system/doth/ns2/named.conf.in | 1 + bin/tests/system/doth/ns3/named.conf.in | 1 + bin/tests/system/doth/ns4/named.conf.in | 1 + bin/tests/system/doth/ns5/named.conf.in | 1 + doc/arm/reference.rst | 15 +++++++++++ doc/misc/mirror.zoneopt | 1 + doc/misc/options | 2 ++ doc/misc/primary.zoneopt | 1 + doc/misc/redirect.zoneopt | 1 + doc/misc/secondary.zoneopt | 1 + doc/misc/static-stub.zoneopt | 1 + doc/misc/stub.zoneopt | 1 + lib/dns/cache.c | 12 +++++++++ lib/dns/db.c | 9 +++++++ lib/dns/include/dns/cache.h | 6 +++++ lib/dns/include/dns/db.h | 9 +++++++ lib/dns/include/dns/rdataslab.h | 6 +++-- lib/dns/include/dns/view.h | 7 ++++++ lib/dns/include/dns/zone.h | 13 ++++++++++ lib/dns/qpcache.c | 33 ++++++++++++++++++------- lib/dns/qpzone.c | 22 ++++++++++++++--- lib/dns/rbt-cachedb.c | 1 + lib/dns/rbt-zonedb.c | 4 ++- lib/dns/rbtdb.c | 33 ++++++++++++++++--------- lib/dns/rbtdb_p.h | 7 ++++++ lib/dns/rdataslab.c | 14 +++++++++-- lib/dns/view.c | 11 +++++++++ lib/dns/zone.c | 14 +++++++++++ lib/isccfg/namedconf.c | 3 +++ 32 files changed, 220 insertions(+), 29 deletions(-) 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,