diff --git a/bind-9.16-CVE-2026-3039.patch b/bind-9.16-CVE-2026-3039.patch new file mode 100644 index 0000000..d56869f --- /dev/null +++ b/bind-9.16-CVE-2026-3039.patch @@ -0,0 +1,376 @@ +From c824f5e341094edc653f64e730a7471d31f9cc14 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 3883058bf284de2889e4e3676767e58ad91a0ae3) +(cherry picked from commit 58c646c4b95efcda759cf194a534290eef38135e) + +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 f2240d2d06a1a68b622bd6b00a52c6fe4274426d) +(cherry picked from commit 7eef47ce676672f65b4988b1b5c086c4f30a9b70) +--- + lib/dns/gssapictx.c | 124 +++++++++++++++++++---------------- + lib/dns/include/dst/gssapi.h | 17 ++--- + lib/dns/tkey.c | 52 +++++++-------- + 3 files changed, 98 insertions(+), 95 deletions(-) + +diff --git a/lib/dns/gssapictx.c b/lib/dns/gssapictx.c +index b0c62497a5..2a78169869 100644 +--- a/lib/dns/gssapictx.c ++++ b/lib/dns/gssapictx.c +@@ -606,7 +606,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", +@@ -632,12 +639,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); +@@ -648,7 +649,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; +@@ -661,16 +662,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 defined(ISC_PLATFORM_GSSAPI_KRB5_HEADER) || defined(WIN32) + gret = gsskrb5_register_acceptor_identity(gssapi_keytab); +@@ -715,8 +711,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: +@@ -729,7 +732,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))); +@@ -740,59 +743,70 @@ 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)); ++ result = isc_buffer_copyregion(*outtokenp, &r); ++ if (result != ISC_R_SUCCESS) { ++ goto out; ++ } + (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 out; ++ } + +- 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; ++ result = dns_name_fromtext(principal, &namebuf, dns_rootname, 0, NULL); ++ if (result != ISC_R_SUCCESS) { ++ goto out; + } + + *ctxout = context; + + out: ++ 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 10f8467aef..92dad43440 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 b4f12a0831..e7800113d9 100644 +--- a/lib/dns/tkey.c ++++ b/lib/dns/tkey.c +@@ -529,19 +529,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); + + /* +@@ -550,28 +540,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) { + #ifdef GSSAPI + OM_uint32 gret, minor, lifetime; +@@ -634,6 +623,10 @@ 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); + } +@@ -646,10 +639,10 @@ failure: + isc_buffer_free(&outtoken); + } + +- tkey_log("process_gsstkey(): %s", isc_result_totext(result)); /* XXXSRA +- */ +- +- return (result); ++ if (result != ISC_R_SUCCESS) { ++ tkey_log("process_gsstkey(): %s", isc_result_totext(result)); ++ } ++ return result; + } + + static isc_result_t +@@ -1558,9 +1551,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/bind9.16.spec b/bind9.16.spec index e1aae9f..b697fd7 100644 --- a/bind9.16.spec +++ b/bind9.16.spec @@ -62,7 +62,7 @@ Summary: The Berkeley Internet Name Domain (BIND) DNS (Domain Name System) serv Name: bind9.16 License: MPLv2.0 Version: 9.16.23 -Release: 0.22%{?dist}.5 +Release: 0.22%{?dist}.6 Epoch: 32 Url: https://www.isc.org/downloads/bind/ # @@ -182,6 +182,8 @@ Patch224: bind-9.16-CVE-2025-40780.patch Patch225: bind-9.16-CVE-2025-40778.patch # https://gitlab.isc.org/isc-projects/bind9/-/commit/a5e8d2354385d4f42a58113b16960d85ec306b09 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 %{?systemd_ordering} Requires: coreutils @@ -522,6 +524,7 @@ in HTML and PDF format. %patch224 -p1 -b .CVE-2025-40780 %patch225 -p1 -b .CVE-2025-40778 %patch226 -p1 -b .CVE-2026-1519 +%patch230 -p1 -b .CVE-2026-3039 %if %{with PKCS11} %patch135 -p1 -b .config-pkcs11 @@ -1264,6 +1267,9 @@ fi; %endif %changelog +* Mon May 25 2026 Petr Menšík - 32:9.16.23-0.22.6 +- Fix GSS-API resource leak (CVE-2026-3039) + * Fri Mar 27 2026 Petr Menšík - 32:9.16.23-0.22.5 - Prevent Denial of Service via maliciously crafted DNSSEC-validated zone (CVE-2026-1519)