[31600] in CVS-changelog-for-Kerberos-V5
krb5 commit: Fix use-after-free of LDAP handle after rebind
daemon@ATHENA.MIT.EDU (ghudson@mit.edu)
Mon Nov 2 19:35:11 2026
From: ghudson@mit.edu
To: cvs-krb5@mit.edu
Message-Id: <20261103003504.84D831057EF@krbdev.mit.edu>
Date: Mon, 2 Nov 2026 19:35:04 -0500 (EST)
MIME-Version: 1.0
Reply-To: krbdev@mit.edu
Content-Type: text/plain; charset="us-ascii"
Content-Transfer-Encoding: 7bit
Errors-To: cvs-krb5-bounces@mit.edu
https://github.com/krb5/krb5/commit/ea21d47ed4eedb530c76a5a4a7e69a6ed3a26770
commit ea21d47ed4eedb530c76a5a4a7e69a6ed3a26770
Author: Alex Moore <98565783+lightspd10@users.noreply.github.com>
Date: Fri Oct 23 20:32:40 2026 +0100
Fix use-after-free of LDAP handle after rebind
Commit 5d3fe31bf1dc48e8ee946bf65428611958cac329 ("Add alias support"),
moved the LDAP search for a principal into helper functions
search_princ() and search_at(). When a search fails because the LDAP
connection has gone away, the LDAP_SEARCH macro calls
krb5_ldap_rebind(), which changes the handle. It either replaces the
handle's LDAP connection and frees the old one, or, if the server
cannot be reached, calls krb5_ldap_request_next_handle_from_pool(),
which frees every pooled handle for that server (including the
caller's) and substitutes the handle of another server or NULL. When
the search happens in one of the new helper functions, LDAP_SEARCH
only updates the local variables of the helper, leaving the caller
with stale pointers. This commit first appeared in release 1.22.
check_dn_exists() and validate_xargs() have the same flaw, which
predates 1.22, but they are only used by kadmin operations.
Pass the server handle by reference to search_at(), search_princ(),
check_dn_exists(), validate_xargs(), and add_alias(). In the callers,
reset LDAP pointer from the handle after each call.
Co-authored by Copilot App.
[ghudson@mit.edu: edited commit message]
ticket: 9279 (new)
tags: pullup
target_version: 1.22-next
src/plugins/kdb/ldap/libkdb_ldap/ldap_principal2.c | 56 +++++++++++++++-------
1 file changed, 38 insertions(+), 18 deletions(-)
diff --git a/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal2.c b/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal2.c
index 5bacaee28..dfdd96ab7 100644
--- a/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal2.c
+++ b/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal2.c
@@ -106,15 +106,22 @@ berval2tl_data(struct berval *in, krb5_tl_data **out)
* single result. Set *entry_out to an object containing all principal
* attributes. Set *result_out to the result message to be freed with
* ldap_msgfree(). Set both to NULL if no matching entry is found.
+ *
+ * The search can fail over to another server, which frees the handle passed
+ * in, or reconnect to the same server, which replaces the handle's LDAP
+ * connection. *ldap_server_handle_ptr is updated to the handle now in use
+ * (NULL if none could be obtained), and callers must reload their LDAP pointer
+ * from it.
*/
static krb5_error_code
search_at(krb5_context context, krb5_ldap_context *ldap_context,
- krb5_ldap_server_handle *ldap_server_handle, const char *base,
+ krb5_ldap_server_handle **ldap_server_handle_ptr, const char *base,
int scope, const char *filter, const char *user,
LDAPMessage **entry_out, LDAPMessage **result_out)
{
krb5_error_code st, tempst;
LDAPMessage *result = NULL;
+ krb5_ldap_server_handle *ldap_server_handle = *ldap_server_handle_ptr;
LDAP *ld = ldap_server_handle->ldap_handle;
int nentries;
@@ -134,10 +141,12 @@ search_at(krb5_context context, krb5_ldap_context *ldap_context,
if (nentries == 1) {
*result_out = result;
*entry_out = ldap_first_entry(ld, result);
+ *ldap_server_handle_ptr = ldap_server_handle;
return 0;
}
cleanup:
+ *ldap_server_handle_ptr = ldap_server_handle;
ldap_msgfree(result);
return st;
}
@@ -146,11 +155,12 @@ cleanup:
* Search for an LDAP object matching princ, either at the specified dn with
* base scope, or, if dn is NULL, in all configured subtrees with the
* configured search scope (usually subtree). Set *entry_out and *result_out
- * in the same way as search_at().
+ * in the same way as search_at(), which also describes
+ * *ldap_server_handle_ptr.
*/
static krb5_error_code
search_princ(krb5_context context, krb5_ldap_context *ldap_context,
- krb5_ldap_server_handle *ldap_server_handle,
+ krb5_ldap_server_handle **ldap_server_handle_ptr,
krb5_const_principal princ, const char *dn,
LDAPMessage **entry_out, LDAPMessage **result_out)
{
@@ -178,7 +188,7 @@ search_princ(krb5_context context, krb5_ldap_context *ldap_context,
}
if (dn != NULL) {
- st = search_at(context, ldap_context, ldap_server_handle, dn,
+ st = search_at(context, ldap_context, ldap_server_handle_ptr, dn,
LDAP_SCOPE_BASE, filter, user, entry_out, result_out);
goto cleanup;
}
@@ -188,7 +198,7 @@ search_princ(krb5_context context, krb5_ldap_context *ldap_context,
goto cleanup;
for (i = 0; i < ntrees; i++) {
- st = search_at(context, ldap_context, ldap_server_handle,
+ st = search_at(context, ldap_context, ldap_server_handle_ptr,
subtreelist[i], ldap_context->lrparams->search_scope,
filter, user, entry_out, result_out);
if (st || *entry_out != NULL)
@@ -244,10 +254,12 @@ krb5_ldap_get_principal(krb5_context context, krb5_const_principal searchfor,
GET_HANDLE();
- st = search_princ(context, ldap_context, ldap_server_handle, searchfor,
+ st = search_princ(context, ldap_context, &ldap_server_handle, searchfor,
NULL, &ent, &result);
if (st)
goto cleanup;
+ /* The search may have moved to a different connection. */
+ ld = ldap_server_handle->ldap_handle;
if (ent == NULL) {
st = KRB5_KDB_NOENTRY;
goto cleanup;
@@ -711,17 +723,19 @@ check_dn_in_container(krb5_context context, const char *dn,
static krb5_error_code
check_dn_exists(krb5_context context,
- krb5_ldap_server_handle *ldap_server_handle,
+ krb5_ldap_server_handle **ldap_server_handle_ptr,
const char *dn, krb5_boolean nonkrb_only)
{
krb5_error_code st = 0, tempst;
krb5_ldap_context *ldap_context = context->dal_handle->db_context;
+ krb5_ldap_server_handle *ldap_server_handle = *ldap_server_handle_ptr;
LDAP *ld = ldap_server_handle->ldap_handle;
LDAPMessage *result = NULL, *ent;
char *attrs[] = { "krbticketpolicyreference", "krbprincipalname", NULL };
char **values;
LDAP_SEARCH_1(dn, LDAP_SCOPE_BASE, 0, attrs, IGNORE_STATUS);
+ *ldap_server_handle_ptr = ldap_server_handle;
if (st != LDAP_SUCCESS)
return set_ldap_error(context, st, OP_SEARCH);
@@ -749,7 +763,7 @@ cleanup:
static krb5_error_code
validate_xargs(krb5_context context,
- krb5_ldap_server_handle *ldap_server_handle,
+ krb5_ldap_server_handle **ldap_server_handle_ptr,
const xargs_t *xargs, const char *standalone_dn,
char *const *subtrees, size_t ntrees)
{
@@ -761,7 +775,7 @@ validate_xargs(krb5_context context,
if (st)
return st;
/* The supplied dn must exist without Kerberos attributes. */
- st = check_dn_exists(context, ldap_server_handle, xargs->dn, TRUE);
+ st = check_dn_exists(context, ldap_server_handle_ptr, xargs->dn, TRUE);
if (st)
return st;
}
@@ -772,7 +786,7 @@ validate_xargs(krb5_context context,
if (st)
return st;
/* The supplied linkdn must exist. */
- st = check_dn_exists(context, ldap_server_handle, xargs->linkdn,
+ st = check_dn_exists(context, ldap_server_handle_ptr, xargs->linkdn,
FALSE);
if (st)
return st;
@@ -791,20 +805,22 @@ validate_xargs(krb5_context context,
static krb5_error_code
add_alias(krb5_context context, krb5_ldap_context *ldap_context,
- krb5_ldap_server_handle *ldap_server_handle,
+ krb5_ldap_server_handle **ldap_server_handle_ptr,
krb5_const_principal alias, krb5_const_principal target)
{
krb5_error_code st;
- LDAP *ld = ldap_server_handle->ldap_handle;
+ LDAP *ld = NULL;
LDAPMessage *ent, *result = NULL;
LDAPMod **mods = NULL;
char **canon = NULL, **names = NULL, *user = NULL, *dn = NULL;
char *strval[2] = { NULL }, errbuf[1024];
- st = search_princ(context, ldap_context, ldap_server_handle, target, NULL,
- &ent, &result);
+ st = search_princ(context, ldap_context, ldap_server_handle_ptr, target,
+ NULL, &ent, &result);
if (st)
goto cleanup;
+ /* The search may have moved to a different connection. */
+ ld = (*ldap_server_handle_ptr)->ldap_handle;
if (ent == NULL) {
st = KRB5_KDB_NOENTRY;
k5_setmsg(context, st, _("target principal not found"));
@@ -922,8 +938,8 @@ krb5_ldap_put_principal(krb5_context context, krb5_db_entry *entry,
if (st)
goto cleanup;
if (alias_target != NULL) {
- st = add_alias(context, ldap_context, ldap_server_handle, entry->princ,
- alias_target);
+ st = add_alias(context, ldap_context, &ldap_server_handle,
+ entry->princ, alias_target);
krb5_free_principal(context, alias_target);
goto cleanup;
}
@@ -964,10 +980,12 @@ krb5_ldap_put_principal(krb5_context context, krb5_db_entry *entry,
* unique (only one object entry has a particular krbprincipalname
* attribute).
*/
- st = search_princ(context, ldap_context, ldap_server_handle,
+ st = search_princ(context, ldap_context, &ldap_server_handle,
entry->princ, principal_dn, &ent, &result);
if (st && st != KRB5_KDB_NOENTRY)
goto cleanup;
+ /* The search may have moved to a different connection. */
+ ld = ldap_server_handle->ldap_handle;
if (ent != NULL && principal_dn == NULL) {
/* Remember this DN to be modified later. */
principal_dn = ldap_get_dn(ld, ent);
@@ -1046,10 +1064,12 @@ krb5_ldap_put_principal(krb5_context context, krb5_db_entry *entry,
if (st)
goto cleanup;
- st = validate_xargs(context, ldap_server_handle, &xargs,
+ st = validate_xargs(context, &ldap_server_handle, &xargs,
standalone_principal_dn, subtreelist, ntrees);
if (st)
goto cleanup;
+ /* The validation may have moved to a different connection. */
+ ld = ldap_server_handle->ldap_handle;
}
if (xargs.linkdn != NULL) {
_______________________________________________
cvs-krb5 mailing list
cvs-krb5@mit.edu
https://mailman.mit.edu/mailman/listinfo/cvs-krb5