import Oracle_OSS 389-ds-base-1.4.3.39-24.module+el8.10.0+90914+745232ac

This commit is contained in:
AlmaLinux RelEng Bot 2026-06-18 13:44:57 -04:00
parent 1d52b251a9
commit c4763fe41f
17 changed files with 3422 additions and 2 deletions

View File

@ -0,0 +1,399 @@
From 71583a67249df813098f0cca39b04198b76e6349 Mon Sep 17 00:00:00 2001
From: Simon Pichugin <spichugi@redhat.com>
Date: Mon, 30 Mar 2026 22:18:49 -0700
Subject: [PATCH 01/13] Issue 7366 - Memory leaks in syncrepl plugin during
persistent search operations (#7367)
Description: When running a syncrepl persistent search as a non-root user,
LeakSanitizer detects memory leaks in the sync repl plugin.
Multiple cleanup paths for SyncRequest had inconsistent
and incomplete resource freeing, leaking pblock contents, filters,
queue nodes, and base DNs. Per-thread operation lists were also leaked
on thread exit due to a missing NSPR thread-private destructor.
Consolidate all SyncRequest cleanup into sync_request_free() and
register a destructor for per-thread OPERATION_PL_CTX_T lists.
Add a test for non-root persistent syncrepl search verifying no
OPERATIONS_ERROR or 'Missing aclpb' errors.
Fixes: https://github.com/389ds/389-ds-base/issues/7366
Reviewed by: @progier389 (Thanks!)
---
.../suites/syncrepl_plugin/basic_test.py | 176 +++++++++++++++++-
ldap/servers/plugins/sync/sync_init.c | 24 ++-
ldap/servers/plugins/sync/sync_persist.c | 103 ++++++----
3 files changed, 264 insertions(+), 39 deletions(-)
diff --git a/dirsrvtests/tests/suites/syncrepl_plugin/basic_test.py b/dirsrvtests/tests/suites/syncrepl_plugin/basic_test.py
index cdf35eeaa..ff9be816f 100644
--- a/dirsrvtests/tests/suites/syncrepl_plugin/basic_test.py
+++ b/dirsrvtests/tests/suites/syncrepl_plugin/basic_test.py
@@ -20,7 +20,7 @@ from lib389.idm.group import Groups
from lib389.topologies import topology_st as topology
from lib389.topologies import topology_m2 as topo_m2
from lib389.paths import Paths
-from lib389.utils import ds_is_older
+from lib389.utils import ds_is_older, is_fips, ensure_bytes
from lib389.plugins import RetroChangelogPlugin, ContentSyncPlugin, ContentSynchronizationPlugin, AutoMembershipPlugin, MemberOfPlugin, MemberOfSharedConfig, AutoMembershipDefinitions, MEPTemplates, MEPConfigs, ManagedEntriesPlugin, MEPTemplate
from lib389._constants import *
@@ -735,3 +735,177 @@ def test_sync_repl_invalid_cookie(topology, request):
pass
request.addfinalizer(fin)
+
+
+def test_sync_repl_non_root_persist(topology, request):
+ """Test sync_repl persistent search as a non-root user triggers
+ proper ACL evaluation without causing OPERATIONS_ERROR.
+
+ :id: 97f4be3d-f8c3-45cf-8295-ed58ee7bd371
+ :setup: Standalone Instance
+ :steps:
+ 1. Enable RetroChangelog and ContentSync plugins
+ 2. Create a test user and set its password
+ 3. Add an ACI granting the test user read access
+ 4. Run a syncrepl persistent search bound as the test user
+ 5. Add entries while the persistent search is active
+ 6. Stop the server to end the sync thread
+ 7. Check that the sync client received results without error
+ 8. Check the error log for 'Missing aclpb' messages
+ :expectedresults:
+ 1. Success
+ 2. Success
+ 3. Success
+ 4. Success - no OPERATIONS_ERROR
+ 5. Success
+ 6. Success
+ 7. The sync client should have received cookies
+ 8. No 'Missing aclpb' errors in the log
+ """
+ inst = topology.standalone
+ inst.restart()
+ inst.enable_tls()
+
+ # Enable RetroChangelog
+ rcl = RetroChangelogPlugin(inst)
+ rcl.disable()
+ rcl.enable()
+ rcl.set('nsslapd-attribute', 'nsuniqueid:targetUniqueId')
+
+ # Enable Content Sync plugin
+ csp = ContentSyncPlugin(inst)
+ csp.enable()
+
+ inst.restart()
+
+ # Create the syncrepl user
+ users = UserAccounts(inst, DEFAULT_SUFFIX)
+ sync_user = users.create_test_user(uid=29999)
+ sync_user.set('userPassword', 'sync_password')
+ SYNC_USER_DN = sync_user.dn
+
+ # Add ACI granting the sync user read access to the suffix
+ ACI = (
+ '(targetattr="*")(version 3.0; acl "Sync user read access";'
+ ' allow (read, search, compare)'
+ ' userdn = "ldap:///%s";)' % SYNC_USER_DN
+ )
+ inst.modify_s(DEFAULT_SUFFIX,
+ [(ldap.MOD_ADD, 'aci', ensure_bytes(ACI))])
+
+ # Start a syncrepl persistent search as the non-root user
+ # Using the Sync_persist-like pattern but with non-root bind
+ sync_error = [None] # mutable container to capture errors from thread
+ sync_cookies = []
+
+ class NonRootSyncer(ReconnectLDAPObject, SyncreplConsumer):
+ def __init__(self, *args, **kwargs):
+ self.cookie = None
+ self.cookies = []
+ ldap.ldapobject.ReconnectLDAPObject.__init__(self, *args, **kwargs)
+
+ def syncrepl_set_cookie(self, cookie):
+ self.cookie = cookie
+ self.cookies.append(cookie.split('#')[2])
+
+ def syncrepl_get_cookie(self):
+ return self.cookie
+
+ def syncrepl_present(self, uuids, refreshDeletes=False):
+ pass
+
+ def syncrepl_delete(self, uuids):
+ pass
+
+ def syncrepl_entry(self, dn, attrs, uuid):
+ log.info("NonRootSyncer received entry: %s" % dn)
+
+ def syncrepl_refreshdone(self):
+ log.info("NonRootSyncer refresh done")
+
+ def get_cookies(self):
+ return self.cookies
+
+ def sync_thread_func():
+ ldap_conn = None
+ try:
+ ldap_conn = NonRootSyncer(inst.toLDAPURL())
+ ldap_conn.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_DEMAND)
+ ldap_conn.set_option(ldap.OPT_X_TLS_CACERTFILE,
+ os.path.join(inst.get_config_dir(), "ca.crt"))
+ if is_fips():
+ ldap_conn.set_option(ldap.OPT_X_TLS_PROTOCOL_MIN,
+ ldap.OPT_X_TLS_PROTOCOL_TLS1_2)
+ ldap_conn.set_option(ldap.OPT_X_TLS_NEWCTX, 0)
+
+ # Bind as the non-root user
+ ldap_conn.simple_bind_s(SYNC_USER_DN, 'sync_password')
+
+ ldap_search = ldap_conn.syncrepl_search(
+ DEFAULT_SUFFIX,
+ ldap.SCOPE_SUBTREE,
+ mode='refreshAndPersist',
+ attrlist=['objectclass', 'cn', 'uid', 'sn'],
+ filterstr='(objectClass=person)',
+ cookie=None
+ )
+
+ while ldap_conn.syncrepl_poll(all=1, msgid=ldap_search):
+ pass
+ except ldap.OPERATIONS_ERROR as e:
+ log.error("SyncRepl got OPERATIONS_ERROR: %s" % e)
+ sync_error[0] = e
+ except (ldap.SERVER_DOWN, ldap.CONNECT_ERROR):
+ pass
+ finally:
+ if ldap_conn:
+ sync_cookies.extend(ldap_conn.get_cookies())
+
+ t = threading.Thread(target=sync_thread_func)
+ t.daemon = True
+ t.start()
+ time.sleep(5)
+
+ # Add entries while persistent search is active (as directory manager)
+ test_users = []
+ for i in range(20001, 20004):
+ test_users.append(users.create_test_user(uid=i))
+ time.sleep(5)
+
+ # Stop the server to end the sync thread cleanly
+ inst.stop()
+ t.join(timeout=30)
+
+ # Verify no OPERATIONS_ERROR was raised
+ assert sync_error[0] is None, \
+ "SyncRepl persistent search as non-root user got OPERATIONS_ERROR: %s" % sync_error[0]
+
+ # Verify the sync client received some cookies (entries were synced)
+ assert len(sync_cookies) > 0, \
+ "SyncRepl persistent search as non-root user received no cookies"
+
+ # Restart and check error log for the specific aclpb error
+ inst.start()
+ assert not inst.searchErrorsLog('Missing aclpb'), \
+ "Found 'Missing aclpb' errors in the error log"
+
+ log.info('test_sync_repl_non_root_persist: PASS')
+
+ def fin():
+ inst.restart()
+ try:
+ inst.modify_s(DEFAULT_SUFFIX,
+ [(ldap.MOD_DELETE, 'aci', ensure_bytes(ACI))])
+ except:
+ pass
+ for user in test_users:
+ try:
+ user.delete()
+ except:
+ pass
+ try:
+ sync_user.delete()
+ except:
+ pass
+
+ request.addfinalizer(fin)
diff --git a/ldap/servers/plugins/sync/sync_init.c b/ldap/servers/plugins/sync/sync_init.c
index 49c7387bf..28c0edf06 100644
--- a/ldap/servers/plugins/sync/sync_init.c
+++ b/ldap/servers/plugins/sync/sync_init.c
@@ -20,6 +20,28 @@ static int sync_persist_register_operation_extension(void);
static PRUintn thread_primary_op;
+/*
+ * Destructor for the per-thread primary operation list HEAD node.
+ * Called by NSPR when a thread exits, to free the HEAD node
+ * and any remaining linked list entries.
+ */
+static void
+sync_thread_primary_op_destructor(void *priv)
+{
+ OPERATION_PL_CTX_T *head = (OPERATION_PL_CTX_T *)priv;
+ if (head) {
+ OPERATION_PL_CTX_T *curr_op = head->next;
+ while (curr_op) {
+ OPERATION_PL_CTX_T *next = curr_op->next;
+ slapi_entry_free(curr_op->entry);
+ slapi_entry_free(curr_op->eprev);
+ slapi_ch_free((void **)&curr_op);
+ curr_op = next;
+ }
+ slapi_ch_free((void **)&head);
+ }
+}
+
int
sync_init(Slapi_PBlock *pb)
{
@@ -187,7 +209,7 @@ sync_start(Slapi_PBlock *pb)
* only contains one operation. For nested, the list contains the operations
* in the order that they were applied
*/
- PR_NewThreadPrivateIndex(&thread_primary_op, NULL);
+ PR_NewThreadPrivateIndex(&thread_primary_op, sync_thread_primary_op_destructor);
sync_persist_initialize(argc, argv);
return (0);
diff --git a/ldap/servers/plugins/sync/sync_persist.c b/ldap/servers/plugins/sync/sync_persist.c
index 283607361..a78c273cd 100644
--- a/ldap/servers/plugins/sync/sync_persist.c
+++ b/ldap/servers/plugins/sync/sync_persist.c
@@ -43,6 +43,69 @@ static void sync_node_free(SyncQueueNode **node);
static int sync_acquire_connection(Slapi_Connection *conn);
static int sync_release_connection(Slapi_PBlock *pb, Slapi_Connection *conn, Slapi_Operation *op, int release);
+/*
+ * Free all resources owned by a SyncRequest and the request itself.
+ * Caller must remove the request from the list (sync_remove_request)
+ * before calling this, if it was added.
+ */
+static void
+sync_request_free(SyncRequest **reqp)
+{
+ SyncRequest *req;
+ SyncQueueNode *qnode, *qnodenext;
+
+ if (reqp == NULL || *reqp == NULL) {
+ return;
+ }
+ req = *reqp;
+
+ if (req->req_pblock) {
+ char **attrs_dup = NULL;
+ char *strFilter = NULL;
+ LDAPControl **ctrls = NULL;
+ Slapi_DN *sdn = NULL;
+
+ slapi_pblock_get(req->req_pblock, SLAPI_SEARCH_TARGET_SDN, &sdn);
+ slapi_sdn_free(&sdn);
+ slapi_pblock_set(req->req_pblock, SLAPI_SEARCH_TARGET_SDN, NULL);
+
+ slapi_pblock_get(req->req_pblock, SLAPI_SEARCH_ATTRS, &attrs_dup);
+ slapi_ch_array_free(attrs_dup);
+ slapi_pblock_set(req->req_pblock, SLAPI_SEARCH_ATTRS, NULL);
+
+ slapi_pblock_get(req->req_pblock, SLAPI_SEARCH_STRFILTER, &strFilter);
+ slapi_ch_free((void **)&strFilter);
+ slapi_pblock_set(req->req_pblock, SLAPI_SEARCH_STRFILTER, NULL);
+
+ slapi_pblock_get(req->req_pblock, SLAPI_REQCONTROLS, &ctrls);
+ if (ctrls) {
+ ldap_controls_free(ctrls);
+ slapi_pblock_set(req->req_pblock, SLAPI_REQCONTROLS, NULL);
+ }
+
+ slapi_pblock_destroy(req->req_pblock);
+ req->req_pblock = NULL;
+ }
+
+ slapi_ch_free((void **)&req->req_orig_base);
+ slapi_filter_free(req->req_filter, 1);
+ req->req_filter = NULL;
+
+ for (qnode = req->ps_eq_head; qnode; qnode = qnodenext) {
+ qnodenext = qnode->sync_next;
+ sync_node_free(&qnode);
+ }
+ req->ps_eq_head = NULL;
+ req->ps_eq_tail = NULL;
+
+ if (req->req_lock) {
+ PR_DestroyLock(req->req_lock);
+ req->req_lock = NULL;
+ }
+
+ slapi_ch_free((void **)reqp);
+}
+
/* This routine appends the operation at the end of the
* per thread pending list of nested operation..
* being a betxn_preop the pending list has the same order
@@ -664,10 +727,7 @@ sync_persist_add(Slapi_PBlock *pb)
prerr, slapi_pr_strerror(prerr));
/* Now remove the ps from the list so call the function ps_remove */
sync_remove_request(req);
- PR_DestroyLock(req->req_lock);
- req->req_lock = NULL;
- slapi_ch_free((void **)&req->req_pblock);
- slapi_ch_free((void **)&req);
+ sync_request_free(&req);
} else {
thread_count++;
return (req->req_tid);
@@ -753,11 +813,7 @@ sync_persist_terminate_all()
/* it frees the structures, just in case it remained connected sync_repl client */
for (req = sync_request_list->sync_req_head; NULL != req; req = next) {
next = req->req_next;
- slapi_pblock_destroy(req->req_pblock);
- req->req_pblock = NULL;
- PR_DestroyLock(req->req_lock);
- req->req_lock = NULL;
- slapi_ch_free((void **)&req);
+ sync_request_free(&req);
}
slapi_ch_free((void **)&sync_request_list);
}
@@ -1060,34 +1116,7 @@ sync_send_results(void *arg)
done:
/* This client closed the connection or shutdown, free the req */
sync_remove_request(req);
- PR_DestroyLock(req->req_lock);
- req->req_lock = NULL;
-
- slapi_pblock_get(req->req_pblock, SLAPI_SEARCH_ATTRS, &attrs_dup);
- slapi_ch_array_free(attrs_dup);
- slapi_pblock_set(req->req_pblock, SLAPI_SEARCH_ATTRS, NULL);
-
- slapi_pblock_get(req->req_pblock, SLAPI_SEARCH_STRFILTER, &strFilter);
- slapi_ch_free((void **)&strFilter);
- slapi_pblock_set(req->req_pblock, SLAPI_SEARCH_STRFILTER, NULL);
-
- slapi_pblock_get(req->req_pblock, SLAPI_REQCONTROLS, &ctrls);
- if (ctrls) {
- ldap_controls_free(ctrls);
- slapi_pblock_set(req->req_pblock, SLAPI_REQCONTROLS, NULL);
- }
-
- slapi_pblock_destroy(req->req_pblock);
- req->req_pblock = NULL;
-
- slapi_ch_free((void **)&req->req_orig_base);
- slapi_filter_free(req->req_filter, 1);
- sync_cookie_free(&req->req_cookie);
- for (qnode = req->ps_eq_head; qnode; qnode = qnodenext) {
- qnodenext = qnode->sync_next;
- sync_node_free(&qnode);
- }
- slapi_ch_free((void **)&req);
+ sync_request_free(&req);
thread_count--;
}
--
2.52.0

View File

