]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
Add a parameter to limit the number of '$GENERATE' steps
authorRemi Gacogne <remi.gacogne@powerdns.com>
Fri, 25 Oct 2019 14:35:37 +0000 (16:35 +0200)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Thu, 31 Oct 2019 09:40:25 +0000 (10:40 +0100)
14 files changed:
docs/settings.rst
modules/bindbackend/bindbackend2.cc
pdns/common_startup.cc
pdns/fuzz_zoneparsertng.cc
pdns/pdns_recursor.cc
pdns/pdnsutil.cc
pdns/recursordist/docs/settings.rst
pdns/reczones.cc
pdns/ws-auth.cc
pdns/zone2json.cc
pdns/zone2ldap.cc
pdns/zone2sql.cc
pdns/zoneparser-tng.cc
pdns/zoneparser-tng.hh

index 54b5137c010dee80d61581bbd0dc55019d920860..17e7e7f6318cd7cf2b28c1edf7bb9aff472864d6 100644 (file)
@@ -927,6 +927,21 @@ will generally suffice for most installations.
 Maximum number of empty non-terminals to add to a zone. This is a
 protection measure to avoid database explosion due to long names.
 
+.. _setting-max-generate-steps:
+
+``max-generate-steps``
+----------------------
+
+.. versionadded:: 4.3.0
+
+-  Integer
+-  Default: 0
+
+Maximum number of steps for a '$GENERATE' directive when parsing a
+zone file. This is a protection measure to prevent consuming a lot of
+CPU and memory when untrusted zones are loaded. Default to 0 which
+means unlimited.
+
 .. _setting-max-nsec3-iterations:
 
 ``max-nsec3-iterations``
index 4322f220f23c6e2fa3edbbf18f4d73cb1ed47724..634fc831d264fe3080430b3c0c6afb24f7b92e96 100644 (file)
@@ -481,11 +481,11 @@ void Bind2Backend::parseZoneFile(BB2DomainInfo *bbd)
     nsec3zone=getNSEC3PARAM(bbd->d_name, &ns3pr);
 
   bbd->d_records = shared_ptr<recordstorage_t>(new recordstorage_t());
-        
   ZoneParserTNG zpt(bbd->d_filename, bbd->d_name, s_binddirectory);
+  zpt.setMaxGenerateSteps(::arg().asNum("max-generate-steps"));
   DNSResourceRecord rr;
   string hashed;
