From 7fbd4526c42806826ca1b335c5686b054486e613 Mon Sep 17 00:00:00 2001
From: Pierre Rogier <progier@redhat.com>
Date: Thu, 27 Feb 2025 16:36:48 +0100
Subject: [PATCH] Security fix for CVE-2025-2487

Origin: https://github.com/389ds/389-ds-base/commit/40e752922e6160356399cd07169ec1f76dd7db99

Description:
A denial of service vulnerability was found in the 389 Directory Server.
The 389 Directory Server may crash (Null Pointer Exception) after some
failed rename subtree operations (i.e. MODDN) issued by a user having enough
privileges to do so.

References:
- https://access.redhat.com/security/cve/CVE-2025-2487
- https://bugzilla.redhat.com/show_bug.cgi?id=2353071
---
 ldap/servers/slapd/back-ldbm/findentry.c   | 36 +++++++++++++++++-----
 ldap/servers/slapd/back-ldbm/ldbm_add.c    |  2 ++
 ldap/servers/slapd/back-ldbm/ldbm_modify.c |  6 ++++
 ldap/servers/slapd/back-ldbm/ldbm_modrdn.c | 13 ++++++--
 4 files changed, 48 insertions(+), 9 deletions(-)

diff --git a/ldap/servers/slapd/back-ldbm/findentry.c b/ldap/servers/slapd/back-ldbm/findentry.c
index 7bb56ef2c4..907b4367a1 100644
--- a/ldap/servers/slapd/back-ldbm/findentry.c
+++ b/ldap/servers/slapd/back-ldbm/findentry.c
@@ -99,6 +99,7 @@ find_entry_internal_dn(
     int isroot = 0;
     int op_type;
     int reverted_entry = 0;
+    int return_err = LDAP_SUCCESS;
 
     /* get the managedsait ldap message control */
     slapi_pblock_get(pb, SLAPI_MANAGEDSAIT, &managedsait);
@@ -121,6 +122,7 @@ find_entry_internal_dn(
                 if (rc) { /* if check_entry_for_referral returns non-zero, result is sent. */
                     *rc = FE_RC_SENT_RESULT;
                 }
+                slapi_set_ldap_result(pb, LDAP_REFERRAL, NULL, NULL, 0, NULL);
                 return (NULL);
             }
         }
@@ -153,7 +155,12 @@ find_entry_internal_dn(
         slapi_log_err(SLAPI_LOG_ERR, "find_entry_internal_dn", "Retry count exceeded (%s)\n", slapi_sdn_get_dn(sdn));
     }
     if (reverted_entry) {
+        CACHE_RETURN(&inst->inst_cache, &e);
+        slapi_set_ldap_result(pb, LDAP_BUSY, NULL, NULL, 0, NULL);
         slapi_send_ldap_result(pb, LDAP_BUSY, NULL, "target entry busy because of a canceled operation", 0, NULL);
+        if (rc) {
+            *rc = FE_RC_SENT_RESULT;  /* Result is sent */
+        }
         return (NULL);
     }
     /*
@@ -179,6 +186,7 @@ find_entry_internal_dn(
                 if (rc) { /* if check_entry_for_referral returns non-zero, result is sent. */
                     *rc = FE_RC_SENT_RESULT;
                 }
+                slapi_set_ldap_result(pb, LDAP_REFERRAL, NULL, NULL, 0, NULL);
                 return (NULL);
             }
             /* else fall through to no such object */
