Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rec: Backport Keytrap to rec-5.0.x #13782

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
29 changes: 21 additions & 8 deletions pdns/recursordist/aggressive_nsec.cc
Expand Up @@ -29,6 +29,7 @@
#include "validate.hh"

std::unique_ptr<AggressiveNSECCache> g_aggressiveNSECCache{nullptr};
uint64_t AggressiveNSECCache::s_nsec3DenialProofMaxCost{0};
uint8_t AggressiveNSECCache::s_maxNSEC3CommonPrefix = AggressiveNSECCache::s_default_maxNSEC3CommonPrefix;

/* this is defined in syncres.hh and we are not importing that here */
Expand Down Expand Up @@ -539,7 +540,7 @@ bool AggressiveNSECCache::synthesizeFromNSECWildcard(time_t now, const DNSName&
return true;
}

bool AggressiveNSECCache::getNSEC3Denial(time_t now, std::shared_ptr<LockGuarded<AggressiveNSECCache::ZoneEntry>>& zoneEntry, std::vector<DNSRecord>& soaSet, std::vector<std::shared_ptr<const RRSIGRecordContent>>& soaSignatures, const DNSName& name, const QType& type, std::vector<DNSRecord>& ret, int& res, bool doDNSSEC, const OptLog& log)
bool AggressiveNSECCache::getNSEC3Denial(time_t now, std::shared_ptr<LockGuarded<AggressiveNSECCache::ZoneEntry>>& zoneEntry, std::vector<DNSRecord>& soaSet, std::vector<std::shared_ptr<const RRSIGRecordContent>>& soaSignatures, const DNSName& name, const QType& type, std::vector<DNSRecord>& ret, int& res, bool doDNSSEC, const OptLog& log, pdns::validation::ValidationContext& validationContext)
{
DNSName zone;
std::string salt;
Expand All @@ -555,7 +556,17 @@ bool AggressiveNSECCache::getNSEC3Denial(time_t now, std::shared_ptr<LockGuarded
iterations = entry->d_iterations;
}

auto nameHash = DNSName(toBase32Hex(hashQNameWithSalt(salt, iterations, name))) + zone;
const auto zoneLabelsCount = zone.countLabels();
if (s_nsec3DenialProofMaxCost != 0) {
const auto worstCaseIterations = getNSEC3DenialProofWorstCaseIterationsCount(name.countLabels() - zoneLabelsCount, iterations, salt.length());
if (worstCaseIterations > s_nsec3DenialProofMaxCost) {
// skip NSEC3 aggressive cache for expensive NSEC3 parameters: "if you want us to take the pain of PRSD away from you, you need to make it cheap for us to do so"
VLOG(log, name << ": Skipping aggressive use of the NSEC3 cache since the zone parameters are too expensive" << endl);
return false;
}
}

auto nameHash = DNSName(toBase32Hex(getHashFromNSEC3(name, iterations, salt, validationContext))) + zone;

ZoneEntry::CacheEntry exactNSEC3;
if (getNSEC3(now, zoneEntry, nameHash, exactNSEC3)) {
Expand Down Expand Up @@ -602,8 +613,10 @@ bool AggressiveNSECCache::getNSEC3Denial(time_t now, std::shared_ptr<LockGuarded
DNSName closestEncloser(name);
bool found = false;
ZoneEntry::CacheEntry closestNSEC3;
while (!found && closestEncloser.chopOff()) {
auto closestHash = DNSName(toBase32Hex(hashQNameWithSalt(salt, iterations, closestEncloser))) + zone;
auto remainingLabels = closestEncloser.countLabels() - 1;
while (!found && closestEncloser.chopOff() && remainingLabels >= zoneLabelsCount) {
auto closestHash = DNSName(toBase32Hex(getHashFromNSEC3(closestEncloser, iterations, salt, validationContext))) + zone;
remainingLabels--;

if (getNSEC3(now, zoneEntry, closestHash, closestNSEC3)) {
VLOG(log, name << ": Found closest encloser at " << closestEncloser << " (" << closestHash << ")" << endl);
Expand Down Expand Up @@ -651,7 +664,7 @@ bool AggressiveNSECCache::getNSEC3Denial(time_t now, std::shared_ptr<LockGuarded
DNSName nsecFound;
DNSName nextCloser(closestEncloser);
nextCloser.prependRawLabel(name.getRawLabel(labelIdx - 1));
auto nextCloserHash = toBase32Hex(hashQNameWithSalt(salt, iterations, nextCloser));
auto nextCloserHash = toBase32Hex(getHashFromNSEC3(nextCloser, iterations, salt, validationContext));
VLOG(log, name << ": Looking for a NSEC3 covering the next closer " << nextCloser << " (" << nextCloserHash << ")" << endl);

ZoneEntry::CacheEntry nextCloserEntry;
Expand Down Expand Up @@ -680,7 +693,7 @@ bool AggressiveNSECCache::getNSEC3Denial(time_t now, std::shared_ptr<LockGuarded
/* An ancestor NSEC3 would be fine here, since it does prove that there is no delegation at the next closer
name (we don't insert opt-out NSEC3s into the cache). */
DNSName wildcard(g_wildcarddnsname + closestEncloser);
auto wcHash = toBase32Hex(hashQNameWithSalt(salt, iterations, wildcard));
auto wcHash = toBase32Hex(getHashFromNSEC3(wildcard, iterations, salt, validationContext));
VLOG(log, name << ": Looking for a NSEC3 covering the wildcard " << wildcard << " (" << wcHash << ")" << endl);

ZoneEntry::CacheEntry wcEntry;
Expand Down Expand Up @@ -764,7 +777,7 @@ bool AggressiveNSECCache::getNSEC3Denial(time_t now, std::shared_ptr<LockGuarded
return true;
}

bool AggressiveNSECCache::getDenial(time_t now, const DNSName& name, const QType& type, std::vector<DNSRecord>& ret, int& res, const ComboAddress& who, const boost::optional<std::string>& routingTag, bool doDNSSEC, const OptLog& log)
bool AggressiveNSECCache::getDenial(time_t now, const DNSName& name, const QType& type, std::vector<DNSRecord>& ret, int& res, const ComboAddress& who, const boost::optional<std::string>& routingTag, bool doDNSSEC, pdns::validation::ValidationContext& validationContext, const OptLog& log)
{
std::shared_ptr<LockGuarded<ZoneEntry>> zoneEntry;
if (type == QType::DS) {
Expand Down Expand Up @@ -804,7 +817,7 @@ bool AggressiveNSECCache::getDenial(time_t now, const DNSName& name, const QType
}

if (nsec3) {
return getNSEC3Denial(now, zoneEntry, soaSet, soaSignatures, name, type, ret, res, doDNSSEC, log);
return getNSEC3Denial(now, zoneEntry, soaSet, soaSignatures, name, type, ret, res, doDNSSEC, log, validationContext);
}

ZoneEntry::CacheEntry entry;
Expand Down
8 changes: 5 additions & 3 deletions pdns/recursordist/aggressive_nsec.hh
Expand Up @@ -36,11 +36,13 @@ using namespace ::boost::multi_index;
#include "lock.hh"
#include "stat_t.hh"
#include "logger.hh"
#include "validate.hh"

class AggressiveNSECCache
{
public:
static const uint8_t s_default_maxNSEC3CommonPrefix = 10;
static constexpr uint8_t s_default_maxNSEC3CommonPrefix = 10;
static uint64_t s_nsec3DenialProofMaxCost;
static uint8_t s_maxNSEC3CommonPrefix;

AggressiveNSECCache(uint64_t entries) :
Expand All @@ -54,7 +56,7 @@ public:
}

void insertNSEC(const DNSName& zone, const DNSName& owner, const DNSRecord& record, const std::vector<std::shared_ptr<const RRSIGRecordContent>>& signatures, bool nsec3);
bool getDenial(time_t, const DNSName& name, const QType& type, std::vector<DNSRecord>& ret, int& res, const ComboAddress& who, const boost::optional<std::string>& routingTag, bool doDNSSEC, const OptLog& log = std::nullopt);
bool getDenial(time_t, const DNSName& name, const QType& type, std::vector<DNSRecord>& ret, int& res, const ComboAddress& who, const boost::optional<std::string>& routingTag, bool doDNSSEC, pdns::validation::ValidationContext& validationContext, const OptLog& log = std::nullopt);

void removeZoneInfo(const DNSName& zone, bool subzones);

Expand Down Expand Up @@ -144,7 +146,7 @@ private:
std::shared_ptr<LockGuarded<ZoneEntry>> getBestZone(const DNSName& zone);
bool getNSECBefore(time_t now, std::shared_ptr<LockGuarded<ZoneEntry>>& zoneEntry, const DNSName& name, ZoneEntry::CacheEntry& entry);
bool getNSEC3(time_t now, std::shared_ptr<LockGuarded<ZoneEntry>>& zoneEntry, const DNSName& name, ZoneEntry::CacheEntry& entry);
bool getNSEC3Denial(time_t now, std::shared_ptr<LockGuarded<ZoneEntry>>& zoneEntry, std::vector<DNSRecord>& soaSet, std::vector<std::shared_ptr<const RRSIGRecordContent>>& soaSignatures, const DNSName& name, const QType& type, std::vector<DNSRecord>& ret, int& res, bool doDNSSEC, const OptLog&);
bool getNSEC3Denial(time_t now, std::shared_ptr<LockGuarded<ZoneEntry>>& zoneEntry, std::vector<DNSRecord>& soaSet, std::vector<std::shared_ptr<const RRSIGRecordContent>>& soaSignatures, const DNSName& name, const QType& type, std::vector<DNSRecord>& ret, int& res, bool doDNSSEC, const OptLog&, pdns::validation::ValidationContext& validationContext);
bool synthesizeFromNSEC3Wildcard(time_t now, const DNSName& name, const QType& type, std::vector<DNSRecord>& ret, int& res, bool doDNSSEC, ZoneEntry::CacheEntry& nextCloser, const DNSName& wildcardName, const OptLog&);
bool synthesizeFromNSECWildcard(time_t now, const DNSName& name, const QType& type, std::vector<DNSRecord>& ret, int& res, bool doDNSSEC, ZoneEntry::CacheEntry& nsec, const DNSName& wildcardName, const OptLog&);

Expand Down
23 changes: 23 additions & 0 deletions pdns/recursordist/pdns_recursor.cc
Expand Up @@ -568,6 +568,15 @@ static PolicyResult handlePolicyHit(const DNSFilterEngine::Policy& appliedPolicy
res = RCode::ServFail;
break;
}
catch (const pdns::validation::TooManySEC3IterationsException& e) {
if (g_logCommonErrors) {
SLOG(g_log << Logger::Notice << "Sending SERVFAIL to " << comboWriter->getRemote() << " during resolve of the custom filter policy '" << appliedPolicy.getName() << "' while resolving '" << comboWriter->d_mdp.d_qname << "' because: " << e.what() << endl,
resolver.d_slog->error(Logr::Notice, e.what(), "Sending SERVFAIL during resolve of the custom filter policy",
"policyName", Logging::Loggable(appliedPolicy.getName()), "exception", Logging::Loggable("TooManySEC3IterationsException")));
}
res = RCode::ServFail;
break;
}
catch (const PolicyHitException& e) {
if (g_logCommonErrors) {
SLOG(g_log << Logger::Notice << "Sending SERVFAIL to " << comboWriter->getRemote() << " during resolve of the custom filter policy '" << appliedPolicy.getName() << "' while resolving '" << comboWriter->d_mdp.d_qname << "' because another RPZ policy was hit" << endl,
Expand Down Expand Up @@ -1270,6 +1279,13 @@ void startDoResolve(void* arg) // NOLINT(readability-function-cognitive-complexi
}
res = RCode::ServFail;
}
catch (const pdns::validation::TooManySEC3IterationsException& e) {
if (g_logCommonErrors) {
SLOG(g_log << Logger::Notice << "Sending SERVFAIL to " << comboWriter->getRemote() << " during resolve of '" << comboWriter->d_mdp.d_qname << "' because: " << e.what() << endl,
resolver.d_slog->error(Logr::Notice, e.what(), "Sending SERVFAIL during resolve"));
}
res = RCode::ServFail;
}
catch (const SendTruncatedAnswerException& e) {
ret.clear();
resolver.d_appliedPolicy.addSOAtoRPZResult(ret);
Expand Down Expand Up @@ -1449,6 +1465,13 @@ void startDoResolve(void* arg) // NOLINT(readability-function-cognitive-complexi
}
goto sendit; // NOLINT(cppcoreguidelines-avoid-goto)
}
catch (const pdns::validation::TooManySEC3IterationsException& e) {
if (g_logCommonErrors) {
SLOG(g_log << Logger::Notice << "Sending SERVFAIL to " << comboWriter->getRemote() << " during validation of '" << comboWriter->d_mdp.d_qname << "|" << QType(comboWriter->d_mdp.d_qtype) << "' because: " << e.what() << endl,
resolver.d_slog->error(Logr::Notice, e.what(), "Sending SERVFAIL during validation", "exception", Logging::Loggable("TooManySEC3IterationsException")));
}
goto sendit; // NOLINT(cppcoreguidelines-avoid-goto)
}
}

if (!ret.empty()) {
Expand Down
7 changes: 7 additions & 0 deletions pdns/recursordist/rec-main.cc
Expand Up @@ -1563,6 +1563,10 @@ static int initDNSSEC(Logr::log_t log)

g_dnssecLogBogus = ::arg().mustDo("dnssec-log-bogus");
g_maxNSEC3Iterations = ::arg().asNum("nsec3-max-iterations");
g_maxRRSIGsPerRecordToConsider = ::arg().asNum("max-rrsigs-per-record");
g_maxNSEC3sPerRecordToConsider = ::arg().asNum("max-nsec3s-per-record");
g_maxDNSKEYsToConsider = ::arg().asNum("max-dnskeys");
g_maxDSsToConsider = ::arg().asNum("max-ds-per-zone");

vector<string> nums;
bool automatic = true;
Expand Down Expand Up @@ -1652,6 +1656,8 @@ static int initSyncRes(Logr::log_t log)
SyncRes::s_maxnsaddressqperq = ::arg().asNum("max-ns-address-qperq");
SyncRes::s_maxtotusec = 1000 * ::arg().asNum("max-total-msec");
SyncRes::s_maxdepth = ::arg().asNum("max-recursion-depth");
SyncRes::s_maxvalidationsperq = ::arg().asNum("max-signature-validations-per-query");
SyncRes::s_maxnsec3iterationsperq = ::arg().asNum("max-nsec3-hash-computations-per-query");
SyncRes::s_rootNXTrust = ::arg().mustDo("root-nx-trust");
SyncRes::s_refresh_ttlperc = ::arg().asNum("refresh-on-ttl-perc");
SyncRes::s_locked_ttlperc = ::arg().asNum("record-cache-locked-ttl-perc");
Expand Down Expand Up @@ -2168,6 +2174,7 @@ static int serviceMain(Logr::log_t log)
}
}

AggressiveNSECCache::s_nsec3DenialProofMaxCost = ::arg().asNum("aggressive-cache-max-nsec3-hash-cost");
AggressiveNSECCache::s_maxNSEC3CommonPrefix = static_cast<uint8_t>(std::round(std::log2(::arg().asNum("aggressive-cache-min-nsec3-hit-ratio"))));
SLOG(g_log << Logger::Debug << "NSEC3 aggressive cache tuning: aggressive-cache-min-nsec3-hit-ratio: " << ::arg().asNum("aggressive-cache-min-nsec3-hit-ratio") << " max common prefix bits: " << std::to_string(AggressiveNSECCache::s_maxNSEC3CommonPrefix) << endl,
log->info(Logr::Debug, "NSEC3 aggressive cache tuning", "aggressive-cache-min-nsec3-hit-ratio", Logging::Loggable(::arg().asNum("aggressive-cache-min-nsec3-hit-ratio")), "maxCommonPrefixBits", Logging::Loggable(AggressiveNSECCache::s_maxNSEC3CommonPrefix)));
Expand Down
14 changes: 8 additions & 6 deletions pdns/recursordist/rec-zonetocache.cc
Expand Up @@ -249,6 +249,8 @@ pdns::ZoneMD::Result ZoneData::processLines(const vector<string>& lines, const R

vState ZoneData::dnssecValidate(pdns::ZoneMD& zonemd, size_t& zonemdCount) const
{
pdns::validation::ValidationContext validationContext;
validationContext.d_nsec3IterationsRemainingQuota = std::numeric_limits<decltype(validationContext.d_nsec3IterationsRemainingQuota)>::max();
zonemdCount = 0;

SyncRes resolver({d_now, 0});
Expand All @@ -272,7 +274,7 @@ vState ZoneData::dnssecValidate(pdns::ZoneMD& zonemd, size_t& zonemdCount) const
}

skeyset_t validKeys;
vState dnsKeyState = validateDNSKeysAgainstDS(d_now, d_zone, dsmap, dnsKeys, records, zonemd.getRRSIGs(), validKeys, std::nullopt);
vState dnsKeyState = validateDNSKeysAgainstDS(d_now, d_zone, dsmap, dnsKeys, records, zonemd.getRRSIGs(), validKeys, std::nullopt, validationContext);
if (dnsKeyState != vState::Secure) {
return dnsKeyState;
}
Expand All @@ -294,7 +296,7 @@ vState ZoneData::dnssecValidate(pdns::ZoneMD& zonemd, size_t& zonemdCount) const

if (!nsecs.records.empty() && !nsecs.signatures.empty()) {
// Valdidate the NSEC
nsecValidationStatus = validateWithKeySet(d_now, d_zone, nsecs.records, nsecs.signatures, validKeys, std::nullopt);
nsecValidationStatus = validateWithKeySet(d_now, d_zone, nsecs.records, nsecs.signatures, validKeys, std::nullopt, validationContext);
csp.emplace(std::pair(d_zone, QType::NSEC), nsecs);
}
else if (!nsec3s.records.empty() && !nsec3s.signatures.empty()) {
Expand All @@ -303,13 +305,13 @@ vState ZoneData::dnssecValidate(pdns::ZoneMD& zonemd, size_t& zonemdCount) const
for (const auto& rec : zonemd.getNSEC3Params()) {
records.emplace(rec);
}
nsecValidationStatus = validateWithKeySet(d_now, d_zone, records, zonemd.getRRSIGs(), validKeys, std::nullopt);
nsecValidationStatus = validateWithKeySet(d_now, d_zone, records, zonemd.getRRSIGs(), validKeys, std::nullopt, validationContext);
if (nsecValidationStatus != vState::Secure) {
d_log->info(Logr::Warning, "NSEC3PARAMS records did not validate");
return nsecValidationStatus;
}
// Valdidate the NSEC3
nsecValidationStatus = validateWithKeySet(d_now, zonemd.getNSEC3Label(), nsec3s.records, nsec3s.signatures, validKeys, std::nullopt);
nsecValidationStatus = validateWithKeySet(d_now, zonemd.getNSEC3Label(), nsec3s.records, nsec3s.signatures, validKeys, std::nullopt, validationContext);
csp.emplace(std::pair(zonemd.getNSEC3Label(), QType::NSEC3), nsec3s);
}
else {
Expand All @@ -322,7 +324,7 @@ vState ZoneData::dnssecValidate(pdns::ZoneMD& zonemd, size_t& zonemdCount) const
return nsecValidationStatus;
}

auto denial = getDenial(csp, d_zone, QType::ZONEMD, false, false, std::nullopt, true);
auto denial = getDenial(csp, d_zone, QType::ZONEMD, false, false, validationContext, std::nullopt, true);
if (denial == dState::NXQTYPE) {
d_log->info(Logr::Info, "Validated denial of existence of ZONEMD record");
return vState::Secure;
Expand All @@ -336,7 +338,7 @@ vState ZoneData::dnssecValidate(pdns::ZoneMD& zonemd, size_t& zonemdCount) const
for (const auto& rec : zonemdRecords) {
records.emplace(rec);
}
return validateWithKeySet(d_now, d_zone, records, zonemd.getRRSIGs(), validKeys, std::nullopt);
return validateWithKeySet(d_now, d_zone, records, zonemd.getRRSIGs(), validKeys, std::nullopt, validationContext);
}

void ZoneData::ZoneToCache(const RecZoneToCache::Config& config)
Expand Down