]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
Add limits to the size of received {A,I}XFR, in megabytes 4133/head
authorRemi Gacogne <remi.gacogne@powerdns.com>
Thu, 7 Jul 2016 12:34:31 +0000 (14:34 +0200)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Thu, 7 Jul 2016 12:34:31 +0000 (14:34 +0200)
This prevents memory exhaustion in case the master is sending a
very large amount of data in an update.

12 files changed:
docs/markdown/authoritative/settings.md
docs/markdown/recursor/settings.md
pdns/common_startup.cc
pdns/ixfr.cc
pdns/ixfr.hh
pdns/rec-lua-conf.cc
pdns/reczones.cc
pdns/resolver.cc
pdns/resolver.hh
pdns/rpzloader.cc
pdns/rpzloader.hh
pdns/slavecommunicator.cc

index 269d4747b182e9517cb19e2551a6a3563b7bcc7c..2ee242779e9e87a765de4115e9b6b74ce6683274 100644 (file)
@@ -874,3 +874,10 @@ If the webserver should print arguments. See ["Performance Monitoring"](../commo
 * Default: yes
 
 If a PID file should be written. Available since 4.0.
+
+## `xfr-max-received-mbytes`
+* Integer
+* Default: 100
+
+Specifies the maximum number of received megabytes allowed on an incoming AXFR/IXFR update, to prevent
+resource exhaustion. A value of 0 means no restriction.
index b812d31b486d60c5832f1479fad6c74eb5e2a7be..fea43026905c9cc8a5dde77cba2a0913b397d3c2 100644 (file)
@@ -472,6 +472,8 @@ In addition to those, `rpzMaster` accepts:
 * tsigalgo = the name of the TSIG algorithm (like 'hmac-md5') used
 * tsigsecret = base64 encoded TSIG secret
 * refresh = an integer describing the interval between checks for updates. By default, the RPZ zone's default is used
+* maxReceivedMBytes = the maximum size in megabytes of an AXFR/IXFR update, to prevent resource exhaustion.
+The default value of 0 means no restriction.
 
 If no settings are included, the RPZ is taken literally with no overrides applied.
 
index c783d9a7479f3b1c927608ed53303dd8cb1e7a7c..5ef6a0053e405e18b31fe87f25aadb69ddb8639d 100644 (file)
@@ -187,6 +187,8 @@ void declareArguments()
 
   ::arg().setSwitch("outgoing-axfr-expand-alias", "Expand ALIAS records during outgoing AXFR")="no";
   ::arg().setSwitch("8bit-dns", "Allow 8bit dns queries")="no";
+
+  ::arg().set("xfr-max-received-mbytes", "Maximum number of megabytes received from an incoming XFR")="100";
 }
 
 static time_t s_start=time(0);
