389-ds-base/0005-Issue-7549-Substring-index-should-validate-minimum-n.patch
Viktor Ashirov 5a8e6532df Bump version to 3.3.0
- Resolves: RHEL-58681 - high wtime can hide a worker reading a large requests
- Resolves: RHEL-62876 - Inconsistencies in the log messages
- Resolves: RHEL-68692 - Replication halt caused by an incorrect setting of "nsslapd-changelogmaxage"
- Resolves: RHEL-76836 - Crash in replica_config_add when manually configuring a replica with an incorrect nsds5ReplicaRoot.
- Resolves: RHEL-80256 - [RFE] RHDS healthcheck tool could provide more information in offline mode
- Resolves: RHEL-84531 - access log - suspicious wtime  optime negative and large values in internal op
- Resolves: RHEL-86325 - dbscan utility not filtering using -k option
- Resolves: RHEL-88942 - LDAP healthcheck and ignoring entrydn index and associated config
- Resolves: RHEL-109505 - Possible memory leak when using the Retro Changelog plugin.
- Resolves: RHEL-117666 - [RFE] Expose stats from deferred memberof
- Resolves: RHEL-118811 - Password policy can be created even though the same one already exists
- Resolves: RHEL-118867 - Memory leak issue in an IPA deployment.
- Resolves: RHEL-121119 - Differences on export ldif using online of off-line method on RHDS
- Resolves: RHEL-121169 - dsctl should report error when the ldif to import doesn't have permissions to be read by dirsrv.
- Resolves: RHEL-129204 - Local password policies can be created with unallowed values
- Resolves: RHEL-129597 - Remediate "Could not read password attribute" error on directory Servers with chaining enabled(DBLink)
- Resolves: RHEL-132767 - An online reinitialization with LMDB is terminating the receiving server
- Resolves: RHEL-134188 - RFE: add server-side configuration to ignore malformed controls and/or override critical controls from clients
- Resolves: RHEL-138569 - AddressSanitizer: SEGV ldap/servers/plugins/automember/automember.c:1962 in automember_mod_post_op
- Resolves: RHEL-140005 - Memory leaks in IPA context on server restart
- Resolves: RHEL-141049 - Fail open condition in ACL
- Resolves: RHEL-141480 - [RFE] Add shadowLastChange fixup task
- Resolves: RHEL-146147 - Fix dsconf to allow specifying multiple certificates for use in 389-ds server
- Resolves: RHEL-149125 - dnaSharedConfig: "dnaPortNum: 0"
- Resolves: RHEL-151246 - dsctl healthcheck DSMOLE0001 inaccurate recommendations when there is more than 1 LDAP backend
- Resolves: RHEL-152373 - Entry cache DN hash table corruption causes SIGSEGV in ns-slapd
- Resolves: RHEL-153088 - [RFE] Add OS-level thread names to all server threads
- Resolves: RHEL-153089 - [RFE] Add work queue metrics to cn=monitor
- Resolves: RHEL-153092 - [RFE] Per-operation thread pool context via STAT extension
- Resolves: RHEL-153372 - dirsrv@.service missing NoNewPrivileges and MemoryDenyWriteExecute despite broad capability set
- Resolves: RHEL-154374 - Prevent the ability of creating a replication agreement from a node back to itself is needed
- Resolves: RHEL-154380 - DS 12 does not handle escape char in bind user
- Resolves: RHEL-157894 - Online export is failing when using the option "-s"
- Resolves: RHEL-163373 - WARN - keys2idl - received NULL idl from index_read_ext_allids
- Resolves: RHEL-165956 - can't modify user's name/dn if it's owner of SubIDs
- Resolves: RHEL-169529 - Rebase 389-ds-base to 3.3.x
- Resolves: RHEL-170277 - Memory leaks in syncrepl plugin during persistent search operations
- Resolves: RHEL-170643 - passwordbadwords attribute of the local password policy is not functioning
- Resolves: RHEL-170907 - Substring index produces empty results and can crash when non-default nsSubStrBegin/nsSubStrEnd lengths are configured
- Resolves: RHEL-172228 - Replacing a group's member list modifies users who were already members
- Resolves: RHEL-174908 - Replication errors in logs
- Resolves: RHEL-181022 - Server shutdown during online reindex may lead to data loss
2026-06-05 14:01:13 +02:00

291 lines
13 KiB
Diff

