diff --git a/bind-9.11-CVE-2026-3039.patch b/bind-9.11-CVE-2026-3039.patch new file mode 100644 index 0000000..e758c40 --- /dev/null +++ b/bind-9.11-CVE-2026-3039.patch @@ -0,0 +1,375 @@ +From 7c2afb490391a308733da1c37730328477bd80d3 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 e3c74f19c6b3b29ec649062f95bf1df5688a0799) + +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 f2f1c0cb244a6d9e2fd8550a38bd2465856abce3) +--- + lib/dns/gssapictx.c | 127 ++++++++++++++++++++--------------- + lib/dns/include/dst/gssapi.h | 17 ++--- + lib/dns/tkey.c | 50 +++++++------- + 3 files changed, 104 insertions(+), 90 deletions(-) + +diff --git a/lib/dns/gssapictx.c b/lib/dns/gssapictx.c +index 482c25e1cc..a58c3071d4 100644 +--- a/lib/dns/gssapictx.c ++++ b/lib/dns/gssapictx.c +@@ -594,7 +594,14 @@ dst_gssapi_initctx(dns_name_t *name, isc_buffer_t *intoken, + 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", +@@ -619,14 +626,10 @@ dst_gssapi_initctx(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) ++out: ++ if (gouttoken.length != 0U) { + (void)gss_release_buffer(&minor, &gouttoken); ++ } + (void)gss_release_name(&minor, &gname); + return (result); + } +@@ -634,7 +637,7 @@ dst_gssapi_initctx(dns_name_t *name, isc_buffer_t *intoken, + isc_result_t + dst_gssapi_acceptctx(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, + gss_ctx_id_t *ctxout, dns_name_t *principal, + isc_mem_t *mctx) { + isc_region_t r; +@@ -647,15 +650,11 @@ dst_gssapi_acceptctx(gss_cred_id_t cred, + 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); +@@ -697,8 +696,15 @@ dst_gssapi_acceptctx(gss_cred_id_t cred, + + 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: +@@ -711,7 +717,7 @@ dst_gssapi_acceptctx(gss_cred_id_t cred, + 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))); +@@ -722,55 +728,70 @@ dst_gssapi_acceptctx(gss_cred_id_t cred, + } + + if (gouttoken.length > 0U) { +- RETERR(isc_buffer_allocate(mctx, outtoken, ++ RETERR(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: ++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 8e31587871..bf1c4c959a 100644 +--- a/lib/dns/include/dst/gssapi.h ++++ b/lib/dns/include/dst/gssapi.h +@@ -126,20 +126,17 @@ dst_gssapi_acceptctx(gss_cred_id_t cred, + * 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 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 ae7a76cd3d..03bf521ee6 100644 +--- a/lib/dns/tkey.c ++++ b/lib/dns/tkey.c +@@ -503,18 +503,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); + + /* +@@ -523,25 +514,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; +@@ -612,6 +605,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); + +@@ -621,10 +618,10 @@ failure: + if (outtoken != NULL) + 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 +@@ -1521,9 +1518,8 @@ dns_tkey_gssnegotiate(dns_message_t *qmsg, dns_message_t *rmsg, + &dstkey, 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(tkeyname, +-- +2.54.0 + diff --git a/bind.spec b/bind.spec index 8b9bcf6..247d486 100644 --- a/bind.spec +++ b/bind.spec @@ -68,7 +68,7 @@ Summary: The Berkeley Internet Name Domain (BIND) DNS (Domain Name System) serv Name: bind License: MPLv2.0 Version: 9.11.36 -Release: 16%{?PATCHVER:.%{PATCHVER}}%{?PREVER:.%{PREVER}}%{?dist}.7 +Release: 16%{?PATCHVER:.%{PATCHVER}}%{?PREVER:.%{PREVER}}%{?dist}.8 Epoch: 32 Url: https://www.isc.org/downloads/bind/ # @@ -207,6 +207,8 @@ Patch213: bind-9.11-d-max-records-checkconf.patch Patch214: bind-9.11-CVE-2025-40778.patch # https://gitlab.isc.org/isc-projects/bind9/commit/e5357c1623da3842227d2c76468b76bc983584d6 Patch215: bind-9.11-CVE-2026-1519.patch +# https://gitlab.isc.org/isc-projects/bind9/commit/94a96c69193587b2c6df30055fbb0c1d00622a7d +Patch216: bind-9.11-CVE-2026-3039.patch # SDB patches Patch11: bind-9.3.2b2-sdbsrc.patch @@ -637,6 +639,7 @@ are used for building ISC DHCP. %patch -P 213 -p1 -b .records-checkconf %patch -P 214 -p1 -b .CVE-2025-40778 %patch -P 215 -p1 -b .CVE-2026-1519 +%patch -P 216 -p1 -b .CVE-2026-3039 mkdir lib/dns/tests/testdata/dstrandom cp -a %{SOURCE50} lib/dns/tests/testdata/dstrandom/random.data @@ -1689,6 +1692,9 @@ rm -rf ${RPM_BUILD_ROOT} %endif %changelog +* Wed May 27 2026 Petr Menšík - 32:9.11.36-16.8 +- Fix GSS-API resource leak (CVE-2026-3039) + * Fri Mar 27 2026 Petr Menšík - 32:9.11.36-16.7 - Denial of Service via maliciously crafted DNSSEC-validated zone (CVE-2026-1519)