Resolves: RHEL-101727 - The numSubordinates value is not matching the number of direct children.

- Resolves: RHEL-101783 - RHDS12: Web console doesn't show Server Version
- Resolves: RHEL-109018 - Allow Uniqueness plugin to search uniqueness attributes using custom matching rules
- Resolves: RHEL-111224 - Error showing local password policy on web UI
- Resolves: RHEL-112675 - Statistics about index lookup report a wrong duration
- Resolves: RHEL-112689 - Crash if repl keep alive entry can not be created
- Resolves: RHEL-112722 - Exception thrown by dsconf instance repl get_ruv
- Resolves: RHEL-113969 - AddressSanitizer: memory leak in memberof_add_memberof_attr
This commit is contained in:
Viktor Ashirov 2025-09-18 14:18:51 +02:00
parent 23917c9198
commit 4d8c2a143b
7 changed files with 2916 additions and 0 deletions

View File

@ -0,0 +1,268 @@
From 7423f0a0b90bac39a23b5ce54a1c61439d0ebcb6 Mon Sep 17 00:00:00 2001
From: Simon Pichugin <spichugi@redhat.com>
Date: Tue, 19 Aug 2025 16:10:09 -0700
Subject: [PATCH] Issue 6940 - dsconf monitor server fails with ldapi:// due to
absent server ID (#6941)
Description: The dsconf monitor server command fails when using ldapi://
protocol because the server ID is not set, preventing PID retrieval from
defaults.inf. This causes the Web console to fail displaying the "Server
Version" field and potentially other CLI/WebUI issues.
The fix attempts to derive the server ID from the LDAPI socket path when
not explicitly provided. This covers the common case where the socket name
contains the instance name (e.g., slapd-instance.socket).
If that's not possible, it also attempts to derive the server ID from the
nsslapd-instancedir configuration attribute. The derived server ID
is validated against actual system instances to ensure it exists.
Note that socket names can vary and nsslapd-instancedir can be changed.
This is a best-effort approach for the common naming pattern.
Also fixes the LDAPI socket path extraction which was incorrectly using
offset 9 instead of 8 for ldapi:// URIs.
The monitor command now handles missing PIDs gracefully, returning zero
values for process-specific stats instead of failing completely.
Fixes: https://github.com/389ds/389-ds-base/issues/6940
Reviewed by: @vashirov, @mreynolds389 (Thanks!!)
---
src/lib389/lib389/__init__.py | 93 +++++++++++++++++++++++++++---
src/lib389/lib389/cli_base/dsrc.py | 4 +-
src/lib389/lib389/monitor.py | 50 ++++++++++++----
3 files changed, 124 insertions(+), 23 deletions(-)
diff --git a/src/lib389/lib389/__init__.py b/src/lib389/lib389/__init__.py
index 0ddfca8ae..23a20739f 100644
--- a/src/lib389/lib389/__init__.py
+++ b/src/lib389/lib389/__init__.py
@@ -17,7 +17,7 @@
import sys
import os
-from urllib.parse import urlparse
+from urllib.parse import urlparse, unquote
import stat
import pwd
import grp
@@ -67,7 +67,8 @@ from lib389.utils import (
get_default_db_lib,
selinux_present,
selinux_label_port,
- get_user_is_root)
+ get_user_is_root,
+ get_instance_list)
from lib389.paths import Paths
from lib389.nss_ssl import NssSsl
from lib389.tasks import BackupTask, RestoreTask, Task
@@ -249,6 +250,57 @@ class DirSrv(SimpleLDAPObject, object):
self.dbdir = self.ds_paths.db_dir
self.changelogdir = os.path.join(os.path.dirname(self.dbdir), DEFAULT_CHANGELOG_DB)
+ def _extract_serverid_from_string(self, text):
+ """Extract serverid from a string containing 'slapd-<serverid>' pattern.
+ Returns the serverid or None if not found or validation fails.
+ Only attempts derivation if serverid is currently None.
+ """
+ if getattr(self, 'serverid', None) is not None:
+ return None
+ if not text:
+ return None
+
+ # Use regex to extract serverid from "slapd-<serverid>" or "slapd-<serverid>.socket"
+ match = re.search(r'slapd-([A-Za-z0-9._-]+?)(?:\.socket)?(?:$|/)', text)
+ if not match:
+ return None
+ candidate = match.group(1)
+
+ self.serverid = candidate
+ try:
+ insts = get_instance_list()
+ except Exception:
+ self.serverid = None
+ return None
+ if f'slapd-{candidate}' in insts or candidate in insts:
+ return candidate
+ # restore original and report failure
+ self.serverid = None
+ return None
+
+ def _derive_serverid_from_ldapi(self):
+ """Attempt to derive serverid from an LDAPI socket path or URI and
+ verify it exists on the system. Returns the serverid or None.
+ """
+ socket_path = None
+ if hasattr(self, 'ldapi_socket') and self.ldapi_socket:
+ socket_path = unquote(self.ldapi_socket)
+ elif hasattr(self, 'ldapuri') and isinstance(self.ldapuri, str) and self.ldapuri.startswith('ldapi://'):
+ socket_path = unquote(self.ldapuri[len('ldapi://'):])
+
+ return self._extract_serverid_from_string(socket_path)
+
+ def _derive_serverid_from_instancedir(self):
+ """Extract serverid from nsslapd-instancedir path like '/usr/lib64/dirsrv/slapd-<serverid>'"""
+ try:
+ from lib389.config import Config
+ config = Config(self)
+ instancedir = config.get_attr_val_utf8_l("nsslapd-instancedir")
+ except Exception:
+ return None
+
+ return self._extract_serverid_from_string(instancedir)
+
def rebind(self):
"""Reconnect to the DS
@@ -528,6 +580,15 @@ class DirSrv(SimpleLDAPObject, object):
self.ldapi_autobind = args.get(SER_LDAPI_AUTOBIND, 'off')
self.isLocal = True
self.log.debug("Allocate %s with %s", self.__class__, self.ldapi_socket)
+ elif self.ldapuri is not None and isinstance(self.ldapuri, str) and self.ldapuri.startswith('ldapi://'):
+ # Try to learn serverid from ldapi uri
+ try:
+ self.ldapi_enabled = 'on'
+ self.ldapi_socket = unquote(self.ldapuri[len('ldapi://'):])
+ self.ldapi_autobind = args.get(SER_LDAPI_AUTOBIND, 'off')
+ self.isLocal = True
+ except Exception:
+ pass
# Settings from args of server attributes
self.strict_hostname = args.get(SER_STRICT_HOSTNAME_CHECKING, False)
if self.strict_hostname is True:
@@ -548,9 +609,16 @@ class DirSrv(SimpleLDAPObject, object):
self.log.debug("Allocate %s with %s:%s", self.__class__, self.host, (self.sslport or self.port))
- if SER_SERVERID_PROP in args:
- self.ds_paths = Paths(serverid=args[SER_SERVERID_PROP], instance=self, local=self.isLocal)
+ # Try to determine serverid if not provided
+ if SER_SERVERID_PROP in args and args.get(SER_SERVERID_PROP) is not None:
self.serverid = args.get(SER_SERVERID_PROP, None)
+ elif getattr(self, 'serverid', None) is None and self.isLocal:
+ sid = self._derive_serverid_from_ldapi()
+ if sid:
+ self.serverid = sid
+
+ if getattr(self, 'serverid', None):
+ self.ds_paths = Paths(serverid=self.serverid, instance=self, local=self.isLocal)
else:
self.ds_paths = Paths(instance=self, local=self.isLocal)
@@ -989,6 +1057,17 @@ class DirSrv(SimpleLDAPObject, object):
self.__initPart2()
self.state = DIRSRV_STATE_ONLINE
# Now that we're online, some of our methods may try to query the version online.
+
+ # After transitioning online, attempt to derive serverid if still unknown.
+ # If we find it, refresh ds_paths and rerun __initPart2
+ if getattr(self, 'serverid', None) is None and self.isLocal:
+ sid = self._derive_serverid_from_instancedir()
+ if sid:
+ self.serverid = sid
+ # Reinitialize paths with the new serverid
+ self.ds_paths = Paths(serverid=self.serverid, instance=self, local=self.isLocal)
+ if not connOnly:
+ self.__initPart2()
self.__add_brookers__()
def close(self):
@@ -3537,8 +3616,4 @@ class DirSrv(SimpleLDAPObject, object):
"""
Get the pid of the running server
"""
- pid = pid_from_file(self.pid_file())
- if pid == 0 or pid is None:
- return 0
- else:
- return pid
+ return pid_from_file(self.pid_file())
diff --git a/src/lib389/lib389/cli_base/dsrc.py b/src/lib389/lib389/cli_base/dsrc.py
index 84567b990..498228ce0 100644
--- a/src/lib389/lib389/cli_base/dsrc.py
+++ b/src/lib389/lib389/cli_base/dsrc.py
@@ -56,7 +56,7 @@ def dsrc_arg_concat(args, dsrc_inst):
new_dsrc_inst['args'][SER_ROOT_DN] = new_dsrc_inst['binddn']
if new_dsrc_inst['uri'][0:8] == 'ldapi://':
new_dsrc_inst['args'][SER_LDAPI_ENABLED] = "on"
- new_dsrc_inst['args'][SER_LDAPI_SOCKET] = new_dsrc_inst['uri'][9:]
+ new_dsrc_inst['args'][SER_LDAPI_SOCKET] = new_dsrc_inst['uri'][8:]
new_dsrc_inst['args'][SER_LDAPI_AUTOBIND] = "on"
# Make new
@@ -170,7 +170,7 @@ def dsrc_to_ldap(path, instance_name, log):
dsrc_inst['args'][SER_ROOT_DN] = dsrc_inst['binddn']
if dsrc_inst['uri'][0:8] == 'ldapi://':
dsrc_inst['args'][SER_LDAPI_ENABLED] = "on"
- dsrc_inst['args'][SER_LDAPI_SOCKET] = dsrc_inst['uri'][9:]
+ dsrc_inst['args'][SER_LDAPI_SOCKET] = dsrc_inst['uri'][8:]
dsrc_inst['args'][SER_LDAPI_AUTOBIND] = "on"
# Return the dict.
diff --git a/src/lib389/lib389/monitor.py b/src/lib389/lib389/monitor.py
index 27b99a7e3..bf3e1df76 100644
--- a/src/lib389/lib389/monitor.py
+++ b/src/lib389/lib389/monitor.py
@@ -92,21 +92,47 @@ class Monitor(DSLdapObject):
Get CPU and memory stats
"""
stats = {}
- pid = self._instance.get_pid()
+ try:
+ pid = self._instance.get_pid()
+ except Exception:
+ pid = None
total_mem = psutil.virtual_memory()[0]
- p = psutil.Process(pid)
- memory_stats = p.memory_full_info()
- # Get memory & CPU stats
+ # Always include total system memory
stats['total_mem'] = [str(total_mem)]
- stats['rss'] = [str(memory_stats[0])]
- stats['vms'] = [str(memory_stats[1])]
- stats['swap'] = [str(memory_stats[9])]
- stats['mem_rss_percent'] = [str(round(p.memory_percent("rss")))]
- stats['mem_vms_percent'] = [str(round(p.memory_percent("vms")))]
- stats['mem_swap_percent'] = [str(round(p.memory_percent("swap")))]
- stats['total_threads'] = [str(p.num_threads())]
- stats['cpu_usage'] = [str(round(p.cpu_percent(interval=0.1)))]
+
+ # Process-specific stats - only if process is running (pid is not None)
+ if pid is not None:
+ try:
+ p = psutil.Process(pid)
+ memory_stats = p.memory_full_info()
+
+ # Get memory & CPU stats
+ stats['rss'] = [str(memory_stats[0])]
+ stats['vms'] = [str(memory_stats[1])]
+ stats['swap'] = [str(memory_stats[9])]
+ stats['mem_rss_percent'] = [str(round(p.memory_percent("rss")))]
+ stats['mem_vms_percent'] = [str(round(p.memory_percent("vms")))]
+ stats['mem_swap_percent'] = [str(round(p.memory_percent("swap")))]
+ stats['total_threads'] = [str(p.num_threads())]
+ stats['cpu_usage'] = [str(round(p.cpu_percent(interval=0.1)))]
+ except (psutil.NoSuchProcess, psutil.AccessDenied):
+ # Process exists in PID file but is not accessible or doesn't exist
+ pid = None
+
+ # If no valid PID, provide zero values for process stats
+ if pid is None:
+ stats['rss'] = ['0']
+ stats['vms'] = ['0']
+ stats['swap'] = ['0']
+ stats['mem_rss_percent'] = ['0']
+ stats['mem_vms_percent'] = ['0']
+ stats['mem_swap_percent'] = ['0']
+ stats['total_threads'] = ['0']
+ stats['cpu_usage'] = ['0']
+ stats['server_status'] = ['PID unavailable']
+ else:
+ stats['server_status'] = ['Server running']
# Connections to DS
if self._instance.port == "0":
--
2.49.0