From 971e6f1405a102b6a3692f1f97d373dcb670e973 Mon Sep 17 00:00:00 2001
From: Viktor Ashirov <vashirov@redhat.com>
Date: Fri, 5 Jun 2026 11:45:07 +0200
Subject: [PATCH 5/6] Issue 7549 - Substring index should validate minimum
nsSubStrBegin/nsSubStrEnd values (#7550)
Bug Description:
The `nsSubStrBegin` and `nsSubStrEnd` values include the anchor character
(`^` for begin, `$` for end) in the configured length. The actual number of
value characters stored in the index key is `configured_value - 1`.
A value of 1 for begin or end produces an index key with 0 value
characters, which matches everything and is not useful.
Fix Description:
Add validation to detect nsSubStrBegin/nsSubStrEnd values less than 2,
adjust them to 2, and log a warning.
Relates: https://github.com/389ds/389-ds-base/issues/7440
Fixes: https://github.com/389ds/389-ds-base/issues/7549
Reviewed by: @progier389 (Thanks!)
---
.../suites/indexes/substring_index_test.py | 127 ++++++++++++++++--
ldap/servers/slapd/back-ldbm/ldbm_attr.c | 39 ++++++
2 files changed, 153 insertions(+), 13 deletions(-)
diff --git a/dirsrvtests/tests/suites/indexes/substring_index_test.py b/dirsrvtests/tests/suites/indexes/substring_index_test.py
index e358caa93..5908d8c95 100644
--- a/dirsrvtests/tests/suites/indexes/substring_index_test.py
+++ b/dirsrvtests/tests/suites/indexes/substring_index_test.py
@@ -325,20 +325,25 @@ def test_substr_low_values(topology_st, uid_index, create_user):
_search_and_assert(topology_st, '(uid=*low)', 2, 'low: final matching both')
-def test_substr_begin_1_middle_3_end_3(topology_st, uid_index, create_user):
- """Test substring index with nsSubStrBegin=1, nsSubStrMiddle=3, nsSubStrEnd=3.
+def test_substr_begin_2_middle_3_end_3(topology_st, uid_index, create_user):
+ """Test substring index with nsSubStrBegin=2, nsSubStrMiddle=3, nsSubStrEnd=3.
+
+ With begin=2, the begin key stores 1 value character (plus the '^'
+ anchor). This allows single-character initial substring searches
+ to use the index. With end=3, the end key stores 2 value characters
+ (plus '$'), so a final substring search needs at least 2 characters.
:id: c28166e9-41aa-44ee-ba66-3300ea3ea4d1
:customerscenario: True
:setup: Standalone instance
:steps:
- 1. Configure uid index with begin=1, middle=3, end=3
+ 1. Configure uid index with begin=2, middle=3, end=3
2. Reindex uid attribute
3. Add two test users
- 4. Search with 1-char initial
- 5. Search with 3-char initial
+ 4. Search with 1-char initial (uses begin index, N-1=1 value char)
+ 5. Search with multi-char initial
6. Search with 3-char any
- 7. Search with 3-char final
+ 7. Search with 2-char final (uses end index, N-1=2 value chars)
8. Search with 2-char any (shorter than middle=3)
:expectedresults:
1. Success
@@ -353,7 +358,7 @@ def test_substr_begin_1_middle_3_end_3(topology_st, uid_index, create_user):
uid_index.add_many(
('objectClass', 'extensibleObject'),
('nsIndexType', 'sub'),
- ('nsSubStrBegin', '1'),
+ ('nsSubStrBegin', '2'),
('nsSubStrMiddle', '3'),
('nsSubStrEnd', '3'),
)
@@ -367,23 +372,25 @@ def test_substr_begin_1_middle_3_end_3(topology_st, uid_index, create_user):
_search_and_assert(topology_st, f'(uid={uid1})', 1, 'exact match sanity check')
_search_and_assert(topology_st, f'(uid={uid1[:len(UID_PREFIX)+2]}*)', 1,
- 'begin=1 two-char initial')
+ 'begin=2 multi-char initial')
_search_and_assert(topology_st, '(uid=*deg*)', 2, 'middle=3 any search')
- _search_and_assert(topology_st, '(uid=*gen)', 2, 'end=3 final search')
+ _search_and_assert(topology_st, '(uid=*gen)', 2,
+ 'end=3 final search (2 value chars + $)')
_search_and_assert(topology_st, '(uid=*7d*)', 1, 'short any, post-filtered')
def test_substr_matching_rule_mixed(topology_st, uid_index, create_user):
"""Test mixed substring lengths via nsMatchingRule format.
- Uses begin=1, middle=3, end=3 via nsMatchingRule format.
+ Uses begin=2, middle=3, end=3 via nsMatchingRule format.
+ With begin=2 the begin key has 1 value character (plus '^').
:id: 0ef6d87e-5ce2-4ea7-a8b7-9cb09f0fcf53
:customerscenario: True
:setup: Standalone instance
:steps:
1. Configure uid index with nsMatchingRule format:
- nssubstrbegin=1, nssubstrmiddle=3, nssubstrend=3
+ nssubstrbegin=2, nssubstrmiddle=3, nssubstrend=3
2. Reindex uid attribute
3. Add test users
4. Search with various filters to verify functionality
@@ -395,7 +402,7 @@ def test_substr_matching_rule_mixed(topology_st, uid_index, create_user):
"""
uid_index.add_many(
('nsIndexType', 'sub'),
- ('nsMatchingRule', 'nssubstrbegin=1'),
+ ('nsMatchingRule', 'nssubstrbegin=2'),
('nsMatchingRule', 'nssubstrmiddle=3'),
('nsMatchingRule', 'nssubstrend=3'),
)
@@ -408,7 +415,7 @@ def test_substr_matching_rule_mixed(topology_st, uid_index, create_user):
_reindex_uid(topology_st)
_search_and_assert(topology_st, f'(uid={uid1[:len(UID_PREFIX)+2]}*)', 1,
- 'matching rule begin=1')
+ 'matching rule begin=2')
_search_and_assert(topology_st, '(uid=*rul*)', 2, 'matching rule middle=3')
_search_and_assert(topology_st, f'(uid=*{uid1[-7:]})', 1, 'matching rule end=3')
@@ -458,6 +465,100 @@ def test_substr_mixed_end_gt_middle(topology_st, uid_index, create_user):
_search_and_assert(topology_st, '(uid=*nd*)', 2, 'middle=2 any search')
+def test_substr_min_begin_value(topology_st, uid_index, create_user):
+ """Test that nsSubStrBegin=1 is restricted to 2 with a warning.
+
+ A value of 1 for begin or end is too small because the anchor
+ character ('^' or '$') consumes the entire key length, leaving
+ zero value characters. The server should adjust it to 2 and log
+ a warning.
+
+ :id: f8e2a3b1-6c7d-4e9f-a0b2-3c4d5e6f7a8b
+ :customerscenario: True
+ :setup: Standalone instance
+ :steps:
+ 1. Configure uid index with nsSubStrBegin=1 (too small)
+ 2. Check that the server logs a warning about the adjustment
+ 3. Reindex uid attribute
+ 4. Add test users with distinct first characters after prefix
+ 5. Search with 1-char initial (should work because begin was adjusted to 2)
+ :expectedresults:
+ 1. Success (value accepted but adjusted)
+ 2. Warning logged about adjustment
+ 3. Success
+ 4. Success
+ 5. Returns correct results
+ """
+ uid_index.add_many(
+ ('objectClass', 'extensibleObject'),
+ ('nsIndexType', 'sub'),
+ ('nsSubStrBegin', '1'),
+ ('nsSubStrMiddle', '3'),
+ ('nsSubStrEnd', '3'),
+ )
+
+ uid1 = f'{UID_PREFIX}n13adj'
+ uid2 = f'{UID_PREFIX}o14adj'
+ create_user(uid1, 'n user13', 'user13')
+ create_user(uid2, 'o user14', 'user14')
+
+ _reindex_uid(topology_st)
+
+ # With begin adjusted to 2, a 1-char initial uses the begin index key
+ # (the key has 1 value char = N-1 = 2-1).
+ _search_and_assert(topology_st, f'(uid={uid1[:len(UID_PREFIX)+1]}*)', 1,
+ 'adjusted begin=2: 1-char after prefix is selective')
+ _search_and_assert(topology_st, '(uid=*adj)', 2,
+ 'adjusted begin=2: both users found by final match')
+
+ # Verify warning was logged
+ assert topology_st.standalone.searchErrorsLog('nsSubStrBegin')
+ assert topology_st.standalone.searchErrorsLog('too small')
+
+
+def test_substr_end_adjusted_matching_rule(topology_st, uid_index, create_user):
+ """Test that nssubstrend=1 via nsMatchingRule is adjusted to 2.
+
+ :id: 4b48136c-69af-41d8-bcc8-8eeb8d7959a9
+ :customerscenario: True
+ :setup: Standalone instance
+ :steps:
+ 1. Configure uid index with nsMatchingRule: nssubstrend=1
+ 2. Check that the server logs a warning about the adjustment
+ 3. Reindex uid attribute
+ 4. Add test users
+ 5. Search with 1-char final (uses end index since adjusted to 2)
+ :expectedresults:
+ 1. Success (value accepted but adjusted)
+ 2. Warning logged
+ 3. Success
+ 4. Success
+ 5. Returns correct results
+ """
+ uid_index.add_many(
+ ('nsIndexType', 'sub'),
+ ('nsMatchingRule', 'nssubstrend=1'),
+ )
+
+ uid1 = f'{UID_PREFIX}p15endcl'
+ uid2 = f'{UID_PREFIX}q16endcm'
+ create_user(uid1, 'p user15', 'user15')
+ create_user(uid2, 'q user16', 'user16')
+
+ _reindex_uid(topology_st)
+
+ # With end adjusted to 2, the end key has 1 value char.
+ # 1-char final should use the end index: finallen(1) > 2-2=0 -> true
+ _search_and_assert(topology_st, f'(uid=*{uid1[-1:]})', 1,
+ 'adjusted end=2: 1-char final')
+ _search_and_assert(topology_st, f'(uid=*{uid1[-4:]})', 1,
+ 'adjusted end=2: multi-char final')
+
+ # Verify warning was logged
+ assert topology_st.standalone.searchErrorsLog('nssubstrend=1')
+ assert topology_st.standalone.searchErrorsLog('too small')
+
+
if __name__ == '__main__':
CURRENT_FILE = os.path.realpath(__file__)
pytest.main("-s %s" % CURRENT_FILE)
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_attr.c b/ldap/servers/slapd/back-ldbm/ldbm_attr.c
index 7fe6f1405..85a89087c 100644
--- a/ldap/servers/slapd/back-ldbm/ldbm_attr.c
+++ b/ldap/servers/slapd/back-ldbm/ldbm_attr.c
@@ -179,6 +179,20 @@ _set_attr_substrlen(int index, char *str, int **substrlens)
if (NULL != p) {
long sublen = strtol(++p, (char **)NULL, 10);
if (sublen > 0) { /* 0 is not acceptable */
+ /*
+ * For begin and end keys, the configured value includes the
+ * anchor character ('^' for begin, '$' for end). So the
+ * actual number of value characters in the key is (N - 1).
+ * A value of 1 means 0 value characters, which produces a
+ * key that matches everything. Adjust to 2.
+ */
+ if ((index == INDEX_SUBSTRBEGIN || index == INDEX_SUBSTREND) && sublen < 2) {
+ slapi_log_err(SLAPI_LOG_WARNING, "attr_index_config",
+ "nsMatchingRule %s value %ld is too small (minimum is 2, "
+ "because the value includes the anchor character). "
+ "Adjusting to 2.\n", str, sublen);
+ sublen = 2;
+ }
if (NULL == *substrlens) {
*substrlens = (int *)slapi_ch_calloc(1,
sizeof(int) * INDEX_SUBSTRLEN);
@@ -777,9 +791,26 @@ attr_index_config(
* nsSubStrBegin: 2
* nsSubStrMiddle: 2
* nsSubStrEnd: 2
+ *
+ * NOTE: For begin and end, the configured value includes the anchor
+ * character ('^' for begin, '$' for end). The actual number of value
+ * characters stored in the index key is (configured_value - 1).
+ * Therefore the minimum useful value for begin and end is 2 (which
+ * gives 1 value character). A value of 1 leaves 0 value
+ * characters (key matches everything) and is adjusted to 2.
+ * For middle, there is no anchor, so the value directly equals
+ * the number of characters in the key.
*/
substrval = slapi_entry_attr_get_int(e, INDEX_ATTR_SUBSTRBEGIN);
if (substrval) {
+ if (substrval < 2) {
+ slapi_log_err(SLAPI_LOG_WARNING, "attr_index_config",
+ "%s: %d is too small (minimum is 2, because the "
+ "value includes the '^' anchor character). "
+ "Adjusting to 2.\n",
+ INDEX_ATTR_SUBSTRBEGIN, substrval);
+ substrval = 2;
+ }
substrlens = (int *)slapi_ch_calloc(1, sizeof(int) * INDEX_SUBSTRLEN);
substrlens[INDEX_SUBSTRBEGIN] = substrval;
}
@@ -792,6 +823,14 @@ attr_index_config(
}
substrval = slapi_entry_attr_get_int(e, INDEX_ATTR_SUBSTREND);
if (substrval) {
+ if (substrval < 2) {
+ slapi_log_err(SLAPI_LOG_WARNING, "attr_index_config",
+ "%s: %d is too small (minimum is 2, because the "
+ "value includes the '$' anchor character). "
+ "Adjusting to 2.\n",
+ INDEX_ATTR_SUBSTREND, substrval);
+ substrval = 2;
+ }
if (!substrlens) {
substrlens = (int *)slapi_ch_calloc(1, sizeof(int) * INDEX_SUBSTRLEN);
}
--
2.54.0