]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
Limit #include depth
authorOtto Moerbeek <otto@drijf.net>
Wed, 20 Oct 2021 09:47:36 +0000 (11:47 +0200)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Tue, 26 Oct 2021 15:06:30 +0000 (17:06 +0200)
17 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/recursordist/rec-zonetocache.cc
pdns/reczones.cc
pdns/rpzloader.cc
pdns/test-zoneparser_tng_cc.cc
pdns/ws-auth.cc
pdns/zone2json.cc
pdns/zone2ldap.cc
pdns/zone2sql.cc
pdns/zoneparser-tng.cc
pdns/zoneparser-tng.hh

index e983d43195865be7809dfe796b9f638a381f5004..308a505d9520346423960d2ecd461d4ba71f224c 100644 (file)
@@ -1011,6 +1011,17 @@ 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-include-depth:
+
+``max-include-depth``
+----------------------
+
+-  Integer
+-  Default: 20
+
+Maximum number of nested ``$INCLUDE`` directives while processing a zone file.
+Zero mean no ``$INCLUDE`` directives will be accepted.
+
 .. _setting-max-generate-steps:
 
 ``max-generate-steps``
index 216b1b9cb3e227dfc732839e081765e56d38235b..54d9c612ba5f0ea7e95ab5b705793cdb0f166c45 100644 (file)
@@ -503,6 +503,7 @@ void Bind2Backend::parseZoneFile(BB2DomainInfo* bbd)
   auto records = std::make_shared<recordstorage_t>();
   ZoneParserTNG zpt(bbd->d_filename, bbd->d_name, s_binddirectory, d_upgradeContent);
   zpt.setMaxGenerateSteps(::arg().asNum("max-generate-steps"));
+  zpt.setMaxIncludes(::arg().asNum("max-include-depth"));
   DNSResourceRecord rr;
   string hashed;
   while (zpt.get(rr)) {
index 261de435b1bfb37d1576f7238a5df394d5267aa8..d40a74a036c50aa4c9e2e782822fee7677720479 100644 (file)
@@ -254,6 +254,7 @@ 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("max-include-depth", "Maximum number of nested $INCLUDE directives while processing a zone file")="20";
   ::arg().setSwitch("upgrade-unknown-types","Transparently upgrade known TYPExxx records. Recommended to keep off, except for PowerDNS upgrades until data sources are cleaned up")="no";
   ::arg().setSwitch("svc-autohints", "Transparently fill ipv6hint=auto ipv4hint=auto SVC params with AAAA/A records for the target name of the record (if within the same zone)")="no";
 
index 3285860a9d1669b284ff95a25e50a2c8d404afdb..98e0d5ce383c9ae4b6e350a4f4eae987607efb6e 100644 (file)
@@ -51,6 +51,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
     ZoneParserTNG zpt(lines, g_rootdnsname);
     /* limit the number of steps for '$GENERATE' entries */
     zpt.setMaxGenerateSteps(10000);
+    zpt.setMaxIncludes(20);
     DNSResourceRecord drr;
     while (zpt.get(drr)) {
     }
index 871dbf7f4484c8e7fa81934dfe9cb58b9c9340a3..f6e483e733b9d8c3fd8b428e9b103b1bd4b4bb6f 100644 (file)
@@ -5992,6 +5992,7 @@ int main(int argc, char **argv)
     ::arg().setSwitch("qname-minimization", "Use Query Name Minimization")="yes";
     ::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)")="dnssec";
     ::arg().set("max-generate-steps", "Maximum number of $GENERATE steps when loading a zone from a file")="0";
+    ::arg().set("max-include-depth", "Maximum nested $INCLUDE depth when loading a zone from a file")="20";
     ::arg().set("record-cache-shards", "Number of shards in the record cache")="1024";
     ::arg().set("refresh-on-ttl-perc", "If a record is requested from the cache and only this % of original TTL remains, refetch") = "0";
 
index 8da10873ca82bcde3d323eac350088aeda0d5c54..97b21483bb8f55233990733ba342f7bbfa905fd0 100644 (file)
@@ -108,6 +108,7 @@ static void loadMainConfig(const std::string& configdir)
   ::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().set("max-include-depth", "Maximum nested $INCLUDE depth when loading a zone from a file")="20";
   ::arg().setSwitch("upgrade-unknown-types","Transparently upgrade known TYPExxx records. Recommended to keep off, except for PowerDNS upgrades until data sources are cleaned up")="no";
   ::arg().laxFile(configname.c_str());
 
