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" + ) diff --git a/doc/arm/changelog.rst b/doc/arm/changelog.rst index 16e059b278..d23f225532 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.18.47.rst .. include:: ../changelog/changelog-9.18.46.rst .. include:: ../changelog/changelog-9.18.45.rst .. include:: ../changelog/changelog-9.18.44.rst diff --git a/doc/arm/notes.rst b/doc/arm/notes.rst index 99f26debbd..fe5d4d28ef 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.18 branch can be found at https://gitlab.isc.org/isc-projects/bind9/-/wikis/Known-Issues-in-BIND-9.18 +.. include:: ../notes/notes-9.18.47.rst .. include:: ../notes/notes-9.18.46.rst .. include:: ../notes/notes-9.18.45.rst .. include:: ../notes/notes-9.18.44.rst diff --git a/doc/changelog/changelog-9.18.47.rst b/doc/changelog/changelog-9.18.47.rst new file mode 100644 index 0000000000..402b03d097 --- /dev/null +++ b/doc/changelog/changelog-9.18.47.rst @@ -0,0 +1,32 @@ +.. 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.18.47 +------------ + +Security Fixes +~~~~~~~~~~~~~~ + +- [CVE-2026-1519] Fix unbounded NSEC3 iterations when validating + referrals to unsigned delegations. ``5ef459eeaa9`` + + 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 150. + 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` + + diff --git a/doc/notes/notes-9.18.47.rst b/doc/notes/notes-9.18.47.rst new file mode 100644 index 0000000000..93b794e8d6 --- /dev/null +++ b/doc/notes/notes-9.18.47.rst @@ -0,0 +1,30 @@ +.. 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.18.47 +---------------------- + +Security Fixes +~~~~~~~~~~~~~~ + +- 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 + 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 150. + 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` diff --git a/lib/dns/include/dns/types.h b/lib/dns/include/dns/types.h index 8ddcbeb4e2..bd9623058a 100644 --- a/lib/dns/include/dns/types.h +++ b/lib/dns/include/dns/types.h @@ -352,6 +352,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 9ec13581ab..7db102062b 100644 --- a/lib/dns/validator.c +++ b/lib/dns/validator.c @@ -256,12 +256,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; @@ -289,7 +302,7 @@ isdelegation(dns_name_t *name, dns_rdataset_t *rdataset, goto trynsec3; } if (result != ISC_R_SUCCESS) { - return false; + return ISC_R_NOTFOUND; } } @@ -303,7 +316,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: /* @@ -342,6 +355,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); @@ -353,7 +378,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; @@ -369,12 +394,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; } /*% @@ -590,8 +615,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 @@ -755,10 +781,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"); @@ -1494,6 +1523,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: @@ -2427,6 +2463,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, @@ -2537,11 +2584,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; @@ -2590,7 +2635,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; } @@ -2626,7 +2672,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; } @@ -2833,7 +2879,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;