diff --git a/SOURCES/bind-9.18-CVE-2026-1519-test.patch b/SOURCES/bind-9.18-CVE-2026-1519-test.patch new file mode 100644 index 0000000..6048e76 --- /dev/null +++ b/SOURCES/bind-9.18-CVE-2026-1519-test.patch @@ -0,0 +1,337 @@ +From 7df00188c93d1fb8496328976e00192dd657d5aa Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Nicki=20K=C5=99=C3=AD=C5=BEek?= +Date: Tue, 3 Feb 2026 18:25:04 +0100 +Subject: [PATCH] Reproducer for CVE-2026-1519 + +When a validating resolver processes a delegation from a DNSSEC-signed +zone which uses too many NSEC3 iterations, it should cease the attempt +to validate due to an NSEC3 iteration limit being exceeded and fall back +to insecure. + +(cherry picked from commit 9bc14a89f1313aa38330e84674ac3b7691db3383) +(cherry picked from commit 2c82f99a3c95f356861d5977f12ef9bbe2063cb6) +--- + .../system/nsec3-delegation/ns1/named.conf.j2 | 35 +++++++++++ + bin/tests/system/nsec3-delegation/ns1/root.db | 25 ++++++++ + .../ns2/iter-too-many.db.j2.manual | 31 ++++++++++ + .../system/nsec3-delegation/ns2/named.conf.j2 | 40 ++++++++++++ + .../nsec3-delegation/ns2/sub.iter-too-many.db | 24 ++++++++ + .../system/nsec3-delegation/ns3/named.conf.j2 | 37 +++++++++++ + .../nsec3-delegation/ns3/trusted.conf.j2 | 1 + + .../tests_excessive_nsec3_iterations.py | 61 +++++++++++++++++++ + 8 files changed, 254 insertions(+) + create mode 100644 bin/tests/system/nsec3-delegation/ns1/named.conf.j2 + create mode 100644 bin/tests/system/nsec3-delegation/ns1/root.db + create mode 100644 bin/tests/system/nsec3-delegation/ns2/iter-too-many.db.j2.manual + create mode 100644 bin/tests/system/nsec3-delegation/ns2/named.conf.j2 + create mode 100644 bin/tests/system/nsec3-delegation/ns2/sub.iter-too-many.db + create mode 100644 bin/tests/system/nsec3-delegation/ns3/named.conf.j2 + create mode 120000 bin/tests/system/nsec3-delegation/ns3/trusted.conf.j2 + create mode 100644 bin/tests/system/nsec3-delegation/tests_excessive_nsec3_iterations.py + +diff --git a/bin/tests/system/nsec3-delegation/ns1/named.conf.j2 b/bin/tests/system/nsec3-delegation/ns1/named.conf.j2 +new file mode 100644 +index 0000000000..65016d1c67 +--- /dev/null ++++ b/bin/tests/system/nsec3-delegation/ns1/named.conf.j2 +@@ -0,0 +1,35 @@ ++/* ++ * Copyright (C) Internet Systems Consortium, Inc. ("ISC") ++ * ++ * SPDX-License-Identifier: MPL-2.0 ++ * ++ * This Source Code Form is subject to the terms of the Mozilla Public ++ * License, v. 2.0. If a copy of the MPL was not distributed with this ++ * file, you can obtain one at https://mozilla.org/MPL/2.0/. ++ * ++ * See the COPYRIGHT file distributed with this work for additional ++ * information regarding copyright ownership. ++ */ ++ ++options { ++ query-source address 10.53.0.1; ++ notify-source 10.53.0.1; ++ transfer-source 10.53.0.1; ++ port @PORT@; ++ pid-file "named.pid"; ++ listen-on { 10.53.0.1; }; ++ listen-on-v6 { none; }; ++ recursion no; ++ dnssec-validation no; ++}; ++ ++controls { ++ inet 10.53.0.1 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; ++}; ++ ++include "../../_common/rndc.key"; ++ ++zone "." { ++ type primary; ++ file "root.db"; ++}; +diff --git a/bin/tests/system/nsec3-delegation/ns1/root.db b/bin/tests/system/nsec3-delegation/ns1/root.db +new file mode 100644 +index 0000000000..c3f80d0d4b +--- /dev/null ++++ b/bin/tests/system/nsec3-delegation/ns1/root.db +@@ -0,0 +1,25 @@ ++; Copyright (C) Internet Systems Consortium, Inc. ("ISC") ++; ++; SPDX-License-Identifier: MPL-2.0 ++; ++; This Source Code Form is subject to the terms of the Mozilla Public ++; License, v. 2.0. If a copy of the MPL was not distributed with this ++; file, you can obtain one at https://mozilla.org/MPL/2.0/. ++; ++; See the COPYRIGHT file distributed with this work for additional ++; information regarding copyright ownership. ++ ++$TTL 300 ++. IN SOA . . ( ++ 2025063000 ; serial ++ 600 ; refresh ++ 600 ; retry ++ 1200 ; expire ++ 600 ; minimum ++ ) ++. NS a.root-servers.nil. ++ ++a.root-servers.nil A 10.53.0.1 ++ ++iter-too-many. NS ns2.iter-too-many. ++ns2.iter-too-many. A 10.53.0.2 +diff --git a/bin/tests/system/nsec3-delegation/ns2/iter-too-many.db.j2.manual b/bin/tests/system/nsec3-delegation/ns2/iter-too-many.db.j2.manual +new file mode 100644 +index 0000000000..fa5023d21b +--- /dev/null ++++ b/bin/tests/system/nsec3-delegation/ns2/iter-too-many.db.j2.manual +@@ -0,0 +1,31 @@ ++; Copyright (C) Internet Systems Consortium, Inc. ("ISC") ++; ++; SPDX-License-Identifier: MPL-2.0 ++; ++; This Source Code Form is subject to the terms of the Mozilla Public ++; License, v. 2.0. If a copy of the MPL was not distributed with this ++; file, you can obtain one at https://mozilla.org/MPL/2.0/. ++; ++; See the COPYRIGHT file distributed with this work for additional ++; information regarding copyright ownership. ++ ++{% raw %} ++$TTL 300 ++@ IN SOA ns2.iter-too-many. hostmaster.iter-too-many. ( ++ 2026020300 ; serial ++ 20 ; refresh (20 seconds) ++ 20 ; retry (20 seconds) ++ 1814400 ; expire (3 weeks) ++ 3600 ; minimum (1 hour) ++) ++ ++@ IN NS ns2.iter-too-many. ++ns2 IN A 10.53.0.2 ++ ++sub IN NS ns2.sub.iter-too-many. ++ns2.sub IN A 10.53.0.2 ++{% endraw %} ++ ++{% for dnskey in dnskeys %} ++@dnskey@ ++{% endfor %} +diff --git a/bin/tests/system/nsec3-delegation/ns2/named.conf.j2 b/bin/tests/system/nsec3-delegation/ns2/named.conf.j2 +new file mode 100644 +index 0000000000..2f4823574f +--- /dev/null ++++ b/bin/tests/system/nsec3-delegation/ns2/named.conf.j2 +@@ -0,0 +1,40 @@ ++/* ++ * Copyright (C) Internet Systems Consortium, Inc. ("ISC") ++ * ++ * SPDX-License-Identifier: MPL-2.0 ++ * ++ * This Source Code Form is subject to the terms of the Mozilla Public ++ * License, v. 2.0. If a copy of the MPL was not distributed with this ++ * file, you can obtain one at https://mozilla.org/MPL/2.0/. ++ * ++ * See the COPYRIGHT file distributed with this work for additional ++ * information regarding copyright ownership. ++ */ ++ ++options { ++ query-source address 10.53.0.2; ++ notify-source 10.53.0.2; ++ transfer-source 10.53.0.2; ++ port @PORT@; ++ pid-file "named.pid"; ++ listen-on { 10.53.0.2; }; ++ listen-on-v6 { none; }; ++ recursion no; ++ dnssec-validation no; ++}; ++ ++controls { ++ inet 10.53.0.2 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; ++}; ++ ++include "../../_common/rndc.key"; ++ ++zone "iter-too-many" { ++ type primary; ++ file "iter-too-many.signed.db"; ++}; ++ ++zone "sub.iter-too-many" { ++ type primary; ++ file "sub.iter-too-many.db"; ++}; +diff --git a/bin/tests/system/nsec3-delegation/ns2/sub.iter-too-many.db b/bin/tests/system/nsec3-delegation/ns2/sub.iter-too-many.db +new file mode 100644 +index 0000000000..09b2bb6fb3 +--- /dev/null ++++ b/bin/tests/system/nsec3-delegation/ns2/sub.iter-too-many.db +@@ -0,0 +1,24 @@ ++; Copyright (C) Internet Systems Consortium, Inc. ("ISC") ++; ++; SPDX-License-Identifier: MPL-2.0 ++; ++; This Source Code Form is subject to the terms of the Mozilla Public ++; License, v. 2.0. If a copy of the MPL was not distributed with this ++; file, you can obtain one at https://mozilla.org/MPL/2.0/. ++; ++; See the COPYRIGHT file distributed with this work for additional ++; information regarding copyright ownership. ++ ++$TTL 300 ++@ IN SOA ns2.sub.iter-too-many. hostmaster.sub.iter-too-many. ( ++ 2026020300 ; serial ++ 20 ; refresh (20 seconds) ++ 20 ; retry (20 seconds) ++ 1814400 ; expire (3 weeks) ++ 3600 ; minimum (1 hour) ++) ++ ++@ IN NS ns2.sub.iter-too-many. ++ns2 IN A 10.53.0.2 ++ ++example IN A 127.0.0.1 +diff --git a/bin/tests/system/nsec3-delegation/ns3/named.conf.j2 b/bin/tests/system/nsec3-delegation/ns3/named.conf.j2 +new file mode 100644 +index 0000000000..e36b88c53e +--- /dev/null ++++ b/bin/tests/system/nsec3-delegation/ns3/named.conf.j2 +@@ -0,0 +1,37 @@ ++/* ++ * Copyright (C) Internet Systems Consortium, Inc. ("ISC") ++ * ++ * SPDX-License-Identifier: MPL-2.0 ++ * ++ * This Source Code Form is subject to the terms of the Mozilla Public ++ * License, v. 2.0. If a copy of the MPL was not distributed with this ++ * file, you can obtain one at https://mozilla.org/MPL/2.0/. ++ * ++ * See the COPYRIGHT file distributed with this work for additional ++ * information regarding copyright ownership. ++ */ ++ ++options { ++ query-source address 10.53.0.3; ++ notify-source 10.53.0.3; ++ transfer-source 10.53.0.3; ++ port @PORT@; ++ pid-file "named.pid"; ++ listen-on { 10.53.0.3; }; ++ listen-on-v6 { none; }; ++ recursion yes; ++ dnssec-validation yes; ++}; ++ ++controls { ++ inet 10.53.0.3 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; ++}; ++ ++include "../../_common/rndc.key"; ++ ++zone "." { ++ type hint; ++ file "../../_common/root.hint"; ++}; ++ ++include "trusted.conf"; +diff --git a/bin/tests/system/nsec3-delegation/ns3/trusted.conf.j2 b/bin/tests/system/nsec3-delegation/ns3/trusted.conf.j2 +new file mode 120000 +index 0000000000..cb0be77b22 +--- /dev/null ++++ b/bin/tests/system/nsec3-delegation/ns3/trusted.conf.j2 +@@ -0,0 +1 @@ ++../../_common/trusted.conf.j2 +\ No newline at end of file +diff --git a/bin/tests/system/nsec3-delegation/tests_excessive_nsec3_iterations.py b/bin/tests/system/nsec3-delegation/tests_excessive_nsec3_iterations.py +new file mode 100644 +index 0000000000..f85384bb1e +--- /dev/null ++++ b/bin/tests/system/nsec3-delegation/tests_excessive_nsec3_iterations.py +@@ -0,0 +1,61 @@ ++# Copyright (C) Internet Systems Consortium, Inc. ("ISC") ++# ++# SPDX-License-Identifier: MPL-2.0 ++# ++# This Source Code Form is subject to the terms of the Mozilla Public ++# License, v. 2.0. If a copy of the MPL was not distributed with this ++# file, you can obtain one at https://mozilla.org/MPL/2.0/. ++# ++# See the COPYRIGHT file distributed with this work for additional ++# information regarding copyright ownership. ++ ++from isctest.run import EnvCmd ++ ++import isctest ++ ++ ++def bootstrap(): ++ templates = isctest.template.TemplateEngine(".") ++ keygen = EnvCmd("KEYGEN", "-a ECDSA256") ++ signer = EnvCmd("SIGNER") ++ ++ isctest.log.info("setup iter-too-many.") ++ zonename = "iter-too-many." ++ ksk_name = keygen(f"-f KSK {zonename}", cwd="ns2").out.strip() ++ zsk_name = keygen(f"{zonename}", cwd="ns2").out.strip() ++ ksk = isctest.kasp.Key(ksk_name, keydir="ns2") ++ zsk = isctest.kasp.Key(zsk_name, keydir="ns2") ++ dnskeys = [ksk.dnskey, zsk.dnskey] ++ ++ tdata = { ++ "dnskeys": dnskeys, ++ } ++ templates.render(f"ns2/{zonename}db", tdata, template=f"ns2/{zonename}db.j2.manual") ++ signer( ++ f"-P -o {zonename} -f {zonename}signed.db -3 A1B2C3D4 -H too-many -H 151 -S {zonename}db", ++ cwd="ns2", ++ ) ++ ++ return { ++ "trust_anchors": [ ++ ksk.into_ta("static-key"), ++ ], ++ } ++ ++ ++def test_excessive_nsec3_iterations_delegation(ns3): ++ # reproducer for CVE-2026-1519 [GL#5708] ++ zone = "example.sub.iter-too-many" ++ msg = isctest.query.create(zone, "A") ++ res = isctest.query.tcp(msg, ns3.ip) ++ ++ # an insecure response is expected regardless of the NSEC3 iteration limit, ++ # because the sub.iter-too-many. zone is unsigned. the real difference is ++ # in the CPU usage required for generating such response, but that can't be ++ # easily and reliably tested in an automated fashion ++ isctest.check.noerror(res) ++ ++ with ns3.watch_log_from_start() as watcher: ++ watcher.wait_for_line( ++ f"validating {zone}/A: validator_callback_ds: too many iterations" ++ ) +-- +2.53.0 + diff --git a/SOURCES/bind-9.18-CVE-2026-1519.patch b/SOURCES/bind-9.18-CVE-2026-1519.patch new file mode 100644 index 0000000..390fc73 --- /dev/null +++ b/SOURCES/bind-9.18-CVE-2026-1519.patch @@ -0,0 +1,259 @@ +From ca79e7575d281e3859afce73ac3276771ce7edbf Mon Sep 17 00:00:00 2001 +From: Matthijs Mekking +Date: Tue, 3 Mar 2026 10:40:36 +0100 +Subject: [PATCH] Check iterations in isdelegation() + +When looking up an NSEC3 as part of an insecurity proof, check the +number of iterations. If this is too high, treat the answer as insecure +by marking the answer with trust level "answer", indicating that they +did not validate, but could be cached as insecure. + +(cherry picked from commit 988040a5e02f86f4a8cdb0704e8d501f9082a89c) +(cherry picked from commit 85c21feff9acb0982fe60f2c88201bf55533bd0e) + +Don't verify already trusted rdatasets + +If we already marked an rdataset as secure (or it has even stronger +trust), there is no need to cryptographically verify it again. + +(cherry picked from commit 0ec08c212022d08c9717f2bc6bd3e8ebd6f034ce) +(cherry picked from commit 8890a91c1c16129333139b9d8a4381e0f741f0d6) + +Check RRset trust in validate_neg_rrset() + +In many places we only create a validator if the RRset has too low +trust (the RRset is pending validation, or could not be validated +before). This check was missing prior to validating negative response +data. + +(cherry picked from commit 6ca67f65cd685cf8699540a852c1e3775bd48d64) +(cherry picked from commit 85fcd704e2f7cc2a25d2195bc4bb28398c889ed3) +--- + lib/dns/include/dns/types.h | 1 + + lib/dns/validator.c | 91 ++++++++++++++++++++++++++++--------- + 2 files changed, 70 insertions(+), 22 deletions(-) + +diff --git a/lib/dns/include/dns/types.h b/lib/dns/include/dns/types.h +index f0aaa24..a945f0e 100644 +--- a/lib/dns/include/dns/types.h ++++ b/lib/dns/include/dns/types.h +@@ -353,6 +353,7 @@ enum { + ((x) == dns_trust_additional || (x) == dns_trust_pending_additional) + #define DNS_TRUST_GLUE(x) ((x) == dns_trust_glue) + #define DNS_TRUST_ANSWER(x) ((x) == dns_trust_answer) ++#define DNS_TRUST_SECURE(x) ((x) >= dns_trust_secure) + + /*% + * Name checking severities. +diff --git a/lib/dns/validator.c b/lib/dns/validator.c +index 5bc0b40..ae98b5f 100644 +--- a/lib/dns/validator.c ++++ b/lib/dns/validator.c +@@ -252,12 +252,25 @@ exit_check(dns_validator_t *val) { + } + + /*% +- * Look in the NSEC record returned from a DS query to see if there is +- * a NS RRset at this name. If it is found we are at a delegation point. ++ * The isdelegation() function is called as part of seeking the DS record. ++ * Look in the NSEC or NSEC3 record returned from a DS query to see if the ++ * record has the NS bitmap set. If so, we are at a delegation point. ++ * ++ * If the response contains NSEC3 records with too high iterations, we cannot ++ * (or rather we are not going to) validate the insecurity proof. Instead we ++ * are going to treat the message as insecure and just assume the DS was at ++ * the delegation. ++ * ++ * Returns: ++ *\li #ISC_R_SUCCESS the NS bitmap was set in the NSEC or NSEC3 record, or ++ * the NSEC3 covers the name (in case of opt-out), or ++ * we cannot validate the insecurity proof and are going ++ * to treat the message as isnecure. ++ *\li #ISC_R_NOTFOUND the NS bitmap was not set, + */ +-static bool +-isdelegation(dns_name_t *name, dns_rdataset_t *rdataset, +- isc_result_t dbresult) { ++static isc_result_t ++isdelegation(dns_validator_t *val, dns_name_t *name, dns_rdataset_t *rdataset, ++ isc_result_t dbresult, const char *caller) { + dns_fixedname_t fixed; + dns_label_t hashlabel; + dns_name_t nsec3name; +@@ -285,7 +298,7 @@ isdelegation(dns_name_t *name, dns_rdataset_t *rdataset, + goto trynsec3; + } + if (result != ISC_R_SUCCESS) { +- return (false); ++ return ISC_R_NOTFOUND; + } + } + +@@ -299,7 +312,7 @@ isdelegation(dns_name_t *name, dns_rdataset_t *rdataset, + dns_rdata_reset(&rdata); + } + dns_rdataset_disassociate(&set); +- return (found); ++ return found ? ISC_R_SUCCESS : ISC_R_NOTFOUND; + + trynsec3: + /* +@@ -335,6 +348,18 @@ trynsec3: + if (nsec3.hash != 1) { + continue; + } ++ /* ++ * If there are too many iterations assume bad things ++ * are happening and bail out early. Treat as if the ++ * DS was at the delegation. ++ */ ++ if (nsec3.iterations > DNS_NSEC3_MAXITERATIONS) { ++ validator_log(val, ISC_LOG_DEBUG(3), ++ "%s: too many iterations", ++ caller); ++ dns_rdataset_disassociate(&set); ++ return ISC_R_SUCCESS; ++ } + length = isc_iterated_hash( + hash, nsec3.hash, nsec3.iterations, nsec3.salt, + nsec3.salt_length, name->ndata, name->length); +@@ -346,7 +371,7 @@ trynsec3: + found = dns_nsec3_typepresent(&rdata, + dns_rdatatype_ns); + dns_rdataset_disassociate(&set); +- return (found); ++ return found ? ISC_R_SUCCESS : ISC_R_NOTFOUND; + } + if ((nsec3.flags & DNS_NSEC3FLAG_OPTOUT) == 0) { + continue; +@@ -362,12 +387,12 @@ trynsec3: + memcmp(hash, nsec3.next, length) < 0))) + { + dns_rdataset_disassociate(&set); +- return (true); ++ return ISC_R_SUCCESS; + } + } + dns_rdataset_disassociate(&set); + } +- return (found); ++ return found ? ISC_R_SUCCESS : ISC_R_NOTFOUND; + } + + /*% +@@ -583,8 +608,9 @@ fetch_callback_ds(isc_task_t *task, isc_event_t *event) { + } else if (eresult == DNS_R_SERVFAIL) { + goto unexpected; + } else if (eresult != DNS_R_CNAME && +- isdelegation(devent->foundname, &val->frdataset, +- eresult)) ++ isdelegation(val, devent->foundname, &val->frdataset, ++ eresult, ++ "fetch_callback_ds") == ISC_R_SUCCESS) + { + /* + * Failed to find a DS while trying to prove +@@ -744,10 +770,13 @@ validator_callback_ds(isc_task_t *task, isc_event_t *event) { + dns_trust_totext(val->frdataset.trust)); + have_dsset = (val->frdataset.type == dns_rdatatype_ds); + name = dns_fixedname_name(&val->fname); ++ + if ((val->attributes & VALATTR_INSECURITY) != 0 && + val->frdataset.covers == dns_rdatatype_ds && + NEGATIVE(&val->frdataset) && +- isdelegation(name, &val->frdataset, DNS_R_NCACHENXRRSET)) ++ isdelegation(val, name, &val->frdataset, ++ DNS_R_NCACHENXRRSET, ++ "validator_callback_ds") == ISC_R_SUCCESS) + { + result = markanswer(val, "validator_callback_ds", + "no DS and this is a delegation"); +@@ -1485,6 +1514,13 @@ verify(dns_validator_t *val, dst_key_t *key, dns_rdata_t *rdata, + bool ignore = false; + dns_name_t *wild; + ++ if (DNS_TRUST_SECURE(val->event->rdataset->trust)) { ++ /* ++ * This RRset was already verified before. ++ */ ++ return ISC_R_SUCCESS; ++ } ++ + val->attributes |= VALATTR_TRIEDVERIFY; + wild = dns_fixedname_initname(&fixed); + again: +@@ -2425,6 +2461,17 @@ validate_neg_rrset(dns_validator_t *val, dns_name_t *name, + } + } + ++ if (rdataset->type != dns_rdatatype_nsec && ++ DNS_TRUST_SECURE(rdataset->trust)) ++ { ++ /* ++ * The negative response data is already verified. ++ * We skip NSEC records, because they require special ++ * processing in validator_callback_nsec(). ++ */ ++ return DNS_R_CONTINUE; ++ } ++ + val->currentset = rdataset; + result = create_validator(val, name, rdataset->type, rdataset, + sigrdataset, validator_callback_nsec, +@@ -2535,11 +2582,9 @@ validate_ncache(dns_validator_t *val, bool resume) { + } + + result = validate_neg_rrset(val, name, rdataset, sigrdataset); +- if (result == DNS_R_CONTINUE) { +- continue; ++ if (result != DNS_R_CONTINUE) { ++ return result; + } +- +- return (result); + } + if (result == ISC_R_NOMORE) { + result = ISC_R_SUCCESS; +@@ -2566,7 +2611,7 @@ validate_nx(dns_validator_t *val, bool resume) { + isc_result_t result; + + if (resume) { +- validator_log(val, ISC_LOG_DEBUG(3), "resuming validate_nx"); ++ validator_log(val, ISC_LOG_DEBUG(3), "%s: resuming validate_nx", __func__); + } + + if (val->event->message == NULL) { +@@ -2588,7 +2633,7 @@ validate_nx(dns_validator_t *val, bool resume) { + result = findnsec3proofs(val); + if (result == DNS_R_NSEC3ITERRANGE) { + validator_log(val, ISC_LOG_DEBUG(3), +- "too many iterations"); ++ "%s: too many iterations", __func__); + markanswer(val, "validate_nx (3)", NULL); + return (ISC_R_SUCCESS); + } +@@ -2624,7 +2669,7 @@ validate_nx(dns_validator_t *val, bool resume) { + result = findnsec3proofs(val); + if (result == DNS_R_NSEC3ITERRANGE) { + validator_log(val, ISC_LOG_DEBUG(3), +- "too many iterations"); ++ "%s: too many iterations", __func__); + markanswer(val, "validate_nx (4)", NULL); + return (ISC_R_SUCCESS); + } +@@ -2841,8 +2886,10 @@ seek_ds(dns_validator_t *val, isc_result_t *resp) { + return (ISC_R_COMPLETE); + } + +- if (isdelegation(tname, &val->frdataset, result)) { +- *resp = markanswer(val, "proveunsecure (4)", ++ result = isdelegation(val, tname, &val->frdataset, result, ++ "seek_ds"); ++ if (result == ISC_R_SUCCESS) { ++ *resp = markanswer(val, "seek_ds (3)", + "this is a delegation"); + return (ISC_R_COMPLETE); + } +-- +2.53.0 + diff --git a/SOURCES/bind-9.20-robust-key-rollovers-tests.patch b/SOURCES/bind-9.20-robust-key-rollovers-tests.patch new file mode 100644 index 0000000..9e8cb77 --- /dev/null +++ b/SOURCES/bind-9.20-robust-key-rollovers-tests.patch @@ -0,0 +1,144 @@ +diff --git a/bin/tests/system/nsec3/ns3/named.conf.in b/bin/tests/system/nsec3/ns3/named.conf.in +index 022e9421bc..2bee4f898a 100644 +--- a/bin/tests/system/nsec3/ns3/named.conf.in ++++ b/bin/tests/system/nsec3/ns3/named.conf.in +@@ -68,7 +68,7 @@ zone "nsec-to-nsec3.kasp" { + + /* + * This zone starts with NSEC, but will be reconfigured to use NSEC3. +- * This should work despite the incompatible RSAHSHA1 algorithm, ++ * This should work despite the incompatible RSASHA1 algorithm, + * because the DS is still in hidden state. + */ + zone "rsasha1-to-nsec3.kasp" { +@@ -92,7 +92,7 @@ zone "rsasha1-to-nsec3-wait.kasp" { + + /* + * This zone starts with NSEC3, but will be reconfigured to use NSEC with an +- * NSEC only algorithm. This should work despite the incompatible RSAHSHA1 ++ * NSEC only algorithm. This should work despite the incompatible RSASHA1 + * algorithm, because the DS is still in hidden state. + */ + zone "nsec3-to-rsasha1.kasp" { +diff --git a/bin/tests/system/nsec3/ns3/setup.sh b/bin/tests/system/nsec3/ns3/setup.sh +index 5ddcfc01b0..3cd7210192 100644 +--- a/bin/tests/system/nsec3/ns3/setup.sh ++++ b/bin/tests/system/nsec3/ns3/setup.sh +@@ -35,24 +35,23 @@ if ( + cd .. + $SHELL ../testcrypto.sh -q RSASHA1 + ); then +- for zn in rsasha1-to-nsec3 rsasha1-to-nsec3-wait nsec3-to-rsasha1 \ +- nsec3-to-rsasha1-ds; do +- setup "${zn}.kasp" +- done +- + longago="now-1y" +- keytimes="-P ${longago} -A ${longago}" ++ keytimes="-P ${longago} -A ${longago} -P sync ${longago}" + O="omnipresent" + +- zone="rsasha1-to-nsec3-wait.kasp" +- CSK=$($KEYGEN -k "rsasha1" -l named.conf $keytimes $zone 2>keygen.out.$zone) +- echo_i "Created key file $CSK" +- $SETTIME -s -g $O -k $O $longago -r $O $longago -z $O $longago -d $O $longago "$CSK" >settime.out.$zone 2>&1 ++ for zn in nsec3-to-rsasha1 nsec3-to-rsasha1-ds; do ++ setup "${zn}.kasp" ++ CSK=$($KEYGEN -k "default" -l named.conf $keytimes $zone 2>keygen.out.$zone) ++ $SETTIME -s -g $O -k $O $longago -r $O $longago -z $O $longago -d $O $longago "$CSK" >settime.out.$zone 2>&1 ++ cat $CSK.key >>$zonefile ++ done + +- zone="nsec3-to-rsasha1-ds.kasp" +- CSK=$($KEYGEN -k "default" -l named.conf $keytimes $zone 2>keygen.out.$zone) +- echo_i "Created key file $CSK" +- $SETTIME -s -g $O -k $O $longago -r $O $longago -z $O $longago -d $O $longago "$CSK" >settime.out.$zone 2>&1 ++ for zn in rsasha1-to-nsec3 rsasha1-to-nsec3-wait; do ++ setup "${zn}.kasp" ++ CSK=$($KEYGEN -k "rsasha1" -l named.conf $keytimes $zone 2>keygen.out.$zone) ++ $SETTIME -s -g $O -k $O $longago -r $O $longago -z $O $longago -d $O $longago "$CSK" >settime.out.$zone 2>&1 ++ cat $CSK.key >>$zonefile ++ done + else + echo_i "skip: skip rsasha1 zones - signing with RSASHA1 not supported" + fi +diff --git a/bin/tests/system/nsec3/tests.sh b/bin/tests/system/nsec3/tests.sh +index 6cb28583c6..b8e9152cf9 100644 +--- a/bin/tests/system/nsec3/tests.sh ++++ b/bin/tests/system/nsec3/tests.sh +@@ -80,10 +80,10 @@ set_key_rsasha1_values() { + set_zonesigning $1 "yes" + + set_keystate $1 "GOAL" "omnipresent" +- set_keystate $1 "STATE_DNSKEY" "rumoured" +- set_keystate $1 "STATE_KRRSIG" "rumoured" +- set_keystate $1 "STATE_ZRRSIG" "rumoured" +- set_keystate $1 "STATE_DS" "hidden" ++ set_keystate $1 "STATE_DNSKEY" "omnipresent" ++ set_keystate $1 "STATE_KRRSIG" "omnipresent" ++ set_keystate $1 "STATE_ZRRSIG" "omnipresent" ++ set_keystate $1 "STATE_DS" "omnipresent" + } + + # Update the key states. +@@ -251,21 +251,21 @@ if ($SHELL ../testcrypto.sh -q RSASHA1); then + set_zone_policy "rsasha1-to-nsec3-wait.kasp" "rsasha1" 1 3600 + set_server "ns3" "10.53.0.3" + set_key_rsasha1_values "KEY1" +- set_key_states "KEY1" "omnipresent" "omnipresent" "omnipresent" "omnipresent" "omnipresent" + echo_i "initial check zone ${ZONE}" + check_nsec + + # Zone: nsec3-to-rsasha1.kasp. + set_zone_policy "nsec3-to-rsasha1.kasp" "nsec3" 1 3600 + set_server "ns3" "10.53.0.3" +- set_key_rsasha1_values "KEY1" ++ set_key_default_values "KEY1" ++ set_key_states "KEY1" "omnipresent" "omnipresent" "omnipresent" "omnipresent" "omnipresent" + echo_i "initial check zone ${ZONE}" + check_nsec3 + + # Zone: nsec3-to-rsasha1-ds.kasp. + set_zone_policy "nsec3-to-rsasha1-ds.kasp" "nsec3" 1 3600 + set_server "ns3" "10.53.0.3" +- set_key_rsasha1_values "KEY1" ++ set_key_default_values "KEY1" + set_key_states "KEY1" "omnipresent" "omnipresent" "omnipresent" "omnipresent" "omnipresent" + echo_i "initial check zone ${ZONE}" + check_nsec3 +@@ -385,12 +385,10 @@ if ($SHELL ../testcrypto.sh -q RSASHA1); then + set_zone_policy "rsasha1-to-nsec3.kasp" "nsec3" 2 3600 + set_server "ns3" "10.53.0.3" + set_key_rsasha1_values "KEY1" +- set_key_states "KEY1" "hidden" "unretentive" "unretentive" "unretentive" "hidden" +- set_keysigning "KEY1" "no" +- set_zonesigning "KEY1" "no" ++ set_key_states "KEY1" "hidden" "omnipresent" "omnipresent" "omnipresent" "omnipresent" + set_key_default_values "KEY2" + echo_i "check zone ${ZONE} after reconfig" +- check_nsec3 ++ check_nsec + + # Zone: rsasha1-to-nsec3-wait.kasp. + set_zone_policy "rsasha1-to-nsec3-wait.kasp" "nsec3" 2 3600 +@@ -406,10 +404,9 @@ if ($SHELL ../testcrypto.sh -q RSASHA1); then + set_nsec3param "1" "0" "0" + set_server "ns3" "10.53.0.3" + set_key_default_values "KEY1" +- set_key_states "KEY1" "hidden" "unretentive" "unretentive" "unretentive" "hidden" +- set_keysigning "KEY1" "no" +- set_zonesigning "KEY1" "no" ++ set_key_states "KEY1" "hidden" "omnipresent" "omnipresent" "omnipresent" "omnipresent" + set_key_rsasha1_values "KEY2" ++ set_key_states "KEY2" "omnipresent" "rumoured" "rumoured" "rumoured" "hidden" + echo_i "check zone ${ZONE} after reconfig" + check_nsec + +@@ -420,6 +417,7 @@ if ($SHELL ../testcrypto.sh -q RSASHA1); then + set_key_default_values "KEY1" + set_key_states "KEY1" "hidden" "omnipresent" "omnipresent" "omnipresent" "omnipresent" + set_key_rsasha1_values "KEY2" ++ set_key_states "KEY2" "omnipresent" "rumoured" "rumoured" "rumoured" "hidden" + echo_i "check zone ${ZONE} after reconfig" + check_nsec + diff --git a/SOURCES/bind-9.20-robust-key-rollovers.patch b/SOURCES/bind-9.20-robust-key-rollovers.patch new file mode 100644 index 0000000..8f28c5b --- /dev/null +++ b/SOURCES/bind-9.20-robust-key-rollovers.patch @@ -0,0 +1,97 @@ +diff --git a/lib/dns/keymgr.c b/lib/dns/keymgr.c +index 4fbebbcb6d..789f68a146 100644 +--- a/lib/dns/keymgr.c ++++ b/lib/dns/keymgr.c +@@ -1224,19 +1224,18 @@ static bool + keymgr_transition_allowed(dns_dnsseckeylist_t *keyring, dns_dnsseckey_t *key, + int type, dst_key_state_t next_state, + bool secure_to_insecure) { ++ bool rule1a, rule1b, rule2a, rule2b, rule3a, rule3b; ++ rule1a = keymgr_have_ds(keyring, key, type, NA, secure_to_insecure); ++ rule1b = keymgr_have_ds(keyring, key, type, next_state, secure_to_insecure); ++ rule2a = keymgr_have_dnskey(keyring, key, type, NA); ++ rule2b = keymgr_have_dnskey(keyring, key, type, next_state); ++ rule3a = keymgr_have_rrsig(keyring, key, type, NA); ++ rule3b = keymgr_have_rrsig(keyring, key, type, next_state); ++ + /* Debug logging. */ + if (isc_log_wouldlog(dns_lctx, ISC_LOG_DEBUG(1))) { +- bool rule1a, rule1b, rule2a, rule2b, rule3a, rule3b; + char keystr[DST_KEY_FORMATSIZE]; + dst_key_format(key->key, keystr, sizeof(keystr)); +- rule1a = keymgr_have_ds(keyring, key, type, NA, +- secure_to_insecure); +- rule1b = keymgr_have_ds(keyring, key, type, next_state, +- secure_to_insecure); +- rule2a = keymgr_have_dnskey(keyring, key, type, NA); +- rule2b = keymgr_have_dnskey(keyring, key, type, next_state); +- rule3a = keymgr_have_rrsig(keyring, key, type, NA); +- rule3b = keymgr_have_rrsig(keyring, key, type, next_state); + isc_log_write( + dns_lctx, DNS_LOGCATEGORY_DNSSEC, DNS_LOGMODULE_DNSSEC, + ISC_LOG_DEBUG(1), +@@ -1249,30 +1248,40 @@ keymgr_transition_allowed(dns_dnsseckeylist_t *keyring, dns_dnsseckey_t *key, + rule3a ? "true" : "false", rule3b ? "true" : "false"); + } + +- return ( +- /* +- * Rule 1: There must be a DS at all times. +- * First check the current situation: if the rule check fails, +- * we allow the transition to attempt to move us out of the +- * invalid state. If the rule check passes, also check if +- * the next state is also still a valid situation. +- */ +- (!keymgr_have_ds(keyring, key, type, NA, secure_to_insecure) || +- keymgr_have_ds(keyring, key, type, next_state, +- secure_to_insecure)) && +- /* +- * Rule 2: There must be a DNSKEY at all times. Again, first +- * check the current situation, then assess the next state. +- */ +- (!keymgr_have_dnskey(keyring, key, type, NA) || +- keymgr_have_dnskey(keyring, key, type, next_state)) && +- /* +- * Rule 3: There must be RRSIG records at all times. Again, +- * first check the current situation, then assess the next +- * state. +- */ +- (!keymgr_have_rrsig(keyring, key, type, NA) || +- keymgr_have_rrsig(keyring, key, type, next_state))); ++ /* ++ * Rule checking: ++ * First check the current situation: if the rule check fails, ++ * we allow the transition to attempt to move us out of the ++ * invalid state. If the rule check passes, also check if ++ * the next state is also still a valid situation. ++ */ ++ char keystr2[DST_KEY_FORMATSIZE]; ++ dst_key_format(key->key, keystr2, sizeof(keystr2)); ++ ++ /* ++ * Rule 1: There must be a DS at all times. ++ */ ++ if (!rule1a && !rule1b && next_state == UNRETENTIVE) { ++ return false; ++ } ++ /* ++ * Rule 2: There must be a DNSKEY at all times. Again, first ++ * check the current situation, then assess the next state. ++ */ ++ if (!rule2a && !rule2b && next_state == UNRETENTIVE) { ++ return false; ++ } ++ /* ++ * Rule 3: There must be RRSIG records at all times. Again, ++ * first check the current situation, then assess the next ++ * state. ++ */ ++ if (!rule3a && !rule3b && next_state == UNRETENTIVE) { ++ return false; ++ } ++ ++ return (!rule1a || rule1b) && (!rule2a || rule2b) && ++ (!rule3a || rule3b); + } + + /* diff --git a/SOURCES/bind-9.20-stale-cname-tests.patch b/SOURCES/bind-9.20-stale-cname-tests.patch new file mode 100644 index 0000000..6280b77 --- /dev/null +++ b/SOURCES/bind-9.20-stale-cname-tests.patch @@ -0,0 +1,94 @@ +diff --git a/bin/tests/system/serve-stale/ns1/stale.test.db b/bin/tests/system/serve-stale/ns1/stale.test.db +index d389e7c6a6..128fb25a10 100644 +--- a/bin/tests/system/serve-stale/ns1/stale.test.db ++++ b/bin/tests/system/serve-stale/ns1/stale.test.db +@@ -17,3 +17,11 @@ cname1.stale.test. 1 CNAME a1.stale.test. + a1.stale.test. 1 A 192.0.2.1 + cname2.stale.test. 1 CNAME a2.stale.test. + a2.stale.test. 300 A 192.0.2.2 ++ ++cname-a1 1 CNAME cname-a2 ++cname-a2 300 CNAME cname-a3 ++cname-a3 300 A 192.0.2.1 ++ ++cname-b1 300 CNAME cname-b2 ++cname-b2 1 CNAME cname-b3 ++cname-b3 1 A 192.0.2.2 +diff --git a/bin/tests/system/serve-stale/tests.sh b/bin/tests/system/serve-stale/tests.sh +index 96cd26505f..a30896b6a4 100755 +--- a/bin/tests/system/serve-stale/tests.sh ++++ b/bin/tests/system/serve-stale/tests.sh +@@ -2256,6 +2256,73 @@ if [ $ret != 0 ]; then + fi + status=$((status + ret)) + ++# New CNAME scenario (GL #5243) ++n=$((n + 1)) ++echo_i "prime cache cname-a1.stale.test A (stale-answer-client-timeout 0) ($n)" ++ret=0 ++$DIG -p ${PORT} @10.53.0.3 cname-a1.stale.test A >dig.out.test$n || ret=1 ++grep "status: NOERROR" dig.out.test$n >/dev/null || ret=1 ++grep "ANSWER: 3," dig.out.test$n >/dev/null || ret=1 ++grep "cname-a1\.stale\.test\..*1.*IN.*CNAME.*cname-a2\.stale\.test\." dig.out.test$n >/dev/null || ret=1 ++grep "cname-a2\.stale\.test\..*300.*IN.*CNAME.*cname-a3\.stale\.test\." dig.out.test$n >/dev/null || ret=1 ++grep "cname-a3\.stale\.test\..*300.*IN.*A.*192\.0\.2\.1" dig.out.test$n >/dev/null || ret=1 ++if [ $ret != 0 ]; then echo_i "failed"; fi ++status=$((status + ret)) ++ ++n=$((n + 1)) ++echo_i "prime cache cname-b1.stale.test A (stale-answer-client-timeout 0) ($n)" ++ret=0 ++$DIG -p ${PORT} @10.53.0.3 cname-b1.stale.test A >dig.out.test$n || ret=1 ++grep "status: NOERROR" dig.out.test$n >/dev/null || ret=1 ++grep "ANSWER: 3," dig.out.test$n >/dev/null || ret=1 ++grep "cname-b1\.stale\.test\..*300.*IN.*CNAME.*cname-b2\.stale\.test\." dig.out.test$n >/dev/null || ret=1 ++grep "cname-b2\.stale\.test\..*1.*IN.*CNAME.*cname-b3\.stale\.test\." dig.out.test$n >/dev/null || ret=1 ++grep "cname-b3\.stale\.test\..*1.*IN.*A.*192\.0\.2\.2" dig.out.test$n >/dev/null || ret=1 ++if [ $ret != 0 ]; then echo_i "failed"; fi ++status=$((status + ret)) ++ ++# Allow RRset to become stale. ++sleep 1 ++ ++n=$((n + 1)) ++ret=0 ++echo_i "check stale cname-a1.stale.test A comes from cache (stale-answer-client-timeout 0) ($n)" ++nextpart ns3/named.run >/dev/null ++$DIG -p ${PORT} @10.53.0.3 cname-a1.stale.test A >dig.out.test$n || ret=1 ++wait_for_log 5 "cname-a1.stale.test A stale answer used, an attempt to refresh the RRset" ns3/named.run || ret=1 ++# Other records in chain are still good, so do not attempt a refresh ++grep "cname-a2.stale.test A stale answer used, an attempt to refresh the RRset" ns3/named.run && ret=1 ++grep "cname-a3.stale.test A stale answer used, an attempt to refresh the RRset" ns3/named.run && ret=1 ++# Check answer ++grep "status: NOERROR" dig.out.test$n >/dev/null || ret=1 ++grep "EDE: 3 (Stale Answer): (stale data prioritized over lookup)" dig.out.test$n >/dev/null || ret=1 ++grep "ANSWER: 3," dig.out.test$n >/dev/null || ret=1 ++grep "cname-a1\.stale\.test\..*3.*IN.*CNAME.*cname-a2\.stale\.test\." dig.out.test$n >/dev/null || ret=1 ++grep "cname-a2\.stale\.test\..*29[0-9].*IN.*CNAME.*cname-a3\.stale\.test\." dig.out.test$n >/dev/null || ret=1 ++grep "cname-a3\.stale\.test\..*29[0-9].*IN.*A.*192\.0\.2\.1" dig.out.test$n >/dev/null || ret=1 ++if [ $ret != 0 ]; then echo_i "failed"; fi ++status=$((status + ret)) ++ ++n=$((n + 1)) ++ret=0 ++echo_i "check stale cname-b1.stale.test A comes from cache (stale-answer-client-timeout 0) ($n)" ++nextpart ns3/named.run >/dev/null ++$DIG -p ${PORT} @10.53.0.3 cname-b1.stale.test A >dig.out.test$n || ret=1 ++wait_for_log 5 "cname-b2.stale.test A stale answer used, an attempt to refresh the RRset" ns3/named.run || ret=1 ++# The next one in the chain (cname-b3.stale.test) is likely not logged because ++# there is already a refresh in progress. And the first record in the chain is ++# still good, so do not attempt a refresh. ++grep "cname-b1.stale.test A stale answer used, an attempt to refresh the RRset" ns3/named.run && ret=1 ++# Check answer ++grep "status: NOERROR" dig.out.test$n >/dev/null || ret=1 ++grep "EDE: 3 (Stale Answer): (stale data prioritized over lookup)" dig.out.test$n >/dev/null || ret=1 ++grep "ANSWER: 3," dig.out.test$n >/dev/null || ret=1 ++grep "cname-b1\.stale\.test\..*29[0-9].*IN.*CNAME.*cname-b2\.stale\.test\." dig.out.test$n >/dev/null || ret=1 ++grep "cname-b2\.stale\.test\..*3.*IN.*CNAME.*cname-b3\.stale\.test\." dig.out.test$n >/dev/null || ret=1 ++grep "cname-b3\.stale\.test\..*3.*IN.*A.*192\.0\.2\.2" dig.out.test$n >/dev/null || ret=1 ++if [ $ret != 0 ]; then echo_i "failed"; fi ++status=$((status + ret)) ++ + #################################################################### + # Test for stale-answer-client-timeout 0 and stale-refresh-time 4. # + #################################################################### diff --git a/SOURCES/bind-9.20-stale-cname.patch b/SOURCES/bind-9.20-stale-cname.patch new file mode 100644 index 0000000..e8c4ef4 --- /dev/null +++ b/SOURCES/bind-9.20-stale-cname.patch @@ -0,0 +1,711 @@ +diff --git a/bin/tests/system/serve-stale/tests.sh b/bin/tests/system/serve-stale/tests.sh +index c001e7a071..96cd26505f 100755 +--- a/bin/tests/system/serve-stale/tests.sh ++++ b/bin/tests/system/serve-stale/tests.sh +@@ -2053,7 +2053,7 @@ ret=0 + echo_i "check stale nodata.example TXT comes from cache (stale-answer-client-timeout 0) ($n)" + nextpart ns3/named.run >/dev/null + $DIG -p ${PORT} @10.53.0.3 nodata.example TXT >dig.out.test$n || ret=1 +-wait_for_log 5 "nodata.example stale answer used, an attempt to refresh the RRset" ns3/named.run || ret=1 ++wait_for_log 5 "nodata.example TXT stale answer used, an attempt to refresh the RRset" ns3/named.run || ret=1 + grep "status: NOERROR" dig.out.test$n >/dev/null || ret=1 + grep "EDE: 3 (Stale Answer): (stale data prioritized over lookup)" dig.out.test$n >/dev/null || ret=1 + grep "ANSWER: 0," dig.out.test$n >/dev/null || ret=1 +@@ -2066,7 +2066,7 @@ ret=0 + echo_i "check stale data.example TXT comes from cache (stale-answer-client-timeout 0) ($n)" + nextpart ns3/named.run >/dev/null + $DIG -p ${PORT} @10.53.0.3 data.example TXT >dig.out.test$n || ret=1 +-wait_for_log 5 "data.example stale answer used, an attempt to refresh the RRset" ns3/named.run || ret=1 ++wait_for_log 5 "data.example TXT stale answer used, an attempt to refresh the RRset" ns3/named.run || ret=1 + grep "status: NOERROR" dig.out.test$n >/dev/null || ret=1 + grep "EDE: 3 (Stale Answer): (stale data prioritized over lookup)" dig.out.test$n >/dev/null || ret=1 + grep "ANSWER: 1," dig.out.test$n >/dev/null || ret=1 +@@ -2196,7 +2196,7 @@ ret=0 + echo_i "check stale cname1.stale.test A comes from cache (stale-answer-client-timeout 0) ($n)" + nextpart ns3/named.run >/dev/null + $DIG -p ${PORT} @10.53.0.3 cname1.stale.test A >dig.out.test$n || ret=1 +-wait_for_log 5 "cname1.stale.test stale answer used, an attempt to refresh the RRset" ns3/named.run || ret=1 ++wait_for_log 5 "cname1.stale.test A stale answer used, an attempt to refresh the RRset" ns3/named.run || ret=1 + grep "status: NOERROR" dig.out.test$n >/dev/null || ret=1 + grep "EDE: 3 (Stale Answer): (stale data prioritized over lookup)" dig.out.test$n >/dev/null || ret=1 + grep "ANSWER: 2," dig.out.test$n >/dev/null || ret=1 +@@ -2235,7 +2235,7 @@ ret=0 + echo_i "check stale cname2.stale.test A comes from cache (stale-answer-client-timeout 0) ($n)" + nextpart ns3/named.run >/dev/null + $DIG -p ${PORT} @10.53.0.3 cname2.stale.test A >dig.out.test$n || ret=1 +-wait_for_log 5 "cname2.stale.test stale answer used, an attempt to refresh the RRset" ns3/named.run || ret=1 ++wait_for_log 5 "cname2.stale.test A stale answer used, an attempt to refresh the RRset" ns3/named.run || ret=1 + grep "status: NOERROR" dig.out.test$n >/dev/null || ret=1 + grep "EDE: 3 (Stale Answer): (stale data prioritized over lookup)" dig.out.test$n >/dev/null || ret=1 + grep "ANSWER: 2," dig.out.test$n >/dev/null || ret=1 +@@ -2312,7 +2312,7 @@ ret=0 + echo_i "check stale data.example TXT comes from cache (stale-answer-client-timeout 0 stale-refresh-time 4) ($n)" + nextpart ns3/named.run >/dev/null + $DIG -p ${PORT} @10.53.0.3 data.example TXT >dig.out.test$n || ret=1 +-wait_for_log 5 "data.example stale answer used, an attempt to refresh the RRset" ns3/named.run || ret=1 ++wait_for_log 5 "data.example TXT stale answer used, an attempt to refresh the RRset" ns3/named.run || ret=1 + grep "status: NOERROR" dig.out.test$n >/dev/null || ret=1 + grep "EDE: 3 (Stale Answer): (stale data prioritized over lookup)" dig.out.test$n >/dev/null || ret=1 + grep "ANSWER: 1," dig.out.test$n >/dev/null || ret=1 +@@ -2356,7 +2356,7 @@ ret=0 + echo_i "check stale data.example TXT comes from cache (stale-answer-client-timeout 0 stale-refresh-time 4) ($n)" + nextpart ns3/named.run >/dev/null + $DIG -p ${PORT} @10.53.0.3 data.example TXT >dig.out.test$n || ret=1 +-wait_for_log 5 "data.example stale answer used, an attempt to refresh the RRset" ns3/named.run || ret=1 ++wait_for_log 5 "data.example TXT stale answer used, an attempt to refresh the RRset" ns3/named.run || ret=1 + grep "status: NOERROR" dig.out.test$n >/dev/null || ret=1 + grep "EDE: 3 (Stale Answer): (stale data prioritized over lookup)" dig.out.test$n >/dev/null || ret=1 + grep "ANSWER: 1," dig.out.test$n >/dev/null || ret=1 +@@ -2368,7 +2368,7 @@ status=$((status + ret)) + n=$((n + 1)) + ret=0 + echo_i "wait until resolver query times out, activating stale-refresh-time" +-wait_for_log 15 "data.example resolver failure, stale answer used" ns3/named.run || ret=1 ++wait_for_log 15 "data.example/TXT stale refresh failed: timed out" ns3/named.run || ret=1 + if [ $ret != 0 ]; then echo_i "failed"; fi + status=$((status + ret)) + +@@ -2419,7 +2419,7 @@ n=$((n + 1)) + ret=0 + echo_i "check stale data.example TXT comes from cache (stale-answer-client-timeout 0 stale-refresh-time 4) ($n)" + $DIG -p ${PORT} @10.53.0.3 data.example TXT >dig.out.test$n || ret=1 +-wait_for_log 5 "data.example stale answer used, an attempt to refresh the RRset" ns3/named.run || ret=1 ++wait_for_log 5 "data.example TXT stale answer used, an attempt to refresh the RRset" ns3/named.run || ret=1 + grep "status: NOERROR" dig.out.test$n >/dev/null || ret=1 + grep "EDE: 3 (Stale Answer): (stale data prioritized over lookup)" dig.out.test$n >/dev/null || ret=1 + grep "ANSWER: 1," dig.out.test$n >/dev/null || ret=1 +diff --git a/lib/dns/include/dns/rdataset.h b/lib/dns/include/dns/rdataset.h +index f63591c5c7..f3f257ad7e 100644 +--- a/lib/dns/include/dns/rdataset.h ++++ b/lib/dns/include/dns/rdataset.h +@@ -199,7 +199,9 @@ struct dns_rdataset { + #define DNS_RDATASETATTR_STALE 0x01000000 + #define DNS_RDATASETATTR_ANCIENT 0x02000000 + #define DNS_RDATASETATTR_STALE_WINDOW 0x04000000 +-#define DNS_RDATASETATTR_STALE_ADDED 0x08000000 ++#define DNS_RDATASETATTR_KEEPCASE 0x10000000 ++#define DNS_RDATASETATTR_STATICSTUB 0x20000000 ++ + + /*% + * _OMITDNSSEC: +diff --git a/lib/ns/include/ns/client.h b/lib/ns/include/ns/client.h +index ea2d83e079..db17f01d79 100644 +--- a/lib/ns/include/ns/client.h ++++ b/lib/ns/include/ns/client.h +@@ -184,6 +184,7 @@ struct ns_client { + (query, update, notify) */ + isc_nmhandle_t *fetchhandle; /* Waiting for recursive fetch */ + isc_nmhandle_t *prefetchhandle; /* Waiting for prefetch / rpzfetch */ ++ isc_nmhandle_t *stalerefreshhandle; + isc_nmhandle_t *updatehandle; /* Waiting for update callback */ + unsigned char *tcpbuf; + size_t tcpbuf_size; +diff --git a/lib/ns/include/ns/query.h b/lib/ns/include/ns/query.h +index 37e55671c2..ce97643297 100644 +--- a/lib/ns/include/ns/query.h ++++ b/lib/ns/include/ns/query.h +@@ -68,6 +68,7 @@ struct ns_query { + isc_mutex_t fetchlock; + dns_fetch_t *fetch; + dns_fetch_t *prefetch; ++ dns_fetch_t *stalerefresh; + ns_hookasync_t *hookactx; + dns_rpz_st_t *rpz_st; + isc_bufferlist_t namebufs; +@@ -119,7 +120,6 @@ struct ns_query { + #define NS_QUERYATTR_RRL_CHECKED 0x010000 + #define NS_QUERYATTR_REDIRECT 0x020000 + #define NS_QUERYATTR_ANSWERED 0x040000 +-#define NS_QUERYATTR_STALEOK 0x080000 + #define NS_QUERYATTR_STALEPENDING 0x100000 + + typedef struct query_ctx query_ctx_t; +@@ -148,7 +148,6 @@ struct query_ctx { + bool authoritative; /* authoritative query? */ + bool want_restart; /* CNAME chain or other + * restart needed */ +- bool refresh_rrset; /* stale RRset refresh needed */ + bool need_wildcardproof; /* wildcard proof needed */ + bool nxrewrite; /* negative answer from RPZ */ + bool findcoveringnsec; /* lookup covering NSEC */ +diff --git a/lib/ns/query.c b/lib/ns/query.c +index 5549e202df..274c044172 100644 +--- a/lib/ns/query.c ++++ b/lib/ns/query.c +@@ -139,9 +139,6 @@ + #define QUERY_STALEPENDING(q) \ + (((q)->attributes & NS_QUERYATTR_STALEPENDING) != 0) + +-/*% Does the query allow stale data in the response? */ +-#define QUERY_STALEOK(q) (((q)->attributes & NS_QUERYATTR_STALEOK) != 0) +- + /*% Does the query wants to check for stale RRset due to a timeout? */ + #define QUERY_STALETIMEOUT(q) (((q)->dboptions & DNS_DBFIND_STALETIMEOUT) != 0) + +@@ -248,6 +245,15 @@ query_addanswer(query_ctx_t *qctx); + static isc_result_t + query_prepare_delegation_response(query_ctx_t *qctx); + ++static isc_result_t ++qctx_prepare_buffers(query_ctx_t *qctx, isc_buffer_t *buffer); ++ ++static void ++qctx_freedata(query_ctx_t *qctx); ++ ++static void ++qctx_destroy(query_ctx_t *qctx); ++ + /* + * Return the hooktable in use with 'qctx', or if there isn't one + * set, return the default hooktable. +@@ -503,9 +509,6 @@ query_addwildcardproof(query_ctx_t *qctx, bool ispositive, bool nodata); + static void + query_addauth(query_ctx_t *qctx); + +-static void +-query_clear_stale(ns_client_t *client); +- + /* + * Increment query statistics counters. + */ +@@ -811,6 +814,7 @@ ns_query_init(ns_client_t *client) { + + client->query.fetch = NULL; + client->query.prefetch = NULL; ++ client->query.stalerefresh = NULL; + client->query.authdb = NULL; + client->query.authzone = NULL; + client->query.authdbset = false; +@@ -2241,10 +2245,6 @@ query_addrrset(query_ctx_t *qctx, dns_name_t **namep, + if ((rdataset->attributes & DNS_RDATASETATTR_REQUIRED) != 0) { + mrdataset->attributes |= DNS_RDATASETATTR_REQUIRED; + } +- if ((rdataset->attributes & DNS_RDATASETATTR_STALE_ADDED) != 0) +- { +- mrdataset->attributes |= DNS_RDATASETATTR_STALE_ADDED; +- } + return; + } else if (result == DNS_R_NXDOMAIN) { + /* +@@ -2530,6 +2530,88 @@ free_devent(ns_client_t *client, isc_event_t **eventp, + isc_event_free(eventp); + } + ++static void ++stale_refresh_aftermath(ns_client_t *client, isc_result_t result) { ++ dns_db_t *db = NULL; ++ unsigned int dboptions; ++ isc_buffer_t buffer; ++ query_ctx_t qctx; ++ dns_clientinfomethods_t cm; ++ dns_clientinfo_t ci; ++ char namebuf[DNS_NAME_FORMATSIZE]; ++ char typebuf[DNS_RDATATYPE_FORMATSIZE]; ++ ++ /* ++ * If refreshing a stale RRset failed, we need to set the ++ * stale-refresh-time window, so that on future requests for this ++ * RRset the stale entry may be used immediately. ++ */ ++ switch (result) { ++ case ISC_R_SUCCESS: ++ case DNS_R_GLUE: ++ case DNS_R_ZONECUT: ++ case ISC_R_NOTFOUND: ++ case DNS_R_DELEGATION: ++ case DNS_R_EMPTYNAME: ++ case DNS_R_NXRRSET: ++ case DNS_R_EMPTYWILD: ++ case DNS_R_NXDOMAIN: ++ case DNS_R_COVERINGNSEC: ++ case DNS_R_NCACHENXDOMAIN: ++ case DNS_R_NCACHENXRRSET: ++ case DNS_R_CNAME: ++ case DNS_R_DNAME: ++ break; ++ default: ++ dns_name_format(client->query.qname, namebuf, sizeof(namebuf)); ++ dns_rdatatype_format(client->query.qtype, typebuf, ++ sizeof(typebuf)); ++ ns_client_log(client, NS_LOGCATEGORY_SERVE_STALE, ++ NS_LOGMODULE_QUERY, ISC_LOG_NOTICE, ++ "%s/%s stale refresh failed: timed out", namebuf, ++ typebuf); ++ ++ /* ++ * Set up a short lived query context, solely to set the ++ * last refresh failure time on the RRset in the cache ++ * database, starting the stale-refresh-time window for it. ++ * This is a condensed form of query_lookup(). ++ */ ++ isc_stdtime_get(&client->now); ++ client->query.attributes &= ~NS_QUERYATTR_RECURSIONOK; ++ qctx_init(client, NULL, 0, &qctx); ++ ++ dns_clientinfomethods_init(&cm, ns_client_sourceip); ++ dns_clientinfo_init(&ci, qctx.client, NULL); ++ if (HAVEECS(qctx.client)) { ++ dns_clientinfo_setecs(&ci, &qctx.client->ecs); ++ } ++ ++ result = qctx_prepare_buffers(&qctx, &buffer); ++ if (result != ISC_R_SUCCESS) { ++ goto cleanup; ++ } ++ ++ dboptions = qctx.client->query.dboptions; ++ dboptions |= DNS_DBFIND_STALEOK; ++ dboptions |= DNS_DBFIND_STALESTART; ++ ++ dns_db_attach(qctx.client->view->cachedb, &db); ++ (void)dns_db_findext(db, qctx.client->query.qname, NULL, ++ qctx.client->query.qtype, dboptions, ++ qctx.client->now, &qctx.node, qctx.fname, ++ &cm, &ci, qctx.rdataset, qctx.sigrdataset); ++ if (qctx.node != NULL) { ++ dns_db_detachnode(db, &qctx.node); ++ } ++ dns_db_detach(&db); ++ ++ cleanup: ++ qctx_freedata(&qctx); ++ qctx_destroy(&qctx); ++ } ++} ++ + static void + prefetch_done(isc_task_t *task, isc_event_t *event) { + dns_fetchevent_t *devent = (dns_fetchevent_t *)event; +@@ -2564,6 +2646,133 @@ prefetch_done(isc_task_t *task, isc_event_t *event) { + isc_nmhandle_detach(&client->prefetchhandle); + } + ++static void ++refresh_done(isc_task_t *task, isc_event_t *event) { ++ dns_fetchevent_t *devent = (dns_fetchevent_t *)event; ++ ns_client_t *client; ++ ++ UNUSED(task); ++ ++ REQUIRE(event->ev_type == DNS_EVENT_FETCHDONE); ++ client = devent->ev_arg; ++ REQUIRE(NS_CLIENT_VALID(client)); ++ REQUIRE(task == client->task); ++ ++ CTRACE(ISC_LOG_DEBUG(3), "refresh_done"); ++ ++ LOCK(&client->query.fetchlock); ++ if (client->query.stalerefresh != NULL) { ++ INSIST(devent->fetch == client->query.stalerefresh); ++ client->query.stalerefresh = NULL; ++ } ++ UNLOCK(&client->query.fetchlock); ++ ++ stale_refresh_aftermath(client, devent->result); ++ ++ if (client->recursionquota != NULL) { ++ isc_quota_detach(&client->recursionquota); ++ ns_stats_decrement(client->sctx->nsstats, ++ ns_statscounter_recursclients); ++ } ++ ++ free_devent(client, &event, &devent); ++ isc_nmhandle_detach(&client->stalerefreshhandle); ++} ++ ++static void ++query_stale_refresh(ns_client_t *client, dns_name_t *qname, ++ dns_rdataset_t *rdataset) { ++ isc_result_t result; ++ isc_sockaddr_t *peeraddr; ++ dns_rdataset_t *tmprdataset; ++ unsigned int options; ++ ++ CTRACE(ISC_LOG_DEBUG(3), "query_stale_refresh"); ++ ++ bool stale_refresh_window = false; ++ bool stale_rrset = true; ++ ++ if (rdataset != NULL) { ++ stale_refresh_window = ++ (STALE_WINDOW(rdataset) && ++ (client->query.dboptions & DNS_DBFIND_STALEENABLED) != 0); ++ stale_rrset = STALE(rdataset); ++ } ++ ++ if (client->query.stalerefresh != NULL || ++ (client->query.dboptions & DNS_DBFIND_STALETIMEOUT) == 0 || ++ !stale_rrset || stale_refresh_window) ++ { ++ return; ++ } ++ ++ char namebuf[DNS_NAME_FORMATSIZE]; ++ char typebuf[DNS_RDATATYPE_FORMATSIZE]; ++ dns_name_format(qname, namebuf, sizeof(namebuf)); ++ dns_rdatatype_format(client->query.qtype, typebuf, sizeof(typebuf)); ++ isc_log_write(ns_lctx, NS_LOGCATEGORY_SERVE_STALE, NS_LOGMODULE_QUERY, ++ ISC_LOG_INFO, ++ "%s %s stale answer used, an attempt " ++ "to refresh the RRset will still be " ++ "made", ++ namebuf, typebuf); ++ ++ client->query.dboptions &= ~(DNS_DBFIND_STALETIMEOUT | ++ DNS_DBFIND_STALEOK | ++ DNS_DBFIND_STALEENABLED); ++ ++ if (client->recursionquota == NULL) { ++ result = isc_quota_attach(&client->sctx->recursionquota, ++ &client->recursionquota); ++ switch (result) { ++ case ISC_R_SUCCESS: ++ ns_stats_increment(client->sctx->nsstats, ++ ns_statscounter_recursclients); ++ break; ++ case ISC_R_SOFTQUOTA: ++ isc_quota_detach(&client->recursionquota); ++ FALLTHROUGH; ++ default: ++ return; ++ } ++ } ++ ++ tmprdataset = ns_client_newrdataset(client); ++ if (tmprdataset == NULL) { ++ return; ++ } ++ ++ if (!TCP(client)) { ++ peeraddr = &client->peeraddr; ++ } else { ++ peeraddr = NULL; ++ } ++ ++ isc_nmhandle_attach(client->handle, &client->stalerefreshhandle); ++ options = client->query.fetchoptions; ++ result = dns_resolver_createfetch( ++ client->view->resolver, qname, client->query.qtype, NULL, NULL, NULL, ++ peeraddr, client->message->id, options, 0, NULL, client->task, ++ refresh_done, client, tmprdataset, NULL, ++ &client->query.stalerefresh); ++ if (result != ISC_R_SUCCESS) { ++ ns_client_putrdataset(client, &tmprdataset); ++ isc_nmhandle_detach(&client->stalerefreshhandle); ++ } ++} ++ ++static void ++query_stale_refresh_ncache(ns_client_t *client) { ++ dns_name_t *qname; ++ ++ if (client->query.origqname != NULL) { ++ qname = client->query.origqname; ++ } else { ++ qname = client->query.qname; ++ } ++ query_stale_refresh(client, qname, NULL); ++} ++ + static void + query_prefetch(ns_client_t *client, dns_name_t *qname, + dns_rdataset_t *rdataset) { +@@ -2579,6 +2788,7 @@ query_prefetch(ns_client_t *client, dns_name_t *qname, + rdataset->ttl > client->view->prefetch_trigger || + (rdataset->attributes & DNS_RDATASETATTR_PREFETCH) == 0) + { ++ query_stale_refresh(client, qname, rdataset); + return; + } + +@@ -2623,6 +2833,7 @@ query_prefetch(ns_client_t *client, dns_name_t *qname, + + dns_rdataset_clearprefetch(rdataset); + ns_stats_increment(client->sctx->nsstats, ns_statscounter_prefetch); ++ return; + } + + static void +@@ -5801,55 +6012,6 @@ error: + return (ISC_R_NOMEMORY); + } + +-/* +- * Setup a new query context for resolving a query. +- * +- * This function is only called if both these conditions are met: +- * 1. BIND is configured with stale-answer-client-timeout 0. +- * 2. A stale RRset is found in cache during initial query +- * database lookup. +- * +- * We continue with this function for refreshing/resolving an RRset +- * after answering a client with stale data. +- */ +-static void +-query_refresh_rrset(query_ctx_t *orig_qctx) { +- isc_buffer_t buffer; +- query_ctx_t qctx; +- +- REQUIRE(orig_qctx != NULL); +- REQUIRE(orig_qctx->client != NULL); +- +- qctx_copy(orig_qctx, &qctx); +- qctx.client->query.dboptions &= ~(DNS_DBFIND_STALETIMEOUT | +- DNS_DBFIND_STALEOK | +- DNS_DBFIND_STALEENABLED); +- qctx.client->nodetach = false; +- +- /* +- * We'll need some resources... +- */ +- if (qctx_prepare_buffers(&qctx, &buffer) != ISC_R_SUCCESS) { +- dns_db_detach(&qctx.db); +- qctx_destroy(&qctx); +- return; +- } +- +- /* +- * Pretend we didn't find anything in cache. +- */ +- (void)query_gotanswer(&qctx, ISC_R_NOTFOUND); +- +- if (qctx.fname != NULL) { +- ns_client_releasename(qctx.client, &qctx.fname); +- } +- if (qctx.rdataset != NULL) { +- ns_client_putrdataset(qctx.client, &qctx.rdataset); +- } +- +- qctx_destroy(&qctx); +-} +- + /*% + * Depending on the db lookup result, we can respond to the + * client this stale answer. +@@ -5921,7 +6083,7 @@ query_lookup(query_ctx_t *qctx) { + rpzqname = qctx->client->query.qname; + } + +- if ((qctx->options & DNS_GETDB_STALEFIRST) != 0) { ++ if ((qctx->options & DNS_GETDB_STALEFIRST) != 0 && !qctx->is_zone) { + /* + * If DNS_GETDB_STALEFIRST is set, it means that a stale + * RRset may be returned as part of this lookup. An attempt +@@ -5931,19 +6093,19 @@ query_lookup(query_ctx_t *qctx) { + qctx->client->query.dboptions |= DNS_DBFIND_STALETIMEOUT; + } + +- dboptions = qctx->client->query.dboptions; +- if (!qctx->is_zone && qctx->findcoveringnsec && +- (qctx->type != dns_rdatatype_null || !dns_name_istat(rpzqname))) +- { +- dboptions |= DNS_DBFIND_COVERINGNSEC; +- } +- + (void)dns_db_getservestalerefresh(qctx->client->view->cachedb, + &stale_refresh); + if (stale_refresh > 0 && + dns_view_staleanswerenabled(qctx->client->view)) + { +- dboptions |= DNS_DBFIND_STALEENABLED; ++ qctx->client->query.dboptions |= DNS_DBFIND_STALEENABLED; ++ } ++ ++ dboptions = qctx->client->query.dboptions; ++ if (!qctx->is_zone && qctx->findcoveringnsec && ++ (qctx->type != dns_rdatatype_null || !dns_name_istat(rpzqname))) ++ { ++ dboptions |= DNS_DBFIND_COVERINGNSEC; + } + + result = dns_db_findext(qctx->db, rpzqname, qctx->version, qctx->type, +@@ -6092,19 +6254,7 @@ query_lookup(query_ctx_t *qctx) { + * Immediately return the stale answer, start a + * resolver fetch to refresh the data in cache. + */ +- isc_log_write( +- ns_lctx, NS_LOGCATEGORY_SERVE_STALE, +- NS_LOGMODULE_QUERY, ISC_LOG_INFO, +- "%s stale answer used, an attempt to " +- "refresh the RRset will still be made", +- namebuf); +- +- qctx->refresh_rrset = STALE(qctx->rdataset); +- /* +- * If we are refreshing the RRSet, we must not +- * detach from the client in query_send(). +- */ +- qctx->client->nodetach = qctx->refresh_rrset; ++ qctx->client->nodetach = false; + + if (stale_found) { + ns_client_extendederror( +@@ -6144,76 +6294,12 @@ query_lookup(query_ctx_t *qctx) { + } + } + +- if (stale_timeout && (answer_found || stale_found)) { +- /* +- * Mark RRsets that we are adding to the client message on a +- * lookup during 'stale-answer-client-timeout', so we can +- * clean it up if needed when we resume from recursion. +- */ +- qctx->client->query.attributes |= NS_QUERYATTR_STALEOK; +- qctx->rdataset->attributes |= DNS_RDATASETATTR_STALE_ADDED; +- } +- + result = query_gotanswer(qctx, result); + + cleanup: + return (result); + } + +-/* +- * Clear all rdatasets from the message that are in the given section and +- * that have the 'attr' attribute set. +- */ +-static void +-message_clearrdataset(dns_message_t *msg, unsigned int attr) { +- unsigned int i; +- dns_name_t *name, *next_name; +- dns_rdataset_t *rds, *next_rds; +- +- /* +- * Clean up name lists by calling the rdataset disassociate function. +- */ +- for (i = DNS_SECTION_ANSWER; i < DNS_SECTION_MAX; i++) { +- name = ISC_LIST_HEAD(msg->sections[i]); +- while (name != NULL) { +- next_name = ISC_LIST_NEXT(name, link); +- +- rds = ISC_LIST_HEAD(name->list); +- while (rds != NULL) { +- next_rds = ISC_LIST_NEXT(rds, link); +- if ((rds->attributes & attr) != attr) { +- rds = next_rds; +- continue; +- } +- ISC_LIST_UNLINK(name->list, rds, link); +- INSIST(dns_rdataset_isassociated(rds)); +- dns_rdataset_disassociate(rds); +- isc_mempool_put(msg->rdspool, rds); +- rds = next_rds; +- } +- +- if (ISC_LIST_EMPTY(name->list)) { +- ISC_LIST_UNLINK(msg->sections[i], name, link); +- if (dns_name_dynamic(name)) { +- dns_name_free(name, msg->mctx); +- } +- isc_mempool_put(msg->namepool, name); +- } +- +- name = next_name; +- } +- } +-} +- +-/* +- * Clear any rdatasets from the client's message that were added on a lookup +- * due to a client timeout. +- */ +-static void +-query_clear_stale(ns_client_t *client) { +- message_clearrdataset(client->message, DNS_RDATASETATTR_STALE_ADDED); +-} +- + /* + * Create a new query context with the sole intent of looking up for a stale + * RRset in cache. If an entry is found, we mark the original query as +@@ -6293,6 +6379,7 @@ fetch_callback(isc_task_t *task, isc_event_t *event) { + } + client->query.fetchoptions &= ~DNS_FETCHOPT_TRYSTALE_ONTIMEOUT; + client->query.dboptions &= ~DNS_DBFIND_STALETIMEOUT; ++ client->query.dboptions &= ~DNS_DBFIND_STALEENABLED; + client->nodetach = false; + + LOCK(&client->query.fetchlock); +@@ -7660,14 +7747,6 @@ query_usestale(query_ctx_t *qctx, isc_result_t result) { + return (false); + } + +- if (qctx->refresh_rrset) { +- /* +- * This is a refreshing query, we have already prioritized +- * stale data, so don't enable serve-stale again. +- */ +- return (false); +- } +- + if (result == DNS_R_DUPLICATE || result == DNS_R_DROP || + result == ISC_R_ALREADYRUNNING) + { +@@ -8207,24 +8286,6 @@ query_addanswer(query_ctx_t *qctx) { + + CALL_HOOK(NS_QUERY_ADDANSWER_BEGIN, qctx); + +- /* +- * On normal lookups, clear any rdatasets that were added on a +- * lookup due to stale-answer-client-timeout. Do not clear if we +- * are going to refresh the RRset, because the stale contents are +- * prioritized. +- */ +- if (QUERY_STALEOK(&qctx->client->query) && +- !QUERY_STALETIMEOUT(&qctx->client->query) && !qctx->refresh_rrset) +- { +- CCTRACE(ISC_LOG_DEBUG(3), "query_clear_stale"); +- query_clear_stale(qctx->client); +- /* +- * We can clear the attribute to prevent redundant clearing +- * in subsequent lookups. +- */ +- qctx->client->query.attributes &= ~NS_QUERYATTR_STALEOK; +- } +- + if (qctx->dns64) { + result = query_dns64(qctx); + qctx->noqname = NULL; +@@ -8258,9 +8319,7 @@ query_addanswer(query_ctx_t *qctx) { + query_filter64(qctx); + ns_client_putrdataset(qctx->client, &qctx->rdataset); + } else { +- if (!qctx->is_zone && RECURSIONOK(qctx->client) && +- !QUERY_STALETIMEOUT(&qctx->client->query)) +- { ++ if (!qctx->is_zone && RECURSIONOK(qctx->client)) { + query_prefetch(qctx->client, qctx->fname, + qctx->rdataset); + } +@@ -10542,6 +10601,10 @@ query_ncache(query_ctx_t *qctx, isc_result_t result) { + } + } + ++ if (!qctx->is_zone && RECURSIONOK(qctx->client)) { ++ query_stale_refresh_ncache(qctx->client); ++ } ++ + return (query_nodata(qctx, result)); + + cleanup: +@@ -12044,20 +12107,6 @@ ns_query_done(query_ctx_t *qctx) { + nodetach = qctx->client->nodetach; + query_send(qctx->client); + +- if (qctx->refresh_rrset) { +- /* +- * If we reached this point then it means that we have found a +- * stale RRset entry in cache and BIND is configured to allow +- * queries to be answered with stale data if no active RRset +- * is available, i.e. "stale-anwer-client-timeout 0". But, we +- * still need to refresh the RRset. To prevent adding duplicate +- * RRsets, clear the RRsets from the message before doing the +- * refresh. +- */ +- message_clearrdataset(qctx->client->message, 0); +- query_refresh_rrset(qctx); +- } +- + if (!nodetach) { + qctx->detach_client = true; + } diff --git a/SPECS/bind9.18.spec b/SPECS/bind9.18.spec index 22f4a26..42a70ad 100644 --- a/SPECS/bind9.18.spec +++ b/SPECS/bind9.18.spec @@ -75,7 +75,7 @@ License: MPL-2.0 AND ISC AND MIT AND BSD-3-Clause AND BSD-2-Clause # ./lib/isc/tm.c BSD-2-clause and/or MPL-2.0 # ./lib/isccfg/parser.c BSD-2-clause and/or MPL-2.0 Version: 9.18.29 -Release: 13%{?dist} +Release: 16%{?dist} Epoch: 32 Url: https://www.isc.org/downloads/bind/ # @@ -143,6 +143,15 @@ Patch225: bind-9.18-CVE-2025-40780.patch Patch226: bind-9.20-CVE-2025-8677-dual-signing.patch # https://gitlab.isc.org/isc-projects/bind9/-/merge_requests/11195 Patch227: bind-9.20-CVE-2025-8677-dual-signing-test.patch +# https://gitlab.isc.org/isc-projects/bind9/-/merge_requests/11329 +Patch228: bind-9.20-robust-key-rollovers.patch +Patch229: bind-9.20-robust-key-rollovers-tests.patch +# https://gitlab.isc.org/isc-projects/bind9/-/merge_requests/10767 +Patch230: bind-9.20-stale-cname.patch +Patch231: bind-9.20-stale-cname-tests.patch +# https://gitlab.isc.org/isc-projects/bind9/-/commit/5ef459eeaa92222ad28d2186f5eae9a586dece70 +Patch232: bind-9.18-CVE-2026-1519.patch +Patch233: bind-9.18-CVE-2026-1519-test.patch %{?systemd_ordering} # https://fedoraproject.org/wiki/Changes/RPMSuportForSystemdSysusers @@ -1006,6 +1015,17 @@ fi; %endif %changelog +* Fri Mar 27 2026 Petr Menšík - 32:9.18.29-16 +- Prevent Denial of Service via maliciously crafted DNSSEC-validated zone + (CVE-2026-1519) +- Correct backport issue in the patch (CVE-2026-1519) + +* Thu Feb 26 2026 Fedor Vorobev - 32:9.18.29-15 +- Backport fixes for stale CNAME chains. (RHEL-86172) + +* Fri Jan 30 2026 Fedor Vorobev - 32:9.18.29-14 +- Backport fix for manual DNSSEC key rolllovers. (RHEL-144422) + * Thu Jan 29 2026 Petr Menšík - 32:9.18.29-13 - Correct changelog version of previous change