diff --git a/bind-9.18-CVE-2026-3039.patch b/bind-9.18-CVE-2026-3039.patch new file mode 100644 index 0000000..deaa099 --- /dev/null +++ b/bind-9.18-CVE-2026-3039.patch @@ -0,0 +1,386 @@ +From 775156d85236e8cc032191f16304af36f30f30c6 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Ond=C5=99ej=20Sur=C3=BD?= +Date: Wed, 18 Mar 2026 00:10:35 +0100 +Subject: [PATCH] Fix GSS-API context leak in TKEY negotiation + +Reject multi-round GSS-API negotiation (GSS_S_CONTINUE_NEEDED) in +dst_gssapi_acceptctx(). Each call to gss_accept_sec_context() +allocates a context inside the GSS library; without this fix, the +context handle was passed back to process_gsstkey() which did not +store it persistently, leaking it on every incomplete negotiation. + +An unauthenticated attacker could exhaust server memory by sending +repeated TKEY queries with GSSAPI tokens, each leaking one GSS +context. The leaked memory is allocated by the GSS library via +malloc(), bypassing BIND's memory accounting. + +In practice, Kerberos/SPNEGO (the only mechanism used with BIND) +completes in a single round, so rejecting continuation does not +affect real-world deployments. See RFC 3645 Section 4.1.3. + +(cherry picked from commit 3d8e0d068f08694282c5ecd3bd6c332de6c75485) +(cherry picked from commit c420039fee2f428032a2f72aa82a4ed2fcc92f31) + +Fix output token and GSS context leaks in TKEY/GSS-API error paths + +In dst_gssapi_acceptctx(), rename outtoken to outtokenp (matching BIND +convention for output pointer parameters) and free the allocated output +token buffer on error in the cleanup path. + +In process_gsstkey(), route the empty-principal error path through +cleanup via CLEANUP() instead of returning early, so that the output +token, GSS context, and TSIG key are all freed consistently by the +existing cleanup block. + +(cherry picked from commit 6c46c85d02849fb659584275313529794039f433) +(cherry picked from commit d273e3def097fbd186f23bcbb146c3ffb4f47301) + +%RH: +Some cleanup changes compared to original commits. +--- + lib/dns/gssapictx.c | 129 +++++++++++++++++++---------------- + lib/dns/include/dst/gssapi.h | 17 ++--- + lib/dns/tkey.c | 49 ++++++------- + 3 files changed, 99 insertions(+), 96 deletions(-) + +diff --git a/lib/dns/gssapictx.c b/lib/dns/gssapictx.c +index 06580c2352..abed2384c5 100644 +--- a/lib/dns/gssapictx.c ++++ b/lib/dns/gssapictx.c +@@ -100,6 +100,13 @@ static gss_OID_desc __gss_spnego_mechanism_oid_desc = { + goto out; \ + } while (0) + ++#define CHECK(x) \ ++ do { \ ++ result = (x); \ ++ if (result != ISC_R_SUCCESS) \ ++ goto cleanup; \ ++ } while (0) ++ + static void + name_to_gbuffer(const dns_name_t *name, isc_buffer_t *buffer, + gss_buffer_desc *gbuffer) { +@@ -612,7 +619,14 @@ dst_gssapi_initctx(const dns_name_t *name, isc_buffer_t *intoken, + GSS_SPNEGO_MECHANISM, flags, 0, NULL, gintokenp, NULL, + &gouttoken, &ret_flags, NULL); + +- if (gret != GSS_S_COMPLETE && gret != GSS_S_CONTINUE_NEEDED) { ++ switch (gret) { ++ case GSS_S_COMPLETE: ++ result = ISC_R_SUCCESS; ++ break; ++ case GSS_S_CONTINUE_NEEDED: ++ result = DNS_R_CONTINUE; ++ break; ++ default: + gss_err_message(mctx, gret, minor, err_message); + if (err_message != NULL && *err_message != NULL) { + gss_log(3, "Failure initiating security context: %s", +@@ -638,12 +652,6 @@ dst_gssapi_initctx(const dns_name_t *name, isc_buffer_t *intoken, + RETERR(isc_buffer_copyregion(outtoken, &r)); + } + +- if (gret == GSS_S_COMPLETE) { +- result = ISC_R_SUCCESS; +- } else { +- result = DNS_R_CONTINUE; +- } +- + out: + if (gouttoken.length != 0U) { + (void)gss_release_buffer(&minor, &gouttoken); +@@ -654,7 +662,7 @@ out: + + isc_result_t + dst_gssapi_acceptctx(dns_gss_cred_id_t cred, const char *gssapi_keytab, +- isc_region_t *intoken, isc_buffer_t **outtoken, ++ isc_region_t *intoken, isc_buffer_t **outtokenp, + dns_gss_ctx_id_t *ctxout, dns_name_t *principal, + isc_mem_t *mctx) { + isc_region_t r; +@@ -667,16 +675,11 @@ dst_gssapi_acceptctx(dns_gss_cred_id_t cred, const char *gssapi_keytab, + isc_result_t result; + char buf[1024]; + +- REQUIRE(outtoken != NULL && *outtoken == NULL); ++ REQUIRE(outtokenp != NULL && *outtokenp == NULL); ++ REQUIRE(*ctxout == NULL); + + REGION_TO_GBUFFER(*intoken, gintoken); + +- if (*ctxout == NULL) { +- context = GSS_C_NO_CONTEXT; +- } else { +- context = *ctxout; +- } +- + if (gssapi_keytab != NULL) { + #if HAVE_GSSAPI_GSSAPI_KRB5_H || HAVE_GSSAPI_KRB5_H + gret = gsskrb5_register_acceptor_identity(gssapi_keytab); +@@ -721,8 +724,15 @@ dst_gssapi_acceptctx(dns_gss_cred_id_t cred, const char *gssapi_keytab, + + switch (gret) { + case GSS_S_COMPLETE: +- case GSS_S_CONTINUE_NEEDED: + break; ++ /* ++ * RFC 3645 4.1.3: we don't handle GSS_S_CONTINUE_NEEDED ++ * Multi-round GSS-API negotiation is not supported. ++ */ ++ case GSS_S_CONTINUE_NEEDED: ++ gss_log(3, "multi-round GSS-API negotiation not supported"); ++ (void)gss_delete_sec_context(&minor, &context, NULL); ++ FALLTHROUGH; + case GSS_S_DEFECTIVE_TOKEN: + case GSS_S_DEFECTIVE_CREDENTIAL: + case GSS_S_BAD_SIG: +@@ -735,7 +745,7 @@ dst_gssapi_acceptctx(dns_gss_cred_id_t cred, const char *gssapi_keytab, + case GSS_S_BAD_MECH: + case GSS_S_FAILURE: + result = DNS_R_INVALIDTKEY; +- /* fall through */ ++ FALLTHROUGH; + default: + gss_log(3, "failed gss_accept_sec_context: %s", + gss_error_tostring(gret, minor, buf, sizeof(buf))); +@@ -746,59 +756,64 @@ dst_gssapi_acceptctx(dns_gss_cred_id_t cred, const char *gssapi_keytab, + } + + if (gouttoken.length > 0U) { +- isc_buffer_allocate(mctx, outtoken, ++ isc_buffer_allocate(mctx, outtokenp, + (unsigned int)gouttoken.length); + GBUFFER_TO_REGION(gouttoken, r); +- RETERR(isc_buffer_copyregion(*outtoken, &r)); ++ CHECK(isc_buffer_copyregion(*outtokenp, &r)); + (void)gss_release_buffer(&minor, &gouttoken); + } + +- if (gret == GSS_S_COMPLETE) { +- gret = gss_display_name(&minor, gname, &gnamebuf, NULL); +- if (gret != GSS_S_COMPLETE) { +- gss_log(3, "failed gss_display_name: %s", +- gss_error_tostring(gret, minor, buf, +- sizeof(buf))); +- RETERR(ISC_R_FAILURE); +- } ++ INSIST(gret == GSS_S_COMPLETE); + +- /* +- * Compensate for a bug in Solaris8's implementation +- * of gss_display_name(). Should be harmless in any +- * case, since principal names really should not +- * contain null characters. +- */ +- if (gnamebuf.length > 0U && +- ((char *)gnamebuf.value)[gnamebuf.length - 1] == '\0') +- { +- gnamebuf.length--; +- } ++ gret = gss_display_name(&minor, gname, &gnamebuf, NULL); ++ if (gret != GSS_S_COMPLETE) { ++ gss_log(3, "failed gss_display_name: %s", ++ gss_error_tostring(gret, minor, buf, sizeof(buf))); ++ result = ISC_R_FAILURE; ++ goto cleanup; ++ } + +- gss_log(3, "gss-api source name (accept) is %.*s", +- (int)gnamebuf.length, (char *)gnamebuf.value); ++ /* ++ * Compensate for a bug in Solaris8's implementation ++ * of gss_display_name(). Should be harmless in any ++ * case, since principal names really should not ++ * contain null characters. ++ */ ++ if (gnamebuf.length > 0U && ++ ((char *)gnamebuf.value)[gnamebuf.length - 1] == '\0') ++ { ++ gnamebuf.length--; ++ } + +- GBUFFER_TO_REGION(gnamebuf, r); +- isc_buffer_init(&namebuf, r.base, r.length); +- isc_buffer_add(&namebuf, r.length); ++ gss_log(3, "gss-api source name (accept) is %.*s", (int)gnamebuf.length, ++ (char *)gnamebuf.value); + +- RETERR(dns_name_fromtext(principal, &namebuf, dns_rootname, 0, +- NULL)); ++ GBUFFER_TO_REGION(gnamebuf, r); ++ isc_buffer_init(&namebuf, r.base, r.length); ++ isc_buffer_add(&namebuf, r.length); + +- if (gnamebuf.length != 0U) { +- gret = gss_release_buffer(&minor, &gnamebuf); +- if (gret != GSS_S_COMPLETE) { +- gss_log(3, "failed gss_release_buffer: %s", +- gss_error_tostring(gret, minor, buf, +- sizeof(buf))); +- } +- } +- } else { +- result = DNS_R_CONTINUE; +- } ++ CHECK(dns_name_fromtext(principal, &namebuf, dns_rootname, 0, NULL)); + + *ctxout = context; + +-out: ++cleanup: ++ if (result != ISC_R_SUCCESS && *outtokenp != NULL) { ++ isc_buffer_free(outtokenp); ++ } ++ ++ if (result != ISC_R_SUCCESS && context != GSS_C_NO_CONTEXT) { ++ (void)gss_delete_sec_context(&minor, &context, NULL); ++ } ++ ++ if (gnamebuf.length != 0U) { ++ gret = gss_release_buffer(&minor, &gnamebuf); ++ if (gret != GSS_S_COMPLETE) { ++ gss_log(3, "failed gss_release_buffer: %s", ++ gss_error_tostring(gret, minor, buf, ++ sizeof(buf))); ++ } ++ } ++ + if (gname != NULL) { + gret = gss_release_name(&minor, &gname); + if (gret != GSS_S_COMPLETE) { +diff --git a/lib/dns/include/dst/gssapi.h b/lib/dns/include/dst/gssapi.h +index 494b4b0762..5945bf7637 100644 +--- a/lib/dns/include/dst/gssapi.h ++++ b/lib/dns/include/dst/gssapi.h +@@ -113,20 +113,17 @@ dst_gssapi_acceptctx(dns_gss_cred_id_t cred, const char *gssapi_keytab, + * generated by gss_accept_sec_context() to be sent to the + * initiator + * 'context' is a valid pointer to receive the generated context handle. +- * On the initial call, it should be a pointer to NULL, which +- * will be allocated as a dns_gss_ctx_id_t. Subsequent calls +- * should pass in the handle generated on the first call. +- * Call dst_gssapi_releasecred to delete the context and free +- * the memory. + * + * Requires: +- * 'outtoken' to != NULL && *outtoken == NULL. ++ * 'outtoken' != NULL && *outtoken == NULL. ++ * 'context' != NULL && *context == NULL. + * + * Returns: +- * ISC_R_SUCCESS msg was successfully updated to include the +- * query to be sent +- * DNS_R_CONTINUE transaction still in progress +- * other an error occurred while building the message ++ * ISC_R_SUCCESS msg was successfully updated to include ++ * the query to be sent ++ * DNS_R_INVALIDTKEY an error occurred while accepting the ++ * context ++ * ISC_R_FAILURE other error occurred + */ + + isc_result_t +diff --git a/lib/dns/tkey.c b/lib/dns/tkey.c +index 66f4c5de52..679280dd14 100644 +--- a/lib/dns/tkey.c ++++ b/lib/dns/tkey.c +@@ -535,19 +535,9 @@ process_gsstkey(dns_message_t *msg, dns_name_t *name, dns_rdata_tkey_t *tkeyin, + return ISC_R_SUCCESS; + } + +- /* +- * XXXDCL need to check for key expiry per 4.1.1 +- * XXXDCL need a way to check fully established, perhaps w/key_flags +- */ +- + intoken.base = tkeyin->key; + intoken.length = tkeyin->keylen; + +- result = dns_tsigkey_find(&tsigkey, name, &tkeyin->algorithm, ring); +- if (result == ISC_R_SUCCESS) { +- gss_ctx = dst_key_getgssctx(tsigkey->key); +- } +- + principal = dns_fixedname_initname(&fixed); + + /* +@@ -556,28 +546,27 @@ process_gsstkey(dns_message_t *msg, dns_name_t *name, dns_rdata_tkey_t *tkeyin, + result = dst_gssapi_acceptctx(tctx->gsscred, tctx->gssapi_keytab, + &intoken, &outtoken, &gss_ctx, principal, + tctx->mctx); +- if (result == DNS_R_INVALIDTKEY) { +- if (tsigkey != NULL) { +- dns_tsigkey_detach(&tsigkey); +- } ++ if (result != ISC_R_SUCCESS) { + tkeyout->error = dns_tsigerror_badkey; +- tkey_log("process_gsstkey(): dns_tsigerror_badkey"); /* XXXSRA +- */ +- return ISC_R_SUCCESS; +- } +- if (result != DNS_R_CONTINUE && result != ISC_R_SUCCESS) { ++ tkey_log("process_gsstkey(): dns_tsigerror_badkey"); ++ result = ISC_R_SUCCESS; + goto failure; + } ++ + /* +- * XXXDCL Section 4.1.3: Limit GSS_S_CONTINUE_NEEDED to 10 times. ++ * Multi-round GSS-API negotiation (GSS_S_CONTINUE_NEEDED) is ++ * rejected in dst_gssapi_acceptctx(), so if we reach here the ++ * negotiation is complete and the principal must be set. + */ + + isc_stdtime_get(&now); + + if (dns_name_countlabels(principal) == 0U) { +- if (tsigkey != NULL) { +- dns_tsigkey_detach(&tsigkey); +- } ++ tkeyout->error = dns_tsigerror_badkey; ++ tkey_log("process_gsstkey(): " ++ "completed context with empty principal"); ++ result = ISC_R_SUCCESS; ++ goto failure; + } else if (tsigkey == NULL) { + #if HAVE_GSSAPI + OM_uint32 gret, minor, lifetime; +@@ -640,6 +629,9 @@ process_gsstkey(dns_message_t *msg, dns_name_t *name, dns_rdata_tkey_t *tkeyin, + return ISC_R_SUCCESS; + + failure: ++ if (dstkey == NULL && gss_ctx != NULL) { ++ dst_gssapi_deletectx(tctx->mctx, &gss_ctx); ++ } + if (tsigkey != NULL) { + dns_tsigkey_detach(&tsigkey); + } +@@ -652,9 +644,9 @@ failure: + isc_buffer_free(&outtoken); + } + +- tkey_log("process_gsstkey(): %s", isc_result_totext(result)); /* XXXSRA +- */ +- ++ if (result != ISC_R_SUCCESS) { ++ tkey_log("process_gsstkey(): %s", isc_result_totext(result)); ++ } + return result; + } + +@@ -1577,9 +1569,8 @@ dns_tkey_gssnegotiate(dns_message_t *qmsg, dns_message_t *rmsg, + NULL)); + + /* +- * XXXSRA This seems confused. If we got CONTINUE from initctx, +- * the GSS negotiation hasn't completed yet, so we can't sign +- * anything yet. ++ * GSS negotiation is complete (CONTINUE returned earlier). ++ * Create the TSIG key from the established context. + */ + + RETERR(dns_tsigkey_createfromkey( +-- +2.54.0 + diff --git a/bind-9.18-CVE-2026-5946.patch b/bind-9.18-CVE-2026-5946.patch new file mode 100644 index 0000000..21aff2a --- /dev/null +++ b/bind-9.18-CVE-2026-5946.patch @@ -0,0 +1,668 @@ +From 549972c7aab076fd4002aaa95eaebc81609c902d 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 + +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 7becff1a14684a68208c92b3b0315c045c05ad75) +(cherry picked from commit 401a6374b026afb76a7b22acc4a1402d21a7e77b) + +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 a6d8e330ed6cf0021bff3f00aa1dc7a296f5aec0) +(cherry picked from commit 04092ed136c8a6db2b1059dcd32693d57a7bdc24) + +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 0a687451505037e9f9a850c9cb113aed4995b03f) +(cherry picked from commit b247dbb3506ef628a683d184fbc6a99fad45ed94) + +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 c66a1b1e1bfd6c79d7b9bc8d4a59e69f4faa1563) +(cherry picked from commit 185c10981b941bfa5b753b3624b6e11ccca8737f) + +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 e62673c765b52307c800e86f0185fe52b573c145) +(cherry picked from commit 772d1d5f905c819d7155e76a08c33218bfcc973e) + +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. + +(cherry picked from commit e2f7ba2a4b6e7e5dba2fb1a2c9b2f0323e9a88be) +(cherry picked from commit e5b3149b70ed24f7590b6bc08aad4492a00c2022) +--- + bin/named/server.c | 42 ++++++------------- + bin/tests/system/allow-query/tests.sh | 2 +- + bin/tests/system/checkconf/tests.sh | 1 + + bin/tests/system/resolver/tests.sh | 8 ++-- + bin/tests/system/unknown/tests.sh | 17 +++++--- + lib/bind9/check.c | 22 ++++++++-- + lib/dns/adb.c | 2 +- + lib/dns/message.c | 11 +++++ + lib/dns/resolver.c | 7 ++++ + lib/ns/client.c | 59 +++++++++++++++++++++++---- + lib/ns/update.c | 41 ++++++++++--------- + 11 files changed, 143 insertions(+), 69 deletions(-) + +diff --git a/bin/named/server.c b/bin/named/server.c +index a0a28c04a3..d95748305e 100644 +--- a/bin/named/server.c ++++ b/bin/named/server.c +@@ -1901,10 +1901,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); +@@ -4429,6 +4431,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 +@@ -5139,34 +5142,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) + */ +@@ -5294,7 +5278,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); +@@ -5394,14 +5379,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/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 7d222ed863..b2c955d312 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 23b42f728c..b5d2cf8b46 100755 +--- a/bin/tests/system/resolver/tests.sh ++++ b/bin/tests/system/resolver/tests.sh +@@ -948,10 +948,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/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/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 { +diff --git a/lib/dns/adb.c b/lib/dns/adb.c +index 8b6e153566..3da839cc12 100644 +--- a/lib/dns/adb.c ++++ b/lib/dns/adb.c +@@ -938,7 +938,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/dns/message.c b/lib/dns/message.c +index f939fa94a7..1dc50c142a 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. +diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c +index 5c2294a0f2..1326cb3428 100644 +--- a/lib/dns/resolver.c ++++ b/lib/dns/resolver.c +@@ -7249,6 +7249,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 +diff --git a/lib/ns/client.c b/lib/ns/client.c +index d8cd84bc5f..df4c570de2 100644 +--- a/lib/ns/client.c ++++ b/lib/ns/client.c +@@ -44,6 +44,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -2068,7 +2069,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) +@@ -2087,12 +2090,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; + } + +@@ -2125,7 +2162,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; +@@ -2322,6 +2359,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, +@@ -2332,6 +2373,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; +@@ -2758,7 +2803,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 248f6ed53b..84ba9c48d1 100644 +--- a/lib/ns/update.c ++++ b/lib/ns/update.c +@@ -1026,7 +1026,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; +@@ -1390,7 +1392,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 +@@ -1533,8 +1538,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; +@@ -1550,7 +1554,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; + } + + /*% +@@ -1652,7 +1656,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; +@@ -1662,10 +1665,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, +@@ -1716,13 +1721,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 +@@ -1806,7 +1811,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)) + { +@@ -2897,7 +2901,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_ssutable_t *ssutable = NULL; + dns_fixedname_t tmpnamefixed; +@@ -2918,10 +2921,10 @@ 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. + */ +@@ -2942,8 +2945,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, +@@ -3006,7 +3009,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;" */ + result = temp_append(&temp, name, &rdata); + if (result != ISC_R_SUCCESS) { +@@ -3068,10 +3071,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.54.0 + diff --git a/bind.spec b/bind.spec index fcfa2d0..d5492d1 100644 --- a/bind.spec +++ b/bind.spec @@ -80,7 +80,7 @@ License: MPL-2.0 AND ISC AND MIT AND BSD-3-Clause AND BSD-2-Clause # Before rebasing bind, ensure bind-dyndb-ldap is ready to be rebuild and use side-tag with it. # Updating just bind will cause freeipa-dns-server package to be uninstallable. Version: 9.18.33 -Release: 15%{?dist}.1 +Release: 15%{?dist}.2 Epoch: 32 Url: https://www.isc.org/downloads/bind/ # @@ -160,6 +160,10 @@ Patch227: bind-9.20-CVE-2025-8677-dual-signing-test.patch # https://gitlab.isc.org/isc-projects/bind9/-/commit/5ef459eeaa92222ad28d2186f5eae9a586dece70 Patch228: bind-9.18-CVE-2026-1519.patch Patch229: bind-9.18-CVE-2026-1519-test.patch +# https://gitlab.isc.org/isc-projects/bind9/-/commit/03ce21cf3099bd461f95ac1f4f22cce80ae1ba65 +Patch230: bind-9.18-CVE-2026-3039.patch +# https://gitlab.isc.org/isc-projects/bind9/-/commit/7ce6ce37b1b04af0953ed2d3211587465085600e +Patch231: bind-9.18-CVE-2026-5946.patch %{?systemd_ordering} # https://fedoraproject.org/wiki/Changes/RPMSuportForSystemdSysusers @@ -961,6 +965,10 @@ fi; %endif %changelog +* Mon May 25 2026 Petr Menšík - 32:9.18.33-15.2 +- 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.18.33-15.1 - Prevent Denial of Service via maliciously crafted DNSSEC-validated zone (CVE-2026-1519)