index 06d9fe5a9f6129ccd177c57d9b333a3fb3739187..dc87f74fbee3ba0e9c97966dfd6637721b59b559 100644 (file)
@@ -7,7 +7,7 @@
 
 // Returns pairs of "remove & add" vectors. If you get an empty remove, it means you got an AXFR!
 vector<pair<vector<DNSRecord>, vector<DNSRecord> > > getIXFRDeltas(const ComboAddress& master, const DNSName& zone, const DNSRecord& oursr, 
-                                                                   const TSIGTriplet& tt, const ComboAddress* laddr)
+                                                                   const TSIGTriplet& tt, const ComboAddress* laddr, size_t maxReceivedBytes)
 {
   vector<pair<vector<DNSRecord>, vector<DNSRecord> > >  ret;
   vector<uint8_t> packet;
@@ -55,6 +55,7 @@ vector<pair<vector<DNSRecord>, vector<DNSRecord> > > getIXFRDeltas(const ComboAd
   // CURRENT MASTER SOA 
   shared_ptr<SOARecordContent> masterSOA;
   vector<DNSRecord> records;
+  size_t receivedBytes = 0;
   for(;;) {
     if(s.read((char*)&len, 2)!=2)
       break;
@@ -62,8 +63,13 @@ vector<pair<vector<DNSRecord>, vector<DNSRecord> > > getIXFRDeltas(const ComboAd
     //    cout<<"Got chunk of "<<len<<" bytes"<<endl;
     if(!len)
       break;
+
+    if (maxReceivedBytes > 0 && (maxReceivedBytes - receivedBytes) < (size_t) len)
+      throw std::runtime_error("Reached the maximum number of received bytes in an IXFR delta for zone '"+zone.toString()+"' from master '"+master.toStringWithPort());
+
     char reply[len]; 
     readn2(s.getHandle(), reply, len);
+    receivedBytes += len;
     MOADNSParser mdp(string(reply, len));
     if(mdp.d_header.rcode) 
       throw std::runtime_error("Got an error trying to IXFR zone '"+zone.toString()+"' from master '"+master.toStringWithPort()+"': "+RCode::to_s(mdp.d_header.rcode));
index 543f4454a2b91273c13f7904f46d1e2a81667dff..eef2252247f0ac5464171bef2bb5d5cc197aafa4 100644 (file)
@@ -4,4 +4,4 @@
 
 vector<pair<vector<DNSRecord>, vector<DNSRecord> > >   getIXFRDeltas(const ComboAddress& master, const DNSName& zone, 
                                                                      const DNSRecord& sr, const TSIGTriplet& tt=TSIGTriplet(),
-                                                                     const ComboAddress* laddr=0);
+                                                                     const ComboAddress* laddr=0, size_t maxReceivedBytes=0);
index 0c1474ed588c81da2b82a3387f7a06604f551c33..e6c07b0e311919451b629c65cc408eae9d3eaf41 100644 (file)
@@ -130,6 +130,7 @@ void loadRecursorLuaConfig(const std::string& fname)
         TSIGTriplet tt;
         int refresh=0;
        std::string polName;
+       size_t maxReceivedXFRMBytes = 0;
        if(options) {
          auto& have = *options;
          if(have.count("policyName")) {
@@ -163,14 +164,17 @@ void loadRecursorLuaConfig(const std::string& fname)
           if(have.count("refresh")) {
             refresh = boost::get<int>(constGet(have,"refresh"));
           }
+          if(have.count("maxReceivedMBytes")) {
+            maxReceivedXFRMBytes = static_cast<size_t>(boost::get<int>(constGet(have,"maxReceivedMBytes")));
+          }
        }
        ComboAddress master(master_, 53);
        DNSName zone(zone_);
 
-       auto sr=loadRPZFromServer(master, zone, lci.dfe, polName, defpol, 0, tt);
+       auto sr=loadRPZFromServer(master, zone, lci.dfe, polName, defpol, 0, tt, maxReceivedXFRMBytes * 1024 * 1024);
         if(refresh)
           sr->d_st.refresh=refresh;
-       std::thread t(RPZIXFRTracker, master, zone, polName, tt, sr);
+       std::thread t(RPZIXFRTracker, master, zone, polName, tt, sr, maxReceivedXFRMBytes * 1024 * 1024);
        t.detach();
       }
       catch(std::exception& e) {
index 74df7ec8e161ceee296eb5597cd5cbafd7263599..3cd3c9e5b8250bd64c2a07894ad8ced126691123 100644 (file)
@@ -311,7 +311,7 @@ string reloadAuthAndForwards()
 }
 
 
-void RPZIXFRTracker(const ComboAddress& master, const DNSName& zone, const std::string& polName, const TSIGTriplet& tt, shared_ptr<SOARecordContent> oursr)
+void RPZIXFRTracker(const ComboAddress& master, const DNSName& zone, const std::string& polName, const TSIGTriplet& tt, shared_ptr<SOARecordContent> oursr, size_t maxReceivedBytes)
 {
   int refresh = oursr->d_st.refresh;
   for(;;) {
@@ -323,7 +323,7 @@ void RPZIXFRTracker(const ComboAddress& master, const DNSName& zone, const std::
     L<<Logger::Info<<"Getting IXFR deltas for "<<zone<<" from "<<master.toStringWithPort()<<", our serial: "<<getRR<SOARecordContent>(dr)->d_st.serial<<endl;
     vector<pair<vector<DNSRecord>, vector<DNSRecord> > > deltas;
     try {
-      deltas = getIXFRDeltas(master, zone, dr, tt);
+      deltas = getIXFRDeltas(master, zone, dr, tt, nullptr, maxReceivedBytes);
     } catch(std::runtime_error& e ){
       L<<Logger::Warning<<e.what()<<endl;
       continue;
index 0220e157366f2976ed3ba36276027fdaa53e3351..b50dc91a6fa03a544f6602fadd34033f56fdd468 100644 (file)
@@ -360,8 +360,9 @@ void Resolver::getSoaSerial(const string &ipport, const DNSName &domain, uint32_
 AXFRRetriever::AXFRRetriever(const ComboAddress& remote,
                              const DNSName& domain,
                              const TSIGTriplet& tt, 
-                             const ComboAddress* laddr)
-  : d_tt(tt), d_tsigPos(0), d_nonSignedMessages(0)
+                             const ComboAddress* laddr,
+                             size_t maxReceivedBytes)
+  : d_tt(tt), d_receivedBytes(0), d_maxReceivedBytes(maxReceivedBytes), d_tsigPos(0), d_nonSignedMessages(0)
 {
   ComboAddress local;
   if (laddr != NULL) {
@@ -445,8 +446,14 @@ int AXFRRetriever::getChunk(Resolver::res_t &res, vector<DNSRecord>* records) //
   int len=getLength();
   if(len<0)
     throw ResolverException("EOF trying to read axfr chunk from remote TCP client");
-  
-  timeoutReadn(len); 
+
+  if (d_maxReceivedBytes > 0 && (d_maxReceivedBytes - d_receivedBytes) < (size_t) len)
+    throw ResolverException("Reached the maximum number of received bytes during AXFR");
+
+  timeoutReadn(len);
+
+  d_receivedBytes += (uint16_t) len;
+
   MOADNSParser mdp(d_buf.get(), len);
 
   int err;
index 24e45454db998870ee1d79b21d9c49166f7e1155..03fb4fdcb8a41ffbbc2523a004a7ba7c5520437a 100644 (file)
@@ -86,8 +86,9 @@ class AXFRRetriever : public boost::noncopyable
     AXFRRetriever(const ComboAddress& remote,
                   const DNSName& zone,
                   const TSIGTriplet& tt = TSIGTriplet(),
-                  const ComboAddress* laddr = NULL);
-       ~AXFRRetriever();
+                  const ComboAddress* laddr = NULL,
+                  size_t maxReceivedBytes=0);
+    ~AXFRRetriever();
     int getChunk(Resolver::res_t &res, vector<DNSRecord>* records=0);  
   
   private:
@@ -104,6 +105,8 @@ class AXFRRetriever : public boost::noncopyable
     TSIGTriplet d_tt;
     string d_prevMac; // RFC2845 4.4
     string d_signData;
+    size_t d_receivedBytes;
+    size_t d_maxReceivedBytes;
     uint32_t d_tsigPos;
     uint d_nonSignedMessages; // RFC2845 4.4
     TSIGRecordContent d_trc;
index 90a2dace320e6bd7118ffe071425dd9df89f8a44..31c5e6cd3a3122aa3f56596f9125abd42d52e8df 100644 (file)
@@ -105,14 +105,14 @@ void RPZRecordToPolicy(const DNSRecord& dr, DNSFilterEngine& target, const std::
   }
 }
 
-shared_ptr<SOARecordContent> loadRPZFromServer(const ComboAddress& master, const DNSName& zone, DNSFilterEngine& target, const std::string& polName, boost::optional<DNSFilterEngine::Policy> defpol, int place,  const TSIGTriplet& tt)
+shared_ptr<SOARecordContent> loadRPZFromServer(const ComboAddress& master, const DNSName& zone, DNSFilterEngine& target, const std::string& polName, boost::optional<DNSFilterEngine::Policy> defpol, int place,  const TSIGTriplet& tt, size_t maxReceivedBytes)
 {
   L<<Logger::Warning<<"Loading RPZ zone '"<<zone<<"' from "<<master.toStringWithPort()<<endl;
   if(!tt.name.empty())
     L<<Logger::Warning<<"With TSIG key '"<<tt.name<<"' of algorithm '"<<tt.algo<<"'"<<endl;
 
   ComboAddress local= master.sin4.sin_family == AF_INET ? ComboAddress("0.0.0.0") : ComboAddress("::"); // should be configurable
-  AXFRRetriever axfr(master, zone, tt, &local);
+  AXFRRetriever axfr(master, zone, tt, &local, maxReceivedBytes);
   unsigned int nrecords=0;
   Resolver::res_t nop;
   vector<DNSRecord> chunk;
index c61993ae4f842d79aab2e6a4fca8e5fa0daeb028..031e82795f8bbdbe1020c750137c5e6f7ff7dbed 100644 (file)
@@ -4,6 +4,6 @@
 #include "dnsrecords.hh"
 
 int loadRPZFromFile(const std::string& fname, DNSFilterEngine& target, const std::string& policyName, boost::optional<DNSFilterEngine::Policy> defpol, int place);
-std::shared_ptr<SOARecordContent> loadRPZFromServer(const ComboAddress& master, const DNSName& zone, DNSFilterEngine& target, const std::string& policyName, boost::optional<DNSFilterEngine::Policy> defpol, int place, const TSIGTriplet& tt);
+std::shared_ptr<SOARecordContent> loadRPZFromServer(const ComboAddress& master, const DNSName& zone, DNSFilterEngine& target, const std::string& policyName, boost::optional<DNSFilterEngine::Policy> defpol, int place, const TSIGTriplet& tt, size_t maxReceivedBytes);
 void RPZRecordToPolicy(const DNSRecord& dr, DNSFilterEngine& target, const std::string& policyName, bool addOrRemove, boost::optional<DNSFilterEngine::Policy> defpol, int place);
-void RPZIXFRTracker(const ComboAddress& master, const DNSName& zone, const std::string& policyName, const TSIGTriplet &tt, shared_ptr<SOARecordContent> oursr);
+void RPZIXFRTracker(const ComboAddress& master, const DNSName& zone, const std::string& policyName, const TSIGTriplet &tt, shared_ptr<SOARecordContent> oursr, size_t maxReceivedBytes);
index 9562c21d48cb4c46e57163b2be80c245fddb5f8b..2828a7289ac2d397368499db3879e0682bafb169 100644 (file)
@@ -103,7 +103,7 @@ void CommunicatorClass::ixfrSuck(const DNSName &domain, const TSIGTriplet& tt, c
 
     DNSRecord dr;
     dr.d_content = std::make_shared<SOARecordContent>(DNSName("."), DNSName("."), st);
-    auto deltas = getIXFRDeltas(remote, domain, dr, tt, laddr.sin4.sin_family ? &laddr : 0);
+    auto deltas = getIXFRDeltas(remote, domain, dr, tt, laddr.sin4.sin_family ? &laddr : 0, ((size_t) ::arg().asNum("xfr-max-received-mbytes")) * 1024 * 1024);
     zs.numDeltas=deltas.size();
     //    cout<<"Got "<<deltas.size()<<" deltas from serial "<<di.serial<<", applying.."<<endl;
     
@@ -235,7 +235,7 @@ static bool processRecordForZS(const DNSName& domain, bool& firstNSEC3, DNSResou
 vector<DNSResourceRecord> doAxfr(const ComboAddress& raddr, const DNSName& domain, const TSIGTriplet& tt, const ComboAddress& laddr,  scoped_ptr<AuthLua>& pdl, ZoneStatus& zs)
 {
   vector<DNSResourceRecord> rrs;
-  AXFRRetriever retriever(raddr, domain, tt, (laddr.sin4.sin_family == 0) ? NULL : &laddr);
+  AXFRRetriever retriever(raddr, domain, tt, (laddr.sin4.sin_family == 0) ? NULL : &laddr, ((size_t) ::arg().asNum("xfr-max-received-mbytes")) * 1024 * 1024);
   Resolver::res_t recs;
   bool first=true;
   bool firstNSEC3{true};