* [OE-core] [scarthgap] [PATCH 2/5] bind: Fix CVE-2026-5950
2026-06-10 10:04 [OE-core] [scarthgap] [PATCH 1/5] bind: Fix CVE-2026-1519 Ashishkumar Parmar X (asparmar - E INFOCHIPS PRIVATE LIMITED at Cisco)
@ 2026-06-10 10:04 ` Ashishkumar Parmar X (asparmar - E INFOCHIPS PRIVATE LIMITED at Cisco)
2026-06-10 10:04 ` [OE-core] [scarthgap] [PATCH 3/5] bind: Fix CVE-2026-3592 Ashishkumar Parmar X (asparmar - E INFOCHIPS PRIVATE LIMITED at Cisco)
` (3 subsequent siblings)
4 siblings, 0 replies; 7+ messages in thread
From: Ashishkumar Parmar X (asparmar - E INFOCHIPS PRIVATE LIMITED at Cisco) @ 2026-06-10 10:04 UTC (permalink / raw)
To: openembedded-core; +Cc: xe-linux-external, to, Ashishkumar Parmar
From: Ashishkumar Parmar <asparmar@cisco.com>
Pick the upstream 9.18 backport [1] for CVE-2026-5950. The public ISC
advisory [2] describes the vulnerability and identifies the fixed BIND
release.
The upstream fix is split across a reproducer, a counter refactor, and
the functional resend-loop fix. Apply them in the same order so the
final fix lands on top of the counter handling it depends on:
- CVE-2026-5950_p1.patch [3] adds the BADCOOKIE resend-loop system-test
reproducer.
- CVE-2026-5950_p2.patch [4] refactors query-counter increment handling
so resend paths can update counters consistently.
- CVE-2026-5950_p3.patch [5] updates rctx_resend() to increment query
counters, preventing the unbounded resend loop from bypassing the
quota accounting.
Keep the patches split to preserve the upstream commit structure and to
make the SRC_URI ordering explicit.
[1] https://gitlab.com/isc-projects/bind9/-/commit/43d173797e3c781c7e38d879c3c765f01cc09c59
[2] https://kb.isc.org/docs/cve-2026-5950
[3] https://gitlab.com/isc-projects/bind9/-/commit/8344a38d6bc72d3872b552b4cae0e0d8be4c3d4a
[4] https://gitlab.com/isc-projects/bind9/-/commit/9ebfca2af824a60ea072292ffb1ab01ff87c7fa7
[5] https://gitlab.com/isc-projects/bind9/-/commit/d3ba533080e31de98986f3beff70d7c330ba9f89
Signed-off-by: Ashishkumar Parmar <asparmar@cisco.com>
---
.../bind/bind/CVE-2026-5950_p1.patch | 245 ++++++++++++++++++
.../bind/bind/CVE-2026-5950_p2.patch | 100 +++++++
.../bind/bind/CVE-2026-5950_p3.patch | 67 +++++
.../recipes-connectivity/bind/bind_9.18.44.bb | 3 +
4 files changed, 415 insertions(+)
create mode 100644 meta/recipes-connectivity/bind/bind/CVE-2026-5950_p1.patch
create mode 100644 meta/recipes-connectivity/bind/bind/CVE-2026-5950_p2.patch
create mode 100644 meta/recipes-connectivity/bind/bind/CVE-2026-5950_p3.patch
diff --git a/meta/recipes-connectivity/bind/bind/CVE-2026-5950_p1.patch b/meta/recipes-connectivity/bind/bind/CVE-2026-5950_p1.patch
new file mode 100644
index 0000000000..3b603e41fd
--- /dev/null
+++ b/meta/recipes-connectivity/bind/bind/CVE-2026-5950_p1.patch
@@ -0,0 +1,245 @@
+From 59bf865e9691f3a047e24bda071498a48e2a5220 Mon Sep 17 00:00:00 2001
+From: Matthijs Mekking <matthijs@isc.org>
+Date: Thu, 9 Apr 2026 11:32:07 +0200
+Subject: [PATCH] Add reproducer for BADCOOKIE resend loop
+
+Run malicious server: resend_loop/ans3/ans.py
+
+Start BIND: ns4
+
+Send single query to test.example
+
+The resolver will repeatedly resend queries until the fetch timeout
+expires, resulting in resulting in thousands of qrysent while the quota
+counter remains 0.
+
+CVE: CVE-2026-5950
+Upstream-Status: Backport [https://gitlab.com/isc-projects/bind9/-/commit/8344a38d6bc72d3872b552b4cae0e0d8be4c3d4a]
+
+(cherry picked from commit 7eeb463bc58cbd71419aaf189d7829f2dfd8d055)
+(cherry picked from commit 8344a38d6bc72d3872b552b4cae0e0d8be4c3d4a)
+Signed-off-by: Ashishkumar Parmar <asparmar@cisco.com>
+---
+ bin/tests/system/resend_loop/ans3/ans.py | 128 ++++++++++++++++++
+ .../system/resend_loop/ns4/named.conf.j2 | 16 +++
+ bin/tests/system/resend_loop/ns4/root.hint | 14 ++
+ .../system/resend_loop/tests_resend_loop.py | 28 ++++
+ 4 files changed, 186 insertions(+)
+ create mode 100644 bin/tests/system/resend_loop/ans3/ans.py
+ create mode 100644 bin/tests/system/resend_loop/ns4/named.conf.j2
+ create mode 100644 bin/tests/system/resend_loop/ns4/root.hint
+ create mode 100644 bin/tests/system/resend_loop/tests_resend_loop.py
+
+diff --git a/bin/tests/system/resend_loop/ans3/ans.py b/bin/tests/system/resend_loop/ans3/ans.py
+new file mode 100644
+index 0000000000..90a3f2f9cc
+--- /dev/null
++++ b/bin/tests/system/resend_loop/ans3/ans.py
+@@ -0,0 +1,128 @@
++# 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 collections.abc import AsyncGenerator
++
++import dns.edns
++import dns.name
++import dns.rcode
++import dns.rdatatype
++import dns.rrset
++
++from isctest.asyncserver import (
++ AsyncDnsServer,
++ DnsResponseSend,
++ QueryContext,
++ ResponseHandler,
++)
++
++
++def _get_cookie(qctx: QueryContext):
++ for o in qctx.query.options:
++ if o.otype == dns.edns.OptionType.COOKIE:
++ cookie = o
++ try:
++ if len(cookie.server) == 0:
++ cookie.server = b"\x11\x22\x33\x44\x55\x66\x77\x88"
++ except AttributeError: # dnspython<2.7.0 compat
++ if len(o.data) == 8:
++ cookie.data *= 2
++
++ return cookie
++
++ return None
++
++
++class PrimeHandler(ResponseHandler):
++ """
++ Specifically handle priming query for "." NS (type 2)
++ """
++
++ def match(self, qctx: QueryContext) -> bool:
++ return len(qctx.qname.labels) == 0 and qctx.qtype == dns.rdatatype.NS
++
++ async def get_responses(
++ self, qctx: QueryContext
++ ) -> AsyncGenerator[DnsResponseSend, None]:
++
++ ns_rrset = dns.rrset.from_text(
++ ".", dns.rdatatype.NS, qctx.qclass, "a.root-servers.nil."
++ )
++ a_rrset = dns.rrset.from_text(
++ "a.root-servers.nil.", dns.rdatatype.A, qctx.qclass, "10.53.0.3"
++ )
++
++ response = qctx.prepare_new_response(with_zone_data=False)
++ response.set_rcode(dns.rcode.NOERROR)
++ response.answer.append(ns_rrset)
++ response.additional.append(a_rrset)
++
++ yield DnsResponseSend(response, authoritative=True)
++
++
++class CookieHandler(ResponseHandler):
++ def match(self, qctx: QueryContext) -> bool:
++ example = dns.name.from_text("example")
++ return qctx.qname.is_subdomain(example)
++
++ async def get_responses(
++ self, qctx: QueryContext
++ ) -> AsyncGenerator[DnsResponseSend, None]:
++
++ qctx.prepare_new_response()
++
++ # Check for client cookie
++ cookie = _get_cookie(qctx)
++
++ # If missing cookie entirely, just return SERVFAIL
++ if cookie is None:
++ qctx.response.set_rcode(dns.rcode.SERVFAIL)
++ yield DnsResponseSend(qctx.response, authoritative=True)
++
++ # If there is a client cookie, mock BADCOOKIE to trigger
++ # the resend loop logic.
++ qctx.response.use_edns(options=[cookie])
++ qctx.response.set_rcode(dns.rcode.BADCOOKIE)
++ yield DnsResponseSend(qctx.response, authoritative=True)
++
++
++class NoErrorHandler(ResponseHandler):
++ """
++ If the query is NOT a subdomain of example, respond with standard NOERROR empty answer
++ """
++
++ async def get_responses(
++ self, qctx: QueryContext
++ ) -> AsyncGenerator[DnsResponseSend, None]:
++
++ qctx.prepare_new_response()
++ qctx.response.set_rcode(dns.rcode.NOERROR)
++ yield DnsResponseSend(qctx.response, authoritative=True)
++
++
++def resend_server() -> AsyncDnsServer:
++ server = AsyncDnsServer(default_aa=True, default_rcode=dns.rcode.NOERROR)
++ server.install_response_handlers(
++ [
++ PrimeHandler(),
++ CookieHandler(),
++ NoErrorHandler(),
++ ]
++ )
++ return server
++
++
++def main() -> None:
++ resend_server().run()
++
++
++if __name__ == "__main__":
++ main()
+diff --git a/bin/tests/system/resend_loop/ns4/named.conf.j2 b/bin/tests/system/resend_loop/ns4/named.conf.j2
+new file mode 100644
+index 0000000000..360bc12e17
+--- /dev/null
++++ b/bin/tests/system/resend_loop/ns4/named.conf.j2
+@@ -0,0 +1,16 @@
++options {
++ query-source address 10.53.0.4;
++ notify-source 10.53.0.4;
++ transfer-source 10.53.0.4;
++ port @PORT@;
++ pid-file "named.pid";
++ listen-on { 10.53.0.4; };
++ listen-on-v6 { none; };
++ recursion yes;
++ dnssec-validation no;
++};
++
++zone "." IN {
++ type hint;
++ file "root.hint";
++};
+diff --git a/bin/tests/system/resend_loop/ns4/root.hint b/bin/tests/system/resend_loop/ns4/root.hint
+new file mode 100644
+index 0000000000..3889a8b353
+--- /dev/null
++++ b/bin/tests/system/resend_loop/ns4/root.hint
+@@ -0,0 +1,14 @@
++; 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 999999
++. IN NS a.root-servers.nil.
++a.root-servers.nil. IN A 10.53.0.3
+diff --git a/bin/tests/system/resend_loop/tests_resend_loop.py b/bin/tests/system/resend_loop/tests_resend_loop.py
+new file mode 100644
+index 0000000000..f7ed4d3da6
+--- /dev/null
++++ b/bin/tests/system/resend_loop/tests_resend_loop.py
+@@ -0,0 +1,28 @@
++# 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.message
++
++import isctest
++
++
++def test_resend_loop_badcookie(ns4):
++ expected_log = "exceeded max queries resolving 'test.example/A'"
++
++ msg = dns.message.make_query("test.example", "A")
++ with ns4.watch_log_from_here() as watcher:
++ res = isctest.query.udp(msg, ns4.ip)
++ watcher.wait_for_line(expected_log)
++
++ isctest.check.servfail(res)
++
++ prohibited_log = "query failed (timed out) for test.example/IN/A"
++ assert prohibited_log not in ns4.log
+--
+2.35.6
+
diff --git a/meta/recipes-connectivity/bind/bind/CVE-2026-5950_p2.patch b/meta/recipes-connectivity/bind/bind/CVE-2026-5950_p2.patch
new file mode 100644
index 0000000000..3921c363f6
--- /dev/null
+++ b/meta/recipes-connectivity/bind/bind/CVE-2026-5950_p2.patch
@@ -0,0 +1,100 @@
+From f090a1d0cf3599380c750557caff25e0379fb4d6 Mon Sep 17 00:00:00 2001
+From: Colin Vidal <colin@isc.org>
+Date: Tue, 7 Apr 2026 22:18:10 +0200
+Subject: [PATCH] Refactor incrementing query counters
+
+Move the logic incrementing the query counter and the global query
+counter into a dedicated helper function.
+
+CVE: CVE-2026-5950
+Upstream-Status: Backport [https://gitlab.com/isc-projects/bind9/-/commit/9ebfca2af824a60ea072292ffb1ab01ff87c7fa7]
+
+(cherry picked from commit 05d6da2de54c093689e675e81ae898ee41220666)
+(cherry picked from commit 9ebfca2af824a60ea072292ffb1ab01ff87c7fa7)
+Signed-off-by: Ashishkumar Parmar <asparmar@cisco.com>
+---
+ lib/dns/resolver.c | 59 ++++++++++++++++++++++++++++------------------
+ 1 file changed, 36 insertions(+), 23 deletions(-)
+
+diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c
+index 1bacec6470..079aa79ea0 100644
+--- a/lib/dns/resolver.c
++++ b/lib/dns/resolver.c
+@@ -4203,6 +4203,39 @@ fctx_nextaddress(fetchctx_t *fctx) {
+ return addrinfo;
+ }
+
++static isc_result_t
++incr_query_counters(fetchctx_t *fctx) {
++ isc_result_t result;
++
++ result = isc_counter_increment(fctx->qc);
++#if WANT_QUERYTRACE
++ FCTXTRACE5("query", "max-recursion-queries, querycount=",
++ isc_counter_used(fctx->qc));
++#endif
++ if (result != ISC_R_SUCCESS) {
++ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER,
++ DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3),
++ "exceeded max queries resolving '%s' "
++ "(max-recursion-queries, querycount=%u)",
++ fctx->info, isc_counter_used(fctx->qc));
++ } else if (fctx->gqc != NULL) {
++ result = isc_counter_increment(fctx->gqc);
++#if WANT_QUERYTRACE
++ FCTXTRACE5("query", "max-query-count, querycount=",
++ isc_counter_used(fctx->gqc));
++#endif
++ if (result != ISC_R_SUCCESS) {
++ isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER,
++ DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3),
++ "exceeded global max queries resolving "
++ "'%s' (max-query-count, querycount=%u)",
++ fctx->info, isc_counter_used(fctx->gqc));
++ }
++ }
++
++ return result;
++}
++
+ static void
+ fctx_try(fetchctx_t *fctx, bool retrying, bool badcache) {
+ isc_result_t result;
+@@ -4357,31 +4390,11 @@ fctx_try(fetchctx_t *fctx, bool retrying, bool badcache) {
+ return;
+ }
+
+- result = isc_counter_increment(fctx->qc);
+- if (result != ISC_R_SUCCESS) {
+- isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER,
+- DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3),
+- "exceeded max queries resolving '%s' "
+- "(max-recursion-queries, querycount=%u)",
+- fctx->info, isc_counter_used(fctx->qc));
+- fctx_done_detach(&fctx, DNS_R_SERVFAIL);
+- return;
+- }
+-
+- if (fctx->gqc != NULL) {
+- result = isc_counter_increment(fctx->gqc);
+- if (result != ISC_R_SUCCESS) {
+- isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER,
+- DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3),
+- "exceeded global max queries resolving "
+- "'%s' (max-query-count, querycount=%u)",
+- fctx->info, isc_counter_used(fctx->gqc));
+- fctx_done_detach(&fctx, DNS_R_SERVFAIL);
+- return;
+- }
+- }
++ CHECK(incr_query_counters(fctx));
+
+ result = fctx_query(fctx, addrinfo, fctx->options);
++
++cleanup:
+ if (result != ISC_R_SUCCESS) {
+ fctx_done_detach(&fctx, result);
+ } else if (retrying) {
+--
+2.35.6
+
diff --git a/meta/recipes-connectivity/bind/bind/CVE-2026-5950_p3.patch b/meta/recipes-connectivity/bind/bind/CVE-2026-5950_p3.patch
new file mode 100644
index 0000000000..9163c956ea
--- /dev/null
+++ b/meta/recipes-connectivity/bind/bind/CVE-2026-5950_p3.patch
@@ -0,0 +1,67 @@
+From 3346293873d9ae59b5e57abc41142485f8dcdc9a Mon Sep 17 00:00:00 2001
+From: Colin Vidal <colin@isc.org>
+Date: Tue, 7 Apr 2026 22:18:58 +0200
+Subject: [PATCH] rctx_resend() increment query counters
+
+Calls to `rctx_resend()` are done internally within the resolver, in
+flow which are not supposed to happens more than once. For instance,
+if some query fails, and a specific flag "F" wasn't set, then set the
+flag and try again. This wouldn't occur more than once because if the
+query fails the next attempt, the flag "F" would be set already, so the
+resolver would move to the next server (or give up).
+
+However, a subtle bug missing checking a flag, for instance, could lead
+to an unbounded loop re-trying to query the same server. This is now
+impossible as `rctx_resend()` also increment the query counters (so if
+such case occurs, it would stop once the maximum limit is reached).
+
+The dns_resstatscounter_retry are also only incremented if the
+`fctx_query()` succeeds, similar to as is done in `fctx_try()`.
+
+CVE: CVE-2026-5950
+Upstream-Status: Backport [https://gitlab.com/isc-projects/bind9/-/commit/d3ba533080e31de98986f3beff70d7c330ba9f89]
+
+(cherry picked from commit f3e74304889a2e8b69c8e88fc9a383589decda32)
+(cherry picked from commit d3ba533080e31de98986f3beff70d7c330ba9f89)
+Signed-off-by: Ashishkumar Parmar <asparmar@cisco.com>
+---
+ lib/dns/resolver.c | 15 +++++++++++----
+ 1 file changed, 11 insertions(+), 4 deletions(-)
+
+diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c
+index 079aa79ea0..cc40f862d1 100644
+--- a/lib/dns/resolver.c
++++ b/lib/dns/resolver.c
+@@ -10089,9 +10089,9 @@ rctx_nextserver(respctx_t *rctx, dns_message_t *message,
+ * rctx_resend():
+ *
+ * Resend the query, probably with the options changed. Calls
+- * fctx_query(), passing rctx->retryopts (which is based on
+- * query->options, but may have been updated since the last time
+- * fctx_query() was called).
++ * fctx_query(), unless query counter limits are hit, passing
++ * rctx->retryopts (which is based on query->options, but may have
++ * been updated since the last time fctx_query() was called).
+ */
+ static void
+ rctx_resend(respctx_t *rctx, dns_adbaddrinfo_t *addrinfo) {
+@@ -10099,8 +10099,15 @@ rctx_resend(respctx_t *rctx, dns_adbaddrinfo_t *addrinfo) {
+ isc_result_t result;
+
+ FCTXTRACE("resend");
+- inc_stats(fctx->res, dns_resstatscounter_retry);
++
++ CHECK(incr_query_counters(fctx));
++
+ result = fctx_query(fctx, addrinfo, rctx->retryopts);
++ if (result == ISC_R_SUCCESS) {
++ inc_stats(fctx->res, dns_resstatscounter_retry);
++ }
++
++cleanup:
+ if (result != ISC_R_SUCCESS) {
+ fctx_done_detach(&rctx->fctx, result);
+ }
+--
+2.35.6
+
diff --git a/meta/recipes-connectivity/bind/bind_9.18.44.bb b/meta/recipes-connectivity/bind/bind_9.18.44.bb
index 9c8b73dccc..d55e0e0c4d 100644
--- a/meta/recipes-connectivity/bind/bind_9.18.44.bb
+++ b/meta/recipes-connectivity/bind/bind_9.18.44.bb
@@ -23,6 +23,9 @@ SRC_URI = "https://ftp.isc.org/isc/bind9/${PV}/${BPN}-${PV}.tar.xz \
file://CVE-2026-1519_p2.patch \
file://CVE-2026-1519_p3.patch \
file://CVE-2026-1519_p4.patch \
+ file://CVE-2026-5950_p1.patch \
+ file://CVE-2026-5950_p2.patch \
+ file://CVE-2026-5950_p3.patch \
"
SRC_URI[sha256sum] = "81f5035a25c576af1a93f0061cf70bde6d00a0c7bd1274abf73f5b5389a6f82d"
--
2.35.6
^ permalink raw reply related [flat|nested] 7+ messages in thread* [OE-core] [scarthgap] [PATCH 3/5] bind: Fix CVE-2026-3592
2026-06-10 10:04 [OE-core] [scarthgap] [PATCH 1/5] bind: Fix CVE-2026-1519 Ashishkumar Parmar X (asparmar - E INFOCHIPS PRIVATE LIMITED at Cisco)
2026-06-10 10:04 ` [OE-core] [scarthgap] [PATCH 2/5] bind: Fix CVE-2026-5950 Ashishkumar Parmar X (asparmar - E INFOCHIPS PRIVATE LIMITED at Cisco)
@ 2026-06-10 10:04 ` Ashishkumar Parmar X (asparmar - E INFOCHIPS PRIVATE LIMITED at Cisco)
2026-06-10 10:04 ` [OE-core] [scarthgap] [PATCH 4/5] bind: Ignore CVE-2026-3039 Ashishkumar Parmar X (asparmar - E INFOCHIPS PRIVATE LIMITED at Cisco)
` (2 subsequent siblings)
4 siblings, 0 replies; 7+ messages in thread
From: Ashishkumar Parmar X (asparmar - E INFOCHIPS PRIVATE LIMITED at Cisco) @ 2026-06-10 10:04 UTC (permalink / raw)
To: openembedded-core; +Cc: xe-linux-external, to, Ashishkumar Parmar
From: Ashishkumar Parmar <asparmar@cisco.com>
Pick the upstream 9.18 backport [1] for CVE-2026-3592. The public ISC
advisory [2] describes the vulnerability and identifies the fixed BIND
release.
The upstream fix is split across resolver limits, glue deduplication,
and system-test coverage. Apply the implementation patches before the
tests so the test changes exercise the same behavior as the upstream
fix set:
- CVE-2026-3592_p1.patch [3] limits the number of addresses returned
per ADB find.
- CVE-2026-3592_p2.patch [4] removes duplicate addresses from the
resolver SLIST.
- CVE-2026-3592_p3.patch [5] adds the self-pointed glue deduplication
system test.
- CVE-2026-3592_p4.patch [6] syncs asyncserver.py from the development
branch, with downstream backport adjustments for the 9.18.44 test
tree.
- CVE-2026-3592_p5.patch [7] adds the SRTT-based server selection
system test.
- CVE-2026-3592_p6.patch [8] fixes the resend_loop system test after
the preceding test-support changes.
Keep the patches split to preserve the upstream commit structure and to
make the SRC_URI ordering explicit.
[1] https://gitlab.com/isc-projects/bind9/-/commit/5abfbc2663023e0c6f2a08ee6a7986f2a404f2f6
[2] https://kb.isc.org/docs/cve-2026-3592
[3] https://gitlab.com/isc-projects/bind9/-/commit/695362e3438c832ed0e39e144a77f233113d3431
[4] https://gitlab.com/isc-projects/bind9/-/commit/e25eaf9e6e09bbdd826252226144570222d542d8
[5] https://gitlab.com/isc-projects/bind9/-/commit/d2a67ba22246029192d8072d31b97e3bfd235f64
[6] https://gitlab.com/isc-projects/bind9/-/commit/b0e8966647e744482edc06e48bc9ff5079a1c541
[7] https://gitlab.com/isc-projects/bind9/-/commit/d5cd9b71ebadf7c0c76f09c5bbb65b6a7b944d0d
[8] https://gitlab.com/isc-projects/bind9/-/commit/cb13dcabdb64bdb5f8f7ed33980aaf470a90e877
Signed-off-by: Ashishkumar Parmar <asparmar@cisco.com>
---
.../bind/bind/CVE-2026-3592_p1.patch | 115 +++
.../bind/bind/CVE-2026-3592_p2.patch | 334 +++++++
.../bind/bind/CVE-2026-3592_p3.patch | 629 +++++++++++++
.../bind/bind/CVE-2026-3592_p4.patch | 891 ++++++++++++++++++
.../bind/bind/CVE-2026-3592_p5.patch | 554 +++++++++++
.../bind/bind/CVE-2026-3592_p6.patch | 41 +
.../recipes-connectivity/bind/bind_9.18.44.bb | 6 +
7 files changed, 2570 insertions(+)
create mode 100644 meta/recipes-connectivity/bind/bind/CVE-2026-3592_p1.patch
create mode 100644 meta/recipes-connectivity/bind/bind/CVE-2026-3592_p2.patch
create mode 100644 meta/recipes-connectivity/bind/bind/CVE-2026-3592_p3.patch
create mode 100644 meta/recipes-connectivity/bind/bind/CVE-2026-3592_p4.patch
create mode 100644 meta/recipes-connectivity/bind/bind/CVE-2026-3592_p5.patch
create mode 100644 meta/recipes-connectivity/bind/bind/CVE-2026-3592_p6.patch
diff --git a/meta/recipes-connectivity/bind/bind/CVE-2026-3592_p1.patch b/meta/recipes-connectivity/bind/bind/CVE-2026-3592_p1.patch
new file mode 100644
index 0000000000..3955165a5f
--- /dev/null
+++ b/meta/recipes-connectivity/bind/bind/CVE-2026-3592_p1.patch
@@ -0,0 +1,115 @@
+From 2910d537ac86aa033023678c70ded227ebda1af3 Mon Sep 17 00:00:00 2001
+From: Colin Vidal <colin@isc.org>
+Date: Thu, 5 Feb 2026 09:46:01 +0100
+Subject: [PATCH] Limit the number of addresses returned per ADB find
+
+Add a hard limit on the number of addresses that ADB returns from a
+single NS lookup (dns_adbfind_t). This mitigates a flood attack
+where an attacker controls a zone with many addresses for a
+nameserver, each returning an invalid response. The global
+max-query count (default 50) also limits this, but significant harm
+can be done before that limit is reached.
+
+The default limit is now 6 (v4 and/or v6) addresses for an ADB find (so,
+ADB looking up for A/AAAA addresses of a name server name). It can be
+overridden for testing via 'named -T adbaddrslimit=N'.
+
+CVE: CVE-2026-3592
+Upstream-Status: Backport [https://gitlab.com/isc-projects/bind9/-/commit/695362e3438c832ed0e39e144a77f233113d3431]
+
+(cherry picked from commit 3ec37fc69356ee682bee7f67940613ac31d93d7b)
+(cherry picked from commit 695362e3438c832ed0e39e144a77f233113d3431)
+Signed-off-by: Ashishkumar Parmar <asparmar@cisco.com>
+---
+ bin/named/main.c | 9 +++++++++
+ lib/dns/adb.c | 26 ++++++++++++++++++++++++++
+ 2 files changed, 35 insertions(+)
+
+diff --git a/bin/named/main.c b/bin/named/main.c
+index df47e8d667..ddd43c9b2c 100644
+--- a/bin/named/main.c
++++ b/bin/named/main.c
+@@ -114,6 +114,8 @@ extern unsigned int dns_zone_mkey_hour;
+ extern unsigned int dns_zone_mkey_day;
+ extern unsigned int dns_zone_mkey_month;
+
++extern size_t dns_adb_addrslimit;
++
+ static bool want_stats = false;
+ static char program_name[NAME_MAX] = "named";
+ static char absolute_conffile[PATH_MAX];
+@@ -805,6 +807,13 @@ parse_T_opt(char *option) {
+ transferstuck = true;
+ } else if (!strncmp(option, "tat=", 4)) {
+ named_g_tat_interval = atoi(option + 4);
++ } else if (!strncmp(option, "adbaddrslimit=", 14)) {
++ size_t adb_addrslimit = atoi(option + 14);
++ if (adb_addrslimit < 1) {
++ named_main_earlyfatal("adbaddrslimit must be at "
++ "least 1");
++ }
++ dns_adb_addrslimit = adb_addrslimit;
+ } else {
+ fprintf(stderr, "unknown -T flag '%s'\n", option);
+ }
+diff --git a/lib/dns/adb.c b/lib/dns/adb.c
+index 6ff9069a47..31b489e04a 100644
+--- a/lib/dns/adb.c
++++ b/lib/dns/adb.c
+@@ -86,6 +86,15 @@
+
+ #define DNS_ADB_MINADBSIZE (1024U * 1024U) /*%< 1 Megabyte */
+
++/*
++ * Default and override for the per-find address limit, the sum of the number of
++ * A and AAAA RR from an ADB NS name resolution. When non-zero, this value is
++ * used instead of the default. Can be set via 'named -T adbaddrslimit=N' for
++ * testing.
++ */
++#define DEFAULT_ADDRSLIMIT 6
++size_t dns_adb_addrslimit = 0;
++
+ typedef ISC_LIST(dns_adbname_t) dns_adbnamelist_t;
+ typedef struct dns_adbnamehook dns_adbnamehook_t;
+ typedef ISC_LIST(dns_adbnamehook_t) dns_adbnamehooklist_t;
+@@ -2200,6 +2209,9 @@ copy_namehook_lists(dns_adb_t *adb, dns_adbfind_t *find,
+ dns_adbaddrinfo_t *addrinfo;
+ dns_adbentry_t *entry;
+ int bucket;
++ size_t count = 0;
++ size_t limit = dns_adb_addrslimit != 0 ? dns_adb_addrslimit
++ : DEFAULT_ADDRSLIMIT;
+
+ bucket = DNS_ADB_INVALIDBUCKET;
+
+@@ -2232,6 +2244,13 @@ copy_namehook_lists(dns_adb_t *adb, dns_adbfind_t *find,
+ inc_entry_refcnt(adb, entry, false);
+ ISC_LIST_APPEND(find->list, addrinfo, publink);
+ addrinfo = NULL;
++
++ if (++count >= limit) {
++ DP(ISC_LOG_DEBUG(3), "skipping addresses");
++ UNLOCK(&adb->entrylocks[bucket]);
++ return;
++ }
++
+ nextv4:
+ UNLOCK(&adb->entrylocks[bucket]);
+ bucket = DNS_ADB_INVALIDBUCKET;
+@@ -2267,6 +2286,13 @@ copy_namehook_lists(dns_adb_t *adb, dns_adbfind_t *find,
+ inc_entry_refcnt(adb, entry, false);
+ ISC_LIST_APPEND(find->list, addrinfo, publink);
+ addrinfo = NULL;
++
++ if (++count >= limit) {
++ DP(ISC_LOG_DEBUG(3), "skipping addresses");
++ UNLOCK(&adb->entrylocks[bucket]);
++ return;
++ }
++
+ nextv6:
+ UNLOCK(&adb->entrylocks[bucket]);
+ bucket = DNS_ADB_INVALIDBUCKET;
+--
+2.35.6
+
diff --git a/meta/recipes-connectivity/bind/bind/CVE-2026-3592_p2.patch b/meta/recipes-connectivity/bind/bind/CVE-2026-3592_p2.patch
new file mode 100644
index 0000000000..024158b13c
--- /dev/null
+++ b/meta/recipes-connectivity/bind/bind/CVE-2026-3592_p2.patch
@@ -0,0 +1,334 @@
+From d77197dd24af873e64a5511800a3ca5aa262eed6 Mon Sep 17 00:00:00 2001
+From: Colin Vidal <colin@isc.org>
+Date: Wed, 4 Feb 2026 10:18:42 +0100
+Subject: [PATCH] Remove duplicate addresses from the resolver SLIST
+
+The SLIST (essentially `fctx->finds`, forwarders and dual-stack
+alternatives aside) can have duplicate server addresses when multiple
+in-domain nameservers share the same IP addresses:
+
+ sub.example. NS ns1.sub.example.
+ sub.example. NS ns2.sub.example.
+ ns1.sub.example. A 1.2.3.4
+ ns1.sub.example. A 5.6.7.8
+ ns2.sub.example. A 1.2.3.4
+ ns2.sub.example. A 5.6.7.8
+
+If both 1.2.3.4 and 5.6.7.8 fail to return a valid answer, the resolver
+would query each address twice.
+
+The problem is fixed by replacing the two-phase server selection (sort
+each find list by SRTT, sort finds by head SRTT) with a single linear
+scan in nextaddress() that finds the lowest-SRTT unmarked, non-duplicate
+address across all find lists.
+
+The old approach had a correctness bug: after sorting, the resolver
+picked the next address from the "current" find list rather than
+globally. For example, with find lists [1, 15, 26] and [3, 4, 5], the
+second pick would be SRTT 15 instead of the correct SRTT 3.
+
+The new approach is both simpler and correct: each call to nextaddress()
+walks all addresses, skips marked and duplicate entries, and returns the
+one with the lowest SRTT. While this walk is repeated for each server
+attempt, it operates on a small bounded list and is negligible compared
+to the network I/O of querying the server.
+
+CVE: CVE-2026-3592
+Upstream-Status: Backport [https://gitlab.com/isc-projects/bind9/-/commit/e25eaf9e6e09bbdd826252226144570222d542d8]
+
+(cherry picked from commit b1c5856a3764b4025e93f8baf06c45c8fa029752)
+(cherry picked from commit e25eaf9e6e09bbdd826252226144570222d542d8)
+Signed-off-by: Ashishkumar Parmar <asparmar@cisco.com>
+---
+ lib/dns/resolver.c | 226 +++++++++++++++++++--------------------------
+ 1 file changed, 93 insertions(+), 133 deletions(-)
+
+diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c
+index cc40f862d1..c1584c3399 100644
+--- a/lib/dns/resolver.c
++++ b/lib/dns/resolver.c
+@@ -369,7 +369,16 @@ struct fetchctx {
+ dns_message_t *qmessage;
+ ISC_LIST(resquery_t) queries;
+ dns_adbfindlist_t finds;
+- dns_adbfind_t *find;
++ /*
++ * This is a state to keep track of the latest upstream server which is
++ * being queried. See `nextaddress()`.
++ *
++ * `addrinfo` is basically a copy of `foundaddrinfo` but came from the
++ * response of the query, so fields like the SRTT/timing might have been
++ * altered. So it might be possible (?) to wrap those two in an union
++ * for clarity (and memory saving).
++ */
++ dns_adbaddrinfo_t *foundaddrinfo;
+ /*
+ * altfinds are names and/or addresses of dual stack servers that
+ * should be used when iterative resolution to a server is not
+@@ -1534,7 +1543,7 @@ fctx_cleanup(fetchctx_t *fctx) {
+ dns_adb_destroyfind(&find);
+ fctx_unref(fctx);
+ }
+- fctx->find = NULL;
++ fctx->foundaddrinfo = NULL;
+
+ for (find = ISC_LIST_HEAD(fctx->altfinds); find != NULL;
+ find = next_find)
+@@ -3355,91 +3364,10 @@ add_bad(fetchctx_t *fctx, dns_message_t *rmessage, dns_adbaddrinfo_t *addrinfo,
+ }
+
+ /*
+- * Sort addrinfo list by RTT.
+- */
+-static void
+-sort_adbfind(dns_adbfind_t *find, unsigned int bias) {
+- dns_adbaddrinfo_t *best, *curr;
+- dns_adbaddrinfolist_t sorted;
+- unsigned int best_srtt, curr_srtt;
+-
+- /* Lame N^2 bubble sort. */
+- ISC_LIST_INIT(sorted);
+- while (!ISC_LIST_EMPTY(find->list)) {
+- best = ISC_LIST_HEAD(find->list);
+- best_srtt = best->srtt;
+- if (isc_sockaddr_pf(&best->sockaddr) != AF_INET6) {
+- best_srtt += bias;
+- }
+- curr = ISC_LIST_NEXT(best, publink);
+- while (curr != NULL) {
+- curr_srtt = curr->srtt;
+- if (isc_sockaddr_pf(&curr->sockaddr) != AF_INET6) {
+- curr_srtt += bias;
+- }
+- if (curr_srtt < best_srtt) {
+- best = curr;
+- best_srtt = curr_srtt;
+- }
+- curr = ISC_LIST_NEXT(curr, publink);
+- }
+- ISC_LIST_UNLINK(find->list, best, publink);
+- ISC_LIST_APPEND(sorted, best, publink);
+- }
+- find->list = sorted;
+-}
+-
+-/*
+- * Sort a list of finds by server RTT.
+- */
+-static void
+-sort_finds(dns_adbfindlist_t *findlist, unsigned int bias) {
+- dns_adbfind_t *best, *curr;
+- dns_adbfindlist_t sorted;
+- dns_adbaddrinfo_t *addrinfo, *bestaddrinfo;
+- unsigned int best_srtt, curr_srtt;
+-
+- /* Sort each find's addrinfo list by SRTT. */
+- for (curr = ISC_LIST_HEAD(*findlist); curr != NULL;
+- curr = ISC_LIST_NEXT(curr, publink))
+- {
+- sort_adbfind(curr, bias);
+- }
+-
+- /* Lame N^2 bubble sort. */
+- ISC_LIST_INIT(sorted);
+- while (!ISC_LIST_EMPTY(*findlist)) {
+- best = ISC_LIST_HEAD(*findlist);
+- bestaddrinfo = ISC_LIST_HEAD(best->list);
+- INSIST(bestaddrinfo != NULL);
+- best_srtt = bestaddrinfo->srtt;
+- if (isc_sockaddr_pf(&bestaddrinfo->sockaddr) != AF_INET6) {
+- best_srtt += bias;
+- }
+- curr = ISC_LIST_NEXT(best, publink);
+- while (curr != NULL) {
+- addrinfo = ISC_LIST_HEAD(curr->list);
+- INSIST(addrinfo != NULL);
+- curr_srtt = addrinfo->srtt;
+- if (isc_sockaddr_pf(&addrinfo->sockaddr) != AF_INET6) {
+- curr_srtt += bias;
+- }
+- if (curr_srtt < best_srtt) {
+- best = curr;
+- best_srtt = curr_srtt;
+- }
+- curr = ISC_LIST_NEXT(curr, publink);
+- }
+- ISC_LIST_UNLINK(*findlist, best, publink);
+- ISC_LIST_APPEND(sorted, best, publink);
+- }
+- *findlist = sorted;
+-}
+-
+-/*
+- * Return true iff the ADB find has a pending fetch for 'type'. This is
+- * used to find out whether we're in a loop, where a fetch is waiting for a
+- * find which is waiting for that same fetch.
++ * Return true iff the ADB find has an already pending fetch for 'type'. This
++ * is used to find out whether we're in a loop, where a fetch is waiting for a
++ * find which is waiting for that same fetch. So if the current find actually
++ * started the fetch, we know it can't be a loop, so we returns false.
+ *
+ * Note: This could be done with either an equivalence check (e.g.,
+ * query_pending == DNS_ADBFIND_INET) or with a bit check, as below. If
+@@ -3546,6 +3474,7 @@ findname(fetchctx_t *fctx, const dns_name_t *name, in_port_t port,
+ }
+ }
+ }
++
+ if ((flags & FCTX_ADDRINFO_DUALSTACK) != 0) {
+ ISC_LIST_APPEND(fctx->altfinds, find, publink);
+ } else {
+@@ -3961,8 +3890,6 @@ out:
+ * We've found some addresses. We might still be
+ * looking for more addresses.
+ */
+- sort_finds(&fctx->finds, res->view->v6bias);
+- sort_finds(&fctx->altfinds, 0);
+ result = ISC_R_SUCCESS;
+ }
+
+@@ -4037,6 +3964,80 @@ possibly_mark(fetchctx_t *fctx, dns_adbaddrinfo_t *addr) {
+ }
+ }
+
++static dns_adbaddrinfo_t *
++nextaddress(fetchctx_t *fctx) {
++ dns_adbaddrinfo_t *prevai = fctx->foundaddrinfo, *lowestsrttai = NULL;
++ unsigned int v6bias = fctx->res->view->v6bias, lowestsrtt = 0;
++
++ /*
++ * Let's walk through the list of dns_adbaddrinfo_t to find the best
++ * next server address to query. This is linear on the number of
++ * dns_adbaddrinfo_t which are grouped in find list (for each ADB find).
++ */
++ for (dns_adbfind_t *find = ISC_LIST_HEAD(fctx->finds); find != NULL;
++ find = ISC_LIST_NEXT(find, publink))
++ {
++ for (dns_adbaddrinfo_t *ai = ISC_LIST_HEAD(find->list);
++ ai != NULL; ai = ISC_LIST_NEXT(ai, publink))
++ {
++ /*
++ * This address has been marked already, skip it.
++ */
++ if (!UNMARKED(ai)) {
++ continue;
++ }
++
++ /*
++ * This address is the same as the previously used
++ * address, it's a duplicate, mark it and skip it!
++ */
++ if (prevai != NULL) {
++ if (prevai->entry == ai->entry) {
++ ai->flags |= FCTX_ADDRINFO_MARK;
++ continue;
++ }
++ }
++
++ /*
++ * Mark and skip this address if incompatible (i.e. IPv6
++ * address on a v4 only server, or for ACL reason, etc.)
++ */
++ possibly_mark(fctx, ai);
++ if (!UNMARKED(ai)) {
++ continue;
++ }
++
++ /*
++ * This address hasn't been tried yet and is a
++ * good candidate. Let's keep track of it if it
++ * has the lowest SRTT so far (or if there is no
++ * address with lowest SRTT found yet).
++ */
++ unsigned int aisrtt = ai->srtt;
++
++ if (isc_sockaddr_pf(&ai->sockaddr) != AF_INET6) {
++ aisrtt += v6bias;
++ }
++
++ if (lowestsrttai == NULL || aisrtt < lowestsrtt) {
++ lowestsrttai = ai;
++ lowestsrtt = aisrtt;
++ continue;
++ }
++ }
++ }
++
++ /*
++ * This is the next address to query. If this is NULL, we're done.
++ */
++ if (lowestsrttai != NULL) {
++ lowestsrttai->flags |= FCTX_ADDRINFO_MARK;
++ }
++ fctx->foundaddrinfo = lowestsrttai;
++
++ return lowestsrttai;
++}
++
+ static dns_adbaddrinfo_t *
+ fctx_nextaddress(fetchctx_t *fctx) {
+ dns_adbfind_t *find, *start;
+@@ -4059,7 +4060,6 @@ fctx_nextaddress(fetchctx_t *fctx) {
+ possibly_mark(fctx, addrinfo);
+ if (UNMARKED(addrinfo)) {
+ addrinfo->flags |= FCTX_ADDRINFO_MARK;
+- fctx->find = NULL;
+ fctx->forwarding = true;
+
+ /*
+@@ -4080,49 +4080,9 @@ fctx_nextaddress(fetchctx_t *fctx) {
+ fctx->forwarding = false;
+ FCTX_ATTR_SET(fctx, FCTX_ATTR_TRIEDFIND);
+
+- find = fctx->find;
+- if (find == NULL) {
+- find = ISC_LIST_HEAD(fctx->finds);
+- } else {
+- find = ISC_LIST_NEXT(find, publink);
+- if (find == NULL) {
+- find = ISC_LIST_HEAD(fctx->finds);
+- }
+- }
+-
+- /*
+- * Find the first unmarked addrinfo.
+- */
+- addrinfo = NULL;
+- if (find != NULL) {
+- start = find;
+- do {
+- for (addrinfo = ISC_LIST_HEAD(find->list);
+- addrinfo != NULL;
+- addrinfo = ISC_LIST_NEXT(addrinfo, publink))
+- {
+- if (!UNMARKED(addrinfo)) {
+- continue;
+- }
+- possibly_mark(fctx, addrinfo);
+- if (UNMARKED(addrinfo)) {
+- addrinfo->flags |= FCTX_ADDRINFO_MARK;
+- break;
+- }
+- }
+- if (addrinfo != NULL) {
+- break;
+- }
+- find = ISC_LIST_NEXT(find, publink);
+- if (find == NULL) {
+- find = ISC_LIST_HEAD(fctx->finds);
+- }
+- } while (find != start);
+- }
+-
+- fctx->find = find;
+- if (addrinfo != NULL) {
+- return addrinfo;
++ faddrinfo = nextaddress(fctx);
++ if (faddrinfo != NULL) {
++ return faddrinfo;
+ }
+
+ /*
+--
+2.35.6
+
diff --git a/meta/recipes-connectivity/bind/bind/CVE-2026-3592_p3.patch b/meta/recipes-connectivity/bind/bind/CVE-2026-3592_p3.patch
new file mode 100644
index 0000000000..a14d0243a8
--- /dev/null
+++ b/meta/recipes-connectivity/bind/bind/CVE-2026-3592_p3.patch
@@ -0,0 +1,629 @@
+From 1d837ed1dfb8a25cb12cf18a3ce998745adcfbd9 Mon Sep 17 00:00:00 2001
+From: Colin Vidal <colin@isc.org>
+Date: Thu, 5 Feb 2026 11:20:11 +0100
+Subject: [PATCH] Add system test for self-pointed glue deduplication
+
+Test the resolver's behavior with self-pointed glue where each NS
+has the same set of addresses. Verify that addresses are
+deduplicated and each unique IP is only queried once.
+
+Also test the ADB address limit knob (-T adbaddrslimit=).
+
+CVE: CVE-2026-3592
+Upstream-Status: Backport [https://gitlab.com/isc-projects/bind9/-/commit/d2a67ba22246029192d8072d31b97e3bfd235f64]
+
+(cherry picked from commit c21fc6cb95d77312d6fb891f17ce9df41a25af6d)
+(cherry picked from commit d2a67ba22246029192d8072d31b97e3bfd235f64)
+Signed-off-by: Ashishkumar Parmar <asparmar@cisco.com>
+---
+ .../system/selfpointedglue/ns1/named.conf.j2 | 28 ++++
+ bin/tests/system/selfpointedglue/ns1/root.db | 24 +++
+ .../system/selfpointedglue/ns2/named.conf.j2 | 28 ++++
+ bin/tests/system/selfpointedglue/ns2/tld.db | 27 +++
+ .../system/selfpointedglue/ns3/example.tld.db | 155 ++++++++++++++++++
+ .../selfpointedglue/ns3/example2.tld.db | 33 ++++
+ .../system/selfpointedglue/ns3/named.conf.j2 | 44 +++++
+ .../system/selfpointedglue/ns4/named.args.j2 | 3 +
+ .../system/selfpointedglue/ns4/named.conf.j2 | 59 +++++++
+ .../system/selfpointedglue/ns4/root.hint | 14 ++
+ bin/tests/system/selfpointedglue/prereq.sh | 20 +++
+ .../selfpointedglue/tests_selfpointedglue.py | 75 +++++++++
+ 12 files changed, 510 insertions(+)
+ create mode 100644 bin/tests/system/selfpointedglue/ns1/named.conf.j2
+ create mode 100644 bin/tests/system/selfpointedglue/ns1/root.db
+ create mode 100644 bin/tests/system/selfpointedglue/ns2/named.conf.j2
+ create mode 100644 bin/tests/system/selfpointedglue/ns2/tld.db
+ create mode 100644 bin/tests/system/selfpointedglue/ns3/example.tld.db
+ create mode 100644 bin/tests/system/selfpointedglue/ns3/example2.tld.db
+ create mode 100644 bin/tests/system/selfpointedglue/ns3/named.conf.j2
+ create mode 100644 bin/tests/system/selfpointedglue/ns4/named.args.j2
+ create mode 100644 bin/tests/system/selfpointedglue/ns4/named.conf.j2
+ create mode 100644 bin/tests/system/selfpointedglue/ns4/root.hint
+ create mode 100644 bin/tests/system/selfpointedglue/prereq.sh
+ create mode 100644 bin/tests/system/selfpointedglue/tests_selfpointedglue.py
+
+diff --git a/bin/tests/system/selfpointedglue/ns1/named.conf.j2 b/bin/tests/system/selfpointedglue/ns1/named.conf.j2
+new file mode 100644
+index 0000000000..fd83fc3c19
+--- /dev/null
++++ b/bin/tests/system/selfpointedglue/ns1/named.conf.j2
+@@ -0,0 +1,28 @@
++/*
++ * 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; };
++ recursion no;
++ dnssec-validation no;
++};
++
++zone "." {
++ type primary;
++ file "root.db";
++};
+diff --git a/bin/tests/system/selfpointedglue/ns1/root.db b/bin/tests/system/selfpointedglue/ns1/root.db
+new file mode 100644
+index 0000000000..bfbf049b80
+--- /dev/null
++++ b/bin/tests/system/selfpointedglue/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 owner.root-servers.nil. a.root.servers.nil. (
++ 2010 ; serial
++ 600 ; refresh
++ 600 ; retry
++ 1200 ; expire
++ 600 ; minimum
++ )
++. NS a.root-servers.nil.
++a.root-servers.nil. A 10.53.0.1
++
++tld. NS ns.tld.
++ns.tld. A 10.53.0.2
+diff --git a/bin/tests/system/selfpointedglue/ns2/named.conf.j2 b/bin/tests/system/selfpointedglue/ns2/named.conf.j2
+new file mode 100644
+index 0000000000..2993832da2
+--- /dev/null
++++ b/bin/tests/system/selfpointedglue/ns2/named.conf.j2
+@@ -0,0 +1,28 @@
++/*
++ * 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; };
++ recursion no;
++ dnssec-validation no;
++};
++
++zone "tld." {
++ type primary;
++ file "tld.db";
++};
+diff --git a/bin/tests/system/selfpointedglue/ns2/tld.db b/bin/tests/system/selfpointedglue/ns2/tld.db
+new file mode 100644
+index 0000000000..5935fd841c
+--- /dev/null
++++ b/bin/tests/system/selfpointedglue/ns2/tld.db
+@@ -0,0 +1,27 @@
++; 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
++tld. IN SOA owner.tld. ns.tld. (
++ 2010 ; serial
++ 600 ; refresh
++ 600 ; retry
++ 1200 ; expire
++ 600 ; minimum
++ )
++tld. NS ns.tld.
++ns.tld. A 10.53.0.2
++
++example.tld. NS ns.example.tld.
++ns.example.tld. A 10.53.0.3
++
++example2.tld. NS ns.example2.tld.
++ns.example2.tld. A 10.53.0.3
+diff --git a/bin/tests/system/selfpointedglue/ns3/example.tld.db b/bin/tests/system/selfpointedglue/ns3/example.tld.db
+new file mode 100644
+index 0000000000..83ea4d37ec
+--- /dev/null
++++ b/bin/tests/system/selfpointedglue/ns3/example.tld.db
+@@ -0,0 +1,155 @@
++; 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
++example.tld. IN SOA owner.dnshoster.tld. ns.dnshoster.tld. (
++ 2010 ; serial
++ 600 ; refresh
++ 600 ; retry
++ 1200 ; expire
++ 600 ; minimum
++ )
++
++example.tld. NS ns.example.tld.
++ns.example.tld. A 10.53.0.3
++
++sub.example.tld. NS ns01.sub.example.tld.
++sub.example.tld. NS ns02.sub.example.tld.
++sub.example.tld. NS ns03.sub.example.tld.
++sub.example.tld. NS ns04.sub.example.tld.
++sub.example.tld. NS ns05.sub.example.tld.
++sub.example.tld. NS ns06.sub.example.tld.
++sub.example.tld. NS ns07.sub.example.tld.
++sub.example.tld. NS ns08.sub.example.tld.
++sub.example.tld. NS ns09.sub.example.tld.
++sub.example.tld. NS ns10.sub.example.tld.
++
++ns01.sub.example.tld. A 10.53.0.5
++ns01.sub.example.tld. A 10.53.0.6
++ns01.sub.example.tld. A 10.53.0.7
++ns01.sub.example.tld. A 10.53.0.8
++ns01.sub.example.tld. A 10.53.0.9
++ns01.sub.example.tld. A 10.53.0.10
++ns01.sub.example.tld. A 10.53.1.1
++ns01.sub.example.tld. A 10.53.1.2
++ns01.sub.example.tld. A 10.53.2.1
++ns01.sub.example.tld. A 10.53.0.3
++; Those RR (same below) pointing to 127.0.0.1 won't ever be used as they
++; exceeded the ADB limit.
++ns01.sub.example.tld. A 127.0.0.1
++
++ns02.sub.example.tld. A 10.53.0.5
++ns02.sub.example.tld. A 10.53.0.6
++ns02.sub.example.tld. A 10.53.0.7
++ns02.sub.example.tld. A 10.53.0.8
++ns02.sub.example.tld. A 10.53.0.9
++ns02.sub.example.tld. A 10.53.0.10
++ns02.sub.example.tld. A 10.53.1.1
++ns02.sub.example.tld. A 10.53.1.2
++ns02.sub.example.tld. A 10.53.2.1
++ns02.sub.example.tld. A 10.53.0.3
++ns02.sub.example.tld. A 127.0.0.1
++
++ns03.sub.example.tld. A 10.53.0.5
++ns03.sub.example.tld. A 10.53.0.6
++ns03.sub.example.tld. A 10.53.0.7
++ns03.sub.example.tld. A 10.53.0.8
++ns03.sub.example.tld. A 10.53.0.9
++ns03.sub.example.tld. A 10.53.0.10
++ns03.sub.example.tld. A 10.53.1.1
++ns03.sub.example.tld. A 10.53.1.2
++ns03.sub.example.tld. A 10.53.2.1
++ns03.sub.example.tld. A 10.53.0.3
++ns03.sub.example.tld. A 127.0.0.1
++
++ns04.sub.example.tld. A 10.53.0.5
++ns04.sub.example.tld. A 10.53.0.6
++ns04.sub.example.tld. A 10.53.0.7
++ns04.sub.example.tld. A 10.53.0.8
++ns04.sub.example.tld. A 10.53.0.9
++ns04.sub.example.tld. A 10.53.0.10
++ns04.sub.example.tld. A 10.53.1.1
++ns04.sub.example.tld. A 10.53.1.2
++ns04.sub.example.tld. A 10.53.2.1
++ns04.sub.example.tld. A 10.53.0.3
++ns04.sub.example.tld. A 127.0.0.1
++
++ns05.sub.example.tld. A 10.53.0.5
++ns05.sub.example.tld. A 10.53.0.6
++ns05.sub.example.tld. A 10.53.0.7
++ns05.sub.example.tld. A 10.53.0.8
++ns05.sub.example.tld. A 10.53.0.9
++ns05.sub.example.tld. A 10.53.0.10
++ns05.sub.example.tld. A 10.53.1.1
++ns05.sub.example.tld. A 10.53.1.2
++ns05.sub.example.tld. A 10.53.2.1
++ns05.sub.example.tld. A 10.53.0.3
++ns05.sub.example.tld. A 127.0.0.1
++
++ns06.sub.example.tld. A 10.53.0.5
++ns06.sub.example.tld. A 10.53.0.6
++ns06.sub.example.tld. A 10.53.0.7
++ns06.sub.example.tld. A 10.53.0.8
++ns06.sub.example.tld. A 10.53.0.9
++ns06.sub.example.tld. A 10.53.0.10
++ns06.sub.example.tld. A 10.53.1.1
++ns06.sub.example.tld. A 10.53.1.2
++ns06.sub.example.tld. A 10.53.2.1
++ns06.sub.example.tld. A 10.53.0.3
++ns06.sub.example.tld. A 127.0.0.1
++
++ns07.sub.example.tld. A 10.53.0.5
++ns07.sub.example.tld. A 10.53.0.6
++ns07.sub.example.tld. A 10.53.0.7
++ns07.sub.example.tld. A 10.53.0.8
++ns07.sub.example.tld. A 10.53.0.9
++ns07.sub.example.tld. A 10.53.0.10
++ns07.sub.example.tld. A 10.53.1.1
++ns07.sub.example.tld. A 10.53.1.2
++ns07.sub.example.tld. A 10.53.2.1
++ns07.sub.example.tld. A 10.53.0.3
++ns07.sub.example.tld. A 127.0.0.1
++
++ns08.sub.example.tld. A 10.53.0.5
++ns08.sub.example.tld. A 10.53.0.6
++ns08.sub.example.tld. A 10.53.0.7
++ns08.sub.example.tld. A 10.53.0.8
++ns08.sub.example.tld. A 10.53.0.9
++ns08.sub.example.tld. A 10.53.0.10
++ns08.sub.example.tld. A 10.53.1.1
++ns08.sub.example.tld. A 10.53.1.2
++ns08.sub.example.tld. A 10.53.2.1
++ns08.sub.example.tld. A 10.53.0.3
++ns08.sub.example.tld. A 127.0.0.1
++
++ns09.sub.example.tld. A 10.53.0.5
++ns09.sub.example.tld. A 10.53.0.6
++ns09.sub.example.tld. A 10.53.0.7
++ns09.sub.example.tld. A 10.53.0.8
++ns09.sub.example.tld. A 10.53.0.9
++ns09.sub.example.tld. A 10.53.0.10
++ns09.sub.example.tld. A 10.53.1.1
++ns09.sub.example.tld. A 10.53.1.2
++ns09.sub.example.tld. A 10.53.2.1
++ns09.sub.example.tld. A 10.53.0.3
++ns09.sub.example.tld. A 127.0.0.1
++
++ns10.sub.example.tld. A 10.53.0.5
++ns10.sub.example.tld. A 10.53.0.6
++ns10.sub.example.tld. A 10.53.0.7
++ns10.sub.example.tld. A 10.53.0.8
++ns10.sub.example.tld. A 10.53.0.9
++ns10.sub.example.tld. A 10.53.0.10
++ns10.sub.example.tld. A 10.53.1.1
++ns10.sub.example.tld. A 10.53.1.2
++ns10.sub.example.tld. A 10.53.2.1
++ns10.sub.example.tld. A 10.53.0.3
++ns10.sub.example.tld. A 127.0.0.1
+diff --git a/bin/tests/system/selfpointedglue/ns3/example2.tld.db b/bin/tests/system/selfpointedglue/ns3/example2.tld.db
+new file mode 100644
+index 0000000000..bcab6e38c1
+--- /dev/null
++++ b/bin/tests/system/selfpointedglue/ns3/example2.tld.db
+@@ -0,0 +1,33 @@
++; 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
++example2.tld. IN SOA owner.dnshoster.tld. ns.dnshoster.tld. (
++ 2010 ; serial
++ 600 ; refresh
++ 600 ; retry
++ 1200 ; expire
++ 600 ; minimum
++ )
++
++example2.tld. NS ns.example2.tld.
++ns.example2.tld. A 10.53.0.3
++
++sub.example2.tld. NS ns01.sub.example2.tld.
++sub.example2.tld. NS ns02.sub.example2.tld.
++sub.example2.tld. NS ns03.sub.example2.tld.
++
++ns01.sub.example2.tld. A 10.53.1.1
++ns01.sub.example2.tld. A 10.53.0.5
++ns02.sub.example2.tld. A 10.53.1.2
++ns02.sub.example2.tld. A 10.53.0.6
++ns03.sub.example2.tld. A 10.53.2.1
++ns03.sub.example2.tld. A 10.53.0.7
+diff --git a/bin/tests/system/selfpointedglue/ns3/named.conf.j2 b/bin/tests/system/selfpointedglue/ns3/named.conf.j2
+new file mode 100644
+index 0000000000..b5c8bfcf33
+--- /dev/null
++++ b/bin/tests/system/selfpointedglue/ns3/named.conf.j2
+@@ -0,0 +1,44 @@
++/*
++ * 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;
++ 10.53.0.5;
++ 10.53.0.6;
++ 10.53.0.7;
++ 10.53.0.8;
++ 10.53.0.9;
++ 10.53.0.10;
++ 10.53.1.1;
++ 10.53.1.2;
++ 10.53.2.1;
++ };
++ recursion no;
++ dnssec-validation no;
++};
++
++zone "example.tld." {
++ type primary;
++ file "example.tld.db";
++};
++
++zone "example2.tld." {
++ type primary;
++ file "example2.tld.db";
++};
+diff --git a/bin/tests/system/selfpointedglue/ns4/named.args.j2 b/bin/tests/system/selfpointedglue/ns4/named.args.j2
+new file mode 100644
+index 0000000000..071508fd70
+--- /dev/null
++++ b/bin/tests/system/selfpointedglue/ns4/named.args.j2
+@@ -0,0 +1,3 @@
++{% set adblimit = adblimit | default("") %}
++
++-D selfpointedglue-ns4 -m record -c named.conf -d 99 -g -T maxcachesize=2097152 -4 @adblimit@
+diff --git a/bin/tests/system/selfpointedglue/ns4/named.conf.j2 b/bin/tests/system/selfpointedglue/ns4/named.conf.j2
+new file mode 100644
+index 0000000000..09fbdd4e70
+--- /dev/null
++++ b/bin/tests/system/selfpointedglue/ns4/named.conf.j2
+@@ -0,0 +1,59 @@
++/*
++ * 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.
++ */
++{% set maxdelegationservers = maxdelegationservers | default(None) %}
++
++options {
++ query-source address 10.53.0.4;
++ notify-source 10.53.0.4;
++ transfer-source 10.53.0.4;
++ port @PORT@;
++ pid-file "named.pid";
++ listen-on { 10.53.0.4; };
++ recursion yes;
++ dnssec-validation no;
++ dnstap { resolver query; };
++ dnstap-output file "dnstap.out";
++ {% if maxdelegationservers %}
++ @maxdelegationservers@
++ {% endif %}
++};
++
++/*
++ * Forcing TCP ensures that ADDITIONAL won't be truncated (responses won't have
++ * the TC flag, hence the resolver won't retry using TCP by itself, see
++ * https://datatracker.ietf.org/doc/html/rfc2181#section-9)
++ */
++server 10.53.0.3 { tcp-only true; };
++server 10.53.0.5 { tcp-only true; };
++server 10.53.0.6 { tcp-only true; };
++server 10.53.0.7 { tcp-only true; };
++server 10.53.0.8 { tcp-only true; };
++server 10.53.0.9 { tcp-only true; };
++server 10.53.0.10 { tcp-only true; };
++server 10.53.1.1 { tcp-only true; };
++server 10.53.1.2 { tcp-only true; };
++server 10.53.2.1 { tcp-only true; };
++
++zone "." {
++ type hint;
++ file "root.hint";
++};
++
++key rndc_key {
++ secret "1234abcd8765";
++ algorithm @DEFAULT_HMAC@;
++};
++
++controls {
++ inet 10.53.0.4 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
++};
+diff --git a/bin/tests/system/selfpointedglue/ns4/root.hint b/bin/tests/system/selfpointedglue/ns4/root.hint
+new file mode 100644
+index 0000000000..d7d0e1faba
+--- /dev/null
++++ b/bin/tests/system/selfpointedglue/ns4/root.hint
+@@ -0,0 +1,14 @@
++; 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 999999
++. IN NS a.root-servers.nil.
++a.root-servers.nil. IN A 10.53.0.1
+diff --git a/bin/tests/system/selfpointedglue/prereq.sh b/bin/tests/system/selfpointedglue/prereq.sh
+new file mode 100644
+index 0000000000..747f448982
+--- /dev/null
++++ b/bin/tests/system/selfpointedglue/prereq.sh
+@@ -0,0 +1,20 @@
++#!/bin/sh
++
++# 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.
++
++. ../conf.sh
++
++$FEATURETEST --enable-dnstap || {
++ echo_i "This test requires dnstap support." >&2
++ exit 255
++}
++exit 0
+diff --git a/bin/tests/system/selfpointedglue/tests_selfpointedglue.py b/bin/tests/system/selfpointedglue/tests_selfpointedglue.py
+new file mode 100644
+index 0000000000..03a71cf545
+--- /dev/null
++++ b/bin/tests/system/selfpointedglue/tests_selfpointedglue.py
+@@ -0,0 +1,75 @@
++# 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 os
++
++import isctest
++
++
++def line_to_ips_and_queries(line):
++ # dnstap-read output line example
++ # 05-Feb-2026 11:00:57.853 RQ 10.53.0.4:38507 -> 10.53.0.3:22047 TCP 56b sub.example.tld/IN/NS
++ _, _, _, _, _, dst, _, _, query = line.split(" ", 9)
++ ip, _ = dst.split(":", 1)
++ return (ip, query)
++
++
++def extract_dnstap(ns, nsid, expectedlen):
++ ns.rndc("dnstap -roll 1")
++ path = os.path.join(nsid, "dnstap.out.0")
++ dnstapread = isctest.run.cmd(
++ [os.getenv("DNSTAPREAD"), path],
++ )
++
++ lines = dnstapread.out.splitlines()
++ assert expectedlen == len(lines)
++ return list(map(line_to_ips_and_queries, lines))
++
++
++# Because DNSTAP doesn't have ordering guarantee, the order doesn't matter here.
++def expect_ip_and_query(expected_ips_and_queries, ips_and_queries):
++ found_count = 0
++ for expected_ip, expected_query in expected_ips_and_queries:
++ found = False
++ for ip, query in ips_and_queries:
++ if ip == expected_ip and query == expected_query:
++ found = True
++ found_count += 1
++ break
++ assert found
++ assert found_count == len(expected_ips_and_queries)
++
++
++def test_selfpointedglue(ns4):
++ msg = isctest.query.create("a.sub.example.tld.", "A")
++ res = isctest.query.tcp(msg, ns4.ip)
++ isctest.check.servfail(res)
++
++ ips_and_queries = extract_dnstap(ns4, "ns4", 10)
++
++ # Thanks to the de-duplication, only the first 6 NS IPs are
++ # queried (once sub.example.tld. NS is found) instead of 60
++ # (60 per NS, with 10 NS).
++ expect_ip_and_query(
++ [
++ ("10.53.0.1", "./IN/NS"),
++ ("10.53.0.1", "tld/IN/NS"),
++ ("10.53.0.2", "example.tld/IN/NS"),
++ ("10.53.0.3", "sub.example.tld/IN/NS"),
++ ("10.53.0.3", "a.sub.example.tld/IN/A"),
++ ("10.53.0.5", "a.sub.example.tld/IN/A"),
++ ("10.53.0.6", "a.sub.example.tld/IN/A"),
++ ("10.53.0.7", "a.sub.example.tld/IN/A"),
++ ("10.53.0.8", "a.sub.example.tld/IN/A"),
++ ("10.53.0.9", "a.sub.example.tld/IN/A"),
++ ],
++ ips_and_queries,
++ )
+--
+2.35.6
+
diff --git a/meta/recipes-connectivity/bind/bind/CVE-2026-3592_p4.patch b/meta/recipes-connectivity/bind/bind/CVE-2026-3592_p4.patch
new file mode 100644
index 0000000000..e399ff86fc
--- /dev/null
+++ b/meta/recipes-connectivity/bind/bind/CVE-2026-3592_p4.patch
@@ -0,0 +1,891 @@
+From 46fe5e321b6e4bf8a27df2f7659e6e4f233c94d9 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Micha=C5=82=20K=C4=99pie=C5=84?= <michal@isc.org>
+Date: Fri, 17 Apr 2026 17:57:05 +0200
+Subject: [PATCH] Sync asyncserver.py with the development branch
+
+Import bin/tests/system/isctest/asyncserver.py as present in commit
+ced002c4ab7b920c9528d315a611a477cb4a9409 on the "main" branch. This
+enables using newer asyncserver.py infrastructure code in system tests
+that need to be backported to maintenance branches.
+
+CVE: CVE-2026-3592
+Upstream-Status: Backport [https://gitlab.com/isc-projects/bind9/-/commit/b0e8966647e744482edc06e48bc9ff5079a1c541]
+
+Backport Changes:
+- Regenerated against the BIND 9.18.44 system-test helper
+ context, which already carries dnspython compatibility code not
+ present in the upstream patch base.
+
+(cherry picked from commit b0e8966647e744482edc06e48bc9ff5079a1c541)
+Signed-off-by: Ashishkumar Parmar <asparmar@cisco.com>
+---
+ bin/tests/system/isctest/asyncserver.py | 477 ++++++++++++++++++------
+ 1 file changed, 370 insertions(+), 107 deletions(-)
+
+diff --git a/bin/tests/system/isctest/asyncserver.py b/bin/tests/system/isctest/asyncserver.py
+index d2b22d7c12..e560ad10c0 100644
+--- a/bin/tests/system/isctest/asyncserver.py
++++ b/bin/tests/system/isctest/asyncserver.py
+@@ -11,20 +11,9 @@ See the COPYRIGHT file distributed with this work for additional
+ information regarding copyright ownership.
+ """
+
++from collections.abc import AsyncGenerator, Callable, Coroutine, Sequence
+ from dataclasses import dataclass, field
+-from typing import (
+- Any,
+- AsyncGenerator,
+- Callable,
+- Coroutine,
+- Dict,
+- List,
+- Optional,
+- Set,
+- Tuple,
+- Union,
+- cast,
+-)
++from typing import Any, cast
+
+ import abc
+ import asyncio
+@@ -52,12 +41,11 @@ import dns.rdataset
+ import dns.rdatatype
+ import dns.rrset
+ import dns.tsig
+-import dns.version
+ import dns.zone
+
+
+ _UdpHandler = Callable[
+- [bytes, Tuple[str, int], asyncio.DatagramTransport], Coroutine[Any, Any, None]
++ [bytes, tuple[str, int], asyncio.DatagramTransport], Coroutine[Any, Any, None]
+ ]
+
+
+@@ -75,7 +63,7 @@ class _AsyncUdpHandler(asyncio.DatagramProtocol):
+ self,
+ handler: _UdpHandler,
+ ) -> None:
+- self._transport: Optional[asyncio.DatagramTransport] = None
++ self._transport: asyncio.DatagramTransport | None = None
+ self._handler: _UdpHandler = handler
+
+ def connection_made(self, transport: asyncio.BaseTransport) -> None:
+@@ -84,7 +72,7 @@ class _AsyncUdpHandler(asyncio.DatagramProtocol):
+ """
+ self._transport = cast(asyncio.DatagramTransport, transport)
+
+- def datagram_received(self, data: bytes, addr: Tuple[str, int]) -> None:
++ def datagram_received(self, data: bytes, addr: tuple[str, int]) -> None:
+ """
+ Called by asyncio when a datagram is received.
+ """
+@@ -109,9 +97,9 @@ class AsyncServer:
+
+ def __init__(
+ self,
+- udp_handler: Optional[_UdpHandler],
+- tcp_handler: Optional[_TcpHandler],
+- pidfile: Optional[str] = None,
++ udp_handler: _UdpHandler | None,
++ tcp_handler: _TcpHandler | None,
++ pidfile: str | None = None,
+ ) -> None:
+ self._abort_if_on_dnspython_version_less_than_2_0_0()
+ logging.basicConfig(
+@@ -134,12 +122,12 @@ class AsyncServer:
+ logging.info("Setting up IPv4 listener at %s:%d", ipv4_address, port)
+ logging.info("Setting up IPv6 listener at [%s]:%d", ipv6_address, port)
+
+- self._ip_addresses: Tuple[str, str] = (ipv4_address, ipv6_address)
++ self._ip_addresses: tuple[str, str] = (ipv4_address, ipv6_address)
+ self._port: int = port
+- self._udp_handler: Optional[_UdpHandler] = udp_handler
+- self._tcp_handler: Optional[_TcpHandler] = tcp_handler
+- self._pidfile: Optional[str] = pidfile
+- self._work_done: Optional[asyncio.Future] = None
++ self._udp_handler: _UdpHandler | None = udp_handler
++ self._tcp_handler: _TcpHandler | None = tcp_handler
++ self._pidfile: str | None = pidfile
++ self._work_done: asyncio.Future | None = None
+
+ @classmethod
+ def _abort_if_on_dnspython_version_less_than_2_0_0(cls) -> None:
+@@ -195,7 +183,7 @@ class AsyncServer:
+ loop.set_exception_handler(self._handle_exception)
+
+ def _handle_exception(
+- self, _: asyncio.AbstractEventLoop, context: Dict[str, Any]
++ self, _: asyncio.AbstractEventLoop, context: dict[str, Any]
+ ) -> None:
+ assert self._work_done
+ exception = context.get("exception", RuntimeError(context["message"]))
+@@ -275,17 +263,16 @@ class QueryContext:
+
+ query: dns.message.Message
+ response: dns.message.Message
++ socket: Peer
+ peer: Peer
+ protocol: DnsProtocol
+- zone: Optional[dns.zone.Zone] = field(default=None, init=False)
+- soa: Optional[dns.rrset.RRset] = field(default=None, init=False)
+- node: Optional[dns.node.Node] = field(default=None, init=False)
+- answer: Optional[dns.rdataset.Rdataset] = field(default=None, init=False)
+- alias: Optional[dns.name.Name] = field(default=None, init=False)
+- _initialized_response: Optional[dns.message.Message] = field(
+- default=None, init=False
+- )
+- _initialized_response_with_zone_data: Optional[dns.message.Message] = field(
++ zone: dns.zone.Zone | None = field(default=None, init=False)
++ soa: dns.rrset.RRset | None = field(default=None, init=False)
++ node: dns.node.Node | None = field(default=None, init=False)
++ answer: dns.rdataset.Rdataset | None = field(default=None, init=False)
++ alias: dns.name.Name | None = field(default=None, init=False)
++ _initialized_response: dns.message.Message | None = field(default=None, init=False)
++ _initialized_response_with_zone_data: dns.message.Message | None = field(
+ default=None, init=False
+ )
+
+@@ -330,7 +317,7 @@ class ResponseAction(abc.ABC):
+ """
+
+ @abc.abstractmethod
+- async def perform(self) -> Optional[Union[dns.message.Message, bytes]]:
++ async def perform(self) -> dns.message.Message | bytes | None:
+ """
+ This method is expected to carry out arbitrary actions (e.g. wait for a
+ specific amount of time, modify the answer, etc.) and then return the
+@@ -353,14 +340,30 @@ class DnsResponseSend(ResponseAction):
+ """
+
+ response: dns.message.Message
+- authoritative: Optional[bool] = None
++ authoritative: bool | None = None
+ delay: float = 0.0
++ acknowledge_hand_rolled_response: bool = False
+
+- async def perform(self) -> Optional[Union[dns.message.Message, bytes]]:
++ async def perform(self) -> dns.message.Message | bytes | None:
+ """
+ Yield a potentially delayed response that is a dns.message.Message.
+ """
+ assert isinstance(self.response, dns.message.Message)
++ if not (
++ _is_asyncserver_response(self.response)
++ or self.acknowledge_hand_rolled_response
++ ):
++ error = "The response you are trying to send was not created using "
++ error += "AsyncDnsServer's response preparation methods. "
++ error += "This will break features such as automatic AA flag "
++ error += "and RCODE handling. If you need a fresh copy of a "
++ error += "response, use `QueryContext.prepare_new_response` "
++ error += "instead of `dns.message.make_response`. "
++ error += "To acknowledge this and proceed anyway, set "
++ error += "`acknowledge_hand_rolled_response=True` in "
++ error += "DnsResponseSend's constructor."
++ raise RuntimeError(error)
++
+ if self.authoritative is not None:
+ if self.authoritative:
+ self.response.flags |= dns.flags.AA
+@@ -387,7 +390,7 @@ class BytesResponseSend(ResponseAction):
+ response: bytes
+ delay: float = 0.0
+
+- async def perform(self) -> Optional[Union[dns.message.Message, bytes]]:
++ async def perform(self) -> dns.message.Message | bytes | None:
+ """
+ Yield a potentially delayed response that is a sequence of bytes.
+ """
+@@ -404,7 +407,7 @@ class ResponseDrop(ResponseAction):
+ Action which does nothing - as if a packet was dropped.
+ """
+
+- async def perform(self) -> Optional[Union[dns.message.Message, bytes]]:
++ async def perform(self) -> dns.message.Message | bytes | None:
+ return None
+
+
+@@ -413,17 +416,16 @@ class _ConnectionTeardownRequested(Exception):
+
+
+ @dataclass
+-class ResponseDropAndCloseConnection(ResponseAction):
++class CloseConnection(ResponseAction):
+ """
+- Action which makes the server close the connection after the DNS query is
+- received by the server (TCP only).
++ Action which makes the server close the connection (TCP only).
+
+ The connection may be closed with a delay if requested.
+ """
+
+ delay: float = 0.0
+
+- async def perform(self) -> Optional[Union[dns.message.Message, bytes]]:
++ async def perform(self) -> dns.message.Message | bytes | None:
+ if self.delay > 0:
+ logging.info("Waiting %.1fs before closing TCP connection", self.delay)
+ await asyncio.sleep(self.delay)
+@@ -505,7 +507,7 @@ class IgnoreAllConnections(ConnectionHandler):
+ client socket, effectively ignoring all incoming connections.
+ """
+
+- _connections: Set[asyncio.StreamWriter] = field(default_factory=set)
++ _connections: set[asyncio.StreamWriter] = field(default_factory=set)
+
+ async def handle(
+ self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter, peer: Peer
+@@ -539,8 +541,8 @@ class ConnectionReset(ConnectionHandler):
+ make the server send an RST segment; this happens when the server closes a
+ client's socket while there is still unread data in that socket's buffer.
+ If closing the connection _after_ the query is read by the server is enough
+- for a given use case, the ResponseDropAndCloseConnection response handler
+- should be used instead.
++ for a given use case, the CloseConnection response handler should be used
++ instead.
+ """
+
+ delay: float = 0.0
+@@ -616,14 +618,14 @@ class QnameHandler(ResponseHandler):
+
+ @property
+ @abc.abstractmethod
+- def qnames(self) -> List[str]:
++ def qnames(self) -> list[str]:
+ """
+ A list of QNAMEs handled by this class.
+ """
+ raise NotImplementedError
+
+ def __init__(self) -> None:
+- self._qnames: List[dns.name.Name] = [dns.name.from_text(d) for d in self.qnames]
++ self._qnames: list[dns.name.Name] = [dns.name.from_text(d) for d in self.qnames]
+
+ def __str__(self) -> str:
+ return f"{self.__class__.__name__}(QNAMEs: {', '.join(self.qnames)})"
+@@ -636,6 +638,105 @@ class QnameHandler(ResponseHandler):
+ return qctx.qname in self._qnames
+
+
++class QnameQtypeHandler(QnameHandler):
++ """
++ Handle queries for which both of the following conditions are true:
++
++ - the query's QNAME is present in `self.qnames`,
++ - the query's QTYPE is present in `self.qtypes`.
++ """
++
++ @property
++ @abc.abstractmethod
++ def qtypes(self) -> list[dns.rdatatype.RdataType]:
++ """
++ A list of QTYPEs handled by this class.
++ """
++ raise NotImplementedError
++
++ def __init__(self) -> None:
++ super().__init__()
++ self._qtypes: list[dns.rdatatype.RdataType] = self.qtypes
++
++ def __str__(self) -> str:
++ return f"{self.__class__.__name__}(QNAMEs: {', '.join(self.qnames)}; QTYPEs: {', '.join(map(str, self.qtypes))})"
++
++ def match(self, qctx: QueryContext) -> bool:
++ """
++ Handle queries whose QNAME and QTYPE match any of the QNAMEs and
++ QTYPEs handled by this class.
++ """
++ return qctx.qtype in self._qtypes and super().match(qctx)
++
++
++class StaticResponseHandler(ResponseHandler):
++ """
++ Base class used for deriving custom static response handlers.
++
++ The derived class can specify the RRsets to be included in the answer,
++ authority, and additional sections of the response, whether to set the AA
++ bit in the response, and a delay before sending the response.
++
++ The default implementation of `get_responses()` uses these properties to
++ prepare and yield a single response.
++ """
++
++ @property
++ def rcode(self) -> dns.rcode.Rcode | None:
++ """
++ Optional RCODE to be set in the response.
++ """
++ return None
++
++ @property
++ def answer(self) -> Sequence[dns.rrset.RRset]:
++ """
++ RRsets to be included in the answer section of the response.
++ """
++ return []
++
++ @property
++ def authority(self) -> Sequence[dns.rrset.RRset]:
++ """
++ RRsets to be included in the authority section of the response.
++ """
++ return []
++
++ @property
++ def additional(self) -> Sequence[dns.rrset.RRset]:
++ """
++ RRsets to be included in the additional section of the response.
++ """
++ return []
++
++ @property
++ def authoritative(self) -> bool | None:
++ """
++ Whether to set the AA bit in the response.
++ """
++ return None
++
++ @property
++ def delay(self) -> float:
++ """
++ Delay before sending the response.
++ """
++ return 0.0
++
++ async def get_responses(
++ self, qctx: QueryContext
++ ) -> AsyncGenerator[DnsResponseSend, None]:
++ qctx.prepare_new_response(with_zone_data=False)
++ qctx.response.answer.extend(self.answer)
++ qctx.response.authority.extend(self.authority)
++ qctx.response.additional.extend(self.additional)
++ if self.rcode is not None:
++ qctx.response.set_rcode(self.rcode)
++ yield DnsResponseSend(
++ qctx.response, authoritative=self.authoritative, delay=self.delay
++ )
++
++
+ class DomainHandler(ResponseHandler):
+ """
+ Base class used for deriving custom domain handlers.
+@@ -643,20 +744,28 @@ class DomainHandler(ResponseHandler):
+ The derived class must specify a list of `domains` that it wants to handle.
+ Queries for any of these domains (and their subdomains) will then be passed
+ to the `get_response()` method in the derived class.
++
++ The most specific matching domain is stored in the `matched_domain` attribute.
+ """
+
+ @property
+ @abc.abstractmethod
+- def domains(self) -> List[str]:
++ def domains(self) -> list[str]:
+ """
+ A list of domain names handled by this class.
+ """
+ raise NotImplementedError
+
+ def __init__(self) -> None:
+- self._domains: List[dns.name.Name] = [
+- dns.name.from_text(d) for d in self.domains
+- ]
++ self._domains: list[dns.name.Name] = sorted(
++ [dns.name.from_text(d) for d in self.domains], reverse=True
++ )
++ self._matched_domain: dns.name.Name | None = None
++
++ @property
++ def matched_domain(self) -> dns.name.Name:
++ assert self._matched_domain is not None
++ return self._matched_domain
+
+ def __str__(self) -> str:
+ return f"{self.__class__.__name__}(domains: {', '.join(self.domains)})"
+@@ -666,20 +775,124 @@ class DomainHandler(ResponseHandler):
+ Handle queries whose QNAME matches any of the domains handled by this
+ class.
+ """
++ self._matched_domain = None
+ for domain in self._domains:
+ if qctx.qname.is_subdomain(domain):
++ self._matched_domain = domain
+ return True
+ return False
+
+
++class ForwarderHandler(ResponseHandler):
++ """
++ A handler forwarding all received queries to another DNS server with an
++ optional delay and then relaying the responses back to the original client.
++
++ Queries are currently always forwarded via UDP.
++ """
++
++ @property
++ @abc.abstractmethod
++ def target(self) -> str:
++ """
++ The address of the DNS server to forward queries to.
++ """
++ raise NotImplementedError
++
++ @property
++ def port(self) -> int:
++ """
++ The port of the DNS server to forward queries to.
++
++ The default value of 0 causes the same port as the one used by this
++ server for listening to be used.
++ """
++ return 0
++
++ @property
++ def delay(self) -> float:
++ """
++ The number of seconds to wait before forwarding each query.
++ """
++ return 0.0
++
++ def __str__(self) -> str:
++ return f"{self.__class__.__name__}(target: {self.target}:{self.port})"
++
++ class ForwarderProtocol(asyncio.DatagramProtocol):
++ def __init__(self, query: bytes, response: asyncio.Future) -> None:
++ self._query = query
++ self._response = response
++
++ def connection_made(self, transport: asyncio.BaseTransport) -> None:
++ logging.debug("[OUT] %s", self._query.hex())
++ cast(asyncio.DatagramTransport, transport).sendto(self._query)
++
++ def datagram_received(self, data: bytes, _: tuple[str, int]) -> None:
++ logging.debug("[IN] %s", data.hex())
++ self._response.set_result(data)
++
++ async def get_responses(
++ self, qctx: QueryContext
++ ) -> AsyncGenerator[ResponseAction, None]:
++ loop = asyncio.get_running_loop()
++ response = loop.create_future()
++ forwarding_target = f"{self.target}:{self.port or qctx.socket.port}"
++
++ if self.delay > 0:
++ logging.info(
++ "Waiting %.1fs before forwarding %s query from %s to %s over UDP",
++ self.delay,
++ qctx.protocol.name,
++ qctx.peer,
++ forwarding_target,
++ )
++ await asyncio.sleep(self.delay)
++
++ logging.info(
++ "Forwarding %s query from %s to %s over UDP",
++ qctx.protocol.name,
++ qctx.peer,
++ forwarding_target,
++ )
++
++ transport, _ = await loop.create_datagram_endpoint(
++ lambda: self.ForwarderProtocol(qctx.query.to_wire(), response),
++ local_addr=(qctx.socket.host, 0),
++ remote_addr=(self.target, self.port or qctx.socket.port),
++ )
++
++ try:
++ await response
++ finally:
++ transport.close()
++
++ logging.info(
++ "Relaying UDP response from %s to %s over %s",
++ forwarding_target,
++ qctx.peer,
++ qctx.protocol.name,
++ )
++
++ try:
++ message = _DnsMessageWithTsigDisabled.from_wire(response.result())
++ yield DnsResponseSend(message, acknowledge_hand_rolled_response=True)
++ except dns.exception.DNSException:
++ logging.warning(
++ "Failed to parse response from %s as a DNS message, relaying it as raw bytes",
++ forwarding_target,
++ )
++ yield BytesResponseSend(response.result())
++
++
+ @dataclass
+ class _ZoneTreeNode:
+ """
+ A node representing a zone with one origin.
+ """
+
+- zone: Optional[dns.zone.Zone]
+- children: List["_ZoneTreeNode"] = field(default_factory=list)
++ zone: dns.zone.Zone | None
++ children: list["_ZoneTreeNode"] = field(default_factory=list)
+
+
+ class _ZoneTree:
+@@ -729,7 +942,7 @@ class _ZoneTree:
+ node_from.children.remove(child)
+ node_to.children.append(child)
+
+- def find_best_zone(self, name: dns.name.Name) -> Optional[dns.zone.Zone]:
++ def find_best_zone(self, name: dns.name.Name) -> dns.zone.Zone | None:
+ """
+ Return the closest matching zone (if any) for the domain name.
+ """
+@@ -747,7 +960,7 @@ class _DnsMessageWithTsigDisabled(dns.message.Message):
+ """
+
+ class _DisableTsigHandling(contextlib.ContextDecorator):
+- def __init__(self, message: Optional[dns.message.Message] = None) -> None:
++ def __init__(self, message: dns.message.Message | None = None) -> None:
+ self.original_tsig_sign = dns.tsig.sign
+ self.original_tsig_validate = dns.tsig.validate
+ if message:
+@@ -759,7 +972,7 @@ class _DnsMessageWithTsigDisabled(dns.message.Message):
+ from failing on messages initialized with `dns.message.from_wire(keyring=False)`.
+ """
+
+- def sign(*_: Any, **__: Any) -> Tuple[dns.rdata.Rdata, None]:
++ def sign(*_: Any, **__: Any) -> tuple[dns.rdata.Rdata, None]:
+ assert self.tsig
+ return self.tsig[0], None
+
+@@ -802,6 +1015,19 @@ class _NoKeyringType:
+ pass
+
+
++_ASYNCSERVER_RESPONSE_MARKER = "__is_asyncserver_response__"
++
++
++def _make_asyncserver_response(query: dns.message.Message) -> dns.message.Message:
++ response = dns.message.make_response(query)
++ setattr(response, _ASYNCSERVER_RESPONSE_MARKER, True)
++ return response
++
++
++def _is_asyncserver_response(message: dns.message.Message) -> bool:
++ return getattr(message, _ASYNCSERVER_RESPONSE_MARKER, False)
++
++
+ class AsyncDnsServer(AsyncServer):
+ """
+ DNS server which responds to queries based on zone data and/or custom
+@@ -822,17 +1048,17 @@ class AsyncDnsServer(AsyncServer):
+ self,
+ /,
+ default_rcode: dns.rcode.Rcode = dns.rcode.REFUSED,
+- default_aa: bool = True,
+- keyring: Union[
+- Dict[dns.name.Name, dns.tsig.Key], None, _NoKeyringType
+- ] = _NoKeyringType(),
++ default_aa: bool = False,
++ keyring: (
++ dict[dns.name.Name, dns.tsig.Key] | None | _NoKeyringType
++ ) = _NoKeyringType(),
+ acknowledge_manual_dname_handling: bool = False,
+ ) -> None:
+ super().__init__(self._handle_udp, self._handle_tcp, "ans.pid")
+
+ self._zone_tree: _ZoneTree = _ZoneTree()
+- self._connection_handler: Optional[ConnectionHandler] = None
+- self._response_handlers: List[ResponseHandler] = []
++ self._connection_handler: ConnectionHandler | None = None
++ self._response_handlers: list[ResponseHandler] = []
+ self._default_rcode = default_rcode
+ self._default_aa = default_aa
+ self._keyring = keyring
+@@ -859,10 +1085,18 @@ class AsyncDnsServer(AsyncServer):
+ else:
+ self._response_handlers.append(handler)
+
+- def install_response_handlers(self, handlers: List[ResponseHandler]) -> None:
++ def install_response_handlers(self, *handlers: ResponseHandler) -> None:
+ for handler in handlers:
+ self.install_response_handler(handler)
+
++ def replace_response_handlers(self, *new_handlers: ResponseHandler) -> None:
++ """
++ Uninstall all currently installed handlers and install the provided ones.
++ """
++ logging.info("Uninstalling response handlers: %s", str(self._response_handlers))
++ self._response_handlers.clear()
++ self.install_response_handlers(*new_handlers)
++
+ def uninstall_response_handler(self, handler: ResponseHandler) -> None:
+ """
+ Remove the specified handler from the list of response handlers.
+@@ -933,11 +1167,13 @@ class AsyncDnsServer(AsyncServer):
+ raise ValueError(error)
+
+ async def _handle_udp(
+- self, wire: bytes, addr: Tuple[str, int], transport: asyncio.DatagramTransport
++ self, wire: bytes, addr: tuple[str, int], transport: asyncio.DatagramTransport
+ ) -> None:
+ logging.debug("Received UDP message: %s", wire.hex())
++ socket_info = transport.get_extra_info("sockname")
++ socket = Peer(socket_info[0], socket_info[1])
+ peer = Peer(addr[0], addr[1])
+- responses = self._handle_query(wire, peer, DnsProtocol.UDP)
++ responses = self._handle_query(wire, socket, peer, DnsProtocol.UDP)
+ async for response in responses:
+ logging.debug("Sending UDP message: %s", response.hex())
+ transport.sendto(response, addr)
+@@ -974,7 +1210,7 @@ class AsyncDnsServer(AsyncServer):
+
+ async def _read_tcp_query(
+ self, reader: asyncio.StreamReader, peer: Peer
+- ) -> Optional[bytes]:
++ ) -> bytes | None:
+ wire_length = await self._read_tcp_query_wire_length(reader, peer)
+ if not wire_length:
+ return None
+@@ -983,7 +1219,7 @@ class AsyncDnsServer(AsyncServer):
+
+ async def _read_tcp_query_wire_length(
+ self, reader: asyncio.StreamReader, peer: Peer
+- ) -> Optional[int]:
++ ) -> int | None:
+ logging.debug("Receiving TCP message length from %s...", peer)
+
+ wire_length_bytes = await self._read_tcp_octets(reader, peer, 2)
+@@ -996,7 +1232,7 @@ class AsyncDnsServer(AsyncServer):
+
+ async def _read_tcp_query_wire(
+ self, reader: asyncio.StreamReader, peer: Peer, wire_length: int
+- ) -> Optional[bytes]:
++ ) -> bytes | None:
+ logging.debug("Receiving TCP message (%d octets) from %s...", wire_length, peer)
+
+ wire = await self._read_tcp_octets(reader, peer, wire_length)
+@@ -1009,7 +1245,7 @@ class AsyncDnsServer(AsyncServer):
+
+ async def _read_tcp_octets(
+ self, reader: asyncio.StreamReader, peer: Peer, expected: int
+- ) -> Optional[bytes]:
++ ) -> bytes | None:
+ buffer = b""
+
+ while len(buffer) < expected:
+@@ -1034,39 +1270,39 @@ class AsyncDnsServer(AsyncServer):
+ async def _send_tcp_response(
+ self, writer: asyncio.StreamWriter, peer: Peer, wire: bytes
+ ) -> None:
+- responses = self._handle_query(wire, peer, DnsProtocol.TCP)
++ socket_info = writer.get_extra_info("sockname")
++ socket = Peer(socket_info[0], socket_info[1])
++ responses = self._handle_query(wire, socket, peer, DnsProtocol.TCP)
+ async for response in responses:
+ logging.debug("Sending TCP response: %s", response.hex())
+ writer.write(response)
+ await writer.drain()
+
+- def _log_query(self, qctx: QueryContext, peer: Peer, protocol: DnsProtocol) -> None:
++ def _log_query(self, qctx: QueryContext) -> None:
+ logging.info(
+- "Received %s/%s/%s (ID=%d) query from %s (%s)",
++ "Received %s/%s/%s (ID=%d) query from %s on %s (%s)",
+ qctx.qname.to_text(omit_final_dot=True),
+ dns.rdataclass.to_text(qctx.qclass),
+ dns.rdatatype.to_text(qctx.qtype),
+ qctx.query.id,
+- peer,
+- protocol.name,
++ qctx.peer,
++ qctx.socket,
++ qctx.protocol.name,
+ )
+ logging.debug(
+ "\n".join([f"[IN] {l}" for l in [""] + str(qctx.query).splitlines()])
+ )
+
+ def _log_response(
+- self,
+- qctx: QueryContext,
+- response: Optional[Union[dns.message.Message, bytes]],
+- peer: Peer,
+- protocol: DnsProtocol,
++ self, qctx: QueryContext, response: dns.message.Message | bytes | None
+ ) -> None:
+ if not response:
+ logging.info(
+- "Not sending a response to query (ID=%d) from %s (%s)",
++ "Not sending a response to query (ID=%d) from %s on %s (%s)",
+ qctx.query.id,
+- peer,
+- protocol.name,
++ qctx.peer,
++ qctx.socket,
++ qctx.protocol.name,
+ )
+ return
+
+@@ -1081,7 +1317,7 @@ class AsyncDnsServer(AsyncServer):
+ qtype = "-"
+
+ logging.info(
+- "Sending %s/%s/%s (ID=%d) response (%d/%d/%d/%d) to a query (ID=%d) from %s (%s)",
++ "Sending %s/%s/%s (ID=%d) response (%d/%d/%d/%d) to a query (ID=%d) from %s on %s (%s)",
+ qname,
+ qclass,
+ qtype,
+@@ -1091,8 +1327,9 @@ class AsyncDnsServer(AsyncServer):
+ len(response.authority),
+ len(response.additional),
+ qctx.query.id,
+- peer,
+- protocol.name,
++ qctx.peer,
++ qctx.socket,
++ qctx.protocol.name,
+ )
+ logging.debug(
+ "\n".join([f"[OUT] {l}" for l in [""] + str(response).splitlines()])
+@@ -1100,16 +1337,17 @@ class AsyncDnsServer(AsyncServer):
+ return
+
+ logging.info(
+- "Sending response (%d bytes) to a query (ID=%d) from %s (%s)",
++ "Sending response (%d bytes) to a query (ID=%d) from %s on %s (%s)",
+ len(response),
+ qctx.query.id,
+- peer,
+- protocol.name,
++ qctx.peer,
++ qctx.socket,
++ qctx.protocol.name,
+ )
+ logging.debug("[OUT] %s", response.hex())
+
+ async def _handle_query(
+- self, wire: bytes, peer: Peer, protocol: DnsProtocol
++ self, wire: bytes, socket: Peer, peer: Peer, protocol: DnsProtocol
+ ) -> AsyncGenerator[bytes, None]:
+ """
+ Yield wire data to send as a response over the established transport.
+@@ -1119,12 +1357,12 @@ class AsyncDnsServer(AsyncServer):
+ except dns.exception.DNSException as exc:
+ logging.error("Invalid query from %s (%s): %s", peer, wire.hex(), exc)
+ return
+- response_stub = dns.message.make_response(query)
+- qctx = QueryContext(query, response_stub, peer, protocol)
+- self._log_query(qctx, peer, protocol)
++ response_stub = _make_asyncserver_response(query)
++ qctx = QueryContext(query, response_stub, socket, peer, protocol)
++ self._log_query(qctx)
+ responses = self._prepare_responses(qctx)
+ async for response in responses:
+- self._log_response(qctx, response, peer, protocol)
++ self._log_response(qctx, response)
+ if response:
+ if isinstance(response, dns.message.Message):
+ response = response.to_wire(max_size=65535)
+@@ -1156,7 +1394,7 @@ class AsyncDnsServer(AsyncServer):
+
+ async def _prepare_responses(
+ self, qctx: QueryContext
+- ) -> AsyncGenerator[Optional[Union[dns.message.Message, bytes]], None]:
++ ) -> AsyncGenerator[dns.message.Message | bytes | None, None]:
+ """
+ Yield response(s) either from response handlers or zone data.
+ """
+@@ -1349,10 +1587,10 @@ class ControllableAsyncDnsServer(AsyncDnsServer):
+ return dns.name.from_text(self._CONTROL_DOMAIN)
+
+ @functools.cached_property
+- def _commands(self) -> Dict[dns.name.Name, "ControlCommand"]:
++ def _commands(self) -> dict[dns.name.Name, "ControlCommand"]:
+ return {}
+
+- def install_control_commands(self, commands: List["ControlCommand"]) -> None:
++ def install_control_commands(self, *commands: "ControlCommand") -> None:
+ for command in commands:
+ self.install_control_command(command)
+
+@@ -1370,7 +1608,7 @@ class ControllableAsyncDnsServer(AsyncDnsServer):
+
+ async def _prepare_responses(
+ self, qctx: QueryContext
+- ) -> AsyncGenerator[Optional[Union[dns.message.Message, bytes]], None]:
++ ) -> AsyncGenerator[dns.message.Message | bytes | None, None]:
+ """
+ Detect and handle control queries, falling back to normal processing
+ for non-control queries.
+@@ -1383,9 +1621,7 @@ class ControllableAsyncDnsServer(AsyncDnsServer):
+ async for response in super()._prepare_responses(qctx):
+ yield response
+
+- def _handle_control_command(
+- self, qctx: QueryContext
+- ) -> Optional[dns.message.Message]:
++ def _handle_control_command(self, qctx: QueryContext) -> dns.message.Message | None:
+ """
+ Detect and handle control queries.
+
+@@ -1460,8 +1696,8 @@ class ControlCommand(abc.ABC):
+
+ @abc.abstractmethod
+ def handle(
+- self, args: List[str], server: ControllableAsyncDnsServer, qctx: QueryContext
+- ) -> Optional[str]:
++ self, args: list[str], server: ControllableAsyncDnsServer, qctx: QueryContext
++ ) -> str | None:
+ """
+ This method is expected to carry out arbitrary actions in response to a
+ control query. Note that it is invoked synchronously (it is not a
+@@ -1499,11 +1735,11 @@ class ToggleResponsesCommand(ControlCommand):
+ control_subdomain = "send-responses"
+
+ def __init__(self) -> None:
+- self._current_handler: Optional[IgnoreAllQueries] = None
++ self._current_handler: IgnoreAllQueries | None = None
+
+ def handle(
+- self, args: List[str], server: ControllableAsyncDnsServer, qctx: QueryContext
+- ) -> Optional[str]:
++ self, args: list[str], server: ControllableAsyncDnsServer, qctx: QueryContext
++ ) -> str | None:
+ if len(args) != 1:
+ logging.error("Invalid %s query %s", self, qctx.qname)
+ qctx.response.set_rcode(dns.rcode.SERVFAIL)
+@@ -1528,3 +1764,30 @@ class ToggleResponsesCommand(ControlCommand):
+ logging.error("Unrecognized response sending mode '%s'", mode)
+ qctx.response.set_rcode(dns.rcode.SERVFAIL)
+ return f"unrecognized response sending mode '{mode}'"
++
++
++class SwitchControlCommand(ControlCommand):
++ """
++ Switch the server's response handlers based on the control query.
++
++ A sequence of response handlers is associated with each key. When a
++ control query is received, the server's response handlers are replaced
++ with the sequence associated with the key extracted from the control
++ query.
++ """
++
++ control_subdomain = "switch"
++
++ def __init__(self, handler_mapping: dict[str, Sequence[ResponseHandler]]):
++ self._handler_mapping = handler_mapping
++
++ def handle(
++ self, args: list[str], server: ControllableAsyncDnsServer, qctx: QueryContext
++ ) -> str | None:
++ if len(args) != 1 or args[0] not in self._handler_mapping:
++ logging.error("Invalid %s query %s", self, qctx.qname)
++ qctx.response.set_rcode(dns.rcode.SERVFAIL)
++ return f"invalid query; exactly one of {list(self._handler_mapping.keys())} is expected in QNAME"
++
++ server.replace_response_handlers(*self._handler_mapping[args[0]])
++ return f"switched to handler set '{args[0]}'"
+--
+2.35.6
+
diff --git a/meta/recipes-connectivity/bind/bind/CVE-2026-3592_p5.patch b/meta/recipes-connectivity/bind/bind/CVE-2026-3592_p5.patch
new file mode 100644
index 0000000000..bfb870851a
--- /dev/null
+++ b/meta/recipes-connectivity/bind/bind/CVE-2026-3592_p5.patch
@@ -0,0 +1,554 @@
+From 1e0b72f5cee9fce3c4beb782fd5e4a17efa84223 Mon Sep 17 00:00:00 2001
+From: Colin Vidal <colin@isc.org>
+Date: Wed, 4 Mar 2026 18:25:32 +0100
+Subject: [PATCH] Add SRTT-based server selection system test
+
+Verify that the resolver selects authoritative servers in increasing
+SRTT order. Four servers are configured with increasing response
+delays. 100 queries are sent, expecting most to go to the fastest
+server (ns2). Then ns2 stops responding, another 100 queries are
+sent and should go to ns3 (the next fastest), and so on through
+ns4 and ns5. Each query uses a unique name to avoid cache hits.
+
+CVE: CVE-2026-3592
+Upstream-Status: Backport [https://gitlab.com/isc-projects/bind9/-/commit/d5cd9b71ebadf7c0c76f09c5bbb65b6a7b944d0d]
+
+(cherry picked from commit a8d11e14f5b4e4d53219ba751d1b741162b0b84b)
+(cherry picked from commit d5cd9b71ebadf7c0c76f09c5bbb65b6a7b944d0d)
+Signed-off-by: Ashishkumar Parmar <asparmar@cisco.com>
+---
+ bin/tests/system/srtt/README | 18 ++++++
+ bin/tests/system/srtt/ans2/ans.py | 36 +++++++++++
+ bin/tests/system/srtt/ans3/ans.py | 36 +++++++++++
+ bin/tests/system/srtt/ans4/ans.py | 36 +++++++++++
+ bin/tests/system/srtt/ans5/ans.py | 36 +++++++++++
+ bin/tests/system/srtt/ns1/named.conf.j2 | 29 +++++++++
+ bin/tests/system/srtt/ns1/root.db | 36 +++++++++++
+ bin/tests/system/srtt/ns6/named.args | 1 +
+ bin/tests/system/srtt/ns6/named.conf.j2 | 41 ++++++++++++
+ bin/tests/system/srtt/prereq.sh | 20 ++++++
+ bin/tests/system/srtt/srtt_ans.py | 59 +++++++++++++++++
+ bin/tests/system/srtt/tests_srtt.py | 86 +++++++++++++++++++++++++
+ 12 files changed, 434 insertions(+)
+ create mode 100644 bin/tests/system/srtt/README
+ create mode 100644 bin/tests/system/srtt/ans2/ans.py
+ create mode 100644 bin/tests/system/srtt/ans3/ans.py
+ create mode 100644 bin/tests/system/srtt/ans4/ans.py
+ create mode 100644 bin/tests/system/srtt/ans5/ans.py
+ create mode 100644 bin/tests/system/srtt/ns1/named.conf.j2
+ create mode 100644 bin/tests/system/srtt/ns1/root.db
+ create mode 100644 bin/tests/system/srtt/ns6/named.args
+ create mode 100644 bin/tests/system/srtt/ns6/named.conf.j2
+ create mode 100644 bin/tests/system/srtt/prereq.sh
+ create mode 100644 bin/tests/system/srtt/srtt_ans.py
+ create mode 100644 bin/tests/system/srtt/tests_srtt.py
+
+diff --git a/bin/tests/system/srtt/README b/bin/tests/system/srtt/README
+new file mode 100644
+index 0000000000..c86a697931
+--- /dev/null
++++ b/bin/tests/system/srtt/README
+@@ -0,0 +1,18 @@
++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.
++
++ns1 is root
++
++ans{2-5} simulates four NS servers making authority on the same domain
++`example.`. ans2 is the quickest to answer, followed by ans3, then ans4, with
++ans5 being the slowest.
++
++ns6 is a resolver
+diff --git a/bin/tests/system/srtt/ans2/ans.py b/bin/tests/system/srtt/ans2/ans.py
+new file mode 100644
+index 0000000000..f7c6f8e71b
+--- /dev/null
++++ b/bin/tests/system/srtt/ans2/ans.py
+@@ -0,0 +1,36 @@
++"""
++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.rcode
++
++from isctest.asyncserver import AsyncDnsServer, IgnoreAllQueries
++
++from srtt_ans import DelayedQnameRangeHandler
++
++
++class Foo1ToFoo99Handler(DelayedQnameRangeHandler):
++ max_qname = 99
++ delay = 0.0
++
++
++def main() -> None:
++ server = AsyncDnsServer(default_aa=True, default_rcode=dns.rcode.NOERROR)
++ server.install_response_handlers(
++ Foo1ToFoo99Handler(),
++ IgnoreAllQueries(),
++ )
++ server.run()
++
++
++if __name__ == "__main__":
++ main()
+diff --git a/bin/tests/system/srtt/ans3/ans.py b/bin/tests/system/srtt/ans3/ans.py
+new file mode 100644
+index 0000000000..5f61e19cd5
+--- /dev/null
++++ b/bin/tests/system/srtt/ans3/ans.py
+@@ -0,0 +1,36 @@
++"""
++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.rcode
++
++from isctest.asyncserver import AsyncDnsServer, IgnoreAllQueries
++
++from srtt_ans import DelayedQnameRangeHandler
++
++
++class Foo1ToFoo199Handler(DelayedQnameRangeHandler):
++ max_qname = 199
++ delay = 0.03
++
++
++def main() -> None:
++ server = AsyncDnsServer(default_aa=True, default_rcode=dns.rcode.NOERROR)
++ server.install_response_handlers(
++ Foo1ToFoo199Handler(),
++ IgnoreAllQueries(),
++ )
++ server.run()
++
++
++if __name__ == "__main__":
++ main()
+diff --git a/bin/tests/system/srtt/ans4/ans.py b/bin/tests/system/srtt/ans4/ans.py
+new file mode 100644
+index 0000000000..2e12b0ba7d
+--- /dev/null
++++ b/bin/tests/system/srtt/ans4/ans.py
+@@ -0,0 +1,36 @@
++"""
++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.rcode
++
++from isctest.asyncserver import AsyncDnsServer, IgnoreAllQueries
++
++from srtt_ans import DelayedQnameRangeHandler
++
++
++class Foo1ToFoo299Handler(DelayedQnameRangeHandler):
++ max_qname = 299
++ delay = 0.08
++
++
++def main() -> None:
++ server = AsyncDnsServer(default_aa=True, default_rcode=dns.rcode.NOERROR)
++ server.install_response_handlers(
++ Foo1ToFoo299Handler(),
++ IgnoreAllQueries(),
++ )
++ server.run()
++
++
++if __name__ == "__main__":
++ main()
+diff --git a/bin/tests/system/srtt/ans5/ans.py b/bin/tests/system/srtt/ans5/ans.py
+new file mode 100644
+index 0000000000..b40306908c
+--- /dev/null
++++ b/bin/tests/system/srtt/ans5/ans.py
+@@ -0,0 +1,36 @@
++"""
++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.rcode
++
++from isctest.asyncserver import AsyncDnsServer, IgnoreAllQueries
++
++from srtt_ans import DelayedQnameRangeHandler
++
++
++class Foo1ToFoo399Handler(DelayedQnameRangeHandler):
++ max_qname = 399
++ delay = 0.15
++
++
++def main() -> None:
++ server = AsyncDnsServer(default_aa=True, default_rcode=dns.rcode.NOERROR)
++ server.install_response_handlers(
++ Foo1ToFoo399Handler(),
++ IgnoreAllQueries(),
++ )
++ server.run()
++
++
++if __name__ == "__main__":
++ main()
+diff --git a/bin/tests/system/srtt/ns1/named.conf.j2 b/bin/tests/system/srtt/ns1/named.conf.j2
+new file mode 100644
+index 0000000000..eb079c95ab
+--- /dev/null
++++ b/bin/tests/system/srtt/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/srtt/ns1/root.db b/bin/tests/system/srtt/ns1/root.db
+new file mode 100644
+index 0000000000..29ecd1d89d
+--- /dev/null
++++ b/bin/tests/system/srtt/ns1/root.db
+@@ -0,0 +1,36 @@
++; 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 owner.root-servers.nil. 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
++
++; The idea is that the resolver would do 2 ADB lookups, so there would be 2
++; find list, both with 2 IPs in it. ns1 (which is actually ans2 and ans5) would
++; have both the slowest and fastest addresses. ns2 (which is actually ans3 and
++; ans4) would have two addresses in the middle.
++
++example. NS ns1.example.
++example. NS ns1.example.
++example. NS ns2.example.
++example. NS ns2.example.
++
++ns1.example. A 10.53.0.2 ; delay is 0
++ns1.example. A 10.53.0.5 ; delay is 0.15
++ns2.example. A 10.53.0.4 ; delay is 0.08
++ns2.example. A 10.53.0.3 ; delay is 0.03
+diff --git a/bin/tests/system/srtt/ns6/named.args b/bin/tests/system/srtt/ns6/named.args
+new file mode 100644
+index 0000000000..b5de5874ec
+--- /dev/null
++++ b/bin/tests/system/srtt/ns6/named.args
+@@ -0,0 +1 @@
++-D srtt-ns6 -m record -c named.conf -d 99 -g -T maxcachesize=2097152 -4
+diff --git a/bin/tests/system/srtt/ns6/named.conf.j2 b/bin/tests/system/srtt/ns6/named.conf.j2
+new file mode 100644
+index 0000000000..1d27505a8e
+--- /dev/null
++++ b/bin/tests/system/srtt/ns6/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.6;
++ notify-source 10.53.0.6;
++ transfer-source 10.53.0.6;
++ port @PORT@;
++ pid-file "named.pid";
++ listen-on { 10.53.0.6; };
++ listen-on-v6 { none; };
++ recursion yes;
++ dnssec-validation no;
++ dnstap { resolver query; };
++ dnstap-output file "dnstap.out";
++};
++
++key rndc_key {
++ secret "1234abcd8765";
++ algorithm @DEFAULT_HMAC@;
++};
++
++controls {
++ inet 10.53.0.6 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
++};
++
++zone "." {
++ type hint;
++ file "../../_common/root.hint";
++};
+diff --git a/bin/tests/system/srtt/prereq.sh b/bin/tests/system/srtt/prereq.sh
+new file mode 100644
+index 0000000000..747f448982
+--- /dev/null
++++ b/bin/tests/system/srtt/prereq.sh
+@@ -0,0 +1,20 @@
++#!/bin/sh
++
++# 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.
++
++. ../conf.sh
++
++$FEATURETEST --enable-dnstap || {
++ echo_i "This test requires dnstap support." >&2
++ exit 255
++}
++exit 0
+diff --git a/bin/tests/system/srtt/srtt_ans.py b/bin/tests/system/srtt/srtt_ans.py
+new file mode 100644
+index 0000000000..9387486993
+--- /dev/null
++++ b/bin/tests/system/srtt/srtt_ans.py
+@@ -0,0 +1,59 @@
++"""
++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 collections.abc import AsyncGenerator
++
++import abc
++
++import dns.rdataclass
++import dns.rdatatype
++import dns.rrset
++
++from isctest.asyncserver import DnsResponseSend, QnameQtypeHandler, QueryContext
++
++
++class DelayedQnameRangeHandler(QnameQtypeHandler):
++ """
++ Respond to queries for QNAMEs "foo1.example." through "foo<N>.example."
++ with QTYPE=A, where <N> must be defined by the subclass. Every response is
++ delayed by a fixed amount of time, which must also be defined (in seconds)
++ by the subclass.
++ """
++
++ @property
++ def qnames(self) -> list[str]:
++ return [f"foo{x}.example." for x in range(1, self.max_qname + 1)]
++
++ qtypes = [dns.rdatatype.A]
++
++ @property
++ @abc.abstractmethod
++ def max_qname(self) -> int:
++ raise NotImplementedError
++
++ @property
++ @abc.abstractmethod
++ def delay(self) -> float:
++ raise NotImplementedError
++
++ def __str__(self) -> str:
++ return f"{self.__class__.__name__}(foo[1-{self.max_qname}].example/A)"
++
++ async def get_responses(
++ self, qctx: QueryContext
++ ) -> AsyncGenerator[DnsResponseSend, None]:
++ a_rrset = dns.rrset.from_text(
++ qctx.qname, 300, dns.rdataclass.IN, dns.rdatatype.A, "10.53.9.9"
++ )
++ qctx.response.answer.append(a_rrset)
++ yield DnsResponseSend(qctx.response, delay=self.delay)
+diff --git a/bin/tests/system/srtt/tests_srtt.py b/bin/tests/system/srtt/tests_srtt.py
+new file mode 100644
+index 0000000000..55611922a7
+--- /dev/null
++++ b/bin/tests/system/srtt/tests_srtt.py
+@@ -0,0 +1,86 @@
++# 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 os
++
++import isctest
++
++
++def line_to_dst_ips(line):
++ # dnstap-read output line example
++ # 05-Feb-2026 11:00:57.853 RQ 10.53.0.6:38507 -> 10.53.0.3:22047 TCP 56b fooXXX.example./IN/NS
++ _, _, _, _, _, dst, _, _, _ = line.split(" ", 9)
++ ip, _ = dst.split(":", 1)
++ return ip
++
++
++def extract_dnstap(ns, nsid):
++ ns.rndc("dnstap -roll 1")
++ path = os.path.join(nsid, "dnstap.out.0")
++ dnstapread = isctest.run.cmd(
++ [os.getenv("DNSTAPREAD"), path],
++ )
++
++ lines = dnstapread.out.splitlines()
++ return map(line_to_dst_ips, lines)
++
++
++def assert_used_auth(ns, nsid, authip):
++ ips = extract_dnstap(ns, nsid)
++ queries = 0
++ matches = 0
++ for ip in ips:
++ queries += 1
++ if ip == authip:
++ matches += 1
++ assert matches > 85
++ assert queries <= 115
++
++
++def test_srtt(ns6):
++ for i in range(1, 100):
++ msg = isctest.query.create(f"foo{i}.example.", "A")
++ res = isctest.query.udp(msg, ns6.ip)
++ isctest.check.noerror(res)
++ assert len(res.answer[0]) == 1
++ res.answer[0].ttl = 300
++ assert str(res.answer[0]) == f"foo{i}.example. 300 IN A 10.53.9.9"
++
++ assert_used_auth(ns6, "ns6", "10.53.0.2")
++
++ for i in range(100, 200):
++ msg = isctest.query.create(f"foo{i}.example.", "A")
++ res = isctest.query.udp(msg, ns6.ip)
++ isctest.check.noerror(res)
++ assert len(res.answer[0]) == 1
++ res.answer[0].ttl = 300
++ assert str(res.answer[0]) == f"foo{i}.example. 300 IN A 10.53.9.9"
++
++ assert_used_auth(ns6, "ns6", "10.53.0.3")
++
++ for i in range(200, 300):
++ msg = isctest.query.create(f"foo{i}.example.", "A")
++ res = isctest.query.udp(msg, ns6.ip)
++ isctest.check.noerror(res)
++ assert len(res.answer[0]) == 1
++ res.answer[0].ttl = 300
++ assert str(res.answer[0]) == f"foo{i}.example. 300 IN A 10.53.9.9"
++
++ assert_used_auth(ns6, "ns6", "10.53.0.4")
++
++ for i in range(300, 400):
++ msg = isctest.query.create(f"foo{i}.example.", "A")
++ res = isctest.query.udp(msg, ns6.ip)
++ isctest.check.noerror(res)
++ assert len(res.answer[0]) == 1
++ res.answer[0].ttl = 300
++ assert str(res.answer[0]) == f"foo{i}.example. 300 IN A 10.53.9.9"
++ assert_used_auth(ns6, "ns6", "10.53.0.5")
+--
+2.35.6
+
diff --git a/meta/recipes-connectivity/bind/bind/CVE-2026-3592_p6.patch b/meta/recipes-connectivity/bind/bind/CVE-2026-3592_p6.patch
new file mode 100644
index 0000000000..449e1a1eee
--- /dev/null
+++ b/meta/recipes-connectivity/bind/bind/CVE-2026-3592_p6.patch
@@ -0,0 +1,41 @@
+From 4edafe63a0dfa9142e88ba66b429c35ee286d4dd Mon Sep 17 00:00:00 2001
+From: Colin Vidal <colin@isc.org>
+Date: Thu, 30 Apr 2026 19:02:47 +0100
+Subject: [PATCH] Fix `resend_loop` system test
+
+Commit `c78016ff91ed33221831b4723108d69639430913` backported asyncserver
+features to 9.18 branches, but the `resend_loop` test was still using
+the previous API to install handlers (passing a list of handlers rather
+than a varags). This is now fixed.
+
+CVE: CVE-2026-3592
+Upstream-Status: Backport [https://gitlab.com/isc-projects/bind9/-/commit/cb13dcabdb64bdb5f8f7ed33980aaf470a90e877]
+
+(cherry picked from commit cb13dcabdb64bdb5f8f7ed33980aaf470a90e877)
+Signed-off-by: Ashishkumar Parmar <asparmar@cisco.com>
+---
+ bin/tests/system/resend_loop/ans3/ans.py | 8 +++-----
+ 1 file changed, 3 insertions(+), 5 deletions(-)
+
+diff --git a/bin/tests/system/resend_loop/ans3/ans.py b/bin/tests/system/resend_loop/ans3/ans.py
+index 90a3f2f9cc..217bae0301 100644
+--- a/bin/tests/system/resend_loop/ans3/ans.py
++++ b/bin/tests/system/resend_loop/ans3/ans.py
+@@ -111,11 +111,9 @@ class NoErrorHandler(ResponseHandler):
+ def resend_server() -> AsyncDnsServer:
+ server = AsyncDnsServer(default_aa=True, default_rcode=dns.rcode.NOERROR)
+ server.install_response_handlers(
+- [
+- PrimeHandler(),
+- CookieHandler(),
+- NoErrorHandler(),
+- ]
++ PrimeHandler(),
++ CookieHandler(),
++ NoErrorHandler(),
+ )
+ return server
+
+--
+2.35.6
+
diff --git a/meta/recipes-connectivity/bind/bind_9.18.44.bb b/meta/recipes-connectivity/bind/bind_9.18.44.bb
index d55e0e0c4d..dd8923f185 100644
--- a/meta/recipes-connectivity/bind/bind_9.18.44.bb
+++ b/meta/recipes-connectivity/bind/bind_9.18.44.bb
@@ -26,6 +26,12 @@ SRC_URI = "https://ftp.isc.org/isc/bind9/${PV}/${BPN}-${PV}.tar.xz \
file://CVE-2026-5950_p1.patch \
file://CVE-2026-5950_p2.patch \
file://CVE-2026-5950_p3.patch \
+ file://CVE-2026-3592_p1.patch \
+ file://CVE-2026-3592_p2.patch \
+ file://CVE-2026-3592_p3.patch \
+ file://CVE-2026-3592_p4.patch \
+ file://CVE-2026-3592_p5.patch \
+ file://CVE-2026-3592_p6.patch \
"
SRC_URI[sha256sum] = "81f5035a25c576af1a93f0061cf70bde6d00a0c7bd1274abf73f5b5389a6f82d"
--
2.35.6
^ permalink raw reply related [flat|nested] 7+ messages in thread* [OE-core] [scarthgap] [PATCH 4/5] bind: Ignore CVE-2026-3039
2026-06-10 10:04 [OE-core] [scarthgap] [PATCH 1/5] bind: Fix CVE-2026-1519 Ashishkumar Parmar X (asparmar - E INFOCHIPS PRIVATE LIMITED at Cisco)
2026-06-10 10:04 ` [OE-core] [scarthgap] [PATCH 2/5] bind: Fix CVE-2026-5950 Ashishkumar Parmar X (asparmar - E INFOCHIPS PRIVATE LIMITED at Cisco)
2026-06-10 10:04 ` [OE-core] [scarthgap] [PATCH 3/5] bind: Fix CVE-2026-3592 Ashishkumar Parmar X (asparmar - E INFOCHIPS PRIVATE LIMITED at Cisco)
@ 2026-06-10 10:04 ` Ashishkumar Parmar X (asparmar - E INFOCHIPS PRIVATE LIMITED at Cisco)
2026-06-10 10:04 ` [OE-core] [scarthgap] [PATCH 5/5] bind: Fix CVE-2026-5946 Ashishkumar Parmar X (asparmar - E INFOCHIPS PRIVATE LIMITED at Cisco)
2026-06-26 16:59 ` [OE-core] [scarthgap] [PATCH 1/5] bind: Fix CVE-2026-1519 Yoann Congal
4 siblings, 0 replies; 7+ messages in thread
From: Ashishkumar Parmar X (asparmar - E INFOCHIPS PRIVATE LIMITED at Cisco) @ 2026-06-10 10:04 UTC (permalink / raw)
To: openembedded-core; +Cc: xe-linux-external, to, Ashishkumar Parmar
From: Ashishkumar Parmar <asparmar@cisco.com>
Analysis:
- CVE-2026-3039 affects BIND servers using TKEY-based
authentication via GSS-API tokens [1].
- This recipe configures BIND with --with-gssapi=no, so the
vulnerable GSS-API TKEY negotiation path is disabled [2].
- Hence ignoring the CVE for this build configuration.
Reference:
[1] https://kb.isc.org/docs/cve-2026-3039
[2] meta/recipes-connectivity/bind/bind_9.18.44.bb
Signed-off-by: Ashishkumar Parmar <asparmar@cisco.com>
---
meta/recipes-connectivity/bind/bind_9.18.44.bb | 1 +
1 file changed, 1 insertion(+)
diff --git a/meta/recipes-connectivity/bind/bind_9.18.44.bb b/meta/recipes-connectivity/bind/bind_9.18.44.bb
index dd8923f185..7b5baf5338 100644
--- a/meta/recipes-connectivity/bind/bind_9.18.44.bb
+++ b/meta/recipes-connectivity/bind/bind_9.18.44.bb
@@ -43,6 +43,7 @@ UPSTREAM_CHECK_REGEX = "(?P<pver>9.(\d*[02468])+(\.\d+)+(-P\d+)*)/"
# Issue only affects dhcpd with recent bind versions. We don't ship dhcpd anymore
# so the issue doesn't affect us.
CVE_STATUS[CVE-2019-6470] = "not-applicable-config: Issue only affects dhcpd with recent bind versions and we don't ship dhcpd anymore."
+CVE_STATUS[CVE-2026-3039] = "not-applicable-config: BIND is built with --with-gssapi=no, so GSS-API TKEY negotiation is disabled."
inherit autotools update-rc.d systemd useradd pkgconfig multilib_header update-alternatives
--
2.35.6
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [OE-core] [scarthgap] [PATCH 5/5] bind: Fix CVE-2026-5946
2026-06-10 10:04 [OE-core] [scarthgap] [PATCH 1/5] bind: Fix CVE-2026-1519 Ashishkumar Parmar X (asparmar - E INFOCHIPS PRIVATE LIMITED at Cisco)
` (2 preceding siblings ...)
2026-06-10 10:04 ` [OE-core] [scarthgap] [PATCH 4/5] bind: Ignore CVE-2026-3039 Ashishkumar Parmar X (asparmar - E INFOCHIPS PRIVATE LIMITED at Cisco)
@ 2026-06-10 10:04 ` Ashishkumar Parmar X (asparmar - E INFOCHIPS PRIVATE LIMITED at Cisco)
2026-06-26 16:59 ` [OE-core] [scarthgap] [PATCH 1/5] bind: Fix CVE-2026-1519 Yoann Congal
4 siblings, 0 replies; 7+ messages in thread
From: Ashishkumar Parmar X (asparmar - E INFOCHIPS PRIVATE LIMITED at Cisco) @ 2026-06-10 10:04 UTC (permalink / raw)
To: openembedded-core; +Cc: xe-linux-external, to, Ashishkumar Parmar
From: Ashishkumar Parmar <asparmar@cisco.com>
Pick the upstream 9.18 backport [1] for CVE-2026-5946. The public ISC
advisory [2] describes the vulnerability and identifies the fixed BIND
release.
The upstream fix is split across prerequisite ownership/update helpers,
non-IN class handling changes, and system-test coverage. Apply the
dependent patches first because later patches use MOVE_OWNERSHIP() and
the updated DNS UPDATE ownership flow:
- CVE-2026-5946-dependent_p1.patch [3] adds the MOVE_OWNERSHIP() helper
used by the following update-path prerequisite.
- CVE-2026-5946-dependent_p2.patch [4] fixes DNS UPDATE SSU table
ownership handling and depends on MOVE_OWNERSHIP().
- CVE-2026-5946_p1.patch [5] disables recursion for non-IN classes.
- CVE-2026-5946_p2.patch [6] disables UPDATE and NOTIFY for non-IN
classes.
- CVE-2026-5946_p3.patch [7] validates DNS message CLASS early in
request processing.
- CVE-2026-5946_p4.patch [8] rejects meta-classes in UPDATE and NOTIFY
messages.
- CVE-2026-5946_p5.patch [9] skips deny-answer-address handling for
non-IN addresses.
- CVE-2026-5946_p6.patch [10] tests CHAOS view recursion behavior.
- CVE-2026-5946_p7.patch [11] tests UPDATE behavior in CHAOS and other
non-IN classes.
- CVE-2026-5946_p8.patch [12] tests server behavior for different UPDATE
requests.
- CVE-2026-5946_p9.patch [13] makes the RD flag optional in
isctest.query() for the test coverage above.
Keep the patches split to preserve the upstream commit structure and to
make the SRC_URI ordering explicit.
[1] https://gitlab.com/isc-projects/bind9/-/commit/7ce6ce37b1b04af0953ed2d3211587465085600e
[2] https://kb.isc.org/docs/cve-2026-5946
[3] https://gitlab.com/isc-projects/bind9/-/commit/8a0a0b01ff00d51509cf7e9ee8ca7bd076ba7b28
[4] https://gitlab.com/isc-projects/bind9/-/commit/f6fdc77c4699db4e54165e1759e7bf3d639b30cb
[5] https://gitlab.com/isc-projects/bind9/-/commit/401a6374b026afb76a7b22acc4a1402d21a7e77b
[6] https://gitlab.com/isc-projects/bind9/-/commit/04092ed136c8a6db2b1059dcd32693d57a7bdc24
[7] https://gitlab.com/isc-projects/bind9/-/commit/b247dbb3506ef628a683d184fbc6a99fad45ed94
[8] https://gitlab.com/isc-projects/bind9/-/commit/185c10981b941bfa5b753b3624b6e11ccca8737f
[9] https://gitlab.com/isc-projects/bind9/-/commit/772d1d5f905c819d7155e76a08c33218bfcc973e
[10] https://gitlab.com/isc-projects/bind9/-/commit/ba6b159d880e803decb85fdc88280b23d6f3a652
[11] https://gitlab.com/isc-projects/bind9/-/commit/e5b3149b70ed24f7590b6bc08aad4492a00c2022
[12] https://gitlab.com/isc-projects/bind9/-/commit/7e3d49815c3b8243b009c29955cef018fe87b7f0
[13] https://gitlab.com/isc-projects/bind9/-/commit/d79f2d3f35887ea4525e283d389d9078fa1ef439
Signed-off-by: Ashishkumar Parmar <asparmar@cisco.com>
---
.../bind/CVE-2026-5946-dependent_p1.patch | 39 ++
.../bind/CVE-2026-5946-dependent_p2.patch | 78 ++++
.../bind/bind/CVE-2026-5946_p1.patch | 216 +++++++++++
.../bind/bind/CVE-2026-5946_p2.patch | 244 +++++++++++++
.../bind/bind/CVE-2026-5946_p3.patch | 169 +++++++++
.../bind/bind/CVE-2026-5946_p4.patch | 56 +++
.../bind/bind/CVE-2026-5946_p5.patch | 40 +++
.../bind/bind/CVE-2026-5946_p6.patch | 336 ++++++++++++++++++
.../bind/bind/CVE-2026-5946_p7.patch | 153 ++++++++
.../bind/bind/CVE-2026-5946_p8.patch | 225 ++++++++++++
.../bind/bind/CVE-2026-5946_p9.patch | 44 +++
.../recipes-connectivity/bind/bind_9.18.44.bb | 11 +
12 files changed, 1611 insertions(+)
create mode 100644 meta/recipes-connectivity/bind/bind/CVE-2026-5946-dependent_p1.patch
create mode 100644 meta/recipes-connectivity/bind/bind/CVE-2026-5946-dependent_p2.patch
create mode 100644 meta/recipes-connectivity/bind/bind/CVE-2026-5946_p1.patch
create mode 100644 meta/recipes-connectivity/bind/bind/CVE-2026-5946_p2.patch
create mode 100644 meta/recipes-connectivity/bind/bind/CVE-2026-5946_p3.patch
create mode 100644 meta/recipes-connectivity/bind/bind/CVE-2026-5946_p4.patch
create mode 100644 meta/recipes-connectivity/bind/bind/CVE-2026-5946_p5.patch
create mode 100644 meta/recipes-connectivity/bind/bind/CVE-2026-5946_p6.patch
create mode 100644 meta/recipes-connectivity/bind/bind/CVE-2026-5946_p7.patch
create mode 100644 meta/recipes-connectivity/bind/bind/CVE-2026-5946_p8.patch
create mode 100644 meta/recipes-connectivity/bind/bind/CVE-2026-5946_p9.patch
diff --git a/meta/recipes-connectivity/bind/bind/CVE-2026-5946-dependent_p1.patch b/meta/recipes-connectivity/bind/bind/CVE-2026-5946-dependent_p1.patch
new file mode 100644
index 0000000000..68ad96da85
--- /dev/null
+++ b/meta/recipes-connectivity/bind/bind/CVE-2026-5946-dependent_p1.patch
@@ -0,0 +1,39 @@
+From 8a0a0b01ff00d51509cf7e9ee8ca7bd076ba7b28 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Ond=C5=99ej=20Sur=C3=BD?= <ondrej@isc.org>
+Date: Fri, 20 Mar 2026 02:15:17 +0100
+Subject: [PATCH] Add MOVE_OWNERSHIP() macro for transferring pointer ownership
+
+A helper macro that returns the current value of a pointer and sets
+it to NULL in one expression, useful for transferring ownership in
+designated initializers.
+
+CVE: CVE-2026-5946
+Upstream-Status: Backport [https://gitlab.com/isc-projects/bind9/-/commit/8a0a0b01ff00d51509cf7e9ee8ca7bd076ba7b28]
+
+(cherry picked from commit 8a0a0b01ff00d51509cf7e9ee8ca7bd076ba7b28)
+Signed-off-by: Ashishkumar Parmar <asparmar@cisco.com>
+---
+ lib/isc/include/isc/util.h | 7 +++++++
+ 1 file changed, 7 insertions(+)
+
+diff --git a/lib/isc/include/isc/util.h b/lib/isc/include/isc/util.h
+index 2d507c3f7e..222dac56bc 100644
+--- a/lib/isc/include/isc/util.h
++++ b/lib/isc/include/isc/util.h
+@@ -47,6 +47,13 @@
+ *** General Macros.
+ ***/
+
++#define MOVE_OWNERSHIP(source) \
++ ({ \
++ __typeof__(source) __ownership = (source); \
++ (source) = NULL; \
++ __ownership; \
++ })
++
+ /*%
+ * Use this to hide unused function arguments.
+ * \code
+--
+GitLab
+
diff --git a/meta/recipes-connectivity/bind/bind/CVE-2026-5946-dependent_p2.patch b/meta/recipes-connectivity/bind/bind/CVE-2026-5946-dependent_p2.patch
new file mode 100644
index 0000000000..d7b5e167d7
--- /dev/null
+++ b/meta/recipes-connectivity/bind/bind/CVE-2026-5946-dependent_p2.patch
@@ -0,0 +1,78 @@
+From f6fdc77c4699db4e54165e1759e7bf3d639b30cb Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Ond=C5=99ej=20Sur=C3=BD?= <ondrej@sury.org>
+Date: Wed, 18 Mar 2026 03:55:51 +0100
+Subject: [PATCH] Fix TOCTOU race in DNS UPDATE SSU table handling
+
+Pass the SSU table through the update event struct from
+send_update() to update_action() instead of reading it from the
+zone twice. If rndc reconfig changed the zone's update policy
+between the two reads (e.g., from allow-update to update-policy),
+send_update() would skip the maxbytype allocation but
+update_action() would see a non-NULL ssutable, triggering
+INSIST(ssutable == NULL || maxbytype != NULL) and crashing named.
+
+The ssutable reference is now taken once in send_update() and
+transferred to update_action() via the event struct, ensuring
+both functions see the same value.
+
+CVE: CVE-2026-5946
+Upstream-Status: Backport [https://gitlab.com/isc-projects/bind9/-/commit/f6fdc77c4699db4e54165e1759e7bf3d639b30cb]
+
+(cherry picked from commit c172416559e62a31de27061648db7ffe3b1b7f63)
+(cherry picked from commit f6fdc77c4699db4e54165e1759e7bf3d639b30cb)
+Signed-off-by: Ashishkumar Parmar <asparmar@cisco.com>
+---
+ lib/ns/update.c | 8 ++++----
+ 1 file changed, 4 insertions(+), 4 deletions(-)
+
+diff --git a/lib/ns/update.c b/lib/ns/update.c
+index c3e4eb115d..c02535130f 100644
+--- a/lib/ns/update.c
++++ b/lib/ns/update.c
+@@ -203,6 +203,7 @@ struct update_event {
+ dns_zone_t *zone;
+ isc_result_t result;
+ dns_message_t *answer;
++ dns_ssutable_t *ssutable;
+ unsigned int *maxbytype;
+ size_t maxbytypelen;
+ };
+@@ -1850,9 +1851,9 @@ send_update_event(ns_client_t *client, dns_zone_t *zone) {
+ sizeof(*event));
+ event->zone = zone;
+ event->result = ISC_R_SUCCESS;
+- event->maxbytype = maxbytype;
++ event->ssutable = MOVE_OWNERSHIP(ssutable);
++ event->maxbytype = MOVE_OWNERSHIP(maxbytype);
+ event->maxbytypelen = maxbytypelen;
+- maxbytype = NULL;
+
+ INSIST(client->nupdates == 0);
+ client->nupdates++;
+@@ -2840,6 +2841,7 @@ update_action(isc_task_t *task, isc_event_t *event) {
+ update_event_t *uev = (update_event_t *)event;
+ dns_zone_t *zone = uev->zone;
+ ns_client_t *client = (ns_client_t *)event->ev_arg;
++ dns_ssutable_t *ssutable = uev->ssutable;
+ unsigned int *maxbytype = uev->maxbytype;
+ size_t update = 0, maxbytypelen = uev->maxbytypelen;
+ isc_result_t result;
+@@ -2854,7 +2856,6 @@ update_action(isc_task_t *task, isc_event_t *event) {
+ dns_message_t *request = client->message;
+ dns_rdataclass_t zoneclass;
+ dns_name_t *zonename = NULL;
+- dns_ssutable_t *ssutable = NULL;
+ dns_fixedname_t tmpnamefixed;
+ dns_name_t *tmpname = NULL;
+ dns_zoneopt_t options;
+@@ -2874,7 +2875,6 @@ update_action(isc_task_t *task, isc_event_t *event) {
+ CHECK(dns_zone_getdb(zone, &db));
+ zonename = dns_db_origin(db);
+ zoneclass = dns_db_class(db);
+- dns_zone_getssutable(zone, &ssutable);
+ options = dns_zone_getoptions(zone);
+
+ /*
+--
+2.35.6
+
diff --git a/meta/recipes-connectivity/bind/bind/CVE-2026-5946_p1.patch b/meta/recipes-connectivity/bind/bind/CVE-2026-5946_p1.patch
new file mode 100644
index 0000000000..0238e2df8e
--- /dev/null
+++ b/meta/recipes-connectivity/bind/bind/CVE-2026-5946_p1.patch
@@ -0,0 +1,216 @@
+From e1a09175d8bb35900dfb8b356717b53ef0cc2cf9 Mon Sep 17 00:00:00 2001
+From: Evan Hunt <each@isc.org>
+Date: Tue, 3 Mar 2026 14:00:38 -0800
+Subject: [PATCH] Disable recursion for non-IN classes
+
+Force recursion off, and set allow-recursion/allow-recursion-on ACLs
+to none, for views with a class other than IN. Log a configuration
+warning if recursion is explicitly enabled for a non-IN view.
+
+This addresses YWH-PGM40640-74 and YWH-PGM40640-75 by preventing any
+attempt at recursive processing in a class-CHAOS view, ensuring that
+server addresses used for recursive queries and received in recursive
+responses are of the expected format.
+
+Fixes: isc-projects/bind9#5780
+Fixes: isc-projects/bind9#5781
+
+CVE: CVE-2026-5946
+Upstream-Status: Backport [https://gitlab.com/isc-projects/bind9/-/commit/401a6374b026afb76a7b22acc4a1402d21a7e77b]
+
+Backport Changes:
+- Adjusted the system-test path from allow_query to allow-query
+ to match the BIND 9.18.44 source tree layout.
+
+(cherry picked from commit 7becff1a14684a68208c92b3b0315c045c05ad75)
+(cherry picked from commit 401a6374b026afb76a7b22acc4a1402d21a7e77b)
+Signed-off-by: Ashishkumar Parmar <asparmar@cisco.com>
+---
+ bin/named/server.c | 41 ++++++++-------------------
+ bin/tests/system/allow-query/tests.sh | 2 +-
+ bin/tests/system/checkconf/tests.sh | 1 +
+ bin/tests/system/resolver/tests.sh | 8 ++++--
+ lib/bind9/check.c | 22 ++++++++++++--
+ 5 files changed, 38 insertions(+), 36 deletions(-)
+
+diff --git a/bin/named/server.c b/bin/named/server.c
+index 125bfa8f94..7aa0f8b140 100644
+--- a/bin/named/server.c
++++ b/bin/named/server.c
+@@ -4515,6 +4515,7 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, cfg_obj_t *config,
+ obj = NULL;
+ result = named_config_get(maps, "max-cache-size", &obj);
+ INSIST(result == ISC_R_SUCCESS);
++
+ /*
+ * If "-T maxcachesize=..." is in effect, it overrides any other
+ * "max-cache-size" setting found in configuration, either implicit or
+@@ -5224,34 +5225,15 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, cfg_obj_t *config,
+ }
+
+ /*
+- * We have default hints for class IN if we need them.
++ * We have default root hints for class IN if we need them.
++ * Each view gets its own rootdb so a priming response only
++ * writes into that view's copy. Other classes don't support
++ * recursion and don't need hints.
+ */
+ if (view->rdclass == dns_rdataclass_in && view->hints == NULL) {
+ dns_view_sethints(view, named_g_server->in_roothints);
+ }
+
+- /*
+- * If we still have no hints, this is a non-IN view with no
+- * "hints zone" configured. Issue a warning, except if this
+- * is a root server. Root servers never need to consult
+- * their hints, so it's no point requiring users to configure
+- * them.
+- */
+- if (view->hints == NULL) {
+- dns_zone_t *rootzone = NULL;
+- (void)dns_view_findzone(view, dns_rootname, &rootzone);
+- if (rootzone != NULL) {
+- dns_zone_detach(&rootzone);
+- need_hints = false;
+- }
+- if (need_hints) {
+- isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL,
+- NAMED_LOGMODULE_SERVER, ISC_LOG_WARNING,
+- "no root hints for view '%s'",
+- view->name);
+- }
+- }
+-
+ /*
+ * Configure the view's transports (DoT/DoH)
+ */
+@@ -5379,7 +5361,8 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, cfg_obj_t *config,
+ obj = NULL;
+ result = named_config_get(maps, "recursion", &obj);
+ INSIST(result == ISC_R_SUCCESS);
+- view->recursion = cfg_obj_asboolean(obj);
++ view->recursion = (view->rdclass == dns_rdataclass_in &&
++ cfg_obj_asboolean(obj));
+
+ obj = NULL;
+ result = named_config_get(maps, "qname-minimization", &obj);
+@@ -5479,14 +5462,14 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, cfg_obj_t *config,
+ CHECK(configure_view_acl(vconfig, config, NULL, "allow-query-cache-on",
+ NULL, actx, named_g_mctx, &view->cacheonacl));
+
+- if (strcmp(view->name, "_bind") != 0 &&
+- view->rdclass != dns_rdataclass_chaos)
+- {
+- /* named.conf only */
++ if (view->rdclass != dns_rdataclass_in) {
++ view->recursion = false;
++ dns_acl_none(named_g_mctx, &view->recursionacl);
++ dns_acl_none(named_g_mctx, &view->recursiononacl);
++ } else {
+ CHECK(configure_view_acl(vconfig, config, NULL,
+ "allow-recursion", NULL, actx,
+ named_g_mctx, &view->recursionacl));
+- /* named.conf only */
+ CHECK(configure_view_acl(vconfig, config, NULL,
+ "allow-recursion-on", NULL, actx,
+ named_g_mctx, &view->recursiononacl));
+diff --git a/bin/tests/system/allow-query/tests.sh b/bin/tests/system/allow-query/tests.sh
+index e59a1abe6b..46d2a78077 100644
+--- a/bin/tests/system/allow-query/tests.sh
++++ b/bin/tests/system/allow-query/tests.sh
+@@ -703,7 +703,7 @@ $DIG -p ${PORT} @10.53.1.2 d.normal.example a >dig.out.ns3.4.$n || ret=1
+ grep 'recursion requested but not available' dig.out.ns3.4.$n >/dev/null || ret=1
+ grep 'status: REFUSED' dig.out.ns3.4.$n >/dev/null || ret=1
+ grep 'EDE: 18 (Prohibited)' dig.out.ns3.4.$n >/dev/null || ret=1
+-nextpart ns3/named.run | grep 'allow-recursion-on did not match' >/dev/null || ret=1
++nextpart ns3/named.run | grep 'allow-query-cache-on did not match' >/dev/null || ret=1
+ if [ $ret != 0 ]; then echo_i "failed"; fi
+ status=$((status + ret))
+
+diff --git a/bin/tests/system/checkconf/tests.sh b/bin/tests/system/checkconf/tests.sh
+index 3bb772e4d5..5da8a20ee1 100644
+--- a/bin/tests/system/checkconf/tests.sh
++++ b/bin/tests/system/checkconf/tests.sh
+@@ -543,6 +543,7 @@ $CHECKCONF -l good.conf \
+ | grep -v "is not implemented" \
+ | grep -v "is not recommended" \
+ | grep -v "no longer exists" \
++ | grep -v "recursion will be disabled" \
+ | grep -v "is obsolete" >checkconf.out$n || ret=1
+ diff good.zonelist checkconf.out$n >diff.out$n || ret=1
+ if [ $ret -ne 0 ]; then
+diff --git a/bin/tests/system/resolver/tests.sh b/bin/tests/system/resolver/tests.sh
+index 2864aae950..db5a18680d 100755
+--- a/bin/tests/system/resolver/tests.sh
++++ b/bin/tests/system/resolver/tests.sh
+@@ -979,10 +979,12 @@ if [ $ret != 0 ]; then echo_i "failed"; fi
+ status=$((status + ret))
+
+ n=$((n + 1))
+-echo_i "checking NXDOMAIN is returned when querying non existing domain in CH class ($n)"
++echo_i "checking REFUSED is returned when querying non existing domain in CH class ($n)"
+ ret=0
+-dig_with_opts @10.53.0.1 id.hostname txt ch >dig.ns1.out.${n} || ret=1
+-grep "status: NXDOMAIN" dig.ns1.out.${n} >/dev/null || ret=1
++dig_with_opts @10.53.0.1 hostname.chaostest txt ch >dig.ns1.out.1.${n} || ret=1
++grep "status: NOERROR" dig.ns1.out.1.${n} >/dev/null || ret=1
++dig_with_opts @10.53.0.1 id.hostname txt ch >dig.ns1.out.2.${n} || ret=1
++grep "status: REFUSED" dig.ns1.out.2.${n} >/dev/null || ret=1
+ if [ $ret != 0 ]; then echo_i "failed"; fi
+ status=$((status + ret))
+
+diff --git a/lib/bind9/check.c b/lib/bind9/check.c
+index cefc3eb3ac..13f3212d08 100644
+--- a/lib/bind9/check.c
++++ b/lib/bind9/check.c
+@@ -2789,13 +2789,17 @@ check_mirror_zone_notify(const cfg_obj_t *zoptions, const char *znamestr,
+ */
+ static bool
+ check_recursion(const cfg_obj_t *config, const cfg_obj_t *voptions,
+- const cfg_obj_t *goptions, isc_log_t *logctx,
+- cfg_aclconfctx_t *actx, isc_mem_t *mctx) {
++ dns_rdataclass_t vclass, const cfg_obj_t *goptions,
++ isc_log_t *logctx, cfg_aclconfctx_t *actx, isc_mem_t *mctx) {
+ dns_acl_t *acl = NULL;
+ const cfg_obj_t *obj;
+ isc_result_t result;
+ bool retval = true;
+
++ if (vclass != dns_rdataclass_in) {
++ return false;
++ }
++
+ /*
+ * Check the "recursion" option first.
+ */
+@@ -3380,7 +3384,8 @@ check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions,
+ * contradicts the purpose of the former.
+ */
+ if (ztype == CFG_ZONE_MIRROR &&
+- !check_recursion(config, voptions, goptions, logctx, actx, mctx))
++ !check_recursion(config, voptions, zclass, goptions, logctx, actx,
++ mctx))
+ {
+ cfg_obj_log(zoptions, logctx, ISC_LOG_ERROR,
+ "zone '%s': mirror zones cannot be used if "
+@@ -5215,6 +5220,17 @@ check_viewconf(const cfg_obj_t *config, const cfg_obj_t *voptions,
+
+ cfg_aclconfctx_create(mctx, &actx);
+
++ if (vclass != dns_rdataclass_in) {
++ if (check_recursion(config, voptions, dns_rdataclass_in,
++ options, logctx, actx, mctx))
++ {
++ cfg_obj_log(opts, logctx, ISC_LOG_WARNING,
++ "recursion will be disabled for "
++ "non-IN view '%s'",
++ viewname);
++ }
++ }
++
+ if (voptions != NULL) {
+ (void)cfg_map_get(voptions, "zone", &zones);
+ } else {
+--
+2.35.6
+
diff --git a/meta/recipes-connectivity/bind/bind/CVE-2026-5946_p2.patch b/meta/recipes-connectivity/bind/bind/CVE-2026-5946_p2.patch
new file mode 100644
index 0000000000..4ed15d72f1
--- /dev/null
+++ b/meta/recipes-connectivity/bind/bind/CVE-2026-5946_p2.patch
@@ -0,0 +1,244 @@
+From 04092ed136c8a6db2b1059dcd32693d57a7bdc24 Mon Sep 17 00:00:00 2001
+From: Evan Hunt <each@isc.org>
+Date: Wed, 4 Mar 2026 13:24:52 -0800
+Subject: [PATCH] Disable UPDATE and NOTIFY for non-IN classes
+
+Return NOTIMP for UPDATE and NOTIFY requests received for views with a
+class other than IN. Only QUERY is now supported for non-IN views such
+as CHAOS.
+
+When running dns dns_rdata_tostruct() with types that are only defined
+for class IN, ensure that the class is correct before proceeding.
+
+Add an assertion that any zone being updated is of class IN. (Note
+that previously, a DLZ zone could have its class value set incorrectly
+to NONE; this has been fixed.)
+
+This addresses YWH-PGM40640-70 and YWH-PGM40640-73 (as well as any
+similar problems that might have occurred in the future) by minimizing
+the code paths that can be reached by rdata classes other than IN, so it
+is safe for the implementation to assume that rdatatypes that are only
+defined for class IN, such as SVCB or WKS, have been parsed and
+validated, and not accepted as unknown/opaque data.
+
+Fixes: isc-projects/bind9#5777
+Fixes: isc-projects/bind9#5779
+
+CVE: CVE-2026-5946
+Upstream-Status: Backport [https://gitlab.com/isc-projects/bind9/-/commit/04092ed136c8a6db2b1059dcd32693d57a7bdc24]
+
+(cherry picked from commit a6d8e330ed6cf0021bff3f00aa1dc7a296f5aec0)
+(cherry picked from commit 04092ed136c8a6db2b1059dcd32693d57a7bdc24)
+Signed-off-by: Ashishkumar Parmar <asparmar@cisco.com>
+---
+ bin/named/server.c | 2 ++
+ lib/dns/adb.c | 2 +-
+ lib/ns/client.c | 8 ++++++++
+ lib/ns/update.c | 41 ++++++++++++++++++++++-------------------
+ 4 files changed, 33 insertions(+), 20 deletions(-)
+
+diff --git a/bin/named/server.c b/bin/named/server.c
+index 307f10cee3..43c4023fb7 100644
+--- a/bin/named/server.c
++++ b/bin/named/server.c
+@@ -1987,10 +1987,12 @@ dlzconfigure_callback(dns_view_t *view, dns_dlzdb_t *dlzdb, dns_zone_t *zone) {
+ dns_rdataclass_t zclass = view->rdclass;
+ isc_result_t result;
+
++ dns_zone_setclass(zone, zclass);
+ result = dns_zonemgr_managezone(named_g_server->zonemgr, zone);
+ if (result != ISC_R_SUCCESS) {
+ return result;
+ }
++
+ dns_zone_setstats(zone, named_g_server->zonestats);
+
+ return named_zone_configure_writeable_dlz(dlzdb, zone, zclass, origin);
+diff --git a/lib/dns/adb.c b/lib/dns/adb.c
+index 87fc357cd8..21ae217a38 100644
+--- a/lib/dns/adb.c
++++ b/lib/dns/adb.c
+@@ -949,7 +949,7 @@ import_rdataset(dns_adbname_t *adbname, dns_rdataset_t *rdataset,
+ INSIST(DNS_ADB_VALID(adb));
+
+ rdtype = rdataset->type;
+- INSIST((rdtype == dns_rdatatype_a) || (rdtype == dns_rdatatype_aaaa));
++ REQUIRE(rdtype == dns_rdatatype_a || rdtype == dns_rdatatype_aaaa);
+
+ addr_bucket = DNS_ADB_INVALIDBUCKET;
+ new_addresses_added = false;
+diff --git a/lib/ns/client.c b/lib/ns/client.c
+index 97e99019a4..4c538f5f9f 100644
+--- a/lib/ns/client.c
++++ b/lib/ns/client.c
+@@ -2337,6 +2337,10 @@ ns__client_request(isc_nmhandle_t *handle, isc_result_t eresult,
+ break;
+ case dns_opcode_update:
+ CTRACE("update");
++ if (client->view->rdclass != dns_rdataclass_in) {
++ ns_client_error(client, DNS_R_NOTIMP);
++ break;
++ }
+ #ifdef HAVE_DNSTAP
+ dns_dt_send(client->view, DNS_DTTYPE_UQ, &client->peeraddr,
+ &client->destsockaddr, TCP_CLIENT(client), NULL,
+@@ -2347,6 +2351,10 @@ ns__client_request(isc_nmhandle_t *handle, isc_result_t eresult,
+ break;
+ case dns_opcode_notify:
+ CTRACE("notify");
++ if (client->view->rdclass != dns_rdataclass_in) {
++ ns_client_error(client, DNS_R_NOTIMP);
++ break;
++ }
+ ns_client_settimeout(client, 60);
+ ns_notify_start(client, handle);
+ break;
+diff --git a/lib/ns/update.c b/lib/ns/update.c
+index 415836fd13..d002019fdf 100644
+--- a/lib/ns/update.c
++++ b/lib/ns/update.c
+@@ -999,7 +999,9 @@ ssu_checkrr(void *data, rr_t *rr) {
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ target = &ptr.ptr;
+ }
+- if (rr->rdata.type == dns_rdatatype_srv) {
++ if (rr->rdata.rdclass == dns_rdataclass_in &&
++ rr->rdata.type == dns_rdatatype_srv)
++ {
+ result = dns_rdata_tostruct(&rr->rdata, &srv, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ target = &srv.target;
+@@ -1354,7 +1356,10 @@ replaces_p(dns_rdata_t *update_rr, dns_rdata_t *db_rr) {
+ return true;
+ }
+ }
+- if (db_rr->type == dns_rdatatype_wks) {
++
++ if (db_rr->rdclass == dns_rdataclass_in &&
++ db_rr->type == dns_rdatatype_wks)
++ {
+ /*
+ * Compare the address and protocol fields only. These
+ * form the first five bytes of the RR data. Do a
+@@ -1497,8 +1502,7 @@ cleanup:
+ * 'rdata', and 'ttl', respectively.
+ */
+ static void
+-get_current_rr(dns_message_t *msg, dns_section_t section,
+- dns_rdataclass_t zoneclass, dns_name_t **name,
++get_current_rr(dns_message_t *msg, dns_section_t section, dns_name_t **name,
+ dns_rdata_t *rdata, dns_rdatatype_t *covers, dns_ttl_t *ttl,
+ dns_rdataclass_t *update_class) {
+ dns_rdataset_t *rdataset;
+@@ -1514,7 +1518,7 @@ get_current_rr(dns_message_t *msg, dns_section_t section,
+ dns_rdataset_current(rdataset, rdata);
+ INSIST(dns_rdataset_next(rdataset) == ISC_R_NOMORE);
+ *update_class = rdata->rdclass;
+- rdata->rdclass = zoneclass;
++ rdata->rdclass = dns_rdataclass_in;
+ }
+
+ /*%
+@@ -1616,7 +1620,6 @@ send_update_event(ns_client_t *client, dns_zone_t *zone) {
+ dns_message_t *request = client->message;
+ isc_mem_t *mctx = client->manager->mctx;
+ dns_aclenv_t *env = client->manager->aclenv;
+- dns_rdataclass_t zoneclass;
+ dns_rdatatype_t covers;
+ dns_name_t *zonename = NULL;
+ unsigned int *maxbytype = NULL;
+@@ -1626,10 +1629,12 @@ send_update_event(ns_client_t *client, dns_zone_t *zone) {
+
+ CHECK(dns_zone_getdb(zone, &db));
+ zonename = dns_db_origin(db);
+- zoneclass = dns_db_class(db);
+ dns_zone_getssutable(zone, &ssutable);
+ dns_db_currentversion(db, &ver);
+
++ /* Updates are only supported for class IN. */
++ INSIST(dns_zone_getclass(zone) == dns_rdataclass_in);
++
+ /*
+ * Update message processing can leak record existence information
+ * so check that we are allowed to query this zone. Additionally,
+@@ -1680,13 +1685,13 @@ send_update_event(ns_client_t *client, dns_zone_t *zone) {
+
+ INSIST(ssutable == NULL || update < maxbytypelen);
+
+- get_current_rr(request, DNS_SECTION_UPDATE, zoneclass, &name,
+- &rdata, &covers, &ttl, &update_class);
++ get_current_rr(request, DNS_SECTION_UPDATE, &name, &rdata,
++ &covers, &ttl, &update_class);
+
+ if (!dns_name_issubdomain(name, zonename)) {
+ FAILC(DNS_R_NOTZONE, "update RR is outside zone");
+ }
+- if (update_class == zoneclass) {
++ if (update_class == dns_rdataclass_in) {
+ /*
+ * Check for meta-RRs. The RFC2136 pseudocode says
+ * check for ANY|AXFR|MAILA|MAILB, but the text adds
+@@ -1776,7 +1781,6 @@ send_update_event(ns_client_t *client, dns_zone_t *zone) {
+ }
+
+ if (update_class == dns_rdataclass_any &&
+- zoneclass == dns_rdataclass_in &&
+ (rdata.type == dns_rdatatype_ptr ||
+ rdata.type == dns_rdatatype_srv))
+ {
+@@ -2860,7 +2864,6 @@ update_action(isc_task_t *task, isc_event_t *event) {
+ isc_mem_t *mctx = client->mctx;
+ dns_rdatatype_t covers;
+ dns_message_t *request = client->message;
+- dns_rdataclass_t zoneclass;
+ dns_name_t *zonename = NULL;
+ dns_fixedname_t tmpnamefixed;
+ dns_name_t *tmpname = NULL;
+@@ -2880,9 +2883,9 @@ update_action(isc_task_t *task, isc_event_t *event) {
+
+ CHECK(dns_zone_getdb(zone, &db));
+ zonename = dns_db_origin(db);
+- zoneclass = dns_db_class(db);
+ options = dns_zone_getoptions(zone);
+
++ INSIST(dns_zone_getclass(zone) == dns_rdataclass_in);
+ /*
+ * Get old and new versions now that queryacl has been checked.
+ */
+@@ -2903,8 +2906,8 @@ update_action(isc_task_t *task, isc_event_t *event) {
+ dns_rdataclass_t update_class;
+ bool flag;
+
+- get_current_rr(request, DNS_SECTION_PREREQUISITE, zoneclass,
+- &name, &rdata, &covers, &ttl, &update_class);
++ get_current_rr(request, DNS_SECTION_PREREQUISITE, &name, &rdata,
++ &covers, &ttl, &update_class);
+
+ if (ttl != 0) {
+ PREREQFAILC(DNS_R_FORMERR,
+@@ -2967,7 +2970,7 @@ update_action(isc_task_t *task, isc_event_t *event) {
+ "prerequisite not satisfied");
+ }
+ }
+- } else if (update_class == zoneclass) {
++ } else if (update_class == dns_rdataclass_in) {
+ /* "temp<rr.name, rr.type> += rr;" */
+ result = temp_append(&temp, name, &rdata);
+ if (result != ISC_R_SUCCESS) {
+@@ -3029,10 +3032,10 @@ update_action(isc_task_t *task, isc_event_t *event) {
+
+ INSIST(ssutable == NULL || update < maxbytypelen);
+
+- get_current_rr(request, DNS_SECTION_UPDATE, zoneclass, &name,
+- &rdata, &covers, &ttl, &update_class);
++ get_current_rr(request, DNS_SECTION_UPDATE, &name, &rdata,
++ &covers, &ttl, &update_class);
+
+- if (update_class == zoneclass) {
++ if (update_class == dns_rdataclass_in) {
+ /*
+ * RFC1123 doesn't allow MF and MD in master files.
+ */
+--
+2.35.6
+
diff --git a/meta/recipes-connectivity/bind/bind/CVE-2026-5946_p3.patch b/meta/recipes-connectivity/bind/bind/CVE-2026-5946_p3.patch
new file mode 100644
index 0000000000..9e102015c3
--- /dev/null
+++ b/meta/recipes-connectivity/bind/bind/CVE-2026-5946_p3.patch
@@ -0,0 +1,169 @@
+From 5c6833d72e2720701e687fd6364ffdc6640e8329 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Ond=C5=99ej=20Sur=C3=BD?= <ondrej@isc.org>
+Date: Wed, 4 Mar 2026 10:46:58 +0100
+Subject: [PATCH] Validate DNS message CLASS early in request processing
+
+Reject requests with unsupported or misused CLASS values before
+further processing. Only IN, CH, HS, RESERVED0 (for DNS Cookies),
+ANY (for TKEY negotiation), and NONE (for DNS UPDATE) are accepted;
+all other classes return NOTIMP. Misuse of NONE or ANY outside
+their allowed contexts returns FORMERR.
+
+This adds further protection against bugs of the same general class
+as YWH-PGM40640-70 and YWH-PGM40640-73.
+
+CVE: CVE-2026-5946
+Upstream-Status: Backport [https://gitlab.com/isc-projects/bind9/-/commit/b247dbb3506ef628a683d184fbc6a99fad45ed94]
+
+(cherry picked from commit 0a687451505037e9f9a850c9cb113aed4995b03f)
+(cherry picked from commit b247dbb3506ef628a683d184fbc6a99fad45ed94)
+Signed-off-by: Ashishkumar Parmar <asparmar@cisco.com>
+---
+ bin/tests/system/unknown/tests.sh | 17 +++++++----
+ lib/ns/client.c | 51 ++++++++++++++++++++++++++-----
+ 2 files changed, 55 insertions(+), 13 deletions(-)
+
+diff --git a/bin/tests/system/unknown/tests.sh b/bin/tests/system/unknown/tests.sh
+index eb61f21f28..cbc2943f17 100644
+--- a/bin/tests/system/unknown/tests.sh
++++ b/bin/tests/system/unknown/tests.sh
+@@ -25,6 +25,11 @@ dig_cmd() {
+ "$DIG" $DIGOPTS "$@" | grep -v '^;'
+ }
+
++dig_full() {
++ # shellcheck disable=SC2086
++ "$DIG" $DIGOPTS "$@"
++}
++
+ n=$((n + 1))
+ echo_i "querying for various representations of an IN A record ($n)"
+ for i in 1 2 3 4 5 6 7 8 9 10 11 12; do
+@@ -81,8 +86,8 @@ n=$((n + 1))
+ echo_i "querying for various representations of a CLASS10 TYPE1 record ($n)"
+ for i in 1 2; do
+ ret=0
+- dig_cmd +short @10.53.0.1 a$i.example a class10 >dig.out.$i.test$n
+- echo '\# 4 0A000001' | diff - dig.out.$i.test$n || ret=1
++ dig_full @10.53.0.1 a$i.example a class10 >dig.out.$i.test$n
++ grep -q "NOTIMP" dig.out.$i.test$n || ret=1
+ if [ $ret != 0 ]; then
+ echo_i "#$i failed"
+ fi
+@@ -93,8 +98,8 @@ n=$((n + 1))
+ echo_i "querying for various representations of a CLASS10 TXT record ($n)"
+ for i in 1 2 3 4; do
+ ret=0
+- dig_cmd +short @10.53.0.1 txt$i.example txt class10 >dig.out.$i.test$n
+- echo '"hello"' | diff - dig.out.$i.test$n || ret=1
++ dig_full @10.53.0.1 txt$i.example txt class10 >dig.out.$i.test$n
++ grep -q "NOTIMP" dig.out.$i.test$n || ret=1
+ if [ $ret != 0 ]; then
+ echo_i "#$i failed"
+ fi
+@@ -105,8 +110,8 @@ n=$((n + 1))
+ echo_i "querying for various representations of a CLASS10 TYPE123 record ($n)"
+ for i in 1 2; do
+ ret=0
+- dig_cmd +short @10.53.0.1 unk$i.example type123 class10 >dig.out.$i.test$n
+- echo '\# 1 00' | diff - dig.out.$i.test$n || ret=1
++ dig_full @10.53.0.1 unk$i.example type123 class10 >dig.out.$i.test$n
++ grep -q "NOTIMP" dig.out.$i.test$n || ret=1
+ if [ $ret != 0 ]; then
+ echo_i "#$i failed"
+ fi
+diff --git a/lib/ns/client.c b/lib/ns/client.c
+index 4c538f5f9f..8680cd6c47 100644
+--- a/lib/ns/client.c
++++ b/lib/ns/client.c
+@@ -44,6 +44,7 @@
+ #include <dns/dispatch.h>
+ #include <dns/dnstap.h>
+ #include <dns/edns.h>
++#include <dns/enumclass.h>
+ #include <dns/events.h>
+ #include <dns/message.h>
+ #include <dns/peer.h>
+@@ -2083,7 +2084,9 @@ ns__client_request(isc_nmhandle_t *handle, isc_result_t eresult,
+ }
+ }
+
+- if (client->message->rdclass == 0) {
++ char classbuf[DNS_RDATACLASS_FORMATSIZE];
++ switch (client->message->rdclass) {
++ case dns_rdataclass_reserved0:
+ if ((client->attributes & NS_CLIENTATTR_WANTCOOKIE) != 0 &&
+ client->message->opcode == dns_opcode_query &&
+ client->message->counts[DNS_SECTION_QUESTION] == 0U)
+@@ -2102,12 +2105,46 @@ ns__client_request(isc_nmhandle_t *handle, isc_result_t eresult,
+ return;
+ }
+
++ ns_client_dumpmessage(client,
++ "message class could not be determined");
++ ns_client_error(client, notimp ? DNS_R_NOTIMP : DNS_R_FORMERR);
++ return;
++ case dns_rdataclass_in:
++ break;
++ case dns_rdataclass_chaos:
++ break;
++ case dns_rdataclass_hs:
++ break;
++ case dns_rdataclass_none:
++ if (client->message->opcode != dns_opcode_update) {
++ ns_client_dumpmessage(client,
++ "message class NONE can be only "
++ "used in DNS updates");
++ ns_client_error(client, DNS_R_FORMERR);
++ return;
++ }
++ break;
++ case dns_rdataclass_any:
++ /*
++ * Required for TKEY negotiation.
++ */
++ if (client->message->tkey == 0) {
++ ns_client_dumpmessage(client,
++ "message class ANY can be only "
++ "used for TKEY negotiation");
++ ns_client_error(client, DNS_R_FORMERR);
++ return;
++ }
++ break;
++ default:
++ dns_rdataclass_format(client->message->rdclass, classbuf,
++ sizeof(classbuf));
++ ns_client_dumpmessage(client, NULL);
+ ns_client_log(client, NS_LOGCATEGORY_CLIENT,
+ NS_LOGMODULE_CLIENT, ISC_LOG_DEBUG(1),
+- "message class could not be determined");
+- ns_client_dumpmessage(client, "message class could not be "
+- "determined");
+- ns_client_error(client, notimp ? DNS_R_NOTIMP : DNS_R_FORMERR);
++ "invalid message class: %s", classbuf);
++
++ ns_client_error(client, DNS_R_NOTIMP);
+ return;
+ }
+
+@@ -2140,7 +2177,7 @@ ns__client_request(isc_nmhandle_t *handle, isc_result_t eresult,
+ ns_client_log(client, NS_LOGCATEGORY_CLIENT,
+ NS_LOGMODULE_CLIENT, ISC_LOG_DEBUG(1),
+ "no matching view in class '%s'", classname);
+- ns_client_dumpmessage(client, "no matching view in class");
++ ns_client_dumpmessage(client, NULL);
+ ns_client_extendederror(client, DNS_EDE_PROHIBITED, NULL);
+ ns_client_error(client, notimp ? DNS_R_NOTIMP : DNS_R_REFUSED);
+ return;
+@@ -2781,7 +2818,7 @@ ns_client_dumpmessage(ns_client_t *client, const char *reason) {
+ int len = 1024;
+ isc_result_t result;
+
+- if (!isc_log_wouldlog(ns_lctx, ISC_LOG_DEBUG(1))) {
++ if (!isc_log_wouldlog(ns_lctx, ISC_LOG_DEBUG(1)) || reason == NULL) {
+ return;
+ }
+
+--
+2.35.6
+
diff --git a/meta/recipes-connectivity/bind/bind/CVE-2026-5946_p4.patch b/meta/recipes-connectivity/bind/bind/CVE-2026-5946_p4.patch
new file mode 100644
index 0000000000..c552168f81
--- /dev/null
+++ b/meta/recipes-connectivity/bind/bind/CVE-2026-5946_p4.patch
@@ -0,0 +1,56 @@
+From d34a7e777747cfe1a3f9a133b031577921653dbf Mon Sep 17 00:00:00 2001
+From: Mark Andrews <marka@isc.org>
+Date: Wed, 4 Mar 2026 10:00:56 +1100
+Subject: [PATCH] Reject meta-classes in UPDATE and NOTIFY messages
+
+NOTIFY and UPDATE messages must specify a data class in the
+QUESTION/ZONE section. NONE and ANY are meta-classes and not
+appropriate here. Return FORMERR if either is used.
+
+Rejecting messages with a query class of NONE addresses YWH-PGM40640-72,
+YWH-PGM40640-82, and YWH-PGM40640-83. Rejecting messages with a query
+class of ANY addresses YWH-PGM40640-87, YWH-PGM40640-88, and
+YWH-PGM40640-117.
+
+Fixes: isc-projects/bind9#5778
+Fixes: isc-projects/bind9#5782
+Fixes: isc-projects/bind9#5783
+Fixes: isc-projects/bind9#5797
+Fixes: isc-projects/bind9#5798
+Fixes: isc-projects/bind9#5853
+
+CVE: CVE-2026-5946
+Upstream-Status: Backport [https://gitlab.com/isc-projects/bind9/-/commit/185c10981b941bfa5b753b3624b6e11ccca8737f]
+
+(cherry picked from commit c66a1b1e1bfd6c79d7b9bc8d4a59e69f4faa1563)
+(cherry picked from commit 185c10981b941bfa5b753b3624b6e11ccca8737f)
+Signed-off-by: Ashishkumar Parmar <asparmar@cisco.com>
+---
+ lib/dns/message.c | 11 +++++++++++
+ 1 file changed, 11 insertions(+)
+
+diff --git a/lib/dns/message.c b/lib/dns/message.c
+index 541a854db0..38f5640500 100644
+--- a/lib/dns/message.c
++++ b/lib/dns/message.c
+@@ -1080,6 +1080,17 @@ getquestions(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t *dctx,
+ rdtype = isc_buffer_getuint16(source);
+ rdclass = isc_buffer_getuint16(source);
+
++ /*
++ * Notify and update messages need to specify the data class.
++ */
++ if ((msg->opcode == dns_opcode_update ||
++ msg->opcode == dns_opcode_notify) &&
++ (rdclass == dns_rdataclass_none ||
++ rdclass == dns_rdataclass_any))
++ {
++ DO_ERROR(DNS_R_FORMERR);
++ }
++
+ /*
+ * If this class is different than the one we already read,
+ * this is an error.
+--
+2.35.6
+
diff --git a/meta/recipes-connectivity/bind/bind/CVE-2026-5946_p5.patch b/meta/recipes-connectivity/bind/bind/CVE-2026-5946_p5.patch
new file mode 100644
index 0000000000..59bd66d345
--- /dev/null
+++ b/meta/recipes-connectivity/bind/bind/CVE-2026-5946_p5.patch
@@ -0,0 +1,40 @@
+From 544f128439772cd710e541df1e1081c308642533 Mon Sep 17 00:00:00 2001
+From: Evan Hunt <each@isc.org>
+Date: Tue, 17 Mar 2026 13:24:43 -0700
+Subject: [PATCH] Skip "deny-answer-address" for non-IN addresses
+
+Ensure that we don't attempt an ACL match for answer addresses
+when handling a class-CHAOS zone. This is an additional line of
+defense for YWH-PGM40640-74.
+
+CVE: CVE-2026-5946
+Upstream-Status: Backport [https://gitlab.com/isc-projects/bind9/-/commit/772d1d5f905c819d7155e76a08c33218bfcc973e]
+
+(cherry picked from commit e62673c765b52307c800e86f0185fe52b573c145)
+(cherry picked from commit 772d1d5f905c819d7155e76a08c33218bfcc973e)
+Signed-off-by: Ashishkumar Parmar <asparmar@cisco.com>
+---
+ lib/dns/resolver.c | 7 +++++++
+ 1 file changed, 7 insertions(+)
+
+diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c
+index c1584c3399..7788171bf2 100644
+--- a/lib/dns/resolver.c
++++ b/lib/dns/resolver.c
+@@ -7333,6 +7333,13 @@ is_answeraddress_allowed(dns_view_t *view, dns_name_t *name,
+ }
+ }
+
++ /*
++ * deny-answer-address doesn't apply to non-IN classes.
++ */
++ if (rdataset->rdclass != dns_rdataclass_in) {
++ return true;
++ }
++
+ /*
+ * Otherwise, search the filter list for a match for each
+ * address record. If a match is found, the address should be
+--
+2.35.6
+
diff --git a/meta/recipes-connectivity/bind/bind/CVE-2026-5946_p6.patch b/meta/recipes-connectivity/bind/bind/CVE-2026-5946_p6.patch
new file mode 100644
index 0000000000..66d8dbd268
--- /dev/null
+++ b/meta/recipes-connectivity/bind/bind/CVE-2026-5946_p6.patch
@@ -0,0 +1,336 @@
+From 760a2e15e3f022a4dcdf9f8f2da3163c2480713f Mon Sep 17 00:00:00 2001
+From: Evan Hunt <each@isc.org>
+Date: Tue, 17 Mar 2026 13:45:11 -0700
+Subject: [PATCH] Test CHAOS view recursion behavior
+
+Check that recursive and forward queries to views of type CHAOS
+are REFUSED, but that authoritative queries are answered correctly.
+
+CVE: CVE-2026-5946
+Upstream-Status: Backport [https://gitlab.com/isc-projects/bind9/-/commit/ba6b159d880e803decb85fdc88280b23d6f3a652]
+
+Backport Changes:
+- Adjusted the added isctest.check.formerr helper to match the
+ BIND 9.18.44 check.py dns_rcode import style.
+
+(cherry picked from commit f33927cd3dd1195f3e70f5798ff7c384f265867e)
+(cherry picked from commit ba6b159d880e803decb85fdc88280b23d6f3a652)
+Signed-off-by: Ashishkumar Parmar <asparmar@cisco.com>
+---
+ bin/tests/system/checkconf/tests.sh | 11 ++++
+ .../checkconf/warn-chaos-recursion.conf | 12 +++++
+ bin/tests/system/class/ns1/chaos.db.in | 4 ++
+ bin/tests/system/class/ns1/named.conf.j2 | 31 +++++++++++
+ bin/tests/system/class/ns2/example.db.in | 6 +++
+ bin/tests/system/class/ns2/localhost.db.in | 6 +++
+ bin/tests/system/class/ns2/named.conf.j2 | 42 +++++++++++++++
+ bin/tests/system/class/ns3/named.conf.j2 | 28 ++++++++++
+ bin/tests/system/class/setup.sh | 19 +++++++
+ bin/tests/system/class/tests_class_chaos.py | 54 +++++++++++++++++++
+ bin/tests/system/isctest/check.py | 4 ++
+ 11 files changed, 217 insertions(+)
+ create mode 100644 bin/tests/system/checkconf/warn-chaos-recursion.conf
+ create mode 100644 bin/tests/system/class/ns1/chaos.db.in
+ create mode 100644 bin/tests/system/class/ns1/named.conf.j2
+ create mode 100644 bin/tests/system/class/ns2/example.db.in
+ create mode 100644 bin/tests/system/class/ns2/localhost.db.in
+ create mode 100644 bin/tests/system/class/ns2/named.conf.j2
+ create mode 100644 bin/tests/system/class/ns3/named.conf.j2
+ create mode 100644 bin/tests/system/class/setup.sh
+ create mode 100644 bin/tests/system/class/tests_class_chaos.py
+
+diff --git a/bin/tests/system/checkconf/tests.sh b/bin/tests/system/checkconf/tests.sh
+index 5da8a20ee1..f29949535a 100644
+--- a/bin/tests/system/checkconf/tests.sh
++++ b/bin/tests/system/checkconf/tests.sh
+@@ -820,5 +820,16 @@ if [ $ret != 0 ]; then
+ fi
+ status=$((status + ret))
+
++n=$((n + 1))
++echo_i "check 'recursion yes;' is warned and disabled in a non-IN view ($n)"
++ret=0
++$CHECKCONF warn-chaos-recursion.conf >checkconf.out$n 2>&1 || ret=1
++grep -F "recursion will be disabled" checkconf.out$n >/dev/null || ret=1
++if [ $ret != 0 ]; then
++ echo_i "failed"
++ ret=1
++fi
++status=$((status + ret))
++
+ echo_i "exit status: $status"
+ [ $status -eq 0 ] || exit 1
+diff --git a/bin/tests/system/checkconf/warn-chaos-recursion.conf b/bin/tests/system/checkconf/warn-chaos-recursion.conf
+new file mode 100644
+index 0000000000..01965102a4
+--- /dev/null
++++ b/bin/tests/system/checkconf/warn-chaos-recursion.conf
+@@ -0,0 +1,12 @@
++options {
++ directory ".";
++};
++
++view chaos ch {
++ match-clients { any; };
++ recursion yes;
++ zone "." {
++ type hint;
++ file "chaos.hints";
++ };
++};
+diff --git a/bin/tests/system/class/ns1/chaos.db.in b/bin/tests/system/class/ns1/chaos.db.in
+new file mode 100644
+index 0000000000..43ca58ffa8
+--- /dev/null
++++ b/bin/tests/system/class/ns1/chaos.db.in
+@@ -0,0 +1,4 @@
++. CH NS ns.root.
++ns.root. CH A ns.root. 1
++ns.root. CH AAAA \# 1 00
++
+diff --git a/bin/tests/system/class/ns1/named.conf.j2 b/bin/tests/system/class/ns1/named.conf.j2
+new file mode 100644
+index 0000000000..76f85fc6c9
+--- /dev/null
++++ b/bin/tests/system/class/ns1/named.conf.j2
+@@ -0,0 +1,31 @@
++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; };
++};
++
++key rndc_key {
++ secret "1234abcd8765";
++ algorithm @DEFAULT_HMAC@;
++};
++
++controls {
++ inet 10.53.0.1 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
++};
++
++view chaos ch {
++ match-clients { any; };
++ recursion yes;
++ zone "." {
++ type hint;
++ file "chaos.db";
++ };
++ zone "version.bind" {
++ type primary;
++ database "_builtin version";
++ };
++};
+diff --git a/bin/tests/system/class/ns2/example.db.in b/bin/tests/system/class/ns2/example.db.in
+new file mode 100644
+index 0000000000..a658ddbd89
+--- /dev/null
++++ b/bin/tests/system/class/ns2/example.db.in
+@@ -0,0 +1,6 @@
++$TTL 300
++@ CH SOA ns.example. hostmaster.example. 1 3600 1200 604800 300
++@ CH NS ns.example.
++ns CH TXT "ns"
++a CH A target.example. 1
++target CH TXT "target"
+diff --git a/bin/tests/system/class/ns2/localhost.db.in b/bin/tests/system/class/ns2/localhost.db.in
+new file mode 100644
+index 0000000000..baa5f74862
+--- /dev/null
++++ b/bin/tests/system/class/ns2/localhost.db.in
+@@ -0,0 +1,6 @@
++$ORIGIN 1.0.0.127.in-addr.arpa.
++$TTL 300
++@ IN SOA ns hostmaster 1 3600 900 604800 300
++@ IN NS ns
++ns IN A 127.0.0.1
++@ IN KX 10 target.example.
+diff --git a/bin/tests/system/class/ns2/named.conf.j2 b/bin/tests/system/class/ns2/named.conf.j2
+new file mode 100644
+index 0000000000..5618c15216
+--- /dev/null
++++ b/bin/tests/system/class/ns2/named.conf.j2
+@@ -0,0 +1,42 @@
++options {
++ directory ".";
++ 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; };
++};
++
++key rndc_key {
++ secret "1234abcd8765";
++ algorithm @DEFAULT_HMAC@;
++};
++
++controls {
++ inet 10.53.0.2 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
++};
++
++view default {
++ match-clients { any; };
++ recursion no;
++ dnssec-validation no;
++ zone "1.0.0.127.in-addr.arpa." {
++ type primary;
++ file "localhost.db";
++ update-policy {
++ grant * tcp-self . ANY;
++ };
++ };
++};
++
++view chaos ch {
++ match-clients { any; };
++ recursion no;
++ zone example {
++ type primary;
++ file "example.db";
++ allow-update { any; };
++ };
++};
+diff --git a/bin/tests/system/class/ns3/named.conf.j2 b/bin/tests/system/class/ns3/named.conf.j2
+new file mode 100644
+index 0000000000..3016333aad
+--- /dev/null
++++ b/bin/tests/system/class/ns3/named.conf.j2
+@@ -0,0 +1,28 @@
++options {
++ directory ".";
++ 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; };
++};
++
++key rndc_key {
++ secret "1234abcd8765";
++ algorithm @DEFAULT_HMAC@;
++};
++
++controls {
++ inet 10.53.0.3 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
++};
++
++view chaos ch {
++ match-clients { any; };
++ recursion yes;
++ dnssec-validation no;
++ forward only;
++ forwarders port @PORT@ { 10.53.0.2; };
++ deny-answer-addresses { 0.0.0.0/0; ::/0; };
++};
+diff --git a/bin/tests/system/class/setup.sh b/bin/tests/system/class/setup.sh
+new file mode 100644
+index 0000000000..c70a2f8290
+--- /dev/null
++++ b/bin/tests/system/class/setup.sh
+@@ -0,0 +1,19 @@
++#!/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
++
++cp ns1/chaos.db.in ns1/chaos.db
++cp ns2/example.db.in ns2/example.db
++cp ns2/localhost.db.in ns2/localhost.db
+diff --git a/bin/tests/system/class/tests_class_chaos.py b/bin/tests/system/class/tests_class_chaos.py
+new file mode 100644
+index 0000000000..5b4fef9ae4
+--- /dev/null
++++ b/bin/tests/system/class/tests_class_chaos.py
+@@ -0,0 +1,54 @@
++# 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.opcode
++import pytest
++
++import isctest
++
++pytestmark = pytest.mark.extra_artifacts(
++ [
++ "*/*.db",
++ ]
++)
++
++
++def test_chaos_recursion():
++ msg = isctest.query.create("foo.example.", "TXT", qclass="CH")
++ res = isctest.query.udp(msg, "10.53.0.1")
++ isctest.check.refused(res)
++
++
++def test_chaos_auth():
++ msg = isctest.query.create("a.example.", "A", qclass="CH")
++ res = isctest.query.udp(msg, "10.53.0.2")
++ isctest.check.noerror(res)
++
++
++def test_chaos_forward():
++ msg = isctest.query.create("a.example.", "A", qclass="CH")
++ res = isctest.query.udp(msg, "10.53.0.3")
++ isctest.check.refused(res)
++
++
++def test_chaos_notify():
++ msg = isctest.query.create("example.", "SOA", qclass="CH", rd=False, dnssec=False)
++ msg.set_opcode(dns.opcode.NOTIFY)
++ msg.flags = dns.opcode.to_flags(dns.opcode.NOTIFY)
++ res = isctest.query.udp(msg, "10.53.0.2")
++ isctest.check.notimp(res)
++
++
++def test_query_class_none():
++ msg = isctest.query.create("example.", "A", qclass="NONE")
++ res = isctest.query.udp(msg, "10.53.0.2")
++ isctest.check.formerr(res)
+diff --git a/bin/tests/system/isctest/check.py b/bin/tests/system/isctest/check.py
+index e7187b8853..597f3aaa7f 100644
+--- a/bin/tests/system/isctest/check.py
++++ b/bin/tests/system/isctest/check.py
+@@ -42,6 +42,10 @@ def servfail(message: dns.message.Message) -> None:
+ rcode(message, dns_rcode.SERVFAIL)
+
+
++def formerr(message: dns.message.Message) -> None:
++ rcode(message, dns.rcode.FORMERR)
++
++
+ def adflag(message: dns.message.Message) -> None:
+ assert (message.flags & dns.flags.AD) != 0, str(message)
+
+--
+2.35.6
+
diff --git a/meta/recipes-connectivity/bind/bind/CVE-2026-5946_p7.patch b/meta/recipes-connectivity/bind/bind/CVE-2026-5946_p7.patch
new file mode 100644
index 0000000000..8e8ae87b58
--- /dev/null
+++ b/meta/recipes-connectivity/bind/bind/CVE-2026-5946_p7.patch
@@ -0,0 +1,153 @@
+From b33de089c7955453f07345ada5386e289c6cc74c Mon Sep 17 00:00:00 2001
+From: Evan Hunt <each@isc.org>
+Date: Tue, 17 Mar 2026 13:45:11 -0700
+Subject: [PATCH] Test UPDATE behavior in CHAOS and other non-IN classes
+
+Send various UPDATE requests that are known to have caused
+crashes previously with deliberately misconfigured non-IN
+zones; confirm that UPDATE is not processed.
+
+CVE: CVE-2026-5946
+Upstream-Status: Backport [https://gitlab.com/isc-projects/bind9/-/commit/e5b3149b70ed24f7590b6bc08aad4492a00c2022]
+
+(cherry picked from commit e2f7ba2a4b6e7e5dba2fb1a2c9b2f0323e9a88be)
+(cherry picked from commit e5b3149b70ed24f7590b6bc08aad4492a00c2022)
+Signed-off-by: Ashishkumar Parmar <asparmar@cisco.com>
+---
+ bin/named/server.c | 1 -
+ bin/tests/system/class/ns2/localhost.db.in | 5 +
+ bin/tests/system/class/tests_class_update.py | 96 ++++++++++++++++++++
+ 3 files changed, 101 insertions(+), 1 deletion(-)
+ create mode 100644 bin/tests/system/class/tests_class_update.py
+
+diff --git a/bin/named/server.c b/bin/named/server.c
+index 59e2aa3313..577efbb299 100644
+--- a/bin/named/server.c
++++ b/bin/named/server.c
+@@ -5465,7 +5465,6 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, cfg_obj_t *config,
+ NULL, actx, named_g_mctx, &view->cacheonacl));
+
+ if (view->rdclass != dns_rdataclass_in) {
+- view->recursion = false;
+ dns_acl_none(named_g_mctx, &view->recursionacl);
+ dns_acl_none(named_g_mctx, &view->recursiononacl);
+ } else {
+diff --git a/bin/tests/system/class/ns2/localhost.db.in b/bin/tests/system/class/ns2/localhost.db.in
+index baa5f74862..a50e5167a9 100644
+--- a/bin/tests/system/class/ns2/localhost.db.in
++++ b/bin/tests/system/class/ns2/localhost.db.in
+@@ -3,4 +3,9 @@ $TTL 300
+ @ IN SOA ns hostmaster 1 3600 900 604800 300
+ @ IN NS ns
+ ns IN A 127.0.0.1
++
+ @ IN KX 10 target.example.
++@ IN PX 10 map822.example. mapx400.example.
++@ IN NSAP 0x47000580ffff0000000001e133ffffff00016200
++@ IN NSAP-PTR target.example.
++@ in EID \# 01 aa
+diff --git a/bin/tests/system/class/tests_class_update.py b/bin/tests/system/class/tests_class_update.py
+new file mode 100644
+index 0000000000..e53bbc77ea
+--- /dev/null
++++ b/bin/tests/system/class/tests_class_update.py
+@@ -0,0 +1,96 @@
++# 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 socket
++import struct
++
++from dns import rdataclass, rdatatype, update
++
++import pytest
++
++import isctest
++
++pytestmark = pytest.mark.extra_artifacts(
++ [
++ "*/*.db",
++ ]
++)
++
++
++def encode_name(name: str) -> bytes:
++ out = b""
++ for label in name.rstrip(".").split("."):
++ out += bytes([len(label)]) + label.encode("ascii")
++ return out + b"\x00"
++
++
++@pytest.mark.parametrize(
++ "rdtype,rdclass,ttl,rdata",
++ [
++ (rdatatype.SRV, rdataclass.NONE, 0, b"\x00"),
++ (rdatatype.KX, rdataclass.NONE, 0, b""),
++ (rdatatype.PX, rdataclass.NONE, 0, b""),
++ (rdatatype.NSAP, rdataclass.NONE, 0, b""),
++ (rdatatype.NSAP_PTR, rdataclass.NONE, 0, b""),
++ (31, rdataclass.NONE, 0, b""), # dnspython doesn't define type EID
++ ],
++)
++def test_class_invalid(rdtype, rdclass, ttl, rdata, named_port):
++ # these update messages are badly formatted, so we construct
++ # them manually instead of using dnspython.
++
++ # opcode=UPDATE, 1 RRset in ZONE, 1 RRset in UPDATE
++ header = struct.pack("!HHHHHH", 0, 0x2800, 1, 0, 1, 0)
++
++ # ZONE section: QNAME=<zone>, QTYPE=SOA, QCLASS=ANY
++ zone_q = encode_name("1.0.0.127.in-addr.arpa") + struct.pack("!HH", 6, 255)
++
++ # UPDATE section RR:
++ update_rr = (
++ encode_name("1.0.0.127.in-addr.arpa")
++ + struct.pack("!HHIH", rdtype, rdclass, ttl, len(rdata))
++ + rdata
++ )
++
++ m = header + zone_q + update_rr
++ packet = struct.pack("!H", len(m)) + m
++
++ with socket.create_connection(
++ ("10.53.0.2", named_port), source_address=("127.0.0.1", 0), timeout=2.0
++ ) as s:
++ s.sendall(packet)
++ try:
++ rwire = s.recv(4096)
++ res = dns.message.from_wire(rwire)
++ isctest.check.formerr(res)
++ except Exception: # pylint: disable=broad-except
++ pass
++
++ # check the server is answering
++ msg = isctest.query.create("1.0.0.127.in-addr.arpa", "SRV")
++ res = isctest.query.udp(msg, "10.53.0.2")
++ isctest.check.noerror(res)
++ isctest.check.rr_count_eq(res.answer, 0)
++
++
++@pytest.mark.parametrize(
++ "rdtype,rdata",
++ [
++ (rdatatype.SVCB, "\\# 02 0000"),
++ (rdatatype.WKS, "\\# 02 4142"),
++ (rdatatype.WKS, "\\# 02 4344"),
++ ],
++)
++def test_class_chaosupdate(rdtype, rdata):
++ up = update.UpdateMessage("example.", rdclass=rdataclass.CHAOS)
++ up.add("foo.example.", 300, rdtype, rdata)
++ res = isctest.query.tcp(up, "10.53.0.2")
++ isctest.check.notimp(res)
+--
+2.35.6
+
diff --git a/meta/recipes-connectivity/bind/bind/CVE-2026-5946_p8.patch b/meta/recipes-connectivity/bind/bind/CVE-2026-5946_p8.patch
new file mode 100644
index 0000000000..216d115f80
--- /dev/null
+++ b/meta/recipes-connectivity/bind/bind/CVE-2026-5946_p8.patch
@@ -0,0 +1,225 @@
+From 992e238e6ed8e4a43948d8a103a219d2512b31f4 Mon Sep 17 00:00:00 2001
+From: Evan Hunt <each@isc.org>
+Date: Mon, 9 Mar 2026 15:50:04 +1100
+Subject: [PATCH] Test server behavior when sending various UPDATE requests
+
+Send update messages for zones with CLASS0, ANY and NONE. The class
+ANY UPDATE also attempts to delete a KX record in an existing IN
+class zone to trigger a REQUIRE.
+
+Test that the server is still running.
+
+CVE: CVE-2026-5946
+Upstream-Status: Backport [https://gitlab.com/isc-projects/bind9/-/commit/7e3d49815c3b8243b009c29955cef018fe87b7f0]
+
+(cherry picked from commit 1fa1e84d286d5a6d9d3b72ed1c2c29142f40c81d)
+(cherry picked from commit 7e3d49815c3b8243b009c29955cef018fe87b7f0)
+Signed-off-by: Ashishkumar Parmar <asparmar@cisco.com>
+---
+ bin/tests/system/class/tests_class_update.py | 45 +++++++++++++++++++-
+ bin/tests/system/nsupdate/setup.sh | 1 +
+ bin/tests/system/nsupdate/tests.sh | 20 +++------
+ bin/tests/system/packet.pl | 25 +++++++++--
+ 4 files changed, 71 insertions(+), 20 deletions(-)
+
+diff --git a/bin/tests/system/class/tests_class_update.py b/bin/tests/system/class/tests_class_update.py
+index e53bbc77ea..30e3ba6d2a 100644
+--- a/bin/tests/system/class/tests_class_update.py
++++ b/bin/tests/system/class/tests_class_update.py
+@@ -12,7 +12,7 @@
+ import socket
+ import struct
+
+-from dns import rdataclass, rdatatype, update
++from dns import message, rdataclass, rdatatype, update
+
+ import pytest
+
+@@ -35,6 +35,7 @@ def encode_name(name: str) -> bytes:
+ @pytest.mark.parametrize(
+ "rdtype,rdclass,ttl,rdata",
+ [
++ (rdatatype.SRV, rdataclass.NONE, 0, b"\x00\x00\x00\x00\x00\x00\x01"),
+ (rdatatype.SRV, rdataclass.NONE, 0, b"\x00"),
+ (rdatatype.KX, rdataclass.NONE, 0, b""),
+ (rdatatype.PX, rdataclass.NONE, 0, b""),
+@@ -69,7 +70,7 @@ def test_class_invalid(rdtype, rdclass, ttl, rdata, named_port):
+ s.sendall(packet)
+ try:
+ rwire = s.recv(4096)
+- res = dns.message.from_wire(rwire)
++ res = message.from_wire(rwire)
+ isctest.check.formerr(res)
+ except Exception: # pylint: disable=broad-except
+ pass
+@@ -94,3 +95,43 @@ def test_class_chaosupdate(rdtype, rdata):
+ up.add("foo.example.", 300, rdtype, rdata)
+ res = isctest.query.tcp(up, "10.53.0.2")
+ isctest.check.notimp(res)
++
++
++def test_class_undefined(ns2):
++ up = update.UpdateMessage(".", rdclass=257)
++ up.present(".", 0)
++ up.answer[0].rdclass = rdataclass.NONE
++ with ns2.watch_log_from_here() as watcher:
++ res = isctest.query.tcp(up, "10.53.0.2")
++ isctest.check.notimp(res)
++ watcher.wait_for_line("invalid message class: CLASS257")
++
++
++def test_class_zero(ns2):
++ up = update.UpdateMessage(".", rdclass=0)
++ up.present(".", 0)
++ up.answer[0].rdclass = rdataclass.NONE
++ with ns2.watch_log_from_here() as watcher:
++ res = isctest.query.tcp(up, "10.53.0.2")
++ isctest.check.formerr(res)
++ watcher.wait_for_line("message class could not be determined")
++
++
++def test_class_any(ns2):
++ up = update.UpdateMessage(".", rdclass=rdataclass.ANY)
++ up.present(".", 0)
++ up.answer[0].rdclass = rdataclass.NONE
++ with ns2.watch_log_from_here() as watcher:
++ res = isctest.query.tcp(up, "10.53.0.2")
++ isctest.check.formerr(res)
++ watcher.wait_for_line("message parsing failed: FORMERR")
++
++
++def test_class_none(ns2):
++ up = update.UpdateMessage(".", rdclass=rdataclass.NONE)
++ up.present(".", 0)
++ up.answer[0].rdclass = rdataclass.NONE
++ with ns2.watch_log_from_here() as watcher:
++ res = isctest.query.tcp(up, "10.53.0.2")
++ isctest.check.formerr(res)
++ watcher.wait_for_line("message parsing failed: FORMERR")
+diff --git a/bin/tests/system/nsupdate/setup.sh b/bin/tests/system/nsupdate/setup.sh
+index 4ee045facc..4402329e3b 100644
+--- a/bin/tests/system/nsupdate/setup.sh
++++ b/bin/tests/system/nsupdate/setup.sh
+@@ -61,6 +61,7 @@ update.nil IN SOA ns1.example.nil. hostmaster.example.nil. (
+ 3600 ; minimum (1 hour)
+ )
+ update.nil. NS ns1.update.nil.
++update.nil. KX 0 .
+ ns1.update.nil. A 10.53.0.2
+ ns2.update.nil. AAAA ::1
+ EOF
+diff --git a/bin/tests/system/nsupdate/tests.sh b/bin/tests/system/nsupdate/tests.sh
+index be0df8aaad..521ba46eff 100755
+--- a/bin/tests/system/nsupdate/tests.sh
++++ b/bin/tests/system/nsupdate/tests.sh
+@@ -340,8 +340,10 @@ grep "status: NOERROR" dig.out.ns1.$n >/dev/null || ret=1
+ n=$((n + 1))
+ ret=0
+ echo_i "check that TYPE=0 update is handled ($n)"
++nextpart ns1/named.run >/dev/null
+ echo "a0e4280000010000000100000000060001c00c000000fe000000000000" \
+- | $PERL ../packet.pl -a 10.53.0.1 -p ${PORT} -t tcp >/dev/null || ret=1
++ | $PERL ../packet.pl -a 10.53.0.1 -p ${PORT} -t tcp -b >/dev/null || ret=1
++wait_for_log 2 "message parsing failed: FORMERR" ns1/named.run || ret=1
+ $DIG $DIGOPTS +tcp version.bind txt ch @10.53.0.1 >dig.out.ns1.$n || ret=1
+ grep "status: NOERROR" dig.out.ns1.$n >/dev/null || ret=1
+ [ $ret = 0 ] || {
+@@ -352,20 +354,10 @@ grep "status: NOERROR" dig.out.ns1.$n >/dev/null || ret=1
+ n=$((n + 1))
+ ret=0
+ echo_i "check that TYPE=0 additional data is handled ($n)"
++nextpart ns1/named.run >/dev/null
+ echo "a0e4280000010000000000010000060001c00c000000fe000000000000" \
+- | $PERL ../packet.pl -a 10.53.0.1 -p ${PORT} -t tcp >/dev/null || ret=1
+-$DIG $DIGOPTS +tcp version.bind txt ch @10.53.0.1 >dig.out.ns1.$n || ret=1
+-grep "status: NOERROR" dig.out.ns1.$n >/dev/null || ret=1
+-[ $ret = 0 ] || {
+- echo_i "failed"
+- status=1
+-}
+-
+-n=$((n + 1))
+-ret=0
+-echo_i "check that update to undefined class is handled ($n)"
+-echo "a0e4280000010001000000000000060101c00c000000fe000000000000" \
+- | $PERL ../packet.pl -a 10.53.0.1 -p ${PORT} -t tcp >/dev/null || ret=1
++ | $PERL ../packet.pl -a 10.53.0.1 -p ${PORT} -t tcp -b >/dev/null || ret=1
++wait_for_log 2 "message parsing failed: FORMERR" ns1/named.run || ret=1
+ $DIG $DIGOPTS +tcp version.bind txt ch @10.53.0.1 >dig.out.ns1.$n || ret=1
+ grep "status: NOERROR" dig.out.ns1.$n >/dev/null || ret=1
+ [ $ret = 0 ] || {
+diff --git a/bin/tests/system/packet.pl b/bin/tests/system/packet.pl
+index 900a0c071e..afb9f4784d 100644
+--- a/bin/tests/system/packet.pl
++++ b/bin/tests/system/packet.pl
+@@ -40,6 +40,7 @@
+ # -p <port>: specify port
+ # -t <protocol>: specify UDP or TCP
+ # -r <num>: send packet <num> times
++# -b: blocking io
+ # -d: dump response packets
+ #
+ # If not specified, address defaults to 127.0.0.1, port to 53, protocol
+@@ -51,6 +52,8 @@ use strict;
+ use Getopt::Std;
+ use IO::File;
+ use IO::Socket;
++use Net::DNS;
++use Net::DNS::Packet;
+
+ sub usage {
+ print ("Usage: packet.pl [-a address] [-d] [-p port] [-t (tcp|udp)] [-r <repeats>] [file]\n");
+@@ -61,8 +64,6 @@ my $sock;
+ my $proto;
+
+ sub dumppacket {
+- use Net::DNS;
+- use Net::DNS::Packet;
+
+ my $rin;
+ my $rout;
+@@ -96,7 +97,7 @@ sub dumppacket {
+ }
+
+ my %options={};
+-getopts("a:dp:t:r:", \%options);
++getopts("a:bdp:t:r:", \%options);
+
+ my $addr = "127.0.0.1";
+ $addr = $options{a} if defined $options{a};
+@@ -111,6 +112,8 @@ usage if ($proto !~ /^(udp|tcp)$/);
+ my $repeats = 1;
+ $repeats = $options{r} if defined $options{r};
+
++my $blocking = defined $options{b} ? 1 : 0;
++
+ my $file = "STDIN";
+ if (@ARGV >= 1) {
+ my $filename = shift @ARGV;
+@@ -132,8 +135,22 @@ my $len = length $data;
+ my $output = unpack("H*", $data);
+ print ("sending $repeats time(s): $output\n");
+
++
++if (defined $options{d}) {
++ my $request;
++ if ($Net::DNS::VERSION > 0.68) {
++ $request = new Net::DNS::Packet(\$data, 0);
++ $@ and die $@;
++ } else {
++ my $err;
++ ($request, $err) = new Net::DNS::Packet(\$data, 0);
++ $err and die $err;
++ }
++ $request->print;
++}
++
+ $sock = IO::Socket::INET->new(PeerAddr => $addr, PeerPort => $port,
+- Blocking => 0,
++ Blocking => $blocking,
+ Proto => $proto,) or die "$!";
+
+ STDOUT->autoflush(1);
+--
+2.35.6
+
diff --git a/meta/recipes-connectivity/bind/bind/CVE-2026-5946_p9.patch b/meta/recipes-connectivity/bind/bind/CVE-2026-5946_p9.patch
new file mode 100644
index 0000000000..c15ee394f3
--- /dev/null
+++ b/meta/recipes-connectivity/bind/bind/CVE-2026-5946_p9.patch
@@ -0,0 +1,44 @@
+From 42b3fcf5285b29f2b885eec67a10cd3de6f11805 Mon Sep 17 00:00:00 2001
+From: Evan Hunt <each@isc.org>
+Date: Mon, 23 Feb 2026 16:27:52 -0800
+Subject: [PATCH] Make the RD flag optional in isctest.query()
+
+Add an 'rd' parameter (default True) to isctest.query.create() so
+that non-recursive queries can be sent with rd=False.
+
+CVE: CVE-2026-5946
+Upstream-Status: Backport [https://gitlab.com/isc-projects/bind9/-/commit/d79f2d3f35887ea4525e283d389d9078fa1ef439]
+
+(cherry picked from commit 12e511310024aac38ce223ee47b5108f06caf8f9)
+(cherry picked from commit d79f2d3f35887ea4525e283d389d9078fa1ef439)
+Signed-off-by: Ashishkumar Parmar <asparmar@cisco.com>
+---
+ bin/tests/system/isctest/query.py | 5 ++++-
+ 1 file changed, 4 insertions(+), 1 deletion(-)
+
+diff --git a/bin/tests/system/isctest/query.py b/bin/tests/system/isctest/query.py
+index b11b165f85..861a002220 100644
+--- a/bin/tests/system/isctest/query.py
++++ b/bin/tests/system/isctest/query.py
+@@ -81,6 +81,7 @@ def create(
+ qtype,
+ qclass=dns.rdataclass.IN,
+ dnssec: bool = True,
++ rd: bool = True,
+ cd: bool = False,
+ ad: bool = True,
+ ) -> dns.message.Message:
+@@ -88,7 +89,9 @@ def create(
+ msg = dns.message.make_query(
+ qname, qtype, qclass, use_edns=True, want_dnssec=dnssec
+ )
+- msg.flags = dns.flags.RD
++ msg.flags = 0
++ if rd:
++ msg.flags = dns.flags.RD
+ if ad:
+ msg.flags |= dns.flags.AD
+ if cd:
+--
+2.35.6
+
diff --git a/meta/recipes-connectivity/bind/bind_9.18.44.bb b/meta/recipes-connectivity/bind/bind_9.18.44.bb
index 7b5baf5338..42651fca4e 100644
--- a/meta/recipes-connectivity/bind/bind_9.18.44.bb
+++ b/meta/recipes-connectivity/bind/bind_9.18.44.bb
@@ -32,6 +32,17 @@ SRC_URI = "https://ftp.isc.org/isc/bind9/${PV}/${BPN}-${PV}.tar.xz \
file://CVE-2026-3592_p4.patch \
file://CVE-2026-3592_p5.patch \
file://CVE-2026-3592_p6.patch \
+ file://CVE-2026-5946-dependent_p1.patch \
+ file://CVE-2026-5946-dependent_p2.patch \
+ file://CVE-2026-5946_p1.patch \
+ file://CVE-2026-5946_p2.patch \
+ file://CVE-2026-5946_p3.patch \
+ file://CVE-2026-5946_p4.patch \
+ file://CVE-2026-5946_p5.patch \
+ file://CVE-2026-5946_p6.patch \
+ file://CVE-2026-5946_p7.patch \
+ file://CVE-2026-5946_p8.patch \
+ file://CVE-2026-5946_p9.patch \
"
SRC_URI[sha256sum] = "81f5035a25c576af1a93f0061cf70bde6d00a0c7bd1274abf73f5b5389a6f82d"
--
2.35.6
^ permalink raw reply related [flat|nested] 7+ messages in thread* Re: [OE-core] [scarthgap] [PATCH 1/5] bind: Fix CVE-2026-1519
2026-06-10 10:04 [OE-core] [scarthgap] [PATCH 1/5] bind: Fix CVE-2026-1519 Ashishkumar Parmar X (asparmar - E INFOCHIPS PRIVATE LIMITED at Cisco)
` (3 preceding siblings ...)
2026-06-10 10:04 ` [OE-core] [scarthgap] [PATCH 5/5] bind: Fix CVE-2026-5946 Ashishkumar Parmar X (asparmar - E INFOCHIPS PRIVATE LIMITED at Cisco)
@ 2026-06-26 16:59 ` Yoann Congal
2026-07-06 10:53 ` Yoann Congal
4 siblings, 1 reply; 7+ messages in thread
From: Yoann Congal @ 2026-06-26 16:59 UTC (permalink / raw)
To: asparmar, openembedded-core; +Cc: xe-linux-external, to
On Wed Jun 10, 2026 at 12:04 PM CEST, Ashishkumar Parmar X (asparmar - E INFOCHIPS PRIVATE LIMITED at Cisco) via lists.openembedded.org wrote:
> From: Ashishkumar Parmar <asparmar@cisco.com>
>
> Pick the upstream 9.18 backport [1] for CVE-2026-1519. The public ISC
> advisory [2] describes the vulnerability and identifies the fixed BIND
> release.
Hello,
I have not checked but bind is one of those rare project to have a stable
branch that match our stable policy (I've already taken some upgrades).
Instead of backporting CVE patches, why not trying to upgrade along the
9.18 branch?
>
> The upstream fix is split across the reproducer and validation commits.
> Also include one prerequisite NSEC3 bounds-check commit that is required
> before the validation changes can be applied cleanly and validated
> safely on the downstream 9.18.44 source:
>
> - CVE-2026-1519_p1.patch [3] adds the upstream system-test reproducer
> for excessive NSEC3 iterations at delegation.
> - CVE-2026-1519-dependent.patch [4] adds the prerequisite NSEC3
> next_length bounds check in isdelegation(). This patch is not part of
> the CVE merge commit, but it touches the same NSEC3 delegation path
> and must be applied before the functional validation changes.
> - CVE-2026-1519_p2.patch [5] adds the iteration-limit handling in
> isdelegation().
> - CVE-2026-1519_p3.patch [6] avoids re-validating already trusted
> rdatasets.
> - CVE-2026-1519_p4.patch [7] checks RRset trust in
> validate_neg_rrset().
>
> Keep the patches split to preserve the upstream commit structure and to
> make the SRC_URI ordering explicit.
>
> [1] https://gitlab.com/isc-projects/bind9/-/commit/5ef459eeaa92222ad28d2186f5eae9a586dece70
> [2] https://kb.isc.org/docs/cve-2026-1519
> [3] https://gitlab.com/isc-projects/bind9/-/commit/2c82f99a3c95f356861d5977f12ef9bbe2063cb6
> [4] https://gitlab.com/isc-projects/bind9/-/commit/368c75a9f567f8b36cf24fefe45023e0a050e47b
> [5] https://gitlab.com/isc-projects/bind9/-/commit/85c21feff9acb0982fe60f2c88201bf55533bd0e
> [6] https://gitlab.com/isc-projects/bind9/-/commit/8890a91c1c16129333139b9d8a4381e0f741f0d6
> [7] https://gitlab.com/isc-projects/bind9/-/commit/85fcd704e2f7cc2a25d2195bc4bb28398c889ed3
>
> Signed-off-by: Ashishkumar Parmar <asparmar@cisco.com>
> ---
> .../bind/bind/CVE-2026-1519-dependent.patch | 50 +++
> .../bind/bind/CVE-2026-1519_p1.patch | 341 ++++++++++++++++++
> .../bind/bind/CVE-2026-1519_p2.patch | 176 +++++++++
> .../bind/bind/CVE-2026-1519_p3.patch | 52 +++
> .../bind/bind/CVE-2026-1519_p4.patch | 59 +++
> .../recipes-connectivity/bind/bind_9.18.44.bb | 5 +
> 6 files changed, 683 insertions(+)
> create mode 100644 meta/recipes-connectivity/bind/bind/CVE-2026-1519-dependent.patch
> create mode 100644 meta/recipes-connectivity/bind/bind/CVE-2026-1519_p1.patch
> create mode 100644 meta/recipes-connectivity/bind/bind/CVE-2026-1519_p2.patch
> create mode 100644 meta/recipes-connectivity/bind/bind/CVE-2026-1519_p3.patch
> create mode 100644 meta/recipes-connectivity/bind/bind/CVE-2026-1519_p4.patch
>
> diff --git a/meta/recipes-connectivity/bind/bind/CVE-2026-1519-dependent.patch b/meta/recipes-connectivity/bind/bind/CVE-2026-1519-dependent.patch
> new file mode 100644
> index 0000000000..eff7a06d82
> --- /dev/null
> +++ b/meta/recipes-connectivity/bind/bind/CVE-2026-1519-dependent.patch
> @@ -0,0 +1,50 @@
> +From af8929ebe72ca8564882632e59999795c781ebd4 Mon Sep 17 00:00:00 2001
> +From: =?UTF-8?q?Ond=C5=99ej=20Sur=C3=BD?= <ondrej@isc.org>
> +Date: Sat, 14 Feb 2026 14:43:41 +0100
> +Subject: [PATCH] Invalid NSEC3 can cause OOB read of the isdelegation() stack
> +
> +When .next_length is longer than NSEC3_MAX_HASH_LENGTH, it causes a
> +harmless out-of-bound read of the isdelegation() stack. This patch
> +fixes the issue by skipping NSEC3 records with an oversized hash length
> +during validation.
> +
> +CVE: CVE-2026-1519
> +Upstream-Status: Backport [https://gitlab.com/isc-projects/bind9/-/commit/368c75a9f567f8b36cf24fefe45023e0a050e47b]
> +
> +(cherry picked from commit 67b4fb56e40bf856e1fccd41e752d5f486b5b569)
> +(cherry picked from commit 368c75a9f567f8b36cf24fefe45023e0a050e47b)
> +Signed-off-by: Ashishkumar Parmar <asparmar@cisco.com>
> +---
> + lib/dns/rdata/generic/nsec3_50.c | 1 +
> + lib/dns/validator.c | 3 +++
> + 2 files changed, 4 insertions(+)
> +
> +diff --git a/lib/dns/rdata/generic/nsec3_50.c b/lib/dns/rdata/generic/nsec3_50.c
> +index f45fe4dc33..e04587bd1b 100644
> +--- a/lib/dns/rdata/generic/nsec3_50.c
> ++++ b/lib/dns/rdata/generic/nsec3_50.c
> +@@ -324,6 +324,7 @@ tostruct_nsec3(ARGS_TOSTRUCT) {
> + }
> +
> + nsec3->mctx = mctx;
> ++
> + return ISC_R_SUCCESS;
> +
> + cleanup:
> +diff --git a/lib/dns/validator.c b/lib/dns/validator.c
> +index 809b7be911..9ec13581ab 100644
> +--- a/lib/dns/validator.c
> ++++ b/lib/dns/validator.c
> +@@ -339,6 +339,9 @@ trynsec3:
> + if (nsec3.hash != 1) {
> + continue;
> + }
> ++ if (nsec3.next_length > NSEC3_MAX_HASH_LENGTH) {
> ++ continue;
> ++ }
> + length = isc_iterated_hash(
> + hash, nsec3.hash, nsec3.iterations, nsec3.salt,
> + nsec3.salt_length, name->ndata, name->length);
> +--
> +2.35.6
> +
> diff --git a/meta/recipes-connectivity/bind/bind/CVE-2026-1519_p1.patch b/meta/recipes-connectivity/bind/bind/CVE-2026-1519_p1.patch
> new file mode 100644
> index 0000000000..f78af9da11
> --- /dev/null
> +++ b/meta/recipes-connectivity/bind/bind/CVE-2026-1519_p1.patch
> @@ -0,0 +1,341 @@
> +From 81f8acc4bdf84eec6f53a65709b61ad3d963b4f7 Mon Sep 17 00:00:00 2001
> +From: =?UTF-8?q?Nicki=20K=C5=99=C3=AD=C5=BEek?= <nicki@isc.org>
> +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.
> +
> +CVE: CVE-2026-1519
> +Upstream-Status: Backport [https://gitlab.com/isc-projects/bind9/-/commit/2c82f99a3c95f356861d5977f12ef9bbe2063cb6]
> +
> +(cherry picked from commit 9bc14a89f1313aa38330e84674ac3b7691db3383)
> +(cherry picked from commit 2c82f99a3c95f356861d5977f12ef9bbe2063cb6)
> +Signed-off-by: Ashishkumar Parmar <asparmar@cisco.com>
> +---
> + .../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.35.6
> +
> diff --git a/meta/recipes-connectivity/bind/bind/CVE-2026-1519_p2.patch b/meta/recipes-connectivity/bind/bind/CVE-2026-1519_p2.patch
> new file mode 100644
> index 0000000000..ee033b4b91
> --- /dev/null
> +++ b/meta/recipes-connectivity/bind/bind/CVE-2026-1519_p2.patch
> @@ -0,0 +1,176 @@
> +From e77c45ddae1ca87058244978868b6489610ca136 Mon Sep 17 00:00:00 2001
> +From: Matthijs Mekking <matthijs@isc.org>
> +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.
> +
> +CVE: CVE-2026-1519
> +Upstream-Status: Backport [https://gitlab.com/isc-projects/bind9/-/commit/85c21feff9acb0982fe60f2c88201bf55533bd0e]
> +
> +(cherry picked from commit 988040a5e02f86f4a8cdb0704e8d501f9082a89c)
> +(cherry picked from commit 85c21feff9acb0982fe60f2c88201bf55533bd0e)
> +Signed-off-by: Ashishkumar Parmar <asparmar@cisco.com>
> +---
> + lib/dns/validator.c | 64 +++++++++++++++++++++++++++++++++------------
> + 1 file changed, 48 insertions(+), 16 deletions(-)
> +
> +diff --git a/lib/dns/validator.c b/lib/dns/validator.c
> +index 9ec13581ab..179b6590b5 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");
> +@@ -2590,7 +2619,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 +2656,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 +2863,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;
> +--
> +2.35.6
> +
> diff --git a/meta/recipes-connectivity/bind/bind/CVE-2026-1519_p3.patch b/meta/recipes-connectivity/bind/bind/CVE-2026-1519_p3.patch
> new file mode 100644
> index 0000000000..0473f40752
> --- /dev/null
> +++ b/meta/recipes-connectivity/bind/bind/CVE-2026-1519_p3.patch
> @@ -0,0 +1,52 @@
> +From 87c7c1aa7c648f15d57810afb198db709aa08ad3 Mon Sep 17 00:00:00 2001
> +From: Matthijs Mekking <matthijs@isc.org>
> +Date: Tue, 3 Mar 2026 11:17:25 +0100
> +Subject: [PATCH] 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.
> +
> +CVE: CVE-2026-1519
> +Upstream-Status: Backport [https://gitlab.com/isc-projects/bind9/-/commit/8890a91c1c16129333139b9d8a4381e0f741f0d6]
> +
> +(cherry picked from commit 0ec08c212022d08c9717f2bc6bd3e8ebd6f034ce)
> +(cherry picked from commit 8890a91c1c16129333139b9d8a4381e0f741f0d6)
> +Signed-off-by: Ashishkumar Parmar <asparmar@cisco.com>
> +---
> + lib/dns/include/dns/types.h | 1 +
> + lib/dns/validator.c | 7 +++++++
> + 2 files changed, 8 insertions(+)
> +
> +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 179b6590b5..47efd3940f 100644
> +--- a/lib/dns/validator.c
> ++++ b/lib/dns/validator.c
> +@@ -1523,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:
> +--
> +2.35.6
> +
> diff --git a/meta/recipes-connectivity/bind/bind/CVE-2026-1519_p4.patch b/meta/recipes-connectivity/bind/bind/CVE-2026-1519_p4.patch
> new file mode 100644
> index 0000000000..fd5d1afcd7
> --- /dev/null
> +++ b/meta/recipes-connectivity/bind/bind/CVE-2026-1519_p4.patch
> @@ -0,0 +1,59 @@
> +From 52b1997275768884d46c648b40f2ea625c386d17 Mon Sep 17 00:00:00 2001
> +From: Matthijs Mekking <matthijs@isc.org>
> +Date: Tue, 3 Mar 2026 11:43:23 +0100
> +Subject: [PATCH] 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.
> +
> +CVE: CVE-2026-1519
> +Upstream-Status: Backport [https://gitlab.com/isc-projects/bind9/-/commit/85fcd704e2f7cc2a25d2195bc4bb28398c889ed3]
> +
> +(cherry picked from commit 6ca67f65cd685cf8699540a852c1e3775bd48d64)
> +(cherry picked from commit 85fcd704e2f7cc2a25d2195bc4bb28398c889ed3)
> +Signed-off-by: Ashishkumar Parmar <asparmar@cisco.com>
> +---
> + lib/dns/validator.c | 17 +++++++++++++----
> + 1 file changed, 13 insertions(+), 4 deletions(-)
> +
> +diff --git a/lib/dns/validator.c b/lib/dns/validator.c
> +index 47efd3940f..7db102062b 100644
> +--- a/lib/dns/validator.c
> ++++ b/lib/dns/validator.c
> +@@ -2463,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,
> +@@ -2573,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;
> +--
> +2.35.6
> +
> diff --git a/meta/recipes-connectivity/bind/bind_9.18.44.bb b/meta/recipes-connectivity/bind/bind_9.18.44.bb
> index d424edcb4e..9c8b73dccc 100644
> --- a/meta/recipes-connectivity/bind/bind_9.18.44.bb
> +++ b/meta/recipes-connectivity/bind/bind_9.18.44.bb
> @@ -18,6 +18,11 @@ SRC_URI = "https://ftp.isc.org/isc/bind9/${PV}/${BPN}-${PV}.tar.xz \
> file://bind-ensure-searching-for-json-headers-searches-sysr.patch \
> file://0001-named-lwresd-V-and-start-log-hide-build-options.patch \
> file://0001-avoid-start-failure-with-bind-user.patch \
> + file://CVE-2026-1519_p1.patch \
> + file://CVE-2026-1519-dependent.patch \
> + file://CVE-2026-1519_p2.patch \
> + file://CVE-2026-1519_p3.patch \
> + file://CVE-2026-1519_p4.patch \
> "
>
> SRC_URI[sha256sum] = "81f5035a25c576af1a93f0061cf70bde6d00a0c7bd1274abf73f5b5389a6f82d"
--
Yoann Congal
Smile ECS
^ permalink raw reply [flat|nested] 7+ messages in thread* Re: [OE-core] [scarthgap] [PATCH 1/5] bind: Fix CVE-2026-1519
2026-06-26 16:59 ` [OE-core] [scarthgap] [PATCH 1/5] bind: Fix CVE-2026-1519 Yoann Congal
@ 2026-07-06 10:53 ` Yoann Congal
0 siblings, 0 replies; 7+ messages in thread
From: Yoann Congal @ 2026-07-06 10:53 UTC (permalink / raw)
To: Yoann Congal, asparmar, openembedded-core; +Cc: xe-linux-external, to
On Fri Jun 26, 2026 at 6:59 PM CEST, Yoann Congal wrote:
> On Wed Jun 10, 2026 at 12:04 PM CEST, Ashishkumar Parmar X (asparmar - E INFOCHIPS PRIVATE LIMITED at Cisco) via lists.openembedded.org wrote:
>> From: Ashishkumar Parmar <asparmar@cisco.com>
>>
>> Pick the upstream 9.18 backport [1] for CVE-2026-1519. The public ISC
>> advisory [2] describes the vulnerability and identifies the fixed BIND
>> release.
>
> Hello,
>
> I have not checked but bind is one of those rare project to have a stable
> branch that match our stable policy (I've already taken some upgrades).
>
> Instead of backporting CVE patches, why not trying to upgrade along the
> 9.18 branch?
Hello,
This whole series is now superseded by
https://patchwork.yoctoproject.org/project/oe-core/patch/20260630060410.1203985-1-asparmar@cisco.com/
Thanks!
>
>>
>> The upstream fix is split across the reproducer and validation commits.
>> Also include one prerequisite NSEC3 bounds-check commit that is required
>> before the validation changes can be applied cleanly and validated
>> safely on the downstream 9.18.44 source:
>>
>> - CVE-2026-1519_p1.patch [3] adds the upstream system-test reproducer
>> for excessive NSEC3 iterations at delegation.
>> - CVE-2026-1519-dependent.patch [4] adds the prerequisite NSEC3
>> next_length bounds check in isdelegation(). This patch is not part of
>> the CVE merge commit, but it touches the same NSEC3 delegation path
>> and must be applied before the functional validation changes.
>> - CVE-2026-1519_p2.patch [5] adds the iteration-limit handling in
>> isdelegation().
>> - CVE-2026-1519_p3.patch [6] avoids re-validating already trusted
>> rdatasets.
>> - CVE-2026-1519_p4.patch [7] checks RRset trust in
>> validate_neg_rrset().
>>
>> Keep the patches split to preserve the upstream commit structure and to
>> make the SRC_URI ordering explicit.
>>
>> [1] https://gitlab.com/isc-projects/bind9/-/commit/5ef459eeaa92222ad28d2186f5eae9a586dece70
>> [2] https://kb.isc.org/docs/cve-2026-1519
>> [3] https://gitlab.com/isc-projects/bind9/-/commit/2c82f99a3c95f356861d5977f12ef9bbe2063cb6
>> [4] https://gitlab.com/isc-projects/bind9/-/commit/368c75a9f567f8b36cf24fefe45023e0a050e47b
>> [5] https://gitlab.com/isc-projects/bind9/-/commit/85c21feff9acb0982fe60f2c88201bf55533bd0e
>> [6] https://gitlab.com/isc-projects/bind9/-/commit/8890a91c1c16129333139b9d8a4381e0f741f0d6
>> [7] https://gitlab.com/isc-projects/bind9/-/commit/85fcd704e2f7cc2a25d2195bc4bb28398c889ed3
>>
>> Signed-off-by: Ashishkumar Parmar <asparmar@cisco.com>
>> ---
>> .../bind/bind/CVE-2026-1519-dependent.patch | 50 +++
>> .../bind/bind/CVE-2026-1519_p1.patch | 341 ++++++++++++++++++
>> .../bind/bind/CVE-2026-1519_p2.patch | 176 +++++++++
>> .../bind/bind/CVE-2026-1519_p3.patch | 52 +++
>> .../bind/bind/CVE-2026-1519_p4.patch | 59 +++
>> .../recipes-connectivity/bind/bind_9.18.44.bb | 5 +
>> 6 files changed, 683 insertions(+)
>> create mode 100644 meta/recipes-connectivity/bind/bind/CVE-2026-1519-dependent.patch
>> create mode 100644 meta/recipes-connectivity/bind/bind/CVE-2026-1519_p1.patch
>> create mode 100644 meta/recipes-connectivity/bind/bind/CVE-2026-1519_p2.patch
>> create mode 100644 meta/recipes-connectivity/bind/bind/CVE-2026-1519_p3.patch
>> create mode 100644 meta/recipes-connectivity/bind/bind/CVE-2026-1519_p4.patch
>>
>> diff --git a/meta/recipes-connectivity/bind/bind/CVE-2026-1519-dependent.patch b/meta/recipes-connectivity/bind/bind/CVE-2026-1519-dependent.patch
>> new file mode 100644
>> index 0000000000..eff7a06d82
>> --- /dev/null
>> +++ b/meta/recipes-connectivity/bind/bind/CVE-2026-1519-dependent.patch
>> @@ -0,0 +1,50 @@
>> +From af8929ebe72ca8564882632e59999795c781ebd4 Mon Sep 17 00:00:00 2001
>> +From: =?UTF-8?q?Ond=C5=99ej=20Sur=C3=BD?= <ondrej@isc.org>
>> +Date: Sat, 14 Feb 2026 14:43:41 +0100
>> +Subject: [PATCH] Invalid NSEC3 can cause OOB read of the isdelegation() stack
>> +
>> +When .next_length is longer than NSEC3_MAX_HASH_LENGTH, it causes a
>> +harmless out-of-bound read of the isdelegation() stack. This patch
>> +fixes the issue by skipping NSEC3 records with an oversized hash length
>> +during validation.
>> +
>> +CVE: CVE-2026-1519
>> +Upstream-Status: Backport [https://gitlab.com/isc-projects/bind9/-/commit/368c75a9f567f8b36cf24fefe45023e0a050e47b]
>> +
>> +(cherry picked from commit 67b4fb56e40bf856e1fccd41e752d5f486b5b569)
>> +(cherry picked from commit 368c75a9f567f8b36cf24fefe45023e0a050e47b)
>> +Signed-off-by: Ashishkumar Parmar <asparmar@cisco.com>
>> +---
>> + lib/dns/rdata/generic/nsec3_50.c | 1 +
>> + lib/dns/validator.c | 3 +++
>> + 2 files changed, 4 insertions(+)
>> +
>> +diff --git a/lib/dns/rdata/generic/nsec3_50.c b/lib/dns/rdata/generic/nsec3_50.c
>> +index f45fe4dc33..e04587bd1b 100644
>> +--- a/lib/dns/rdata/generic/nsec3_50.c
>> ++++ b/lib/dns/rdata/generic/nsec3_50.c
>> +@@ -324,6 +324,7 @@ tostruct_nsec3(ARGS_TOSTRUCT) {
>> + }
>> +
>> + nsec3->mctx = mctx;
>> ++
>> + return ISC_R_SUCCESS;
>> +
>> + cleanup:
>> +diff --git a/lib/dns/validator.c b/lib/dns/validator.c
>> +index 809b7be911..9ec13581ab 100644
>> +--- a/lib/dns/validator.c
>> ++++ b/lib/dns/validator.c
>> +@@ -339,6 +339,9 @@ trynsec3:
>> + if (nsec3.hash != 1) {
>> + continue;
>> + }
>> ++ if (nsec3.next_length > NSEC3_MAX_HASH_LENGTH) {
>> ++ continue;
>> ++ }
>> + length = isc_iterated_hash(
>> + hash, nsec3.hash, nsec3.iterations, nsec3.salt,
>> + nsec3.salt_length, name->ndata, name->length);
>> +--
>> +2.35.6
>> +
>> diff --git a/meta/recipes-connectivity/bind/bind/CVE-2026-1519_p1.patch b/meta/recipes-connectivity/bind/bind/CVE-2026-1519_p1.patch
>> new file mode 100644
>> index 0000000000..f78af9da11
>> --- /dev/null
>> +++ b/meta/recipes-connectivity/bind/bind/CVE-2026-1519_p1.patch
>> @@ -0,0 +1,341 @@
>> +From 81f8acc4bdf84eec6f53a65709b61ad3d963b4f7 Mon Sep 17 00:00:00 2001
>> +From: =?UTF-8?q?Nicki=20K=C5=99=C3=AD=C5=BEek?= <nicki@isc.org>
>> +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.
>> +
>> +CVE: CVE-2026-1519
>> +Upstream-Status: Backport [https://gitlab.com/isc-projects/bind9/-/commit/2c82f99a3c95f356861d5977f12ef9bbe2063cb6]
>> +
>> +(cherry picked from commit 9bc14a89f1313aa38330e84674ac3b7691db3383)
>> +(cherry picked from commit 2c82f99a3c95f356861d5977f12ef9bbe2063cb6)
>> +Signed-off-by: Ashishkumar Parmar <asparmar@cisco.com>
>> +---
>> + .../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.35.6
>> +
>> diff --git a/meta/recipes-connectivity/bind/bind/CVE-2026-1519_p2.patch b/meta/recipes-connectivity/bind/bind/CVE-2026-1519_p2.patch
>> new file mode 100644
>> index 0000000000..ee033b4b91
>> --- /dev/null
>> +++ b/meta/recipes-connectivity/bind/bind/CVE-2026-1519_p2.patch
>> @@ -0,0 +1,176 @@
>> +From e77c45ddae1ca87058244978868b6489610ca136 Mon Sep 17 00:00:00 2001
>> +From: Matthijs Mekking <matthijs@isc.org>
>> +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.
>> +
>> +CVE: CVE-2026-1519
>> +Upstream-Status: Backport [https://gitlab.com/isc-projects/bind9/-/commit/85c21feff9acb0982fe60f2c88201bf55533bd0e]
>> +
>> +(cherry picked from commit 988040a5e02f86f4a8cdb0704e8d501f9082a89c)
>> +(cherry picked from commit 85c21feff9acb0982fe60f2c88201bf55533bd0e)
>> +Signed-off-by: Ashishkumar Parmar <asparmar@cisco.com>
>> +---
>> + lib/dns/validator.c | 64 +++++++++++++++++++++++++++++++++------------
>> + 1 file changed, 48 insertions(+), 16 deletions(-)
>> +
>> +diff --git a/lib/dns/validator.c b/lib/dns/validator.c
>> +index 9ec13581ab..179b6590b5 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");
>> +@@ -2590,7 +2619,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 +2656,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 +2863,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;
>> +--
>> +2.35.6
>> +
>> diff --git a/meta/recipes-connectivity/bind/bind/CVE-2026-1519_p3.patch b/meta/recipes-connectivity/bind/bind/CVE-2026-1519_p3.patch
>> new file mode 100644
>> index 0000000000..0473f40752
>> --- /dev/null
>> +++ b/meta/recipes-connectivity/bind/bind/CVE-2026-1519_p3.patch
>> @@ -0,0 +1,52 @@
>> +From 87c7c1aa7c648f15d57810afb198db709aa08ad3 Mon Sep 17 00:00:00 2001
>> +From: Matthijs Mekking <matthijs@isc.org>
>> +Date: Tue, 3 Mar 2026 11:17:25 +0100
>> +Subject: [PATCH] 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.
>> +
>> +CVE: CVE-2026-1519
>> +Upstream-Status: Backport [https://gitlab.com/isc-projects/bind9/-/commit/8890a91c1c16129333139b9d8a4381e0f741f0d6]
>> +
>> +(cherry picked from commit 0ec08c212022d08c9717f2bc6bd3e8ebd6f034ce)
>> +(cherry picked from commit 8890a91c1c16129333139b9d8a4381e0f741f0d6)
>> +Signed-off-by: Ashishkumar Parmar <asparmar@cisco.com>
>> +---
>> + lib/dns/include/dns/types.h | 1 +
>> + lib/dns/validator.c | 7 +++++++
>> + 2 files changed, 8 insertions(+)
>> +
>> +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 179b6590b5..47efd3940f 100644
>> +--- a/lib/dns/validator.c
>> ++++ b/lib/dns/validator.c
>> +@@ -1523,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:
>> +--
>> +2.35.6
>> +
>> diff --git a/meta/recipes-connectivity/bind/bind/CVE-2026-1519_p4.patch b/meta/recipes-connectivity/bind/bind/CVE-2026-1519_p4.patch
>> new file mode 100644
>> index 0000000000..fd5d1afcd7
>> --- /dev/null
>> +++ b/meta/recipes-connectivity/bind/bind/CVE-2026-1519_p4.patch
>> @@ -0,0 +1,59 @@
>> +From 52b1997275768884d46c648b40f2ea625c386d17 Mon Sep 17 00:00:00 2001
>> +From: Matthijs Mekking <matthijs@isc.org>
>> +Date: Tue, 3 Mar 2026 11:43:23 +0100
>> +Subject: [PATCH] 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.
>> +
>> +CVE: CVE-2026-1519
>> +Upstream-Status: Backport [https://gitlab.com/isc-projects/bind9/-/commit/85fcd704e2f7cc2a25d2195bc4bb28398c889ed3]
>> +
>> +(cherry picked from commit 6ca67f65cd685cf8699540a852c1e3775bd48d64)
>> +(cherry picked from commit 85fcd704e2f7cc2a25d2195bc4bb28398c889ed3)
>> +Signed-off-by: Ashishkumar Parmar <asparmar@cisco.com>
>> +---
>> + lib/dns/validator.c | 17 +++++++++++++----
>> + 1 file changed, 13 insertions(+), 4 deletions(-)
>> +
>> +diff --git a/lib/dns/validator.c b/lib/dns/validator.c
>> +index 47efd3940f..7db102062b 100644
>> +--- a/lib/dns/validator.c
>> ++++ b/lib/dns/validator.c
>> +@@ -2463,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,
>> +@@ -2573,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;
>> +--
>> +2.35.6
>> +
>> diff --git a/meta/recipes-connectivity/bind/bind_9.18.44.bb b/meta/recipes-connectivity/bind/bind_9.18.44.bb
>> index d424edcb4e..9c8b73dccc 100644
>> --- a/meta/recipes-connectivity/bind/bind_9.18.44.bb
>> +++ b/meta/recipes-connectivity/bind/bind_9.18.44.bb
>> @@ -18,6 +18,11 @@ SRC_URI = "https://ftp.isc.org/isc/bind9/${PV}/${BPN}-${PV}.tar.xz \
>> file://bind-ensure-searching-for-json-headers-searches-sysr.patch \
>> file://0001-named-lwresd-V-and-start-log-hide-build-options.patch \
>> file://0001-avoid-start-failure-with-bind-user.patch \
>> + file://CVE-2026-1519_p1.patch \
>> + file://CVE-2026-1519-dependent.patch \
>> + file://CVE-2026-1519_p2.patch \
>> + file://CVE-2026-1519_p3.patch \
>> + file://CVE-2026-1519_p4.patch \
>> "
>>
>> SRC_URI[sha256sum] = "81f5035a25c576af1a93f0061cf70bde6d00a0c7bd1274abf73f5b5389a6f82d"
--
Yoann Congal
Smile ECS
^ permalink raw reply [flat|nested] 7+ messages in thread