]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
Add a limit to the number of RRs in RRSets
authorOndřej Surý <ondrej@isc.org>
Fri, 1 Mar 2024 07:26:07 +0000 (08:26 +0100)
committerNicki Křížek <nicki@isc.org>
Mon, 10 Jun 2024 14:55:07 +0000 (16:55 +0200)
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.

32 files changed:
bin/named/config.c
bin/named/server.c
bin/named/zoneconf.c
bin/tests/system/doth/ns2/named.conf.in
bin/tests/system/doth/ns3/named.conf.in
bin/tests/system/doth/ns4/named.conf.in
bin/tests/system/doth/ns5/named.conf.in
doc/arm/reference.rst
doc/misc/mirror.zoneopt
doc/misc/options
doc/misc/primary.zoneopt
doc/misc/redirect.zoneopt
doc/misc/secondary.zoneopt
doc/misc/static-stub.zoneopt
doc/misc/stub.zoneopt
lib/dns/cache.c
lib/dns/db.c
lib/dns/include/dns/cache.h
lib/dns/include/dns/db.h
lib/dns/include/dns/rdataslab.h
lib/dns/include/dns/view.h
lib/dns/include/dns/zone.h
lib/dns/qpcache.c
lib/dns/qpzone.c
lib/dns/rbt-cachedb.c
lib/dns/rbt-zonedb.c
lib/dns/rbtdb.c
lib/dns/rbtdb_p.h
lib/dns/rdataslab.c
lib/dns/view.c
lib/dns/zone.c
lib/isccfg/namedconf.c

index 38ddc7ca238be60bcaf66c2d5bfb71af11b7681c..1943eb187900ef9e9604c414523adafae5e6855e 100644 (file)
@@ -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\
index a17375dbaaaf9c3619ecb8e444812ccaef286c01..6bcd0b5d561780e387180c3f93fef60719424774 100644 (file)
@@ -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);
index c45051b42a46754fc1fd057521480b0a664de1fe..f6646e38198f36745f70685c9a2dc730bc6fb99c 100644 (file)
@@ -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);
index 96200d0fd384dffea7a7dad92fdee7c98ec03a1a..a7b09611d1c2cfcd2c4fdbd8638bec6af61ece53 100644 (file)
@@ -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;
 };
index 69de2ca1468bd12206f6ab32e4502743f8c12085..daf31646430db8af987878f17d7bd73a7f9b5ff3 100644 (file)
@@ -44,6 +44,7 @@ options {
        ixfr-from-differences yes;
        check-integrity no;
        dnssec-validation yes;
+       max-records-per-type 0;
 };
 
 trust-anchors { };
index 60072ce9c20a2d054ba5d4119b9400efac7fe9fd..d637a9c9ed4e74f3a5fae125ca9b667f32db53ee 100644 (file)
@@ -52,6 +52,7 @@ options {
        ixfr-from-differences yes;
        check-integrity no;
        dnssec-validation yes;
+       max-records-per-type 0;
 };
 
 trust-anchors { };
index e161a3e4cffa21763c02034afc2cc26accf561c0..7aa3757cdb0ea692f1623d6379209930bc5f591e 100644 (file)
@@ -40,6 +40,7 @@ options {
        ixfr-from-differences yes;
        check-integrity no;
        dnssec-validation yes;
+       max-records-per-type 0;
 };
 
 trust-anchors { };