-  while(zpt.get(rr)) { 
+  while(zpt.get(rr)) {
     if(rr.qtype.getCode() == QType::NSEC || rr.qtype.getCode() == QType::NSEC3 || rr.qtype.getCode() == QType::NSEC3PARAM)
       continue; // we synthesise NSECs on demand
 
index bce6b27ef67bb758dfa8ef609cb303d83b46639c..1a383c71fac9ba74bb447590c0dde51ff1607ddf 100644 (file)
@@ -228,6 +228,8 @@ void declareArguments()
 
   ::arg().set("tcp-fast-open", "Enable TCP Fast Open support on the listening sockets, using the supplied numerical value as the queue size")="0";
 
+  ::arg().set("max-generate-steps", "Maximum number of $GENERATE steps when loading a zone from a file")="0";
+
   ::arg().set("rng", "Specify the random number generator to use. Valid values are auto,sodium,openssl,getrandom,arc4random,urandom.")="auto";
 }
 
index a6f30d38590f2c143e75147c6686c6f8550c86b2..442555b12200d17b07640c0e22608ba589fe9c5f 100644 (file)
@@ -47,6 +47,8 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
     boost::split(lines, tmp, boost::is_any_of("\n"));
 
     ZoneParserTNG zpt(lines, g_rootdnsname);
+    /* limit the number of steps for '$GENERATE' entries */
+    zpt.setMaxGenerateSteps(10000);
     DNSResourceRecord drr;
     while (zpt.get(drr)) {
     }
index d7c3716d2a88c1ccfcbd78e7d77bfd0cc8a909ba..3f1a5166fdafd240421b8fc66bf6495620d278b4 100644 (file)
@@ -4698,6 +4698,7 @@ int main(int argc, char **argv)
     ::arg().set("distribution-load-factor", "The load factor used when PowerDNS is distributing queries to worker threads")="0.0";
     ::arg().setSwitch("qname-minimization", "Use Query Name Minimization")="no";
     ::arg().setSwitch("nothing-below-nxdomain", "When an NXDOMAIN exists in cache for a name with fewer labels than the qname, send NXDOMAIN without doing a lookup (see RFC 8020)")="yes";
+    ::arg().set("max-generate-steps", "Maximum number of $GENERATE steps when loading a zone from a file")="0";
 #ifdef NOD_ENABLED
     ::arg().set("new-domain-tracking", "Track newly observed domains (i.e. never seen before).")="no";
     ::arg().set("new-domain-log", "Log newly observed domains.")="yes";
index c26b82aca1caa94eec39aa49dad359a6c8571a08..d13ab9ec50d51e0eb346a8427ffcbd2c4bbd7bf8 100644 (file)
@@ -90,6 +90,7 @@ void loadMainConfig(const std::string& configdir)
   ::arg().set("max-nsec3-iterations","Limit the number of NSEC3 hash iterations")="500"; // RFC5155 10.3
   ::arg().set("max-signature-cache-entries", "Maximum number of signatures cache entries")="";
   ::arg().set("rng", "Specify random number generator to use. Valid values are auto,sodium,openssl,getrandom,arc4random,urandom.")="auto";
+  ::arg().set("max-generate-steps", "Maximum number of $GENERATE steps when loading a zone from a file")="0";
   ::arg().laxFile(configname.c_str());
 
   if(!::arg()["load-modules"].empty()) {
@@ -917,6 +918,7 @@ int editZone(const DNSName &zone) {
   }
   cmdline.clear();
   ZoneParserTNG zpt(tmpnam, g_rootdnsname);
+  zpt.setMaxGenerateSteps(::arg().asNum("max-generate-steps"));
   DNSResourceRecord zrr;
   map<pair<DNSName,uint16_t>, vector<DNSRecord> > grouped;
   try {
@@ -1088,6 +1090,7 @@ int loadZone(DNSName zone, const string& fname) {
   }
   DNSBackend* db = di.backend;
   ZoneParserTNG zpt(fname, zone);
+  zpt.setMaxGenerateSteps(::arg().asNum("max-generate-steps"));
 
   DNSResourceRecord rr;
   if(!db->startTransaction(zone, di.id)) {
@@ -1416,6 +1419,7 @@ void testSpeed(DNSSECKeeper& dk, const DNSName& zone, const string& remote, int
 void verifyCrypto(const string& zone)
 {
   ZoneParserTNG zpt(zone);
+  zpt.setMaxGenerateSteps(::arg().asNum("max-generate-steps"));
   DNSResourceRecord rr;
   DNSKEYRecordContent drc;
   RRSIGRecordContent rrc;
index d9b65d35bf0ec7d8eabff18afb0b01bfef4325fc..ba7770d4bce4d84d6fe68f7a3ffab0d5adfbd829 100644 (file)
@@ -872,6 +872,20 @@ Maximum number of incoming requests handled concurrently per tcp
 connection. This number must be larger than 0 and smaller than 65536
 and also smaller than `max-mthreads`.
 
+.. _setting-max-generate-steps:
+
+``max-generate-steps``
+----------------------
+
+.. versionadded:: 4.3.0
+
+-  Integer
+-  Default: 0
+
+Maximum number of steps for a '$GENERATE' directive when parsing a
+zone file. This is a protection measure to prevent consuming a lot of
+CPU and memory when untrusted zones are loaded. Default to 0 which
+means unlimited.
 
 .. _setting-max-mthreads:
 
index 505a18402edda79e9591881954ce3b882c019e95..c811b0c98946a0dea863b29649aecd36593fc38e 100644 (file)
@@ -84,6 +84,7 @@ void primeHints(void)
   }
   else {
     ZoneParserTNG zpt(::arg()["hint-file"]);
+    zpt.setMaxGenerateSteps(::arg().asNum("max-generate-steps"));
     DNSResourceRecord rr;
 
     while(zpt.get(rr)) {
@@ -381,6 +382,7 @@ std::shared_ptr<SyncRes::domainmap_t> parseAuthAndForwards()
         ad.d_rdForward = false;
         g_log<<Logger::Error<<"Parsing authoritative data for zone '"<<headers.first<<"' from file '"<<headers.second<<"'"<<endl;
         ZoneParserTNG zpt(headers.second, DNSName(headers.first));
+        zpt.setMaxGenerateSteps(::arg().asNum("max-generate-steps"));
         DNSResourceRecord rr;
        DNSRecord dr;
         while(zpt.get(rr)) {
index c82775ababfce5141f100cf3c66efce33289d8ca..54f4e0b17848c974c6fb368e36ff5c67e7700bdd 100644 (file)
@@ -1304,6 +1304,7 @@ static void gatherRecordsFromZone(const std::string& zonestring, vector<DNSResou
   stringtok(zonedata, zonestring, "\r\n");
 
   ZoneParserTNG zpt(zonedata, zonename);
+  zpt.setMaxGenerateSteps(::arg().asNum("max-generate-steps"));
 
   bool seenSOA=false;
 
index 512fe87c14c1c7cc592cd6698ce32970ca41d0cb..e4d0cdb1c44b1a82579748127ab5a9201a41df1d 100644 (file)
@@ -108,6 +108,7 @@ try
     ::arg().set("soa-refresh-default","Do not change")="0";
     ::arg().set("soa-retry-default","Do not change")="0";
     ::arg().set("soa-expire-default","Do not change")="0";
+    ::arg().set("max-generate-steps", "Maximum number of $GENERATE steps when loading a zone from a file")="0";
 
     ::arg().setCmd("help","Provide a helpful message");
     ::arg().setCmd("version","Print the version");
@@ -174,6 +175,7 @@ try
             Json::object obj;
             Json::array recs;
             ZoneParserTNG zpt(i->filename, i->name, BP.getDirectory());
+            zpt.setMaxGenerateSteps(::arg().asNum("max-generate-steps"));
             DNSResourceRecord rr;
             obj["name"] = i->name.toString();
 
@@ -205,6 +207,7 @@ try
     }
     else {
       ZoneParserTNG zpt(zonefile, DNSName(::arg()["zone-name"]));
+      zpt.setMaxGenerateSteps(::arg().asNum("max-generate-steps"));
       DNSResourceRecord rr;
       string zname;
       Json::object obj;
index 1d760dec3595506c15de4e9ed0725f8dc5780ef6..b7ebcb3c31c9fd16bc018824790fc5829fe76271 100644 (file)
@@ -248,6 +248,7 @@ int main( int argc, char* argv[] )
                 args.set( "layout", "How to arrange entries in the directory (simple or as tree)" ) = "simple";
                 args.set( "domainid", "Domain ID of the first domain found (incremented afterwards)" ) = "1";
                 args.set( "metadata-dn", "DN under which to store the domain metadata" ) = "";
+                args.set( "max-generate-steps", "Maximum number of $GENERATE steps when loading a zone from a file")="0";
 
                 args.parse( argc, argv );
 
@@ -316,6 +317,7 @@ int main( int argc, char* argv[] )
                                                 cerr << "Parsing file: " << i.filename << ", domain: " << i.name << endl;
                                                 g_zonename = i.name;
                                                 ZoneParserTNG zpt(i.filename, i.name, BP.getDirectory());
+                                                zpt.setMaxGenerateSteps(args.asNum("max-generate-steps"));
                                                 DNSResourceRecord rr;
                                                 while(zpt.get(rr)) {
                                                         callback(g_domainid, rr.qname, rr.qtype.getName(), encode_non_ascii(rr.content), rr.ttl);
@@ -344,6 +346,7 @@ int main( int argc, char* argv[] )
 
                         g_zonename = DNSName(args["zone-name"]);
                         ZoneParserTNG zpt(args["zone-file"], g_zonename);
+                        zpt.setMaxGenerateSteps(args.asNum("max-generate-steps"));
                         DNSResourceRecord rr;
                         while(zpt.get(rr)) {
                                 callback(g_domainid, rr.qname, rr.qtype.getName(), encode_non_ascii(rr.content), rr.ttl);
index 5b86fa34102846bfb9285e04419af703ae2849cc..08fca465d4ada00c430e17772c2e974b61a51844 100644 (file)
@@ -236,6 +236,7 @@ try
     ::arg().set("soa-refresh-default","Do not change")="0";
     ::arg().set("soa-retry-default","Do not change")="0";
     ::arg().set("soa-expire-default","Do not change")="0";
+    ::arg().set("max-generate-steps", "Maximum number of $GENERATE steps when loading a zone from a file")="0";
 
     ::arg().setCmd("help","Provide a helpful message");
     ::arg().setCmd("version","Print the version");
@@ -319,6 +320,7 @@ try
             emitDomain(i->name, &(i->masters));
             
             ZoneParserTNG zpt(i->filename, i->name, BP.getDirectory());
+            zpt.setMaxGenerateSteps(::arg().asNum("max-generate-steps"));
             DNSResourceRecord rr;
             bool seenSOA=false;
             string comment;
@@ -357,6 +359,7 @@ try
         zonename = DNSName(::arg()["zone-name"]);
 
       ZoneParserTNG zpt(zonefile, zonename);
+      zpt.setMaxGenerateSteps(::arg().asNum("max-generate-steps"));
       DNSResourceRecord rr;
       startNewTransaction();
       string comment;
index 9772e36ada35b6afbe40b217da0c229b5c3b1e2d..b2d88b3a940fddabf0ffb24592392cbdf15f320e 100644 (file)
@@ -328,6 +328,12 @@ bool ZoneParserTNG::get(DNSResourceRecord& rr, std::string* comment)
           d_templatestop < d_templatecounter) {
         throw exception("Invalid $GENERATE parameters");
       }
+      if (d_maxGenerateSteps != 0) {
+        size_t numberOfSteps = (d_templatestop - d_templatecounter) / d_templatestep;
+        if (numberOfSteps > d_maxGenerateSteps) {
+          throw exception("The number of $GENERATE steps (" + std::to_string(numberOfSteps) + ") is too high, the maximum is set to " + std::to_string(d_maxGenerateSteps));
+        }
+      }
       d_templateline=d_line;
       parts.pop_front();
       parts.pop_front();
index 36d58f9b39a66d668671d0572cd7faaeafc0b0bf..f20845371d303b40dc4466cec9118185581aea6f 100644 (file)
@@ -41,6 +41,10 @@ public:
   DNSName getZoneName();
   string getLineOfFile(); // for error reporting purposes
   pair<string,int> getLineNumAndFile(); // idem
+  void setMaxGenerateSteps(size_t max)
+  {
+    d_maxGenerateSteps = max;
+  }
 private:
   bool getLine();
   bool getTemplateLine();
@@ -63,6 +67,7 @@ private:
   vector<string>::iterator d_zonedataline;
   std::stack<filestate> d_filestates;
   parts_t d_templateparts;
+  size_t d_maxGenerateSteps{0};
   int d_defaultttl;
   uint32_t d_templatecounter, d_templatestop, d_templatestep;
   bool d_havedollarttl;