@ -0,0 +1,197 @@
From f02302eb0967647fc0bb08b9de9900f6c9b47396 Mon Sep 17 00:00:00 2001
From: Viktor Ashirov <vashirov@redhat.com>
Date: Thu, 2 Apr 2026 11:44:19 +0200
Subject: [PATCH 02/13] Issue 7126 - WARN - keys2idl - received NULL idl from
index_read_ext_allids (#7127)
Bug Description:
Under high concurrency, occasional NULL IDL warnings were logged:
[29/Nov/2025:22:59:13.930441639 +0000] - WARN - keys2idl - received NULL idl from index_read_ext_allids, treating as empty set
[29/Nov/2025:22:59:13.936543122 +0000] - WARN - keys2idl - this is probably a bug that should be reported
[29/Nov/2025:22:59:13.947131944 +0000] - ERR - build_candidate_list - Database error -12795
Since #7124 all cursor operations use transaction isolation, which
increases read-write lock contention under high concurrency. Deadlocked
read transactions returned DBI_RC_RETRY, and after retry exhaustion
`index_read_ext_allids()` returned NULL to callers that did not expect it.
Fix Description:
* Ensure `index_read_ext_allids()` never returns NULL. All early exits
and the retry-exhaustion path now return `idl_alloc(0)` (empty IDL).
* Increase IDL_FETCH_RETRY_COUNT from 5 to 10 and replace the uniform
random sleep with exponential backoff.
Fixes: https://github.com/389ds/389-ds-base/issues/7126
Reviewed by: @tbordaz, @droideck, @progier389 (Thanks!)
---
ldap/servers/slapd/back-ldbm/back-ldbm.h | 2 +-
.../slapd/back-ldbm/db-bdb/bdb_ldif2db.c | 5 +-
ldap/servers/slapd/back-ldbm/index.c | 51 +++++++++++++++----
3 files changed, 46 insertions(+), 12 deletions(-)
diff --git a/ldap/servers/slapd/back-ldbm/back-ldbm.h b/ldap/servers/slapd/back-ldbm/back-ldbm.h
index 462d1763c..25cbfb59b 100644
--- a/ldap/servers/slapd/back-ldbm/back-ldbm.h
+++ b/ldap/servers/slapd/back-ldbm/back-ldbm.h
@@ -165,7 +165,7 @@ typedef unsigned short u_int16_t;
#define SUBLEN 3
#define LDBM_CACHE_RETRY_COUNT 1000 /* Number of times we re-try a cache operation */
#define RETRY_CACHE_LOCK 2 /* error code to signal a retry of the cache lock */
-#define IDL_FETCH_RETRY_COUNT 5 /* Number of times we re-try idl_fetch if it returns deadlock */
+#define IDL_FETCH_RETRY_COUNT 10 /* Number of times we re-try idl_fetch if it returns deadlock */
#define IMPORT_SUBCOUNT_HASHTABLE_SIZE 500 /* Number of buckets in hash used to accumulate subcount for broody parents */
/* minimum max ids that a single index entry can map to in ldbm */
diff --git a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_ldif2db.c b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_ldif2db.c
index 7972850e9..7d904df91 100644
--- a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_ldif2db.c
+++ b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_ldif2db.c
@@ -531,8 +531,8 @@ bdb_fetch_subtrees(backend *be, char **include, int *err)
*err = ldbm_ancestorid_read(be, txn, id, &idl);
}
slapi_sdn_done(&sdn);
- if (idl == NULL) {
- if (DB_NOTFOUND == *err) {
+ if (idl == NULL || IDL_NIDS(idl) == 0) {
+ if (*err == 0 || DB_NOTFOUND == *err) {
slapi_log_err(SLAPI_LOG_BACKLDBM,
"bdb_fetch_subtrees", "Entry id %u has no descendants according to %s. "
"Index file created by this reindex will be empty.\n",
@@ -543,6 +543,7 @@ bdb_fetch_subtrees(backend *be, char **include, int *err)
"bdb_fetch_subtrees", "%s not indexed on %u\n",
entryrdn_get_noancestorid() ? "entryrdn" : "ancestorid", id);
}
+ idl_free(&idl);
continue;
}
diff --git a/ldap/servers/slapd/back-ldbm/index.c b/ldap/servers/slapd/back-ldbm/index.c
index 30fa09ebb..b22dda3c3 100644
--- a/ldap/servers/slapd/back-ldbm/index.c
+++ b/ldap/servers/slapd/back-ldbm/index.c
@@ -932,7 +932,7 @@ index_read_ext_allids(
prefix = index_index2prefix(indextype);
if (prefix == NULL) {
slapi_log_err(SLAPI_LOG_ERR, "index_read_ext_allids", "NULL prefix\n");
- return NULL;
+ return idl_alloc(0);
}
if (slapi_is_loglevel_set(LDAP_DEBUG_TRACE)) {
slapi_log_err(SLAPI_LOG_TRACE, "index_read_ext_allids", "=> ( \"%s\" %s \"%s\" )\n",
@@ -948,7 +948,7 @@ index_read_ext_allids(
if (ai == NULL) {
index_free_prefix(prefix);
slapi_ch_free_string(&basetmp);
- return NULL;
+ return idl_alloc(0);
}
slapi_log_err(SLAPI_LOG_ARGS, "index_read_ext_allids", "indextype: \"%s\" indexmask: 0x%x\n",
@@ -967,7 +967,7 @@ index_read_ext_allids(
slapi_ch_free_string(&basetmp);
if (NULL == val || NULL == val->bv_val) {
/* entrydn value was not given */
- return NULL;
+ return idl_alloc(0);
}
slapi_sdn_init_dn_byval(&sdn, val->bv_val);
rc = entryrdn_index_read(be, &sdn, &id, txn);
@@ -976,11 +976,11 @@ index_read_ext_allids(
/* return an empty list */
return idl_alloc(0);
} else if (rc) { /* failure */
- return NULL;
+ return idl_alloc(0);
} else { /* success */
rc = idl_append_extend(&idl, id);
if (rc) { /* failure */
- return NULL;
+ return idl_alloc(0);
}
return idl;
}
@@ -1022,11 +1022,11 @@ index_read_ext_allids(
}
if ((*err = dblayer_get_index_file(be, ai, &db, DBOPEN_CREATE)) != 0) {
slapi_log_err(SLAPI_LOG_TRACE, "index_read_ext_allids",
- "<= NULL (index file open for attr %s)\n",
+ "<= empty IDL (index file open for attr %s)\n",
basetype);
index_free_prefix(prefix);
slapi_ch_free_string(&basetmp);
- return (NULL);
+ return idl_alloc(0);
}
if (val != NULL) {
@@ -1067,12 +1067,32 @@ index_read_ext_allids(
idl = idl_fetch_ext(be, db, &key, db_txn, ai, err, allidslimit);
if (*err == DB_LOCK_DEADLOCK) {
ldbm_nasty("index_read_ext_allids", "index read retrying transaction", 1045, *err);
+ slapi_log_err(SLAPI_LOG_BACKLDBM, "index_read_ext_allids",
+ "DBI_RC_RETRY on retry %d/%d for %s\n",
+ retry_count + 1, IDL_FETCH_RETRY_COUNT, basetype);
#ifdef FIX_TXN_DEADLOCKS
#error can only retry here if txn == NULL - otherwise, have to abort and retry txn
#endif
- interval = PR_MillisecondsToInterval(slapi_rand() % 100);
+ /* Exponential backoff with jitter, capped at 200ms */
+ {
+ PRUint32 backoff_ms = 10;
+ int i;
+ for (i = 0; i < retry_count && backoff_ms < 200; i++) {
+ backoff_ms *= 2;
+ }
+ if (backoff_ms > 200) {
+ backoff_ms = 200;
+ }
+ backoff_ms += slapi_rand() % (backoff_ms + 1);
+ interval = PR_MillisecondsToInterval(backoff_ms);
+ }
DS_Sleep(interval);
continue;
+ } else if (*err == DB_NOTFOUND) {
+ /* Key not found in index - this is normal, not an error */
+ idl_free(&idl);
+ idl = idl_alloc(0);
+ break;
} else if (*err != 0 || idl == NULL) {
/* The database might not exist. We have to assume it means empty set */
slapi_log_err(SLAPI_LOG_TRACE, "index_read_ext_allids", "Failed to access idl index for %s\n", basetype);
@@ -1086,10 +1106,13 @@ index_read_ext_allids(
}
if (retry_count == IDL_FETCH_RETRY_COUNT) {
ldbm_nasty("index_read_ext_allids", "index_read retry count exceeded", 1046, *err);
+ /* Ensure we don't return NULL after exhausting retries */
+ if (idl == NULL) {
+ idl = idl_alloc(0);
+ }
} else if (*err != 0 && *err != DB_NOTFOUND) {
ldbm_nasty("index_read_ext_allids", errmsg, 1050, *err);
}
- slapi_ch_free_string(&basetmp);
slapi_ch_free_string(&tmpbuf);
dblayer_release_index_file(be, ai, db);
@@ -1100,6 +1123,16 @@ index_read_ext_allids(
ber_bvfree(encrypted_val);
}
+ /* Ensure we never return NULL - always return valid IDL */
+ if (idl == NULL) {
+ slapi_log_err(SLAPI_LOG_WARNING, "index_read_ext_allids",
+ "Returning empty IDL for %s after error %d\n",
+ basetype, *err);
+ idl = idl_alloc(0);
+ }
+
+ slapi_ch_free_string(&basetmp);
+
slapi_log_err(SLAPI_LOG_TRACE, "index_read_ext_allids", "<= %lu candidates\n",
(u_long)IDL_NIDS(idl));
return (idl);
--
2.52.0

View File

@ -0,0 +1,65 @@
From 92e89927c9ed213d24c79f3cc62dc65027e09b99 Mon Sep 17 00:00:00 2001
From: Viktor Ashirov <vashirov@redhat.com>
Date: Tue, 9 Dec 2025 11:46:11 +0100
Subject: [PATCH 03/13] Issue 7124 - (2nd) BDB cursor race condition with
transaction isolation
Description:
1.4.x. only.
Add checks to idl_new_fetch() and idl_new_range_fetch() if the
database's environment actually supports transactions.
Fixes: https://github.com/389ds/389-ds-base/issues/7124
---
ldap/servers/slapd/back-ldbm/idl_new.c | 28 ++++++++++++++++++++++++++
1 file changed, 28 insertions(+)
diff --git a/ldap/servers/slapd/back-ldbm/idl_new.c b/ldap/servers/slapd/back-ldbm/idl_new.c
index 212f31b7b..205d0007e 100644
--- a/ldap/servers/slapd/back-ldbm/idl_new.c
+++ b/ldap/servers/slapd/back-ldbm/idl_new.c
@@ -163,6 +163,20 @@ idl_new_fetch(
dblayer_read_txn_begin(be, txn, &s_txn);
}
+ /*
+ * Verify the database's environment actually supports transactions.
+ * During import, databases are opened with a private environment that
+ * lacks DB_INIT_TXN. If we try to use a transaction from the main
+ * environment with a database from the import environment, BDB will fail.
+ * Clear the txn if the database's environment is NULL or doesn't support
+ * transactions.
+ */
+ if (s_txn.back_txn_txn != NULL) {
+ if (db->dbenv == NULL || !dblayer_db_uses_transactions(db->dbenv)) {
+ s_txn.back_txn_txn = NULL;
+ }
+ }
+
/* Make a cursor */
ret = db->cursor(db, txn, &cursor, 0);
if (0 != ret) {
@@ -410,6 +424,20 @@ idl_new_range_fetch(
dblayer_read_txn_begin(be, txn, &s_txn);
}
+ /*
+ * Verify the database's environment actually supports transactions.
+ * During import, databases are opened with a private environment that
+ * lacks DB_INIT_TXN. If we try to use a transaction from the main
+ * environment with a database from the import environment, BDB will fail.
+ * Clear the txn if the database's environment is NULL or doesn't support
+ * transactions.
+ */
+ if (s_txn.back_txn_txn != NULL) {
+ if (db->dbenv == NULL || !dblayer_db_uses_transactions(db->dbenv)) {
+ s_txn.back_txn_txn = NULL;
+ }
+ }
+
/* Make a cursor */
ret = db->cursor(db, txn, &cursor, 0);
if (0 != ret) {
--
2.52.0

View File

@ -0,0 +1,682 @@
From d53726c4c04e8d4844ffe0a78b9713e938e1a348 Mon Sep 17 00:00:00 2001
From: Viktor Ashirov <vashirov@redhat.com>
Date: Sat, 29 Nov 2025 22:45:21 +0100
Subject: [PATCH 04/13] Issue 7124 - BDB cursor race condition with transaction
isolation (#7125)
Bug Description:
ASAN reported crashes in `__db_ditem_nolog()` with negative-size-param
errors. Cursor operations without transaction isolation allowed
concurrent page modifications to corrupt cursor state, leading to
invalid memory access.
The race condition occurs when:
1. T1 opens a cursor without transaction protection
2. T2 modifies the same index page
3. T1 cursor operates on stale page metadata
4. `__db_ditem_nolog()` calculates negative size for `memmove()`
5. Crash: `AddressSanitizer: negative-size-param: (size=-8)`
Reproducer: dirsrvtests/tests/stress/backend/bdb_cursor_race_test.py
Crash under ASAN usually happens within 10-30 minutes, but sometimes it
can run for hours without any crash.
Fix Description:
Implement transaction isolation for cursors in `idl_new_fetch()` and
`idl_new_range_fetch()` by always calling `dblayer_read_txn_begin()`.
In `bdb_txn_begin()` verify if the environment supports transactions
(has DB_INIT_TXN flag) before attempting to begin a transaction.
This prevents errors during offline import which uses a private
environment without transaction support.
In `bdb_public_new_cursor()` skip transaction usage when the database's
environment doesn't support transactions.
Fixes: https://github.com/389ds/389-ds-base/issues/7124
Reviewed by: @progier389, @tbordaz (Thanks!)
---
.../stress/backend/bdb_cursor_race_test.py | 544 ++++++++++++++++++
.../slapd/back-ldbm/db-bdb/bdb_layer.c | 13 +-
ldap/servers/slapd/back-ldbm/idl_new.c | 24 +-
3 files changed, 571 insertions(+), 10 deletions(-)
create mode 100644 dirsrvtests/tests/stress/backend/bdb_cursor_race_test.py
diff --git a/dirsrvtests/tests/stress/backend/bdb_cursor_race_test.py b/dirsrvtests/tests/stress/backend/bdb_cursor_race_test.py
new file mode 100644
index 000000000..54a20e000
--- /dev/null
+++ b/dirsrvtests/tests/stress/backend/bdb_cursor_race_test.py
@@ -0,0 +1,544 @@
+# --- BEGIN COPYRIGHT BLOCK ---
+# Copyright (C) 2025 Red Hat, Inc.
+# All rights reserved.
+#
+# License: GPL (version 3 or any later version).
+# See LICENSE for details.
+# --- END COPYRIGHT BLOCK ---
+#
+
+import pytest
+import logging
+import ldap
+import ldap.modlist as modlist
+from threading import Thread, Event, Lock
+import random
+import time
+from lib389.topologies import topology_st
+from lib389._constants import DEFAULT_SUFFIX, DN_DM, PW_DM
+from lib389.idm.user import UserAccounts
+from lib389.idm.organizationalunit import OrganizationalUnits
+
+pytestmark = pytest.mark.tier2
+
+log = logging.getLogger(__name__)
+
+# Test configuration
+SEARCH_THREADS = 30
+MODIFY_THREADS = 15
+DELETE_THREADS = 8
+CHURN_THREADS = 5
+DEFAULT_TIMEOUT = 60 * 60 # 60 minutes in seconds
+
+# Statistics and crash detection
+stats_lock = Lock()
+stats = {
+ 'searches': 0,
+ 'modifies': 0,
+ 'deletes': 0,
+ 'adds': 0,
+ 'errors': 0,
+ 'start_time': None
+}
+
+# Global flag to track if server crashed
+server_crashed = False
+crash_lock = Lock()
+
+HIGH_CONTENTION_FILTERS = [
+ "(objectClass=inetOrgPerson)",
+ "(objectClass=person)",
+ "(objectClass=*)",
+ "(uid=*)",
+ "(cn=*)",
+ "(mail=*)",
+ "(description=*)",
+ "(telephoneNumber=*)",
+]
+
+
+def create_raw_connection(host, port):
+ """Create a raw LDAP connection independent of the instance connection pool
+
+ :param host: LDAP server hostname
+ :param port: LDAP server port
+ :returns: Raw LDAP connection
+ """
+ # Use ldap.initialize to create a completely independent connection
+ uri = f"ldap://{host}:{port}"
+ conn = ldap.initialize(uri)
+ conn.protocol_version = ldap.VERSION3
+
+ # Short timeout to fail fast
+ conn.set_option(ldap.OPT_NETWORK_TIMEOUT, 5.0)
+ conn.set_option(ldap.OPT_TIMEOUT, 5.0)
+
+ # Bind as Directory Manager
+ conn.simple_bind_s(DN_DM, PW_DM)
+ return conn
+
+
+def update_stats(operation, count=1):
+ """Update operation statistics in a thread-safe manner"""
+ with stats_lock:
+ stats[operation] = stats.get(operation, 0) + count
+
+
+def mark_server_crashed():
+ """Mark that the server has crashed"""
+ global server_crashed
+ with crash_lock:
+ server_crashed = True
+
+
+def print_stats():
+ """Print current statistics"""
+ with stats_lock:
+ if stats['start_time']:
+ elapsed = time.time() - stats['start_time']
+ log.info(f"Statistics after {elapsed:.1f}s: "
+ f"searches={stats['searches']}, "
+ f"modifies={stats['modifies']}, "
+ f"adds={stats['adds']}, "
+ f"deletes={stats['deletes']}, "
+ f"errors={stats['errors']}")
+
+
+def setup_test_data(inst, num_entries=1000):
+ """Setup test data with multi-valued attributes
+
+ :param inst: DirSrv instance
+ :param num_entries: Number of test entries to create
+ """
+ log.info(f"Setting up {num_entries} test entries with multi-valued attributes...")
+
+ # Create entries with many multi-valued attributes using raw LDAP
+ created = 0
+ for i in range(num_entries):
+ try:
+ dn = f"uid=test{i},ou=people,{DEFAULT_SUFFIX}"
+
+ descriptions = [f"description_{i}_{j}_{'x'*50}".encode() for j in range(20)]
+ phones = [f"+1-555-{i:04d}-{j:04d}".encode() for j in range(10)]
+ mails = [f"test{i}.{j}@example.com".encode() for j in range(5)]
+
+ attrs = {
+ 'objectClass': [b'top', b'person', b'organizationalPerson', b'inetOrgPerson'],
+ 'uid': [f'test{i}'.encode()],
+ 'cn': [f'Test User {i}'.encode()],
+ 'sn': [f'User{i}'.encode()],
+ 'description': descriptions,
+ 'telephoneNumber': phones,
+ 'mail': mails,
+ 'userPassword': [b'password'],
+ }
+
+ inst.add_s(dn, modlist.addModlist(attrs))
+ created += 1
+
+ if (i + 1) % 100 == 0:
+ log.info(f"Created {i + 1}/{num_entries} entries...")
+
+ except ldap.ALREADY_EXISTS:
+ pass
+ except Exception as e:
+ log.warning(f"Error creating entry {i}: {e}")
+
+ log.info(f"Test data setup complete. Created {created} new entries.")
+
+
+def search_worker(stop_event, host, port, thread_id):
+ """Worker thread for high-contention searches
+
+ :param stop_event: Event to signal thread termination
+ :param host: LDAP server hostname
+ :param port: LDAP server port
+ :param thread_id: Thread identifier
+ """
+ # Create independent raw LDAP connection for this thread
+ conn = create_raw_connection(host, port)
+ log.info(f"Search worker {thread_id} started")
+ search_count = 0
+
+ try:
+ while not stop_event.is_set():
+ try:
+ filter_str = random.choice(HIGH_CONTENTION_FILTERS)
+ scope = random.choice([ldap.SCOPE_SUBTREE, ldap.SCOPE_ONELEVEL])
+
+ conn.search_s(
+ DEFAULT_SUFFIX,
+ scope,
+ filter_str,
+ attrlist=['dn']
+ )
+
+ search_count += 1
+
+ if search_count % 100 == 0:
+ update_stats('searches', 100)
+
+ except ldap.SERVER_DOWN:
+ log.error(f"Search worker {thread_id}: SERVER DOWN - ns-slapd crashed!")
+ mark_server_crashed()
+ stop_event.set()
+ break
+ except Exception as e:
+ update_stats('errors')
+ if search_count < 10:
+ log.debug(f"Search worker {thread_id} error: {e}")
+ finally:
+ try:
+ conn.unbind_s()
+ except:
+ pass
+ log.info(f"Search worker {thread_id} stopped (total searches: {search_count})")
+
+
+def modify_worker(stop_event, host, port, thread_id):
+ """Worker thread for modifying multi-valued attributes
+
+ :param stop_event: Event to signal thread termination
+ :param host: LDAP server hostname
+ :param port: LDAP server port
+ :param thread_id: Thread identifier
+ """
+ # Create independent raw LDAP connection for this thread
+ conn = create_raw_connection(host, port)
+ log.info(f"Modify worker {thread_id} started")
+ modify_count = 0
+
+ try:
+ while not stop_event.is_set():
+ try:
+ entry_num = random.randint(0, 999)
+ dn = f"uid=test{entry_num},ou=people,{DEFAULT_SUFFIX}"
+ attr = 'description'
+ value = f"{attr}_{int(time.time() * 1000000)}_{'x'*100}"
+
+ # Strategy 1: Add a new value (increases index size)
+ if random.random() < 0.3:
+ mod_attrs = [(ldap.MOD_ADD, attr, [value.encode()])]
+
+ # Strategy 2: Replace all values (causes delete + add)
+ # This should trigger __db_ditem_nolog() in the delete phase
+ elif random.random() < 0.7:
+ new_values = [f"{attr}_new_{i}_{'y'*100}".encode() for i in range(20)]
+ mod_attrs = [(ldap.MOD_REPLACE, attr, new_values)]
+
+ # Strategy 3: Delete all values, then add multiple new ones
+ else:
+ new_values = [f"{attr}_churn_{i}_{'z'*100}".encode() for i in range(15)]
+ mod_attrs = [
+ (ldap.MOD_DELETE, attr, []),
+ (ldap.MOD_ADD, attr, new_values)
+ ]
+
+ conn.modify_s(dn, mod_attrs)
+ modify_count += 1
+
+ if modify_count % 50 == 0:
+ update_stats('modifies', 50)
+
+ except ldap.SERVER_DOWN:
+ log.error(f"Modify worker {thread_id}: SERVER DOWN - ns-slapd crashed!")
+ mark_server_crashed()
+ stop_event.set()
+ break
+ except ldap.NO_SUCH_OBJECT:
+ pass
+ except Exception as e:
+ update_stats('errors')
+ if modify_count < 10:
+ log.debug(f"Modify worker {thread_id} error: {e}")
+ finally:
+ try:
+ conn.unbind_s()
+ except:
+ pass
+ log.info(f"Modify worker {thread_id} stopped (total modifies: {modify_count})")
+
+
+def delete_worker(stop_event, host, port, thread_id):
+ """Worker thread for deleting entries
+
+ :param stop_event: Event to signal thread termination
+ :param host: LDAP server hostname
+ :param port: LDAP server port
+ :param thread_id: Thread identifier
+ """
+ # Create independent raw LDAP connection for this thread
+ conn = create_raw_connection(host, port)
+ log.info(f"Delete worker {thread_id} started")
+ delete_count = 0
+ delete_range_start = 5000 + (thread_id * 1000)
+
+ try:
+ while not stop_event.is_set():
+ try:
+ entry_num = delete_range_start + (delete_count % 1000)
+ dn = f"uid=delete{entry_num},ou=people,{DEFAULT_SUFFIX}"
+
+ conn.delete_s(dn)
+ delete_count += 1
+
+ if delete_count % 20 == 0:
+ update_stats('deletes', 20)
+
+ time.sleep(0.01)
+
+ except ldap.SERVER_DOWN:
+ log.error(f"Delete worker {thread_id}: SERVER DOWN - ns-slapd crashed!")
+ mark_server_crashed()
+ stop_event.set()
+ break
+ except ldap.NO_SUCH_OBJECT:
+ pass
+ except Exception as e:
+ update_stats('errors')
+ if delete_count < 10:
+ log.debug(f"Delete worker {thread_id} error: {e}")
+ finally:
+ try:
+ conn.unbind_s()
+ except:
+ pass
+ log.info(f"Delete worker {thread_id} stopped (total deletes: {delete_count})")
+
+
+def churn_worker(stop_event, host, port, thread_id):
+ """Worker thread for add/delete churn
+
+ :param stop_event: Event to signal thread termination
+ :param host: LDAP server hostname
+ :param port: LDAP server port
+ :param thread_id: Thread identifier
+ """
+ # Create independent raw LDAP connection for this thread
+ conn = create_raw_connection(host, port)
+ log.info(f"Churn worker {thread_id} started")
+ counter = 10000 + (thread_id * 10000)
+ add_count = 0
+
+ try:
+ while not stop_event.is_set():
+ try:
+ dn = f"uid=churn{counter},ou=people,{DEFAULT_SUFFIX}"
+
+ attrs = {
+ 'objectClass': [b'top', b'person', b'inetOrgPerson'],
+ 'uid': [f'churn{counter}'.encode()],
+ 'cn': [f'Churn {counter}'.encode()],
+ 'sn': [f'User{counter}'.encode()],
+ 'description': [f'desc_{i}'.encode() for i in range(15)],
+ 'telephoneNumber': [f'+1-555-churn-{i:04d}'.encode() for i in range(8)],
+ 'userPassword': [b'password'],
+ }
+
+ conn.add_s(dn, modlist.addModlist(attrs))
+ add_count += 1
+ update_stats('adds')
+
+ time.sleep(0.005)
+
+ conn.delete_s(dn)
+ update_stats('deletes')
+
+ counter += 1
+
+ time.sleep(0.01)
+
+ except ldap.SERVER_DOWN:
+ log.error(f"Churn worker {thread_id}: SERVER DOWN - ns-slapd crashed!")
+ mark_server_crashed()
+ stop_event.set()
+ break
+ except ldap.ALREADY_EXISTS:
+ try:
+ conn.delete_s(dn)
+ except:
+ pass
+ counter += 1
+ except Exception as e:
+ update_stats('errors')
+ if add_count < 10:
+ log.debug(f"Churn worker {thread_id} error: {e}")
+ counter += 1
+ finally:
+ try:
+ conn.unbind_s()
+ except:
+ pass
+ log.info(f"Churn worker {thread_id} stopped (total adds: {add_count})")
+
+
+def monitor_worker(stop_event, duration):
+ """Worker thread to monitor progress and enforce timeout
+
+ :param stop_event: Event to signal thread termination
+ :param duration: Test duration in seconds
+ """
+ log.info(f"Monitor worker started (duration: {duration}s / {duration/60:.1f}m)")
+
+ start_time = time.time()
+ last_stats_time = start_time
+
+ while not stop_event.is_set():
+ current_time = time.time()
+ elapsed = current_time - start_time
+
+ # Print stats every 30 seconds
+ if current_time - last_stats_time >= 30:
+ print_stats()
+ log.info(f"Progress: {elapsed:.0f}s / {duration}s ({elapsed/duration*100:.1f}%)")
+ last_stats_time = current_time
+
+ # Check if duration has been reached
+ if elapsed >= duration:
+ log.info(f"Duration {duration}s reached. Test completed successfully!")
+ stop_event.set()
+ break
+
+ time.sleep(1)
+
+ log.info("Monitor worker stopped")
+
+
+@pytest.mark.slow
+def test_bdb_cursor_race(topology_st):
+ """Stress test to reproduce BDB cursor race condition
+
+ :id: d53a9580-58e5-454c-96fd-57839660fd51
+ :setup: Standalone instance
+ :steps:
+ 1. Setup test data with 1000 entries containing multi-valued attributes
+ 2. Start search, modify, delete and churn threads
+ 3. Run for 60 minutes
+ :expectedresults:
+ 1. Success
+ 2. Success
+ 3. Server doesn't crash
+ """
+
+ inst = topology_st.standalone
+
+ log.info("=" * 80)
+ log.info("BDB Cursor Race Condition Test")
+ log.info(f"Instance: {inst.serverid}")
+ log.info(f"Base DN: {DEFAULT_SUFFIX}")
+ log.info(f"Duration: {DEFAULT_TIMEOUT}s ({DEFAULT_TIMEOUT/60:.0f} minutes)")
+ log.info("=" * 80)
+
+ # Enable log buffering for better performance
+ log.info("Enabling log buffering for access and error logs...")
+ inst.config.set('nsslapd-accesslog-logbuffering', 'on')
+ inst.config.set('nsslapd-errorlog-logbuffering', 'on')
+
+ # Setup test data
+ setup_test_data(inst, num_entries=1000)
+
+ # Extract connection parameters once to avoid sharing inst object
+ ldap_host = inst.host
+ ldap_port = inst.port
+
+ # Reset statistics and crash flag
+ global stats, server_crashed
+ stats = {
+ 'searches': 0,
+ 'modifies': 0,
+ 'deletes': 0,
+ 'adds': 0,
+ 'errors': 0,
+ 'start_time': time.time()
+ }
+ server_crashed = False
+
+ # Create stop event
+ stop_event = Event()
+
+ # Start all worker threads
+ threads = []
+
+ # Search workers - hammer equality indexes
+ for i in range(SEARCH_THREADS):
+ t = Thread(target=search_worker,
+ args=(stop_event, ldap_host, ldap_port, i),
+ name=f"search-{i}")
+ t.daemon = True
+ t.start()
+ threads.append(t)
+
+ # Modify workers - update multi-valued attributes
+ for i in range(MODIFY_THREADS):
+ t = Thread(target=modify_worker,
+ args=(stop_event, ldap_host, ldap_port, i),
+ name=f"modify-{i}")
+ t.daemon = True
+ t.start()
+ threads.append(t)
+
+ # Delete workers - trigger index cleanup
+ for i in range(DELETE_THREADS):
+ t = Thread(target=delete_worker,
+ args=(stop_event, ldap_host, ldap_port, i),
+ name=f"delete-{i}")
+ t.daemon = True
+ t.start()
+ threads.append(t)
+
+ # Churn workers - add/delete cycle
+ for i in range(CHURN_THREADS):
+ t = Thread(target=churn_worker,
+ args=(stop_event, ldap_host, ldap_port, i),
+ name=f"churn-{i}")
+ t.daemon = True
+ t.start()
+ threads.append(t)
+
+ # Monitor worker - enforce timeout and print stats
+ monitor_thread = Thread(target=monitor_worker,
+ args=(stop_event, DEFAULT_TIMEOUT),
+ name="monitor")
+ monitor_thread.daemon = True
+ monitor_thread.start()
+ threads.append(monitor_thread)
+
+ log.info("=" * 80)
+ log.info(f"Started {len(threads)} worker threads:")
+ log.info(f" - {SEARCH_THREADS} search threads (equality index queries)")
+ log.info(f" - {MODIFY_THREADS} modify threads (multi-valued attr updates)")
+ log.info(f" - {DELETE_THREADS} delete threads (index cleanup)")
+ log.info(f" - {CHURN_THREADS} churn threads (add/delete cycle)")
+ log.info(f" - 1 monitor thread (timeout enforcement)")
+ log.info("=" * 80)
+ log.info(f"Running for {DEFAULT_TIMEOUT}s ({DEFAULT_TIMEOUT/60:.0f} minutes)...")
+ log.info("=" * 80)
+
+ # Wait for all threads to finish or timeout
+ for t in threads:
+ t.join()
+
+ # Final statistics
+ log.info("=" * 80)
+ log.info("Final Statistics:")
+ print_stats()
+ log.info("=" * 80)
+
+ # Check if server crashed during the test
+ if server_crashed:
+ log.error("=" * 80)
+ log.error("Test FAILED: Server crashed during the test!")
+ log.error("=" * 80)
+ pytest.fail("ns-slapd crashed during BDB cursor race test")
+ else:
+ log.info("=" * 80)
+ log.info("Test PASSED: Completed full duration without crash")
+ log.info("=" * 80)
+
+
+if __name__ == '__main__':
+ # Run isolated
+ # -s for DEBUG mode
+ import os
+ CURRENT_FILE = os.path.realpath(__file__)
+ pytest.main(["-s", "-v", CURRENT_FILE])
diff --git a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_layer.c b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_layer.c
index 651e595ee..a2dcc61e1 100644
--- a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_layer.c
+++ b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_layer.c
@@ -2634,10 +2634,19 @@ bdb_txn_begin(struct ldbminfo *li, back_txnid parent_txn, back_txn *txn, PRBool
txn->back_txn_txn = NULL;
}
- if (conf->bdb_enable_transactions) {
+ bdb_db_env *pEnv = (bdb_db_env *)priv->dblayer_env;
+
+ /*
+ * Check both config and actual environment capabilities before starting a transaction.
+ * During offline import, the environment is created without DB_INIT_TXN even though
+ * bdb_enable_transactions may be true in config. We use bdb_openflags (cached at
+ * environment open time) rather than get_open_flags() to safely handle cases where
+ * the environment handle exists but may not be fully opened yet.
+ */
+ if (pEnv && pEnv->bdb_DB_ENV && conf->bdb_enable_transactions &&
+ (pEnv->bdb_openflags & DB_INIT_TXN)) {
int txn_begin_flags;
- bdb_db_env *pEnv = (bdb_db_env *)priv->dblayer_env;
if (use_lock)
slapi_rwlock_rdlock(pEnv->bdb_env_lock);
if (!parent_txn) {
diff --git a/ldap/servers/slapd/back-ldbm/idl_new.c b/ldap/servers/slapd/back-ldbm/idl_new.c
index 205d0007e..fff2c805e 100644
--- a/ldap/servers/slapd/back-ldbm/idl_new.c
+++ b/ldap/servers/slapd/back-ldbm/idl_new.c
@@ -158,10 +158,14 @@ idl_new_fetch(
return NULL;
}
+ /*
+ * Always use transaction isolation for cursor operations to prevent
+ * race conditions. Without a transaction, concurrent modifications to
+ * index pages can corrupt cursor state, leading to crashes in BDB's
+ * internal functions (e.g., negative size passed to memmove).
+ */
dblayer_txn_init(li, &s_txn);
- if (txn) {
- dblayer_read_txn_begin(be, txn, &s_txn);
- }
+ dblayer_read_txn_begin(be, txn, &s_txn);
/*
* Verify the database's environment actually supports transactions.
@@ -178,7 +182,7 @@ idl_new_fetch(
}
/* Make a cursor */
- ret = db->cursor(db, txn, &cursor, 0);
+ ret = db->cursor(db, s_txn.back_txn_txn, &cursor, 0);
if (0 != ret) {
ldbm_nasty("idl_new_fetch", filename, 1, ret);
cursor = NULL;
@@ -419,10 +423,14 @@ idl_new_range_fetch(
return NULL;
}
+ /*
+ * Always use transaction isolation for cursor operations to prevent
+ * race conditions. Without a transaction, concurrent modifications to
+ * index pages can corrupt cursor state, leading to crashes in BDB's
+ * internal functions (e.g., negative size passed to memmove).
+ */
dblayer_txn_init(li, &s_txn);
- if (txn) {
- dblayer_read_txn_begin(be, txn, &s_txn);
- }
+ dblayer_read_txn_begin(be, txn, &s_txn);
/*
* Verify the database's environment actually supports transactions.
@@ -439,7 +447,7 @@ idl_new_range_fetch(
}
/* Make a cursor */
- ret = db->cursor(db, txn, &cursor, 0);
+ ret = db->cursor(db, s_txn.back_txn_txn, &cursor, 0);
if (0 != ret) {
ldbm_nasty("idl_new_range_fetch", filename, 1, ret);
cursor = NULL;
--
2.52.0

View File

@ -0,0 +1,37 @@
From 6a159091cce5a2dedef9b3b9b64dfb5b332ec9c4 Mon Sep 17 00:00:00 2001
From: James Chapman <jachapma@redhat.com>
Date: Wed, 1 Apr 2026 23:04:56 +0100
Subject: [PATCH 05/13] Issue 7380 - Internal op with negative wtime and large
optime (#7381)
Description:
The retro CL plugin triggers internal operations that log incorrect wtime and optime
to the server access logs.This happens because seq_internal_callback_pb() calls the
backend directly without setting the operation start time.
Fix:
Add slapi_operation_set_time_started(op) to seq_internal_callback_pb() to record
when the operation actually starts processing.
Fixes: https://github.com/389ds/389-ds-base/issues/7380
Reviewed by: @mreynolds389 (Thank you)
---
ldap/servers/slapd/plugin_internal_op.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/ldap/servers/slapd/plugin_internal_op.c b/ldap/servers/slapd/plugin_internal_op.c
index a140e7988..38de6177e 100644
--- a/ldap/servers/slapd/plugin_internal_op.c
+++ b/ldap/servers/slapd/plugin_internal_op.c
@@ -378,6 +378,7 @@ seq_internal_callback_pb(Slapi_PBlock *pb, void *callback_data, plugin_result_ca
set_common_params(pb);
slapi_td_internal_op_start();
+ slapi_operation_set_time_started(op);
if (be->be_seq != NULL) {
rc = (*be->be_seq)(pb);
} else {
--
2.52.0

View File

@ -0,0 +1,82 @@
From 159eb43433002378ded06af6c30ea7aed8c220ad Mon Sep 17 00:00:00 2001
From: Viktor Ashirov <vashirov@redhat.com>
Date: Wed, 14 Jan 2026 17:55:29 +0100
Subject: [PATCH 06/13] Issue 7152 - ns-slapd fails to shutdown when deferred
memberof update is in progress (#7187)
Bug Description:
When a deferred memberof update is in progress during shutdown, the
backend operations (add, modify, delete, modrdn) wait in a polling loop
for the deferred task to complete. However, if the deferred thread exits
before clearing the SLAPI_DEFERRED_MEMBEROF flag, the loop becomes
infinite, causing the server to hang during shutdown.
Fix Description:
Add additional check to the polling loops so they exit immediately when
the server is shutting down.
Fixes: https://github.com/389ds/389-ds-base/issues/7152
Reviewed by: @tbordaz (Thanks!)
---
ldap/servers/slapd/back-ldbm/ldbm_add.c | 2 +-
ldap/servers/slapd/back-ldbm/ldbm_delete.c | 2 +-
ldap/servers/slapd/back-ldbm/ldbm_modify.c | 2 +-
ldap/servers/slapd/back-ldbm/ldbm_modrdn.c | 2 +-
4 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_add.c b/ldap/servers/slapd/back-ldbm/ldbm_add.c
index bca660f22..226b846cb 100644
--- a/ldap/servers/slapd/back-ldbm/ldbm_add.c
+++ b/ldap/servers/slapd/back-ldbm/ldbm_add.c
@@ -1451,7 +1451,7 @@ common_return:
if (deferred) {
delay = PR_MillisecondsToInterval(100);
}
- while (deferred) {
+ while (deferred && !g_get_shutdown()) {
DS_Sleep(delay);
slapi_pblock_get(pb, SLAPI_DEFERRED_MEMBEROF, &deferred);
}
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_delete.c b/ldap/servers/slapd/back-ldbm/ldbm_delete.c
index c0f2516de..cf79daa77 100644
--- a/ldap/servers/slapd/back-ldbm/ldbm_delete.c
+++ b/ldap/servers/slapd/back-ldbm/ldbm_delete.c
@@ -1577,7 +1577,7 @@ diskfull_return:
if (deferred) {
delay = PR_MillisecondsToInterval(100);
}
- while (deferred) {
+ while (deferred && !g_get_shutdown()) {
DS_Sleep(delay);
slapi_pblock_get(pb, SLAPI_DEFERRED_MEMBEROF, &deferred);
}
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_modify.c b/ldap/servers/slapd/back-ldbm/ldbm_modify.c
index 0c62c87f0..ccecce571 100644
--- a/ldap/servers/slapd/back-ldbm/ldbm_modify.c
+++ b/ldap/servers/slapd/back-ldbm/ldbm_modify.c
@@ -1168,7 +1168,7 @@ common_return:
if (deferred) {
delay = PR_MillisecondsToInterval(100);
}
- while (deferred) {
+ while (deferred && !g_get_shutdown()) {
DS_Sleep(delay);
slapi_pblock_get(pb, SLAPI_DEFERRED_MEMBEROF, &deferred);
}
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c b/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c
index fe35991b5..31e2caa46 100644
--- a/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c
+++ b/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c
@@ -1519,7 +1519,7 @@ common_return:
if (deferred) {
delay = PR_MillisecondsToInterval(100);
}
- while (deferred) {
+ while (deferred && !g_get_shutdown()) {
DS_Sleep(delay);
slapi_pblock_get(pb, SLAPI_DEFERRED_MEMBEROF, &deferred);
}
--
2.52.0

View File

@ -0,0 +1,86 @@
From cb1caa0093381d7b52ea61931db7d33804a025b8 Mon Sep 17 00:00:00 2001
From: Mark Reynolds <mreynolds@redhat.com>
Date: Mon, 23 Feb 2026 12:37:26 -0500
Subject: [PATCH 07/13] Issue 7271 - plugins that create threads need to update
active thread count
Description:
Plugins that create threads need to up to the global active thread count.
Otherwise when the server is being stopped the plugin's close function gets
called while these threads are still running and still using the plugin
configuration. This can lead to crashes.
relates: https://github.com/389ds/389-ds-base/issues/7271
Reviewed by: progier & tbordaz (Thanks!!)
---
ldap/servers/plugins/replication/repl5_protocol.c | 5 +++++
ldap/servers/plugins/retrocl/retrocl_trim.c | 7 ++++++-
2 files changed, 11 insertions(+), 1 deletion(-)
diff --git a/ldap/servers/plugins/replication/repl5_protocol.c b/ldap/servers/plugins/replication/repl5_protocol.c
index dfdb69889..6cdbcc4f1 100644
--- a/ldap/servers/plugins/replication/repl5_protocol.c
+++ b/ldap/servers/plugins/replication/repl5_protocol.c
@@ -23,6 +23,7 @@
#include "repl5.h"
#include "repl5_prot_private.h"
+#include "slap.h"
#define PROTOCOL_5_INCREMENTAL 1
#define PROTOCOL_5_TOTAL 2
@@ -237,6 +238,8 @@ prot_thread_main(void *arg)
return;
}
+ g_incr_active_threadcnt();
+
set_thread_private_agmtname(agmt_get_long_name(agmt));
done = 0;
@@ -301,6 +304,8 @@ prot_thread_main(void *arg)
done = 1;
}
}
+
+ g_decr_active_threadcnt();
}
/*
diff --git a/ldap/servers/plugins/retrocl/retrocl_trim.c b/ldap/servers/plugins/retrocl/retrocl_trim.c
index 158f801cc..6514fc790 100644
--- a/ldap/servers/plugins/retrocl/retrocl_trim.c
+++ b/ldap/servers/plugins/retrocl/retrocl_trim.c
@@ -243,6 +243,8 @@ trim_changelog(void)
now_interval = slapi_current_rel_time_t(); /* monotonic time for interval */
+ g_incr_active_threadcnt();
+
PR_Lock(ts.ts_s_trim_mutex);
max_age = ts.ts_c_max_age;
trim_interval = ts.ts_c_trim_interval;
@@ -257,7 +259,7 @@ trim_changelog(void)
*/
done = 0;
now_maxage = slapi_current_utc_time(); /* real time for trim candidates */
- while (!done && retrocl_trimming == 1) {
+ while (!done && retrocl_trimming == 1 && !slapi_is_shutting_down()) {
int did_delete;
did_delete = 0;
@@ -309,6 +311,9 @@ trim_changelog(void)
"trim_changelog: removed %d change records\n",
num_deleted);
}
+
+ g_decr_active_threadcnt();
+
return rc;
}
--
2.52.0

View File

@ -0,0 +1,257 @@
From 0e98684fb9a4db555b92d5fd2c261f26e89dc16f Mon Sep 17 00:00:00 2001
From: Mark Reynolds <mreynolds@redhat.com>
Date: Mon, 2 Mar 2026 14:31:29 -0500
Subject: [PATCH 08/13] Issue 7271 - implement a pre-close plugin function
Description:
replication protocol could benefit from being notifioed the shutdown process
has started before calling the "close" plugin function. This would allow the
replication protocol to wake up and adjust its active thread count to prevent
a hang during shutdown.
relates: https://github.com/389ds/389-ds-base/issues/7271
Reviewed by: progier, spichugi, and vashirov(Thanks!!!)
---
ldap/servers/plugins/replication/repl5_init.c | 20 +++++++++++-----
ldap/servers/slapd/daemon.c | 6 ++++-
ldap/servers/slapd/pblock.c | 14 ++++++++++-
ldap/servers/slapd/plugin.c | 24 ++++++++++++++++++-
ldap/servers/slapd/proto-slap.h | 3 ++-
ldap/servers/slapd/slap.h | 3 ++-
ldap/servers/slapd/slapi-plugin.h | 5 ++--
7 files changed, 62 insertions(+), 13 deletions(-)
diff --git a/ldap/servers/plugins/replication/repl5_init.c b/ldap/servers/plugins/replication/repl5_init.c
index 9b6523a2e..dd4870746 100644
--- a/ldap/servers/plugins/replication/repl5_init.c
+++ b/ldap/servers/plugins/replication/repl5_init.c
@@ -1,6 +1,6 @@
/** BEGIN COPYRIGHT BLOCK
* Copyright (C) 2001 Sun Microsystems, Inc. Used by permission.
- * Copyright (C) 2005 Red Hat, Inc.
+ * Copyright (C) 2026 Red Hat, Inc.
* All rights reserved.
*
* License: GPL (version 3 or any later version).
@@ -122,6 +122,8 @@ static PRUintn thread_private_agmtname; /* thread private index for logging*/
static PRUintn thread_private_cache;
static PRUintn thread_primary_csn;
+static int multisupplier_pre_stop(Slapi_PBlock *pb __attribute__((unused)));
+
char *
get_thread_private_agmtname()
{
@@ -832,10 +834,6 @@ multimaster_stop(Slapi_PBlock *pb __attribute__((unused)))
int rc = 0; /* OK */
if (!multimaster_stopped_flag) {
- if (!is_ldif_dump) {
- /* Shut down replication agreements */
- agmtlist_shutdown();
- }
/* if we are cleaning a ruv, stop */
stop_ruv_cleaning();
/* unregister backend state change notification */
@@ -849,6 +847,16 @@ multimaster_stop(Slapi_PBlock *pb __attribute__((unused)))
return rc;
}
+static int
+multisupplier_pre_stop(Slapi_PBlock *pb __attribute__((unused)))
+{
+ if (!multisupplier_stopped_flag) {
+ /* Shut down replication agreements which will stop all the
+ * replication protocol threads */
+ agmtlist_shutdown();
+ }
+ return 0;
+}
PRBool
multimaster_started()
@@ -896,7 +904,7 @@ replication_multimaster_plugin_init(Slapi_PBlock *pb)
rc = slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&multimasterdesc);
rc = slapi_pblock_set(pb, SLAPI_PLUGIN_START_FN, (void *)multimaster_start);
rc = slapi_pblock_set(pb, SLAPI_PLUGIN_CLOSE_FN, (void *)multimaster_stop);
-
+ rc = slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_CLOSE_FN, (void *)multisupplier_pre_stop);
/* Register the plugin interfaces we implement */
/* preop acquires csn generator handle */
rc = slapi_register_plugin("preoperation", 1 /* Enabled */,
diff --git a/ldap/servers/slapd/daemon.c b/ldap/servers/slapd/daemon.c
index 2534483c1..c2977aa6f 100644
--- a/ldap/servers/slapd/daemon.c
+++ b/ldap/servers/slapd/daemon.c
@@ -1,6 +1,6 @@
/** BEGIN COPYRIGHT BLOCK
* Copyright (C) 2001 Sun Microsystems, Inc. Used by permission.
- * Copyright (C) 2021 Red Hat, Inc.
+ * Copyright (C) 2026 Red Hat, Inc.
* All rights reserved.
*
* License: GPL (version 3 or any later version).
@@ -1367,6 +1367,10 @@ slapd_daemon(daemon_ports_t *ports)
task_cancel_all();
}
+ /* Call plugin pre close functions */
+ plugin_pre_closeall();
+
+ /* Now wait for active threads to terminate */
threads = g_get_active_threadcnt();
if (threads > 0) {
slapi_log_err(SLAPI_LOG_INFO, "slapd_daemon",
diff --git a/ldap/servers/slapd/pblock.c b/ldap/servers/slapd/pblock.c
index 7c1cdc81c..b97c1acef 100644
--- a/ldap/servers/slapd/pblock.c
+++ b/ldap/servers/slapd/pblock.c
@@ -1,6 +1,6 @@
/** BEGIN COPYRIGHT BLOCK
* Copyright (C) 2001 Sun Microsystems, Inc. Used by permission.
- * Copyright (C) 2005 Red Hat, Inc.
+ * Copyright (C) 2026 Red Hat, Inc.
* Copyright (C) 2009 Hewlett-Packard Development Company, L.P.
* All rights reserved.
*
@@ -2524,6 +2524,14 @@ slapi_pblock_get(Slapi_PBlock *pblock, int arg, void *value)
}
break;
+ case SLAPI_PLUGIN_PRE_CLOSE_FN:
+ if (pblock->pb_plugin != NULL) {
+ (*(IFP *)value) = pblock->pb_plugin->plg_pre_close;
+ } else {
+ (*(IFP *)value) = NULL;
+ }
+ break;
+
default:
slapi_log_err(SLAPI_LOG_ERR, "slapi_pblock_get", "Unknown parameter block argument %d\n", arg);
PR_ASSERT(0);
@@ -4218,6 +4226,10 @@ slapi_pblock_set(Slapi_PBlock *pblock, int arg, void *value)
pblock->pb_misc->pb_aci_target_check = *((int *)value);
break;
+ case SLAPI_PLUGIN_PRE_CLOSE_FN:
+ pblock->pb_plugin->plg_pre_close = (IFP)value;
+ break;
+
default:
slapi_log_err(SLAPI_LOG_ERR, "slapi_pblock_set",
"Unknown parameter block argument %d\n", arg);
diff --git a/ldap/servers/slapd/plugin.c b/ldap/servers/slapd/plugin.c
index 449be2142..8b62dba32 100644
--- a/ldap/servers/slapd/plugin.c
+++ b/ldap/servers/slapd/plugin.c
@@ -1,6 +1,6 @@
/** BEGIN COPYRIGHT BLOCK
* Copyright (C) 2001 Sun Microsystems, Inc. Used by permission.
- * Copyright (C) 2005 Red Hat, Inc.
+ * Copyright (C) 2026 Red Hat, Inc.
* All rights reserved.
*
* License: GPL (version 3 or any later version).
@@ -1881,6 +1881,28 @@ plugin_dependency_closeall(void)
}
}
+/* Call the pre close functions of all the plugins */
+void
+plugin_pre_closeall(void)
+{
+ Slapi_PBlock *pb = NULL;
+ int plugins_pre_closed = 0;
+ int index = 0;
+
+ while (plugins_pre_closed < global_plugins_started) {
+ if (global_plugin_shutdown_order[index].name) {
+ if (!global_plugin_shutdown_order[index].removed) {
+ pb = slapi_pblock_new();
+ plugin_call_one(global_plugin_shutdown_order[index].plugin,
+ SLAPI_PLUGIN_PRE_CLOSE_FN, pb);
+ slapi_pblock_destroy(pb);
+ }
+ plugins_pre_closed++;
+ }
+ index++;
+ }
+}
+
void
plugin_freeall(void)
{
diff --git a/ldap/servers/slapd/proto-slap.h b/ldap/servers/slapd/proto-slap.h
index 6e473a08e..ab42c0e0f 100644
--- a/ldap/servers/slapd/proto-slap.h
+++ b/ldap/servers/slapd/proto-slap.h
@@ -1,6 +1,6 @@
/** BEGIN COPYRIGHT BLOCK
* Copyright (C) 2001 Sun Microsystems, Inc. Used by permission.
- * Copyright (C) 2005 Red Hat, Inc.
+ * Copyright (C) 2026 Red Hat, Inc.
* All rights reserved.
*
* License: GPL (version 3 or any later version).
@@ -959,6 +959,7 @@ int plugin_call_exop_plugins(Slapi_PBlock *pb, struct slapdplugin *p);
Slapi_Backend *plugin_extended_op_getbackend(Slapi_PBlock *pb, struct slapdplugin *p);
const char *plugin_extended_op_oid2string(const char *oid);
void plugin_closeall(int close_backends, int close_globals);
+void plugin_pre_closeall(void);
void plugin_dependency_freeall(void);
void plugin_startall(int argc, char **argv, char **plugin_list);
void plugin_get_plugin_dependencies(char *plugin_name, char ***names);
diff --git a/ldap/servers/slapd/slap.h b/ldap/servers/slapd/slap.h
index e6b49d366..0f67dda7d 100644
--- a/ldap/servers/slapd/slap.h
+++ b/ldap/servers/slapd/slap.h
@@ -1,6 +1,6 @@
/** BEGIN COPYRIGHT BLOCK
* Copyright (C) 2001 Sun Microsystems, Inc. Used by permission.
- * Copyright (C) 2009 Red Hat, Inc.
+ * Copyright (C) 2026 Red Hat, Inc.
* Copyright (C) 2009 Hewlett-Packard Development Company, L.P.
* All rights reserved.
*
@@ -1009,6 +1009,7 @@ struct slapdplugin
char *plg_libpath; /* library path for dll/so */
char *plg_initfunc; /* init symbol */
IFP plg_close; /* close function */
+ IFP plg_pre_close; /* pre close function */
Slapi_PluginDesc plg_desc; /* vendor's info */
char *plg_name; /* used for plugin rdn in cn=config */
struct slapdplugin *plg_next; /* for plugin lists */
diff --git a/ldap/servers/slapd/slapi-plugin.h b/ldap/servers/slapd/slapi-plugin.h
index 6517665a9..5dd78004c 100644
--- a/ldap/servers/slapd/slapi-plugin.h
+++ b/ldap/servers/slapd/slapi-plugin.h
@@ -1,6 +1,6 @@
/* BEGIN COPYRIGHT BLOCK
* Copyright (C) 2001 Sun Microsystems, Inc. Used by permission.
- * Copyright (C) 2009 Red Hat, Inc.
+ * Copyright (C) 2026 Red Hat, Inc.
* Copyright (C) 2009 Hewlett-Packard Development Company, L.P.
* All rights reserved.
*
@@ -6789,7 +6789,7 @@ time_t slapi_current_time(void) __attribute__((deprecated));
* and should NOT be used for timer information.
*/
int32_t slapi_clock_gettime(struct timespec *tp);
-/*
+/*
* slapi_clock_gettime should have better been called
* slapi_clock_utc_gettime but sice the function pre-existed
* we are just adding an alias (to avoid risking to break
@@ -7093,6 +7093,7 @@ typedef struct slapi_plugindesc
/* miscellaneous plugin functions */
#define SLAPI_PLUGIN_CLOSE_FN 210
+#define SLAPI_PLUGIN_PRE_CLOSE_FN 211
#define SLAPI_PLUGIN_START_FN 212
#define SLAPI_PLUGIN_CLEANUP_FN 232
#define SLAPI_PLUGIN_POSTSTART_FN 233
--
2.52.0

View File

@ -0,0 +1,34 @@
From fbe7a335332c6afc676174a47bf109e2c8cf1647 Mon Sep 17 00:00:00 2001
From: Mark Reynolds <mreynolds@redhat.com>
Date: Wed, 4 Mar 2026 14:47:01 -0500
Subject: [PATCH 09/13] Issue 7271 - Add new plugin pre-close function check to
plugin_invoke_plugin_pb
Description:
In plugin_invoke_plugin_pb we were not checking for the new pre-close function
which led to an error in the logs: pb_op is NULL. In a debug build this leads
to an assertion error at shutdown.
relates: https://github.com/389ds/389-ds-base/issues/7271
Reviewed by: vashirov(Thanks!)
---
ldap/servers/slapd/plugin.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/ldap/servers/slapd/plugin.c b/ldap/servers/slapd/plugin.c
index 8b62dba32..ab7875e87 100644
--- a/ldap/servers/slapd/plugin.c
+++ b/ldap/servers/slapd/plugin.c
@@ -3674,6 +3674,7 @@ plugin_invoke_plugin_pb(struct slapdplugin *plugin, int operation, Slapi_PBlock
if (operation == SLAPI_PLUGIN_START_FN ||
operation == SLAPI_PLUGIN_POSTSTART_FN ||
operation == SLAPI_PLUGIN_CLOSE_FN ||
+ operation == SLAPI_PLUGIN_PRE_CLOSE_FN ||
operation == SLAPI_PLUGIN_CLEANUP_FN ||
operation == SLAPI_PLUGIN_BE_PRE_CLOSE_FN ||
operation == SLAPI_PLUGIN_BE_POST_OPEN_FN ||
--
2.52.0

View File

@ -0,0 +1,38 @@
From b18b6ced81be921ba9901e96566d412aa8f2cec6 Mon Sep 17 00:00:00 2001
From: Mark Reynolds <mreynolds@redhat.com>
Date: Wed, 4 Mar 2026 14:28:59 -0500
Subject: [PATCH 10/13] Issue 7304 - retrocl should not cache DN
Description:
When adding a record to the retro changelog we pass in the flag
SLAPI_OP_FLAG_NEVER_CACHE to prevent the entry from being cached,
but we still update the DN cache leading to unexpected memory growth.
relates: https://github.com/389ds/389-ds-base/issues/7304
Reviewed by: tbordaz(Thanks!)
---
ldap/servers/slapd/back-ldbm/ldbm_add.c | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_add.c b/ldap/servers/slapd/back-ldbm/ldbm_add.c
index 226b846cb..89a3f18f6 100644
--- a/ldap/servers/slapd/back-ldbm/ldbm_add.c
+++ b/ldap/servers/slapd/back-ldbm/ldbm_add.c
@@ -1394,8 +1394,11 @@ common_return:
struct backdn *bdn = dncache_find_id(&inst->inst_dncache,
addingentry->ep_id);
if (bdn) { /* already in the dncache */
+ if (is_remove_from_cache) {
+ CACHE_REMOVE(&inst->inst_dncache, bdn);
+ }
CACHE_RETURN(&inst->inst_dncache, &bdn);
- } else { /* not in the dncache yet */
+ } else if (!is_remove_from_cache) { /* not in the dncache yet */
Slapi_DN *addingsdn =
slapi_sdn_dup(slapi_entry_get_sdn(addingentry->ep_entry));
if (addingsdn) {
--
2.52.0

View File

@ -0,0 +1,136 @@
From 317360e7704aacfba1b05d3f68ab99be0fa427b4 Mon Sep 17 00:00:00 2001
From: James Chapman <jachapma@redhat.com>
Date: Tue, 3 Mar 2026 16:04:16 +0000
Subject: [PATCH 11/13] Issue 7291 - Crash when configuring a replica with an
incorrect nsds5ReplicaRoot (#7292)
Description:
Configuring a replica when the replica_root does not exist, results in
a NULL mapping tree node extension, which is deferenced without checking
for NULL.
Fix:
Add a NULL check, log an error message, return LDAP_UNWILLING_TO_PERFORM. Added a CI test.
Fixes: https://github.com/389ds/389-ds-base/issues/7291
Reviewed by: @progier389, @mreynolds389, @droideck (Thank you)
---
.../suites/replication/replica_config_test.py | 41 +++++++++++++++++++
.../replication/repl5_replica_config.c | 20 ++++++---
2 files changed, 55 insertions(+), 6 deletions(-)
diff --git a/dirsrvtests/tests/suites/replication/replica_config_test.py b/dirsrvtests/tests/suites/replication/replica_config_test.py
index 570c3877b..1f1cde565 100644
--- a/dirsrvtests/tests/suites/replication/replica_config_test.py
+++ b/dirsrvtests/tests/suites/replication/replica_config_test.py
@@ -17,6 +17,7 @@ from lib389.topologies import topology_st as topo
from lib389.replica import Replicas
from lib389.agreement import Agreements
from lib389.utils import ds_is_older
+from lib389 import Entry
pytestmark = pytest.mark.tier1
@@ -39,6 +40,7 @@ replica_dict = {'nsDS5ReplicaRoot': 'dc=example,dc=com',
'nsDS5ReplicaBindDN': 'cn=u',
'cn': 'replica'}
+
agmt_dict = {'cn': 'test_agreement',
'nsDS5ReplicaRoot': 'dc=example,dc=com',
'nsDS5ReplicaHost': 'localhost.localdomain',
@@ -193,6 +195,45 @@ def test_replica_num_modify(topo, attr, too_small, too_big, overflow, notnum, va
# Value is valid
replica.replace(attr, valid)
+def test_replica_config_add_nonexistent_replicaroot(topo):
+ """Test that adding a replica config entry with a non existent
+ nsds5replicaroot is correctly rejected.
+
+ :id: 8eec4244-8ebb-40e1-b6f7-364b85a1862b
+ :parametrized: no
+ :setup: standalone instance
+ :steps:
+ 1. Attempt to add a replica config entry with an invalid nsds5replicaroot
+ 2. Verify that no replica entry is created
+ 3. Verify the server is still running
+ :expectedresults:
+ 1. The operation is rejected with UNWILLING_TO_PERFORM
+ 2. Success
+ 3. Success
+ """
+ inst = topo.standalone
+ dn = "cn=replica,cn=dc\\3Dexample\\2Cdc\\3Dcom,cn=mapping tree,cn=config"
+
+ entry = Entry((dn, {
+ 'objectclass': [
+ 'top',
+ 'nsds5replica',
+ 'extensibleObject'
+ ],
+ 'cn': 'replica',
+ 'nsds5replicaroot': 'ou=idontexist',
+ 'nsds5replicaid': '65535',
+ 'nsds5replicatype': '2',
+ 'nsds5ReplicaBindDN': 'cn=replication manager,cn=config',
+ 'nsds5flags': '0',
+ }))
+ with pytest.raises(ldap.UNWILLING_TO_PERFORM ):
+ inst.add_s(entry)
+
+ with pytest.raises(ldap.NO_SUCH_OBJECT):
+ inst.getEntry(dn, ldap.SCOPE_BASE, '(objectClass=*)')
+
+ assert inst.status()
@pytest.mark.xfail(reason="Agreement validation current does not work.")
@pytest.mark.parametrize("attr, too_small, too_big, overflow, notnum, valid", agmt_attrs)
diff --git a/ldap/servers/plugins/replication/repl5_replica_config.c b/ldap/servers/plugins/replication/repl5_replica_config.c
index 8cc7423bf..c6d730892 100644
--- a/ldap/servers/plugins/replication/repl5_replica_config.c
+++ b/ldap/servers/plugins/replication/repl5_replica_config.c
@@ -279,10 +279,17 @@ replica_config_add(Slapi_PBlock *pb __attribute__((unused)),
replica_add_by_dn(replica_root);
mtnode_ext = _replica_config_get_mtnode_ext(e);
- PR_ASSERT(mtnode_ext);
-
+ if (mtnode_ext == NULL) {
+ if (errortext != NULL) {
+ PR_snprintf(errortext, SLAPI_DSE_RETURNTEXT_SIZE, "replica root '%s' does not exist", replica_root);
+ }
+ slapi_log_err(SLAPI_LOG_ERR, repl_plugin_name, "replica_config_add - replica root '%s' does not exist",
+ replica_root);
+ *returncode = LDAP_UNWILLING_TO_PERFORM;
+ goto done;
+ }
if (mtnode_ext->replica) {
- if ( errortext != NULL ) {
+ if (errortext != NULL) {
PR_snprintf(errortext, SLAPI_DSE_RETURNTEXT_SIZE, MSG_ALREADYCONFIGURED, replica_root);
}
slapi_log_err(SLAPI_LOG_ERR, repl_plugin_name, "replica_config_add - "MSG_ALREADYCONFIGURED, replica_root);
@@ -306,15 +313,16 @@ replica_config_add(Slapi_PBlock *pb __attribute__((unused)),
/* add replica object to the hash */
*returncode = replica_add_by_name(replica_get_name(r), r); /* Increments object refcnt */
- /* delete the dn from the dn hash - done with configuration */
- replica_delete_by_dn(replica_root);
done:
+ /* delete the dn from the dn hash - done with configuration */
+ replica_delete_by_dn(replica_root);
+
PR_Unlock(s_configLock);
if (*returncode != LDAP_SUCCESS) {
- if (mtnode_ext->replica)
+ if (mtnode_ext && mtnode_ext->replica)
object_release(mtnode_ext->replica);
return SLAPI_DSE_CALLBACK_ERROR;
} else
--
2.52.0

View File

@ -0,0 +1,822 @@
From 6ea0a4542e09191fbce053b9aca9dc0db31a744c Mon Sep 17 00:00:00 2001
From: Simon Pichugin <spichugi@redhat.com>
Date: Thu, 5 Mar 2026 17:09:13 -0800
Subject: [PATCH 12/13] Issue 7300 - RFE - Add OS-level thread names to all
server threads (#7301)
Description: Add slapi_set_thread_name() API wrapping pthread_setname_np
and apply it to all server threads.
Workers appear as worker-0..N, the listener as "listener",
replication threads as "repl-prot", "repl-inc-res", etc.
For functions that serve as both thread entry points and direct calls
(db2ldif_skip_all, dbmdb_recno_cache_build), thin wrapper functions
are used to keep thread naming separate from the core logic.
Fixes: https://github.com/389ds/389-ds-base/issues/7300
Reviewed by: @vashirov, @jchapma, @mreynolds389 (Thanks!!!)
---
dirsrvtests/tests/suites/threads/__init__.py | 0
.../suites/threads/thread_naming_test.py | 76 +++++++++++++++++++
ldap/servers/plugins/automember/automember.c | 4 +
ldap/servers/plugins/cos/cos_cache.c | 1 +
ldap/servers/plugins/linkedattrs/fixup_task.c | 1 +
ldap/servers/plugins/memberof/memberof.c | 2 +
.../plugins/posix-winsync/posix-group-task.c | 5 ++
ldap/servers/plugins/referint/referint.c | 1 +
ldap/servers/plugins/replication/cl5_api.c | 1 +
ldap/servers/plugins/replication/cl5_test.c | 1 +
.../plugins/replication/repl5_inc_protocol.c | 1 +
.../plugins/replication/repl5_protocol.c | 1 +
.../plugins/replication/repl5_replica.c | 1 +
.../plugins/replication/repl5_tot_protocol.c | 1 +
ldap/servers/plugins/retrocl/retrocl_trim.c | 1 +
ldap/servers/plugins/roles/roles_cache.c | 1 +
.../plugins/schema_reload/schema_reload.c | 1 +
ldap/servers/plugins/sync/sync_persist.c | 11 +--
ldap/servers/plugins/syntaxes/validate_task.c | 1 +
ldap/servers/plugins/usn/usn_cleanup.c | 1 +
ldap/servers/plugins/views/views.c | 1 +
.../slapd/back-ldbm/db-bdb/bdb_import.c | 1 +
.../back-ldbm/db-bdb/bdb_import_threads.c | 3 +
.../slapd/back-ldbm/db-bdb/bdb_layer.c | 7 ++
ldap/servers/slapd/connection.c | 3 +
ldap/servers/slapd/csngen.c | 13 ++--
ldap/servers/slapd/daemon.c | 2 +
ldap/servers/slapd/eventq-deprecated.c | 1 +
ldap/servers/slapd/eventq.c | 1 +
ldap/servers/slapd/house.c | 1 +
ldap/servers/slapd/psearch.c | 1 +
ldap/servers/slapd/slapi-plugin.h | 3 +
ldap/servers/slapd/task.c | 6 ++
ldap/servers/slapd/test-plugins/sampletask.c | 5 ++
ldap/servers/slapd/thread_data.c | 8 ++
ldap/servers/slapd/vattr.c | 1 +
36 files changed, 159 insertions(+), 10 deletions(-)
create mode 100644 dirsrvtests/tests/suites/threads/__init__.py
create mode 100644 dirsrvtests/tests/suites/threads/thread_naming_test.py
diff --git a/dirsrvtests/tests/suites/threads/__init__.py b/dirsrvtests/tests/suites/threads/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/dirsrvtests/tests/suites/threads/thread_naming_test.py b/dirsrvtests/tests/suites/threads/thread_naming_test.py
new file mode 100644
index 000000000..0f2380ac4
--- /dev/null
+++ b/dirsrvtests/tests/suites/threads/thread_naming_test.py
@@ -0,0 +1,76 @@
+# --- BEGIN COPYRIGHT BLOCK ---
+# Copyright (C) 2026 Red Hat, Inc.
+# All rights reserved.
+#
+# License: GPL (version 3 or any later version).
+# See LICENSE for details.
+# --- END COPYRIGHT BLOCK ---
+#
+import logging
+import os
+import platform
+import pytest
+
+from lib389 import pid_from_file
+from lib389.topologies import topology_st
+
+pytestmark = pytest.mark.tier1
+
+logging.getLogger(__name__).setLevel(logging.INFO)
+log = logging.getLogger(__name__)
+
+
+def _get_thread_names(pid):
+ """Read thread names from /proc/<pid>/task/*/comm"""
+ names = []
+ task_dir = f"/proc/{pid}/task"
+ if not os.path.isdir(task_dir):
+ pytest.skip(f"{task_dir} not available")
+ for tid in os.listdir(task_dir):
+ comm_path = os.path.join(task_dir, tid, "comm")
+ try:
+ with open(comm_path, "r") as f:
+ names.append(f.read().strip())
+ except (FileNotFoundError, PermissionError):
+ pass
+ return names
+
+
+def test_thread_names_present(topology_st):
+ """Test that key threads have been named via pthread_setname_np
+
+ :id: a95b4eca-e806-48dc-8c91-debbc3ef4053
+ :setup: Standalone instance
+ :steps:
+ 1. Get the server PID
+ 2. Read thread names from /proc/{pid}/task/{tid}/comm
+ 3. Verify expected thread names are present
+ :expectedresults:
+ 1. PID is obtained
+ 2. Thread names are readable
+ 3. Core thread names (listener, worker, event-q,
+ housekeep) are found
+ """
+ inst = topology_st.standalone
+ pid = str(pid_from_file(inst.ds_paths.pid_file))
+ log.info(f"Server PID: {pid}")
+
+ thread_names = _get_thread_names(pid)
+ log.info(f"Found {len(thread_names)} threads: {sorted(set(thread_names))}")
+
+ # Verify the listener thread
+ assert "listener" in thread_names, \
+ f"'listener' thread not found in {thread_names}"
+
+ # Verify at least one worker thread
+ worker_threads = [n for n in thread_names if n.startswith("worker-")]
+ assert len(worker_threads) > 0, \
+ f"No 'worker-*' threads found in {thread_names}"
+
+ # Verify event queue thread
+ assert "event-q" in thread_names, \
+ f"'event-q' thread not found in {thread_names}"
+
+ # Verify housekeeping thread
+ assert "housekeep" in thread_names, \
+ f"'housekeep' thread not found in {thread_names}"
diff --git a/ldap/servers/plugins/automember/automember.c b/ldap/servers/plugins/automember/automember.c
index 1b1da39b3..65404a574 100644
--- a/ldap/servers/plugins/automember/automember.c
+++ b/ldap/servers/plugins/automember/automember.c
@@ -2300,6 +2300,7 @@ automember_task_abort(Slapi_PBlock *pb, Slapi_Entry *e, Slapi_Entry *eAfter __at
void
automember_task_abort_thread(void *arg)
{
+ slapi_set_thread_name("automem-abort");
Slapi_Task *task = (Slapi_Task *)arg;
slapi_task_inc_refcount(task);
@@ -2456,6 +2457,7 @@ out:
void
automember_rebuild_task_thread(void *arg)
{
+ slapi_set_thread_name("automem-rebld");
Slapi_Task *task = (Slapi_Task *)arg;
struct configEntry *config = NULL;
Slapi_PBlock *search_pb = NULL;
@@ -2759,6 +2761,7 @@ out:
void
automember_export_task_thread(void *arg)
{
+ slapi_set_thread_name("automem-exprt");
Slapi_Task *task = (Slapi_Task *)arg;
Slapi_PBlock *search_pb = NULL;
Slapi_Entry **entries = NULL;
@@ -2949,6 +2952,7 @@ out:
void
automember_map_task_thread(void *arg)
{
+ slapi_set_thread_name("automem-map");
Slapi_Task *task = (Slapi_Task *)arg;
Slapi_Entry *e = NULL;
int result = SLAPI_DSE_CALLBACK_OK;
diff --git a/ldap/servers/plugins/cos/cos_cache.c b/ldap/servers/plugins/cos/cos_cache.c
index d404ff901..d8ebbbe3d 100644
--- a/ldap/servers/plugins/cos/cos_cache.c
+++ b/ldap/servers/plugins/cos/cos_cache.c
@@ -373,6 +373,7 @@ out:
static void
cos_cache_wait_on_change(void *arg __attribute__((unused)))
{
+ slapi_set_thread_name("cos-cache");
slapi_log_err(SLAPI_LOG_TRACE, COS_PLUGIN_SUBSYSTEM, "--> cos_cache_wait_on_change thread\n");
slapi_lock_mutex(stop_lock);
diff --git a/ldap/servers/plugins/linkedattrs/fixup_task.c b/ldap/servers/plugins/linkedattrs/fixup_task.c
index f47ccf6d1..e69c62d86 100644
--- a/ldap/servers/plugins/linkedattrs/fixup_task.c
+++ b/ldap/servers/plugins/linkedattrs/fixup_task.c
@@ -107,6 +107,7 @@ linked_attrs_fixup_task_destructor(Slapi_Task *task)
static void
linked_attrs_fixup_task_thread(void *arg)
{
+ slapi_set_thread_name("linked-fix");
Slapi_Task *task = (Slapi_Task *)arg;
task_data *td = NULL;
PRCList *main_config = NULL;
diff --git a/ldap/servers/plugins/memberof/memberof.c b/ldap/servers/plugins/memberof/memberof.c
index b52bc0331..153b08ca0 100644
--- a/ldap/servers/plugins/memberof/memberof.c
+++ b/ldap/servers/plugins/memberof/memberof.c
@@ -994,6 +994,7 @@ is_memberof_plugin_started(struct slapdplugin **plg_addr)
void
deferred_thread_func(void *arg)
{
+ slapi_set_thread_name("memberof-def");
MemberofDeferredList *deferred_list = (MemberofDeferredList *) arg;
MemberofDeferredTask *task;
struct slapdplugin *plg = NULL;
@@ -3862,6 +3863,7 @@ memberof_qsort_compare(const void *a, const void *b)
void
memberof_fixup_task_thread(void *arg)
{
+ slapi_set_thread_name("memberof-fix");
MemberOfConfig configCopy = {0};
Slapi_Task *task = (Slapi_Task *)arg;
task_data *td = NULL;
diff --git a/ldap/servers/plugins/posix-winsync/posix-group-task.c b/ldap/servers/plugins/posix-winsync/posix-group-task.c
index 922c4f743..96f205822 100644
--- a/ldap/servers/plugins/posix-winsync/posix-group-task.c
+++ b/ldap/servers/plugins/posix-winsync/posix-group-task.c
@@ -1,3 +1,7 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
#include <string.h>
#include "posix-wsp-ident.h"
#include "posix-group-func.h"
@@ -463,6 +467,7 @@ bail:
static void
posix_group_fixup_task_thread(void *arg)
{
+ slapi_set_thread_name("posix-grp-fix");
slapi_log_err(SLAPI_LOG_PLUGIN, POSIX_WINSYNC_PLUGIN_NAME,
"_task_thread ==>\n");
diff --git a/ldap/servers/plugins/referint/referint.c b/ldap/servers/plugins/referint/referint.c
index c5e259d8d..2dd3f1ed5 100644
--- a/ldap/servers/plugins/referint/referint.c
+++ b/ldap/servers/plugins/referint/referint.c
@@ -1393,6 +1393,7 @@ referint_postop_close(Slapi_PBlock *pb __attribute__((unused)))
void
referint_thread_func(void *arg __attribute__((unused)))
{
+ slapi_set_thread_name("referint");
PRFileDesc *prfd = NULL;
char *logfilename = NULL;
char thisline[MAX_LINE];
diff --git a/ldap/servers/plugins/replication/cl5_api.c b/ldap/servers/plugins/replication/cl5_api.c
index 21d2f5b8b..87d517dcf 100644
--- a/ldap/servers/plugins/replication/cl5_api.c
+++ b/ldap/servers/plugins/replication/cl5_api.c
@@ -3156,6 +3156,7 @@ do_cl_compact(time_t when, void *arg)
static int
_cl5TrimMain(void *param __attribute__((unused)))
{
+ slapi_set_thread_name("cl-trim");
time_t timePrev = slapi_current_utc_time();
time_t timeCompactPrev = slapi_current_utc_time();
time_t timeNow;
diff --git a/ldap/servers/plugins/replication/cl5_test.c b/ldap/servers/plugins/replication/cl5_test.c
index efb8c543a..c9078eb2e 100644
--- a/ldap/servers/plugins/replication/cl5_test.c
+++ b/ldap/servers/plugins/replication/cl5_test.c
@@ -551,6 +551,7 @@ clearCSNList(CSN ***csnList, int count)
static void
threadMain(void *data)
{
+ slapi_set_thread_name("cl5-test");
int entryCount = *(int *)data;
CSN **csnList;
diff --git a/ldap/servers/plugins/replication/repl5_inc_protocol.c b/ldap/servers/plugins/replication/repl5_inc_protocol.c
index 846951b9e..23e5b474c 100644
--- a/ldap/servers/plugins/replication/repl5_inc_protocol.c
+++ b/ldap/servers/plugins/replication/repl5_inc_protocol.c
@@ -237,6 +237,7 @@ repl5_inc_log_operation_failure(int operation_code, int ldap_error, char *ldap_e
static void
repl5_inc_result_threadmain(void *param)
{
+ slapi_set_thread_name("repl-inc-res");
result_data *rd = (result_data *)param;
ConnResult conres = 0;
Repl_Connection *conn = rd->prp->conn;
diff --git a/ldap/servers/plugins/replication/repl5_protocol.c b/ldap/servers/plugins/replication/repl5_protocol.c
index 6cdbcc4f1..f230fb702 100644
--- a/ldap/servers/plugins/replication/repl5_protocol.c
+++ b/ldap/servers/plugins/replication/repl5_protocol.c
@@ -226,6 +226,7 @@ finished (any) finished
static void
prot_thread_main(void *arg)
{
+ slapi_set_thread_name("repl-prot");
Repl_Protocol *rp = (Repl_Protocol *)arg;
int done;
Repl_Agmt *agmt = NULL;
diff --git a/ldap/servers/plugins/replication/repl5_replica.c b/ldap/servers/plugins/replication/repl5_replica.c
index cec140140..edfbfce3a 100644
--- a/ldap/servers/plugins/replication/repl5_replica.c
+++ b/ldap/servers/plugins/replication/repl5_replica.c
@@ -3158,6 +3158,7 @@ process_reap_entry(Slapi_Entry *entry, void *cb_data)
static void
_replica_reap_tombstones(void *arg)
{
+ slapi_set_thread_name("tomb-reap");
const char *replica_name = (const char *)arg;
Slapi_PBlock *pb = NULL;
Replica *replica = NULL;
diff --git a/ldap/servers/plugins/replication/repl5_tot_protocol.c b/ldap/servers/plugins/replication/repl5_tot_protocol.c
index 4b2064912..1ec930116 100644
--- a/ldap/servers/plugins/replication/repl5_tot_protocol.c
+++ b/ldap/servers/plugins/replication/repl5_tot_protocol.c
@@ -102,6 +102,7 @@ repl5_tot_log_operation_failure(int ldap_error, char *ldap_error_string, const c
static void
repl5_tot_result_threadmain(void *param)
{
+ slapi_set_thread_name("repl-tot-res");
callback_data *cb = (callback_data *)param;
ConnResult conres = 0;
Repl_Connection *conn = cb->prp->conn;
diff --git a/ldap/servers/plugins/retrocl/retrocl_trim.c b/ldap/servers/plugins/retrocl/retrocl_trim.c
index 6514fc790..a363c8a30 100644
--- a/ldap/servers/plugins/retrocl/retrocl_trim.c
+++ b/ldap/servers/plugins/retrocl/retrocl_trim.c
@@ -334,6 +334,7 @@ static int retrocl_active_threads;
static void
changelog_trim_thread_fn(void *arg __attribute__((unused)))
{
+ slapi_set_thread_name("retrocl-trim");
PR_AtomicIncrement(&retrocl_active_threads);
trim_changelog();
PR_AtomicDecrement(&retrocl_active_threads);
diff --git a/ldap/servers/plugins/roles/roles_cache.c b/ldap/servers/plugins/roles/roles_cache.c
index 329b8c888..5030a2d1b 100644
--- a/ldap/servers/plugins/roles/roles_cache.c
+++ b/ldap/servers/plugins/roles/roles_cache.c
@@ -359,6 +359,7 @@ roles_cache_create_suffix(Slapi_DN *sdn)
static void
roles_cache_wait_on_change(void *arg)
{
+ slapi_set_thread_name("roles-cache");
roles_cache_def *roles_def = (roles_cache_def *)arg;
slapi_log_err(SLAPI_LOG_PLUGIN, ROLES_PLUGIN_SUBSYSTEM, "--> roles_cache_wait_on_change\n");
diff --git a/ldap/servers/plugins/schema_reload/schema_reload.c b/ldap/servers/plugins/schema_reload/schema_reload.c
index 847323af4..8b0b64bca 100644
--- a/ldap/servers/plugins/schema_reload/schema_reload.c
+++ b/ldap/servers/plugins/schema_reload/schema_reload.c
@@ -125,6 +125,7 @@ typedef struct _task_data
static void
schemareload_thread(void *arg)
{
+ slapi_set_thread_name("schema-reload");
Slapi_Task *task = (Slapi_Task *)arg;
int rv = 0;
int total_work = 2;
diff --git a/ldap/servers/plugins/sync/sync_persist.c b/ldap/servers/plugins/sync/sync_persist.c
index a78c273cd..fdb41fd23 100644
--- a/ldap/servers/plugins/sync/sync_persist.c
+++ b/ldap/servers/plugins/sync/sync_persist.c
@@ -260,7 +260,7 @@ ignore_op_pl(Slapi_PBlock *pb)
* of the completed operation.
* When all operations are completed, if the primary operation is successful it
* flushes (enqueue) the operations to the sync repl queue(s), else it just free
- * the pending list (skipping enqueue).
+ * the pending list (skipping enqueue).
*/
static void
sync_update_persist_op(Slapi_PBlock *pb, Slapi_Entry *e, Slapi_Entry *eprev, ber_int_t op_tag, char *label)
@@ -286,7 +286,7 @@ sync_update_persist_op(Slapi_PBlock *pb, Slapi_Entry *e, Slapi_Entry *eprev, ber
ignore_op_pl(pb);
return;
}
-
+
/* Retrieve the result of the operation */
if (slapi_op_internal(pb)) {
slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc);
@@ -351,11 +351,11 @@ sync_update_persist_op(Slapi_PBlock *pb, Slapi_Entry *e, Slapi_Entry *eprev, ber
}
}
if (!curr_op) {
- slapi_log_err(SLAPI_LOG_ERR, SYNC_PLUGIN_SUBSYSTEM, "%s - operation (op=0x%lx, idx_pl=%d) not found on the pendling list\n",
+ slapi_log_err(SLAPI_LOG_ERR, SYNC_PLUGIN_SUBSYSTEM, "%s - operation (op=0x%lx, idx_pl=%d) not found on the pendling list\n",
label, (ulong) pb_op, ident->idx_pl);
PR_ASSERT(curr_op);
}
-
+
/* for diagnostic of the pending list, dump its content if it is too long */
for (count = 0, curr_op = prim_op; curr_op; count++, curr_op = curr_op->next);
if (loglevel_is_set(SLAPI_LOG_PLUGIN) && (count > 10)) {
@@ -432,7 +432,7 @@ sync_update_persist_op(Slapi_PBlock *pb, Slapi_Entry *e, Slapi_Entry *eprev, ber
if (enqueue_it) {
sync_queue_change(curr_op);
}
-
+
/* now free this pending operation */
next = curr_op->next;
slapi_entry_free(curr_op->entry);
@@ -975,6 +975,7 @@ sync_release_connection(Slapi_PBlock *pb, Slapi_Connection *conn, Slapi_Operatio
static void
sync_send_results(void *arg)
{
+ slapi_set_thread_name("sync-send");
SyncRequest *req = (SyncRequest *)arg;
SyncQueueNode *qnode, *qnodenext;
int conn_acq_flag = 0;
diff --git a/ldap/servers/plugins/syntaxes/validate_task.c b/ldap/servers/plugins/syntaxes/validate_task.c
index a634fd762..49829ab08 100644
--- a/ldap/servers/plugins/syntaxes/validate_task.c
+++ b/ldap/servers/plugins/syntaxes/validate_task.c
@@ -166,6 +166,7 @@ syntax_validate_task_destructor(Slapi_Task *task)
static void
syntax_validate_task_thread(void *arg)
{
+ slapi_set_thread_name("syntax-valid");
int rc = 0;
Slapi_Task *task = (Slapi_Task *)arg;
task_data *td = NULL;
diff --git a/ldap/servers/plugins/usn/usn_cleanup.c b/ldap/servers/plugins/usn/usn_cleanup.c
index 7eaf0f88f..47940eabe 100644
--- a/ldap/servers/plugins/usn/usn_cleanup.c
+++ b/ldap/servers/plugins/usn/usn_cleanup.c
@@ -45,6 +45,7 @@ usn_cleanup_close(void)
static void
usn_cleanup_thread(void *arg)
{
+ slapi_set_thread_name("usn-cleanup");
Slapi_Task *task = (Slapi_Task *)arg;
int rv = 0;
int total_work = 2;
diff --git a/ldap/servers/plugins/views/views.c b/ldap/servers/plugins/views/views.c
index 64e305a3f..ed2ba73e4 100644
--- a/ldap/servers/plugins/views/views.c
+++ b/ldap/servers/plugins/views/views.c
@@ -1797,5 +1797,6 @@ views_cache_backend_state_change(void *handle __attribute__((unused)), char *be_
static void
views_cache_act_on_change_thread(void *arg __attribute__((unused)))
{
+ slapi_set_thread_name("views-cache");
views_cache_create();
}
diff --git a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_import.c b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_import.c
index 7f484934f..2eede5931 100644
--- a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_import.c
+++ b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_import.c
@@ -2663,6 +2663,7 @@ error:
void
import_main(void *arg)
{
+ slapi_set_thread_name("bdb-import");
/* For online import tasks increment/decrement the global thread count */
g_incr_active_threadcnt();
import_main_offline(arg);
diff --git a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_import_threads.c b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_import_threads.c
index e433f3db2..350266a02 100644
--- a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_import_threads.c
+++ b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_import_threads.c
@@ -340,6 +340,7 @@ import_add_created_attrs(Slapi_Entry *e)
void
import_producer(void *param)
{
+ slapi_set_thread_name("bdb-imp-prod");
ImportWorkerInfo *info = (ImportWorkerInfo *)param;
ImportJob *job = info->job;
ID id = job->first_ID, id_filestart = id;
@@ -2469,6 +2470,7 @@ foreman_do_entryrdn(ImportJob *job, FifoItem *fi)
void
import_foreman(void *param)
{
+ slapi_set_thread_name("bdb-imp-frmn");
ImportWorkerInfo *info = (ImportWorkerInfo *)param;
ImportJob *job = info->job;
ldbm_instance *inst = job->inst;
@@ -2812,6 +2814,7 @@ error:
void
import_worker(void *param)
{
+ slapi_set_thread_name("bdb-imp-work");
ImportWorkerInfo *info = (ImportWorkerInfo *)param;
ImportJob *job = info->job;
ldbm_instance *inst = job->inst;
diff --git a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_layer.c b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_layer.c
index a2dcc61e1..ec7524d02 100644
--- a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_layer.c
+++ b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_layer.c
@@ -2909,6 +2909,7 @@ bdb_start_perf_thread(struct ldbminfo *li)
static int
perf_threadmain(void *param)
{
+ slapi_set_thread_name("bdb-perf");
struct ldbminfo *li = NULL;
PR_ASSERT(NULL != param);
@@ -2959,6 +2960,7 @@ bdb_start_locks_monitoring_thread(struct ldbminfo *li)
static int
locks_monitoring_threadmain(void *param)
{
+ slapi_set_thread_name("bdb-lock-mon");
int ret = 0;
uint64_t current_locks = 0;
uint64_t max_locks = 0;
@@ -3169,6 +3171,7 @@ txn_test_init_cfg(txn_test_cfg *cfg)
static int
txn_test_threadmain(void *param)
{
+ slapi_set_thread_name("bdb-txn-test");
struct ldbminfo *li = NULL;
Object *inst_obj;
int rc = 0;
@@ -3467,6 +3470,7 @@ bdb_start_txn_test_thread(struct ldbminfo *li)
static int
deadlock_threadmain(void *param)
{
+ slapi_set_thread_name("bdb-deadlock");
int rval = -1;
struct ldbminfo *li = NULL;
PRIntervalTime interval; /*NSPR timeout stuffy*/
@@ -3564,6 +3568,7 @@ bdb_start_log_flush_thread(struct ldbminfo *li)
static int
log_flush_threadmain(void *param)
{
+ slapi_set_thread_name("bdb-logflush");
PRIntervalTime interval_flush, interval_def;
PRIntervalTime last_flush = 0;
int i;
@@ -3761,6 +3766,7 @@ bdb_start_checkpoint_thread(struct ldbminfo *li)
static int
checkpoint_threadmain(void *param)
{
+ slapi_set_thread_name("bdb-chkpoint");
PRIntervalTime interval;
int rval = -1;
struct ldbminfo *li = NULL;
@@ -3964,6 +3970,7 @@ bdb_start_trickle_thread(struct ldbminfo *li)
static int
trickle_threadmain(void *param)
{
+ slapi_set_thread_name("bdb-trickle");
PRIntervalTime interval; /*NSPR timeout stuffy*/
int rval = -1;
dblayer_private *priv = NULL;
diff --git a/ldap/servers/slapd/connection.c b/ldap/servers/slapd/connection.c
index 6c5ef5291..e757c838a 100644
--- a/ldap/servers/slapd/connection.c
+++ b/ldap/servers/slapd/connection.c
@@ -1634,6 +1634,9 @@ connection_threadmain(void *arg)
{
Slapi_PBlock *pb = slapi_pblock_new();
int32_t *snmp_vars_idx = (int32_t *) arg;
+ char tname[16];
+ snprintf(tname, sizeof(tname), "worker-%d", *snmp_vars_idx);
+ slapi_set_thread_name(tname);
/* wait forever for new pb until one is available or shutdown */
int32_t interval = 0; /* used be 10 seconds */
Connection *conn = NULL;
diff --git a/ldap/servers/slapd/csngen.c b/ldap/servers/slapd/csngen.c
index c7c5c2ba8..8fff8e87e 100644
--- a/ldap/servers/slapd/csngen.c
+++ b/ldap/servers/slapd/csngen.c
@@ -315,15 +315,15 @@ csngen_adjust_time(CSNGen *gen, const CSN *csn)
/* To avoid beat phenomena between suppliers let put 1 second in local_offset
* it will be eaten at next clock tick rather than increasing remote offset
* If we do not do that we will have a time skew drift of 1 second per 2 seconds
- * if suppliers are desynchronized by 0.5 second
+ * if suppliers are desynchronized by 0.5 second
*/
if (gen->state.local_offset == 0) {
gen->state.local_offset++;
gen->state.remote_offset--;
}
}
- /* Time to compute seqnum so that
- * new csn >= remote csn and new csn >= old local csn
+ /* Time to compute seqnum so that
+ * new csn >= remote csn and new csn >= old local csn
*/
new_time = CSN_CALC_TSTAMP(gen);
PR_ASSERT(new_time >= old_time);
@@ -570,7 +570,7 @@ _csngen_adjust_local_time(CSNGen *gen)
time_t cur_time;
int rc;
-
+
if ((rc = gen->gettime(&now)) != 0) {
/* Failed to get system time, we must abort */
slapi_log_err(SLAPI_LOG_ERR, "csngen_new_csn",
@@ -617,7 +617,7 @@ _csngen_adjust_local_time(CSNGen *gen)
gen->state.sampled_time = cur_time;
gen->state.local_offset = MAX_VAL(0, gen->state.local_offset - time_diff);
/* new local_offset = MAX_VAL(0, old sample_time + old local_offset - cur_time)
- * ==> new local_offset >= 0 and
+ * ==> new local_offset >= 0 and
* new local_offset + cur_time >= old sample_time + old local_offset
* ==> new local_offset + cur_time + remote_offset >=
* sample_time + old local_offset + remote_offset
@@ -731,6 +731,7 @@ _csngen_stop_test_threads(void)
static void
_csngen_gen_tester_main(void *data)
{
+ slapi_set_thread_name("csn-gen-test");
CSNGen *gen = (CSNGen *)data;
CSN *csn = NULL;
char buff[CSN_STRSIZE];
@@ -761,6 +762,7 @@ _csngen_gen_tester_main(void *data)
static void
_csngen_remote_tester_main(void *data)
{
+ slapi_set_thread_name("csn-rmt-test");
CSNGen *gen = (CSNGen *)data;
CSN *csn;
time_t csn_time;
@@ -798,6 +800,7 @@ _csngen_remote_tester_main(void *data)
static void
_csngen_local_tester_main(void *data)
{
+ slapi_set_thread_name("csn-lcl-test");
CSNGen *gen = (CSNGen *)data;
PR_ASSERT(gen);
diff --git a/ldap/servers/slapd/daemon.c b/ldap/servers/slapd/daemon.c
index c2977aa6f..36e52cace 100644
--- a/ldap/servers/slapd/daemon.c
+++ b/ldap/servers/slapd/daemon.c
@@ -426,6 +426,7 @@ disk_mon_check_diskspace(char **dirs, uint64_t threshold, uint64_t *disk_space)
void
disk_monitoring_thread(void *nothing __attribute__((unused)))
{
+ slapi_set_thread_name("disk-mon");
char **dirs = NULL;
char *dirstr = NULL;
uint64_t previous_mark = 0;
@@ -946,6 +947,7 @@ convert_pbe_des_to_aes(void)
void
accept_thread(void *vports)
{
+ slapi_set_thread_name("listener");
daemon_ports_t *ports = (daemon_ports_t *)vports;
Connection_Table *ct = the_connection_table;
PRIntn num_poll = 0;
diff --git a/ldap/servers/slapd/eventq-deprecated.c b/ldap/servers/slapd/eventq-deprecated.c
index 71a7bf8f5..b9811c905 100644
--- a/ldap/servers/slapd/eventq-deprecated.c
+++ b/ldap/servers/slapd/eventq-deprecated.c
@@ -302,6 +302,7 @@ eq_call_all(void)
static void
eq_loop(void *arg __attribute__((unused)))
{
+ slapi_set_thread_name("event-q-old");
while (eq_running) {
time_t curtime = slapi_current_utc_time();
PRIntervalTime timeout;
diff --git a/ldap/servers/slapd/eventq.c b/ldap/servers/slapd/eventq.c
index 4c39e08cf..d3a849f66 100644
--- a/ldap/servers/slapd/eventq.c
+++ b/ldap/servers/slapd/eventq.c
@@ -298,6 +298,7 @@ eq_call_all_rel(void)
static void
eq_loop_rel(void *arg __attribute__((unused)))
{
+ slapi_set_thread_name("event-q");
while (eq_rel_running) {
time_t curtime = slapi_current_rel_time_t();
int until;
diff --git a/ldap/servers/slapd/house.c b/ldap/servers/slapd/house.c
index ac1d94f26..78164d5ab 100644
--- a/ldap/servers/slapd/house.c
+++ b/ldap/servers/slapd/house.c
@@ -30,6 +30,7 @@ static pthread_cond_t housekeeping_cvar;
static void
housecleaning(void *cur_time __attribute__((unused)))
{
+ slapi_set_thread_name("housekeep");
while (!g_get_shutdown()) {
struct timespec current_time = {0};
/*
diff --git a/ldap/servers/slapd/psearch.c b/ldap/servers/slapd/psearch.c
index c60e6a8ed..df1dc0162 100644
--- a/ldap/servers/slapd/psearch.c
+++ b/ldap/servers/slapd/psearch.c
@@ -259,6 +259,7 @@ pe_ch_free(PSEQNode **pe)
static void
ps_send_results(void *arg)
{
+ slapi_set_thread_name("ps-send");
PSearch *ps = (PSearch *)arg;
PSEQNode *peq, *peqnext;
struct slapi_filter *filter = 0;
diff --git a/ldap/servers/slapd/slapi-plugin.h b/ldap/servers/slapd/slapi-plugin.h
index 5dd78004c..e1cbe5db7 100644
--- a/ldap/servers/slapd/slapi-plugin.h
+++ b/ldap/servers/slapd/slapi-plugin.h
@@ -6109,6 +6109,9 @@ int slapi_wait_condvar(Slapi_CondVar *cvar, struct timeval *timeout) __attribute
int slapi_notify_condvar(Slapi_CondVar *cvar, int notify_all);
int slapi_wait_condvar_pt(Slapi_CondVar *cvar, Slapi_Mutex *mutex, struct timeval *timeout);
+/* Set OS-level thread name for visibility in ps, top, perf, etc. (max 15 chars) */
+void slapi_set_thread_name(const char *name);
+
/**
* Creates a new read/write lock
* If prio_writer the rwlock gives priority on writers
diff --git a/ldap/servers/slapd/task.c b/ldap/servers/slapd/task.c
index 1a8be6c85..350a62814 100644
--- a/ldap/servers/slapd/task.c
+++ b/ldap/servers/slapd/task.c
@@ -1113,6 +1113,7 @@ out:
static void
task_export_thread(void *arg)
{
+ slapi_set_thread_name("export");
Slapi_PBlock *pb = (Slapi_PBlock *)arg;
// I think someone is mis-using this point to store multiple names ...
char **instance_names = NULL;
@@ -1484,6 +1485,7 @@ out:
static void
task_backup_thread(void *arg)
{
+ slapi_set_thread_name("backup");
Slapi_PBlock *pb = (Slapi_PBlock *)arg;
Slapi_Task *task = NULL;
struct slapdplugin *pb_plugin;
@@ -1635,6 +1637,7 @@ out:
static void
task_restore_thread(void *arg)
{
+ slapi_set_thread_name("restore");
Slapi_PBlock *pb = (Slapi_PBlock *)arg;
Slapi_Task *task = NULL;
struct slapdplugin *pb_plugin;
@@ -1789,6 +1792,7 @@ out:
static void
task_index_thread(void *arg)
{
+ slapi_set_thread_name("index");
Slapi_PBlock *pb = (Slapi_PBlock *)arg;
char *instance_name = NULL;
char **db2index_attrs = NULL;
@@ -2346,6 +2350,7 @@ struct task_tombstone_data
static void
task_fixup_tombstone_thread(void *arg)
{
+ slapi_set_thread_name("tombfix");
struct task_tombstone_data *task_data = arg;
Slapi_Entry **entries = NULL;
Slapi_Task *task = task_data->task;
@@ -2956,6 +2961,7 @@ compact_db_task_destructor(Slapi_Task *task)
static void
task_compact_thread(void *arg)
{
+ slapi_set_thread_name("compact");
struct task_compact_data *task_data = arg;
Slapi_Task *task = task_data->task;
Slapi_Backend *be = NULL;
diff --git a/ldap/servers/slapd/test-plugins/sampletask.c b/ldap/servers/slapd/test-plugins/sampletask.c
index 1734e2904..08c8315a4 100644
--- a/ldap/servers/slapd/test-plugins/sampletask.c
+++ b/ldap/servers/slapd/test-plugins/sampletask.c
@@ -48,6 +48,10 @@
* [...] - Sample task finished.
*/
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
#include "slapi-plugin.h"
#include "nspr.h"
@@ -88,6 +92,7 @@ task_sampletask_start(Slapi_PBlock *pb)
static void
task_sampletask_thread(void *arg)
{
+ slapi_set_thread_name("sample-task");
Slapi_Task *task = (Slapi_Task *)arg;
char *myarg = NULL;
int i, rv = 0;
diff --git a/ldap/servers/slapd/thread_data.c b/ldap/servers/slapd/thread_data.c
index fb9951d13..1e64a0d77 100644
--- a/ldap/servers/slapd/thread_data.c
+++ b/ldap/servers/slapd/thread_data.c
@@ -60,6 +60,14 @@ slapi_td_init(void)
* Wrapper Functions
*/
+/* Set OS-level thread name (visible in ps, top, perf, pstack, etc.)
+ * The name is silently truncated to 15 characters by the kernel. */
+void
+slapi_set_thread_name(const char *name)
+{
+ pthread_setname_np(pthread_self(), name);
+}
+
/* plugin list locking */
int32_t
slapi_td_set_plugin_locked()
diff --git a/ldap/servers/slapd/vattr.c b/ldap/servers/slapd/vattr.c
index 6fad473c4..b722185c2 100644
--- a/ldap/servers/slapd/vattr.c
+++ b/ldap/servers/slapd/vattr.c
@@ -140,6 +140,7 @@ vattr_cleanup()
static void
vattr_check_thread(void *arg)
{
+ slapi_set_thread_name("vattr-chk");
Slapi_Backend *be = NULL;
char *cookie = NULL;
Slapi_DN *base_sdn = NULL;
--
2.52.0

View File

@ -0,0 +1,43 @@
From dfb686b4d3542c931dcfd4b8ad641a319566d7dc Mon Sep 17 00:00:00 2001
From: progier389 <progier@redhat.com>
Date: Fri, 17 Nov 2023 12:33:38 +0100
Subject: [PATCH 13/13] Issue 5947 - Do not release the target entry in
ldbm_back_search_cleanup
Partial backport from f5bd03748 for 1.4.3.
---
ldap/servers/slapd/back-ldbm/ldbm_search.c | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_search.c b/ldap/servers/slapd/back-ldbm/ldbm_search.c
index 27301f453..768151985 100644
--- a/ldap/servers/slapd/back-ldbm/ldbm_search.c
+++ b/ldap/servers/slapd/back-ldbm/ldbm_search.c
@@ -157,7 +157,9 @@ ldbm_back_search_cleanup(Slapi_PBlock *pb,
ldbm_instance *inst;
back_search_result_set *sr = NULL;
int free_candidates = 1;
+ Slapi_Operation *op = NULL;
+ slapi_pblock_get(pb, SLAPI_OPERATION, &op);
slapi_pblock_get(pb, SLAPI_BACKEND, &be);
inst = (ldbm_instance *)be->be_instance_info;
/*
@@ -165,7 +167,13 @@ ldbm_back_search_cleanup(Slapi_PBlock *pb,
* clean it up for the following sessions.
*/
slapi_be_unset_flag(be, SLAPI_BE_FLAG_DONT_BYPASS_FILTERTEST);
- CACHE_RETURN(&inst->inst_cache, &e); /* NULL e is handled correctly */
+ if (e != operation_get_target_entry(op)) {
+ /*
+ * Target entry is released later on
+ * (in cache_return_target_entry called by op_shared_search).
+ */
+ CACHE_RETURN(&inst->inst_cache, &e); /* NULL e is handled correctly */
+ }
if (inst->inst_ref_count) {
slapi_counter_decrement(inst->inst_ref_count);
}
--
2.52.0

View File

@ -0,0 +1,39 @@
From b0b0bb3bc096cca0715fa46a7548c4f5a8bf2717 Mon Sep 17 00:00:00 2001
From: Viktor Ashirov <vashirov@redhat.com>
Date: Mon, 18 May 2026 14:16:06 +0200
Subject: [PATCH] Issue 7271 - Fix build failure on 1.4.3
Co-authored-by: James Chapman <jachapma@redhat.com>
---
ldap/servers/plugins/dna/dna.c | 1 +
ldap/servers/plugins/replication/repl5_init.c | 2 +-
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/ldap/servers/plugins/dna/dna.c b/ldap/servers/plugins/dna/dna.c
index ee4132646..e23bfbbae 100644
--- a/ldap/servers/plugins/dna/dna.c
+++ b/ldap/servers/plugins/dna/dna.c
@@ -9,6 +9,7 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
+#include <stdbool.h>
/**
diff --git a/ldap/servers/plugins/replication/repl5_init.c b/ldap/servers/plugins/replication/repl5_init.c
index dd4870746..526c759a5 100644
--- a/ldap/servers/plugins/replication/repl5_init.c
+++ b/ldap/servers/plugins/replication/repl5_init.c
@@ -850,7 +850,7 @@ multimaster_stop(Slapi_PBlock *pb __attribute__((unused)))
static int
multisupplier_pre_stop(Slapi_PBlock *pb __attribute__((unused)))
{
- if (!multisupplier_stopped_flag) {
+ if (!multimaster_stopped_flag) {
/* Shut down replication agreements which will stop all the
* replication protocol threads */
agmtlist_shutdown();
--
2.52.0

View File

@ -0,0 +1,35 @@
From 98f67447cf068377efe0a91d8e3c5ef44883fdbc Mon Sep 17 00:00:00 2001
From: Viktor Ashirov <vashirov@redhat.com>
Date: Mon, 11 Aug 2025 13:19:13 +0200
Subject: [PATCH] Issue 6929 - Compilation failure with rust-1.89 on Fedora ELN
Bug Description:
The `ValueArrayRefIter` struct has a lifetime parameter `'a`.
But in the `iter` method the return type doesn't specify the lifetime parameter.
Fix Description:
Make the lifetime explicit.
Fixes: https://github.com/389ds/389-ds-base/issues/6929
Reviewed by: @droideck (Thanks!)
---
src/slapi_r_plugin/src/value.rs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/slapi_r_plugin/src/value.rs b/src/slapi_r_plugin/src/value.rs
index 2fd35c808..fec74ac25 100644
--- a/src/slapi_r_plugin/src/value.rs
+++ b/src/slapi_r_plugin/src/value.rs
@@ -61,7 +61,7 @@ impl ValueArrayRef {
ValueArrayRef { raw_slapi_val }
}
- pub fn iter(&self) -> ValueArrayRefIter {
+ pub fn iter(&self) -> ValueArrayRefIter<'_> {
ValueArrayRefIter {
idx: 0,
va_ref: &self,
--
2.52.0

View File

@ -0,0 +1,439 @@
From b7063e97edf005e5e4553faa31c910ddab954257 Mon Sep 17 00:00:00 2001
From: Mark Reynolds <mreynolds@redhat.com>
Date: Thu, 21 May 2026 09:17:39 -0400
Subject: [PATCH] Issue 7503 - CVE-2026-9064 - Add a limit to the number
controls per operation
Description:
Security fix for CVE-2026-9064
To prevent resource starvation limit the number of controls the server will
process per operation. Reject the operation if number of controls exceeds
the limit
relates: https://github.com/389ds/389-ds-base/issues/7503
References:
- https://access.redhat.com/security/cve/cve-2026-9064
- https://bugzilla.redhat.com/show_bug.cgi?id=2480093
CI test assisted by: Cursor
Reviewed by: jchapman & tbordaz (Thanks!!)
(cherry-picked 08a31c002b946973b09e01a5f27214273691fbea)
---
.../suites/features/ldap_controls_test.py | 157 ++++++++++++++++++
ldap/schema/01core389.ldif | 5 +-
ldap/servers/slapd/control.c | 14 +-
ldap/servers/slapd/libglobs.c | 52 ++++++
ldap/servers/slapd/proto-slap.h | 2 +
ldap/servers/slapd/slap.h | 4 +
.../389-console/src/lib/server/tuning.jsx | 6 +
7 files changed, 237 insertions(+), 3 deletions(-)
create mode 100644 dirsrvtests/tests/suites/features/ldap_controls_test.py
diff --git a/dirsrvtests/tests/suites/features/ldap_controls_test.py b/dirsrvtests/tests/suites/features/ldap_controls_test.py
new file mode 100644
index 000000000..59a58b21d
--- /dev/null
+++ b/dirsrvtests/tests/suites/features/ldap_controls_test.py
@@ -0,0 +1,157 @@
+# --- BEGIN COPYRIGHT BLOCK ---
+# Copyright (C) 2025 Red Hat, Inc.
+# All rights reserved.
+#
+# License: GPL (version 3 or any later version).
+# See LICENSE for details.
+# --- END COPYRIGHT BLOCK ---
+
+import logging
+import pytest
+import ldap
+from ldap.controls import RequestControl
+from ldap.controls.readentry import PostReadControl
+from lib389.idm.user import UserAccounts, UserAccount
+from test389.topologies import topology_st
+from lib389._constants import DEFAULT_SUFFIX, DN_DM, PASSWORD
+
+pytestmark = pytest.mark.tier1
+
+log = logging.getLogger(__name__)
+
+MANAGE_DSAIT_OID = "2.16.840.1.113730.3.4.2"
+MAX_CONTROLS_PER_OP_ATTR = "nsslapd-maxcontrolsperop"
+
+def test_postread_ctrl_modify(topology_st):
+ """Test PostReadControl with LDAP modify operations.
+
+ :id: 47920dc1-7a9b-4e8d-9f3a-6c5d4e3f2a1b
+ :setup: Standalone instance
+ :steps:
+ 1. Create test user entry with initial description
+ 2. Verify initial description value
+ 3. Modify description with PostReadControl requesting 'cn' and 'description'
+ 4. Verify PostReadControl response contains both requested attributes
+ 5. Verify the entry was actually modified in the database
+ :expectedresults:
+ 1. User entry created successfully
+ 2. Initial description matches expected value
+ 3. Modify operation with control succeeds
+ 4. Control response contains both 'cn' and 'description' attributes
+ 5. Database entry reflects the modification
+ """
+ inst = topology_st.standalone
+ INITIAL_DESC = "initial description"
+ FINAL_DESC = "final description"
+
+ log.info("Create test user with initial description")
+ users = UserAccounts(inst, DEFAULT_SUFFIX)
+ user = users.create(properties={
+ 'uid': 'postread_user',
+ 'cn': 'postread_user',
+ 'sn': 'user',
+ 'uidNumber': '1000',
+ 'gidNumber': '2000',
+ 'homeDirectory': '/home/postread_user',
+ 'description': INITIAL_DESC
+ })
+
+ log.info("Verify initial description value")
+ assert user.present('description', INITIAL_DESC)
+
+ log.info("Modify description with PostReadControl")
+ pr_ctrl = PostReadControl(criticality=True, attrList=['cn', 'description'])
+ msg_id = inst.modify_ext(
+ user.dn,
+ [(ldap.MOD_REPLACE, 'description', FINAL_DESC.encode('utf-8'))],
+ serverctrls=[pr_ctrl]
+ )
+
+ log.info("Get result with response controls")
+ _, _, _, resp_ctrls = inst.result3(msg_id)
+
+ log.info("Verify PostReadControl response is properly encoded")
+ assert resp_ctrls, "Server should return PostReadControl"
+ assert resp_ctrls[0].dn == user.dn, "Control should return correct DN"
+ assert 'description' in resp_ctrls[0].entry, "Control should return description attribute"
+ assert 'cn' in resp_ctrls[0].entry, "Control should return cn attribute"
+
+ log.info("Verify entry was modified with correct value")
+ user = UserAccount(inst, user.dn)
+ assert user.get_attr_val_utf8('description') == FINAL_DESC
+ user.delete()
+
+
+def _make_request_controls(count):
+ return [
+ RequestControl(controlType=MANAGE_DSAIT_OID, criticality=False)
+ for _ in range(count)
+ ]
+
+
+def test_bind_excessive_controls(topology_st):
+ """Bind request control count is limited by nsslapd-maxcontrolsperop
+
+ :id: c3888f02-2107-4682-a50a-2189d1436233
+ :setup: Standalone instance
+ :steps:
+ 1. Read nsslapd-maxcontrolsperop from cn=config (default 10)
+ 2. Bind with one fewer control than the limit
+ 3. Bind with one more control than the limit
+ 4. Set nsslapd-maxcontrolsperop to 5
+ 5. Bind with 4 controls (new limit minus one)
+ 6. Bind with 6 controls (over new limit)
+ 7. Restore nsslapd-maxcontrolsperop and re-bind as Directory Manager
+ :expectedresults:
+ 1. Config value is 10
+ 2. Bind succeeds
+ 3. Bind fails with ldap.UNWILLING_TO_PERFORM
+ 4. Success
+ 5. Bind succeeds
+ 6. Bind fails with ldap.UNWILLING_TO_PERFORM
+ 7. Success
+ """
+ inst = topology_st.standalone
+ original_max = inst.config.get_attr_val_utf8(MAX_CONTROLS_PER_OP_ATTR)
+
+ try:
+ max_controls = int(inst.config.get_attr_val_utf8(MAX_CONTROLS_PER_OP_ATTR))
+ assert max_controls == 10
+
+ log.info("Bind with %d controls (limit %d, limit minus one)",
+ max_controls - 1, max_controls)
+ inst.simple_bind_s(DN_DM, PASSWORD,
+ serverctrls=_make_request_controls(max_controls - 1))
+
+ log.info("Bind with %d controls (limit %d plus one)",
+ max_controls + 1, max_controls)
+ with pytest.raises(ldap.UNWILLING_TO_PERFORM):
+ inst.simple_bind_s(DN_DM, PASSWORD,
+ serverctrls=_make_request_controls(max_controls + 1))
+ inst.simple_bind_s(DN_DM, PASSWORD)
+
+ lowered_max = 5
+ log.info("Set %s to %d", MAX_CONTROLS_PER_OP_ATTR, lowered_max)
+ inst.config.set(MAX_CONTROLS_PER_OP_ATTR, str(lowered_max))
+ assert int(inst.config.get_attr_val_utf8(MAX_CONTROLS_PER_OP_ATTR)) == lowered_max
+
+ log.info("Bind with %d controls (lowered limit %d, limit minus one)",
+ lowered_max - 1, lowered_max)
+ inst.simple_bind_s(DN_DM, PASSWORD,
+ serverctrls=_make_request_controls(lowered_max - 1))
+
+ log.info("Bind with %d controls (lowered limit %d plus one)",
+ lowered_max + 1, lowered_max)
+ with pytest.raises(ldap.UNWILLING_TO_PERFORM):
+ inst.simple_bind_s(DN_DM, PASSWORD,
+ serverctrls=_make_request_controls(lowered_max + 1))
+ finally:
+ log.info("Restore %s to %s", MAX_CONTROLS_PER_OP_ATTR, original_max)
+ inst.config.set(MAX_CONTROLS_PER_OP_ATTR, original_max)
+ inst.simple_bind_s(DN_DM, PASSWORD)
+
+
+if __name__ == '__main__':
+ CURRENT_FILE = __file__
+ pytest.main(["-s", "-v", CURRENT_FILE])
+
diff --git a/ldap/schema/01core389.ldif b/ldap/schema/01core389.ldif
index 4745a6ca0..d2f9db08f 100644
--- a/ldap/schema/01core389.ldif
+++ b/ldap/schema/01core389.ldif
@@ -5,7 +5,7 @@
# All rights reserved.
#
# License: GPL (version 3 or any later version).
-# See LICENSE for details.
+# See LICENSE for details.
# END COPYRIGHT BLOCK
#
#
@@ -331,6 +331,9 @@ attributeTypes: ( 2.16.840.1.113730.3.1.2390 NAME 'nsds5ReplicaKeepAliveUpdateIn
attributeTypes: ( 2.16.840.1.113730.3.1.2391 NAME 'dsEntryDN' DESC '389 Directory Server defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 NO-USER-MODIFICATION SINGLE-VALUE USAGE directoryOperation X-ORIGIN '389 Directory Server' )
attributeTypes: ( 2.16.840.1.113730.3.1.2392 NAME 'nsslapd-return-original-entrydn' DESC '389 Directory Server defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN '389 Directory Server' )
attributeTypes: ( 2.16.840.1.113730.3.1.2393 NAME 'nsslapd-auditlog-display-attrs' DESC '389 Directory Server defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN '389 Directory Server' )
+attributeTypes: ( 2.16.840.1.113730.3.1.2398 NAME 'nsslapd-haproxy-trusted-ip' DESC '389 Directory Server defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN '389 Directory Server' )
+attributeTypes: ( 2.16.840.1.113730.3.1.2400 NAME 'nsslapd-pwdPBKDF2NumIterations' DESC '389 Directory Server defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'Directory Server' )
+attributeTypes: ( 2.16.840.1.113730.3.1.2402 NAME 'nsslapd-maxcontrolsperop' DESC '389 Directory Server defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN '389 Directory Server' )
#
# objectclasses
#
diff --git a/ldap/servers/slapd/control.c b/ldap/servers/slapd/control.c
index 4fd8473be..d9b1db2fc 100644
--- a/ldap/servers/slapd/control.c
+++ b/ldap/servers/slapd/control.c
@@ -175,7 +175,7 @@ get_ldapmessage_controls_ext(
ber_tag_t tag;
/* ber_len_t is uint, cannot be -1 */
ber_len_t len = LBER_ERROR;
- int rc, maxcontrols, curcontrols;
+ int rc, maxcontrols, curcontrols, maxcontrols_per_op;
char *last;
int managedsait, pwpolicy_ctrl;
Connection *pb_conn = NULL;
@@ -251,11 +251,21 @@ get_ldapmessage_controls_ext(
return (LDAP_PROTOCOL_ERROR);
}
+ maxcontrols_per_op = config_get_maxcontrolsperop();
maxcontrols = curcontrols = 0;
for (tag = ber_first_element(ber, &len, &last);
tag != LBER_ERROR && tag != LBER_END_OF_SEQORSET;
- tag = ber_next_element(ber, &len, last)) {
+ tag = ber_next_element(ber, &len, last))
+ {
len = -1; /* reset */
+ if (curcontrols >= maxcontrols_per_op) {
+ slapi_log_err(SLAPI_LOG_ERR, "get_ldapmessage_controls_ext",
+ "Too many controls in LDAP request (max %d)\n",
+ maxcontrols_per_op);
+ rc = LDAP_UNWILLING_TO_PERFORM;
+ goto free_and_return;
+ }
+
if (curcontrols >= maxcontrols - 1) {
#define CONTROL_GRABSIZE 6
maxcontrols += CONTROL_GRABSIZE;
diff --git a/ldap/servers/slapd/libglobs.c b/ldap/servers/slapd/libglobs.c
index 99b2c5d8e..d956f6940 100644
--- a/ldap/servers/slapd/libglobs.c
+++ b/ldap/servers/slapd/libglobs.c
@@ -1323,6 +1323,11 @@ static struct config_get_and_set
(void **)&global_slapdFrontendConfig.ldapssotoken_ttl,
CONFIG_INT, NULL, SLAPD_DEFAULT_LDAPSSOTOKEN_TTL_STR, NULL},
#endif
+ {CONFIG_MAXCONTROLS_PER_OP_ATTRIBUTE, config_set_maxcontrolsperop,
+ NULL, 0,
+ (void **)&global_slapdFrontendConfig.maxcontrols_per_op,
+ CONFIG_INT, (ConfigGetFunc)config_get_maxcontrolsperop,
+ SLAPD_DEFAULT_MAXCONTROLS_PER_OP_STR, NULL},
/* End config */
};
@@ -1836,6 +1841,7 @@ FrontendConfig_init(void)
init_cn_uses_dn_syntax_in_dns = cfg->cn_uses_dn_syntax_in_dns = LDAP_OFF;
init_global_backend_local = LDAP_OFF;
cfg->maxsimplepaged_per_conn = SLAPD_DEFAULT_MAXSIMPLEPAGED_PER_CONN;
+ cfg->maxcontrols_per_op = SLAPD_DEFAULT_MAXCONTROLS_PER_OP;
cfg->maxbersize = SLAPD_DEFAULT_MAXBERSIZE;
cfg->logging_backend = slapi_ch_strdup(SLAPD_INIT_LOGGING_BACKEND_INTERNAL);
cfg->rootdn = slapi_ch_strdup(SLAPD_DEFAULT_DIRECTORY_MANAGER);
@@ -9046,6 +9052,52 @@ config_get_maxsimplepaged_per_conn()
return retVal;
}
+int
+config_set_maxcontrolsperop(const char *attrname, char *value, char *errorbuf, int apply)
+{
+ int retVal = LDAP_SUCCESS;
+ slapdFrontendConfig_t *slapdFrontendConfig = getFrontendConfig();
+ long size;
+ char *endp;
+
+ if (config_value_is_null(attrname, value, errorbuf, 0)) {
+ return LDAP_OPERATIONS_ERROR;
+ }
+
+ errno = 0;
+ size = strtol(value, &endp, 10);
+ if (*endp != '\0' || errno == ERANGE || size < 1 || size > 1000) {
+ slapi_create_errormsg(errorbuf, SLAPI_DSE_RETURNTEXT_SIZE,
+ "(%s) value (%s) is invalid, must be at least 1 and less than 1000\n",
+ attrname, value);
+ return LDAP_OPERATIONS_ERROR;
+ }
+
+ if (!apply) {
+ return retVal;
+ }
+
+ CFG_LOCK_WRITE(slapdFrontendConfig);
+
+ slapdFrontendConfig->maxcontrols_per_op = size;
+
+ CFG_UNLOCK_WRITE(slapdFrontendConfig);
+ return retVal;
+}
+
+int
+config_get_maxcontrolsperop()
+{
+ slapdFrontendConfig_t *slapdFrontendConfig = getFrontendConfig();
+ int retVal;
+
+ retVal = slapdFrontendConfig->maxcontrols_per_op;
+ if (retVal == 0) {
+ retVal = SLAPD_DEFAULT_MAXCONTROLS_PER_OP;
+ }
+ return retVal;
+}
+
int32_t
config_set_extract_pem(const char *attrname, char *value, char *errorbuf, int apply)
{
diff --git a/ldap/servers/slapd/proto-slap.h b/ldap/servers/slapd/proto-slap.h
index ab42c0e0f..2ec7c06b7 100644
--- a/ldap/servers/slapd/proto-slap.h
+++ b/ldap/servers/slapd/proto-slap.h
@@ -410,6 +410,7 @@ int32_t config_set_maxdescriptors(const char *attrname, char *value, char *error
int config_set_localuser(const char *attrname, char *value, char *errorbuf, int apply);
int config_set_maxsimplepaged_per_conn(const char *attrname, char *value, char *errorbuf, int apply);
+int config_set_maxcontrolsperop(const char *attrname, char *value, char *errorbuf, int apply);
int log_external_libs_debug_set_log_fn(void);
int log_set_backend(const char *attrname, char *value, int logtype, char *errorbuf, int apply);
@@ -609,6 +610,7 @@ int config_get_malloc_mmap_threshold(void);
#endif
int config_get_maxsimplepaged_per_conn(void);
+int config_get_maxcontrolsperop(void);
int config_get_extract_pem(void);
int32_t config_get_enable_upgrade_hash(void);
diff --git a/ldap/servers/slapd/slap.h b/ldap/servers/slapd/slap.h
index 0f67dda7d..6296de5af 100644
--- a/ldap/servers/slapd/slap.h
+++ b/ldap/servers/slapd/slap.h
@@ -290,6 +290,8 @@ upgrade_status upgrade_server(void);
#define SLAPD_DEFAULT_MAXSIMPLEPAGED_PER_CONN_STR "-1"
/* We'd like this number to be prime for the hash into the Connection table */
#define SLAPD_DEFAULT_CONNTABLESIZE 64000 /* connection table size */
+#define SLAPD_DEFAULT_MAXCONTROLS_PER_OP 10
+#define SLAPD_DEFAULT_MAXCONTROLS_PER_OP_STR "10"
#define SLAPD_DEFAULT_LDAPSSOTOKEN_TTL 3600
#define SLAPD_DEFAULT_LDAPSSOTOKEN_TTL_STR "3600"
@@ -2318,6 +2320,7 @@ typedef struct _slapdEntryPoints
#define CONFIG_CN_USES_DN_SYNTAX_IN_DNS "nsslapd-cn-uses-dn-syntax-in-dns"
#define CONFIG_MAXSIMPLEPAGED_PER_CONN_ATTRIBUTE "nsslapd-maxsimplepaged-per-conn"
+#define CONFIG_MAXCONTROLS_PER_OP_ATTRIBUTE "nsslapd-maxcontrolsperop"
#define CONFIG_LOGGING_BACKEND "nsslapd-logging-backend"
#define CONFIG_EXTRACT_PEM "nsslapd-extract-pemfiles"
@@ -2613,6 +2616,7 @@ typedef struct _slapdFrontendConfig
slapi_onoff_t cn_uses_dn_syntax_in_dns; /* indicates the cn value in dns has dn syntax */
slapi_onoff_t global_backend_lock;
slapi_int_t maxsimplepaged_per_conn; /* max simple paged results reqs handled per connection */
+ slapi_int_t maxcontrols_per_op; /* max LDAP controls allowed per operation */
slapi_onoff_t enable_nunc_stans; /* Despite the removal of NS, we have to leave the value in
* case someone was setting it.
*/
diff --git a/src/cockpit/389-console/src/lib/server/tuning.jsx b/src/cockpit/389-console/src/lib/server/tuning.jsx
index a8abe1fcc..d5a8817bc 100644
--- a/src/cockpit/389-console/src/lib/server/tuning.jsx
+++ b/src/cockpit/389-console/src/lib/server/tuning.jsx
@@ -27,6 +27,7 @@ const tuning_attrs = [
'nsslapd-connection-nocanon',
'nsslapd-enable-turbo-mode',
'nsslapd-threadnumber',
+ 'nsslapd-maxthreadsperconn',
'nsslapd-maxdescriptors',
'nsslapd-timelimit',
'nsslapd-sizelimit',
@@ -38,6 +39,7 @@ const tuning_attrs = [
'nsslapd-maxsasliosize',
'nsslapd-listen-backlog-size',
'nsslapd-max-filter-nest-level',
+ 'nsslapd-maxcontrolsperop',
'nsslapd-ndn-cache-max-size',
];
@@ -163,6 +165,7 @@ export class ServerTuning extends React.Component {
'nsslapd-connection-nocanon': connNoCannon,
'nsslapd-enable-turbo-mode': turboMode,
'nsslapd-threadnumber': attrs['nsslapd-threadnumber'][0],
+ 'nsslapd-maxthreadsperconn': attrs['nsslapd-maxthreadsperconn'][0],
'nsslapd-maxdescriptors': attrs['nsslapd-maxdescriptors'][0],
'nsslapd-timelimit': attrs['nsslapd-timelimit'][0],
'nsslapd-sizelimit': attrs['nsslapd-sizelimit'][0],
@@ -174,6 +177,7 @@ export class ServerTuning extends React.Component {
'nsslapd-maxsasliosize': attrs['nsslapd-maxsasliosize'][0],
'nsslapd-listen-backlog-size': attrs['nsslapd-listen-backlog-size'][0],
'nsslapd-max-filter-nest-level': attrs['nsslapd-max-filter-nest-level'][0],
+ 'nsslapd-maxcontrolsperop': attrs['nsslapd-maxcontrolsperop'][0],
'nsslapd-ndn-cache-max-size': attrs['nsslapd-ndn-cache-max-size'][0],
// Record original values
'_nsslapd-ndn-cache-enabled': ndnEnabled,
@@ -181,6 +185,7 @@ export class ServerTuning extends React.Component {
'_nsslapd-connection-nocanon': connNoCannon,
'_nsslapd-enable-turbo-mode': turboMode,
'_nsslapd-threadnumber': attrs['nsslapd-threadnumber'][0],
+ '_nsslapd-maxthreadsperconn': attrs['nsslapd-maxthreadsperconn'][0],
'_nsslapd-maxdescriptors': attrs['nsslapd-maxdescriptors'][0],
'_nsslapd-timelimit': attrs['nsslapd-timelimit'][0],
'_nsslapd-sizelimit': attrs['nsslapd-sizelimit'][0],
@@ -192,6 +197,7 @@ export class ServerTuning extends React.Component {
'_nsslapd-maxsasliosize': attrs['nsslapd-maxsasliosize'][0],
'_nsslapd-listen-backlog-size': attrs['nsslapd-listen-backlog-size'][0],
'_nsslapd-max-filter-nest-level': attrs['nsslapd-max-filter-nest-level'][0],
+ '_nsslapd-maxcontrolsperop': attrs['nsslapd-maxcontrolsperop'][0],
'_nsslapd-ndn-cache-max-size': attrs['nsslapd-ndn-cache-max-size'][0],
}, this.props.enableTree());
})
--
2.52.0

View File

@ -52,7 +52,7 @@ ExcludeArch: i686
Summary: 389 Directory Server (base)
Name: 389-ds-base
Version: 1.4.3.39
Release: %{?relprefix}23%{?prerel}%{?dist}
Release: %{?relprefix}24%{?prerel}%{?dist}
License: GPL-3.0-or-later WITH GPL-3.0-389-ds-base-exception AND (0BSD OR Apache-2.0 OR MIT) AND (Apache-2.0 OR Apache-2.0 WITH LLVM-exception OR MIT) AND (Apache-2.0 OR BSD-2-Clause OR MIT) AND (Apache-2.0 OR BSL-1.0) AND (Apache-2.0 OR LGPL-2.1-or-later OR MIT) AND (Apache-2.0 OR MIT OR Zlib) AND (Apache-2.0 OR MIT) AND (MIT OR Apache-2.0) AND Unicode-3.0 AND (MIT OR Unlicense) AND Apache-2.0 AND BSD-3-Clause AND MIT AND MPL-2.0
URL: https://www.port389.org
Group: System Environment/Daemons
@ -385,7 +385,22 @@ Patch84: 0084-Issue-7223-Use-lexicographical-order-for-ancestorid.patch
Patch85: 0085-Issue-7223-Remove-integerOrderingMatch-requirement-f.patch
Patch86: 0086-Security-fix-for-CVE-2025-14905.patch
Patch87: 0087-Issue-7096-During-replication-online-total-init-the-.patch
Patch88: 0088-Issue-7366-Memory-leaks-in-syncrepl-plugin-during-pe.patch
Patch89: 0089-Issue-7126-WARN-keys2idl-received-NULL-idl-from-inde.patch
Patch90: 0090-Issue-7124-2nd-BDB-cursor-race-condition-with-transa.patch
Patch91: 0091-Issue-7124-BDB-cursor-race-condition-with-transactio.patch
Patch92: 0092-Issue-7380-Internal-op-with-negative-wtime-and-large.patch
Patch93: 0093-Issue-7152-ns-slapd-fails-to-shutdown-when-deferred-.patch
Patch94: 0094-Issue-7271-plugins-that-create-threads-need-to-updat.patch
Patch95: 0095-Issue-7271-implement-a-pre-close-plugin-function.patch
Patch96: 0096-Issue-7271-Add-new-plugin-pre-close-function-check-t.patch
Patch97: 0097-Issue-7304-retrocl-should-not-cache-DN.patch
Patch98: 0098-Issue-7291-Crash-when-configuring-a-replica-with-an-.patch
Patch99: 0099-Issue-7300-RFE-Add-OS-level-thread-names-to-all-serv.patch
Patch100: 0100-Issue-5947-Do-not-release-the-target-entry-in-ldbm_b.patch
Patch101: 0101-Issue-7271-fixed-build-error-in-repl5_init.patch
Patch102: 0102-Issue-6929-Compilation-failure-with-rust-1.89.patch
Patch103: 0103-Issue-7503-CVE-2026-9064-Add-a-limit-to-the-number-c.patch
#Patch100: cargo.patch
@ -1045,6 +1060,20 @@ exit 0
%doc README.md
%changelog
* Wed May 13 2026 Arun Bansal <arbansal@redhat.com> - 1.4.3.39-24
- Bump version to 1.4.3.39-24
- Resolves: RHEL-170278 - Memory leaks in syncrepl plugin during persistent search operations [rhel-8.10.z]
- Resolves: RHEL-163375 - WARN - keys2idl - received NULL idl from index_read_ext_allids
- Resolves: RHEL-159306 - ns-slapd crash in libdb possible memory corruption [rhel-8.10.z]
- Resolves: RHEL-170284 - access log - suspicious wtime optime negative and large values in internal op [rhel-8.10.z]
- Resolves: RHEL-170507 - ns-slapd fails to shutdown when deferred memberof update is in progress [rhel-8.10.z]
- Resolves: RHEL-170509 - Crash in trim_changelog() during the Retro Changelog trimming [rhel-8.10.z]
- Resolves: RHEL-170514 - Possible memory leak when using the Retro Changelog plugin [rhel-8.10.z]
- Resolves: RHEL-170512 - Crash in replica_config_add when manually configuring a replica with an incorrect nsds5ReplicaRoot [rhel-8.10.z]
- Resolves: RHEL-174523 - [RFE] Add OS-level thread names to all server threads [rhel-8.10.z]
- Resolves: RHEL-170483 - test_vlv_recreation_reindex fails on LMDB [rhel-8.10.z]
- Resolves: RHEL-178076 - CVE-2026-9064 389-ds:1.4/389-ds-base: unbounded LDAP controls count in get_ldapmessage_controls_ext() causes CPU and heap amplification (remote DoS) [rhel-8.10.z]
* Wed Mar 04 2026 Arun Bansal <arbansal@redhat.com> - 1.4.3.39-23
- Resolves: RHEL-137074 - CVE-2025-14905 389-ds:1.4/389-ds-base: 389-ds-base: Remote Code Execution and Denial of Service via heap buffer overflow [rhel-8.10.z]
- Resolves: RHEL-152098 - Scalability issue of replication online initialization with large database [rhel-8.10.z]