View File

@ -0,0 +1,569 @@
From 594333d1a6a8bba4d485b8227c4474e4ca2aa6a4 Mon Sep 17 00:00:00 2001
From: Simon Pichugin <spichugi@redhat.com>
Date: Tue, 19 Aug 2025 14:30:15 -0700
Subject: [PATCH] Issue 6936 - Make user/subtree policy creation idempotent
(#6937)
Description: Correct the CLI mapping typo to use 'nsslapd-pwpolicy-local',
rework subtree policy detection to validate CoS templates and add user-policy detection.
Make user/subtree policy creation idempotent via ensure_state, and improve deletion
logic to distinguish subtree vs user policies and fail if none exist.
Add a test suite (pwp_history_local_override_test.py) exercising global-only and local-only
history enforcement, local overriding global counts, immediate effect of dsconf updates,
and fallback to global after removing a user policy, ensuring reliable behavior
and preventing regressions.
Fixes: https://github.com/389ds/389-ds-base/issues/6936
Reviewed by: @mreynolds389 (Thanks!)
---
.../pwp_history_local_override_test.py | 351 ++++++++++++++++++
src/lib389/lib389/cli_conf/pwpolicy.py | 4 +-
src/lib389/lib389/pwpolicy.py | 107 ++++--
3 files changed, 424 insertions(+), 38 deletions(-)
create mode 100644 dirsrvtests/tests/suites/password/pwp_history_local_override_test.py
diff --git a/dirsrvtests/tests/suites/password/pwp_history_local_override_test.py b/dirsrvtests/tests/suites/password/pwp_history_local_override_test.py
new file mode 100644
index 000000000..6d72725fa
--- /dev/null
+++ b/dirsrvtests/tests/suites/password/pwp_history_local_override_test.py
@@ -0,0 +1,351 @@
+# --- 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 os
+import time
+import ldap
+import pytest
+import subprocess
+import logging
+
+from lib389._constants import DEFAULT_SUFFIX, DN_DM, PASSWORD, DN_CONFIG
+from lib389.topologies import topology_st
+from lib389.idm.user import UserAccounts
+from lib389.idm.domain import Domain
+from lib389.pwpolicy import PwPolicyManager
+
+pytestmark = pytest.mark.tier1
+
+DEBUGGING = os.getenv("DEBUGGING", default=False)
+if DEBUGGING:
+ logging.getLogger(__name__).setLevel(logging.DEBUG)
+else:
+ logging.getLogger(__name__).setLevel(logging.INFO)
+log = logging.getLogger(__name__)
+
+OU_DN = f"ou=People,{DEFAULT_SUFFIX}"
+USER_ACI = '(targetattr="userpassword || passwordHistory")(version 3.0; acl "pwp test"; allow (all) userdn="ldap:///self";)'
+
+
+@pytest.fixture(autouse=True, scope="function")
+def restore_global_policy(topology_st, request):
+ """Snapshot and restore global password policy around each test in this file."""
+ inst = topology_st.standalone
+ inst.simple_bind_s(DN_DM, PASSWORD)
+
+ attrs = [
+ 'nsslapd-pwpolicy-local',
+ 'nsslapd-pwpolicy-inherit-global',
+ 'passwordHistory',
+ 'passwordInHistory',
+ 'passwordChange',
+ ]
+
+ entry = inst.getEntry(DN_CONFIG, ldap.SCOPE_BASE, '(objectClass=*)', attrs)
+ saved = {attr: entry.getValue(attr) for attr in attrs}
+
+ def fin():
+ inst.simple_bind_s(DN_DM, PASSWORD)
+ for attr, value in saved.items():
+ inst.config.replace(attr, value)
+
+ request.addfinalizer(fin)
+
+
+@pytest.fixture(scope="function")
+def setup_entries(topology_st, request):
+ """Create test OU and user, and install an ACI for self password changes."""
+
+ inst = topology_st.standalone
+
+ suffix = Domain(inst, DEFAULT_SUFFIX)
+ suffix.add('aci', USER_ACI)
+
+ users = UserAccounts(inst, DEFAULT_SUFFIX)
+ try:
+ user = users.create_test_user(uid=1)
+ except ldap.ALREADY_EXISTS:
+ user = users.get("test_user_1")
+
+ def fin():
+ pwp = PwPolicyManager(inst)
+ try:
+ pwp.delete_local_policy(OU_DN)
+ except Exception as e:
+ if "No password policy" in str(e):
+ pass
+ else:
+ raise e
+ try:
+ pwp.delete_local_policy(user.dn)
+ except Exception as e:
+ if "No password policy" in str(e):
+ pass
+ else:
+ raise e
+ suffix.remove('aci', USER_ACI)
+ request.addfinalizer(fin)
+
+ return user
+
+
+def set_user_password(inst, user, new_password, bind_as_user_password=None, expect_violation=False):
+ if bind_as_user_password is not None:
+ user.rebind(bind_as_user_password)
+ try:
+ user.reset_password(new_password)
+ if expect_violation:
+ pytest.fail("Password change unexpectedly succeeded")
+ except ldap.CONSTRAINT_VIOLATION:
+ if not expect_violation:
+ pytest.fail("Password change unexpectedly rejected with CONSTRAINT_VIOLATION")
+ finally:
+ inst.simple_bind_s(DN_DM, PASSWORD)
+ time.sleep(1)
+
+
+def set_global_history(inst, enabled: bool, count: int, inherit_global: str = 'on'):
+ inst.simple_bind_s(DN_DM, PASSWORD)
+ inst.config.replace('nsslapd-pwpolicy-local', 'on')
+ inst.config.replace('nsslapd-pwpolicy-inherit-global', inherit_global)
+ inst.config.replace('passwordHistory', 'on' if enabled else 'off')
+ inst.config.replace('passwordInHistory', str(count))
+ inst.config.replace('passwordChange', 'on')
+ time.sleep(1)
+
+
+def ensure_local_subtree_policy(inst, count: int, track_update_time: str = 'on'):
+ pwp = PwPolicyManager(inst)
+ pwp.create_subtree_policy(OU_DN, {
+ 'passwordChange': 'on',
+ 'passwordHistory': 'on',
+ 'passwordInHistory': str(count),
+ 'passwordTrackUpdateTime': track_update_time,
+ })
+ time.sleep(1)
+
+
+def set_local_history_via_cli(inst, count: int):
+ sbin_dir = inst.get_sbin_dir()
+ inst_name = inst.serverid
+ cmd = [f"{sbin_dir}/dsconf", inst_name, "localpwp", "set", f"--pwdhistorycount={count}", OU_DN]
+ rc = subprocess.call(cmd)
+ assert rc == 0, f"dsconf command failed rc={rc}: {' '.join(cmd)}"
+ time.sleep(1)
+
+
+def test_global_history_only_enforced(topology_st, setup_entries):
+ """Global-only history enforcement with count 2
+
+ :id: 3d8cf35b-4a33-4587-9814-ebe18b7a1f92
+ :setup: Standalone instance, test OU and user, ACI for self password changes
+ :steps:
+ 1. Remove local policies
+ 2. Set global policy: passwordHistory=on, passwordInHistory=2
+ 3. Set password to Alpha1, then change to Alpha2 and Alpha3 as the user
+ 4. Attempt to change to Alpha1 and Alpha2
+ 5. Attempt to change to Alpha4
+ :expectedresults:
+ 1. Success
+ 2. Success
+ 3. Success
+ 4. Changes to Welcome1 and Welcome2 are rejected with CONSTRAINT_VIOLATION
+ 5. Change to Welcome4 is accepted
+ """
+ inst = topology_st.standalone
+ inst.simple_bind_s(DN_DM, PASSWORD)
+
+ set_global_history(inst, enabled=True, count=2)
+
+ user = setup_entries
+ user.reset_password('Alpha1')
+ set_user_password(inst, user, 'Alpha2', bind_as_user_password='Alpha1')
+ set_user_password(inst, user, 'Alpha3', bind_as_user_password='Alpha2')
+
+ # Within last 2
+ set_user_password(inst, user, 'Alpha2', bind_as_user_password='Alpha3', expect_violation=True)
+ set_user_password(inst, user, 'Alpha1', bind_as_user_password='Alpha3', expect_violation=True)
+
+ # New password should be allowed
+ set_user_password(inst, user, 'Alpha4', bind_as_user_password='Alpha3', expect_violation=False)
+
+
+def test_local_overrides_global_history(topology_st, setup_entries):
+ """Local subtree policy (history=3) overrides global (history=1)
+
+ :id: 97c22f56-5ea6-40c1-8d8c-1cece3bf46fd
+ :setup: Standalone instance, test OU and user
+ :steps:
+ 1. Set global policy passwordInHistory=1
+ 2. Create local subtree policy on the OU with passwordInHistory=3
+ 3. Set password to Bravo1, then change to Bravo2 and Bravo3 as the user
+ 4. Attempt to change to Bravo1
+ 5. Attempt to change to Bravo5
+ :expectedresults:
+ 1. Success
+ 2. Success
+ 3. Success
+ 4. Change to Welcome1 is rejected (local policy wins)
+ 5. Change to Welcome5 is accepted
+ """
+ inst = topology_st.standalone
+ inst.simple_bind_s(DN_DM, PASSWORD)
+
+ set_global_history(inst, enabled=True, count=1, inherit_global='on')
+
+ ensure_local_subtree_policy(inst, count=3)
+
+ user = setup_entries
+ user.reset_password('Bravo1')
+ set_user_password(inst, user, 'Bravo2', bind_as_user_password='Bravo1')
+ set_user_password(inst, user, 'Bravo3', bind_as_user_password='Bravo2')
+
+ # Third prior should be rejected under local policy count=3
+ set_user_password(inst, user, 'Bravo1', bind_as_user_password='Bravo3', expect_violation=True)
+
+ # New password allowed
+ set_user_password(inst, user, 'Bravo5', bind_as_user_password='Bravo3', expect_violation=False)
+
+
+def test_change_local_history_via_cli_affects_enforcement(topology_st, setup_entries):
+ """Changing local policy via CLI is enforced immediately
+
+ :id: 5a6d0d14-4009-4bad-86e1-cde5000c43dc
+ :setup: Standalone instance, test OU and user, dsconf available
+ :steps:
+ 1. Ensure local subtree policy passwordInHistory=3
+ 2. Set password to Charlie1, then change to Charlie2 and Charlie3 as the user
+ 3. Attempt to change to Charlie1 (within last 3)
+ 4. Run: dsconf <inst> localpwp set --pwdhistorycount=1 "ou=product testing,<suffix>"
+ 5. Attempt to change to Charlie1 again
+ :expectedresults:
+ 1. Success
+ 2. Success
+ 3. Change to Welcome1 is rejected
+ 4. CLI command succeeds
+ 5. Change to Welcome1 now succeeds (only last 1 is disallowed)
+ """
+ inst = topology_st.standalone
+ inst.simple_bind_s(DN_DM, PASSWORD)
+
+ ensure_local_subtree_policy(inst, count=3)
+
+ user = setup_entries
+ user.reset_password('Charlie1')
+ set_user_password(inst, user, 'Charlie2', bind_as_user_password='Charlie1', expect_violation=False)
+ set_user_password(inst, user, 'Charlie3', bind_as_user_password='Charlie2', expect_violation=False)
+
+ # With count=3, Welcome1 is within history
+ set_user_password(inst, user, 'Charlie1', bind_as_user_password='Charlie3', expect_violation=True)
+
+ # Reduce local count to 1 via CLI to exercise CLI mapping and updated code
+ set_local_history_via_cli(inst, count=1)
+
+ # Now Welcome1 should be allowed
+ set_user_password(inst, user, 'Charlie1', bind_as_user_password='Charlie3', expect_violation=False)
+
+
+def test_history_local_only_enforced(topology_st, setup_entries):
+ """Local-only history enforcement with count 3
+
+ :id: af6ff34d-ac94-4108-a7b6-2b589c960154
+ :setup: Standalone instance, test OU and user
+ :steps:
+ 1. Disable global password history (passwordHistory=off, passwordInHistory=0, inherit off)
+ 2. Ensure local subtree policy with passwordInHistory=3
+ 3. Set password to Delta1, then change to Delta2 and Delta3 as the user
+ 4. Attempt to change to Delta1
+ 5. Attempt to change to Delta5
+ 6. Change once more to Delta6, then change to Delta1
+ :expectedresults:
+ 1. Success
+ 2. Success
+ 3. Success
+ 4. Change to Welcome1 is rejected (within last 3)
+ 5. Change to Welcome5 is accepted
+ 6. Welcome1 is now older than the last 3 and is accepted
+ """
+ inst = topology_st.standalone
+ inst.simple_bind_s(DN_DM, PASSWORD)
+
+ set_global_history(inst, enabled=False, count=0, inherit_global='off')
+
+ ensure_local_subtree_policy(inst, count=3)
+
+ user = setup_entries
+ user.reset_password('Delta1')
+ set_user_password(inst, user, 'Delta2', bind_as_user_password='Delta1')
+ set_user_password(inst, user, 'Delta3', bind_as_user_password='Delta2')
+
+ # Within last 2
+ set_user_password(inst, user, 'Delta1', bind_as_user_password='Delta3', expect_violation=True)
+
+ # New password allowed
+ set_user_password(inst, user, 'Delta5', bind_as_user_password='Delta3', expect_violation=False)
+
+ # Now Welcome1 is older than last 2 after one more change
+ set_user_password(inst, user, 'Delta6', bind_as_user_password='Delta5', expect_violation=False)
+ set_user_password(inst, user, 'Delta1', bind_as_user_password='Delta6', expect_violation=False)
+
+
+def test_user_policy_detection_and_enforcement(topology_st, setup_entries):
+ """User local policy is detected and enforced; removal falls back to global policy
+
+ :id: 2213126a-1f47-468c-8337-0d2ee5d2d585
+ :setup: Standalone instance, test OU and user
+ :steps:
+ 1. Set global policy passwordInHistory=1
+ 2. Create a user local password policy on the user with passwordInHistory=3
+ 3. Verify is_user_policy(USER_DN) is True
+ 4. Set password to Echo1, then change to Echo2 and Echo3 as the user
+ 5. Attempt to change to Echo1 (within last 3)
+ 6. Delete the user local policy
+ 7. Verify is_user_policy(USER_DN) is False
+ 8. Attempt to change to Echo1 again (now only last 1 disallowed by global)
+ :expectedresults:
+ 1. Success
+ 2. Success
+ 3. is_user_policy returns True
+ 4. Success
+ 5. Change to Welcome1 is rejected
+ 6. Success
+ 7. is_user_policy returns False
+ 8. Change to Welcome1 succeeds (two back is allowed by global=1)
+ """
+ inst = topology_st.standalone
+ inst.simple_bind_s(DN_DM, PASSWORD)
+
+ set_global_history(inst, enabled=True, count=1, inherit_global='on')
+
+ pwp = PwPolicyManager(inst)
+ user = setup_entries
+ pwp.create_user_policy(user.dn, {
+ 'passwordChange': 'on',
+ 'passwordHistory': 'on',
+ 'passwordInHistory': '3',
+ })
+
+ assert pwp.is_user_policy(user.dn) is True
+
+ user.reset_password('Echo1')
+ set_user_password(inst, user, 'Echo2', bind_as_user_password='Echo1', expect_violation=False)
+ set_user_password(inst, user, 'Echo3', bind_as_user_password='Echo2', expect_violation=False)
+ set_user_password(inst, user, 'Echo1', bind_as_user_password='Echo3', expect_violation=True)
+
+ pwp.delete_local_policy(user.dn)
+ assert pwp.is_user_policy(user.dn) is False
+
+ # With only global=1, Echo1 (two back) is allowed
+ set_user_password(inst, user, 'Echo1', bind_as_user_password='Echo3', expect_violation=False)
+
+
+if __name__ == '__main__':
+ # Run isolated
+ # -s for DEBUG mode
+ CURRENT_FILE = os.path.realpath(__file__)
+ pytest.main("-s %s" % CURRENT_FILE)
diff --git a/src/lib389/lib389/cli_conf/pwpolicy.py b/src/lib389/lib389/cli_conf/pwpolicy.py
index 2d4ba9b21..a3e59a90c 100644
--- a/src/lib389/lib389/cli_conf/pwpolicy.py
+++ b/src/lib389/lib389/cli_conf/pwpolicy.py
@@ -1,5 +1,5 @@
# --- BEGIN COPYRIGHT BLOCK ---
-# Copyright (C) 2023 Red Hat, Inc.
+# Copyright (C) 2025 Red Hat, Inc.
# All rights reserved.
#
# License: GPL (version 3 or any later version).
@@ -43,7 +43,7 @@ def _get_pw_policy(inst, targetdn, log, use_json=None):
targetdn = 'cn=config'
policydn = targetdn
basedn = targetdn
- attr_list.extend(['passwordisglobalpolicy', 'nsslapd-pwpolicy_local'])
+ attr_list.extend(['passwordisglobalpolicy', 'nsslapd-pwpolicy-local'])
all_attrs = inst.config.get_attrs_vals_utf8(attr_list)
attrs = {k: v for k, v in all_attrs.items() if len(v) > 0}
else:
diff --git a/src/lib389/lib389/pwpolicy.py b/src/lib389/lib389/pwpolicy.py
index 6a47a44fe..539c230a9 100644
--- a/src/lib389/lib389/pwpolicy.py
+++ b/src/lib389/lib389/pwpolicy.py
@@ -1,5 +1,5 @@
# --- BEGIN COPYRIGHT BLOCK ---
-# Copyright (C) 2018 Red Hat, Inc.
+# Copyright (C) 2025 Red Hat, Inc.
# All rights reserved.
#
# License: GPL (version 3 or any later version).
@@ -7,6 +7,7 @@
# --- END COPYRIGHT BLOCK ---
import ldap
+from ldap import filter as ldap_filter
from lib389._mapped_object import DSLdapObject, DSLdapObjects
from lib389.backend import Backends
from lib389.config import Config
@@ -74,19 +75,56 @@ class PwPolicyManager(object):
}
def is_subtree_policy(self, dn):
- """Check if the entry has a subtree password policy. If we can find a
- template entry it is subtree policy
+ """Check if a subtree password policy exists for a given entry DN.
- :param dn: Entry DN with PwPolicy set up
+ A subtree policy is indicated by the presence of any CoS template
+ (under `cn=nsPwPolicyContainer,<dn>`) that has a `pwdpolicysubentry`
+ attribute pointing to an existing entry with objectClass `passwordpolicy`.
+
+ :param dn: Entry DN to check for subtree policy
:type dn: str
- :returns: True if the entry has a subtree policy, False otherwise
+ :returns: True if a subtree policy exists, False otherwise
+ :rtype: bool
"""
- cos_templates = CosTemplates(self._instance, 'cn=nsPwPolicyContainer,{}'.format(dn))
try:
- cos_templates.get('cn=nsPwTemplateEntry,%s' % dn)
- return True
- except:
+ container_basedn = 'cn=nsPwPolicyContainer,{}'.format(dn)
+ templates = CosTemplates(self._instance, container_basedn).list()
+ for tmpl in templates:
+ pwp_dn = tmpl.get_attr_val_utf8('pwdpolicysubentry')
+ if not pwp_dn:
+ continue
+ # Validate that the referenced entry exists and is a passwordpolicy
+ pwp_entry = PwPolicyEntry(self._instance, pwp_dn)
+ if pwp_entry.exists() and pwp_entry.present('objectClass', 'passwordpolicy'):
+ return True
+ except ldap.LDAPError:
+ pass
+ return False
+
+ def is_user_policy(self, dn):
+ """Check if the entry has a user password policy.
+
+ A user policy is indicated by the target entry having a
+ `pwdpolicysubentry` attribute that points to an existing
+ entry with objectClass `passwordpolicy`.
+
+ :param dn: Entry DN to check
+ :type dn: str
+
+ :returns: True if the entry has a user policy, False otherwise
+ :rtype: bool
+ """
+ try:
+ entry = Account(self._instance, dn)
+ if not entry.exists():
+ return False
+ pwp_dn = entry.get_attr_val_utf8('pwdpolicysubentry')
+ if not pwp_dn:
+ return False
+ pwp_entry = PwPolicyEntry(self._instance, pwp_dn)
+ return pwp_entry.exists() and pwp_entry.present('objectClass', 'passwordpolicy')
+ except ldap.LDAPError:
return False
def create_user_policy(self, dn, properties):
@@ -114,10 +152,10 @@ class PwPolicyManager(object):
pwp_containers = nsContainers(self._instance, basedn=parentdn)
pwp_container = pwp_containers.ensure_state(properties={'cn': 'nsPwPolicyContainer'})
- # Create policy entry
+ # Create or update the policy entry
properties['cn'] = 'cn=nsPwPolicyEntry_user,%s' % dn
pwp_entries = PwPolicyEntries(self._instance, pwp_container.dn)
- pwp_entry = pwp_entries.create(properties=properties)
+ pwp_entry = pwp_entries.ensure_state(properties=properties)
try:
# Add policy to the entry
user_entry.replace('pwdpolicysubentry', pwp_entry.dn)
@@ -152,32 +190,27 @@ class PwPolicyManager(object):
pwp_containers = nsContainers(self._instance, basedn=dn)
pwp_container = pwp_containers.ensure_state(properties={'cn': 'nsPwPolicyContainer'})
- # Create policy entry
- pwp_entry = None
+ # Create or update the policy entry
properties['cn'] = 'cn=nsPwPolicyEntry_subtree,%s' % dn
pwp_entries = PwPolicyEntries(self._instance, pwp_container.dn)
- pwp_entry = pwp_entries.create(properties=properties)
- try:
- # The CoS template entry (nsPwTemplateEntry) that has the pwdpolicysubentry
- # value pointing to the above (nsPwPolicyEntry) entry
- cos_template = None
- cos_templates = CosTemplates(self._instance, pwp_container.dn)
- cos_template = cos_templates.create(properties={'cosPriority': '1',
- 'pwdpolicysubentry': pwp_entry.dn,
- 'cn': 'cn=nsPwTemplateEntry,%s' % dn})
-
- # The CoS specification entry at the subtree level
- cos_pointer_defs = CosPointerDefinitions(self._instance, dn)
- cos_pointer_defs.create(properties={'cosAttribute': 'pwdpolicysubentry default operational-default',
- 'cosTemplateDn': cos_template.dn,
- 'cn': 'nsPwPolicy_CoS'})
- except ldap.LDAPError as e:
- # Something went wrong, remove what we have done
- if pwp_entry is not None:
- pwp_entry.delete()
- if cos_template is not None:
- cos_template.delete()
- raise e
+ pwp_entry = pwp_entries.ensure_state(properties=properties)
+
+ # Ensure the CoS template entry (nsPwTemplateEntry) that points to the
+ # password policy entry
+ cos_templates = CosTemplates(self._instance, pwp_container.dn)
+ cos_template = cos_templates.ensure_state(properties={
+ 'cosPriority': '1',
+ 'pwdpolicysubentry': pwp_entry.dn,
+ 'cn': 'cn=nsPwTemplateEntry,%s' % dn
+ })
+
+ # Ensure the CoS specification entry at the subtree level
+ cos_pointer_defs = CosPointerDefinitions(self._instance, dn)
+ cos_pointer_defs.ensure_state(properties={
+ 'cosAttribute': 'pwdpolicysubentry default operational-default',
+ 'cosTemplateDn': cos_template.dn,
+ 'cn': 'nsPwPolicy_CoS'
+ })
# make sure that local policies are enabled
self.set_global_policy({'nsslapd-pwpolicy-local': 'on'})
@@ -244,10 +277,12 @@ class PwPolicyManager(object):
if self.is_subtree_policy(entry.dn):
parentdn = dn
subtree = True
- else:
+ elif self.is_user_policy(entry.dn):
dn_comps = ldap.dn.explode_dn(dn)
dn_comps.pop(0)
parentdn = ",".join(dn_comps)
+ else:
+ raise ValueError('The target entry dn does not have a password policy')
# Starting deleting the policy, ignore the parts that might already have been removed
pwp_container = nsContainer(self._instance, 'cn=nsPwPolicyContainer,%s' % parentdn)
--
2.49.0

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,574 @@
From 5d2dc7f78f0a834e46d5665f0c12024da5ddda9e Mon Sep 17 00:00:00 2001
From: Mark Reynolds <mreynolds@redhat.com>
Date: Mon, 28 Jul 2025 17:12:33 -0400
Subject: [PATCH] Issue 6910 - Fix latest coverity issues
Description:
Fix various coverity/ASAN warnings:
- CID 1618837: Out-of-bounds read (OVERRUN) - bdb_bdbreader_glue.c
- CID 1618831: Resource leak (RESOURCE_LEAK) - bdb_layer.c
- CID 1612606: Resource leak (RESOURCE_LEAK) - log.c
- CID 1611461: Uninitialized pointer read (UNINIT) - repl5_agmt.c
- CID 1568589: Dereference before null check (REVERSE_INULL) - repl5_agmt.c
- CID 1590353: Logically dead code (DEADCODE) - repl5_agmt.c
- CID 1611460: Logically dead code (DEADCODE) - control.c
- CID 1610568: Dereference after null check (FORWARD_NULL) - modify.c
- CID 1591259: Out-of-bounds read (OVERRUN) - memberof.c
- CID 1550231: Unsigned compared against 0 (NO_EFFECT) - memberof_config.c
- CID 1548904: Overflowed constant (INTEGER_OVERFLOW) - ch_malloc.c
- CID 1548902: Overflowed constant (INTEGER_OVERFLOW) - dse.lc
- CID 1548900: Overflowed return value (INTEGER_OVERFLOW) - acct_util.c
- CID 1548898: Overflowed constant (INTEGER_OVERFLOW) - parents.c
- CID 1546849: Resource leak (RESOURCE_LEAK) - referint.c
- ASAN - Use after free - automember.c
Relates: http://github.com/389ds/389-ds-base/issues/6910
Reviewed by: progier & spichugi(Thanks!)
---
ldap/servers/plugins/acctpolicy/acct_util.c | 6 ++-
ldap/servers/plugins/automember/automember.c | 9 ++--
ldap/servers/plugins/memberof/memberof.c | 15 +++++--
.../plugins/memberof/memberof_config.c | 11 +++--
ldap/servers/plugins/referint/referint.c | 4 +-
ldap/servers/plugins/replication/repl5_agmt.c | 41 ++++++++-----------
.../slapd/back-ldbm/db-bdb/bdb_import.c | 5 ++-
.../back-ldbm/db-bdb/bdb_instance_config.c | 3 +-
.../slapd/back-ldbm/db-bdb/bdb_layer.c | 13 ++++--
ldap/servers/slapd/back-ldbm/parents.c | 4 +-
ldap/servers/slapd/ch_malloc.c | 4 +-
ldap/servers/slapd/control.c | 5 +--
ldap/servers/slapd/dse.c | 4 +-
ldap/servers/slapd/log.c | 5 ++-
ldap/servers/slapd/modify.c | 6 +--
ldap/servers/slapd/passwd_extop.c | 2 +-
ldap/servers/slapd/unbind.c | 12 ++++--
17 files changed, 88 insertions(+), 61 deletions(-)
diff --git a/ldap/servers/plugins/acctpolicy/acct_util.c b/ldap/servers/plugins/acctpolicy/acct_util.c
index b27eeaff1..7735d10e6 100644
--- a/ldap/servers/plugins/acctpolicy/acct_util.c
+++ b/ldap/servers/plugins/acctpolicy/acct_util.c
@@ -17,7 +17,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
Contributors:
Hewlett-Packard Development Company, L.P.
-Copyright (C) 2021 Red Hat, Inc.
+Copyright (C) 2025 Red Hat, Inc.
******************************************************************************/
#include <stdio.h>
@@ -248,6 +248,10 @@ gentimeToEpochtime(char *gentimestr)
/* Turn tm object into local epoch time */
epochtime = mktime(&t);
+ if (epochtime == (time_t) -1) {
+ /* mktime failed */
+ return 0;
+ }
/* Turn local epoch time into GMT epoch time */
epochtime -= zone_offset;
diff --git a/ldap/servers/plugins/automember/automember.c b/ldap/servers/plugins/automember/automember.c
index f900db7f2..9eade495e 100644
--- a/ldap/servers/plugins/automember/automember.c
+++ b/ldap/servers/plugins/automember/automember.c
@@ -1,5 +1,5 @@
/** BEGIN COPYRIGHT BLOCK
- * Copyright (C) 2022 Red Hat, Inc.
+ * Copyright (C) 2025 Red Hat, Inc.
* All rights reserved.
*
* License: GPL (version 3 or any later version).
@@ -1756,9 +1756,10 @@ automember_update_member_value(Slapi_Entry *member_e, const char *group_dn, char
mod_pb = slapi_pblock_new();
/* Do a single mod with error overrides for DEL/ADD */
- result = slapi_single_modify_internal_override(mod_pb, slapi_sdn_new_dn_byval(group_dn), mods,
- automember_get_plugin_id(), 0);
-
+ Slapi_DN *sdn = slapi_sdn_new_normdn_byref(group_dn);
+ result = slapi_single_modify_internal_override(mod_pb, sdn, mods,
+ automember_get_plugin_id(), 0);
+ slapi_sdn_free(&sdn);
if(add){
if (result != LDAP_SUCCESS) {
slapi_log_err(SLAPI_LOG_ERR, AUTOMEMBER_PLUGIN_SUBSYSTEM,
diff --git a/ldap/servers/plugins/memberof/memberof.c b/ldap/servers/plugins/memberof/memberof.c
index 073d8d938..cfda977f0 100644
--- a/ldap/servers/plugins/memberof/memberof.c
+++ b/ldap/servers/plugins/memberof/memberof.c
@@ -1,5 +1,5 @@
/** BEGIN COPYRIGHT BLOCK
- * Copyright (C) 2021 Red Hat, Inc.
+ * Copyright (C) 2025 Red Hat, Inc.
* All rights reserved.
*
* License: GPL (version 3 or any later version).
@@ -1655,6 +1655,7 @@ memberof_call_foreach_dn(Slapi_PBlock *pb __attribute__((unused)), Slapi_DN *sdn
/* We already did the search for this backend, don't
* do it again when we fall through */
do_suffix_search = PR_FALSE;
+ slapi_pblock_init(search_pb);
}
}
} else if (!all_backends) {
@@ -3763,6 +3764,10 @@ memberof_replace_list(Slapi_PBlock *pb, MemberOfConfig *config, Slapi_DN *group_
pre_index++;
} else {
+ if (pre_index >= pre_total || post_index >= post_total) {
+ /* Don't overrun pre_array/post_array */
+ break;
+ }
/* decide what to do */
int cmp = memberof_compare(
config,
@@ -4453,10 +4458,12 @@ memberof_add_memberof_attr(LDAPMod **mods, const char *dn, char *add_oc)
while (1) {
slapi_pblock_init(mod_pb);
-
+ Slapi_DN *sdn = slapi_sdn_new_normdn_byref(dn);
/* Internal mod with error overrides for DEL/ADD */
- rc = slapi_single_modify_internal_override(mod_pb, slapi_sdn_new_normdn_byref(dn), single_mod,
- memberof_get_plugin_id(), SLAPI_OP_FLAG_BYPASS_REFERRALS);
+ rc = slapi_single_modify_internal_override(mod_pb, sdn, single_mod,
+ memberof_get_plugin_id(),
+ SLAPI_OP_FLAG_BYPASS_REFERRALS);
+ slapi_sdn_free(&sdn);
if (rc == LDAP_OBJECT_CLASS_VIOLATION) {
if (!add_oc || added_oc) {
/*
diff --git a/ldap/servers/plugins/memberof/memberof_config.c b/ldap/servers/plugins/memberof/memberof_config.c
index 1e83ba6e0..e4da351d9 100644
--- a/ldap/servers/plugins/memberof/memberof_config.c
+++ b/ldap/servers/plugins/memberof/memberof_config.c
@@ -1,5 +1,5 @@
/** BEGIN COPYRIGHT BLOCK
- * Copyright (C) 2021 Red Hat, Inc.
+ * Copyright (C) 2025 Red Hat, Inc.
* All rights reserved.
*
* License: GPL (version 3 or any later version).
@@ -570,21 +570,24 @@ memberof_apply_config(Slapi_PBlock *pb __attribute__((unused)),
if (num_groupattrs > 1) {
size_t bytes_out = 0;
size_t filter_str_len = groupattr_name_len + (num_groupattrs * 4) + 4;
+ int32_t rc = 0;
/* Allocate enough space for the filter */
filter_str = slapi_ch_malloc(filter_str_len);
/* Add beginning of filter. */
- bytes_out = snprintf(filter_str, filter_str_len - bytes_out, "(|");
- if (bytes_out<0) {
+ rc = snprintf(filter_str, filter_str_len - bytes_out, "(|");
+ if (rc < 0) {
slapi_log_err(SLAPI_LOG_ERR, MEMBEROF_PLUGIN_SUBSYSTEM, "snprintf unexpectly failed in memberof_apply_config.\n");
*returncode = LDAP_UNWILLING_TO_PERFORM;
goto done;
+ } else {
+ bytes_out = rc;
}
/* Add filter section for each groupattr. */
for (size_t i=0; theConfig.groupattrs && theConfig.groupattrs[i]; i++) {
- size_t bytes_read = snprintf(filter_str + bytes_out, filter_str_len - bytes_out, "(%s=*)", theConfig.groupattrs[i]);
+ int32_t bytes_read = snprintf(filter_str + bytes_out, filter_str_len - bytes_out, "(%s=*)", theConfig.groupattrs[i]);
if (bytes_read<0) {
slapi_log_err(SLAPI_LOG_ERR, MEMBEROF_PLUGIN_SUBSYSTEM, "snprintf unexpectly failed in memberof_apply_config.\n");
*returncode = LDAP_UNWILLING_TO_PERFORM;
diff --git a/ldap/servers/plugins/referint/referint.c b/ldap/servers/plugins/referint/referint.c
index 5d7f9e5dd..5746b913f 100644
--- a/ldap/servers/plugins/referint/referint.c
+++ b/ldap/servers/plugins/referint/referint.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) 2025 Red Hat, Inc.
* All rights reserved.
*
* License: GPL (version 3 or any later version).
@@ -1499,6 +1499,8 @@ referint_thread_func(void *arg __attribute__((unused)))
slapi_sdn_free(&sdn);
continue;
}
+
+ slapi_sdn_free(&tmpsuperior);
if (!strcasecmp(ptoken, "NULL")) {
tmpsuperior = NULL;
} else {
diff --git a/ldap/servers/plugins/replication/repl5_agmt.c b/ldap/servers/plugins/replication/repl5_agmt.c
index 0a81167b7..eed97578e 100644
--- a/ldap/servers/plugins/replication/repl5_agmt.c
+++ b/ldap/servers/plugins/replication/repl5_agmt.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) 2025 Red Hat, Inc.
* All rights reserved.
*
* License: GPL (version 3 or any later version).
@@ -202,7 +202,7 @@ agmt_init_session_id(Repl_Agmt *ra)
char *host = NULL; /* e.g. localhost.domain */
char port[10]; /* e.g. 389 */
char sport[10]; /* e.g. 636 */
- char *hash_in;
+ char *hash_in = NULL;
int32_t max_str_sid = SESSION_ID_STR_SZ - 4;
if (ra == NULL) {
@@ -2718,31 +2718,26 @@ agmt_update_init_status(Repl_Agmt *ra)
mod_idx++;
}
- if (nb_mods) {
- /* it is ok to release the lock here because we are done with the agreement data.
- we have to do it before issuing the modify operation because it causes
- agmtlist_notify_all to be called which uses the same lock - hence the deadlock */
- PR_Unlock(ra->lock);
-
- pb = slapi_pblock_new();
- mods[nb_mods] = NULL;
+ /* it is ok to release the lock here because we are done with the agreement data.
+ we have to do it before issuing the modify operation because it causes
+ agmtlist_notify_all to be called which uses the same lock - hence the deadlock */
+ PR_Unlock(ra->lock);
- slapi_modify_internal_set_pb_ext(pb, ra->dn, mods, NULL, NULL,
- repl_get_plugin_identity(PLUGIN_MULTISUPPLIER_REPLICATION), 0);
- slapi_modify_internal_pb(pb);
+ pb = slapi_pblock_new();
+ mods[nb_mods] = NULL;
- slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc);
- if (rc != LDAP_SUCCESS && rc != LDAP_NO_SUCH_ATTRIBUTE) {
- slapi_log_err(SLAPI_LOG_ERR, repl_plugin_name, "agmt_update_consumer_ruv - "
- "%s: agmt_update_consumer_ruv: "
- "failed to update consumer's RUV; LDAP error - %d\n",
- ra->long_name, rc);
- }
+ slapi_modify_internal_set_pb_ext(pb, ra->dn, mods, NULL, NULL,
+ repl_get_plugin_identity(PLUGIN_MULTISUPPLIER_REPLICATION), 0);
+ slapi_modify_internal_pb(pb);
- slapi_pblock_destroy(pb);
- } else {
- PR_Unlock(ra->lock);
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc);
+ if (rc != LDAP_SUCCESS && rc != LDAP_NO_SUCH_ATTRIBUTE) {
+ slapi_log_err(SLAPI_LOG_ERR, repl_plugin_name, "agmt_update_consumer_ruv - "
+ "%s: agmt_update_consumer_ruv: failed to update consumer's RUV; LDAP error - %d\n",
+ ra->long_name, rc);
}
+
+ slapi_pblock_destroy(pb);
slapi_ch_free((void **)&mods);
slapi_mod_done(&smod_start_time);
slapi_mod_done(&smod_end_time);
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 46c80ec3d..0127bf2f9 100644
--- a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_import.c
+++ b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_import.c
@@ -1,5 +1,5 @@
/** BEGIN COPYRIGHT BLOCK
- * Copyright (C) 2020 Red Hat, Inc.
+ * Copyright (C) 2025 Red Hat, Inc.
* All rights reserved.
*
* License: GPL (version 3 or any later version).
@@ -947,6 +947,7 @@ bdb_ancestorid_new_idl_create_index(backend *be, ImportJob *job)
EQ_PREFIX, (u_long)id);
key.size++; /* include the null terminator */
ret = NEW_IDL_NO_ALLID;
+ idl_free(&children);
children = idl_fetch(be, db_pid, &key, txn, ai_pid, &ret);
if (ret != 0) {
ldbm_nasty("bdb_ancestorid_new_idl_create_index", sourcefile, 13070, ret);
@@ -957,6 +958,7 @@ bdb_ancestorid_new_idl_create_index(backend *be, ImportJob *job)
if (job->flags & FLAG_ABORT) {
import_log_notice(job, SLAPI_LOG_ERR, "bdb_ancestorid_new_idl_create_index",
"ancestorid creation aborted.");
+ idl_free(&children);
ret = -1;
break;
}
@@ -1290,6 +1292,7 @@ bdb_update_subordinatecounts(backend *be, ImportJob *job, DB_TXN *txn)
}
bdb_close_subcount_cursor(&c_entryrdn);
bdb_close_subcount_cursor(&c_objectclass);
+
return ret;
}
diff --git a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_instance_config.c b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_instance_config.c
index bb515a23f..44a624fde 100644
--- a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_instance_config.c
+++ b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_instance_config.c
@@ -1,5 +1,5 @@
/** BEGIN COPYRIGHT BLOCK
- * Copyright (C) 2020 Red Hat, Inc.
+ * Copyright (C) 2025 Red Hat, Inc.
* All rights reserved.
*
* License: GPL (version 3 or any later version).
@@ -261,6 +261,7 @@ bdb_instance_cleanup(struct ldbm_instance *inst)
if (inst_dirp && *inst_dir) {
return_value = env->remove(env, inst_dirp, 0);
} else {
+ slapi_ch_free((void **)&env);
return_value = -1;
}
if (return_value == EBUSY) {
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 53f1cde69..b1e44a919 100644
--- a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_layer.c
+++ b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_layer.c
@@ -1,5 +1,5 @@
/** BEGIN COPYRIGHT BLOCK
- * Copyright (C) 2023 Red Hat, Inc.
+ * Copyright (C) 2025 Red Hat, Inc.
* All rights reserved.
*
* License: GPL (version 3 or any later version).
@@ -2027,9 +2027,13 @@ bdb_pre_close(struct ldbminfo *li)
conf = (bdb_config *)li->li_dblayer_config;
bdb_db_env *pEnv = (bdb_db_env *)priv->dblayer_env;
+ if (pEnv == NULL) {
+ return;
+ }
+
pthread_mutex_lock(&pEnv->bdb_thread_count_lock);
- if (conf->bdb_stop_threads || !pEnv) {
+ if (conf->bdb_stop_threads) {
/* already stopped. do nothing... */
goto timeout_escape;
}
@@ -2203,6 +2207,7 @@ bdb_remove_env(struct ldbminfo *li)
}
if (NULL == li) {
slapi_log_err(SLAPI_LOG_ERR, "bdb_remove_env", "No ldbm info is given\n");
+ slapi_ch_free((void **)&env);
return -1;
}
@@ -2212,10 +2217,11 @@ bdb_remove_env(struct ldbminfo *li)
if (rc) {
slapi_log_err(SLAPI_LOG_ERR,
"bdb_remove_env", "Failed to remove DB environment files. "
- "Please remove %s/__db.00# (# is 1 through 6)\n",
+ "Please remove %s/__db.00# (# is 1 through 6)\n",
home_dir);
}
}
+ slapi_ch_free((void **)&env);
return rc;
}
@@ -6341,6 +6347,7 @@ bdb_back_ctrl(Slapi_Backend *be, int cmd, void *info)
db->close(db, 0);
rc = bdb_db_remove_ex((bdb_db_env *)priv->dblayer_env, path, NULL, PR_TRUE);
inst->inst_changelog = NULL;
+ slapi_ch_free_string(&path);
slapi_ch_free_string(&instancedir);
}
}
diff --git a/ldap/servers/slapd/back-ldbm/parents.c b/ldap/servers/slapd/back-ldbm/parents.c
index 31107591e..52c665ca4 100644
--- a/ldap/servers/slapd/back-ldbm/parents.c
+++ b/ldap/servers/slapd/back-ldbm/parents.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) 2025 Red Hat, Inc.
* All rights reserved.
*
* License: GPL (version 3 or any later version).
@@ -123,7 +123,7 @@ parent_update_on_childchange(modify_context *mc, int op, size_t *new_sub_count)
/* Now compute the new value */
if ((PARENTUPDATE_ADD == op) || (PARENTUPDATE_RESURECT == op)) {
current_sub_count++;
- } else {
+ } else if (current_sub_count > 0) {
current_sub_count--;
}
diff --git a/ldap/servers/slapd/ch_malloc.c b/ldap/servers/slapd/ch_malloc.c
index cbab1d170..27ed546a5 100644
--- a/ldap/servers/slapd/ch_malloc.c
+++ b/ldap/servers/slapd/ch_malloc.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) 2025 Red Hat, Inc.
* All rights reserved.
*
* License: GPL (version 3 or any later version).
@@ -254,7 +254,7 @@ slapi_ch_bvecdup(struct berval **v)
++i;
newberval = (struct berval **)slapi_ch_malloc((i + 1) * sizeof(struct berval *));
newberval[i] = NULL;
- while (i-- > 0) {
+ while (i > 0 && i-- > 0) {
newberval[i] = slapi_ch_bvdup(v[i]);
}
}
diff --git a/ldap/servers/slapd/control.c b/ldap/servers/slapd/control.c
index 7aeeba885..d661dc6e1 100644
--- a/ldap/servers/slapd/control.c
+++ b/ldap/servers/slapd/control.c
@@ -174,7 +174,6 @@ create_sessiontracking_ctrl(const char *session_tracking_id, LDAPControl **sessi
char *undefined_sid = "undefined sid";
const char *sid;
int rc = 0;
- int tag;
LDAPControl *ctrl = NULL;
if (session_tracking_id) {
@@ -183,9 +182,7 @@ create_sessiontracking_ctrl(const char *session_tracking_id, LDAPControl **sessi
sid = undefined_sid;
}
ctrlber = ber_alloc();
- tag = ber_printf( ctrlber, "{nnno}", sid, strlen(sid));
- if (rc == LBER_ERROR) {
- tag = -1;
+ if ((rc = ber_printf( ctrlber, "{nnno}", sid, strlen(sid)) == LBER_ERROR)) {
goto done;
}
slapi_build_control(LDAP_CONTROL_X_SESSION_TRACKING, ctrlber, 0, &ctrl);
diff --git a/ldap/servers/slapd/dse.c b/ldap/servers/slapd/dse.c
index b788054db..bec3e32f4 100644
--- a/ldap/servers/slapd/dse.c
+++ b/ldap/servers/slapd/dse.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) 2025 Red Hat, Inc.
* All rights reserved.
*
* License: GPL (version 3 or any later version).
@@ -637,7 +637,7 @@ dse_updateNumSubordinates(Slapi_Entry *entry, int op)
/* Now compute the new value */
if (SLAPI_OPERATION_ADD == op) {
current_sub_count++;
- } else {
+ } else if (current_sub_count > 0) {
current_sub_count--;
}
{
diff --git a/ldap/servers/slapd/log.c b/ldap/servers/slapd/log.c
index 91ba23047..a9a5f3b3f 100644
--- a/ldap/servers/slapd/log.c
+++ b/ldap/servers/slapd/log.c
@@ -1,6 +1,6 @@
/** BEGIN COPYRIGHT BLOCK
* Copyright (C) 2001 Sun Microsystems, Inc. Used by permission.
- * Copyright (C) 2005-2024 Red Hat, Inc.
+ * Copyright (C) 2025 Red Hat, Inc.
* Copyright (C) 2010 Hewlett-Packard Development Company, L.P.
* All rights reserved.
*
@@ -201,6 +201,7 @@ compress_log_file(char *log_name, int32_t mode)
if ((source = fopen(log_name, "r")) == NULL) {
/* Failed to open log file */
+ /* coverity[leaked_storage] gzclose does close FD */
gzclose(outfile);
return -1;
}
@@ -211,11 +212,13 @@ compress_log_file(char *log_name, int32_t mode)
if (bytes_written == 0)
{
fclose(source);
+ /* coverity[leaked_storage] gzclose does close FD */
gzclose(outfile);
return -1;
}
bytes_read = fread(buf, 1, LOG_CHUNK, source);
}
+ /* coverity[leaked_storage] gzclose does close FD */
gzclose(outfile);
fclose(source);
PR_Delete(log_name); /* remove the old uncompressed log */
diff --git a/ldap/servers/slapd/modify.c b/ldap/servers/slapd/modify.c
index 0a351d46a..9e5bce80b 100644
--- a/ldap/servers/slapd/modify.c
+++ b/ldap/servers/slapd/modify.c
@@ -1,6 +1,6 @@
/** BEGIN COPYRIGHT BLOCK
* Copyright (C) 2001 Sun Microsystems, Inc. Used by permission.
- * Copyright (C) 2009 Red Hat, Inc.
+ * Copyright (C) 2025 Red Hat, Inc.
* Copyright (C) 2009, 2010 Hewlett-Packard Development Company, L.P.
* All rights reserved.
*
@@ -498,7 +498,7 @@ slapi_modify_internal_set_pb_ext(Slapi_PBlock *pb, const Slapi_DN *sdn, LDAPMod
*
* Any other errors encountered during the operation will be returned as-is.
*/
-int
+int
slapi_single_modify_internal_override(Slapi_PBlock *pb, const Slapi_DN *sdn, LDAPMod **mod, Slapi_ComponentId *plugin_id, int op_flags)
{
int rc = 0;
@@ -512,7 +512,7 @@ slapi_single_modify_internal_override(Slapi_PBlock *pb, const Slapi_DN *sdn, LDA
!pb ? "pb " : "",
!sdn ? "sdn " : "",
!mod ? "mod " : "",
- !mod[0] ? "mod[0] " : "");
+ !mod || !mod[0] ? "mod[0] " : "");
return LDAP_PARAM_ERROR;
}
diff --git a/ldap/servers/slapd/passwd_extop.c b/ldap/servers/slapd/passwd_extop.c
index 69bb3494c..5f05cf74e 100644
--- a/ldap/servers/slapd/passwd_extop.c
+++ b/ldap/servers/slapd/passwd_extop.c
@@ -1,5 +1,5 @@
/** BEGIN COPYRIGHT BLOCK
- * Copyright (C) 2005 Red Hat, Inc.
+ * Copyright (C) 2025 Red Hat, Inc.
* All rights reserved.
*
* License: GPL (version 3 or any later version).
diff --git a/ldap/servers/slapd/unbind.c b/ldap/servers/slapd/unbind.c
index fa8cd649f..c4e7a5efd 100644
--- a/ldap/servers/slapd/unbind.c
+++ b/ldap/servers/slapd/unbind.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) 2025 Red Hat, Inc.
* All rights reserved.
*
* License: GPL (version 3 or any later version).
@@ -112,8 +112,12 @@ do_unbind(Slapi_PBlock *pb)
/* pass the unbind to all backends */
be_unbindall(pb_conn, operation);
-free_and_return:;
+free_and_return:
- /* close the connection to the client */
- disconnect_server(pb_conn, operation->o_connid, operation->o_opid, SLAPD_DISCONNECT_UNBIND, 0);
+ /* close the connection to the client after refreshing the operation */
+ slapi_pblock_get(pb, SLAPI_OPERATION, &operation);
+ disconnect_server(pb_conn,
+ operation ? operation->o_connid : -1,
+ operation ? operation->o_opid : -1,
+ SLAPD_DISCONNECT_UNBIND, 0);
}
--
2.49.0

View File

@ -0,0 +1,35 @@
From ea62e862c8ca7e036f7d1e23ec3a27bffbc39bdf 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.49.0

View File

@ -333,6 +333,11 @@ Patch: 0033-Issue-6768-ns-slapd-crashes-when-a-referral-is-added.patc
Patch: 0034-Issues-6913-6886-6250-Adjust-xfail-marks-6914.patch
Patch: 0035-Issue-6875-Fix-dsidm-tests.patch
Patch: 0036-Issue-6519-Add-basic-dsidm-account-tests.patch
Patch: 0037-Issue-6940-dsconf-monitor-server-fails-with-ldapi-du.patch
Patch: 0038-Issue-6936-Make-user-subtree-policy-creation-idempot.patch
Patch: 0039-Issue-6919-numSubordinates-tombstoneNumSubordinates-.patch
Patch: 0040-Issue-6910-Fix-latest-coverity-issues.patch
Patch: 0041-Issue-6929-Compilation-failure-with-rust-1.89-on-Fed.patch
%description
389 Directory Server is an LDAPv3 compliant server. The base package includes

View File

@ -1,4 +1,9 @@
---
abidiff:
ignore:
- /usr/lib64/dirsrv/plugins/*.so
- /usr/lib64/dirsrv/libsds.so*
- /usr/lib64/dirsrv/libslapd.so*
specname:
match: suffix
runpath: