]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
Backport of "Prevent updating the status of all cached records for a name" to 4.3.x 9604/head
authorOtto Moerbeek <otto.moerbeek@open-xchange.com>
Mon, 12 Oct 2020 08:13:39 +0000 (10:13 +0200)
committerOtto Moerbeek <otto.moerbeek@open-xchange.com>
Mon, 12 Oct 2020 08:13:39 +0000 (10:13 +0200)
pdns/recursor_cache.cc
pdns/recursor_cache.hh
pdns/recursordist/test-syncres_cc8.cc
pdns/syncres.cc
pdns/validate.cc
pdns/validate.hh

index ca1f8bd1cbe029bc07fcf12ed2de378790bd6417..7e85de2db1a88e92b9df7844550c471c0812c5f1 100644 (file)
@@ -38,7 +38,39 @@ unsigned int MemRecursorCache::bytes() const
   return ret;
 }
 
-int32_t MemRecursorCache::handleHit(MemRecursorCache::OrderedTagIterator_t& entry, const DNSName& qname, const ComboAddress& who, vector<DNSRecord>* res, vector<std::shared_ptr<RRSIGRecordContent>>* signatures, std::vector<std::shared_ptr<DNSRecord>>* authorityRecs, bool* variable, vState* state, bool* wasAuth)
+static void updateDNSSECValidationStateFromCache(boost::optional<vState>& state, const vState stateUpdate)
+{
+  // if there was no state it's easy */
+  if (state == boost::none) {
+    state = stateUpdate;
+    return;
+  }
+
+  if (stateUpdate == vState::TA) {
+    state = vState::Secure;
+  }
+  else if (stateUpdate == vState::NTA) {
+    state = vState::Insecure;
+  }
+  else if (stateUpdate == vState::Bogus) {
+    state = stateUpdate;
+  }
+  else if (stateUpdate == vState::Indeterminate) {
+    state = stateUpdate;
+  }
+  else if (stateUpdate == vState::Insecure) {
+    if (*state != vState::Bogus && *state != vState::Indeterminate) {
+      state = stateUpdate;
+    }
+  }
+  else if (stateUpdate == vState::Secure) {
+    if (*state != vState::Bogus && *state != vState::Indeterminate) {
+      state = stateUpdate;
+    }
+  }
+}
+
+int32_t MemRecursorCache::handleHit(MemRecursorCache::OrderedTagIterator_t& entry, const DNSName& qname, const ComboAddress& who, vector<DNSRecord>* res, vector<std::shared_ptr<RRSIGRecordContent>>* signatures, std::vector<std::shared_ptr<DNSRecord>>* authorityRecs, bool* variable, boost::optional<vState>& state, bool* wasAuth)
 {
   int32_t ttd = entry->d_ttd;
 
@@ -62,20 +94,18 @@ int32_t MemRecursorCache::handleHit(MemRecursorCache::OrderedTagIterator_t& entr
     }
   }
 
