]> git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/recursordist/rec-zonetocache.cc
rec: CVE-2023-50387 and CVE-2023-50868
[thirdparty/pdns.git] / pdns / recursordist / rec-zonetocache.cc
1 /*
2 * This file is part of PowerDNS or dnsdist.
3 * Copyright -- PowerDNS.COM B.V. and its contributors
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of version 2 of the GNU General Public License as
7 * published by the Free Software Foundation.
8 *
9 * In addition, for the avoidance of any doubt, permission is granted to
10 * link this program with OpenSSL and to (re)distribute the binaries
11 * produced as the result of such linking.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 */
22
23 #include "rec-zonetocache.hh"
24
25 #include "syncres.hh"
26 #include "zoneparser-tng.hh"
27 #include "query-local-address.hh"
28 #include "axfr-retriever.hh"
29 #include "validate-recursor.hh"
30 #include "logging.hh"
31 #include "rec-lua-conf.hh"
32 #include "zonemd.hh"
33 #include "validate.hh"
34
35 #ifdef HAVE_LIBCURL
36 #include "minicurl.hh"
37 #endif
38
39 #include <fstream>
40
41 struct ZoneData
42 {
43 ZoneData(const std::shared_ptr<Logr::Logger>& log, const std::string& zone) :
44 d_log(log),
45 d_zone(zone),
46 d_now(time(nullptr)) {}
47
48 // Potentially the two fields below could be merged into a single map. ATM it is not clear to me
49 // if that would make the code easier to read.
50 std::map<pair<DNSName, QType>, vector<DNSRecord>> d_all;
51 std::map<pair<DNSName, QType>, vector<shared_ptr<const RRSIGRecordContent>>> d_sigs;
52
53 // Maybe use a SuffixMatchTree?
54 std::set<DNSName> d_delegations;
55
56 std::shared_ptr<Logr::Logger> d_log;
57 DNSName d_zone;
58 time_t d_now;
59
60 [[nodiscard]] bool isRRSetAuth(const DNSName& qname, QType qtype) const;
61 void parseDRForCache(DNSRecord& resourceRecord);
62 pdns::ZoneMD::Result getByAXFR(const RecZoneToCache::Config& config, pdns::ZoneMD& zonemd);
63 pdns::ZoneMD::Result processLines(const std::vector<std::string>& lines, const RecZoneToCache::Config& config, pdns::ZoneMD& zonemd);
64 void ZoneToCache(const RecZoneToCache::Config& config);
65 vState dnssecValidate(pdns::ZoneMD& zonemd, size_t& zonemdCount) const;
66 };
67
68 bool ZoneData::isRRSetAuth(const DNSName& qname, QType qtype) const
69 {
70 DNSName delegatedZone(qname);
71 if (qtype == QType::DS) {
72 delegatedZone.chopOff();
73 }
74 bool isDelegated = false;
75 for (;;) {
76 if (d_delegations.count(delegatedZone) > 0) {
77 isDelegated = true;
78 break;
79 }
80 delegatedZone.chopOff();
81 if (delegatedZone == g_rootdnsname || delegatedZone == d_zone) {
82 break;
83 }
84 }
85 return !isDelegated;
86 }
87
88 void ZoneData::parseDRForCache(DNSRecord& dnsRecord)
89 {
90 if (dnsRecord.d_class != QClass::IN) {
91 return;
92 }
93 const auto key = pair(dnsRecord.d_name, dnsRecord.d_type);
94
95 dnsRecord.d_ttl += d_now;
96
97 switch (dnsRecord.d_type) {
98 case QType::NSEC:
99 case QType::NSEC3:
100 break;
101 case QType::RRSIG: {
102 const auto rrsig = getRR<RRSIGRecordContent>(dnsRecord);
103 if (rrsig == nullptr) {
104 break;
105 }
106 const auto sigkey = pair(key.first, rrsig->d_type);
107 auto found = d_sigs.find(sigkey);
108 if (found != d_sigs.end()) {
109 found->second.push_back(rrsig);
110 }
111 else {
112 vector<shared_ptr<const RRSIGRecordContent>> sigsrr;
113 sigsrr.push_back(rrsig);
114 d_sigs.insert({sigkey, sigsrr});
115 }
116 break;
117 }
118 case QType::NS:
119 if (dnsRecord.d_name != d_zone) {
120 d_delegations.insert(dnsRecord.d_name);
121 }
122 break;
123 default:
124 break;
125 }
126
127 auto found = d_all.find(key);
128 if (found != d_all.end()) {
129 found->second.push_back(dnsRecord);
130 }
131 else {
132 vector<DNSRecord> dnsRecords;
133 dnsRecords.push_back(dnsRecord);
134 d_all.insert({key, dnsRecords});
135 }
136 }
137
138 pdns::ZoneMD::Result ZoneData::getByAXFR(const RecZoneToCache::Config& config, pdns::ZoneMD& zonemd)
139 {
140 ComboAddress primary = ComboAddress(config.d_sources.at(0), 53);
141 uint16_t axfrTimeout = config.d_timeout;
142 size_t maxReceivedBytes = config.d_maxReceivedBytes;
143 const TSIGTriplet tsigTriplet = config.d_tt;
144 ComboAddress local = config.d_local;
145 if (local == ComboAddress()) {
146 local = pdns::getQueryLocalAddress(primary.sin4.sin_family, 0);
147 }
148
149 AXFRRetriever axfr(primary, d_zone, tsigTriplet, &local, maxReceivedBytes, axfrTimeout);
150 Resolver::res_t nop;
151 vector<DNSRecord> chunk;
152 time_t axfrStart = time(nullptr);
153 time_t axfrNow = time(nullptr);
154
155 // coverity[store_truncates_time_t]
156 while (axfr.getChunk(nop, &chunk, (axfrStart + axfrTimeout - axfrNow)) != 0) {
157 for (auto& dnsRecord : chunk) {
158 if (config.d_zonemd != pdns::ZoneMD::Config::Ignore) {
159 zonemd.readRecord(dnsRecord);
160 }
161 parseDRForCache(dnsRecord);
162 }
163 axfrNow = time(nullptr);
164 if (axfrNow < axfrStart || axfrNow - axfrStart > axfrTimeout) {
165 throw std::runtime_error("Total AXFR time for zoneToCache exceeded!");
166 }
167 }
168 if (config.d_zonemd != pdns::ZoneMD::Config::Ignore) {
169 bool validationDone = false;
170 bool validationSuccess = false;
171 zonemd.verify(validationDone, validationSuccess);
172 d_log->info(Logr::Info, "ZONEMD digest validation", "validationDone", Logging::Loggable(validationDone),
173 "validationSuccess", Logging::Loggable(validationSuccess));
174 if (!validationDone) {
175 return pdns::ZoneMD::Result::NoValidationDone;
176 }
177 if (!validationSuccess) {
178 return pdns::ZoneMD::Result::ValidationFailure;
179 }
180 }
181 return pdns::ZoneMD::Result::OK;
182 }
183
184 static std::vector<std::string> getLinesFromFile(const std::string& file)
185 {
186
187 std::vector<std::string> lines;
188 std::ifstream stream(file);
189 if (!stream) {
190 throw std::runtime_error("Cannot read file: " + file);
191 }
192 std::string line;
193 while (std::getline(stream, line)) {
194 lines.push_back(line);
195 }
196 return lines;
197 }
198
199 static std::vector<std::string> getURL(const RecZoneToCache::Config& config)
200 {
201 std::vector<std::string> lines;
202 #ifdef HAVE_LIBCURL
203 MiniCurl miniCurl;
204 ComboAddress local = config.d_local;
205 std::string reply = miniCurl.getURL(config.d_sources.at(0), nullptr, local == ComboAddress() ? nullptr : &local, static_cast<int>(config.d_timeout), false, true);
206 if (config.d_maxReceivedBytes > 0 && reply.size() > config.d_maxReceivedBytes) {
207 // We should actually detect this *during* the GET
208 throw std::runtime_error("Retrieved data exceeds maxReceivedBytes");
209 }
210 std::istringstream stream(reply);
211 string line;
212 while (std::getline(stream, line)) {
213 lines.push_back(line);
214 }
215 #else
216 throw std::runtime_error("url method configured but libcurl not compiled in");
217 #endif
218 return lines;
219 }
220
221 pdns::ZoneMD::Result ZoneData::processLines(const vector<string>& lines, const RecZoneToCache::Config& config, pdns::ZoneMD& zonemd)
222 {
223 DNSResourceRecord drr;
224 ZoneParserTNG zpt(lines, d_zone, true);
225 zpt.setMaxGenerateSteps(1);
226 zpt.setMaxIncludes(0);
227
228 while (zpt.get(drr)) {
229 DNSRecord dnsRecord(drr);
230 if (config.d_zonemd != pdns::ZoneMD::Config::Ignore) {
231 zonemd.readRecord(dnsRecord);
232 }
233 parseDRForCache(dnsRecord);
234 }
235 if (config.d_zonemd != pdns::ZoneMD::Config::Ignore) {
236 bool validationDone = false;
237 bool validationSuccess = false;
238 zonemd.verify(validationDone, validationSuccess);
239 d_log->info(Logr::Info, "ZONEMD digest validation", "validationDone", Logging::Loggable(validationDone),
240 "validationSuccess", Logging::Loggable(validationSuccess));
241 if (!validationDone) {
242 return pdns::ZoneMD::Result::NoValidationDone;
243 }
244 if (!validationSuccess) {
245 return pdns::ZoneMD::Result::ValidationFailure;
246 }
247 }
248 return pdns::ZoneMD::Result::OK;
249 }
250
251 vState ZoneData::dnssecValidate(pdns::ZoneMD& zonemd, size_t& zonemdCount) const
252 {
253 pdns::validation::ValidationContext validationContext;
254 validationContext.d_nsec3IterationsRemainingQuota = std::numeric_limits<decltype(validationContext.d_nsec3IterationsRemainingQuota)>::max();
255 zonemdCount = 0;
256
257 SyncRes resolver({d_now, 0});
258 resolver.setDoDNSSEC(true);
259 resolver.setDNSSECValidationRequested(true);
260
261 dsmap_t dsmap; // Actually a set
262 vState dsState = resolver.getDSRecords(d_zone, dsmap, false, 0, "");
263 if (dsState != vState::Secure) {
264 return dsState;
265 }
266
267 skeyset_t dnsKeys;
268 sortedRecords_t records;
269 if (zonemd.getDNSKEYs().empty()) {
270 return vState::BogusUnableToGetDNSKEYs;
271 }
272 for (const auto& key : zonemd.getDNSKEYs()) {
273 dnsKeys.emplace(key);
274 records.emplace(key);
275 }
276
277 skeyset_t validKeys;
278 vState dnsKeyState = validateDNSKeysAgainstDS(d_now, d_zone, dsmap, dnsKeys, records, zonemd.getRRSIGs(), validKeys, std::nullopt, validationContext);
279 if (dnsKeyState != vState::Secure) {
280 return dnsKeyState;
281 }
282
283 if (validKeys.empty()) {
284 return vState::BogusNoValidDNSKEY;
285 }
286
287 auto zonemdRecords = zonemd.getZONEMDs();
288 zonemdCount = zonemdRecords.size();
289
290 // De we need to do a denial validation?
291 if (zonemdCount == 0) {
292 const auto& nsecs = zonemd.getNSECs();
293 const auto& nsec3s = zonemd.getNSEC3s();
294 cspmap_t csp;
295
296 vState nsecValidationStatus = vState::Indeterminate;
297
298 if (!nsecs.records.empty() && !nsecs.signatures.empty()) {
299 // Valdidate the NSEC
300 nsecValidationStatus = validateWithKeySet(d_now, d_zone, nsecs.records, nsecs.signatures, validKeys, std::nullopt, validationContext);
301 csp.emplace(std::pair(d_zone, QType::NSEC), nsecs);
302 }
303 else if (!nsec3s.records.empty() && !nsec3s.signatures.empty()) {
304 // Validate NSEC3PARAMS
305 records.clear();
306 for (const auto& rec : zonemd.getNSEC3Params()) {
307 records.emplace(rec);
308 }
309 nsecValidationStatus = validateWithKeySet(d_now, d_zone, records, zonemd.getRRSIGs(), validKeys, std::nullopt, validationContext);
310 if (nsecValidationStatus != vState::Secure) {
311 d_log->info(Logr::Warning, "NSEC3PARAMS records did not validate");
312 return nsecValidationStatus;
313 }
314 // Valdidate the NSEC3
315 nsecValidationStatus = validateWithKeySet(d_now, zonemd.getNSEC3Label(), nsec3s.records, nsec3s.signatures, validKeys, std::nullopt, validationContext);
316 csp.emplace(std::pair(zonemd.getNSEC3Label(), QType::NSEC3), nsec3s);
317 }
318 else {
319 d_log->info(Logr::Warning, "No NSEC(3) records and/or RRSIGS found to deny ZONEMD");
320 return vState::BogusInvalidDenial;
321 }
322
323 if (nsecValidationStatus != vState::Secure) {
324 d_log->info(Logr::Warning, "zone NSEC(3) record does not validate");
325 return nsecValidationStatus;
326 }
327
328 auto denial = getDenial(csp, d_zone, QType::ZONEMD, false, false, validationContext, std::nullopt, true);
329 if (denial == dState::NXQTYPE) {
330 d_log->info(Logr::Info, "Validated denial of existence of ZONEMD record");
331 return vState::Secure;
332 }
333 d_log->info(Logr::Warning, "No ZONEMD record, but NSEC(3) record does not deny it");
334 return vState::BogusInvalidDenial;
335 }
336
337 // Collect the ZONEMD records and validate them using the validated DNSSKEYs
338 records.clear();
339 for (const auto& rec : zonemdRecords) {
340 records.emplace(rec);
341 }
342 return validateWithKeySet(d_now, d_zone, records, zonemd.getRRSIGs(), validKeys, std::nullopt, validationContext);
343 }
344
345 void ZoneData::ZoneToCache(const RecZoneToCache::Config& config)
346 {
347 if (config.d_sources.size() > 1) {
348 d_log->info(Logr::Warning, "Multiple sources not yet supported, using first");
349 }
350
351 if (config.d_dnssec == pdns::ZoneMD::Config::Require && (g_dnssecmode == DNSSECMode::Off || g_dnssecmode == DNSSECMode::ProcessNoValidate)) {
352 throw PDNSException("ZONEMD DNSSEC validation failure: DNSSEC validation is switched off but required by ZoneToCache");
353 }
354
355 // First scan all records collecting info about delegations and sigs
356 // A this moment, we ignore NSEC and NSEC3 records. It is not clear to me yet under which conditions
357 // they could be entered in into the (neg)cache.
358
359 auto zonemd = pdns::ZoneMD(DNSName(config.d_zone));
360 pdns::ZoneMD::Result result = pdns::ZoneMD::Result::OK;
361 if (config.d_method == "axfr") {
362 d_log->info(Logr::Info, "Getting zone by AXFR");
363 result = getByAXFR(config, zonemd);
364 }
365 else {
366 vector<string> lines;
367 if (config.d_method == "url") {
368 d_log->info(Logr::Info, "Getting zone by URL");
369 lines = getURL(config);
370 }
371 else if (config.d_method == "file") {
372 d_log->info(Logr::Info, "Getting zone from file");
373 lines = getLinesFromFile(config.d_sources.at(0));
374 }
375 result = processLines(lines, config, zonemd);
376 }
377
378 // Validate DNSKEYs and ZONEMD, rest of records are validated on-demand by SyncRes
379 if (config.d_dnssec == pdns::ZoneMD::Config::Require || (g_dnssecmode != DNSSECMode::Off && g_dnssecmode != DNSSECMode::ProcessNoValidate && config.d_dnssec != pdns::ZoneMD::Config::Ignore)) {
380 size_t zonemdCount = 0;
381 auto validationStatus = dnssecValidate(zonemd, zonemdCount);
382 d_log->info(Logr::Info, "ZONEMD record related DNSSEC validation", "validationStatus", Logging::Loggable(validationStatus),
383 "zonemdCount", Logging::Loggable(zonemdCount));
384 if (config.d_dnssec == pdns::ZoneMD::Config::Require && validationStatus != vState::Secure) {
385 throw PDNSException("ZONEMD required DNSSEC validation failed");
386 }
387 if (validationStatus != vState::Secure && validationStatus != vState::Insecure) {
388 throw PDNSException("ZONEMD record DNSSEC validation failed");
389 }
390 }
391
392 if (config.d_zonemd == pdns::ZoneMD::Config::Require && result != pdns::ZoneMD::Result::OK) {
393 // We do not accept NoValidationDone in this case
394 throw PDNSException("ZONEMD digest validation failure");
395 return;
396 }
397 if (config.d_zonemd == pdns::ZoneMD::Config::Validate && result == pdns::ZoneMD::Result::ValidationFailure) {
398 throw PDNSException("ZONEMD digest validation failure");
399 return;
400 }
401
402 // Rerun, now inserting the rrsets into the cache with associated sigs
403 d_now = time(nullptr);
404 for (const auto& [key, v] : d_all) {
405 const auto& [qname, qtype] = key;
406 switch (qtype) {
407 case QType::NSEC:
408 case QType::NSEC3:
409 case QType::RRSIG:
410 break;
411 default: {
412 vector<shared_ptr<const RRSIGRecordContent>> sigsrr;
413 auto iter = d_sigs.find(key);
414 if (iter != d_sigs.end()) {
415 sigsrr = iter->second;
416 }
417 bool auth = isRRSetAuth(qname, qtype);
418 // Same decision as updateCacheFromRecords() (we do not test for NSEC since we skip those completely)
419 if (auth || (qtype == QType::NS || qtype == QType::A || qtype == QType::AAAA || qtype == QType::DS)) {
420 g_recCache->replace(d_now, qname, qtype, v, sigsrr,
421 std::vector<std::shared_ptr<DNSRecord>>(), auth, d_zone);
422 }
423 break;
424 }
425 }
426 }
427 }
428
429 void RecZoneToCache::maintainStates(const map<DNSName, Config>& configs, map<DNSName, State>& states, uint64_t mygeneration)
430 {
431 // Delete states that have no config
432 for (auto it = states.begin(); it != states.end();) {
433 if (configs.find(it->first) == configs.end()) {
434 it = states.erase(it);
435 }
436 else {
437 it = ++it;
438 }
439 }
440 // Reset states for which the config generation changed and create new states for new configs
441 for (const auto& config : configs) {
442 auto state = states.find(config.first);
443 if (state != states.end()) {
444 if (state->second.d_generation != mygeneration) {
445 state->second = {0, 0, mygeneration};
446 }
447 }
448 else {
449 states.emplace(config.first, State{0, 0, mygeneration});
450 }
451 }
452 }
453
454 void RecZoneToCache::ZoneToCache(const RecZoneToCache::Config& config, RecZoneToCache::State& state)
455 {
456 if (state.d_waittime == 0 && state.d_lastrun > 0) {
457 // single shot
458 return;
459 }
460 if (state.d_lastrun > 0 && state.d_lastrun + state.d_waittime > time(nullptr)) {
461 return;
462 }
463 auto log = g_slog->withName("ztc")->withValues("zone", Logging::Loggable(config.d_zone));
464
465 state.d_waittime = config.d_retryOnError;
466 try {
467 ZoneData data(log, config.d_zone);
468 data.ZoneToCache(config);
469 state.d_waittime = config.d_refreshPeriod;
470 log->info(Logr::Info, "Loaded zone into cache", "refresh", Logging::Loggable(state.d_waittime));
471 }
472 catch (const PDNSException& e) {
473 log->error(Logr::Error, e.reason, "Unable to load zone into cache, will retry", "exception", Logging::Loggable("PDNSException"), "refresh", Logging::Loggable(state.d_waittime));
474 }
475 catch (const std::runtime_error& e) {
476 log->error(Logr::Error, e.what(), "Unable to load zone into cache, will retry", "exception", Logging::Loggable("std::runtime_error"), "refresh", Logging::Loggable(state.d_waittime));
477 }
478 catch (...) {
479 log->info(Logr::Error, "Unable to load zone into cache, will retry", "refresh", Logging::Loggable(state.d_waittime));
480 }
481 state.d_lastrun = time(nullptr);
482 }