@@ -189,7 +197,7 @@ find_entry_internal_dn(
             if (me && !isroot) {
                 /* If not root, you may not want to reveal it. */
                 int acl_type = -1;
-                int return_err = LDAP_NO_SUCH_OBJECT;
+                return_err = LDAP_NO_SUCH_OBJECT;
                 err = LDAP_SUCCESS;
                 switch (op_type) {
                 case SLAPI_OPERATION_ADD:
@@ -230,18 +238,22 @@ find_entry_internal_dn(
                      * do not return the "matched" DN.
                      * Plus, the bind case returns LDAP_INAPPROPRIATE_AUTH.
                      */
+                    slapi_set_ldap_result(pb, return_err, NULL, NULL, 0, NULL);
                     slapi_send_ldap_result(pb, return_err, NULL, NULL, 0, NULL);
                 } else {
+                slapi_set_ldap_result(pb, LDAP_NO_SUCH_OBJECT, NULL, NULL, 0, NULL);
                     slapi_send_ldap_result(pb, LDAP_NO_SUCH_OBJECT,
                                            (char *)slapi_sdn_get_dn(&ancestorsdn), NULL, 0, NULL);
                 }
             } else {
+                slapi_set_ldap_result(pb, LDAP_NO_SUCH_OBJECT, NULL, NULL, 0, NULL);
                 slapi_send_ldap_result(pb, LDAP_NO_SUCH_OBJECT,
                                        (char *)slapi_sdn_get_dn(&ancestorsdn), NULL, 0, NULL);
             }
         } else {
-            slapi_send_ldap_result(pb, (LDAP_INVALID_DN_SYNTAX == err) ? LDAP_INVALID_DN_SYNTAX : LDAP_OPERATIONS_ERROR,
-                                   (char *)slapi_sdn_get_dn(&ancestorsdn), NULL, 0, NULL);
+            return_err = (LDAP_INVALID_DN_SYNTAX == err) ? LDAP_INVALID_DN_SYNTAX : LDAP_OPERATIONS_ERROR;
+            slapi_set_ldap_result(pb, return_err, NULL, NULL, 0, NULL);
+            slapi_send_ldap_result(pb, return_err, (char *)slapi_sdn_get_dn(&ancestorsdn), NULL, 0, NULL);
         }
         if (rc) {
             *rc = FE_RC_SENT_RESULT;
@@ -265,13 +277,15 @@ find_entry_internal_uniqueid(
     backend *be,
     const char *uniqueid,
     int lock,
-    back_txn *txn)
+    back_txn *txn,
+    int *rc)
 {
     ldbm_instance *inst = (ldbm_instance *)be->be_instance_info;
     struct backentry *e;
     int err;
     size_t tries = 0;
     int reverted_entry = 0;
+    int return_err = 0;
 
     while ((tries < LDBM_CACHE_RETRY_COUNT) &&
            (e = uniqueid2entry(be, uniqueid, txn, &err)) != NULL) {
@@ -307,12 +321,20 @@ find_entry_internal_uniqueid(
     }
 
     if (reverted_entry) {
+        slapi_set_ldap_result(pb, LDAP_BUSY, NULL, NULL, 0, NULL);
         slapi_send_ldap_result(pb, LDAP_BUSY, NULL, "target entry busy because of a canceled operation", 0, NULL);
+        if (rc) {
+            *rc = FE_RC_SENT_RESULT;  /* Result is sent */
+        }
         return (NULL);
     } else {
         /* entry not found */
-        slapi_send_ldap_result(pb, (0 == err || DBI_RC_NOTFOUND == err) ? LDAP_NO_SUCH_OBJECT : LDAP_OPERATIONS_ERROR, NULL /* matched */, NULL,
-                               0, NULL);
+        return_err = (0 == err || DBI_RC_NOTFOUND == err) ? LDAP_NO_SUCH_OBJECT : LDAP_OPERATIONS_ERROR;
+        slapi_set_ldap_result(pb, return_err, NULL, NULL, 0, NULL);
+        slapi_send_ldap_result(pb, return_err, NULL /* matched */, NULL, 0, NULL);
+        if (rc) {
+            *rc = FE_RC_SENT_RESULT;  /* Result is sent */
+        }
     }
     slapi_log_err(SLAPI_LOG_TRACE,
                   "find_entry_internal_uniqueid", "<= not found; uniqueid = (%s)\n",
@@ -334,7 +356,7 @@ find_entry_internal(
     if (addr->uniqueid != NULL) {
         slapi_log_err(SLAPI_LOG_TRACE, "find_entry_internal", "=> (uniqueid=%s) lock %d\n",
                       addr->uniqueid, lock);
-        return (find_entry_internal_uniqueid(pb, be, addr->uniqueid, lock, txn));
+        return (find_entry_internal_uniqueid(pb, be, addr->uniqueid, lock, txn, rc));
     } else {
         struct backentry *entry = NULL;
 
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_add.c b/ldap/servers/slapd/back-ldbm/ldbm_add.c
index 4a5ed025ee..6a41740524 100644
--- a/ldap/servers/slapd/back-ldbm/ldbm_add.c
+++ b/ldap/servers/slapd/back-ldbm/ldbm_add.c
@@ -435,6 +435,8 @@ ldbm_back_add(Slapi_PBlock *pb)
                     slapi_log_err(SLAPI_LOG_BACKLDBM, "ldbm_back_add",
                                   "find_entry2modify_only returned NULL parententry pdn: %s, uniqueid: %s\n",
                                   slapi_sdn_get_dn(&parentsdn), addr.uniqueid ? addr.uniqueid : "none");
+                    slapi_pblock_get(pb, SLAPI_RESULT_CODE, &ldap_result_code);
+                    goto error_return;
                 }
                 modify_init(&parent_modify_c, parententry);
             }
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_modify.c b/ldap/servers/slapd/back-ldbm/ldbm_modify.c
index 6c90389f5a..ea49a4c567 100644
--- a/ldap/servers/slapd/back-ldbm/ldbm_modify.c
+++ b/ldap/servers/slapd/back-ldbm/ldbm_modify.c
@@ -177,6 +177,12 @@ modify_update_all(backend *be, Slapi_PBlock *pb, modify_context *mc, back_txn *t
         slapi_pblock_get(pb, SLAPI_OPERATION, &operation);
         is_ruv = operation_is_flag_set(operation, OP_FLAG_REPL_RUV);
     }
+    if (NULL == mc->new_entry) {
+        /* test entry to avoid crashing in id2entry_add_ext */
+        slapi_log_err(SLAPI_LOG_BACKLDBM, "modify_update_all",
+                      "No entry in modify_context ==> operation is aborted.\n");
+        return -1;
+    }
     /*
      * Update the ID to Entry index.
      * Note that id2entry_add replaces the entry, so the Entry ID stays the same.
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c b/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c
index 78d01fa4e0..066f53ad67 100644
--- a/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c
+++ b/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c
@@ -485,8 +485,8 @@ ldbm_back_modrdn(Slapi_PBlock *pb)
             slapi_pblock_get(pb, SLAPI_TARGET_ADDRESS, &old_addr);
             e = find_entry2modify(pb, be, old_addr, &txn, &result_sent);
             if (e == NULL) {
-                ldap_result_code = -1;
-                goto error_return; /* error result sent by find_entry2modify() */
+                slapi_pblock_get(pb, SLAPI_RESULT_CODE, &ldap_result_code);
+                goto error_return; /* error result set and sent by find_entry2modify() */
             }
             if (slapi_entry_flag_is_set(e->ep_entry, SLAPI_ENTRY_FLAG_TOMBSTONE) &&
                 !is_resurect_operation) {
@@ -518,6 +518,11 @@ ldbm_back_modrdn(Slapi_PBlock *pb)
                 oldparent_addr.uniqueid = NULL;
             }
             parententry = find_entry2modify_only(pb, be, &oldparent_addr, &txn, &result_sent);
+            if (parententry == NULL) {
+                slapi_pblock_get(pb, SLAPI_RESULT_CODE, &ldap_result_code);
+                goto error_return; /* error result set and sent by find_entry2modify() */
+            }
+
             modify_init(&parent_modify_context, parententry);
 
             /* Fetch and lock the new parent of the entry that is moving */
@@ -528,6 +533,10 @@ ldbm_back_modrdn(Slapi_PBlock *pb)
                 }
                 newparententry = find_entry2modify_only(pb, be, newsuperior_addr, &txn, &result_sent);
                 slapi_ch_free_string(&newsuperior_addr->uniqueid);
+                if (newparententry == NULL) {
+                    slapi_pblock_get(pb, SLAPI_RESULT_CODE, &ldap_result_code);
+                    goto error_return; /* error result set and sent by find_entry2modify() */
+                }
                 modify_init(&newparent_modify_context, newparententry);
             }
 