@@ -1190,6 +1191,7 @@ static int editZone(const DNSName &zone) {
   cmdline.clear();
   ZoneParserTNG zpt(tmpnam, g_rootdnsname);
   zpt.setMaxGenerateSteps(::arg().asNum("max-generate-steps"));
+  zpt.setMaxIncludes(::arg().asNum("max-include-depth"));
   DNSResourceRecord zrr;
   map<pair<DNSName,uint16_t>, vector<DNSRecord> > grouped;
   try {
index 8842288a9231716bda32f15f254c0f6cda6459f0..804436da57304777fec9836e76f366edbc5c0add 100644 (file)
@@ -1044,6 +1044,17 @@ 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-include-depth:
+
+``max-include-depth``
+----------------------
+
+-  Integer
+-  Default: 20
+
+Maximum number of nested ``$INCLUDE`` directives while processing a zone file.
+Zero mean no ``$INCLUDE`` directives will be accepted.
+
 .. _setting-max-generate-steps:
 
 ``max-generate-steps``
index c55168e148104da1b8418f10264af3031f223364..fa3894bb9c5763a1df620138009c44197f4b79b4 100644 (file)
@@ -218,6 +218,7 @@ void ZoneData::ZoneToCache(const RecZoneToCache::Config& config, uint64_t config
     DNSResourceRecord drr;
     ZoneParserTNG zpt(lines, d_zone);
     zpt.setMaxGenerateSteps(1);
+    zpt.setMaxIncludes(0);
 
     while (zpt.get(drr)) {
       DNSRecord dr(drr);
index a5430c85157e0ab90edec1cc7dcfa8e52a852ef9..560f204f8989b4aa43f55735142ac82e7609823c 100644 (file)
@@ -99,6 +99,7 @@ bool primeHints(time_t ignored)
   else {
     ZoneParserTNG zpt(::arg()["hint-file"]);
     zpt.setMaxGenerateSteps(::arg().asNum("max-generate-steps"));
+    zpt.setMaxIncludes(::arg().asNum("max-include-depth"));
     DNSResourceRecord rr;
     set<DNSName> seenNS;
     set<DNSName> seenA;
@@ -399,6 +400,7 @@ std::shared_ptr<SyncRes::domainmap_t> parseAuthAndForwards()
         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"));
+        zpt.setMaxIncludes(::arg().asNum("max-include-depth"));
         DNSResourceRecord rr;
         DNSRecord dr;
         while (zpt.get(rr)) {
index 5fa63ed22caef7a9f03d986c91171cdf0bec4e60..03d8228126289c00bdcc2239f258cdfb8c821b83 100644 (file)
@@ -286,6 +286,7 @@ std::shared_ptr<SOARecordContent> loadRPZFromFile(const std::string& fname, std:
   shared_ptr<SOARecordContent> sr = nullptr;
   ZoneParserTNG zpt(fname);
   zpt.setMaxGenerateSteps(::arg().asNum("max-generate-steps"));
+  zpt.setMaxIncludes(::arg().asNum("max-include-depth"));
   DNSResourceRecord drr;
   DNSName domain;
   while(zpt.get(drr)) {
index d435b503a8d45582e55534230b7d58aa3253550d..751645b2289904815d84fb42d83f47f592684342 100644 (file)
@@ -166,20 +166,33 @@ BOOST_AUTO_TEST_CASE(test_tng_record_generate) {
     BOOST_CHECK_THROW(zp.get(rr), std::exception);
   }
 
-   {
+  {
     /* test invalid generate parameters: negative counter */
     ZoneParserTNG zp(std::vector<std::string>({"$GENERATE -1-4/1 $.${1,2,o}.${3,4,d}.${5,6,X}.${7,8,x} 86400   IN      A 1.2.3.4"}), DNSName("test"));
     DNSResourceRecord rr;
     BOOST_CHECK_THROW(zp.get(rr), std::exception);
   }
+  {
+    /* test invalid generate parameters: counter out of bounds */
+    ZoneParserTNG zp(std::vector<std::string>({"$GENERATE 4294967296-4/1 $.${1,2,o}.${3,4,d}.${5,6,X}.${7,8,x} 86400   IN      A 1.2.3.4"}), DNSName("test"));
+    DNSResourceRecord rr;
+    BOOST_CHECK_THROW(zp.get(rr), std::exception);
+  }
 
-   {
+  {
     /* test invalid generate parameters: negative stop */
     ZoneParserTNG zp(std::vector<std::string>({"$GENERATE 0--4/1 $.${1,2,o}.${3,4,d}.${5,6,X}.${7,8,x} 86400   IN      A 1.2.3.4"}), DNSName("test"));
     DNSResourceRecord rr;
     BOOST_CHECK_THROW(zp.get(rr), std::exception);
   }
 
+  {
+    /* test invalid generate parameters: stop out of bounds */
+    ZoneParserTNG zp(std::vector<std::string>({"$GENERATE 0-4294967296/1 $.${1,2,o}.${3,4,d}.${5,6,X}.${7,8,x} 86400   IN      A 1.2.3.4"}), DNSName("test"));
+    DNSResourceRecord rr;
+    BOOST_CHECK_THROW(zp.get(rr), std::exception);
+  }
+
   {
     /* test invalid generate parameters: negative step */
     ZoneParserTNG zp(std::vector<std::string>({"$GENERATE 0-4/-1 $.${1,2,o}.${3,4,d}.${5,6,X}.${7,8,x} 86400   IN      A 1.2.3.4"}), DNSName("test"));
index d1d06b037a20930713802e358f6a9f29b52374b8..118d34c46284c6572f3563645d6c439acbc02bb7 100644 (file)
@@ -1385,6 +1385,7 @@ static void gatherRecordsFromZone(const std::string& zonestring, vector<DNSResou
 
   ZoneParserTNG zpt(zonedata, zonename);
   zpt.setMaxGenerateSteps(::arg().asNum("max-generate-steps"));
+  zpt.setMaxIncludes(::arg().asNum("max-include-depth"));
 
   bool seenSOA=false;
 
index 9da32bf5d54f9141f713ed0865b691e1225686ed..f2e0b9d394f6a6a09999dacdef11a8bb84e1c786 100644 (file)
@@ -104,6 +104,7 @@ try
     ::arg().set("zone-name","Specify an $ORIGIN in case it is not present")="";
     ::arg().set("named-conf","Bind 8/9 named.conf to parse")="";
     ::arg().set("max-generate-steps", "Maximum number of $GENERATE steps when loading a zone from a file")="0";
+    ::arg().set("max-include-depth", "Maximum level of nested $INCLUDE depth when loading a zone from a file")="20";
 
     ::arg().setCmd("help","Provide a helpful message");
     ::arg().setCmd("version","Print the version");
@@ -171,6 +172,7 @@ try
             Json::array recs;
             ZoneParserTNG zpt(i->filename, i->name, BP.getDirectory());
             zpt.setMaxGenerateSteps(::arg().asNum("max-generate-steps"));
+            zpt.setMaxIncludes(::arg().asNum("max-include-depth"));
             DNSResourceRecord rr;
             obj["name"] = i->name.toString();
 
index b1f3434da671f2248f8f41cfe5ef76ea7f911a96..28039bf48e370f3c270a0561401cc5581e26f632 100644 (file)
@@ -249,6 +249,7 @@ int main( int argc, char* argv[] )
                 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.set( "max-include-depth", "Maximum nested $INCLUDE depth when loading a zone from a file")="20";
 
                 args.parse( argc, argv );
 
@@ -318,6 +319,7 @@ int main( int argc, char* argv[] )
                                                 g_zonename = i.name;
                                                 ZoneParserTNG zpt(i.filename, i.name, BP.getDirectory());
                                                 zpt.setMaxGenerateSteps(args.asNum("max-generate-steps"));
+                                                zpt.setMaxIncludes(args.asNum("max-include-depth"));
                                                 DNSResourceRecord rr;
                                                 while(zpt.get(rr)) {
                                                         callback(g_domainid, rr.qname, rr.qtype.toString(), encode_non_ascii(rr.content), rr.ttl);
index 505fd1dfd7f518d8c7bd0fc7870dbe6d98008806..51f18b91c0299a9476751d64ba3316999a2675fe 100644 (file)
@@ -213,6 +213,7 @@ try
     ::arg().set("named-conf","Bind 8/9 named.conf to parse")="";
 
     ::arg().set("max-generate-steps", "Maximum number of $GENERATE steps when loading a zone from a file")="0";
+    ::arg().set("max-include-depth", "Maximum nested $INCLUDE depth when loading a zone from a file")="20";
 
     ::arg().setCmd("help","Provide a helpful message");
     ::arg().setCmd("version","Print the version");
@@ -295,6 +296,7 @@ try
             
             ZoneParserTNG zpt(domain.filename, domain.name, BP.getDirectory());
             zpt.setMaxGenerateSteps(::arg().asNum("max-generate-steps"));
+            zpt.setMaxIncludes(::arg().asNum("max-include-depth"));
             DNSResourceRecord rr;
             bool seenSOA=false;
             string comment;
index 701bbbf91a3fec7172db760d7973db76056514f3..ee0741f862bc476cb66a0f360ea2bfea8b95d9af 100644 (file)
@@ -36,8 +36,9 @@
 #include <boost/algorithm/string.hpp>
 #include <system_error>
 #include <cinttypes>
+#include <sys/stat.h>
 
-static string g_INstr("IN");
+const static string g_INstr("IN");
 
 ZoneParserTNG::ZoneParserTNG(const string& fname, DNSName  zname, string  reldir, bool upgradeContent):
   d_reldir(std::move(reldir)), d_zonename(std::move(zname)), d_defaultttl(3600), 
@@ -57,10 +58,34 @@ ZoneParserTNG::ZoneParserTNG(const vector<string>& zonedata, DNSName  zname, boo
 
 void ZoneParserTNG::stackFile(const std::string& fname)
 {
-  FILE *fp=fopen(fname.c_str(), "r");
-  if(!fp) {
-    std::error_code ec (errno,std::generic_category());
-    throw std::system_error(ec, "Unable to open file '"+fname+"': "+stringerror());
+  if (d_filestates.size() >= d_maxIncludes) {
+    std::error_code ec (0, std::generic_category());
+    throw std::system_error(ec, "Include limit reached");
+  }
+  int fd = open(fname.c_str(), O_RDONLY, 0);
+  if (fd == -1) {
+    int err = errno;
+    std::error_code ec (err, std::generic_category());
+    throw std::system_error(ec, "Unable to open file '" + fname + "': " + stringerror(err));
+  }
+  struct stat st;
+  if (fstat(fd, &st) == -1) {
+    int err = errno;
+    close(fd);
+    std::error_code ec (err, std::generic_category());
+    throw std::system_error(ec, "Unable to stat file '" + fname + "': " + stringerror(err));
+  }
+  if (!S_ISREG(st.st_mode)) {
+    close(fd);
+    std::error_code ec (0, std::generic_category());
+    throw std::system_error(ec, "File '" + fname + "': not a regular file");
+  }
+  FILE *fp = fdopen(fd, "r");
+  if (!fp) {
+    int err = errno;
+    close(fd);
+    std::error_code ec (err, std::generic_category());
+    throw std::system_error(ec, "Unable to open file '" + fname + "': " + stringerror(err));
   }
 
   filestate fs(fp, fname);
index fd4464738fe5b768a0754970301ec0e2cb2a82fb..1b7562766a275b6b5643baa678d702805e12528e 100644 (file)
@@ -49,6 +49,10 @@ public:
   {
     d_maxGenerateSteps = max;
   }
+  void setMaxIncludes(size_t max)
+  {
+    d_maxIncludes = max;
+  }
 private:
   bool getLine();
   bool getTemplateLine();
@@ -73,6 +77,7 @@ private:
   std::stack<filestate> d_filestates;
   parts_t d_templateparts;
   size_t d_maxGenerateSteps{0};
+  size_t d_maxIncludes{20};
   int d_defaultttl;
   uint32_t d_templatecounter, d_templatestop, d_templatestep;
   bool d_havedollarttl;