From: Otto Date: Mon, 15 Mar 2021 16:11:37 +0000 (+0100) Subject: Don't pick up random root NS records from AUTHORITY sections X-Git-Tag: rec-4.5.0-beta1~20^2~3 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=f0cc2ba2ffce041d709592c2e8775b7ff28d773d;p=thirdparty%2Fpdns.git Don't pick up random root NS records from AUTHORITY sections --- diff --git a/pdns/recursordist/test-syncres_cc1.cc b/pdns/recursordist/test-syncres_cc1.cc index e44f61259d..2f2020d3d9 100644 --- a/pdns/recursordist/test-syncres_cc1.cc +++ b/pdns/recursordist/test-syncres_cc1.cc @@ -129,6 +129,64 @@ BOOST_AUTO_TEST_CASE(test_root_not_primed_and_no_response) } } +BOOST_AUTO_TEST_CASE(test_root_ns_poison_resistance) +{ + std::unique_ptr sr; + initSR(sr); + + primeHints(); + const DNSName target("www.example.com."); + + sr->setAsyncCallback([target](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, LWResult* res, bool* chained) { + + if (domain == g_rootdnsname && type == QType::NS) { + + setLWResult(res, 0, true, false, true); + char addr[] = "a.root-servers.net."; + for (char idx = 'a'; idx <= 'm'; idx++) { + addr[0] = idx; + addRecordToLW(res, g_rootdnsname, QType::NS, std::string(addr), DNSResourceRecord::ANSWER, 3600); + } + + addRecordToLW(res, "a.root-servers.net.", QType::A, "198.41.0.4", DNSResourceRecord::ADDITIONAL, 3600); + addRecordToLW(res, "a.root-servers.net.", QType::AAAA, "2001:503:ba3e::2:30", DNSResourceRecord::ADDITIONAL, 3600); + + return LWResult::Result::Success; + } + + if (domain == target && type == QType::A) { + + setLWResult(res, 0, true, false, true); + addRecordToLW(res, target, QType::A, "1.2.3.4", DNSResourceRecord::ANSWER, 3600); + + addRecordToLW(res, ".", QType::NS, "poison.name.", DNSResourceRecord::AUTHORITY, 3600); + addRecordToLW(res, "poison.name", QType::A, "4.5.6.7", DNSResourceRecord::ADDITIONAL, 3600); + + return LWResult::Result::Success; + } + + return LWResult::Result::Timeout; + }); + + vector ret; + // Check we have 13 root servers + int res = sr->beginResolve(g_rootdnsname, QType(QType::NS), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_REQUIRE_EQUAL(ret.size(), 13U); + + // Try to poison + ret.clear(); + res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_REQUIRE_EQUAL(ret.size(), 1U); + + // Still should have 13 + ret.clear(); + res = sr->beginResolve(g_rootdnsname, QType(QType::NS), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_REQUIRE_EQUAL(ret.size(), 13U); +} + static void test_edns_formerr_fallback_f(bool sample) { std::unique_ptr sr; diff --git a/pdns/reczones.cc b/pdns/reczones.cc index 2bfbfae3c3..5d1b537a97 100644 --- a/pdns/reczones.cc +++ b/pdns/reczones.cc @@ -114,11 +114,13 @@ bool primeHints(time_t ignored) for (auto const& r: seenA) { if (seenNS.count(r)) { reachableA = true; + break; } } for (auto const& r: seenAAAA) { if (seenNS.count(r)) { reachableAAAA = true; + break; } } if (SyncRes::s_doIPv4 && !SyncRes::s_doIPv6 && !reachableA) { diff --git a/pdns/syncres.cc b/pdns/syncres.cc index 5e4e5db830..996717f733 100644 --- a/pdns/syncres.cc +++ b/pdns/syncres.cc @@ -2970,7 +2970,7 @@ void SyncRes::sanitizeRecords(const std::string& prefix, LWResult& lwr, const DN } if (rec->d_place == DNSResourceRecord::AUTHORITY && rec->d_type == QType::NS && (isNXDomain || isNXQType)) { - /* we don't want to pick up NS records in AUTHORITY or ADDITIONAL sections of NXDomain answers + /* we don't want to pick up NS records in AUTHORITY and their ADDITIONAL sections of NXDomain answers because they are somewhat easy to insert into a large, fragmented UDP response for an off-path attacker by injecting spoofed UDP fragments. */ @@ -2979,6 +2979,14 @@ void SyncRes::sanitizeRecords(const std::string& prefix, LWResult& lwr, const DN continue; } + if (rec->d_place == DNSResourceRecord::AUTHORITY && rec->d_type == QType::NS && !d_updatingRootNS && rec->d_name == g_rootdnsname) { + /* we don't want to pick up NS records in AUTHORITY and their ADDITIONALs sections of random queries + */ + LOG(prefix<<"Removing NS record '"<d_name<<"|"<d_type)<<"|"<d_content->getZoneRepresentation()<<"' in the "<<(int)rec->d_place<<" section of a response received from "<d_place == DNSResourceRecord::AUTHORITY && rec->d_type == QType::NS) { allowAdditionalEntry(allowedAdditionals, *rec); }