Compare commits

...

No commits in common. 'c9' and 'c9-beta' have entirely different histories.
c9 ... c9-beta

@ -1,83 +0,0 @@
From fcdeec3b876a28e06bb53a60fe502cb702403931 Mon Sep 17 00:00:00 2001
From: Simon Pichugin <spichugi@redhat.com>
Date: Tue, 27 Feb 2024 16:30:47 -0800
Subject: [PATCH] Issue 3527 - Support HAProxy and Instance on the same machine
configuration (#6107)
Description: Improve how we handle HAProxy connections to work better when
the DS and HAProxy are on the same machine.
Ensure the client and header destination IPs are checked against the trusted IP list.
Additionally, this change will also allow configuration having
HAProxy is listening on a different subnet than the one used to forward the request.
Related: https://github.com/389ds/389-ds-base/issues/3527
Reviewed by: @progier389, @jchapma (Thanks!)
---
ldap/servers/slapd/connection.c | 35 +++++++++++++++++++++++++--------
1 file changed, 27 insertions(+), 8 deletions(-)
diff --git a/ldap/servers/slapd/connection.c b/ldap/servers/slapd/connection.c
index a30511c97..07d629475 100644
--- a/ldap/servers/slapd/connection.c
+++ b/ldap/servers/slapd/connection.c
@@ -1187,6 +1187,8 @@ connection_read_operation(Connection *conn, Operation *op, ber_tag_t *tag, int *
char str_ip[INET6_ADDRSTRLEN + 1] = {0};
char str_haproxy_ip[INET6_ADDRSTRLEN + 1] = {0};
char str_haproxy_destip[INET6_ADDRSTRLEN + 1] = {0};
+ int trusted_matches_ip_found = 0;
+ int trusted_matches_destip_found = 0;
struct berval **bvals = NULL;
int proxy_connection = 0;
@@ -1245,21 +1247,38 @@ connection_read_operation(Connection *conn, Operation *op, ber_tag_t *tag, int *
normalize_IPv4(conn->cin_addr, buf_ip, sizeof(buf_ip), str_ip, sizeof(str_ip));
normalize_IPv4(&pr_netaddr_dest, buf_haproxy_destip, sizeof(buf_haproxy_destip),
str_haproxy_destip, sizeof(str_haproxy_destip));
+ size_t ip_len = strlen(buf_ip);
+ size_t destip_len = strlen(buf_haproxy_destip);
/* Now, reset RC and set it to 0 only if a match is found */
haproxy_rc = -1;
- /* Allow only:
- * Trusted IP == Original Client IP == HAProxy Header Destination IP */
+ /*
+ * We need to allow a configuration where DS instance and HAProxy are on the same machine.
+ * In this case, we need to check if
+ * the HAProxy client IP (which will be a loopback address) matches one of the the trusted IP addresses,
+ * while still checking that
+ * the HAProxy header destination IP address matches one of the trusted IP addresses.
+ * Additionally, this change will also allow configuration having
+ * HAProxy listening on a different subnet than one used to forward the request.
+ */
for (size_t i = 0; bvals[i] != NULL; ++i) {
- if ((strlen(bvals[i]->bv_val) == strlen(buf_ip)) &&
- (strlen(bvals[i]->bv_val) == strlen(buf_haproxy_destip)) &&
- (strncasecmp(bvals[i]->bv_val, buf_ip, strlen(buf_ip)) == 0) &&
- (strncasecmp(bvals[i]->bv_val, buf_haproxy_destip, strlen(buf_haproxy_destip)) == 0)) {
- haproxy_rc = 0;
- break;
+ size_t bval_len = strlen(bvals[i]->bv_val);
+
+ /* Check if the Client IP (HAProxy's machine IP) address matches the trusted IP address */
+ if (!trusted_matches_ip_found) {
+ trusted_matches_ip_found = (bval_len == ip_len) && (strncasecmp(bvals[i]->bv_val, buf_ip, ip_len) == 0);
+ }
+ /* Check if the HAProxy header destination IP address matches the trusted IP address */
+ if (!trusted_matches_destip_found) {
+ trusted_matches_destip_found = (bval_len == destip_len) && (strncasecmp(bvals[i]->bv_val, buf_haproxy_destip, destip_len) == 0);
}
}
+
+ if (trusted_matches_ip_found && trusted_matches_destip_found) {
+ haproxy_rc = 0;
+ }
+
if (haproxy_rc == -1) {
slapi_log_err(SLAPI_LOG_CONNS, "connection_read_operation", "HAProxy header received from unknown source.\n");
disconnect_server_nomutex(conn, conn->c_connid, -1, SLAPD_DISCONNECT_PROXY_UNKNOWN, EPROTO);
--
2.43.0

@ -1,237 +0,0 @@
From 3cd7d30628007f839436c417af6dd8a056c6a165 Mon Sep 17 00:00:00 2001
From: Mark Reynolds <mreynolds@redhat.com>
Date: Fri, 1 Mar 2024 11:28:17 -0500
Subject: [PATCH 2/3] Issue 6112 - RFE - add new operation note for MFA
authentications
Add a new operation note to indicate that a MFA plugin performed the
BIND. This implies that the plugin must set the note itself as there is
no other way to detect this:
slapi_pblock_set_flag_operation_notes(pb, SLAPI_OP_NOTE_MFA_AUTH);
The purpose for this is for auditing needs
Fixes: https://github.com/389ds/389-ds-base/issues/6112
Reviewed by: spichugi(Thanks!)
---
ldap/admin/src/logconv.pl | 37 ++++++++++++++++++-------------
ldap/servers/slapd/log.c | 6 ++++-
ldap/servers/slapd/result.c | 2 +-
ldap/servers/slapd/slapi-plugin.h | 1 +
4 files changed, 28 insertions(+), 18 deletions(-)
diff --git a/ldap/admin/src/logconv.pl b/ldap/admin/src/logconv.pl
index 5ba91e99c..10bd5d2aa 100755
--- a/ldap/admin/src/logconv.pl
+++ b/ldap/admin/src/logconv.pl
@@ -2,11 +2,11 @@
#
# BEGIN COPYRIGHT BLOCK
# Copyright (C) 2001 Sun Microsystems, Inc. Used by permission.
-# Copyright (C) 2022 Red Hat, Inc.
+# Copyright (C) 2010-2024 Red Hat, Inc.
# All rights reserved.
#
# License: GPL (version 3 or any later version).
-# See LICENSE for details.
+# See LICENSE for details.
# END COPYRIGHT BLOCK
#
@@ -218,6 +218,7 @@ my $sslClientFailedCount = 0;
my $objectclassTopCount= 0;
my $pagedSearchCount = 0;
my $invalidFilterCount = 0;
+my $mfaCount = 0;
my $bindCount = 0;
my $filterCount = 0;
my $baseCount = 0;
@@ -407,7 +408,7 @@ sub statusreport {
##########################################
# #
# Parse Access Logs #
-# #
+# #
##########################################
if ($files[$#files] =~ m/access.rotationinfo/) {
@@ -709,7 +710,7 @@ if($endTime){
#
# Get the start time in seconds
-#
+#
my $logStart = $start;
my $startTotal = convertTimeToNanoseconds($logStart);
@@ -890,6 +891,7 @@ $etimeAvg = $totalEtime / $etimeCount;
print sprintf "Average etime (elapsed time): %.9f\n", $etimeAvg;
print "\n";
+print "Multi-factor Authentications: $mfaCount\n";
print "Proxied Auth Operations: $proxiedAuthCount\n";
print "Persistent Searches: $persistentSrchCount\n";
print "Internal Operations: $internalOpCount\n";
@@ -1758,7 +1760,7 @@ if ($usage =~ /j/i || $verb eq "yes"){
$recCount++;
}
if ($objectclassTopCount > ($srchCount *.25)){
- print "\n $recCount. You have a high number of searches that query the entire search base. Although this is not necessarily bad, it could be resource intensive if the search base contains many entries.\n";
+ print "\n $recCount. You have a high number of searches that query the entire search base. Although this is not necessarily bad, it could be resource intensive if the search base contains many entries.\n";
$recCount++;
}
if ($recCount == 1){
@@ -1792,7 +1794,7 @@ sub displayUsage {
print " -h, --help help/usage\n";
print " -d, --rootDN <Directory Managers DN> default is \"cn=directory manager\"\n";
- print " -D, --data <Location for temporary data files> default is \"/tmp\"\n";
+ print " -D, --data <Location for temporary data files> default is \"/tmp\"\n";
print " -s, --sizeLimit <Number of results to return per catagory> default is 20\n";
print " -X, --excludeIP <IP address to exclude from connection stats> E.g. Load balancers\n";
print " -v, --version show version of tool\n";
@@ -1800,8 +1802,8 @@ sub displayUsage {
print " E.g. \"[28/Mar/2002:13:14:22 -0800]\"\n";
print " -E, --endTime <time to stop analyzing logfile>\n";
print " E.g. \"[28/Mar/2002:13:24:62 -0800]\"\n";
- print " -m, --reportFileSecs <CSV output file - per second stats>\n";
- print " -M, --reportFileMins <CSV output file - per minute stats>\n";
+ print " -m, --reportFileSecs <CSV output file - per second stats>\n";
+ print " -M, --reportFileMins <CSV output file - per minute stats>\n";
print " -B, --bind <ALL | ANONYMOUS | \"Actual Bind DN\">\n";
print " -T, --minEtime <minimum etime to report unindexed searches>\n";
print " -V, --verbose <enable verbose output - includes all stats listed below>\n";
@@ -2288,6 +2290,9 @@ sub parseLineNormal
if (m/ RESULT err=/ && m/ notes=[A-Z,]*P/){
$pagedSearchCount++;
}
+ if (m/ RESULT err=/ && m/ notes=[A-Z,]*M/){
+ $mfaCount++;
+ }
if (m/ RESULT err=/ && m/ notes=[A-Z,]*F/){
$invalidFilterCount++;
$con = "";
@@ -2318,7 +2323,7 @@ sub parseLineNormal
if ($vlvconn[$i] eq $con && $vlvop[$i] eq $op){ $vlvNotesACount++; $isVlvNotes="1";}
}
if($isVlvNotes == 0){
- # We don't want to record vlv unindexed searches for our regular "bad"
+ # We don't want to record vlv unindexed searches for our regular "bad"
# unindexed search stat, as VLV unindexed searches aren't that bad
$unindexedSrchCountNotesA++;
if($reportStats){ inc_stats('notesA',$s_stats,$m_stats); }
@@ -2345,7 +2350,7 @@ sub parseLineNormal
if ($vlvconn[$i] eq $con && $vlvop[$i] eq $op){ $vlvNotesUCount++; $isVlvNotes="1";}
}
if($isVlvNotes == 0){
- # We don't want to record vlv unindexed searches for our regular "bad"
+ # We don't want to record vlv unindexed searches for our regular "bad"
# unindexed search stat, as VLV unindexed searches aren't that bad
$unindexedSrchCountNotesU++;
if($reportStats){ inc_stats('notesU',$s_stats,$m_stats); }
@@ -2586,7 +2591,7 @@ sub parseLineNormal
if ($errcode ne "0"){ $errorCount++;}
else { $successCount++;}
}
- if ($_ =~ /etime= *([0-9.]+)/ ) {
+ if ($_ =~ /etime= *([0-9.]+)/ ) {
my $etime_val = $1;
$totalEtime = $totalEtime + $1;
$etimeCount++;
@@ -2608,10 +2613,10 @@ sub parseLineNormal
if ($reportStats){ inc_stats_val('optime',$optime_val,$s_stats,$m_stats); }
}
if ($_ =~ / tag=101 / || $_ =~ / tag=111 / || $_ =~ / tag=100 / || $_ =~ / tag=115 /){
- if ($_ =~ / nentries= *([0-9]+)/i ){
+ if ($_ =~ / nentries= *([0-9]+)/i ){
my $nents = $1;
- if ($usage =~ /n/i || $verb eq "yes"){
- $hashes->{nentries}->{$nents}++;
+ if ($usage =~ /n/i || $verb eq "yes"){
+ $hashes->{nentries}->{$nents}++;
}
}
}
@@ -2621,7 +2626,7 @@ sub parseLineNormal
if (m/ EXT oid=/){
$extopCount++;
my $oid;
- if ($_ =~ /oid=\" *([0-9\.]+)/i ){
+ if ($_ =~ /oid=\" *([0-9\.]+)/i ){
$oid = $1;
if ($usage =~ /x/i || $verb eq "yes"){$hashes->{oid}->{$oid}++; }
}
@@ -2921,7 +2926,7 @@ printClients
my $IPcount = "1";
foreach my $ip ( keys %connList ){ # Loop over all the IP addresses
- foreach my $bc (@bindConns){ # Loop over each bind conn number and compare it
+ foreach my $bc (@bindConns){ # Loop over each bind conn number and compare it
if($connList{$ip} =~ / $bc /){
print(" [$IPcount] $ip\n");
$IPcount++;
diff --git a/ldap/servers/slapd/log.c b/ldap/servers/slapd/log.c
index 4aa905576..2c7bd933b 100644
--- a/ldap/servers/slapd/log.c
+++ b/ldap/servers/slapd/log.c
@@ -3892,6 +3892,7 @@ slapi_log_security(Slapi_PBlock *pb, const char *event_type, const char *msg)
int isroot = 0;
int rc = 0;
uint64_t conn_id = 0;
+ uint32_t operation_notes = 0;
int32_t op_id = 0;
json_object *log_json = NULL;
@@ -3916,6 +3917,8 @@ slapi_log_security(Slapi_PBlock *pb, const char *event_type, const char *msg)
client_ip = pb_conn->c_ipaddr;
server_ip = pb_conn->c_serveripaddr;
ldap_version = pb_conn->c_ldapversion;
+ operation_notes = slapi_pblock_get_operation_notes(pb);
+
if (saslmech) {
external_bind = !strcasecmp(saslmech, LDAP_SASL_EXTERNAL);
}
@@ -3982,7 +3985,8 @@ slapi_log_security(Slapi_PBlock *pb, const char *event_type, const char *msg)
break;
default:
/* Simple auth */
- PR_snprintf(method_and_mech, sizeof(method_and_mech), "SIMPLE");
+ PR_snprintf(method_and_mech, sizeof(method_and_mech), "%s",
+ (operation_notes & SLAPI_OP_NOTE_MFA_AUTH) ? "SIMPLE/MFA" : "SIMPLE");
}
/* Get the time */
diff --git a/ldap/servers/slapd/result.c b/ldap/servers/slapd/result.c
index 56ba6db8b..97af5a2b8 100644
--- a/ldap/servers/slapd/result.c
+++ b/ldap/servers/slapd/result.c
@@ -1946,11 +1946,11 @@ static struct slapi_note_map notemap[] = {
{SLAPI_OP_NOTE_SIMPLEPAGED, "P", "Paged Search"},
{SLAPI_OP_NOTE_FULL_UNINDEXED, "A", "Fully Unindexed Filter"},
{SLAPI_OP_NOTE_FILTER_INVALID, "F", "Filter Element Missing From Schema"},
+ {SLAPI_OP_NOTE_MFA_AUTH, "M", "Multi-factor Authentication"},
};
#define SLAPI_NOTEMAP_COUNT (sizeof(notemap) / sizeof(struct slapi_note_map))
-
/*
* fill buf with a string representation of the bits present in notes.
*
diff --git a/ldap/servers/slapd/slapi-plugin.h b/ldap/servers/slapd/slapi-plugin.h
index 4853e143b..12bc1f0aa 100644
--- a/ldap/servers/slapd/slapi-plugin.h
+++ b/ldap/servers/slapd/slapi-plugin.h
@@ -7323,6 +7323,7 @@ typedef enum _slapi_op_note_t {
SLAPI_OP_NOTE_SIMPLEPAGED = 0x02,
SLAPI_OP_NOTE_FULL_UNINDEXED = 0x04,
SLAPI_OP_NOTE_FILTER_INVALID = 0x08,
+ SLAPI_OP_NOTE_MFA_AUTH = 0x10,
} slapi_op_note_t;
--
2.44.0

@ -1,54 +0,0 @@
From 189e078f574f586f6cff6f80081eded2c22c8868 Mon Sep 17 00:00:00 2001
From: Mark Reynolds <mreynolds@redhat.com>
Date: Tue, 26 Mar 2024 11:19:10 -0400
Subject: [PATCH 3/3] Issue 6133 - Move slapi_pblock_set_flag_operation_notes()
to slapi-plugin.h
Description:
slapi_pblock_set_flag_operation_notes() is currently only available in slapi-private.h, but with the latest changes at add "notes=M" it needs to be available to plugins.
relates: https://github.com/389ds/389-ds-base/issues/6133
Reviewed by: spichugi(Thanks!)
---
ldap/servers/slapd/slapi-plugin.h | 10 ++++++++++
ldap/servers/slapd/slapi-private.h | 1 -
2 files changed, 10 insertions(+), 1 deletion(-)
diff --git a/ldap/servers/slapd/slapi-plugin.h b/ldap/servers/slapd/slapi-plugin.h
index 12bc1f0aa..5d4af7c20 100644
--- a/ldap/servers/slapd/slapi-plugin.h
+++ b/ldap/servers/slapd/slapi-plugin.h
@@ -7326,6 +7326,16 @@ typedef enum _slapi_op_note_t {
SLAPI_OP_NOTE_MFA_AUTH = 0x10,
} slapi_op_note_t;
+/**
+ * Set an operation note on an operation. This will append a notes keyword
+ * in the access log result line for this operation
+ *
+ * \param pb - The slapi_pblock structure
+ * \param opnotes
+ * \return void
+ */
+void slapi_pblock_set_operation_notes(Slapi_PBlock *pb, uint32_t opnotes);
+
/* Allows controls to be passed before operation object is created */
#define SLAPI_CONTROLS_ARG 58
diff --git a/ldap/servers/slapd/slapi-private.h b/ldap/servers/slapd/slapi-private.h
index 17eedc2de..ee7659ac0 100644
--- a/ldap/servers/slapd/slapi-private.h
+++ b/ldap/servers/slapd/slapi-private.h
@@ -1510,7 +1510,6 @@ struct slapi_entry *slapi_pblock_get_pw_entry(Slapi_PBlock *pb);
void slapi_pblock_set_pw_entry(Slapi_PBlock *pb, struct slapi_entry *entry);
uint32_t slapi_pblock_get_operation_notes(Slapi_PBlock *pb);
-void slapi_pblock_set_operation_notes(Slapi_PBlock *pb, uint32_t opnotes);
void slapi_pblock_set_flag_operation_notes(Slapi_PBlock *pb, uint32_t opflag);
void slapi_pblock_set_result_text_if_empty(Slapi_PBlock *pb, char *text);
--
2.44.0

@ -1,108 +0,0 @@
From 23956cfb86a312318667fb9376322574fa8ec7f4 Mon Sep 17 00:00:00 2001
From: James Chapman <jachapma@redhat.com>
Date: Wed, 1 May 2024 15:01:33 +0100
Subject: [PATCH] CVE-2024-2199
---
.../tests/suites/password/password_test.py | 56 +++++++++++++++++++
ldap/servers/slapd/modify.c | 8 ++-
2 files changed, 62 insertions(+), 2 deletions(-)
diff --git a/dirsrvtests/tests/suites/password/password_test.py b/dirsrvtests/tests/suites/password/password_test.py
index 1245feb31..e4abd9907 100644
--- a/dirsrvtests/tests/suites/password/password_test.py
+++ b/dirsrvtests/tests/suites/password/password_test.py
@@ -63,6 +63,62 @@ def test_password_delete_specific_password(topology_st):
log.info('test_password_delete_specific_password: PASSED')
+def test_password_modify_non_utf8(topology_st):
+ """Attempt a modify of the userPassword attribute with
+ an invalid non utf8 value
+
+ :id: a31af9d5-d665-42b9-8d6e-fea3d0837d36
+ :setup: Standalone instance
+ :steps:
+ 1. Add a user if it doesnt exist and set its password
+ 2. Verify password with a bind
+ 3. Modify userPassword attr with invalid value
+ 4. Attempt a bind with invalid password value
+ 5. Verify original password with a bind
+ :expectedresults:
+ 1. The user with userPassword should be added successfully
+ 2. Operation should be successful
+ 3. Server returns ldap.UNWILLING_TO_PERFORM
+ 4. Server returns ldap.INVALID_CREDENTIALS
+ 5. Operation should be successful
+ """
+
+ log.info('Running test_password_modify_non_utf8...')
+
+ # Create user and set password
+ standalone = topology_st.standalone
+ users = UserAccounts(standalone, DEFAULT_SUFFIX)
+ if not users.exists(TEST_USER_PROPERTIES['uid'][0]):
+ user = users.create(properties=TEST_USER_PROPERTIES)
+ else:
+ user = users.get(TEST_USER_PROPERTIES['uid'][0])
+ user.set('userpassword', PASSWORD)
+
+ # Verify password
+ try:
+ user.bind(PASSWORD)
+ except ldap.LDAPError as e:
+ log.fatal('Failed to bind as {}, error: '.format(user.dn) + e.args[0]['desc'])
+ assert False
+
+ # Modify userPassword with an invalid value
+ password = b'tes\x82t-password' # A non UTF-8 encoded password
+ with pytest.raises(ldap.UNWILLING_TO_PERFORM):
+ user.replace('userpassword', password)
+
+ # Verify a bind fails with invalid pasword
+ with pytest.raises(ldap.INVALID_CREDENTIALS):
+ user.bind(password)
+
+ # Verify we can still bind with original password
+ try:
+ user.bind(PASSWORD)
+ except ldap.LDAPError as e:
+ log.fatal('Failed to bind as {}, error: '.format(user.dn) + e.args[0]['desc'])
+ assert False
+
+ log.info('test_password_modify_non_utf8: PASSED')
+
if __name__ == '__main__':
# Run isolated
# -s for DEBUG mode
diff --git a/ldap/servers/slapd/modify.c b/ldap/servers/slapd/modify.c
index a20984e0b..fb65d58b3 100644
--- a/ldap/servers/slapd/modify.c
+++ b/ldap/servers/slapd/modify.c
@@ -762,8 +762,10 @@ op_shared_modify(Slapi_PBlock *pb, int pw_change, char *old_pw)
* flagged - leave mod attributes alone */
if (!repl_op && !skip_modified_attrs && lastmod) {
modify_update_last_modified_attr(pb, &smods);
+ slapi_pblock_set(pb, SLAPI_MODIFY_MODS, slapi_mods_get_ldapmods_byref(&smods));
}
+
if (0 == slapi_mods_get_num_mods(&smods)) {
/* nothing to do - no mods - this is not an error - just
send back LDAP_SUCCESS */
@@ -930,8 +932,10 @@ op_shared_modify(Slapi_PBlock *pb, int pw_change, char *old_pw)
/* encode password */
if (pw_encodevals_ext(pb, sdn, va)) {
- slapi_log_err(SLAPI_LOG_CRIT, "op_shared_modify", "Unable to hash userPassword attribute for %s.\n", slapi_entry_get_dn_const(e));
- send_ldap_result(pb, LDAP_UNWILLING_TO_PERFORM, NULL, "Unable to store attribute \"userPassword\" correctly\n", 0, NULL);
+ slapi_log_err(SLAPI_LOG_CRIT, "op_shared_modify", "Unable to hash userPassword attribute for %s, "
+ "check value is utf8 string.\n", slapi_entry_get_dn_const(e));
+ send_ldap_result(pb, LDAP_UNWILLING_TO_PERFORM, NULL, "Unable to hash \"userPassword\" attribute, "
+ "check value is utf8 string.\n", 0, NULL);
valuearray_free(&va);
goto free_and_return;
}
--
2.41.0

@ -1,213 +0,0 @@
From 5cfa136c48c477765cb20b007ad441ed21534e86 Mon Sep 17 00:00:00 2001
From: Pierre Rogier <progier@redhat.com>
Date: Wed, 17 Apr 2024 18:18:04 +0200
Subject: [PATCH] CVE-2024-3657
---
.../tests/suites/filter/large_filter_test.py | 34 +++++-
ldap/servers/slapd/back-ldbm/index.c | 111 ++++++++++--------
2 files changed, 92 insertions(+), 53 deletions(-)
diff --git a/dirsrvtests/tests/suites/filter/large_filter_test.py b/dirsrvtests/tests/suites/filter/large_filter_test.py
index 964facae5..5390a0f9c 100644
--- a/dirsrvtests/tests/suites/filter/large_filter_test.py
+++ b/dirsrvtests/tests/suites/filter/large_filter_test.py
@@ -13,19 +13,29 @@ verify and testing Filter from a search
import os
import pytest
+import ldap
-from lib389._constants import PW_DM
+from lib389._constants import PW_DM, DEFAULT_SUFFIX, ErrorLog
from lib389.topologies import topology_st as topo
from lib389.idm.user import UserAccounts, UserAccount
from lib389.idm.account import Accounts
from lib389.backend import Backends
from lib389.idm.domain import Domain
+from lib389.utils import get_ldapurl_from_serverid
SUFFIX = 'dc=anuj,dc=com'
pytestmark = pytest.mark.tier1
+def open_new_ldapi_conn(dsinstance):
+ ldapurl, certdir = get_ldapurl_from_serverid(dsinstance)
+ assert 'ldapi://' in ldapurl
+ conn = ldap.initialize(ldapurl)
+ conn.sasl_interactive_bind_s("", ldap.sasl.external())
+ return conn
+
+
@pytest.fixture(scope="module")
def _create_entries(request, topo):
"""
@@ -159,6 +169,28 @@ def test_large_filter(topo, _create_entries, real_value):
assert len(Accounts(conn, SUFFIX).filter(real_value)) == 3
+def test_long_filter_value(topo):
+ """Exercise large eq filter with dn syntax attributes
+
+ :id: b069ef72-fcc3-11ee-981c-482ae39447e5
+ :setup: Standalone
+ :steps:
+ 1. Try to pass filter rules as per the condition.
+ :expectedresults:
+ 1. Pass
+ """
+ inst = topo.standalone
+ conn = open_new_ldapi_conn(inst.serverid)
+ inst.config.loglevel(vals=(ErrorLog.DEFAULT,ErrorLog.TRACE,ErrorLog.SEARCH_FILTER))
+ filter_value = "a\x1Edmin" * 1025
+ conn.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE, f'(cn={filter_value})')
+ filter_value = "aAdmin" * 1025
+ conn.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE, f'(cn={filter_value})')
+ filter_value = "*"
+ conn.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE, f'(cn={filter_value})')
+ inst.config.loglevel(vals=(ErrorLog.DEFAULT,))
+
+
if __name__ == '__main__':
CURRENT_FILE = os.path.realpath(__file__)
pytest.main("-s -v %s" % CURRENT_FILE)
diff --git a/ldap/servers/slapd/back-ldbm/index.c b/ldap/servers/slapd/back-ldbm/index.c
index 86bc825fe..bdac0a616 100644
--- a/ldap/servers/slapd/back-ldbm/index.c
+++ b/ldap/servers/slapd/back-ldbm/index.c
@@ -74,6 +74,32 @@ typedef struct _index_buffer_handle index_buffer_handle;
#define INDEX_BUFFER_FLAG_SERIALIZE 1
#define INDEX_BUFFER_FLAG_STATS 2
+/*
+ * space needed to encode a byte:
+ * 0x00-0x31 and 0x7f-0xff requires 3 bytes: \xx
+ * 0x22 and 0x5C requires 2 bytes: \" and \\
+ * other requires 1 byte: c
+ */
+static char encode_size[] = {
+ /* 0x00 */ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ /* 0x10 */ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ /* 0x20 */ 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ /* 0x30 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ /* 0x40 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ /* 0x50 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1,
+ /* 0x60 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ /* 0x70 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3,
+ /* 0x80 */ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ /* 0x90 */ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ /* 0xA0 */ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ /* 0xB0 */ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ /* 0xC0 */ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ /* 0xD0 */ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ /* 0xE0 */ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ /* 0xF0 */ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+};
+
+
/* Index buffering functions */
static int
@@ -802,65 +828,46 @@ index_add_mods(
/*
* Convert a 'struct berval' into a displayable ASCII string
+ * returns the printable string
*/
-
-#define SPECIAL(c) (c < 32 || c > 126 || c == '\\' || c == '"')
-
const char *
encode(const struct berval *data, char buf[BUFSIZ])
{
- char *s;
- char *last;
- if (data == NULL || data->bv_len == 0)
- return "";
- last = data->bv_val + data->bv_len - 1;
- for (s = data->bv_val; s < last; ++s) {
- if (SPECIAL(*s)) {
- char *first = data->bv_val;
- char *bufNext = buf;
- size_t bufSpace = BUFSIZ - 4;
- while (1) {
- /* printf ("%lu bytes ASCII\n", (unsigned long)(s - first)); */
- if (bufSpace < (size_t)(s - first))
- s = first + bufSpace - 1;
- if (s != first) {
- memcpy(bufNext, first, s - first);
- bufNext += (s - first);
- bufSpace -= (s - first);
- }
- do {
- if (bufSpace) {
- *bufNext++ = '\\';
- --bufSpace;
- }
- if (bufSpace < 2) {
- memcpy(bufNext, "..", 2);
- bufNext += 2;
- goto bail;
- }
- if (*s == '\\' || *s == '"') {
- *bufNext++ = *s;
- --bufSpace;
- } else {
- sprintf(bufNext, "%02x", (unsigned)*(unsigned char *)s);
- bufNext += 2;
- bufSpace -= 2;
- }
- } while (++s <= last && SPECIAL(*s));
- if (s > last)
- break;
- first = s;
- while (!SPECIAL(*s) && s <= last)
- ++s;
- }
- bail:
- *bufNext = '\0';
- /* printf ("%lu chars in buffer\n", (unsigned long)(bufNext - buf)); */
+ if (!data || !data->bv_val) {
+ strcpy(buf, "<NULL>");
+ return buf;
+ }
+ char *endbuff = &buf[BUFSIZ-4]; /* Reserve space to append "...\0" */
+ char *ptout = buf;
+ unsigned char *ptin = (unsigned char*) data->bv_val;
+ unsigned char *endptin = ptin+data->bv_len;
+
+ while (ptin < endptin) {
+ if (ptout >= endbuff) {
+ /*
+ * BUFSIZ(8K) > SLAPI_LOG_BUFSIZ(2K) so the error log message will be
+ * truncated anyway. So there is no real interrest to test if the original
+ * data contains no special characters and return it as is.
+ */
+ strcpy(endbuff, "...");
return buf;
}
+ switch (encode_size[*ptin]) {
+ case 1:
+ *ptout++ = *ptin++;
+ break;
+ case 2:
+ *ptout++ = '\\';
+ *ptout++ = *ptin++;
+ break;
+ case 3:
+ sprintf(ptout, "\\%02x", *ptin++);
+ ptout += 3;
+ break;
+ }
}
- /* printf ("%lu bytes, all ASCII\n", (unsigned long)(s - data->bv_val)); */
- return data->bv_val;
+ *ptout = 0;
+ return buf;
}
static const char *
--
2.44.0

@ -1,145 +0,0 @@
From 52a9ee6556a0467f5134fb6392ff1681a38f3252 Mon Sep 17 00:00:00 2001
From: Pierre Rogier <progier@redhat.com>
Date: Fri, 14 Jun 2024 13:27:10 +0200
Subject: [PATCH] CVE-2024-5953
---
.../tests/suites/password/regression_test.py | 51 ++++++++++++++++++-
ldap/servers/plugins/pwdstorage/md5_pwd.c | 9 +++-
ldap/servers/plugins/pwdstorage/pbkdf2_pwd.c | 6 +++
3 files changed, 64 insertions(+), 2 deletions(-)
diff --git a/dirsrvtests/tests/suites/password/regression_test.py b/dirsrvtests/tests/suites/password/regression_test.py
index 4876ff435..160d6f01d 100644
--- a/dirsrvtests/tests/suites/password/regression_test.py
+++ b/dirsrvtests/tests/suites/password/regression_test.py
@@ -8,11 +8,12 @@
import pytest
import time
import glob
+import base64
from lib389._constants import PASSWORD, DN_DM, DEFAULT_SUFFIX
from lib389._constants import SUFFIX, PASSWORD, DN_DM, DN_CONFIG, PLUGIN_RETRO_CHANGELOG, DEFAULT_SUFFIX, DEFAULT_CHANGELOG_DB, DEFAULT_BENAME
from lib389 import Entry
from lib389.topologies import topology_m1 as topo_supplier
-from lib389.idm.user import UserAccounts
+from lib389.idm.user import UserAccounts, UserAccount
from lib389.utils import ldap, os, logging, ensure_bytes, ds_is_newer, ds_supports_new_changelog
from lib389.topologies import topology_st as topo
from lib389.idm.organizationalunit import OrganizationalUnits
@@ -40,6 +41,13 @@ TEST_PASSWORDS += ['CNpwtest1ZZZZ', 'ZZZZZCNpwtest1',
TEST_PASSWORDS2 = (
'CN12pwtest31', 'SN3pwtest231', 'UID1pwtest123', 'MAIL2pwtest12@redhat.com', '2GN1pwtest123', 'People123')
+SUPPORTED_SCHEMES = (
+ "{SHA}", "{SSHA}", "{SHA256}", "{SSHA256}",
+ "{SHA384}", "{SSHA384}", "{SHA512}", "{SSHA512}",
+ "{crypt}", "{NS-MTA-MD5}", "{clear}", "{MD5}",
+ "{SMD5}", "{PBKDF2_SHA256}", "{PBKDF2_SHA512}",
+ "{GOST_YESCRYPT}", "{PBKDF2-SHA256}", "{PBKDF2-SHA512}" )
+
def _check_unhashed_userpw(inst, user_dn, is_present=False):
"""Check if unhashed#user#password attribute is present or not in the changelog"""
unhashed_pwd_attribute = 'unhashed#user#password'
@@ -319,6 +327,47 @@ def test_unhashed_pw_switch(topo_supplier):
# Add debugging steps(if any)...
pass
+@pytest.mark.parametrize("scheme", SUPPORTED_SCHEMES )
+def test_long_hashed_password(topo, create_user, scheme):
+ """Check that hashed password with very long value does not cause trouble
+
+ :id: 252a1f76-114b-11ef-8a7a-482ae39447e5
+ :setup: standalone Instance
+ :parametrized: yes
+ :steps:
+ 1. Add a test user user
+ 2. Set a long password with requested scheme
+ 3. Bind on that user using a wrong password
+ 4. Check that instance is still alive
+ 5. Remove the added user
+ :expectedresults:
+ 1. Success
+ 2. Success
+ 3. Should get ldap.INVALID_CREDENTIALS exception
+ 4. Success
+ 5. Success
+ """
+ inst = topo.standalone
+ inst.simple_bind_s(DN_DM, PASSWORD)
+ users = UserAccounts(inst, DEFAULT_SUFFIX)
+ # Make sure that server is started as this test may crash it
+ inst.start()
+ # Adding Test user (It may already exists if previous test failed)
+ user2 = UserAccount(inst, dn='uid=test_user_1002,ou=People,dc=example,dc=com')
+ if not user2.exists():
+ user2 = users.create_test_user(uid=1002, gid=2002)
+ # Setting hashed password
+ passwd = 'A'*4000
+ hashed_passwd = scheme.encode('utf-8') + base64.b64encode(passwd.encode('utf-8'))
+ user2.replace('userpassword', hashed_passwd)
+ # Bind on that user using a wrong password
+ with pytest.raises(ldap.INVALID_CREDENTIALS):
+ conn = user2.bind(PASSWORD)
+ # Check that instance is still alive
+ assert inst.status()
+ # Remove the added user
+ user2.delete()
+
if __name__ == '__main__':
# Run isolated
diff --git a/ldap/servers/plugins/pwdstorage/md5_pwd.c b/ldap/servers/plugins/pwdstorage/md5_pwd.c
index 1e2cf58e7..b9a48d5ca 100644
--- a/ldap/servers/plugins/pwdstorage/md5_pwd.c
+++ b/ldap/servers/plugins/pwdstorage/md5_pwd.c
@@ -37,6 +37,7 @@ md5_pw_cmp(const char *userpwd, const char *dbpwd)
unsigned char hash_out[MD5_HASH_LEN];
unsigned char b2a_out[MD5_HASH_LEN * 2]; /* conservative */
SECItem binary_item;
+ size_t dbpwd_len = strlen(dbpwd);
ctx = PK11_CreateDigestContext(SEC_OID_MD5);
if (ctx == NULL) {
@@ -45,6 +46,12 @@ md5_pw_cmp(const char *userpwd, const char *dbpwd)
goto loser;
}
+ if (dbpwd_len >= sizeof b2a_out) {
+ slapi_log_err(SLAPI_LOG_PLUGIN, MD5_SUBSYSTEM_NAME,
+ "The hashed password stored in the user entry is longer than any valid md5 hash");
+ goto loser;
+ }
+
/* create the hash */
PK11_DigestBegin(ctx);
PK11_DigestOp(ctx, (const unsigned char *)userpwd, strlen(userpwd));
@@ -57,7 +64,7 @@ md5_pw_cmp(const char *userpwd, const char *dbpwd)
bver = NSSBase64_EncodeItem(NULL, (char *)b2a_out, sizeof b2a_out, &binary_item);
/* bver points to b2a_out upon success */
if (bver) {
- rc = slapi_ct_memcmp(bver, dbpwd, strlen(dbpwd));
+ rc = slapi_ct_memcmp(bver, dbpwd, dbpwd_len);
} else {
slapi_log_err(SLAPI_LOG_PLUGIN, MD5_SUBSYSTEM_NAME,
"Could not base64 encode hashed value for password compare");
diff --git a/ldap/servers/plugins/pwdstorage/pbkdf2_pwd.c b/ldap/servers/plugins/pwdstorage/pbkdf2_pwd.c
index dcac4fcdd..82b8c9501 100644
--- a/ldap/servers/plugins/pwdstorage/pbkdf2_pwd.c
+++ b/ldap/servers/plugins/pwdstorage/pbkdf2_pwd.c
@@ -255,6 +255,12 @@ pbkdf2_sha256_pw_cmp(const char *userpwd, const char *dbpwd)
passItem.data = (unsigned char *)userpwd;
passItem.len = strlen(userpwd);
+ if (pwdstorage_base64_decode_len(dbpwd, dbpwd_len) > sizeof dbhash) {
+ /* Hashed value is too long and cannot match any value generated by pbkdf2_sha256_hash */
+ slapi_log_err(SLAPI_LOG_ERR, (char *)schemeName, "Unable to base64 decode dbpwd value. (hashed value is too long)\n");
+ return result;
+ }
+
/* Decode the DBpwd to bytes from b64 */
if (PL_Base64Decode(dbpwd, dbpwd_len, dbhash) == NULL) {
slapi_log_err(SLAPI_LOG_ERR, (char *)schemeName, "Unable to base64 decode dbpwd value\n");
--
2.44.0

@ -1,25 +0,0 @@
From 323f74c69f84a8482413ecd73cf61d09cfc4a0a1 Mon Sep 17 00:00:00 2001
From: Thierry Bordaz <tbordaz@redhat.com>
Date: Mon, 24 Jun 2024 15:51:28 +0200
Subject: [PATCH] CVE-2024-6237
---
ldap/servers/plugins/syntaxes/inchain.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/ldap/servers/plugins/syntaxes/inchain.c b/ldap/servers/plugins/syntaxes/inchain.c
index df19c973b..0a6a04e9f 100644
--- a/ldap/servers/plugins/syntaxes/inchain.c
+++ b/ldap/servers/plugins/syntaxes/inchain.c
@@ -277,7 +277,7 @@ inchain_values2keys(Slapi_PBlock *pb, Slapi_Value **vals, Slapi_Value ***ivals,
slapi_pblock_get(pb, SLAPI_SEARCH_TARGET_SDN, &base_sdn);
if (! slapi_attr_is_dn_syntax_type(mrTYPE)) {
- slapi_log_err(SLAPI_LOG_ERR, "inchain", "Requires distinguishedName syntax. AttributeDescription %s is not distinguishedName\n");
+ slapi_log_err(SLAPI_LOG_ERR, "inchain", "Requires distinguishedName syntax. AttributeDescription %s is not distinguishedName\n", mrTYPE);
result = (Slapi_Value **)slapi_ch_calloc(1, sizeof(Slapi_Value *));
*ivals = result;
return(0);
--
2.44.0

@ -1,192 +0,0 @@
From 9501c34df01e35f483201a4bba12a93091b2b13f Mon Sep 17 00:00:00 2001
From: progier389 <progier@redhat.com>
Date: Thu, 13 Jun 2024 15:17:36 +0200
Subject: [PATCH] Issue 5772 - ONE LEVEL search fails to return sub-suffixes
(#6219)
Problem: ONE LEVEL scoped search fails to return sub-suffixes entries
Reason: When such search is done, a one level search is done on the main suffix and base search are done on any matching sub-suffix. But main suffix is processed search (to ensure that parent entries are returned before children ones when searching subtree) and ldbm_back_search change the filter to (&(parentid=xxx)old_filter) so the filter test reject the entry on the sub-suffixes.
Solution: Revert the backend list when doing one level search so that the sub-suffixes are processed first
and restore the base dn for the main suffix.
Alternative rejected: reset the filter when discivering a sub-suffix. Not so easy because filter is altered by the rewriteres.
And systematic duplication is an useless overhead if there is no matching sub-suffixes (which is the usual case)
Issue: #5772
Reviewed by: @tbordaz, @droideck (Thanks!)
(cherry picked from commit 407bdaa00d1da9f5ff53d66a2e012b17ad658907)
---
.../suites/mapping_tree/regression_test.py | 36 +++++++++++++++++-
ldap/servers/slapd/filterentry.c | 38 ++++++++++++++++++-
ldap/servers/slapd/opshared.c | 22 ++++++++++-
3 files changed, 92 insertions(+), 4 deletions(-)
diff --git a/dirsrvtests/tests/suites/mapping_tree/regression_test.py b/dirsrvtests/tests/suites/mapping_tree/regression_test.py
index f4877da2b..c3fc2c0a2 100644
--- a/dirsrvtests/tests/suites/mapping_tree/regression_test.py
+++ b/dirsrvtests/tests/suites/mapping_tree/regression_test.py
@@ -92,7 +92,6 @@ EXPECTED_ENTRIES = (("dc=parent", 39), ("dc=child1,dc=parent", 13), ("dc=child2,
@pytest.mark.skipif(not has_orphan_attribute, reason = "compatibility attribute not yet implemented in this version")
def test_sub_suffixes(topo, orphan_param):
""" check the entries found on suffix/sub-suffix
- used int
:id: 5b4421c2-d851-11ec-a760-482ae39447e5
:feature: mapping-tree
@@ -122,8 +121,41 @@ def test_sub_suffixes(topo, orphan_param):
log.info(f'Verifying domain component entries count for search under {suffix} ...')
entries = topo.standalone.search_s(suffix, ldap.SCOPE_SUBTREE, "(dc=*)")
assert len(entries) == expected
- log.info('Found {expected} domain component entries as expected while searching {suffix}')
+ log.info(f'Found {expected} domain component entries as expected while searching {suffix}')
log.info('Test PASSED')
+def test_one_level_search_on_sub_suffixes(topo):
+ """ Perform one level scoped search accross suffix and sub-suffix
+
+ :id: 92f3139e-280e-11ef-a989-482ae39447e5
+ :feature: mapping-tree
+ :setup: Standalone instance with 3 additional backends:
+ dc=parent, dc=child1,dc=parent, dc=childr21,dc=parent
+ :steps:
+ 1. Perform a ONE LEVEL search on dc=parent
+ 2. Check that all expected entries have been returned
+ 3. Check that only the expected entries have been returned
+ :expectedresults:
+ 1. Success
+ 2. each expected dn should be in the result set
+ 3. Number of returned entries should be the same as the number of expected entries
+ """
+ expected_dns = ( 'dc=child1,dc=parent',
+ 'dc=child2,dc=parent',
+ 'ou=accounting,dc=parent',
+ 'ou=product development,dc=parent',
+ 'ou=product testing,dc=parent',
+ 'ou=human resources,dc=parent',
+ 'ou=payroll,dc=parent',
+ 'ou=people,dc=parent',
+ 'ou=groups,dc=parent', )
+ entries = topo.standalone.search_s("dc=parent", ldap.SCOPE_ONELEVEL, "(objectClass=*)",
+ attrlist=("dc","ou"), escapehatch='i am sure')
+ log.info(f'one level search on dc=parent returned the following entries: {entries}')
+ dns = [ entry.dn for entry in entries ]
+ for dn in expected_dns:
+ assert dn in dns
+ assert len(entries) == len(expected_dns)
+
diff --git a/ldap/servers/slapd/filterentry.c b/ldap/servers/slapd/filterentry.c
index 4de4aa66e..d2c7e3082 100644
--- a/ldap/servers/slapd/filterentry.c
+++ b/ldap/servers/slapd/filterentry.c
@@ -240,6 +240,36 @@ slapi_filter_test_ext(
}
+static const char *
+filter_type_as_string(int filter_type)
+{
+ switch (filter_type) {
+ case LDAP_FILTER_AND:
+ return "&";
+ case LDAP_FILTER_OR:
+ return "|";
+ case LDAP_FILTER_NOT:
+ return "!";
+ case LDAP_FILTER_EQUALITY:
+ return "=";
+ case LDAP_FILTER_SUBSTRINGS:
+ return "*";
+ case LDAP_FILTER_GE:
+ return ">=";
+ case LDAP_FILTER_LE:
+ return "<=";
+ case LDAP_FILTER_PRESENT:
+ return "=*";
+ case LDAP_FILTER_APPROX:
+ return "~";
+ case LDAP_FILTER_EXT:
+ return "EXT";
+ default:
+ return "?";
+ }
+}
+
+
int
test_ava_filter(
Slapi_PBlock *pb,
@@ -253,7 +283,13 @@ test_ava_filter(
{
int rc;
- slapi_log_err(SLAPI_LOG_FILTER, "test_ava_filter", "=>\n");
+ if (slapi_is_loglevel_set(SLAPI_LOG_FILTER)) {
+ char *val = slapi_berval_get_string_copy(&ava->ava_value);
+ char buf[BUFSIZ];
+ slapi_log_err(SLAPI_LOG_FILTER, "test_ava_filter", "=> AVA: %s%s%s\n",
+ ava->ava_type, filter_type_as_string(ftype), escape_string(val, buf));
+ slapi_ch_free_string(&val);
+ }
*access_check_done = 0;
diff --git a/ldap/servers/slapd/opshared.c b/ldap/servers/slapd/opshared.c
index f77043afa..540597f45 100644
--- a/ldap/servers/slapd/opshared.c
+++ b/ldap/servers/slapd/opshared.c
@@ -219,6 +219,7 @@ cache_return_target_entry(Slapi_PBlock *pb, Slapi_Backend *be, Slapi_Operation *
operation_set_target_entry_id(operation, 0);
}
}
+
/*
* Returns: 0 - if the operation is successful
* < 0 - if operation fails.
@@ -481,6 +482,20 @@ op_shared_search(Slapi_PBlock *pb, int send_result)
while (be_list[index] && be_list[index + 1]) {
index++;
}
+ if (scope == LDAP_SCOPE_ONELEVEL) {
+ /*
+ * ONE LEVEL searches may ends up on multiple backends
+ * with a ONE LEVEL search on a suffix and a BASE search on its
+ * subsuffixes. Because LDAP_SCOPE_ONELEVEL rewrite the filter
+ * the backends should be reversed so that the BASE search(es)
+ * are done first (with the original filter).
+ */
+ for (int idx = 0; idx <= index/2; idx++) {
+ be = be_list[index-idx];
+ be_list[index-idx] = be_list[idx];
+ be_list[idx] = be;
+ }
+ }
be = be_list[index];
} else {
be = NULL;
@@ -779,7 +794,6 @@ op_shared_search(Slapi_PBlock *pb, int send_result)
(slapi_sdn_get_ndn_len(basesdn) == 0)) {
int tmp_scope = LDAP_SCOPE_BASE;
slapi_pblock_set(pb, SLAPI_SEARCH_SCOPE, &tmp_scope);
-
if (free_sdn) {
slapi_pblock_get(pb, SLAPI_SEARCH_TARGET_SDN, &sdn);
slapi_sdn_free(&sdn);
@@ -790,6 +804,12 @@ op_shared_search(Slapi_PBlock *pb, int send_result)
} else if (slapi_sdn_issuffix(basesdn, be_suffix)) {
int tmp_scope = LDAP_SCOPE_ONELEVEL;
slapi_pblock_set(pb, SLAPI_SEARCH_SCOPE, &tmp_scope);
+ if (free_sdn) {
+ slapi_pblock_get(pb, SLAPI_SEARCH_TARGET_SDN, &sdn);
+ slapi_sdn_free(&sdn);
+ sdn = slapi_sdn_dup(basesdn);
+ slapi_pblock_set(pb, SLAPI_SEARCH_TARGET_SDN, (void *)sdn);
+ }
} else {
slapi_sdn_done(&monitorsdn);
goto next_be;
--
2.45.2

@ -1,220 +0,0 @@
From 45e14d64c47080951d9bfa3bcfe8c267d01ce251 Mon Sep 17 00:00:00 2001
From: tbordaz <tbordaz@redhat.com>
Date: Wed, 22 May 2024 11:29:05 +0200
Subject: [PATCH] Issue 6172 - RFE: improve the performance of evaluation of
filter component when tested against a large valueset (like group members)
(#6173)
Bug description:
Before returning an entry (to a SRCH) the server checks that the entry matches the SRCH filter.
If a filter component (equality) is testing the value (ava) against a
large valueset (like uniquemember values), it takes a long time because
of the large number of values and required normalization of the values.
This can be improved taking benefit of sorted valueset. Those sorted
valueset were created to improve updates of large valueset (groups) but
at that time not implemented in SRCH path.
Fix description:
In case of LDAP_FILTER_EQUALITY component, the server can get
benefit of the sorted valuearray.
To limit the risk of regression, we use the sorted valuearray
only for the DN syntax attribute. Indeed the sorted valuearray was
designed for those type of attribute.
With those two limitations, there is no need of a toggle and
the call to plugin_call_syntax_filter_ava can be replaced by
a call to slapi_valueset_find.
In both cases, sorted valueset and plugin_call_syntax_filter_ava, ava and
values are normalized.
In sorted valueset, the values have been normalized to insert the index
in the sorted array and then comparison is done on normalized values.
In plugin_call_syntax_filter_ava, all values in valuearray (of valueset) are normalized
before comparison.
relates: #6172
Reviewed by: Pierre Rogier, Simon Pichugin (Big Thanks !!!)
---
.../tests/suites/filter/filter_test.py | 125 ++++++++++++++++++
ldap/servers/slapd/filterentry.c | 22 ++-
2 files changed, 146 insertions(+), 1 deletion(-)
diff --git a/dirsrvtests/tests/suites/filter/filter_test.py b/dirsrvtests/tests/suites/filter/filter_test.py
index d6bfa5a3b..4baaf04a7 100644
--- a/dirsrvtests/tests/suites/filter/filter_test.py
+++ b/dirsrvtests/tests/suites/filter/filter_test.py
@@ -9,7 +9,11 @@
import logging
import pytest
+import time
+from lib389.dirsrv_log import DirsrvAccessLog
from lib389.tasks import *
+from lib389.backend import Backends, Backend
+from lib389.dbgen import dbgen_users, dbgen_groups
from lib389.topologies import topology_st
from lib389._constants import PASSWORD, DEFAULT_SUFFIX, DN_DM, SUFFIX
from lib389.utils import *
@@ -304,6 +308,127 @@ def test_extended_search(topology_st):
ents = topology_st.standalone.search_s(SUFFIX, ldap.SCOPE_SUBTREE, myfilter)
assert len(ents) == 1
+def test_match_large_valueset(topology_st):
+ """Test that when returning a big number of entries
+ and that we need to match the filter from a large valueset
+ we get benefit to use the sorted valueset
+
+ :id: 7db5aa88-50e0-4c31-85dd-1d2072cb674c
+
+ :setup: Standalone instance
+
+ :steps:
+ 1. Create a users and groups backends and tune them
+ 2. Generate a test ldif (2k users and 1K groups with all users)
+ 3. Import test ldif file using Offline import (ldif2db).
+ 4. Prim the 'groups' entrycache with a "fast" search
+ 5. Search the 'groups' with a difficult matching value
+ 6. check that etime from step 5 is less than a second
+
+ :expectedresults:
+ 1. Create a users and groups backends should PASS
+ 2. Generate LDIF should PASS.
+ 3. Offline import should PASS.
+ 4. Priming should PASS.
+ 5. Performance search should PASS.
+ 6. Etime of performance search should PASS.
+ """
+
+ log.info('Running test_match_large_valueset...')
+ #
+ # Test online/offline LDIF imports
+ #
+ inst = topology_st.standalone
+ inst.start()
+ backends = Backends(inst)
+ users_suffix = "ou=users,%s" % DEFAULT_SUFFIX
+ users_backend = 'users'
+ users_ldif = 'users_import.ldif'
+ groups_suffix = "ou=groups,%s" % DEFAULT_SUFFIX
+ groups_backend = 'groups'
+ groups_ldif = 'groups_import.ldif'
+ groups_entrycache = '200000000'
+ users_number = 2000
+ groups_number = 1000
+
+
+ # For priming the cache we just want to be fast
+ # taking the first value in the valueset is good
+ # whether the valueset is sorted or not
+ priming_user_rdn = "user0001"
+
+ # For performance testing, this is important to use
+ # user1000 rather then user0001
+ # Because user0001 is the first value in the valueset
+ # whether we use the sorted valuearray or non sorted
+ # valuearray the performance will be similar.
+ # With middle value user1000, the performance boost of
+ # the sorted valuearray will make the difference.
+ perf_user_rdn = "user1000"
+
+ # Step 1. Prepare the backends and tune the groups entrycache
+ try:
+ be_users = backends.create(properties={'parent': DEFAULT_SUFFIX, 'nsslapd-suffix': users_suffix, 'name': users_backend})
+ be_groups = backends.create(properties={'parent': DEFAULT_SUFFIX, 'nsslapd-suffix': groups_suffix, 'name': groups_backend})
+
+ # set the entry cache to 200Mb as the 1K groups of 2K users require at least 170Mb
+ be_groups.replace('nsslapd-cachememsize', groups_entrycache)
+ except:
+ raise
+
+ # Step 2. Generate a test ldif (10k users entries)
+ log.info("Generating users LDIF...")
+ ldif_dir = inst.get_ldif_dir()
+ users_import_ldif = "%s/%s" % (ldif_dir, users_ldif)
+ groups_import_ldif = "%s/%s" % (ldif_dir, groups_ldif)
+ dbgen_users(inst, users_number, users_import_ldif, suffix=users_suffix, generic=True, parent=users_suffix)
+
+ # Generate a test ldif (800 groups with 10k members) that fit in 700Mb entry cache
+ props = {
+ "name": "group",
+ "suffix": groups_suffix,
+ "parent": groups_suffix,
+ "number": groups_number,
+ "numMembers": users_number,
+ "createMembers": False,
+ "memberParent": users_suffix,
+ "membershipAttr": "uniquemember",
+ }
+ dbgen_groups(inst, groups_import_ldif, props)
+
+ # Step 3. Do the both offline imports
+ inst.stop()
+ if not inst.ldif2db(users_backend, None, None, None, users_import_ldif):
+ log.fatal('test_basic_import_export: Offline users import failed')
+ assert False
+ if not inst.ldif2db(groups_backend, None, None, None, groups_import_ldif):
+ log.fatal('test_basic_import_export: Offline groups import failed')
+ assert False
+ inst.start()
+
+ # Step 4. first prime the cache
+ # Just request the 'DN'. We are interested by the time of matching not by the time of transfert
+ entries = topology_st.standalone.search_s(groups_suffix, ldap.SCOPE_SUBTREE, "(&(objectclass=groupOfUniqueNames)(uniquemember=uid=%s,%s))" % (priming_user_rdn, users_suffix), ['dn'])
+ assert len(entries) == groups_number
+
+ # Step 5. Now do the real performance checking it should take less than a second
+ # Just request the 'DN'. We are interested by the time of matching not by the time of transfert
+ search_start = time.time()
+ entries = topology_st.standalone.search_s(groups_suffix, ldap.SCOPE_SUBTREE, "(&(objectclass=groupOfUniqueNames)(uniquemember=uid=%s,%s))" % (perf_user_rdn, users_suffix), ['dn'])
+ duration = time.time() - search_start
+ log.info("Duration of the search was %f", duration)
+
+ # Step 6. Gather the etime from the access log
+ inst.stop()
+ access_log = DirsrvAccessLog(inst)
+ search_result = access_log.match(".*RESULT err=0 tag=101 nentries=%s.*" % groups_number)
+ log.info("Found patterns are %s", search_result[0])
+ log.info("Found patterns are %s", search_result[1])
+ etime = float(search_result[1].split('etime=')[1])
+ log.info("Duration of the search from access log was %f", etime)
+ assert len(entries) == groups_number
+ assert (etime < 1)
+
if __name__ == '__main__':
# Run isolated
# -s for DEBUG mode
diff --git a/ldap/servers/slapd/filterentry.c b/ldap/servers/slapd/filterentry.c
index 2a7102828..4de4aa66e 100644
--- a/ldap/servers/slapd/filterentry.c
+++ b/ldap/servers/slapd/filterentry.c
@@ -296,7 +296,27 @@ test_ava_filter(
rc = -1;
for (; a != NULL; a = a->a_next) {
if (slapi_attr_type_cmp(ava->ava_type, a->a_type, SLAPI_TYPE_CMP_SUBTYPE) == 0) {
- rc = plugin_call_syntax_filter_ava(a, ftype, ava);
+ if ((ftype == LDAP_FILTER_EQUALITY) &&
+ (slapi_attr_is_dn_syntax_type(a->a_type))) {
+ /* This path is for a performance improvement */
+
+ /* In case of equality filter we can get benefit of the
+ * sorted valuearray (from valueset).
+ * This improvement is limited to DN syntax attributes for
+ * which the sorted valueset was designed.
+ */
+ Slapi_Value *sval = NULL;
+ sval = slapi_value_new_berval(&ava->ava_value);
+ if (slapi_valueset_find((const Slapi_Attr *)a, &a->a_present_values, sval)) {
+ rc = 0;
+ }
+ slapi_value_free(&sval);
+ } else {
+ /* When sorted valuearray optimization cannot be used
+ * lets filter the value according to its syntax
+ */
+ rc = plugin_call_syntax_filter_ava(a, ftype, ava);
+ }
if (rc == 0) {
break;
}
--
2.45.2

@ -47,8 +47,8 @@ ExcludeArch: i686
Summary: 389 Directory Server (base)
Name: 389-ds-base
Version: 2.4.5
Release: 9%{?dist}
License: GPL-3.0-or-later 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 BSL-1.0) AND (Apache-2.0 OR MIT OR Zlib) AND (Apache-2.0 OR MIT) AND (MIT OR Apache-2.0) AND Unicode-DFS-2016 AND (MIT OR Unlicense) AND Apache-2.0 AND BSD-3-Clause AND MIT AND MPL-2.0
Release: 3%{?dist}
License: GPLv3+ and (ASL 2.0 or MIT) and MIT and (Unlicense or MIT) and (0BSD or MIT or ASL 2.0) and MPLv2.0 and ASL 2.0 and (MIT or zlib or ASL 2.0) and ((MIT or ASL 2.0) and Unicode-DFS-2016) and (ASL 2.0 or Boost) and BSD
URL: https://www.port389.org
Conflicts: selinux-policy-base < 3.9.8
Conflicts: freeipa-server < 4.0.3
@ -66,6 +66,7 @@ Provides: bundled(crate(atty)) = 0.2.14
Provides: bundled(crate(autocfg)) = 1.1.0
Provides: bundled(crate(backtrace)) = 0.3.69
Provides: bundled(crate(base64)) = 0.13.1
Provides: bundled(crate(bitflags)) = 1.3.2
Provides: bundled(crate(bitflags)) = 2.4.1
Provides: bundled(crate(byteorder)) = 1.5.0
Provides: bundled(crate(cbindgen)) = 0.9.1
@ -79,6 +80,8 @@ Provides: bundled(crate(crossbeam-deque)) = 0.8.5
Provides: bundled(crate(crossbeam-epoch)) = 0.9.18
Provides: bundled(crate(crossbeam-queue)) = 0.3.11
Provides: bundled(crate(crossbeam-utils)) = 0.8.19
Provides: bundled(crate(entryuuid)) = 0.1.0
Provides: bundled(crate(entryuuid_syntax)) = 0.1.0
Provides: bundled(crate(errno)) = 0.3.8
Provides: bundled(crate(fastrand)) = 2.0.1
Provides: bundled(crate(fernet)) = 0.1.4
@ -92,6 +95,8 @@ Provides: bundled(crate(instant)) = 0.1.12
Provides: bundled(crate(itoa)) = 1.0.10
Provides: bundled(crate(jobserver)) = 0.1.27
Provides: bundled(crate(libc)) = 0.2.152
Provides: bundled(crate(librnsslapd)) = 0.1.0
Provides: bundled(crate(librslapd)) = 0.1.0
Provides: bundled(crate(linux-raw-sys)) = 0.4.12
Provides: bundled(crate(lock_api)) = 0.4.11
Provides: bundled(crate(log)) = 0.4.20
@ -112,20 +117,25 @@ Provides: bundled(crate(pkg-config)) = 0.3.28
Provides: bundled(crate(ppv-lite86)) = 0.2.17
Provides: bundled(crate(proc-macro-hack)) = 0.5.20+deprecated
Provides: bundled(crate(proc-macro2)) = 1.0.76
Provides: bundled(crate(pwdchan)) = 0.1.0
Provides: bundled(crate(quote)) = 1.0.35
Provides: bundled(crate(rand)) = 0.8.5
Provides: bundled(crate(rand_chacha)) = 0.3.1
Provides: bundled(crate(rand_core)) = 0.6.4
Provides: bundled(crate(redox_syscall)) = 0.2.16
Provides: bundled(crate(redox_syscall)) = 0.4.1
Provides: bundled(crate(rustc-demangle)) = 0.1.23
Provides: bundled(crate(rustix)) = 0.38.30
Provides: bundled(crate(rustix)) = 0.38.28
Provides: bundled(crate(ryu)) = 1.0.16
Provides: bundled(crate(scopeguard)) = 1.2.0
Provides: bundled(crate(serde)) = 1.0.195
Provides: bundled(crate(serde_derive)) = 1.0.195
Provides: bundled(crate(serde_json)) = 1.0.111
Provides: bundled(crate(smallvec)) = 1.12.0
Provides: bundled(crate(slapd)) = 0.1.0
Provides: bundled(crate(slapi_r_plugin)) = 0.1.0
Provides: bundled(crate(smallvec)) = 1.11.2
Provides: bundled(crate(strsim)) = 0.8.0
Provides: bundled(crate(syn)) = 1.0.109
Provides: bundled(crate(syn)) = 2.0.48
Provides: bundled(crate(tempfile)) = 3.9.0
Provides: bundled(crate(textwrap)) = 0.11.0
@ -282,15 +292,6 @@ Source2: %{name}-devel.README
Source3: https://github.com/jemalloc/%{jemalloc_name}/releases/download/%{jemalloc_ver}/%{jemalloc_name}-%{jemalloc_ver}.tar.bz2
%endif
Source4: 389-ds-base.sysusers
Patch01: 0001-Issue-3527-Support-HAProxy-and-Instance-on-the-same-.patch
Patch02: 0002-Issue-6112-RFE-add-new-operation-note-for-MFA-authen.patch
Patch03: 0003-Issue-6133-Move-slapi_pblock_set_flag_operation_note.patch
Patch04: 0004-CVE-2024-2199.patch
Patch05: 0005-CVE-2024-3657.patch
Patch06: 0006-CVE-2024-5953.patch
Patch07: 0007-CVE-2024-6237.patch
Patch08: 0008-Issue-5772-ONE-LEVEL-search-fails-to-return-sub-suff.patch
Patch09: 0009-Issue-6172-RFE-improve-the-performance-of-evaluation.patch
%description
@ -734,34 +735,6 @@ exit 0
%endif
%changelog
* Tue Jul 16 2024 James Chapman <jachapma@redhat.com> - 2.4.5-9
- Bump version to 2.4.5-9
- Resolves: RHEL-44323 - unauthenticated user can trigger a DoS by sending a specific extended search request
- Resolves: RHEL-40945 - Malformed userPassword hash may cause Denial of Service
- Resolves: RHEL-49457 - perf search result investigation for many large static groups and members 
- Resolves: RHEL-49459 - subsuffix are not returned in one level scoped search
* Fri May 31 2024 Viktor Ashirov <vashirov@redhat.com> - 2.4.5-8
- Bump version to 2.4.5-8
- Fix License tag
* Tue Apr 30 2024 James Chapman <jachapma@redhat.com> - 2.4.5-7
- Bump version to 2.4.5-7
- Resolves: RHEL-34819 - redhat-ds:11/389-ds-base: Malformed userPassword may cause crash at do_modify in slapd/modify.c
- Resolves: RHEL-34825 - redhat-ds:11/389-ds-base: potential denial of service via specially crafted kerberos AS-REQ requ
* Thu Apr 04 2024 Viktor Ashirov <vashirov@redhat.com> - 2.4.5-6
- Bump version to 2.4.5-6
- Resolves: RHEL-30588 - [RFE] allows plugins to log multi-factor authentication notification
* Mon Mar 18 2024 Simon Pichugin <spichugi@redhat.com> - 2.4.5-5
- Bump version to 2.4.5-5
- Rebuild for exception phase
* Thu Mar 14 2024 Simon Pichugin <spichugi@redhat.com> - 2.4.5-4
- Bump version to 2.4.5-4
- Resolves: RHEL-5130 - RFE Add PROXY protocol support to 389-ds-base via confiuration item - similar to Postfix
* Fri Jan 19 2024 Viktor Ashirov <vashirov@redhat.com> - 2.4.5-3
- Bump version to 2.4.5-3
- Fix License tag

Loading…
Cancel
Save