Version in base suite: 4.8.4-1 Version in overlay suite: 4.8.6-1 Base version: pdns-recursor_4.8.6-1 Target version: pdns-recursor_4.8.7-1 Base file: /srv/ftp-master.debian.org/ftp/pool/main/p/pdns-recursor/pdns-recursor_4.8.6-1.dsc Target file: /srv/ftp-master.debian.org/policy/pool/main/p/pdns-recursor/pdns-recursor_4.8.7-1.dsc configure | 20 ++--- configure.ac | 2 debian/changelog | 7 ++ effective_tld_names.dat | 93 +++++++++++++++++++++++++++ pdns_recursor.1 | 2 pubsuffix.cc | 57 ++++++++++++++++ rec-zonetocache.cc | 6 - rec_control.1 | 2 root-addresses.hh | 4 - syncres.cc | 55 +++++++++++++++- test-aggressive_nsec_cc.cc | 1 test-syncres_cc10.cc | 152 +++++++++++++++++++++++++++++++++++++++++++++ test-syncres_cc3.cc | 73 +++++++++++++++++++++ test-syncres_cc4.cc | 3 zonemd.cc | 2 zonemd.hh | 9 +- 16 files changed, 459 insertions(+), 29 deletions(-) diff -Nru pdns-recursor-4.8.6/configure pdns-recursor-4.8.7/configure --- pdns-recursor-4.8.6/configure 2024-02-13 12:37:30.000000000 +0000 +++ pdns-recursor-4.8.7/configure 2024-03-06 13:48:46.000000000 +0000 @@ -1,6 +1,6 @@ #! /bin/sh # Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.69 for pdns-recursor 4.8.6. +# Generated by GNU Autoconf 2.69 for pdns-recursor 4.8.7. # # # Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc. @@ -587,8 +587,8 @@ # Identity of this package. PACKAGE_NAME='pdns-recursor' PACKAGE_TARNAME='pdns-recursor' -PACKAGE_VERSION='4.8.6' -PACKAGE_STRING='pdns-recursor 4.8.6' +PACKAGE_VERSION='4.8.7' +PACKAGE_STRING='pdns-recursor 4.8.7' PACKAGE_BUGREPORT='' PACKAGE_URL='' @@ -1552,7 +1552,7 @@ # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF -\`configure' configures pdns-recursor 4.8.6 to adapt to many kinds of systems. +\`configure' configures pdns-recursor 4.8.7 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1623,7 +1623,7 @@ if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of pdns-recursor 4.8.6:";; + short | recursive ) echo "Configuration of pdns-recursor 4.8.7:";; esac cat <<\_ACEOF @@ -1810,7 +1810,7 @@ test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -pdns-recursor configure 4.8.6 +pdns-recursor configure 4.8.7 generated by GNU Autoconf 2.69 Copyright (C) 2012 Free Software Foundation, Inc. @@ -2569,7 +2569,7 @@ This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. -It was created by pdns-recursor $as_me 4.8.6, which was +It was created by pdns-recursor $as_me 4.8.7, which was generated by GNU Autoconf 2.69. Invocation command line was $ $0 $@ @@ -3437,7 +3437,7 @@ # Define the identity of the package. PACKAGE='pdns-recursor' - VERSION='4.8.6' + VERSION='4.8.7' cat >>confdefs.h <<_ACEOF @@ -28252,7 +28252,7 @@ # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" -This file was extended by pdns-recursor $as_me 4.8.6, which was +This file was extended by pdns-recursor $as_me 4.8.7, which was generated by GNU Autoconf 2.69. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -28318,7 +28318,7 @@ cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" ac_cs_version="\\ -pdns-recursor config.status 4.8.6 +pdns-recursor config.status 4.8.7 configured by $0, generated by GNU Autoconf 2.69, with options \\"\$ac_cs_config\\" diff -Nru pdns-recursor-4.8.6/configure.ac pdns-recursor-4.8.7/configure.ac --- pdns-recursor-4.8.6/configure.ac 2024-02-13 12:37:24.000000000 +0000 +++ pdns-recursor-4.8.7/configure.ac 2024-03-06 13:48:40.000000000 +0000 @@ -1,6 +1,6 @@ AC_PREREQ([2.69]) -AC_INIT([pdns-recursor], [4.8.6]) +AC_INIT([pdns-recursor], [4.8.7]) AC_CONFIG_AUX_DIR([build-aux]) AM_INIT_AUTOMAKE([foreign dist-bzip2 no-dist-gzip tar-ustar -Wno-portability subdir-objects parallel-tests 1.11]) AM_SILENT_RULES([yes]) diff -Nru pdns-recursor-4.8.6/debian/changelog pdns-recursor-4.8.7/debian/changelog --- pdns-recursor-4.8.6/debian/changelog 2024-02-16 11:21:56.000000000 +0000 +++ pdns-recursor-4.8.7/debian/changelog 2024-03-17 16:21:52.000000000 +0000 @@ -1,3 +1,10 @@ +pdns-recursor (4.8.7-1) bookworm-security; urgency=medium + + * New upstream version 4.8.7 + * Fixes regression introduced in the fixes for CVE-2023-50387 and CVE-2023-50868 + + -- Chris Hofstaedtler Sun, 17 Mar 2024 17:21:52 +0100 + pdns-recursor (4.8.6-1) bookworm-security; urgency=medium * New upstream version 4.8.6 diff -Nru pdns-recursor-4.8.6/effective_tld_names.dat pdns-recursor-4.8.7/effective_tld_names.dat --- pdns-recursor-4.8.6/effective_tld_names.dat 2024-02-13 12:38:18.000000000 +0000 +++ pdns-recursor-4.8.7/effective_tld_names.dat 2024-03-06 13:49:35.000000000 +0000 @@ -11229,6 +11229,10 @@ // Submitted by Ofer Kalaora activetrail.biz +// Adaptable.io : https://adaptable.io +// Submitted by Mark Terrel +adaptable.app + // Adobe : https://www.adobe.com/ // Submitted by Ian Boston and Lars Trieloff adobeaemcloud.com @@ -12041,6 +12045,10 @@ // Submitted by Thomas Orozco on-aptible.com +// Aquapal : https://aquapal.net/ +// Submitted by Aki Ueno +f5.si + // ASEINet : https://www.aseinet.com/ // Submitted by Asei SEKIGUCHI user.aseinet.ne.jp @@ -12213,7 +12221,9 @@ // Canva Pty Ltd : https://canva.com/ // Submitted by Joel Aquilina canva-apps.cn +*.my.canvasite.cn canva-apps.com +*.my.canva.site // Carrd : https://carrd.co // Submitted by AJ @@ -12405,6 +12415,10 @@ webhosting.be hosting-cluster.nl +// Convex : https://convex.dev/ +// Submitted by James Cowling +convex.site + // Coordination Center for TLD RU and XN--P1AI : https://cctld.ru/en/domains/domens_ru/reserved/ // Submitted by George Georgievsky ac.ru @@ -12434,6 +12448,10 @@ // Submitted by Ales Krajnik realm.cz +// Crisp IM SAS : https://crisp.chat/ +// Submitted by Baptiste Jamin +on.crisp.email + // Cryptonomic : https://cryptonomic.net/ // Submitted by Andrew Cady *.cryptonomic.net @@ -12941,6 +12959,10 @@ easypanel.app easypanel.host +// EasyWP : https://www.easywp.com +// Submitted by +*.ewp.live + // Elementor : Elementor Ltd. // Submitted by Anton Barkan elementor.cloud @@ -14159,6 +14181,10 @@ // Submitted by Grayson Martin forte.id +// MODX Systems LLC : https://modx.com +// Submitted by Elizabeth Southwell +modx.dev + // Mozilla Corporation : https://mozilla.com // Submitted by Ben Francis mozilla-iot.org @@ -14220,6 +14246,7 @@ sa.ngrok.io us.ngrok.io ngrok.pizza +ngrok.pro // Nicolaus Copernicus University in Torun - MSK TORMAN (https://www.man.torun.pl) torun.pl @@ -14635,6 +14662,8 @@ // Rad Web Hosting: https://radwebhosting.com // Submitted by Scott Claeys cloudsite.builders +myradweb.net +servername.us // Redgate Software: https://red-gate.com // Submitted by Andrew Farries @@ -14701,11 +14730,40 @@ onrender.com // Repl.it : https://repl.it -// Submitted by Lincoln Bergeson +// Submitted by Lincoln Bergeson +replit.app +id.replit.app firewalledreplit.co id.firewalledreplit.co repl.co id.repl.co +replit.dev +archer.replit.dev +bones.replit.dev +canary.replit.dev +global.replit.dev +hacker.replit.dev +id.replit.dev +janeway.replit.dev +kim.replit.dev +kira.replit.dev +kirk.replit.dev +odo.replit.dev +paris.replit.dev +picard.replit.dev +pike.replit.dev +prerelease.replit.dev +reed.replit.dev +riker.replit.dev +sisko.replit.dev +spock.replit.dev +staging.replit.dev +sulu.replit.dev +tarpit.replit.dev +teams.replit.dev +tucker.replit.dev +wesley.replit.dev +worf.replit.dev repl.run // Resin.io : https://resin.io @@ -14988,6 +15046,14 @@ // Submitted by Adrien Gillon stackhero-network.com +// STACKIT : https://www.stackit.de/en/ +// Submitted by STACKIT-DNS Team (Simon Stier) +runs.onstackit.cloud +stackit.gg +stackit.rocks +stackit.run +stackit.zone + // Staclar : https://staclar.com // Submitted by Q Misell musician.io @@ -15054,6 +15120,19 @@ // Submitted by Jacob Lee api.stdlib.com +// stereosense GmbH : https://www.involve.me +// Submitted by Florian Burmann +feedback.ac +forms.ac +assessments.cx +calculators.cx +funnels.cx +paynow.cx +quizzes.cx +researched.cx +tests.cx +surveys.so + // Storipress : https://storipress.com // Submitted by Benno Liu storipress.app @@ -15062,6 +15141,12 @@ // Submitted by Philip Hutchins storj.farm +// Streak : https://streak.com +// Submitted by Blake Kadatz +streak-link.com +streaklinks.com +streakusercontent.com + // Studenten Net Twente : http://www.snt.utwente.nl/ // Submitted by Silke Hofstra utwente.io @@ -15124,6 +15209,7 @@ // Submitted by David Anderson beta.tailscale.net ts.net +*.c.ts.net // TASK geographical domains (www.task.gda.pl/uslugi/dns) gda.pl @@ -15359,6 +15445,11 @@ // Submitted by Masayuki Note wafflecell.com +// Webflow, Inc. : https://www.webflow.com +// Submitted by Webflow Security Team +webflow.io +webflowtest.io + // WebHare bv: https://www.webhare.com/ // Submitted by Arnold Hendriks *.webhare.dev diff -Nru pdns-recursor-4.8.6/pdns_recursor.1 pdns-recursor-4.8.7/pdns_recursor.1 --- pdns-recursor-4.8.6/pdns_recursor.1 2024-02-13 12:38:18.000000000 +0000 +++ pdns-recursor-4.8.7/pdns_recursor.1 2024-03-06 13:49:35.000000000 +0000 @@ -27,7 +27,7 @@ .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "PDNS_RECURSOR" "1" "Feb 13, 2024" "" "PowerDNS Recursor" +.TH "PDNS_RECURSOR" "1" "Mar 06, 2024" "" "PowerDNS Recursor" .SH NAME pdns_recursor \- The PowerDNS Recursor binary .SH SYNOPSIS diff -Nru pdns-recursor-4.8.6/pubsuffix.cc pdns-recursor-4.8.7/pubsuffix.cc --- pdns-recursor-4.8.6/pubsuffix.cc 2024-02-13 12:38:18.000000000 +0000 +++ pdns-recursor-4.8.7/pubsuffix.cc 2024-03-06 13:49:35.000000000 +0000 @@ -5117,6 +5117,7 @@ "cpserver.com", "graphox.us", "activetrail.biz", +"adaptable.app", "adobeaemcloud.com", "aem.live", "hlx.live", @@ -5766,6 +5767,7 @@ "appspaceusercontent.com", "appudo.net", "on-aptible.com", +"f5.si", "user.aseinet.ne.jp", "gv.vc", "d.gv.vc", @@ -5913,6 +5915,7 @@ "co.no", "webhosting.be", "hosting-cluster.nl", +"convex.site", "ac.ru", "edu.ru", "gov.ru", @@ -5929,6 +5932,7 @@ "knx-server.net", "static-access.net", "realm.cz", +"on.crisp.email", "cupcake.is", "curv.dev", "cyclic.app", @@ -7049,6 +7053,7 @@ "csx.cc", "mintere.site", "forte.id", +"modx.dev", "mozilla-iot.org", "bmoattachments.org", "net.ru", @@ -7084,6 +7089,7 @@ "sa.ngrok.io", "us.ngrok.io", "ngrok.pizza", +"ngrok.pro", "torun.pl", "nh-serv.co.uk", "nfshost.com", @@ -7295,6 +7301,8 @@ "ladesk.com", "qbuser.com", "cloudsite.builders", +"myradweb.net", +"servername.us", "instances.spawn.cc", "instantcloud.cn", "ras.ru", @@ -7312,10 +7320,39 @@ "rhcloud.com", "app.render.com", "onrender.com", +"replit.app", +"id.replit.app", "firewalledreplit.co", "id.firewalledreplit.co", "repl.co", "id.repl.co", +"replit.dev", +"archer.replit.dev", +"bones.replit.dev", +"canary.replit.dev", +"global.replit.dev", +"hacker.replit.dev", +"id.replit.dev", +"janeway.replit.dev", +"kim.replit.dev", +"kira.replit.dev", +"kirk.replit.dev", +"odo.replit.dev", +"paris.replit.dev", +"picard.replit.dev", +"pike.replit.dev", +"prerelease.replit.dev", +"reed.replit.dev", +"riker.replit.dev", +"sisko.replit.dev", +"spock.replit.dev", +"staging.replit.dev", +"sulu.replit.dev", +"tarpit.replit.dev", +"teams.replit.dev", +"tucker.replit.dev", +"wesley.replit.dev", +"worf.replit.dev", "repl.run", "resindevice.io", "devices.resinstaging.io", @@ -7462,6 +7499,11 @@ "try-snowplow.com", "srht.site", "stackhero-network.com", +"runs.onstackit.cloud", +"stackit.gg", +"stackit.rocks", +"stackit.run", +"stackit.zone", "musician.io", "novecore.site", "static.land", @@ -7496,8 +7538,21 @@ "myspreadshop.se", "myspreadshop.co.uk", "api.stdlib.com", +"feedback.ac", +"forms.ac", +"assessments.cx", +"calculators.cx", +"funnels.cx", +"paynow.cx", +"quizzes.cx", +"researched.cx", +"tests.cx", +"surveys.so", "storipress.app", "storj.farm", +"streak-link.com", +"streaklinks.com", +"streakusercontent.com", "utwente.io", "soc.srcf.net", "user.srcf.net", @@ -7656,6 +7711,8 @@ "me.vu", "v.ua", "wafflecell.com", +"webflow.io", +"webflowtest.io", "reserve-online.net", "reserve-online.com", "bookonline.app", diff -Nru pdns-recursor-4.8.6/rec-zonetocache.cc pdns-recursor-4.8.7/rec-zonetocache.cc --- pdns-recursor-4.8.6/rec-zonetocache.cc 2024-02-13 12:37:14.000000000 +0000 +++ pdns-recursor-4.8.7/rec-zonetocache.cc 2024-03-06 13:48:26.000000000 +0000 @@ -268,7 +268,7 @@ } skeyset_t validKeys; - vState dnsKeyState = validateDNSKeysAgainstDS(d_now, d_zone, dsmap, dnsKeys, records, zonemd.getRRSIGs(), validKeys, validationContext); + vState dnsKeyState = validateDNSKeysAgainstDS(d_now, d_zone, dsmap, dnsKeys, records, zonemd.getRRSIGs(QType::DNSKEY), validKeys, validationContext); if (dnsKeyState != vState::Secure) { return dnsKeyState; } @@ -299,7 +299,7 @@ for (const auto& rec : zonemd.getNSEC3Params()) { records.emplace(rec); } - nsecValidationStatus = validateWithKeySet(d_now, d_zone, records, zonemd.getRRSIGs(), validKeys, validationContext); + nsecValidationStatus = validateWithKeySet(d_now, d_zone, records, zonemd.getRRSIGs(QType::NSEC3PARAM), validKeys, validationContext); if (nsecValidationStatus != vState::Secure) { d_log->info("NSEC3PARAMS records did not validate"); return nsecValidationStatus; @@ -332,7 +332,7 @@ for (const auto& rec : zonemdRecords) { records.emplace(rec); } - return validateWithKeySet(d_now, d_zone, records, zonemd.getRRSIGs(), validKeys, validationContext); + return validateWithKeySet(d_now, d_zone, records, zonemd.getRRSIGs(QType::ZONEMD), validKeys, validationContext); } void ZoneData::ZoneToCache(const RecZoneToCache::Config& config) diff -Nru pdns-recursor-4.8.6/rec_control.1 pdns-recursor-4.8.7/rec_control.1 --- pdns-recursor-4.8.6/rec_control.1 2024-02-13 12:38:18.000000000 +0000 +++ pdns-recursor-4.8.7/rec_control.1 2024-03-06 13:49:35.000000000 +0000 @@ -27,7 +27,7 @@ .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "REC_CONTROL" "1" "Feb 13, 2024" "" "PowerDNS Recursor" +.TH "REC_CONTROL" "1" "Mar 06, 2024" "" "PowerDNS Recursor" .SH NAME rec_control \- Command line tool to control a running Recursor .SH SYNOPSIS diff -Nru pdns-recursor-4.8.6/root-addresses.hh pdns-recursor-4.8.7/root-addresses.hh --- pdns-recursor-4.8.6/root-addresses.hh 2024-02-13 12:37:14.000000000 +0000 +++ pdns-recursor-4.8.7/root-addresses.hh 2024-03-06 13:48:26.000000000 +0000 @@ -22,7 +22,7 @@ #pragma once static const char* const rootIps4[]={"198.41.0.4", // a.root-servers.net. - "199.9.14.201", // b.root-servers.net. + "170.247.170.2", // b.root-servers.net. "192.33.4.12", // c.root-servers.net. "199.7.91.13", // d.root-servers.net. "192.203.230.10", // e.root-servers.net. @@ -38,7 +38,7 @@ static size_t const rootIps4Count = sizeof(rootIps4) / sizeof(*rootIps4); static const char* const rootIps6[]={"2001:503:ba3e::2:30", // a.root-servers.net. - "2001:500:200::b", // b.root-servers.net. + "2801:1b8:10::b", // b.root-servers.net. "2001:500:2::c", // c.root-servers.net. "2001:500:2d::d", // d.root-servers.net. "2001:500:a8::e", // e.root-servers.net. diff -Nru pdns-recursor-4.8.6/syncres.cc pdns-recursor-4.8.7/syncres.cc --- pdns-recursor-4.8.6/syncres.cc 2024-02-13 12:37:14.000000000 +0000 +++ pdns-recursor-4.8.7/syncres.cc 2024-03-06 13:48:26.000000000 +0000 @@ -4290,11 +4290,37 @@ sanitizeRecords(prefix, lwr, qname, qtype, auth, wasForwarded, rdQuery); std::vector> authorityRecs; - const unsigned int labelCount = qname.countLabels(); bool isCNAMEAnswer = false; bool isDNAMEAnswer = false; DNSName seenAuth; + // names that might be expanded from a wildcard, and thus require denial of existence proof + // this is the queried name and any part of the CNAME chain from the queried name + // the key is the name itself, the value is initially false and is set to true once we have + // confirmed it was actually expanded from a wildcard + std::map wildcardCandidates{{qname, false}}; + + if (rdQuery) { + std::unordered_map cnames; + for (const auto& rec : lwr.d_records) { + if (rec.d_type != QType::CNAME || rec.d_class != QClass::IN) { + continue; + } + if (auto content = getRR(rec)) { + cnames[rec.d_name] = DNSName(content->getTarget()); + } + } + auto initial = qname; + while (true) { + auto cnameIt = cnames.find(initial); + if (cnameIt == cnames.end()) { + break; + } + initial = cnameIt->second; + wildcardCandidates.emplace(initial, false); + } + } + for (auto& rec : lwr.d_records) { if (rec.d_type == QType::OPT || rec.d_class != QClass::IN) { continue; @@ -4314,6 +4340,7 @@ seenAuth = rec.d_name; } + const auto labelCount = rec.d_name.countLabels(); if (rec.d_type == QType::RRSIG) { auto rrsig = getRR(rec); if (rrsig) { @@ -4321,7 +4348,8 @@ count can be lower than the name's label count if it was synthesized from the wildcard. Note that the difference might be > 1. */ - if (rec.d_name == qname && isWildcardExpanded(labelCount, rrsig)) { + if (auto wcIt = wildcardCandidates.find(rec.d_name); wcIt != wildcardCandidates.end() && isWildcardExpanded(labelCount, rrsig)) { + wcIt->second = true; gatherWildcardProof = true; if (!isWildcardExpandedOntoItself(rec.d_name, labelCount, rrsig)) { /* if we have a wildcard expanded onto itself, we don't need to prove @@ -4598,7 +4626,13 @@ if (isAA && i->first.type == QType::NS && s_save_parent_ns_set) { rememberParentSetIfNeeded(i->first.name, i->second.records, depth); } - g_recCache->replace(d_now.tv_sec, i->first.name, i->first.type, i->second.records, i->second.signatures, authorityRecs, i->first.type == QType::DS ? true : isAA, auth, i->first.place == DNSResourceRecord::ANSWER ? ednsmask : boost::none, d_routingTag, recordState, remoteIP, d_refresh); + bool thisRRNeedsWildcardProof = false; + if (gatherWildcardProof) { + if (auto wcIt = wildcardCandidates.find(i->first.name); wcIt != wildcardCandidates.end() && wcIt->second) { + thisRRNeedsWildcardProof = true; + } + } + g_recCache->replace(d_now.tv_sec, i->first.name, i->first.type, i->second.records, i->second.signatures, thisRRNeedsWildcardProof ? authorityRecs : std::vector>(), i->first.type == QType::DS ? true : isAA, auth, i->first.place == DNSResourceRecord::ANSWER ? ednsmask : boost::none, d_routingTag, recordState, remoteIP, d_refresh); // Delete potential negcache entry. When a record recovers with serve-stale the negcache entry can cause the wrong entry to // be served, as negcache entries are checked before record cache entries @@ -4606,10 +4640,11 @@ g_negCache->wipeTyped(i->first.name, i->first.type); } - if (g_aggressiveNSECCache && needWildcardProof && recordState == vState::Secure && i->first.place == DNSResourceRecord::ANSWER && i->first.name == qname && !i->second.signatures.empty() && !d_routingTag && !ednsmask) { + if (g_aggressiveNSECCache && thisRRNeedsWildcardProof && recordState == vState::Secure && i->first.place == DNSResourceRecord::ANSWER && i->first.name == qname && !i->second.signatures.empty() && !d_routingTag && !ednsmask) { /* we have an answer synthesized from a wildcard and aggressive NSEC is enabled, we need to store the wildcard in its non-expanded form in the cache to be able to synthesize wildcard answers later */ const auto& rrsig = i->second.signatures.at(0); + const auto labelCount = i->first.name.countLabels(); if (isWildcardExpanded(labelCount, rrsig) && !isWildcardExpandedOntoItself(i->first.name, labelCount, rrsig)) { DNSName realOwner = getNSECOwnerName(i->first.name, i->second.signatures); @@ -4642,6 +4677,13 @@ } } + if (gatherWildcardProof) { + if (auto wcIt = wildcardCandidates.find(qname); wcIt != wildcardCandidates.end() && !wcIt->second) { + // the queried name was not expanded from a wildcard, a record in the CNAME chain was, so we don't need to gather wildcard proof now: we will do that when looking up the CNAME chain + gatherWildcardProof = false; + } + } + return RCode::NoError; } @@ -5003,6 +5045,11 @@ ne.d_ttd = d_now.tv_sec + lowestTTL; ne.d_orig_ttl = lowestTTL; if (qtype.getCode()) { // prevents us from NXDOMAIN'ing a whole domain + // doCNAMECacheCheck() checks record cache and does not look into negcache. That means that an old record might be found if + // serve-stale is active. Avoid that by explicitly zapping that CNAME record. + if (qtype == QType::CNAME && MemRecursorCache::s_maxServedStaleExtensions > 0) { + g_recCache->doWipeCache(qname, false, qtype); + } g_negCache->add(ne); } diff -Nru pdns-recursor-4.8.6/test-aggressive_nsec_cc.cc pdns-recursor-4.8.7/test-aggressive_nsec_cc.cc --- pdns-recursor-4.8.6/test-aggressive_nsec_cc.cc 2024-02-13 12:37:14.000000000 +0000 +++ pdns-recursor-4.8.7/test-aggressive_nsec_cc.cc 2024-03-06 13:48:26.000000000 +0000 @@ -1398,7 +1398,6 @@ BOOST_CHECK_EQUAL(cache->getEntriesCount(), 1U); /* the cache should now be able to deny other types (except the DS) */ - /* the cache should now be able to deny other types (except the DS) */ BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, name, QType::AAAA, RCode::NoError, 3U), true); /* but not the DS that lives in the parent zone */ BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, name, QType::DS, std::nullopt, 0U), false); diff -Nru pdns-recursor-4.8.6/test-syncres_cc10.cc pdns-recursor-4.8.7/test-syncres_cc10.cc --- pdns-recursor-4.8.6/test-syncres_cc10.cc 2024-02-13 12:37:14.000000000 +0000 +++ pdns-recursor-4.8.7/test-syncres_cc10.cc 2024-03-06 13:48:26.000000000 +0000 @@ -1628,6 +1628,158 @@ BOOST_CHECK_EQUAL(lookupCount, 3U); } +BOOST_AUTO_TEST_CASE(test_servestale_cname_to_nodata) +{ + std::unique_ptr sr; + initSR(sr); + MemRecursorCache::s_maxServedStaleExtensions = 1440; + NegCache::s_maxServedStaleExtensions = 1440; + + primeHints(); + + const DNSName target("www.powerdns.com."); + const DNSName auth("powerdns.com."); + + std::set downServers; + size_t downCount = 0; + size_t lookupCount = 0; + bool cnameOK = true; + + const time_t theTTL = 5; + const time_t negTTL = 60; + + sr->setAsyncCallback([&downServers, &downCount, &lookupCount, &cnameOK, target, auth](const ComboAddress& ipAddress, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional& /* srcmask */, const boost::optional& /* context */, LWResult* res, bool* /* chained */) { + /* this will cause issue with qname minimization if we ever implement it */ + if (downServers.find(ipAddress) != downServers.end()) { + downCount++; + return LWResult::Result::Timeout; + } + + if (isRootServer(ipAddress)) { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY); + addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL); + addRecordToLW(res, "a.gtld-servers.net.", QType::AAAA, "2001:DB8::1", DNSResourceRecord::ADDITIONAL); + return LWResult::Result::Success; + } + if (ipAddress == ComboAddress("192.0.2.1:53") || ipAddress == ComboAddress("[2001:DB8::1]:53")) { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, theTTL); + addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns2.powerdns.com.", DNSResourceRecord::AUTHORITY, theTTL); + addRecordToLW(res, "pdns-public-ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, theTTL); + addRecordToLW(res, "pdns-public-ns1.powerdns.com.", QType::AAAA, "2001:DB8::2", DNSResourceRecord::ADDITIONAL, theTTL); + addRecordToLW(res, "pdns-public-ns2.powerdns.com.", QType::A, "192.0.2.3", DNSResourceRecord::ADDITIONAL, theTTL); + addRecordToLW(res, "pdns-public-ns2.powerdns.com.", QType::AAAA, "2001:DB8::3", DNSResourceRecord::ADDITIONAL, theTTL); + return LWResult::Result::Success; + } + if (ipAddress == ComboAddress("192.0.2.2:53") || ipAddress == ComboAddress("192.0.2.3:53") || ipAddress == ComboAddress("[2001:DB8::2]:53") || ipAddress == ComboAddress("[2001:DB8::3]:53")) { + if (cnameOK) { + setLWResult(res, 0, true, false, true); + addRecordToLW(res, target, QType::CNAME, "cname.powerdns.com.", DNSResourceRecord::ANSWER, 5); + addRecordToLW(res, DNSName("cname.powerdns.com"), QType::A, "192.0.2.4", DNSResourceRecord::ANSWER, theTTL); + lookupCount++; + return LWResult::Result::Success; + } + setLWResult(res, RCode::NoError, true, false, true); + addRecordToLW(res, auth, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 60", DNSResourceRecord::AUTHORITY, negTTL); + lookupCount++; + return LWResult::Result::Success; + } + return LWResult::Result::Timeout; + }); + + time_t now = time(nullptr); + + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_REQUIRE_EQUAL(ret.size(), 2U); + BOOST_CHECK(ret[0].d_type == QType::CNAME); + BOOST_CHECK(ret[1].d_type == QType::A); + BOOST_CHECK_EQUAL(ret[0].d_name, target); + BOOST_CHECK_EQUAL(ret[1].d_name, DNSName("cname.powerdns.com")); + BOOST_CHECK_EQUAL(downCount, 0U); + BOOST_CHECK_EQUAL(lookupCount, 2U); + + downServers.insert(ComboAddress("192.0.2.2:53")); + downServers.insert(ComboAddress("192.0.2.3:53")); + downServers.insert(ComboAddress("[2001:DB8::2]:53")); + downServers.insert(ComboAddress("[2001:DB8::3]:53")); + + sr->setNow(timeval{now + theTTL + 1, 0}); + + // record is expired, so serve stale should kick in + ret.clear(); + res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_REQUIRE_EQUAL(ret.size(), 2U); + BOOST_CHECK(ret[0].d_type == QType::CNAME); + BOOST_CHECK(ret[1].d_type == QType::A); + BOOST_CHECK_EQUAL(ret[0].d_name, target); + BOOST_CHECK_EQUAL(ret[1].d_name, DNSName("cname.powerdns.com")); + BOOST_CHECK_EQUAL(downCount, 8U); + BOOST_CHECK_EQUAL(lookupCount, 2U); + + // Again, no lookup as the record is marked stale + ret.clear(); + res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_REQUIRE_EQUAL(ret.size(), 2U); + BOOST_CHECK(ret[0].d_type == QType::CNAME); + BOOST_CHECK(ret[1].d_type == QType::A); + BOOST_CHECK_EQUAL(ret[0].d_name, target); + BOOST_CHECK_EQUAL(ret[1].d_name, DNSName("cname.powerdns.com")); + BOOST_CHECK_EQUAL(downCount, 8U); + BOOST_CHECK_EQUAL(lookupCount, 2U); + + // Again, no lookup as the record is marked stale but as the TTL has passed a task should have been pushed + sr->setNow(timeval{now + 2 * (theTTL + 1), 0}); + ret.clear(); + res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_REQUIRE_EQUAL(ret.size(), 2U); + BOOST_CHECK(ret[0].d_type == QType::CNAME); + BOOST_CHECK(ret[1].d_type == QType::A); + BOOST_CHECK_EQUAL(ret[0].d_name, target); + BOOST_CHECK_EQUAL(ret[1].d_name, DNSName("cname.powerdns.com")); + BOOST_CHECK_EQUAL(downCount, 8U); + BOOST_CHECK_EQUAL(lookupCount, 2U); + + BOOST_REQUIRE_EQUAL(getTaskSize(), 2U); + auto task = taskQueuePop(); + BOOST_CHECK(task.d_qname == target); + BOOST_CHECK_EQUAL(task.d_qtype, QType::CNAME); + task = taskQueuePop(); + BOOST_CHECK(task.d_qname == DNSName("cname.powerdns.com")); + BOOST_CHECK_EQUAL(task.d_qtype, QType::A); + + // Now simulate a succeeding task execution and NoDATA on explicit CNAME result becomes available + cnameOK = false; + sr->setNow(timeval{now + 3 * (theTTL + 1), 0}); + downServers.clear(); + sr->setRefreshAlmostExpired(true); + + ret.clear(); + res = sr->beginResolve(target, QType(QType::CNAME), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_REQUIRE_EQUAL(ret.size(), 1U); + BOOST_CHECK(ret[0].d_type == QType::SOA); + BOOST_CHECK_EQUAL(ret[0].d_name, auth); + BOOST_CHECK_EQUAL(downCount, 8U); + BOOST_CHECK_EQUAL(lookupCount, 3U); + + // And again, result should come from cache + sr->setRefreshAlmostExpired(false); + ret.clear(); + res = sr->beginResolve(target, QType(QType::CNAME), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_REQUIRE_EQUAL(ret.size(), 1U); + BOOST_CHECK(ret[0].d_type == QType::SOA); + BOOST_CHECK_EQUAL(ret[0].d_name, auth); + BOOST_CHECK_EQUAL(downCount, 8U); + BOOST_CHECK_EQUAL(lookupCount, 3U); +} + BOOST_AUTO_TEST_CASE(test_servestale_immediateservfail) { std::unique_ptr sr; diff -Nru pdns-recursor-4.8.6/test-syncres_cc3.cc pdns-recursor-4.8.7/test-syncres_cc3.cc --- pdns-recursor-4.8.6/test-syncres_cc3.cc 2024-02-13 12:37:14.000000000 +0000 +++ pdns-recursor-4.8.7/test-syncres_cc3.cc 2024-03-06 13:48:26.000000000 +0000 @@ -1214,6 +1214,79 @@ BOOST_CHECK_EQUAL(queriesCount, 4U); } +BOOST_AUTO_TEST_CASE(test_forward_zone_recurse_rd_dnssec_cname_wildcard_expanded) +{ + std::unique_ptr testSR; + initSR(testSR, true); + + setDNSSECValidation(testSR, DNSSECMode::ValidateAll); + + primeHints(); + /* unsigned */ + const DNSName target("test."); + /* signed */ + const DNSName cnameTarget("cname."); + testkeysset_t keys; + + auto luaconfsCopy = g_luaconfs.getCopy(); + luaconfsCopy.dsAnchors.clear(); + generateKeyMaterial(g_rootdnsname, DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys, luaconfsCopy.dsAnchors); + generateKeyMaterial(cnameTarget, DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys); + g_luaconfs.setState(luaconfsCopy); + + const ComboAddress forwardedNS("192.0.2.42:53"); + size_t queriesCount = 0; + + SyncRes::AuthDomain authDomain; + authDomain.d_rdForward = true; + authDomain.d_servers.push_back(forwardedNS); + (*SyncRes::t_sstorage.domainmap)[g_rootdnsname] = authDomain; + + testSR->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool sendRDQuery, int /* EDNS0Level */, struct timeval* /* now */, boost::optional& /* srcmask */, boost::optional /* context */, LWResult* res, bool* /* chained */) { + queriesCount++; + + BOOST_CHECK_EQUAL(sendRDQuery, true); + + if (address != forwardedNS) { + return LWResult::Result::Timeout; + } + + if (type == QType::DS || type == QType::DNSKEY) { + return genericDSAndDNSKEYHandler(res, domain, DNSName("."), type, keys); + } + + if (domain == target && type == QType::A) { + + setLWResult(res, 0, false, false, true); + addRecordToLW(res, target, QType::CNAME, cnameTarget.toString()); + addRecordToLW(res, cnameTarget, QType::A, "192.0.2.1"); + /* the RRSIG proves that the cnameTarget was expanded from a wildcard */ + addRRSIG(keys, res->d_records, cnameTarget, 300, false, boost::none, DNSName("*")); + /* we need to add the proof that this name does not exist, so the wildcard may apply */ + addNSECRecordToLW(DNSName("cnamd."), DNSName("cnamf."), {QType::A, QType::NSEC, QType::RRSIG}, 60, res->d_records); + addRRSIG(keys, res->d_records, cnameTarget, 300); + + return LWResult::Result::Success; + } + return LWResult::Result::Timeout; + }); + + vector ret; + int res = testSR->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(testSR->getValidationState(), vState::Insecure); + BOOST_REQUIRE_EQUAL(ret.size(), 5U); + BOOST_CHECK_EQUAL(queriesCount, 5U); + + /* again, to test the cache */ + ret.clear(); + res = testSR->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(testSR->getValidationState(), vState::Insecure); + BOOST_REQUIRE_EQUAL(ret.size(), 5U); + BOOST_CHECK_EQUAL(queriesCount, 5U); +} + BOOST_AUTO_TEST_CASE(test_auth_zone_oob) { std::unique_ptr sr; diff -Nru pdns-recursor-4.8.6/test-syncres_cc4.cc pdns-recursor-4.8.7/test-syncres_cc4.cc --- pdns-recursor-4.8.6/test-syncres_cc4.cc 2024-02-13 12:37:14.000000000 +0000 +++ pdns-recursor-4.8.7/test-syncres_cc4.cc 2024-03-06 13:48:26.000000000 +0000 @@ -461,7 +461,8 @@ pdns::validation::ValidationContext validationContext; BOOST_CHECK(validateWithKeySet(now, qname, recordcontents, sigs, keyset, validationContext) == vState::Secure); - BOOST_CHECK_EQUAL(validationContext.d_validationsCounter, 1U);} + BOOST_CHECK_EQUAL(validationContext.d_validationsCounter, 1U); +} BOOST_AUTO_TEST_CASE(test_dnssec_root_validation_csk) { diff -Nru pdns-recursor-4.8.6/zonemd.cc pdns-recursor-4.8.7/zonemd.cc --- pdns-recursor-4.8.6/zonemd.cc 2024-02-13 12:37:14.000000000 +0000 +++ pdns-recursor-4.8.7/zonemd.cc 2024-03-06 13:48:26.000000000 +0000 @@ -84,7 +84,7 @@ if (rrsig == nullptr) { throw PDNSException("Invalid RRSIG record"); } - d_rrsigs.emplace_back(rrsig); + d_rrsigs[rrsig->d_type].emplace_back(rrsig); if (rrsig->d_type == QType::NSEC) { d_nsecs.signatures.emplace_back(rrsig); } diff -Nru pdns-recursor-4.8.6/zonemd.hh pdns-recursor-4.8.7/zonemd.hh --- pdns-recursor-4.8.6/zonemd.hh 2024-02-13 12:37:14.000000000 +0000 +++ pdns-recursor-4.8.7/zonemd.hh 2024-03-06 13:48:26.000000000 +0000 @@ -65,9 +65,12 @@ } // Return the zone's apex RRSIGs - const std::vector>& getRRSIGs() const + [[nodiscard]] const std::vector>& getRRSIGs(QType requestedType) { - return d_rrsigs; + if (d_rrsigs.count(requestedType) == 0) { + d_rrsigs[requestedType] = {}; + } + return d_rrsigs[requestedType]; } // Return the zone's apex ZONEMDs @@ -138,8 +141,8 @@ std::shared_ptr d_soaRecordContent; std::set> d_dnskeys; - std::vector> d_rrsigs; std::vector> d_nsec3params; + std::map>> d_rrsigs; ContentSigPair d_nsecs; map d_nsec3s; DNSName d_nsec3label;