[9.18] [CVE-2026-3039] sec: usr: Fix GSS-API resource leak
Fixed a memory leak where each GSS-API TKEY negotiation leaked a security context inside the GSS library. An unauthenticated attacker could exhaust server memory by sending repeated TKEY queries to a server with tkey-gssapi-keytab configured. The leaked memory was allocated by the GSS library, bypassing BIND's memory accounting. Multi-round GSS-API negotiation (GSS_S_CONTINUE_NEEDED) is now rejected, as BIND never supported it correctly and Kerberos/SPNEGO completes in a single round. Also implemented missing RFC 3645 requirement: the client now verifies that mutual authentication and integrity flags are granted by the GSS-API mechanism (Section 3.1.1). Resolves-Vulnerability: CVE-2026-3039 Resolves: RHEL-177758
This commit is contained in:
parent
d598399af2
commit
84ee08768a
386
bind-9.18-CVE-2026-3039.patch
Normal file
386
bind-9.18-CVE-2026-3039.patch
Normal file
@ -0,0 +1,386 @@
|
||||
From 775156d85236e8cc032191f16304af36f30f30c6 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Ond=C5=99ej=20Sur=C3=BD?= <ondrej@isc.org>
|
||||
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
|
||||
|
||||
@ -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: 18%{?dist}
|
||||
Release: 19%{?dist}
|
||||
Epoch: 32
|
||||
Url: https://www.isc.org/downloads/bind/
|
||||
#
|
||||
@ -166,6 +166,8 @@ Patch231: bind-9.20-stale-cname-tests.patch
|
||||
# https://gitlab.isc.org/isc-projects/bind9/-/commit/5ef459eeaa92222ad28d2186f5eae9a586dece70
|
||||
Patch232: bind-9.18-CVE-2026-1519.patch
|
||||
Patch233: bind-9.18-CVE-2026-1519-test.patch
|
||||
# https://gitlab.isc.org/isc-projects/bind9/-/commit/03ce21cf3099bd461f95ac1f4f22cce80ae1ba65
|
||||
Patch234: bind-9.18-CVE-2026-3039.patch
|
||||
|
||||
%{?systemd_ordering}
|
||||
# https://fedoraproject.org/wiki/Changes/RPMSuportForSystemdSysusers
|
||||
@ -967,6 +969,9 @@ fi;
|
||||
%endif
|
||||
|
||||
%changelog
|
||||
* Mon May 25 2026 Petr Menšík <pemensik@redhat.com> - 32:9.18.33-19
|
||||
- Fix GSS-API resource leak (CVE-2026-3039)
|
||||
|
||||
* Fri Mar 27 2026 Petr Menšík <pemensik@redhat.com> - 32:9.18.33-18
|
||||
- Prevent Denial of Service via maliciously crafted DNSSEC-validated zone
|
||||
(CVE-2026-1519)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user