index 4c0cc55e581fb75c3f353d629b7fc0b6dd86bd34..0decf3a6e0341c6797d7569eec9a94e4e2e423ff 100644 (file)
@@ -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.
index cc9dbaa446badbba3749a468450729f6f368a65f..4238e689f59cbb449d6efe886d85e588fc961620 100644 (file)
@@ -16,6 +16,7 @@ zone <string> [ <class> ] {
        max-ixfr-ratio ( unlimited | <percentage> );
        max-journal-size ( default | unlimited | <sizeval> );
        max-records <integer>;
+       max-records-per-type <integer>;
        max-refresh-time <integer>;
        max-retry-time <integer>;
        max-transfer-idle-in <integer>;
index 7c94dcd1804fabc7da5eeb87d9e8774f141f5939..261d46d0935dd2425085449145818737c21b44d4 100644 (file)
@@ -183,6 +183,7 @@ options {
        max-journal-size ( default | unlimited | <sizeval> );
        max-ncache-ttl <duration>;
        max-records <integer>;
+       max-records-per-type <integer>;
        max-recursion-depth <integer>;
        max-recursion-queries <integer>;
        max-refresh-time <integer>;
@@ -468,6 +469,7 @@ view <string> [ <class> ] {
        max-journal-size ( default | unlimited | <sizeval> );
        max-ncache-ttl <duration>;
        max-records <integer>;
+       max-records-per-type <integer>;
        max-recursion-depth <integer>;
        max-recursion-queries <integer>;
        max-refresh-time <integer>;
index e3c6ef69d5106600bc7493bcbdadf15ad7bcaf37..6586686300f3413d6a29f2cf1c888cc38d86df27 100644 (file)
@@ -37,6 +37,7 @@ zone <string> [ <class> ] {
        max-ixfr-ratio ( unlimited | <percentage> );
        max-journal-size ( default | unlimited | <sizeval> );
        max-records <integer>;
+       max-records-per-type <integer>;
        max-transfer-idle-out <integer>;
        max-transfer-time-out <integer>;
        max-zone-ttl ( unlimited | <duration> ); // deprecated
index c0bee863fb5fb932efc952620ce8444528d1270d..b389f6eede942d59b16d1416a38d70155e2a82db 100644 (file)
@@ -7,6 +7,7 @@ zone <string> [ <class> ] {
        masterfile-format ( raw | text );
        masterfile-style ( full | relative );
        max-records <integer>;
+       max-records-per-type <integer>;
        max-zone-ttl ( unlimited | <duration> ); // deprecated
        primaries [ port <integer> ] [ source ( <ipv4_address> | * ) ] [ source-v6 ( <ipv6_address> | * ) ] { ( <remote-servers> | <ipv4_address> [ port <integer> ] | <ipv6_address> [ port <integer> ] ) [ key <string> ] [ tls <string> ]; ... };
        zone-statistics ( full | terse | none | <boolean> );
index 26eca8e20a75ad3829c8cb85f464d99142be80df..4ded7c8e1926363b77d779b9212ff4635793a3cc 100644 (file)
@@ -28,6 +28,7 @@ zone <string> [ <class> ] {
        max-ixfr-ratio ( unlimited | <percentage> );
        max-journal-size ( default | unlimited | <sizeval> );
        max-records <integer>;
+       max-records-per-type <integer>;
        max-refresh-time <integer>;
        max-retry-time <integer>;
        max-transfer-idle-in <integer>;
index 85c158fbcbd33c562c22847bc2b42b0fbc2a39ed..5f68d83c52f90c220771e086167489cbfe389393 100644 (file)
@@ -5,6 +5,7 @@ zone <string> [ <class> ] {
        forward ( first | only );
        forwarders [ port <integer> ] [ tls <string> ] { ( <ipv4_address> | <ipv6_address> ) [ port <integer> ] [ tls <string> ]; ... };
        max-records <integer>;
+       max-records-per-type <integer>;
        server-addresses { ( <ipv4_address> | <ipv6_address> ); ... };
        server-names { <string>; ... };
        zone-statistics ( full | terse | none | <boolean> );
index 6d7c98cb45684e4350a5f89d6f809764eb955420..8d0537b136424c3362e5a883ff7a43cbab99c428 100644 (file)
@@ -11,6 +11,7 @@ zone <string> [ <class> ] {
        masterfile-format ( raw | text );
        masterfile-style ( full | relative );
        max-records <integer>;
+       max-records-per-type <integer>;
        max-refresh-time <integer>;
        max-retry-time <integer>;
        max-transfer-idle-in <integer>;
index 43821dca1766dd83c90638522c1fd51b9c4a70c9..52d92037d3141752103a9101251ca3729d4dbb8a 100644 (file)
@@ -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
index ad082bbe2be45158cfacabe747aa59ba000668c5..3f3ca0ede1dc0f7f05ff743c1c77cb0d9fdc98bf 100644 (file)
@@ -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);
+       }
+}
index 72cf80c3f47a49ffbf63d529adb2aee5f18b12f8..738ab4cfe08f86da4b62bf5400f21c4da816d92a 100644 (file)
@@ -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);
index fe968f3abead1bdb4dd4aa846f0bdbbb098aa9ce..96f9d58a12ad152021dcf3d49890b61441830c50 100644 (file)
@@ -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
index ab57716ef5db9376657428292b562352c6ac5457..4227854669fd397ea6c877c293428c9cf976c29e 100644 (file)
@@ -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'.
  */
index b58282826648b1788ea48859839da216d929a285..e97835f8c636234ec65bc159ca66177d20dee5d7 100644 (file)
@@ -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);
 /*%<
index 2519f979117e75967e9d174c2a35f4f17b0cc4bf..bdcff3061c2eed14bf21e0c2c320737587ca111e 100644 (file)
@@ -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);
 /*%<
index c4ef39f9d8a98ca9f60b19a435a7d0fd44f1d695..329decbb6f097357184e016f7b79852a0abe31b6 100644 (file)
@@ -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,
-                                           &region, sizeof(dns_slabheader_t));
+                                           &region, 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
index b2b56749495d7162ee1ed93d4122446d2170d52f..da692d2538d1a77b1e36fe7ab8107607c907d6ba 100644 (file)
@@ -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,
-                                           &region, sizeof(dns_slabheader_t));
+                                           &region, 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,
-                                           &region, sizeof(dns_slabheader_t));
+                                           &region, 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,
-                                           &region, sizeof(dns_slabheader_t));
+                                           &region, 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
index f884174673ddfd6975de4577b1281e5f0f211bf6..779eb143d6499d4029eec9039569070016cf085c 100644 (file)
@@ -1582,6 +1582,7 @@ dns_dbmethods_t dns__rbtdb_cachemethods = {
        .unlocknode = dns__rbtdb_unlocknode,
        .expiredata = expiredata,
        .deletedata = dns__rbtdb_deletedata,
+       .setmaxrrperset = dns__rbtdb_setmaxrrperset,
 };
 
 /*
index 43599fa381db934fcd5e686db384ee1dfc8a0d34..93b71b9a98d497ec061edf45ad34a09887789730 100644 (file)
@@ -1749,7 +1749,8 @@ loading_addrdataset(void *arg, const dns_name_t *name,
        }
 
        result = dns_rdataslab_fromrdataset(rdataset, rbtdb->common.mctx,
-                                           &region, sizeof(dns_slabheader_t));
+                                           &region, 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
index 0dee744b7a4fc8186d6408f93af2c8c2e1f7eebd..71ac5c1951ba4b93b07f3c099140e67cc495e03f 100644 (file)
@@ -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,
-                                           &region, sizeof(dns_slabheader_t));
+                                           &region, 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,
-                                           &region, sizeof(dns_slabheader_t));
+                                           &region, 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;
+}
index 46da32691a9b1bba5ea8b72cda5f74958eb386cf..fe06b30b1397107349cc8860b3f7798378b51ec6 100644 (file)
@@ -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.
  */
index 6063c1be9ce555094e96a2a88de9f8ba0435e095..abbb902317d76f94537c253672497514d9e30419 100644 (file)
@@ -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 */
index d338c80afa152c9e7011b15997c3743d985561d1..15e2e303dbfd14269e01a7b6074f7ca7c7ff3ef9 100644 (file)
@@ -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));
index 3c2b2011dc744aa90cf68fef1c341382866f1e66..6c27dfe3ec0543efcf4d2120fd5f238d1c7e26df 100644 (file)
@@ -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;
 
index 70bf565f19116c7bcd438a9a0535ea3e3a5e1501..528a52de05fb0a02ec896c85284f74fe2781a26f 100644 (file)
@@ -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,