From fc16ff66ec4c12c863a9967fce9148e2bb0d02b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Men=C5=A1=C3=ADk?= Date: Mon, 25 May 2026 16:04:47 +0200 Subject: [PATCH] [9.16] [CVE-2026-5946] sec: usr: Disable recursion, UPDATE, and NOTIFY for non-IN views Recursion, dynamic updates (UPDATE), and zone change notifications (NOTIFY) are now disabled for views with a class other than IN (such as CHAOS or HESIOD); authoritative service for non-IN zones (e.g. version.bind in class CHAOS) continues to work as before. Servers configured with recursion yes in a non-IN view will log a warning at startup, and named-checkconf flags the same condition. UPDATE and NOTIFY messages that specify the meta-classes ANY or NONE in the question section are now rejected with FORMERR. This addresses a set of closely related security issues collectively identified as CVE-2026-5946. ISC would like to thank Mcsky23 for bringing these issues to our attention. [9.16] fix: dev: Pass empty string instead of NULL to ns_client_dumpmessage() Pass "" instead of NULL to ns_client_dumpmessage() to get the log message printed. Resolves-Vulnerability: CVE-2026-5946 Resolves: RHEL-177651 --- bind-9.16-CVE-2026-5946.patch | 729 ++++++++++++++++++++++++++++++++++ bind9.16.spec | 5 + 2 files changed, 734 insertions(+) create mode 100644 bind-9.16-CVE-2026-5946.patch diff --git a/bind-9.16-CVE-2026-5946.patch b/bind-9.16-CVE-2026-5946.patch new file mode 100644 index 0000000..b4eab87 --- /dev/null +++ b/bind-9.16-CVE-2026-5946.patch @@ -0,0 +1,729 @@ +From f215b6bb4a2a2bbed98195be7aa25e81d6e13b5c Mon Sep 17 00:00:00 2001 +From: Evan Hunt +Date: Tue, 3 Mar 2026 14:00:38 -0800 +Subject: [PATCH] Disable recursion for non-IN classes +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +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 + +(cherry picked from commit 70532a37a1aec761e8a12444852866ce9d9d5fcc) +(cherry picked from commit cf0d5a4e385525e21f2ae39098b1ab90c1137a2a) +(cherry picked from commit e577560f65dbc6109fca8a597d16568a1cd8987c) + +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 + +(cherry picked from commit 9ae24c32bec16c0f64225ef04f34670018bf0765) +(cherry picked from commit a2ca2408b3ff031c426c5dc785f550c9d30bf4aa) +(cherry picked from commit bec30ad70d17e36241df9da259bb2ac85b3c3435) + +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. + +(cherry picked from commit d41865a458b9ecd76be4097ac1bea1005cad72db) +(cherry picked from commit 1c8016c91c3674929f87cbe7ad09f3670e05ad4e) +(cherry picked from commit 986533b5ae8e855e23430bd7f6af7552d82e0ece) + +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 + +(cherry picked from commit 7de5160517ae69196d1c323b8627b267cdd10761) +(cherry picked from commit 3c44de9e6252ec1c7742ef02ecc0d6cbf1cde5e9) +(cherry picked from commit e7468f6be6dc98f86508627f4f333b6d09d2ac31) + +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. + +(cherry picked from commit 4cd3d8e6d866143ddc62df821a1007bf3ee7f083) +(cherry picked from commit fa60101e910346e64fa2a684b903fbcb84d8243b) +(cherry picked from commit e60ee54f7d650783d3b6dd7fdd749c64c6593353) + +Pass empty string instead of NULL to ns_client_dumpmessage() + +The two new call sites added by the CLASS-validation work passed NULL +as the reason, but ns_client_dumpmessage() bails out early on a NULL +reason — so the message dump never happened. The intent was to dump +the message and let the follow-up ns_client_log() carry the reason +text, so pass "" to suppress the prefix without short-circuiting the +dump. + +(cherry picked from commit 8af39c360407a92ef31bf233b9df760d1bb9fb5f) +(cherry picked from commit cd3a72e414d35c2ae573f4faea1148a95db7d5ff) + +%RH +fixed resolver, checkconf test +--- + bin/named/server.c | 41 ++++-------- + bin/tests/system/checkconf/tests.sh | 14 +++- + .../checkconf/warn-chaos-recursion.conf | 25 ++++++++ + bin/tests/system/resolver/tests.sh | 12 +++- + bin/tests/system/unknown/tests.sh | 12 ++-- + lib/bind9/check.c | 22 ++++++- + lib/dns/adb.c | 3 +- + lib/dns/message.c | 11 ++++ + lib/dns/resolver.c | 13 +++- + lib/ns/client.c | 64 ++++++++++++++++--- + lib/ns/update.c | 38 +++++------ + 11 files changed, 181 insertions(+), 74 deletions(-) + create mode 100644 bin/tests/system/checkconf/warn-chaos-recursion.conf + +diff --git a/bin/named/server.c b/bin/named/server.c +index 4cc69b5..c61de53 100644 +--- a/bin/named/server.c ++++ b/bin/named/server.c +@@ -1925,10 +1925,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, +@@ -4180,6 +4182,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 +@@ -4930,34 +4933,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 TSIG keys. + */ +@@ -5067,7 +5051,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); +@@ -5168,13 +5153,13 @@ 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) { ++ 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/checkconf/tests.sh b/bin/tests/system/checkconf/tests.sh +index c1feed1..921707e 100644 +--- a/bin/tests/system/checkconf/tests.sh ++++ b/bin/tests/system/checkconf/tests.sh +@@ -402,7 +402,8 @@ $CHECKCONF -l good.conf | + grep -v "is not implemented" | + grep -v "is not recommended" | + grep -v "no longer exists" | +-grep -v "is obsolete" > checkconf.out$n || ret=1 ++grep -v "is obsolete" | ++grep -v "recursion will be disabled" > checkconf.out$n || ret=1 + diff good.zonelist checkconf.out$n > diff.out$n || ret=1 + if [ $ret != 0 ]; then echo_i "failed"; ret=1; fi + status=`expr $status + $ret` +@@ -603,5 +604,16 @@ status=$((status+ret)) + + rmdir keys + ++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 0000000..e384533 +--- /dev/null ++++ b/bin/tests/system/checkconf/warn-chaos-recursion.conf +@@ -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. ++ */ ++ ++options { ++ directory "."; ++}; ++ ++view chaos ch { ++ match-clients { any; }; ++ recursion yes; ++ zone "." { ++ type hint; ++ file "chaos.hints"; ++ }; ++}; +diff --git a/bin/tests/system/resolver/tests.sh b/bin/tests/system/resolver/tests.sh +index 2eae16f..30e86bc 100755 +--- a/bin/tests/system/resolver/tests.sh ++++ b/bin/tests/system/resolver/tests.sh +@@ -16,6 +16,10 @@ DIGOPTS="-p ${PORT}" + RESOLVOPTS="-p ${PORT}" + RNDCCMD="$RNDC -c $SYSTEMTESTTOP/common/rndc.conf -p ${CONTROLPORT} -s" + ++dig_with_opts() { ++ "$DIG" $DIGOPTS "$@" ++} ++ + status=0 + n=0 + +@@ -853,10 +857,12 @@ if [ $ret != 0 ]; then echo_i "failed"; fi + status=`expr $status + $ret` + + n=`expr $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 $DIGOPTS @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=`expr $status + $ret` + +diff --git a/bin/tests/system/unknown/tests.sh b/bin/tests/system/unknown/tests.sh +index e480f66..5576216 100644 +--- a/bin/tests/system/unknown/tests.sh ++++ b/bin/tests/system/unknown/tests.sh +@@ -80,8 +80,8 @@ echo_i "querying for various representations of a CLASS10 TYPE1 record ($n)" + for i in 1 2 + do + ret=0 +- $DIG +short $DIGOPTS @10.53.0.1 a$i.example a class10 > dig.out.$i.test$n || ret=1 +- echo '\# 4 0A000001' | $DIFF - dig.out.$i.test$n || ret=1 ++ $DIG $DIGOPTS @10.53.0.1 a$i.example a class10 >dig.out.$i.test$n || ret=1 ++ grep -q "NOTIMP" dig.out.$i.test$n || ret=1 + if [ $ret != 0 ] + then + echo_i "#$i failed" +@@ -94,8 +94,8 @@ echo_i "querying for various representations of a CLASS10 TXT record ($n)" + for i in 1 2 3 4 + do + ret=0 +- $DIG +short $DIGOPTS @10.53.0.1 txt$i.example txt class10 > dig.out.$i.test$n || ret=1 +- echo '"hello"' | $DIFF - dig.out.$i.test$n || ret=1 ++ $DIG $DIGOPTS @10.53.0.1 txt$i.example txt class10 >dig.out.$i.test$n || ret=1 ++ grep -q "NOTIMP" dig.out.$i.test$n || ret=1 + if [ $ret != 0 ] + then + echo_i "#$i failed" +@@ -108,8 +108,8 @@ echo_i "querying for various representations of a CLASS10 TYPE123 record ($n)" + for i in 1 2 + do + ret=0 +- $DIG +short $DIGOPTS @10.53.0.1 unk$i.example type123 class10 > dig.out.$i.test$n || ret=1 +- echo '\# 1 00' | $DIFF - dig.out.$i.test$n || ret=1 ++ $DIG $DIGOPTS @10.53.0.1 unk$i.example type123 class10 >dig.out.$i.test$n || ret=1 ++ grep -q "NOTIMP" dig.out.$i.test$n || ret=1 + if [ $ret != 0 ] + then + echo_i "#$i failed" +diff --git a/lib/bind9/check.c b/lib/bind9/check.c +index 2225bee..e55c4fc 100644 +--- a/lib/bind9/check.c ++++ b/lib/bind9/check.c +@@ -2271,13 +2271,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. + */ +@@ -2831,7 +2835,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 " +@@ -4553,6 +4558,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 { +diff --git a/lib/dns/adb.c b/lib/dns/adb.c +index 74fdc72..cc00798 100644 +--- a/lib/dns/adb.c ++++ b/lib/dns/adb.c +@@ -941,7 +941,8 @@ 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); ++ + if (rdtype == dns_rdatatype_a) { + findoptions = DNS_ADBFIND_INET; + } else { +diff --git a/lib/dns/message.c b/lib/dns/message.c +index aa434a7..a81943d 100644 +--- a/lib/dns/message.c ++++ b/lib/dns/message.c +@@ -1085,6 +1085,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. +diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c +index 5973bc1..460a15e 100644 +--- a/lib/dns/resolver.c ++++ b/lib/dns/resolver.c +@@ -7369,9 +7369,16 @@ is_answeraddress_allowed(dns_view_t *view, dns_name_t *name, + } + + /* +- * Otherwise, search the filter list for a match for each address +- * record. If a match is found, the address should be filtered, +- * so should the entire answer. ++ * 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 ++ * filtered, so should the entire answer. + */ + for (result = dns_rdataset_first(rdataset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(rdataset)) +diff --git a/lib/ns/client.c b/lib/ns/client.c +index 87b8a18..554934f 100644 +--- a/lib/ns/client.c ++++ b/lib/ns/client.c +@@ -40,6 +40,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -1937,7 +1938,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) +@@ -1958,12 +1961,49 @@ 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); ++ isc_task_unpause(client->task); ++ 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); ++ isc_task_unpause(client->task); ++ 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); ++ isc_task_unpause(client->task); ++ return; ++ } ++ break; ++ default: ++ dns_rdataclass_format(client->message->rdclass, classbuf, ++ sizeof(classbuf)); ++ ns_client_dumpmessage(client, ""); + 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); + isc_task_unpause(client->task); + return; + } +@@ -2017,7 +2057,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, ""); + ns_client_error(client, notimp ? DNS_R_NOTIMP : DNS_R_REFUSED); + isc_task_unpause(client->task); + return; +@@ -2201,6 +2241,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, +@@ -2211,6 +2255,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; +@@ -2302,7 +2350,7 @@ ns__client_setup(ns_client_t *client, ns_clientmgr_t *mgr, bool new) { + * The caller is responsible for that. + */ + +- REQUIRE(NS_CLIENT_VALID(client) || (new &&client != NULL)); ++ REQUIRE(NS_CLIENT_VALID(client) || (new && client != NULL)); + REQUIRE(VALID_MANAGER(mgr) || !new); + + if (new) { +@@ -2748,7 +2796,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; + } + +diff --git a/lib/ns/update.c b/lib/ns/update.c +index 0e0bdc9..dc6ebdf 100644 +--- a/lib/ns/update.c ++++ b/lib/ns/update.c +@@ -1288,7 +1288,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 +@@ -1432,8 +1435,7 @@ failure: + * '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; +@@ -1449,7 +1451,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; + } + + /*% +@@ -1551,7 +1553,6 @@ send_update_event(ns_client_t *client, dns_zone_t *zone) { + dns_message_t *request = client->message; + dns_aclenv_t *env = + ns_interfacemgr_getaclenv(client->manager->interface->mgr); +- dns_rdataclass_t zoneclass; + dns_rdatatype_t covers; + dns_name_t *zonename = NULL; + dns_db_t *db = NULL; +@@ -1559,10 +1560,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, +@@ -1603,13 +1606,13 @@ send_update_event(ns_client_t *client, dns_zone_t *zone) { + dns_ttl_t ttl; + dns_rdataclass_t update_class; + +- get_current_rr(request, DNS_SECTION_UPDATE, zoneclass, &name, ++ 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 +@@ -2733,9 +2736,7 @@ 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_ssutable_t *ssutable = NULL; + dns_fixedname_t tmpnamefixed; + dns_name_t *tmpname = NULL; + dns_zoneopt_t options; +@@ -2754,10 +2755,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); +- dns_zone_getssutable(zone, &ssutable); + options = dns_zone_getoptions(zone); + ++ INSIST(dns_zone_getclass(zone) == dns_rdataclass_in); + /* + * Get old and new versions now that queryacl has been checked. + */ +@@ -2778,8 +2778,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, "prerequisite TTL is not " +@@ -2846,7 +2846,7 @@ update_action(isc_task_t *task, isc_event_t *event) { + "satisfied"); + } + } +- } else if (update_class == zoneclass) { ++ } else if (update_class == dns_rdataclass_in) { + /* "temp += rr;" */ + result = temp_append(&temp, name, &rdata); + if (result != ISC_R_SUCCESS) { +@@ -2906,10 +2906,10 @@ update_action(isc_task_t *task, isc_event_t *event) { + dns_rdataclass_t update_class; + bool flag; + +- get_current_rr(request, DNS_SECTION_UPDATE, zoneclass, &name, ++ 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 zones. + */ +@@ -3488,10 +3488,6 @@ common: + dns_db_detach(&db); + } + +- if (ssutable != NULL) { +- dns_ssutable_detach(&ssutable); +- } +- + isc_task_detach(&task); + uev->result = result; + if (zone != NULL) { +-- +2.54.0 + diff --git a/bind9.16.spec b/bind9.16.spec index b697fd7..fd11515 100644 --- a/bind9.16.spec +++ b/bind9.16.spec @@ -184,6 +184,9 @@ Patch225: bind-9.16-CVE-2025-40778.patch Patch226: bind-9.16-CVE-2026-1519.patch # https://gitlab.isc.org/isc-projects/bind9/-/commit/7f04d7104304fdc6b858c41bb44ad151b2c3e1b7 Patch230: bind-9.16-CVE-2026-3039.patch +# https://gitlab.isc.org/isc-projects/bind9/-/commit/ec2c98181115bd5f6c7087fcc74d816490d4312e +# https://gitlab.isc.org/isc-projects/bind9/-/commit/e5abd37cb2330af1fbfeba68eb32f2873390226d +Patch231: bind-9.16-CVE-2026-5946.patch %{?systemd_ordering} Requires: coreutils @@ -525,6 +528,7 @@ in HTML and PDF format. %patch225 -p1 -b .CVE-2025-40778 %patch226 -p1 -b .CVE-2026-1519 %patch230 -p1 -b .CVE-2026-3039 +%patch231 -p1 -b .CVE-2026-5946 %if %{with PKCS11} %patch135 -p1 -b .config-pkcs11 @@ -1269,6 +1273,7 @@ fi; %changelog * Mon May 25 2026 Petr Menšík - 32:9.16.23-0.22.6 - Fix GSS-API resource leak (CVE-2026-3039) +- Invalid handling of CLASS != IN (CVE-2026-5946) * Fri Mar 27 2026 Petr Menšík - 32:9.16.23-0.22.5 - Prevent Denial of Service via maliciously crafted DNSSEC-validated zone