new file mode 100644
@@ -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
+
new file mode 100644
@@ -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
+
new file mode 100644
@@ -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
+
new file mode 100644
@@ -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
+
new file mode 100644
@@ -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
+
new file mode 100644
@@ -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
+
new file mode 100644
@@ -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
+
new file mode 100644
@@ -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
+
new file mode 100644
@@ -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
+
new file mode 100644
@@ -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
+
new file mode 100644
@@ -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
+
@@ -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"