-  if(signatures) { // if you do an ANY lookup you are hosed XXXX
-    *signatures = entry->d_signatures;
+  if (signatures) {
+    signatures->insert(signatures->end(), entry->d_signatures.begin(), entry->d_signatures.end());
   }
 
-  if(authorityRecs) {
-    *authorityRecs = entry->d_authorityRecs;
+  if (authorityRecs) {
+    authorityRecs->insert(authorityRecs->end(), entry->d_authorityRecs.begin(), entry->d_authorityRecs.end());
   }
 
-  if (state) {
-    *state = entry->d_state;
-  }
+  updateDNSSECValidationStateFromCache(state, entry->d_state);
 
   if (wasAuth) {
-    *wasAuth = entry->d_auth;
+    *wasAuth = *wasAuth && entry->d_auth;
   }
 
   moveCacheItemToBack<SequencedTag>(d_cache, entry);
@@ -173,6 +203,7 @@ bool MemRecursorCache::entryMatches(MemRecursorCache::OrderedTagIterator_t& entr
 // returns -1 for no hits
 int32_t MemRecursorCache::get(time_t now, const DNSName &qname, const QType& qt, bool requireAuth, vector<DNSRecord>* res, const ComboAddress& who, vector<std::shared_ptr<RRSIGRecordContent>>* signatures, std::vector<std::shared_ptr<DNSRecord>>* authorityRecs, bool* variable, vState* state, bool* wasAuth)
 {
+  boost::optional<vState> cachedState{boost::none};
   time_t ttd=0;
   //  cerr<<"looking up "<< qname<<"|"+qt.getName()<<"\n";
   if(res) {
@@ -180,6 +211,12 @@ int32_t MemRecursorCache::get(time_t now, const DNSName &qname, const QType& qt,
   }
 
   const uint16_t qtype = qt.getCode();
+  if (wasAuth) {
+    // we might retrieve more than one entry, we need to set that to true
+    // so it will be set to false if at least one entry is not auth
+    *wasAuth = true;
+  }
+
   /* If we don't have any netmask-specific entries at all, let's just skip this
      to be able to use the nice d_cachecache hack. */
   if (qtype != QType::ANY && !d_ecsIndex.empty()) {
@@ -188,23 +225,32 @@ int32_t MemRecursorCache::get(time_t now, const DNSName &qname, const QType& qt,
 
       auto entryA = getEntryUsingECSIndex(now, qname, QType::A, requireAuth, who);
       if (entryA != d_cache.end()) {
-        ret = handleHit(entryA, qname, who, res, signatures, authorityRecs, variable, state, wasAuth);
+        ret = handleHit(entryA, qname, who, res, signatures, authorityRecs, variable, cachedState, wasAuth);
       }
       auto entryAAAA = getEntryUsingECSIndex(now, qname, QType::AAAA, requireAuth, who);
       if (entryAAAA != d_cache.end()) {
-        int32_t ttdAAAA = handleHit(entryAAAA, qname, who, res, signatures, authorityRecs, variable, state, wasAuth);
+        int32_t ttdAAAA = handleHit(entryAAAA, qname, who, res, signatures, authorityRecs, variable, cachedState, wasAuth);
         if (ret > 0) {
           ret = std::min(ret, ttdAAAA);
         } else {
           ret = ttdAAAA;
         }
       }
+
+      if (state && cachedState) {
+        *state = *cachedState;
+      }
       return ret > 0 ? static_cast<int32_t>(ret-now) : ret;
     }
     else {
       auto entry = getEntryUsingECSIndex(now, qname, qtype, requireAuth, who);
       if (entry != d_cache.end()) {
-        return static_cast<int32_t>(handleHit(entry, qname, who, res, signatures, authorityRecs, variable, state, wasAuth) - now);
+        int32_t ret = handleHit(entry, qname, who, res, signatures, authorityRecs, variable, cachedState, wasAuth);
+        if (state && cachedState) {
+          *state = *cachedState;
+        }
+        return static_cast<int32_t>(ret-now);
+
       }
       return -1;
     }
@@ -224,12 +270,14 @@ int32_t MemRecursorCache::get(time_t now, const DNSName &qname, const QType& qt,
       if (!entryMatches(firstIndexIterator, qtype, requireAuth, who))
         continue;
 
-      ttd = handleHit(firstIndexIterator, qname, who, res, signatures, authorityRecs, variable, state, wasAuth);
+      ttd = handleHit(firstIndexIterator, qname, who, res, signatures, authorityRecs, variable, cachedState, wasAuth);
 
       if(qt.getCode()!=QType::ANY && qt.getCode()!=QType::ADDR) // normally if we have a hit, we are done
         break;
     }
-
+    if (state && cachedState) {
+      *state = *cachedState;
+    }
     // cerr<<"time left : "<<ttd - now<<", "<< (res ? res->size() : 0) <<"\n";
     return static_cast<int32_t>(ttd-now);
   }
@@ -408,7 +456,15 @@ bool MemRecursorCache::updateValidationStatus(time_t now, const DNSName &qname,
 {
   bool updated = false;
   uint16_t qtype = qt.getCode();
-  if (qtype != QType::ANY && qtype != QType::ADDR && !d_ecsIndex.empty()) {
+  if (qtype == QType::ANY) {
+    throw std::runtime_error("Trying to update the DNSSEC validation status of all (via ANY) records for " + qname.toLogString());
+  }
+  if (qtype == QType::ADDR) {
+    throw std::runtime_error("Trying to update the DNSSEC validation status of several (via ADDR) records for " + qname.toLogString());
+  }
+
+  
+  if (!d_ecsIndex.empty()) {
     auto entry = getEntryUsingECSIndex(now, qname, qtype, requireAuth, who);
     if (entry == d_cache.end()) {
       return false;
@@ -435,8 +491,7 @@ bool MemRecursorCache::updateValidationStatus(time_t now, const DNSName &qname,
     }
     updated = true;
 
-    if(qtype != QType::ANY && qtype != QType::ADDR) // normally if we have a hit, we are done
-      break;
+    break;
   }
 
   return updated;
index e9a44c86c747c4edd173fa3d9402b4e47e343a91..22ffcdfbd05c3c6e2f3c599fd99010072456e971 100644 (file)
@@ -200,7 +200,7 @@ private:
   bool entryMatches(OrderedTagIterator_t& entry, uint16_t qt, bool requireAuth, const ComboAddress& who);
   std::pair<NameOnlyHashedTagIterator_t, NameOnlyHashedTagIterator_t> getEntries(const DNSName &qname, const QType& qt);
   cache_t::const_iterator getEntryUsingECSIndex(time_t now, const DNSName &qname, uint16_t qtype, bool requireAuth, const ComboAddress& who);
-  int32_t handleHit(OrderedTagIterator_t& entry, const DNSName& qname, const ComboAddress& who, vector<DNSRecord>* res, vector<std::shared_ptr<RRSIGRecordContent>>* signatures, std::vector<std::shared_ptr<DNSRecord>>* authorityRecs, bool* variable, vState* state, bool* wasAuth);
+  int32_t handleHit(OrderedTagIterator_t& entry, const DNSName& qname, const ComboAddress& who, vector<DNSRecord>* res, vector<std::shared_ptr<RRSIGRecordContent>>* signatures, std::vector<std::shared_ptr<DNSRecord>>* authorityRecs, bool* variable, boost::optional<vState>& state, bool* wasAuth);
 
 public:
   void preRemoval(const CacheEntry& entry)
index a1f0b85e8ef34107ed8337bfe33914ea11ecae29..739a05f5b06335db2a9118091eb722ebf65bf64d 100644 (file)
@@ -1050,4 +1050,102 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_from_cache_bogus)
   BOOST_CHECK_EQUAL(queriesCount, 3U);
 }
 
+BOOST_AUTO_TEST_CASE(test_dnssec_validation_from_cache_secure_any)
+{
+  /*
+    Validation is optional, and the first two queries (A, AAAA) do not ask for it,
+    so the answer are cached as Indeterminate.
+    The third query asks for validation, and is for ANY, so the answer should be marked as
+    Secure, after just-in-time validation.
+    The last query also requests validation but is for AAAA only.
+  */
+  std::unique_ptr<SyncRes> sr;
+  initSR(sr, true);
+
+  setDNSSECValidation(sr, DNSSECMode::Process);
+
+  primeHints();
+  const DNSName target("com.");
+  testkeysset_t keys;
+
+  auto luaconfsCopy = g_luaconfs.getCopy();
+  luaconfsCopy.dsAnchors.clear();
+  generateKeyMaterial(g_rootdnsname, DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys, luaconfsCopy.dsAnchors);
+  g_luaconfs.setState(luaconfsCopy);
+
+  size_t queriesCount = 0;
+
+  sr->setAsyncCallback([target, &queriesCount, keys](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, LWResult* res, bool* chained) {
+    queriesCount++;
+
+    if (type == QType::DS || type == QType::DNSKEY) {
+      return genericDSAndDNSKEYHandler(res, domain, domain, type, keys, false);
+    }
+    else {
+      if (domain == target && type == QType::A) {
+        setLWResult(res, 0, true, false, true);
+        addRecordToLW(res, target, QType::A, "192.0.2.1");
+        addRRSIG(keys, res->d_records, DNSName("."), 300);
+        return 1;
+      }
+      else if (domain == target && type == QType::AAAA) {
+        setLWResult(res, 0, true, false, true);
+        addRecordToLW(res, target, QType::AAAA, "2001:db8::1");
+        addRRSIG(keys, res->d_records, DNSName("."), 300);
+        return 1;
+      }
+    }
+
+    return 0;
+  });
+
+  vector<DNSRecord> ret;
+  /* first query does not require validation */
+  sr->setDNSSECValidationRequested(false);
+  int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::NoError);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Indeterminate);
+  BOOST_REQUIRE_EQUAL(ret.size(), 2U);
+  for (const auto& record : ret) {
+    BOOST_CHECK(record.d_type == QType::A || record.d_type == QType::RRSIG);
+  }
+  BOOST_CHECK_EQUAL(queriesCount, 1U);
+
+  ret.clear();
+  /* second query does not require validation either */
+  sr->setDNSSECValidationRequested(false);
+  res = sr->beginResolve(target, QType(QType::AAAA), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::NoError);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Indeterminate);
+  BOOST_REQUIRE_EQUAL(ret.size(), 2U);
+  for (const auto& record : ret) {
+    BOOST_CHECK(record.d_type == QType::AAAA || record.d_type == QType::RRSIG);
+  }
+  BOOST_CHECK_EQUAL(queriesCount, 2U);
+
+  ret.clear();
+  /* third one _does_ require validation */
+  sr->setDNSSECValidationRequested(true);
+  res = sr->beginResolve(target, QType(QType::ANY), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::NoError);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
+  BOOST_REQUIRE_EQUAL(ret.size(), 4U);
+  for (const auto& record : ret) {
+    BOOST_CHECK(record.d_type == QType::A || record.d_type == QType::AAAA || record.d_type == QType::RRSIG);
+  }
+  BOOST_CHECK_EQUAL(queriesCount, 4U);
+
+  ret.clear();
+  /* last one also requires validation */
+  sr->setDNSSECValidationRequested(true);
+  res = sr->beginResolve(target, QType(QType::AAAA), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::NoError);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
+  BOOST_REQUIRE_EQUAL(ret.size(), 2U);
+  for (const auto& record : ret) {
+    BOOST_CHECK(record.d_type == QType::AAAA || record.d_type == QType::RRSIG);
+  }
+  BOOST_CHECK_EQUAL(queriesCount, 4U);
+}
+
 BOOST_AUTO_TEST_SUITE_END()
index f31137df723b90b295d90acd12beb26af454438a..e8c40fb8488169dac084399bcb6b82891aff347a 100644 (file)
@@ -1161,6 +1161,11 @@ DNSName SyncRes::getBestNSNamesFromCache(const DNSName &qname, const QType& qtyp
 
 void SyncRes::updateValidationStatusInCache(const DNSName &qname, const QType& qt, bool aa, vState newState) const
 {
+  if (qt == QType::ANY || qt == QType::ADDR) {
+    // not doing that
+    return;
+  }
+
   if (newState == Bogus) {
     t_RC->updateValidationStatus(d_now.tv_sec, qname, qt, d_cacheRemote, aa, newState, s_maxbogusttl + d_now.tv_sec);
   }
@@ -1397,6 +1402,20 @@ static void reapRecordsFromNegCacheEntryForValidation(tcache_t& tcache, const ve
   }
 }
 
+static void reapRecordsForValidation(std::map<uint16_t, CacheEntry>& entries, const vector<DNSRecord>& records)
+{
+  for (const auto& rec : records) {
+    entries[rec.d_type].records.push_back(rec);
+  }
+}
+
+static void reapSignaturesForValidation(std::map<uint16_t, CacheEntry>& entries, const vector<std::shared_ptr<RRSIGRecordContent>>& signatures)
+{
+  for (const auto& sig : signatures) {
+    entries[sig->d_type].signatures.push_back(sig);
+  }
+}
+
 /*!
  * Convience function to push the records from records into ret with a new TTL
  *
@@ -1601,7 +1620,25 @@ bool SyncRes::doCacheCheck(const DNSName &qname, const DNSName& authname, bool w
           cachedState = validateDNSKeys(sqname, cset, signatures, depth);
         }
         else {
-          cachedState = SyncRes::validateRecordsWithSigs(depth, sqname, sqt, sqname, cset, signatures);
+          if (sqt == QType::ANY) {
+            std::map<uint16_t, CacheEntry> types;
+            reapRecordsForValidation(types, cset);
+            reapSignaturesForValidation(types, signatures);
+
+            for (const auto& type : types) {
+              vState cachedRecordState;
+              if (type.first == QType::DNSKEY) {
+                cachedRecordState = validateDNSKeys(sqname, type.second.records, type.second.signatures, depth);
+              }
+              else {
+                cachedRecordState = SyncRes::validateRecordsWithSigs(depth, sqname, QType(type.first), sqname, type.second.records, type.second.signatures);
+              }
+              updateDNSSECValidationState(cachedState, cachedRecordState);
+            }
+          }
+          else {
+            cachedState = SyncRes::validateRecordsWithSigs(depth, sqname, sqt, sqname, cset, signatures);
+          }
         }
       }
       else {
@@ -1613,7 +1650,9 @@ bool SyncRes::doCacheCheck(const DNSName &qname, const DNSName& authname, bool w
         if (cachedState == Bogus) {
           capTTL = s_maxbogusttl;
         }
-        updateValidationStatusInCache(sqname, sqt, wasCachedAuth, cachedState);
+        if (sqt != QType::ANY && sqt != QType::ADDR) {
+          updateValidationStatusInCache(sqname, sqt, wasCachedAuth, cachedState);
+        }
       }
     }
 
@@ -1962,24 +2001,7 @@ uint32_t SyncRes::computeLowestTTD(const std::vector<DNSRecord>& records, const
 void SyncRes::updateValidationState(vState& state, const vState stateUpdate)
 {
   LOG(d_prefix<<"validation state was "<<std::string(vStates[state])<<", state update is "<<std::string(vStates[stateUpdate]));
-
-  if (stateUpdate == TA) {
-    state = Secure;
-  }
-  else if (stateUpdate == NTA) {
-    state = Insecure;
-  }
-  else if (stateUpdate == Bogus) {
-    state = Bogus;
-  }
-  else if (state == Indeterminate) {
-    state = stateUpdate;
-  }
-  else if (stateUpdate == Insecure) {
-    if (state != Bogus) {
-      state = Insecure;
-    }
-  }
+  updateDNSSECValidationState(state, stateUpdate);
   LOG(", validation state is now "<<std::string(vStates[state])<<endl);
 }
 
@@ -2404,7 +2426,7 @@ vState SyncRes::validateRecordsWithSigs(unsigned int depth, const DNSName& qname
     recordcontents.insert(record.d_content);
   }
 
-  LOG(d_prefix<<"Going to validate "<<recordcontents.size()<< " record contents with "<<signatures.size()<<" sigs and "<<keys.size()<<" keys for "<<name<<endl);
+  LOG(d_prefix<<"Going to validate "<<recordcontents.size()<< " record contents with "<<signatures.size()<<" sigs and "<<keys.size()<<" keys for "<<name<<"|"<<qtype.getName()<<endl);
   if (validateWithKeySet(d_now.tv_sec, name, recordcontents, signatures, keys, false)) {
     LOG(d_prefix<<"Secure!"<<endl);
     return Secure;
index b8ce53a8d277bb1ad4858939a56bec34ba8b627a..0c6a303b2ce7430a3d609a2ec2ebe13960e8ae22 100644 (file)
@@ -1098,3 +1098,24 @@ DNSName getSigner(const std::vector<std::shared_ptr<RRSIGRecordContent> >& signa
 
   return DNSName();
 }
+
+void updateDNSSECValidationState(vState& state, const vState stateUpdate)
+{
+  if (stateUpdate == vState::TA) {
+    state = vState::Secure;
+  }
+  else if (stateUpdate == vState::NTA) {
+    state = vState::Insecure;
+  }
+  else if (stateUpdate == vState::Bogus) {
+    state = vState::Bogus;
+  }
+  else if (state == vState::Indeterminate) {
+    state = stateUpdate;
+  }
+  else if (stateUpdate == vState::Insecure) {
+    if (state != vState::Bogus) {
+      state = vState::Insecure;
+    }
+  }
+}
index 76e9de3d046ab204d1da8c0bb92cd33c44492b30..0f5e4bf5b770caf932e9f506e7ae85bcb37245e0 100644 (file)
@@ -80,3 +80,4 @@ bool denialProvesNoDelegation(const DNSName& zone, const std::vector<DNSRecord>&
 bool isRRSIGNotExpired(const time_t now, const shared_ptr<RRSIGRecordContent> sig);
 bool isWildcardExpanded(unsigned int labelCount, const std::shared_ptr<RRSIGRecordContent>& sign);
 bool isWildcardExpandedOntoItself(const DNSName& owner, unsigned int labelCount, const std::shared_ptr<RRSIGRecordContent>& sign);
+void updateDNSSECValidationState(vState& state, const vState stateUpdate);