From 05c51d3a5aedf7cb56407c5df02f7ab6deaf5755 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 01/15] 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) --- .../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..5bd17ed874 --- /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 51 -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" + ) From cd1f2fed56d8e93ddd1e0b7b240cb61f6774dcfd Mon Sep 17 00:00:00 2001 From: Matthijs Mekking Date: Tue, 3 Mar 2026 10:40:36 +0100 Subject: [PATCH 02/15] 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) --- lib/dns/validator.c | 66 +++++++++++++++++++++++++++++++++------------ 1 file changed, 49 insertions(+), 17 deletions(-) diff --git a/lib/dns/validator.c b/lib/dns/validator.c index 4ab4ee407e..884209fc12 100644 --- a/lib/dns/validator.c +++ b/lib/dns/validator.c @@ -257,12 +257,25 @@ validator_done(dns_validator_t *val, isc_result_t result) { } /*% - * 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; @@ -290,7 +303,7 @@ isdelegation(dns_name_t *name, dns_rdataset_t *rdataset, goto trynsec3; } if (result != ISC_R_SUCCESS) { - return false; + return ISC_R_NOTFOUND; } } @@ -304,7 +317,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: /* @@ -343,6 +356,18 @@ trynsec3: if (nsec3.next_length > NSEC3_MAX_HASH_LENGTH) { 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); @@ -354,7 +379,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; @@ -370,12 +395,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; } static void @@ -589,9 +614,10 @@ fetch_callback_ds(void *arg) { break; case DNS_R_NXRRSET: case DNS_R_NCACHENXRRSET: - if (isdelegation(resp->foundname, &val->frdataset, - eresult)) - { + result = isdelegation(val, resp->foundname, + &val->frdataset, eresult, + "fetch_callback_ds"); + if (result == ISC_R_SUCCESS) { /* * Failed to find a DS while trying to prove * insecurity. If this is a zone cut, that @@ -707,10 +733,13 @@ validator_callback_ds(void *arg) { 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"); @@ -2932,7 +2961,8 @@ 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; } @@ -2968,7 +2998,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; } @@ -3181,7 +3211,9 @@ seek_ds(dns_validator_t *val, isc_result_t *resp) { return ISC_R_COMPLETE; } - if (isdelegation(tname, &val->frdataset, result)) { + 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; From 4ba3caff98f8d0ed585e6a30087202007e072ffb Mon Sep 17 00:00:00 2001 From: Matthijs Mekking Date: Tue, 3 Mar 2026 11:17:25 +0100 Subject: [PATCH 03/15] 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) --- lib/dns/include/dns/types.h | 1 + lib/dns/validator.c | 10 +++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/dns/include/dns/types.h b/lib/dns/include/dns/types.h index 992bfceef5..f44427f3e2 100644 --- a/lib/dns/include/dns/types.h +++ b/lib/dns/include/dns/types.h @@ -393,6 +393,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 884209fc12..ebf7a95dde 100644 --- a/lib/dns/validator.c +++ b/lib/dns/validator.c @@ -1516,11 +1516,19 @@ 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->rdataset->trust)) { + /* + * This RRset was already verified before. + */ + return ISC_R_SUCCESS; + } + val->attributes |= VALATTR_TRIEDVERIFY; - wild = dns_fixedname_initname(&fixed); if (over_max_validations(val)) { return ISC_R_QUOTA; } + wild = dns_fixedname_initname(&fixed); + again: result = dns_dnssec_verify(val->name, val->rdataset, key, ignore, val->view->maxbits, val->view->mctx, rdata, From 1fb2c667fd3b38614687056244ba96200c31b59b Mon Sep 17 00:00:00 2001 From: Matthijs Mekking Date: Tue, 3 Mar 2026 11:18:55 +0100 Subject: [PATCH 04/15] Combine validator_log and marksecure When we mark RRsets as secure, we most of the time also log a debug message. Combine this the same way as 'markanswer()' does. (cherry picked from commit d4c7c83a7085fee6addda47e84e2a9a47540f3f2) --- lib/dns/validator.c | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/lib/dns/validator.c b/lib/dns/validator.c index ebf7a95dde..61e77a8995 100644 --- a/lib/dns/validator.c +++ b/lib/dns/validator.c @@ -230,7 +230,8 @@ markanswer(dns_validator_t *val, const char *where, const char *mbstext) { * Mark the RRsets in val->vstat with trust level secure. */ static void -marksecure(dns_validator_t *val) { +marksecure(dns_validator_t *val, const char *where) { + validator_log(val, ISC_LOG_DEBUG(3), "marking as secure (%s)", where); dns_rdataset_settrust(val->rdataset, dns_trust_secure); if (val->sigrdataset != NULL) { dns_rdataset_settrust(val->sigrdataset, dns_trust_secure); @@ -1883,9 +1884,7 @@ validate_answer_finish(void *arg) { } if (val->result == ISC_R_SUCCESS) { - marksecure(val); - validator_log(val, ISC_LOG_DEBUG(3), - "marking as secure, noqname proof not needed"); + marksecure(val, "noqname proof not needed"); validate_async_done(val, val->result); return; } @@ -2094,8 +2093,7 @@ validate_dnskey_dsset_done(dns_validator_t *val, isc_result_t result) { /* Abort, abort, abort! */ break; case ISC_R_SUCCESS: - marksecure(val); - validator_log(val, ISC_LOG_DEBUG(3), "marking as secure (DS)"); + marksecure(val, "validate_dnskey (DS)"); break; case ISC_R_NOMORE: if (val->unsupported_algorithm != 0 || @@ -2978,9 +2976,7 @@ validate_nx(dns_validator_t *val, bool resume) { if (FOUNDNOQNAME(val) && FOUNDCLOSEST(val) && !FOUNDOPTOUT(val)) { - validator_log(val, ISC_LOG_DEBUG(3), - "marking as secure, noqname proof found"); - marksecure(val); + marksecure(val, "validate_nx (noqname proof found)"); return ISC_R_SUCCESS; } else if (FOUNDOPTOUT(val) && dns_name_countlabels( @@ -3034,7 +3030,8 @@ validate_nx(dns_validator_t *val, bool resume) { validator_log(val, ISC_LOG_DEBUG(3), "nonexistence proof(s) found"); if (val->message == NULL) { - marksecure(val); + marksecure(val, + "validate_nx (nonexistence proofs found)"); } else { val->secure = true; } From ef01ff31db4be0d737949fd785fa52c491041eb4 Mon Sep 17 00:00:00 2001 From: Matthijs Mekking Date: Tue, 3 Mar 2026 11:43:23 +0100 Subject: [PATCH 05/15] 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) --- lib/dns/validator.c | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/lib/dns/validator.c b/lib/dns/validator.c index 61e77a8995..81fb39ebd1 100644 --- a/lib/dns/validator.c +++ b/lib/dns/validator.c @@ -2804,7 +2804,19 @@ 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->nxset = rdataset; + result = create_validator(val, name, rdataset->type, rdataset, sigrdataset, validator_callback_nsec, "validate_neg_rrset"); @@ -2914,11 +2926,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; From b858e25438e011b5526fe68fa2f2de5940a02d20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicki=20K=C5=99=C3=AD=C5=BEek?= Date: Fri, 20 Feb 2026 16:35:29 +0100 Subject: [PATCH 06/15] Test excessive RRSIG(NSEC) in signed zones Trigger a memory leak by adding extra RRSIG(NSEC) to a signed zone which exceeds the resolver's configured max-records-per-type limit. (cherry picked from commit a338e254beda3085fbe7b91905e668ba9b0cfb7c) --- bin/tests/system/nsec/ns1/named.conf.j2 | 29 +++++++ bin/tests/system/nsec/ns1/root.db | 24 +++++ .../nsec/ns2/excessive-nsec-rrsigs.db.in | 24 +++++ bin/tests/system/nsec/ns2/named.conf.j2 | 29 +++++++ bin/tests/system/nsec/ns3/named.conf.j2 | 35 ++++++++ bin/tests/system/nsec/ns3/trusted.conf.j2 | 1 + .../system/nsec/tests_excessive_rrsigs.py | 87 +++++++++++++++++++ 7 files changed, 229 insertions(+) create mode 100644 bin/tests/system/nsec/ns1/named.conf.j2 create mode 100644 bin/tests/system/nsec/ns1/root.db create mode 100644 bin/tests/system/nsec/ns2/excessive-nsec-rrsigs.db.in create mode 100644 bin/tests/system/nsec/ns2/named.conf.j2 create mode 100644 bin/tests/system/nsec/ns3/named.conf.j2 create mode 120000 bin/tests/system/nsec/ns3/trusted.conf.j2 create mode 100755 bin/tests/system/nsec/tests_excessive_rrsigs.py diff --git a/bin/tests/system/nsec/ns1/named.conf.j2 b/bin/tests/system/nsec/ns1/named.conf.j2 new file mode 100644 index 0000000000..eb079c95ab --- /dev/null +++ b/bin/tests/system/nsec/ns1/named.conf.j2 @@ -0,0 +1,29 @@ +/* + * 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; + notify yes; +}; + +zone "." { + type primary; + file "root.db"; +}; diff --git a/bin/tests/system/nsec/ns1/root.db b/bin/tests/system/nsec/ns1/root.db new file mode 100644 index 0000000000..49f0d671cb --- /dev/null +++ b/bin/tests/system/nsec/ns1/root.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 . a.root.servers.nil. ( + 2000042100 ; serial + 600 ; refresh + 600 ; retry + 1200 ; expire + 600 ; minimum + ) +. NS a.root-servers.nil. +a.root-servers.nil. A 10.53.0.1 + +excessive-nsec-rrsigs. NS ns2.excessive-nsec-rrsigs. +ns2.excessive-nsec-rrsigs. A 10.53.0.2 diff --git a/bin/tests/system/nsec/ns2/excessive-nsec-rrsigs.db.in b/bin/tests/system/nsec/ns2/excessive-nsec-rrsigs.db.in new file mode 100644 index 0000000000..135e93d287 --- /dev/null +++ b/bin/tests/system/nsec/ns2/excessive-nsec-rrsigs.db.in @@ -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 mname1. . ( + 1 ; serial + 600 ; refresh + 600 ; retry + 1200 ; expire + 600 ; minimum + ) + +@ NS ns2 +ns2 A 10.53.0.2 + +* A 127.0.0.1 diff --git a/bin/tests/system/nsec/ns2/named.conf.j2 b/bin/tests/system/nsec/ns2/named.conf.j2 new file mode 100644 index 0000000000..32a566ad79 --- /dev/null +++ b/bin/tests/system/nsec/ns2/named.conf.j2 @@ -0,0 +1,29 @@ +/* + * 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; + notify yes; +}; + +zone "excessive-nsec-rrsigs" { + type primary; + file "excessive-nsec-rrsigs.db.signed"; +}; diff --git a/bin/tests/system/nsec/ns3/named.conf.j2 b/bin/tests/system/nsec/ns3/named.conf.j2 new file mode 100644 index 0000000000..5a8ef09276 --- /dev/null +++ b/bin/tests/system/nsec/ns3/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. + */ + +// validating resolver + +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; + + max-records-per-type 2; +}; + +zone "." { + type hint; + file "../../_common/root.hint"; +}; + +include "trusted.conf"; diff --git a/bin/tests/system/nsec/ns3/trusted.conf.j2 b/bin/tests/system/nsec/ns3/trusted.conf.j2 new file mode 120000 index 0000000000..cb0be77b22 --- /dev/null +++ b/bin/tests/system/nsec/ns3/trusted.conf.j2 @@ -0,0 +1 @@ +../../_common/trusted.conf.j2 \ No newline at end of file diff --git a/bin/tests/system/nsec/tests_excessive_rrsigs.py b/bin/tests/system/nsec/tests_excessive_rrsigs.py new file mode 100755 index 0000000000..8bc62fda71 --- /dev/null +++ b/bin/tests/system/nsec/tests_excessive_rrsigs.py @@ -0,0 +1,87 @@ +#!/usr/bin/python3 + +# 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. + +import dns.rdataclass +import dns.rdatatype +import dns.rdtypes.ANY.RRSIG +import dns.zone + +from isctest.run import EnvCmd + +import isctest + + +def duplicate_rrsig(rdata, i): + return dns.rdtypes.ANY.RRSIG.RRSIG( + rdclass=rdata.rdclass, + rdtype=rdata.rdtype, + type_covered=rdata.type_covered, + algorithm=rdata.algorithm, + labels=rdata.labels, + # increment orig TTL so the rdataset isn't treated as identical record by dnspython + original_ttl=rdata.original_ttl + i, + expiration=rdata.expiration, + inception=rdata.inception, + key_tag=rdata.key_tag, + signer=rdata.signer, + signature=rdata.signature, + ) + + +def bootstrap(): + keygen = EnvCmd("KEYGEN", "-a ECDSA256 -Kns2 -q") + signer = EnvCmd("SIGNER", "-S -g") + + zone = "excessive-nsec-rrsigs" + infile = f"{zone}.db.in" + signedfile = f"{zone}.db.signed" + + isctest.log.info(f"{zone}: generate ksk and zsk") + ksk_name = keygen(f"-f KSK {zone}").out.strip() + keygen(f"{zone}").out.strip() + ksk = isctest.kasp.Key(ksk_name, keydir="ns2") + + isctest.log.info(f"{zone}: sign zone") + signer(f"-P -x -O full -o {zone} -f {signedfile} {infile}", cwd="ns2") + + isctest.log.info( + f"{zone}: duplicate the RRSIG(NSEC) to exceed max-records-per-type" + ) + zone = dns.zone.from_file(f"ns2/{signedfile}", origin=zone) + for node in zone.values(): + rrsig_rdataset = node.find_rdataset( + dns.rdataclass.IN, dns.rdatatype.RRSIG, dns.rdatatype.NSEC + ) + orig = rrsig_rdataset[0] + rrsig_rdataset.add(duplicate_rrsig(orig, 1)) + rrsig_rdataset.add(duplicate_rrsig(orig, 2)) + zone.to_file(f"ns2/{signedfile}", sorted=True) + + return { + "trust_anchors": [ + ksk.into_ta("static-key"), + ], + } + + +# reproducer for CVE-2026-3104 [GL#5742] +def test_excessive_rrsigs(ns3): + # the real test is that there is no crash on shutdown - checked by the test + # framework when the test finishes + + # multiple queries seem more reliable to reproduce the memory leak, using a + # single query sometimes didn't cause a crash on shutdown + for i in range(10): + msg = isctest.query.create(f"x{i}.excessive-nsec-rrsigs", "A") + res = isctest.query.udp(msg, ns3.ip, attempts=1) + isctest.check.servfail(res) From 5f15df5c53a445846083c46a9437910f8f6c3127 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Sur=C3=BD?= Date: Tue, 10 Feb 2026 16:16:25 +0100 Subject: [PATCH 07/15] Fix memory leak in QPcache addnoqname/addclosest mechanism The attacker that controls DNSSEC-signed zone can trigger a memory leak in the addnoqname() and/or addclosest() by creating more than max-records-per-type RRSIG for any NSEC records. The memory leaks have been fixed. (cherry picked from commit a854a5c83d3a8556d31df880d22e3f835527d45d) --- lib/dns/qpcache.c | 20 ++++++++++++++++++-- lib/dns/rbtdb.c | 20 ++++++++++++++++++-- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/lib/dns/qpcache.c b/lib/dns/qpcache.c index 8ceb23acc7..1969d2116f 100644 --- a/lib/dns/qpcache.c +++ b/lib/dns/qpcache.c @@ -3279,7 +3279,7 @@ addnoqname(isc_mem_t *mctx, dns_slabheader_t *newheader, uint32_t maxrrperset, dns_slabheader_proof_t *noqname = NULL; dns_name_t name = DNS_NAME_INITEMPTY; dns_rdataset_t neg = DNS_RDATASET_INIT, negsig = DNS_RDATASET_INIT; - isc_region_t r1, r2; + isc_region_t r1 = { .base = NULL }, r2 = { .base = NULL }; result = dns_rdataset_getnoqname(rdataset, &name, &neg, &negsig); RUNTIME_CHECK(result == ISC_R_SUCCESS); @@ -3305,6 +3305,14 @@ addnoqname(isc_mem_t *mctx, dns_slabheader_t *newheader, uint32_t maxrrperset, newheader->noqname = noqname; cleanup: + if (result != ISC_R_SUCCESS) { + if (r1.base != NULL) { + isc_mem_put(mctx, r1.base, r1.length); + } + if (r2.base != NULL) { + isc_mem_put(mctx, r2.base, r2.length); + } + } dns_rdataset_disassociate(&neg); dns_rdataset_disassociate(&negsig); @@ -3318,7 +3326,7 @@ addclosest(isc_mem_t *mctx, dns_slabheader_t *newheader, uint32_t maxrrperset, dns_slabheader_proof_t *closest = NULL; dns_name_t name = DNS_NAME_INITEMPTY; dns_rdataset_t neg = DNS_RDATASET_INIT, negsig = DNS_RDATASET_INIT; - isc_region_t r1, r2; + isc_region_t r1 = { .base = NULL }, r2 = { .base = NULL }; result = dns_rdataset_getclosest(rdataset, &name, &neg, &negsig); RUNTIME_CHECK(result == ISC_R_SUCCESS); @@ -3344,6 +3352,14 @@ addclosest(isc_mem_t *mctx, dns_slabheader_t *newheader, uint32_t maxrrperset, newheader->closest = closest; cleanup: + if (result != ISC_R_SUCCESS) { + if (r1.base != NULL) { + isc_mem_put(mctx, r1.base, r1.length); + } + if (r2.base != NULL) { + isc_mem_put(mctx, r2.base, r2.length); + } + } dns_rdataset_disassociate(&neg); dns_rdataset_disassociate(&negsig); return result; diff --git a/lib/dns/rbtdb.c b/lib/dns/rbtdb.c index 2abaab2901..386b862924 100644 --- a/lib/dns/rbtdb.c +++ b/lib/dns/rbtdb.c @@ -3180,7 +3180,7 @@ addnoqname(isc_mem_t *mctx, dns_slabheader_t *newheader, uint32_t maxrrperset, dns_slabheader_proof_t *noqname = NULL; dns_name_t name = DNS_NAME_INITEMPTY; dns_rdataset_t neg = DNS_RDATASET_INIT, negsig = DNS_RDATASET_INIT; - isc_region_t r1, r2; + isc_region_t r1 = { .base = NULL }, r2 = { .base = NULL }; result = dns_rdataset_getnoqname(rdataset, &name, &neg, &negsig); RUNTIME_CHECK(result == ISC_R_SUCCESS); @@ -3206,6 +3206,14 @@ addnoqname(isc_mem_t *mctx, dns_slabheader_t *newheader, uint32_t maxrrperset, newheader->noqname = noqname; cleanup: + if (result != ISC_R_SUCCESS) { + if (r1.base != NULL) { + isc_mem_put(mctx, r1.base, r1.length); + } + if (r2.base != NULL) { + isc_mem_put(mctx, r2.base, r2.length); + } + } dns_rdataset_disassociate(&neg); dns_rdataset_disassociate(&negsig); @@ -3219,7 +3227,7 @@ addclosest(isc_mem_t *mctx, dns_slabheader_t *newheader, uint32_t maxrrperset, dns_slabheader_proof_t *closest = NULL; dns_name_t name = DNS_NAME_INITEMPTY; dns_rdataset_t neg = DNS_RDATASET_INIT, negsig = DNS_RDATASET_INIT; - isc_region_t r1, r2; + isc_region_t r1 = { .base = NULL }, r2 = { .base = NULL }; result = dns_rdataset_getclosest(rdataset, &name, &neg, &negsig); RUNTIME_CHECK(result == ISC_R_SUCCESS); @@ -3245,6 +3253,14 @@ addclosest(isc_mem_t *mctx, dns_slabheader_t *newheader, uint32_t maxrrperset, newheader->closest = closest; cleanup: + if (result != ISC_R_SUCCESS) { + if (r1.base != NULL) { + isc_mem_put(mctx, r1.base, r1.length); + } + if (r2.base != NULL) { + isc_mem_put(mctx, r2.base, r2.length); + } + } dns_rdataset_disassociate(&neg); dns_rdataset_disassociate(&negsig); return result; From 527ec954563641c9922a4dd126096eef3e59f527 Mon Sep 17 00:00:00 2001 From: Aram Sargsyan Date: Fri, 20 Feb 2026 13:48:17 +0000 Subject: [PATCH 08/15] Test sending a TKEY query with deletion and unrecognized modes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This new test sends two signed TKEY queries, one in delegation mode and one in an unrecognized mode to check that named correctly processes them. Co-authored-by: Nicki Křížek (cherry picked from commit ab77b3dffa343f88e36d24571dc2b142a65f8f4b) --- bin/tests/system/tkey/ns1/example.db | 23 ++++++++ bin/tests/system/tkey/ns1/named.conf.j2 | 35 +++++++++++ bin/tests/system/tkey/tests_cve_2026_3119.py | 62 ++++++++++++++++++++ 3 files changed, 120 insertions(+) create mode 100644 bin/tests/system/tkey/ns1/example.db create mode 100644 bin/tests/system/tkey/ns1/named.conf.j2 create mode 100644 bin/tests/system/tkey/tests_cve_2026_3119.py diff --git a/bin/tests/system/tkey/ns1/example.db b/bin/tests/system/tkey/ns1/example.db new file mode 100644 index 0000000000..49c499c3a0 --- /dev/null +++ b/bin/tests/system/tkey/ns1/example.db @@ -0,0 +1,23 @@ +; 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. + +$ORIGIN . +$TTL 300 ; 5 minutes +example.nil IN SOA ns1.example.nil. hostmaster.example.nil. ( + 1 ; serial + 2000 ; refresh (2000 seconds) + 2000 ; retry (2000 seconds) + 1814400 ; expire (3 weeks) + 3600 ; minimum (1 hour) + ) +example.nil. NS ns1.example.nil. +ns1.example.nil. A 10.53.0.1 +a.example.nil. A 10.53.0.1 diff --git a/bin/tests/system/tkey/ns1/named.conf.j2 b/bin/tests/system/tkey/ns1/named.conf.j2 new file mode 100644 index 0000000000..4603956744 --- /dev/null +++ b/bin/tests/system/tkey/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; + notify no; +}; + +key "test-key" { + algorithm "hmac-sha256"; + secret "R16NojROxtxH/xbDl//ehDsHm5DjWTQ2YXV+hGC2iBY="; +}; + +zone "example.nil" { + type primary; + file "example.db"; +}; diff --git a/bin/tests/system/tkey/tests_cve_2026_3119.py b/bin/tests/system/tkey/tests_cve_2026_3119.py new file mode 100644 index 0000000000..bbf673d8df --- /dev/null +++ b/bin/tests/system/tkey/tests_cve_2026_3119.py @@ -0,0 +1,62 @@ +#!/usr/bin/python3 + +# 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. + +# pylint: disable=unused-variable + +import time + +import dns.message +import dns.rdataclass +import dns.rdatatype +import dns.rdtypes.ANY.TKEY +import dns.rrset +import dns.tsigkeyring +import pytest + +import isctest + +pytestmark = pytest.mark.extra_artifacts([]) + + +def create_tkey_msg(qname, mode, alg="hmac-sha256"): + msg = dns.message.make_query(qname, "TKEY") + now = int(time.time()) + rdata = dns.rdtypes.ANY.TKEY.TKEY( + rdclass=dns.rdataclass.ANY, + rdtype=dns.rdatatype.TKEY, + algorithm=alg, + inception=now - 3600, + expiration=now + 86400, + mode=mode, + error=0, + key=b"", + ) + rrset = dns.rrset.from_rdata(qname, dns.rdatatype.TKEY, rdata) + msg.additional.append(rrset) + return msg + + +def test_tkey_cve_2026_3119(ns1): + keyring = dns.tsigkeyring.from_text( + { + "test-key": "R16NojROxtxH/xbDl//ehDsHm5DjWTQ2YXV+hGC2iBY=", + } + ) + + msg_delete = create_tkey_msg("a.example.nil.", 5) + msg_delete.use_tsig(keyring, keyname="test-key") + isctest.query.tcp(msg_delete, ns1.ip, attempts=1) + + msg_unsupported = create_tkey_msg("a.example.nil.", 99) + msg_unsupported.use_tsig(keyring, keyname="test-key") + isctest.query.tcp(msg_unsupported, ns1.ip, attempts=1) From 163db61ebdae99894b83dbbb9bcea0485a3bc7ee Mon Sep 17 00:00:00 2001 From: Aram Sargsyan Date: Fri, 20 Feb 2026 11:18:52 +0000 Subject: [PATCH 09/15] Fix a bug in dns_tkey_processquery() The 'keyname' variable could be used in the add_rdata_to_list() call without being initialized. Make sure that 'keyname' is non-NULL for all the cases that do not jump to the 'cleanup:' label. (cherry picked from commit 172f5496ba9f1e890bfda25b85b92f079de68f37) --- lib/dns/tkey.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/dns/tkey.c b/lib/dns/tkey.c index d878189bea..ecaec03d58 100644 --- a/lib/dns/tkey.c +++ b/lib/dns/tkey.c @@ -420,7 +420,8 @@ dns_tkey_processquery(dns_message_t *msg, dns_tkeyctx_t *tctx, /* * A delete operation uses the fully specified qname. */ - CHECK(process_deletetkey(signer, qname, &tkeyin, &tkeyout, + keyname = qname; + CHECK(process_deletetkey(signer, keyname, &tkeyin, &tkeyout, ring)); break; case DNS_TKEYMODE_GSSAPI: @@ -463,6 +464,10 @@ dns_tkey_processquery(dns_message_t *msg, dns_tkeyctx_t *tctx, result = DNS_R_NOTIMP; goto cleanup; default: + /* + * For unrecognized modes also use the fully specified qname. + */ + keyname = qname; tkeyout.error = dns_tsigerror_badmode; } From e85af603df437eddbe472d874742918688ec8f75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Sur=C3=BD?= Date: Tue, 24 Feb 2026 19:20:14 +0100 Subject: [PATCH 10/15] Add system test using SIG(0) and ACL matching This adds a system test to verify that asynchronous SIG(0) validation correctly retains the ACL environment and network addresses of the caller, preventing unauthorized ACL bypass when evaluating match-clients and match-destinations. (cherry picked from commit 613a93478ba7c406a9f111b3dad0fe1b36ee8a8d) --- bin/tests/system/sig0/ns1/named.conf.j2 | 41 ++++++++ bin/tests/system/sig0/setup.sh | 17 ++++ bin/tests/system/sig0/tests_sig0.py | 119 ++++++++++++++++++++++++ 3 files changed, 177 insertions(+) create mode 100644 bin/tests/system/sig0/ns1/named.conf.j2 create mode 100644 bin/tests/system/sig0/setup.sh create mode 100644 bin/tests/system/sig0/tests_sig0.py diff --git a/bin/tests/system/sig0/ns1/named.conf.j2 b/bin/tests/system/sig0/ns1/named.conf.j2 new file mode 100644 index 0000000000..724a40c58c --- /dev/null +++ b/bin/tests/system/sig0/ns1/named.conf.j2 @@ -0,0 +1,41 @@ +/* + * 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; + notify no; +}; + +key rndc_key { + secret "1234abcd8765"; + algorithm @DEFAULT_HMAC@; +}; + +controls { + inet 10.53.0.2 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; +}; + +view "v1" { + match-clients { any; }; + zone "." { + type hint; + file "/dev/null"; + }; +}; diff --git a/bin/tests/system/sig0/setup.sh b/bin/tests/system/sig0/setup.sh new file mode 100644 index 0000000000..64a8c3a481 --- /dev/null +++ b/bin/tests/system/sig0/setup.sh @@ -0,0 +1,17 @@ +#!/bin/sh -e + +# 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. + +# shellcheck source=conf.sh +. ../conf.sh + +key=$($KEYGEN -q -a RSASHA256 -b 2048 sig0.) diff --git a/bin/tests/system/sig0/tests_sig0.py b/bin/tests/system/sig0/tests_sig0.py new file mode 100644 index 0000000000..500d2b5eab --- /dev/null +++ b/bin/tests/system/sig0/tests_sig0.py @@ -0,0 +1,119 @@ +# 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. + +import base64 +import glob +import os +import struct +import time + +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.asymmetric import padding, rsa + +import dns.flags +import dns.message +import dns.name +import dns.rdata +import dns.rdataclass +import dns.rdatatype +import dns.renderer +import dns.rrset + +import isctest + + +def load_bind_private_key(filename): + """Parses a BIND 9 .private key file.""" + with open(filename, "r", encoding="utf-8") as f: + lines = f.readlines() + + data = {} + for line in lines: + if ":" in line: + key, value = line.split(":", 1) + data[key.strip()] = value.strip() + + def b64int(k): + return int.from_bytes(base64.b64decode(data[k]), byteorder="big") + + rsa_key = rsa.RSAPrivateNumbers( + p=b64int("Prime1"), + q=b64int("Prime2"), + d=b64int("PrivateExponent"), + dmp1=b64int("Exponent1"), + dmq1=b64int("Exponent2"), + iqmp=b64int("Coefficient"), + public_numbers=rsa.RSAPublicNumbers( + e=b64int("PublicExponent"), n=b64int("Modulus") + ), + ).private_key(default_backend()) + + return rsa_key + + +def make_sig0_query(key_file, key_name_str): + private_key = load_bind_private_key(key_file) + + qname = dns.name.from_text(".") + query = dns.message.make_query(qname, dns.rdatatype.SOA) + query.flags |= dns.flags.RD + + # Render message to bytes (needed for signing) + renderer = dns.renderer.Renderer() + query.to_wire(renderer) + msg_bytes = renderer.get_wire() + + # SIG(0) Constants + basename = os.path.basename(key_file) + key_tag = int(basename.split("+")[2].split(".")[0]) + + now = int(time.time()) + expiration = now + 3600 + inception = now - 3600 + signer_name = dns.name.from_text(key_name_str) + + # Construct SIG RDATA header (0=SIG(0), 8=RSASHA256, 0=Labels) + sig_rdata_header = struct.pack( + "!HBBIIIH", 0, 8, 0, 0, expiration, inception, key_tag + ) + + sig_rdata_pre_sig = sig_rdata_header + signer_name.to_wire() + + # Sign: ( SIG RDATA sans signature ) + ( Message ) + signature = private_key.sign( + sig_rdata_pre_sig + msg_bytes, padding.PKCS1v15(), hashes.SHA256() + ) + + # Create the SIG RR + full_sig_rdata = sig_rdata_pre_sig + signature + sig_rr = dns.rdata.from_wire( + dns.rdataclass.ANY, + dns.rdatatype.SIG, + full_sig_rdata, + 0, + len(full_sig_rdata), + ) + sig_rrset = dns.rrset.from_rdata(qname, 0, sig_rr) + query.additional.append(sig_rrset) + + return query + + +def test_sig0_acl_bypass(): + key_files = glob.glob("Ksig0.+*.private") + assert len(key_files) == 1 + + query = make_sig0_query(key_files[0], "sig0.") + + # Send the query + res = isctest.query.tcp(query, "10.53.0.1") + isctest.check.servfail(res) From 4a2048ea7f98b7ad9528463a045abc9d224a0f43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Sur=C3=BD?= Date: Fri, 20 Feb 2026 09:46:32 +0100 Subject: [PATCH 11/15] Fix stack Use-After-Return in SIG(0) handling The asynchronous SIG(0) handling improperly used srcaddr, and dstaddr from the caller's stack and didn't attach to aclenv. This could possibly lead to ACL bypass as an invalid srcaddr could be matched or possible assertion failure if the ACL environment would change between the initial call and the SIG(0) processing due to the server reconfiguration. This has been fixed. (cherry picked from commit b4b81deed9930fdb59cfbf8179218621c812497b) --- bin/named/server.c | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/bin/named/server.c b/bin/named/server.c index 796b4cc97e..a59ec7bb7c 100644 --- a/bin/named/server.c +++ b/bin/named/server.c @@ -24,6 +24,8 @@ #include #include +#include + #ifdef HAVE_DNSTAP #include #endif @@ -280,10 +282,10 @@ struct zonelistentry { * asynchronously. */ typedef struct matching_view_ctx { - isc_netaddr_t *srcaddr; - isc_netaddr_t *destaddr; + isc_netaddr_t srcaddr; + isc_netaddr_t destaddr; dns_message_t *message; - dns_aclenv_t *env; + dns_aclenv_t *aclenv; ns_server_t *sctx; isc_loop_t *loop; isc_job_cb cb; @@ -10356,6 +10358,8 @@ get_matching_view_done(void *cbarg) { mvctx->cb(mvctx->cbarg); + dns_aclenv_detach(&mvctx->aclenv); + if (mvctx->quota_result == ISC_R_SUCCESS) { isc_quota_release(&mvctx->sctx->sig0checksquota); } @@ -10397,10 +10401,10 @@ get_matching_view_continue(void *cbarg, isc_result_t result) { tsig = dns_tsigkey_identity(mvctx->message->tsigkey); } - if (dns_acl_allowed(mvctx->srcaddr, tsig, mvctx->view->matchclients, - mvctx->env) && - dns_acl_allowed(mvctx->destaddr, tsig, - mvctx->view->matchdestinations, mvctx->env) && + if (dns_acl_allowed(&mvctx->srcaddr, tsig, mvctx->view->matchclients, + mvctx->aclenv) && + dns_acl_allowed(&mvctx->destaddr, tsig, + mvctx->view->matchdestinations, mvctx->aclenv) && !(mvctx->view->matchrecursiveonly && (mvctx->message->flags & DNS_MESSAGEFLAG_RD) == 0)) { @@ -10472,9 +10476,9 @@ get_matching_view(isc_netaddr_t *srcaddr, isc_netaddr_t *destaddr, matching_view_ctx_t *mvctx = isc_mem_get(message->mctx, sizeof(*mvctx)); *mvctx = (matching_view_ctx_t){ - .srcaddr = srcaddr, - .destaddr = destaddr, - .env = env, + .srcaddr = *srcaddr, + .destaddr = *destaddr, + .aclenv = dns_aclenv_ref(env), .cb = cb, .cbarg = cbarg, .sigresult = sigresult, From 826a2240123bce5104f8e6087029422ead7756a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20K=C4=99pie=C5=84?= Date: Fri, 13 Mar 2026 21:33:25 +0100 Subject: [PATCH 12/15] Generate changelog for BIND 9.20.21 --- doc/arm/changelog.rst | 1 + doc/changelog/changelog-9.20.21.rst | 77 +++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 doc/changelog/changelog-9.20.21.rst diff --git a/doc/arm/changelog.rst b/doc/arm/changelog.rst index d23a87e873..6fbcff98ea 100644 --- a/doc/arm/changelog.rst +++ b/doc/arm/changelog.rst @@ -18,6 +18,7 @@ Changelog development. Regular users should refer to :ref:`Release Notes ` for changes relevant to them. +.. include:: ../changelog/changelog-9.20.21.rst .. include:: ../changelog/changelog-9.20.20.rst .. include:: ../changelog/changelog-9.20.19.rst .. include:: ../changelog/changelog-9.20.18.rst diff --git a/doc/changelog/changelog-9.20.21.rst b/doc/changelog/changelog-9.20.21.rst new file mode 100644 index 0000000000..8df58a103e --- /dev/null +++ b/doc/changelog/changelog-9.20.21.rst @@ -0,0 +1,77 @@ +.. 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. + +BIND 9.20.21 +------------ + +Security Fixes +~~~~~~~~~~~~~~ + +- [CVE-2026-1519] Fix unbounded NSEC3 iterations when validating + referrals to unsigned delegations. ``5af03a06066`` + + DNSSEC-signed zones may contain high iteration-count NSEC3 records, + which prove that certain delegations are insecure. Previously, a + validating resolver encountering such a delegation processed these + iterations up to the number given, which could be a maximum of 65,535. + This has been addressed by introducing a processing limit, set at 50. + Now, if such an NSEC3 record is encountered, the delegation will be + treated as insecure. + + ISC would like to thank Samy Medjahed/Ap4sh for bringing this + vulnerability to our attention. :gl:`#5708` + +- [CVE-2026-3104] Fix memory leaks in code preparing DNSSEC proofs of + non-existence. ``13215b9cbbf`` + + An attacker controlling a DNSSEC-signed zone could trigger a memory + leak in the logic preparing DNSSEC proofs of non-existence, by + creating more than :any:`max-records-per-type` RRSIGs for NSEC + records. These memory leaks have been fixed. + + ISC would like to thank Vitaly Simonovich for bringing this + vulnerability to our attention. :gl:`#5742` + +- [CVE-2026-3119] Prevent a crash in code processing queries containing + a TKEY record. ``308baa89105`` + + The :iscman:`named` process could terminate unexpectedly when + processing a correctly signed query containing a TKEY record. This has + been fixed. + + ISC would like to thank Vitaly Simonovich for bringing this + vulnerability to our attention. :gl:`#5748` + +- [CVE-2026-3591] Fix a stack use-after-return flaw in SIG(0) handling + code. ``aaaae0fd97e`` + + A stack use-after-return flaw in SIG(0) handling code could enable ACL + bypass and/or assertion failures in certain circumstances. This flaw + has been fixed. + + ISC would like to thank Mcsky23 for bringing this vulnerability to our + attention. :gl:`#5754` + +Bug Fixes +~~~~~~~~~ + +- Resolve "key defined in view is not found" ``819fe452745`` + + Commit `2956e4fc` hardened the `key` name check when used in + `primaries` to reject the configuration if the key was not defined, + rather than simply checking whether the key name was correctly formed. + + However, the key name check didn't include the view configuration, + causing keys not to be recognized if they were defined inside the view + and not at the global level. This regression is now fixed. + :gl:`#5761` :gl:`!11613` + + From a6aae97118080bd9f899d629f5eee071bea3c749 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20K=C4=99pie=C5=84?= Date: Fri, 13 Mar 2026 21:33:25 +0100 Subject: [PATCH 13/15] Prepare release notes for BIND 9.20.21 --- doc/arm/notes.rst | 1 + doc/notes/notes-9.20.21.rst | 77 +++++++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 doc/notes/notes-9.20.21.rst diff --git a/doc/arm/notes.rst b/doc/arm/notes.rst index ba33babf79..333624d83e 100644 --- a/doc/arm/notes.rst +++ b/doc/arm/notes.rst @@ -45,6 +45,7 @@ The list of known issues affecting the latest version in the 9.20 branch can be found at https://gitlab.isc.org/isc-projects/bind9/-/wikis/Known-Issues-in-BIND-9.20 +.. include:: ../notes/notes-9.20.21.rst .. include:: ../notes/notes-9.20.20.rst .. include:: ../notes/notes-9.20.19.rst .. include:: ../notes/notes-9.20.18.rst diff --git a/doc/notes/notes-9.20.21.rst b/doc/notes/notes-9.20.21.rst new file mode 100644 index 0000000000..1ba9a82197 --- /dev/null +++ b/doc/notes/notes-9.20.21.rst @@ -0,0 +1,77 @@ +.. 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. + +Notes for BIND 9.20.21 +---------------------- + +Security Fixes +~~~~~~~~~~~~~~ + +- [CVE-2026-1519] Fix unbounded NSEC3 iterations when validating + referrals to unsigned delegations. + + DNSSEC-signed zones may contain high iteration-count NSEC3 records, + which prove that certain delegations are insecure. Previously, a + validating resolver encountering such a delegation processed these + iterations up to the number given, which could be a maximum of 65,535. + This has been addressed by introducing a processing limit, set at 50. + Now, if such an NSEC3 record is encountered, the delegation will be + treated as insecure. + + ISC would like to thank Samy Medjahed/Ap4sh for bringing this + vulnerability to our attention. :gl:`#5708` + +- [CVE-2026-3104] Fix memory leaks in code preparing DNSSEC proofs of + non-existence. + + An attacker controlling a DNSSEC-signed zone could trigger a memory + leak in the logic preparing DNSSEC proofs of non-existence, by + creating more than :any:`max-records-per-type` RRSIGs for NSEC + records. These memory leaks have been fixed. + + ISC would like to thank Vitaly Simonovich for bringing this + vulnerability to our attention. :gl:`#5742` + +- [CVE-2026-3119] Prevent a crash in code processing queries containing + a TKEY record. + + The :iscman:`named` process could terminate unexpectedly when + processing a correctly signed query containing a TKEY record. This has + been fixed. + + ISC would like to thank Vitaly Simonovich for bringing this + vulnerability to our attention. :gl:`#5748` + +- [CVE-2026-3591] Fix a stack use-after-return flaw in SIG(0) handling + code. + + A stack use-after-return flaw in SIG(0) handling code could enable ACL + bypass and/or assertion failures in certain circumstances. This flaw + has been fixed. + + ISC would like to thank Mcsky23 for bringing this vulnerability to our + attention. :gl:`#5754` + +Bug Fixes +~~~~~~~~~ + +- Resolve "key defined in view is not found" + + Commit `2956e4fc` hardened the `key` name check when used in + `primaries` to reject the configuration if the key was not defined, + rather than simply checking whether the key name was correctly formed. + + However, the key name check didn't include the view configuration, + causing keys not to be recognized if they were defined inside the view + and not at the global level. This regression is now fixed. + :gl:`#5761` + + From 1a008b282ab1d4265609fdce24cbcd992ce43f66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20K=C4=99pie=C5=84?= Date: Fri, 13 Mar 2026 21:33:25 +0100 Subject: [PATCH 14/15] Tweak and reword release notes --- doc/notes/notes-9.20.21.rst | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/doc/notes/notes-9.20.21.rst b/doc/notes/notes-9.20.21.rst index 1ba9a82197..fe8dcb8fe4 100644 --- a/doc/notes/notes-9.20.21.rst +++ b/doc/notes/notes-9.20.21.rst @@ -15,8 +15,8 @@ Notes for BIND 9.20.21 Security Fixes ~~~~~~~~~~~~~~ -- [CVE-2026-1519] Fix unbounded NSEC3 iterations when validating - referrals to unsigned delegations. +- Fix unbounded NSEC3 iterations when validating referrals to unsigned + delegations. :cve:`2026-1519` DNSSEC-signed zones may contain high iteration-count NSEC3 records, which prove that certain delegations are insecure. Previously, a @@ -29,8 +29,8 @@ Security Fixes ISC would like to thank Samy Medjahed/Ap4sh for bringing this vulnerability to our attention. :gl:`#5708` -- [CVE-2026-3104] Fix memory leaks in code preparing DNSSEC proofs of - non-existence. +- Fix memory leaks in code preparing DNSSEC proofs of non-existence. + :cve:`2026-3104` An attacker controlling a DNSSEC-signed zone could trigger a memory leak in the logic preparing DNSSEC proofs of non-existence, by @@ -40,8 +40,8 @@ Security Fixes ISC would like to thank Vitaly Simonovich for bringing this vulnerability to our attention. :gl:`#5742` -- [CVE-2026-3119] Prevent a crash in code processing queries containing - a TKEY record. +- Prevent a crash in code processing queries containing a TKEY record. + :cve:`2026-3119` The :iscman:`named` process could terminate unexpectedly when processing a correctly signed query containing a TKEY record. This has @@ -50,8 +50,8 @@ Security Fixes ISC would like to thank Vitaly Simonovich for bringing this vulnerability to our attention. :gl:`#5748` -- [CVE-2026-3591] Fix a stack use-after-return flaw in SIG(0) handling - code. +- Fix a stack use-after-return flaw in SIG(0) handling code. + :cve:`2026-3591` A stack use-after-return flaw in SIG(0) handling code could enable ACL bypass and/or assertion failures in certain circumstances. This flaw @@ -63,15 +63,13 @@ Security Fixes Bug Fixes ~~~~~~~~~ -- Resolve "key defined in view is not found" +- Fix the handling of :namedconf:ref:`key` statements defined inside + views. - Commit `2956e4fc` hardened the `key` name check when used in - `primaries` to reject the configuration if the key was not defined, - rather than simply checking whether the key name was correctly formed. - - However, the key name check didn't include the view configuration, - causing keys not to be recognized if they were defined inside the view - and not at the global level. This regression is now fixed. + A recent change introduced in BIND 9.20.17 hardened the + :namedconf:ref:`key` name check when used in :any:`primaries`, to + immediately reject the configuration if the key was not defined + (rather than only checking whether the key name was correctly formed). + However, that change introduced a regression that prevented the use of + a :namedconf:ref:`key` defined in a view. This has now been fixed. :gl:`#5761` - - From 12f97d4162d435940598de4870d0088b8e6d266c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20K=C4=99pie=C5=84?= Date: Fri, 13 Mar 2026 22:37:53 +0100 Subject: [PATCH 15/15] Update BIND version for release --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 7ea00bc756..c09513f664 100644 --- a/configure.ac +++ b/configure.ac @@ -17,7 +17,7 @@ m4_define([bind_VERSION_MAJOR], 9)dnl m4_define([bind_VERSION_MINOR], 20)dnl m4_define([bind_VERSION_PATCH], 21)dnl -m4_define([bind_VERSION_EXTRA], -dev)dnl +m4_define([bind_VERSION_EXTRA], )dnl m4_define([bind_DESCRIPTION], [(Stable Release)])dnl m4_define([bind_SRCID], [m4_esyscmd_s([git rev-parse --short HEAD | cut -b1-7])])dnl m4_define([bind_PKG_VERSION], [[bind_VERSION_MAJOR.bind_VERSION_MINOR.bind_VERSION_PATCH]bind_VERSION_EXTRA])dnl