pdns-recursor: add patch for CVE-2022-27227
authorPeter van Dijk <peter.van.dijk@powerdns.com>
Thu, 14 Apr 2022 17:57:58 +0000 (19:57 +0200)
committerRosen Penev <rosenp@gmail.com>
Fri, 15 Apr 2022 01:54:40 +0000 (18:54 -0700)
Signed-off-by: Peter van Dijk <peter.van.dijk@powerdns.com>
net/pdns-recursor/patches/300-cve-2022-27227.patch [new file with mode: 0644]

diff --git a/net/pdns-recursor/patches/300-cve-2022-27227.patch b/net/pdns-recursor/patches/300-cve-2022-27227.patch
new file mode 100644 (file)
index 0000000..4cc9951
--- /dev/null
@@ -0,0 +1,171 @@
+--- a/ixfr.cc
++++ b/ixfr.cc
+@@ -123,7 +123,7 @@ vector<pair<vector<DNSRecord>, vector<DN
+ }
+ // 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, 
++vector<pair<vector<DNSRecord>, vector<DNSRecord> > > getIXFRDeltas(const ComboAddress& primary, const DNSName& zone, const DNSRecord& oursr, 
+                                                                    const TSIGTriplet& tt, const ComboAddress* laddr, size_t maxReceivedBytes)
+ {
+   vector<pair<vector<DNSRecord>, vector<DNSRecord> > >  ret;
+@@ -137,7 +137,7 @@ vector<pair<vector<DNSRecord>, vector<DN
+   pw.commit();
+   TSIGRecordContent trc;
+-  TSIGTCPVerifier tsigVerifier(tt, master, trc);
++  TSIGTCPVerifier tsigVerifier(tt, primary, trc);
+   if(!tt.algo.empty()) {
+     TSIGHashEnum the;
+     getTSIGHashEnum(tt.algo, the);
+@@ -156,11 +156,11 @@ vector<pair<vector<DNSRecord>, vector<DN
+   string msg((const char*)&len, 2);
+   msg.append((const char*)&packet[0], packet.size());
+-  Socket s(master.sin4.sin_family, SOCK_STREAM);
++  Socket s(primary.sin4.sin_family, SOCK_STREAM);
+   //  cout<<"going to connect"<<endl;
+   if(laddr)
+     s.bind(*laddr);
+-  s.connect(master);
++  s.connect(primary);
+   //  cout<<"Connected"<<endl;
+   s.writen(msg);
+@@ -171,16 +171,24 @@ vector<pair<vector<DNSRecord>, vector<DN
+   //   SOA WHERE THIS DELTA GOES
+   //   RECORDS TO ADD
+   // CURRENT MASTER SOA 
+-  std::shared_ptr<SOARecordContent> masterSOA = nullptr;
++  std::shared_ptr<SOARecordContent> primarySOA = nullptr;
+   vector<DNSRecord> records;
+   size_t receivedBytes = 0;
+-  int8_t ixfrInProgress = -2;
+   std::string reply;
++  enum transferStyle { Unknown, AXFR, IXFR } style = Unknown;
++  const unsigned int expectedSOAForAXFR = 2;
++  const unsigned int expectedSOAForIXFR = 3;
++  unsigned int primarySOACount = 0;
++
+   for(;;) {
+-    // IXFR end
+-    if (ixfrInProgress >= 0)
++    // IXFR or AXFR style end reached? We don't want to process trailing data after the closing SOA
++    if (style == AXFR && primarySOACount == expectedSOAForAXFR) {
++      break;
++    }
++    else if (style == IXFR && primarySOACount == expectedSOAForIXFR) {
+       break;
++    }
+     if(s.read((char*)&len, sizeof(len)) != sizeof(len))
+       break;
+@@ -191,7 +199,7 @@ vector<pair<vector<DNSRecord>, vector<DN
+       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.toLogString()+"' from master "+master.toStringWithPort());
++      throw std::runtime_error("Reached the maximum number of received bytes in an IXFR delta for zone '"+zone.toLogString()+"' from primary "+primary.toStringWithPort());
+     reply.resize(len);
+     readn2(s.getHandle(), &reply.at(0), len);
+@@ -199,7 +207,7 @@ vector<pair<vector<DNSRecord>, vector<DN
+     MOADNSParser mdp(false, reply);
+     if(mdp.d_header.rcode) 
+-      throw std::runtime_error("Got an error trying to IXFR zone '"+zone.toLogString()+"' from master '"+master.toStringWithPort()+"': "+RCode::to_s(mdp.d_header.rcode));
++      throw std::runtime_error("Got an error trying to IXFR zone '"+zone.toLogString()+"' from primary '"+primary.toStringWithPort()+"': "+RCode::to_s(mdp.d_header.rcode));
+     //    cout<<"Got a response, rcode: "<<mdp.d_header.rcode<<", got "<<mdp.d_answers.size()<<" answers"<<endl;
+@@ -209,32 +217,47 @@ vector<pair<vector<DNSRecord>, vector<DN
+     for(auto& r: mdp.d_answers) {
+       //      cout<<r.first.d_name<< " " <<r.first.d_content->getZoneRepresentation()<<endl;
+-      if(!masterSOA) {
++      if(!primarySOA) {
+         // we have not seen the first SOA record yet
+         if (r.first.d_type != QType::SOA) {
+-          throw std::runtime_error("The first record of the IXFR answer for zone '"+zone.toLogString()+"' from master '"+master.toStringWithPort()+"' is not a SOA ("+QType(r.first.d_type).getName()+")");
++          throw std::runtime_error("The first record of the IXFR answer for zone '"+zone.toLogString()+"' from primary '"+primary.toStringWithPort()+"' is not a SOA ("+QType(r.first.d_type).getName()+")");
+         }
+         auto sr = getRR<SOARecordContent>(r.first);
+         if (!sr) {
+-          throw std::runtime_error("Error getting the content of the first SOA record of the IXFR answer for zone '"+zone.toLogString()+"' from master '"+master.toStringWithPort()+"'");
++          throw std::runtime_error("Error getting the content of the first SOA record of the IXFR answer for zone '"+zone.toLogString()+"' from primary '"+primary.toStringWithPort()+"'");
+         }
+         if(sr->d_st.serial == std::dynamic_pointer_cast<SOARecordContent>(oursr.d_content)->d_st.serial) {
+           // we are up to date
+           return ret;
+         }
+-        masterSOA = sr;
++        primarySOA = sr;
++        ++primarySOACount;
+       } else if (r.first.d_type == QType::SOA) {
+         auto sr = getRR<SOARecordContent>(r.first);
+         if (!sr) {
+-          throw std::runtime_error("Error getting the content of SOA record of IXFR answer for zone '"+zone.toLogString()+"' from master '"+master.toStringWithPort()+"'");
++          throw std::runtime_error("Error getting the content of SOA record of IXFR answer for zone '"+zone.toLogString()+"' from primary '"+primary.toStringWithPort()+"'");
+         }
+-        // we hit the last SOA record
+-        // IXFR is considered to be done if we hit the last SOA record twice
+-        if (masterSOA->d_st.serial == sr->d_st.serial) {
+-          ixfrInProgress++;
++        // we hit a marker SOA record
++        if (primarySOA->d_st.serial == sr->d_st.serial) {
++          ++primarySOACount;
++        }
++      }
++      // When we see the 2nd record, we can decide what the style is
++      if (records.size() == 1 && style == Unknown) {
++        if (r.first.d_type != QType::SOA) {
++          // Non-empty AXFR style has a non-SOA record following the first SOA
++          style = AXFR;
++        }
++        else if (primarySOACount == expectedSOAForAXFR) {
++          // Empty zone AXFR style: start SOA is immediately followed by end marker SOA
++          style = AXFR;
++        }
++        else {
++          // IXFR has a 2nd SOA (with different serial) following the first
++          style = IXFR;
+         }
+       }
+@@ -245,7 +268,7 @@ vector<pair<vector<DNSRecord>, vector<DN
+         if(r.first.d_type == QType::OPT)
+           continue;
+-        throw std::runtime_error("Unexpected record (" +QType(r.first.d_type).getName()+") in non-answer section ("+std::to_string(r.first.d_place)+")in IXFR response for zone '"+zone.toLogString()+"' from master '"+master.toStringWithPort());
++        throw std::runtime_error("Unexpected record (" +QType(r.first.d_type).getName()+") in non-answer section ("+std::to_string(r.first.d_place)+")in IXFR response for zone '"+zone.toLogString()+"' from primary '"+primary.toStringWithPort());
+       }
+       r.first.d_name.makeUsRelative(zone);
+@@ -253,7 +276,21 @@ vector<pair<vector<DNSRecord>, vector<DN
+     }
+   }
+-  //  cout<<"Got "<<records.size()<<" records"<<endl;
++  switch (style) {
++  case IXFR:
++    if (primarySOACount != expectedSOAForIXFR) {
++      throw std::runtime_error("Incomplete IXFR transfer for '" + zone.toLogString() + "' from primary '" + primary.toStringWithPort());
++    }
++    break;
++  case AXFR:
++    if (primarySOACount != expectedSOAForAXFR){
++      throw std::runtime_error("Incomplete AXFR style transfer for '" + zone.toLogString() + "' from primary '" + primary.toStringWithPort());
++    }
++    break;
++  case Unknown:
++    throw std::runtime_error("Incomplete XFR for '" + zone.toLogString() + "' from primary '" + primary.toStringWithPort());
++    break;
++  }
+-  return processIXFRRecords(master, zone, records, masterSOA);
++  return processIXFRRecords(primary, zone, records, primarySOA);
+ }