diff --git a/SOURCES/0034-CVE-2021-4091-BZ-2030367-double-free-of-the-virtual-.patch b/SOURCES/0034-CVE-2021-4091-BZ-2030367-double-free-of-the-virtual-.patch new file mode 100644 index 0000000..a4554f2 --- /dev/null +++ b/SOURCES/0034-CVE-2021-4091-BZ-2030367-double-free-of-the-virtual-.patch @@ -0,0 +1,105 @@ +From 1cdb49e70e35ad69e76be10f93233cdf504375df Mon Sep 17 00:00:00 2001 +From: Mark Reynolds +Date: Thu, 16 Dec 2021 16:13:08 -0500 +Subject: [PATCH 1/4] CVE-2021-4091 (BZ#2030367) double-free of the virtual + attribute context in persistent search + +description: + A search is processed by a worker using a private pblock. + If the search is persistent, the worker spawn a thread + and kind of duplicate its private pblock so that the spawn + thread continue to process the persistent search. + Then worker ends the initial search, reinit (free) its private pblock, + and returns monitoring the wait_queue. + When the persistent search completes, it frees the duplicated + pblock. + The problem is that private pblock and duplicated pblock + are referring to a same structure (pb_vattr_context). + That lead to a double free + +Fix: + When cloning the pblock (slapi_pblock_clone) make sure + to transfert the references inside the original (private) + pblock to the target (cloned) one + That includes pb_vattr_context pointer. + +Reviewed by: Mark Reynolds, James Chapman, Pierre Rogier (Thanks !) +--- + ldap/servers/slapd/connection.c | 8 +++++--- + ldap/servers/slapd/pblock.c | 14 ++++++++++++-- + 2 files changed, 17 insertions(+), 5 deletions(-) + +diff --git a/ldap/servers/slapd/connection.c b/ldap/servers/slapd/connection.c +index e0c1a52d2..fc7ed9c4a 100644 +--- a/ldap/servers/slapd/connection.c ++++ b/ldap/servers/slapd/connection.c +@@ -1823,9 +1823,11 @@ connection_threadmain() + pthread_mutex_unlock(&(conn->c_mutex)); + } + /* ps_add makes a shallow copy of the pb - so we +- * can't free it or init it here - just set operation to NULL. +- * ps_send_results will call connection_remove_operation_ext to free it +- */ ++ * can't free it or init it here - just set operation to NULL. ++ * ps_send_results will call connection_remove_operation_ext to free it ++ * The connection_thread private pblock ('pb') has be cloned and should only ++ * be reinit (slapi_pblock_init) ++ */ + slapi_pblock_set(pb, SLAPI_OPERATION, NULL); + slapi_pblock_init(pb); + } else { +diff --git a/ldap/servers/slapd/pblock.c b/ldap/servers/slapd/pblock.c +index a64986aeb..c78d1250f 100644 +--- a/ldap/servers/slapd/pblock.c ++++ b/ldap/servers/slapd/pblock.c +@@ -292,6 +292,12 @@ _pblock_assert_pb_deprecated(Slapi_PBlock *pblock) + } + } + ++/* It clones the pblock ++ * the content of the source pblock is transfered ++ * to the target pblock (returned) ++ * The source pblock should not be used for any operation ++ * it needs to be reinit (slapi_pblock_init) ++ */ + Slapi_PBlock * + slapi_pblock_clone(Slapi_PBlock *pb) + { +@@ -312,28 +318,32 @@ slapi_pblock_clone(Slapi_PBlock *pb) + if (pb->pb_task != NULL) { + _pblock_assert_pb_task(new_pb); + *(new_pb->pb_task) = *(pb->pb_task); ++ memset(pb->pb_task, 0, sizeof(slapi_pblock_task)); + } + if (pb->pb_mr != NULL) { + _pblock_assert_pb_mr(new_pb); + *(new_pb->pb_mr) = *(pb->pb_mr); ++ memset(pb->pb_mr, 0, sizeof(slapi_pblock_matching_rule)); + } + if (pb->pb_misc != NULL) { + _pblock_assert_pb_misc(new_pb); + *(new_pb->pb_misc) = *(pb->pb_misc); ++ memset(pb->pb_misc, 0, sizeof(slapi_pblock_misc)); + } + if (pb->pb_intop != NULL) { + _pblock_assert_pb_intop(new_pb); + *(new_pb->pb_intop) = *(pb->pb_intop); +- /* set pwdpolicy to NULL so this clone allocates its own policy */ +- new_pb->pb_intop->pwdpolicy = NULL; ++ memset(pb->pb_intop, 0, sizeof(slapi_pblock_intop)); + } + if (pb->pb_intplugin != NULL) { + _pblock_assert_pb_intplugin(new_pb); + *(new_pb->pb_intplugin) = *(pb->pb_intplugin); ++ memset(pb->pb_intplugin, 0,sizeof(slapi_pblock_intplugin)); + } + if (pb->pb_deprecated != NULL) { + _pblock_assert_pb_deprecated(new_pb); + *(new_pb->pb_deprecated) = *(pb->pb_deprecated); ++ memset(pb->pb_deprecated, 0, sizeof(slapi_pblock_deprecated)); + } + #ifdef PBLOCK_ANALYTICS + new_pb->analytics = NULL; +-- +2.31.1 + diff --git a/SOURCES/0035-Issue-4775-Add-entryuuid-CLI-and-Fixup-4776.patch b/SOURCES/0035-Issue-4775-Add-entryuuid-CLI-and-Fixup-4776.patch new file mode 100644 index 0000000..21d36ae --- /dev/null +++ b/SOURCES/0035-Issue-4775-Add-entryuuid-CLI-and-Fixup-4776.patch @@ -0,0 +1,228 @@ +From cf620c104ac50af48e48e8c0040820b6c073f7d7 Mon Sep 17 00:00:00 2001 +From: Firstyear +Date: Thu, 19 Aug 2021 10:46:00 +1000 +Subject: [PATCH 2/4] Issue 4775 - Add entryuuid CLI and Fixup (#4776) + +Bug Description: EntryUUID when added was missing it's CLI +and helpers for fixups. + +Fix Description: Add the CLI elements. + +fixes: https://github.com/389ds/389-ds-base/issues/4775 + +Author: William Brown + +Review by: @mreynolds389 (thanks!) +--- + src/lib389/lib389/cli_conf/plugin.py | 2 + + .../lib389/cli_conf/plugins/entryuuid.py | 39 +++++++++++++++++++ + src/plugins/entryuuid/src/lib.rs | 34 +++++++++------- + 3 files changed, 60 insertions(+), 15 deletions(-) + create mode 100644 src/lib389/lib389/cli_conf/plugins/entryuuid.py + +diff --git a/src/lib389/lib389/cli_conf/plugin.py b/src/lib389/lib389/cli_conf/plugin.py +index b50837cb8..1bd0c70db 100644 +--- a/src/lib389/lib389/cli_conf/plugin.py ++++ b/src/lib389/lib389/cli_conf/plugin.py +@@ -27,6 +27,7 @@ from lib389.cli_conf.plugins import passthroughauth as cli_passthroughauth + from lib389.cli_conf.plugins import retrochangelog as cli_retrochangelog + from lib389.cli_conf.plugins import automember as cli_automember + from lib389.cli_conf.plugins import posix_winsync as cli_posix_winsync ++from lib389.cli_conf.plugins import entryuuid as cli_entryuuid + + SINGULAR = Plugin + MANY = Plugins +@@ -113,6 +114,7 @@ def create_parser(subparsers): + cli_passthroughauth.create_parser(subcommands) + cli_retrochangelog.create_parser(subcommands) + cli_posix_winsync.create_parser(subcommands) ++ cli_entryuuid.create_parser(subcommands) + + list_parser = subcommands.add_parser('list', help="List current configured (enabled and disabled) plugins") + list_parser.set_defaults(func=plugin_list) +diff --git a/src/lib389/lib389/cli_conf/plugins/entryuuid.py b/src/lib389/lib389/cli_conf/plugins/entryuuid.py +new file mode 100644 +index 000000000..6c86bff4b +--- /dev/null ++++ b/src/lib389/lib389/cli_conf/plugins/entryuuid.py +@@ -0,0 +1,39 @@ ++# --- BEGIN COPYRIGHT BLOCK --- ++# Copyright (C) 2021 William Brown ++# All rights reserved. ++# ++# License: GPL (version 3 or any later version). ++# See LICENSE for details. ++# --- END COPYRIGHT BLOCK --- ++ ++import ldap ++from lib389.plugins import EntryUUIDPlugin ++from lib389.cli_conf import add_generic_plugin_parsers, generic_object_edit, generic_object_add ++ ++def do_fixup(inst, basedn, log, args): ++ plugin = EntryUUIDPlugin(inst) ++ log.info('Attempting to add task entry...') ++ if not plugin.status(): ++ log.error("'%s' is disabled. Fix up task can't be executed" % plugin.rdn) ++ return ++ fixup_task = plugin.fixup(args.DN, args.filter) ++ fixup_task.wait() ++ exitcode = fixup_task.get_exit_code() ++ if exitcode != 0: ++ log.error('EntryUUID fixup task has failed. Please, check the error log for more - %s' % exitcode) ++ else: ++ log.info('Successfully added task entry') ++ ++def create_parser(subparsers): ++ referint = subparsers.add_parser('entryuuid', help='Manage and configure EntryUUID plugin') ++ subcommands = referint.add_subparsers(help='action') ++ ++ add_generic_plugin_parsers(subcommands, EntryUUIDPlugin) ++ ++ fixup = subcommands.add_parser('fixup', help='Run the fix-up task for EntryUUID plugin') ++ fixup.set_defaults(func=do_fixup) ++ fixup.add_argument('DN', help="Base DN that contains entries to fix up") ++ fixup.add_argument('-f', '--filter', ++ help='Filter for entries to fix up.\n If omitted, all entries under base DN' ++ 'will have their EntryUUID attribute regenerated if not present.') ++ +diff --git a/src/plugins/entryuuid/src/lib.rs b/src/plugins/entryuuid/src/lib.rs +index 0197c5e83..29a9f1258 100644 +--- a/src/plugins/entryuuid/src/lib.rs ++++ b/src/plugins/entryuuid/src/lib.rs +@@ -33,7 +33,7 @@ fn assign_uuid(e: &mut EntryRef) { + // 🚧 safety barrier 🚧 + if e.contains_attr("entryUUID") { + log_error!( +- ErrorLevel::Trace, ++ ErrorLevel::Plugin, + "assign_uuid -> entryUUID exists, skipping dn {}", + sdn.to_dn_string() + ); +@@ -47,7 +47,7 @@ fn assign_uuid(e: &mut EntryRef) { + if sdn.is_below_suffix(&*config_sdn) || sdn.is_below_suffix(&*schema_sdn) { + // We don't need to assign to these suffixes. + log_error!( +- ErrorLevel::Trace, ++ ErrorLevel::Plugin, + "assign_uuid -> not assigning to {:?} as part of system suffix", + sdn.to_dn_string() + ); +@@ -57,7 +57,7 @@ fn assign_uuid(e: &mut EntryRef) { + // Generate a new Uuid. + let u: Uuid = Uuid::new_v4(); + log_error!( +- ErrorLevel::Trace, ++ ErrorLevel::Plugin, + "assign_uuid -> assigning {:?} to dn {}", + u, + sdn.to_dn_string() +@@ -78,13 +78,13 @@ impl SlapiPlugin3 for EntryUuid { + fn betxn_pre_add(pb: &mut PblockRef) -> Result<(), PluginError> { + if pb.get_is_replicated_operation() { + log_error!( +- ErrorLevel::Trace, ++ ErrorLevel::Plugin, + "betxn_pre_add -> replicated operation, will not change" + ); + return Ok(()); + } + +- log_error!(ErrorLevel::Trace, "betxn_pre_add -> start"); ++ log_error!(ErrorLevel::Plugin, "betxn_pre_add -> start"); + + let mut e = pb.get_op_add_entryref().map_err(|_| PluginError::Pblock)?; + assign_uuid(&mut e); +@@ -105,7 +105,7 @@ impl SlapiPlugin3 for EntryUuid { + .first() + .ok_or_else(|| { + log_error!( +- ErrorLevel::Trace, ++ ErrorLevel::Plugin, + "task_validate basedn error -> empty value array?" + ); + LDAPError::Operation +@@ -113,7 +113,7 @@ impl SlapiPlugin3 for EntryUuid { + .as_ref() + .try_into() + .map_err(|e| { +- log_error!(ErrorLevel::Trace, "task_validate basedn error -> {:?}", e); ++ log_error!(ErrorLevel::Plugin, "task_validate basedn error -> {:?}", e); + LDAPError::Operation + })?, + None => return Err(LDAPError::ObjectClassViolation), +@@ -124,7 +124,7 @@ impl SlapiPlugin3 for EntryUuid { + .first() + .ok_or_else(|| { + log_error!( +- ErrorLevel::Trace, ++ ErrorLevel::Plugin, + "task_validate filter error -> empty value array?" + ); + LDAPError::Operation +@@ -132,7 +132,7 @@ impl SlapiPlugin3 for EntryUuid { + .as_ref() + .try_into() + .map_err(|e| { +- log_error!(ErrorLevel::Trace, "task_validate filter error -> {:?}", e); ++ log_error!(ErrorLevel::Plugin, "task_validate filter error -> {:?}", e); + LDAPError::Operation + })?, + None => { +@@ -144,7 +144,11 @@ impl SlapiPlugin3 for EntryUuid { + // Error if the first filter is empty? + + // Now, to make things faster, we wrap the filter in a exclude term. +- let raw_filter = format!("(&{}(!(entryuuid=*)))", raw_filter); ++ let raw_filter = if !raw_filter.starts_with('(') && !raw_filter.ends_with('(') { ++ format!("(&({})(!(entryuuid=*)))", raw_filter) ++ } else { ++ format!("(&{}(!(entryuuid=*)))", raw_filter) ++ }; + + Ok(FixupData { basedn, raw_filter }) + } +@@ -155,7 +159,7 @@ impl SlapiPlugin3 for EntryUuid { + + fn task_handler(_task: &Task, data: Self::TaskData) -> Result { + log_error!( +- ErrorLevel::Trace, ++ ErrorLevel::Plugin, + "task_handler -> start thread with -> {:?}", + data + ); +@@ -195,12 +199,12 @@ impl SlapiPlugin3 for EntryUuid { + } + + fn start(_pb: &mut PblockRef) -> Result<(), PluginError> { +- log_error!(ErrorLevel::Trace, "plugin start"); ++ log_error!(ErrorLevel::Plugin, "plugin start"); + Ok(()) + } + + fn close(_pb: &mut PblockRef) -> Result<(), PluginError> { +- log_error!(ErrorLevel::Trace, "plugin close"); ++ log_error!(ErrorLevel::Plugin, "plugin close"); + Ok(()) + } + } +@@ -212,7 +216,7 @@ pub fn entryuuid_fixup_mapfn(e: &EntryRef, _data: &()) -> Result<(), PluginError + /* Sanity check that entryuuid doesn't already exist */ + if e.contains_attr("entryUUID") { + log_error!( +- ErrorLevel::Trace, ++ ErrorLevel::Plugin, + "skipping fixup for -> {}", + sdn.to_dn_string() + ); +@@ -232,7 +236,7 @@ pub fn entryuuid_fixup_mapfn(e: &EntryRef, _data: &()) -> Result<(), PluginError + + match lmod.execute() { + Ok(_) => { +- log_error!(ErrorLevel::Trace, "fixed-up -> {}", sdn.to_dn_string()); ++ log_error!(ErrorLevel::Plugin, "fixed-up -> {}", sdn.to_dn_string()); + Ok(()) + } + Err(e) => { +-- +2.31.1 + diff --git a/SOURCES/0036-Issue-4877-RFE-EntryUUID-to-validate-UUIDs-on-fixup-.patch b/SOURCES/0036-Issue-4877-RFE-EntryUUID-to-validate-UUIDs-on-fixup-.patch new file mode 100644 index 0000000..ee0d3af --- /dev/null +++ b/SOURCES/0036-Issue-4877-RFE-EntryUUID-to-validate-UUIDs-on-fixup-.patch @@ -0,0 +1,443 @@ +From 0ed5299841733f9e18c5fe8c6a3f9d3fbd49c75a Mon Sep 17 00:00:00 2001 +From: Firstyear +Date: Fri, 20 Aug 2021 09:18:50 +1000 +Subject: [PATCH 3/4] Issue 4877 - RFE - EntryUUID to validate UUIDs on fixup + (#4878) + +Bug Description: Due to changing the syntax of EntryUUID's +to string, we may have invalid EntryUUID's imported into +the database. + +Fix Description: To resolve this during a fixup we validate +that Uuid's have a valid syntax. If they do not, we regenerate +them. + +fixes: https://github.com/389ds/389-ds-base/issues/4877 + +Author: William Brown + +Review by: @mreynolds389 +--- + .../entryuuid/localhost-userRoot-invalid.ldif | 233 ++++++++++++++++++ + .../tests/suites/entryuuid/basic_test.py | 58 ++++- + src/plugins/entryuuid/src/lib.rs | 28 ++- + src/slapi_r_plugin/src/pblock.rs | 4 +- + src/slapi_r_plugin/src/value.rs | 10 +- + 5 files changed, 322 insertions(+), 11 deletions(-) + create mode 100644 dirsrvtests/tests/data/entryuuid/localhost-userRoot-invalid.ldif + +diff --git a/dirsrvtests/tests/data/entryuuid/localhost-userRoot-invalid.ldif b/dirsrvtests/tests/data/entryuuid/localhost-userRoot-invalid.ldif +new file mode 100644 +index 000000000..9703babed +--- /dev/null ++++ b/dirsrvtests/tests/data/entryuuid/localhost-userRoot-invalid.ldif +@@ -0,0 +1,233 @@ ++version: 1 ++ ++# entry-id: 1 ++dn: dc=example,dc=com ++objectClass: top ++objectClass: domain ++dc: example ++description: dc=example,dc=com ++creatorsName: cn=Directory Manager ++modifiersName: cn=Directory Manager ++createTimestamp: 20200325015542Z ++modifyTimestamp: 20200325015542Z ++nsUniqueId: a2b33229-6e3b11ea-8de0c78c-83e27eda ++aci: (targetattr="dc || description || objectClass")(targetfilter="(objectClas ++ s=domain)")(version 3.0; acl "Enable anyone domain read"; allow (read, search ++ , compare)(userdn="ldap:///anyone");) ++aci: (targetattr="ou || objectClass")(targetfilter="(objectClass=organizationa ++ lUnit)")(version 3.0; acl "Enable anyone ou read"; allow (read, search, compa ++ re)(userdn="ldap:///anyone");) ++ ++# entry-id: 2 ++dn: cn=389_ds_system,dc=example,dc=com ++objectClass: top ++objectClass: nscontainer ++objectClass: ldapsubentry ++cn: 389_ds_system ++creatorsName: cn=Directory Manager ++modifiersName: cn=Directory Manager ++createTimestamp: 20200325015542Z ++modifyTimestamp: 20200325015542Z ++nsUniqueId: a2b3322a-6e3b11ea-8de0c78c-83e27eda ++ ++# entry-id: 3 ++dn: ou=groups,dc=example,dc=com ++objectClass: top ++objectClass: organizationalunit ++ou: groups ++aci: (targetattr="cn || member || gidNumber || nsUniqueId || description || ob ++ jectClass")(targetfilter="(objectClass=groupOfNames)")(version 3.0; acl "Enab ++ le anyone group read"; allow (read, search, compare)(userdn="ldap:///anyone") ++ ;) ++aci: (targetattr="member")(targetfilter="(objectClass=groupOfNames)")(version ++ 3.0; acl "Enable group_modify to alter members"; allow (write)(groupdn="ldap: ++ ///cn=group_modify,ou=permissions,dc=example,dc=com");) ++aci: (targetattr="cn || member || gidNumber || description || objectClass")(ta ++ rgetfilter="(objectClass=groupOfNames)")(version 3.0; acl "Enable group_admin ++ to manage groups"; allow (write, add, delete)(groupdn="ldap:///cn=group_admi ++ n,ou=permissions,dc=example,dc=com");) ++creatorsName: cn=Directory Manager ++modifiersName: cn=Directory Manager ++createTimestamp: 20200325015543Z ++modifyTimestamp: 20200325015543Z ++nsUniqueId: a2b3322b-6e3b11ea-8de0c78c-83e27eda ++ ++# entry-id: 4 ++dn: ou=people,dc=example,dc=com ++objectClass: top ++objectClass: organizationalunit ++ou: people ++aci: (targetattr="objectClass || description || nsUniqueId || uid || displayNa ++ me || loginShell || uidNumber || gidNumber || gecos || homeDirectory || cn || ++ memberOf || mail || nsSshPublicKey || nsAccountLock || userCertificate")(tar ++ getfilter="(objectClass=posixaccount)")(version 3.0; acl "Enable anyone user ++ read"; allow (read, search, compare)(userdn="ldap:///anyone");) ++aci: (targetattr="displayName || legalName || userPassword || nsSshPublicKey") ++ (version 3.0; acl "Enable self partial modify"; allow (write)(userdn="ldap:// ++ /self");) ++aci: (targetattr="legalName || telephoneNumber || mobile || sn")(targetfilter= ++ "(|(objectClass=nsPerson)(objectClass=inetOrgPerson))")(version 3.0; acl "Ena ++ ble self legalname read"; allow (read, search, compare)(userdn="ldap:///self" ++ );) ++aci: (targetattr="legalName || telephoneNumber")(targetfilter="(objectClass=ns ++ Person)")(version 3.0; acl "Enable user legalname read"; allow (read, search, ++ compare)(groupdn="ldap:///cn=user_private_read,ou=permissions,dc=example,dc= ++ com");) ++aci: (targetattr="uid || description || displayName || loginShell || uidNumber ++ || gidNumber || gecos || homeDirectory || cn || memberOf || mail || legalNam ++ e || telephoneNumber || mobile")(targetfilter="(&(objectClass=nsPerson)(objec ++ tClass=nsAccount))")(version 3.0; acl "Enable user admin create"; allow (writ ++ e, add, delete, read)(groupdn="ldap:///cn=user_admin,ou=permissions,dc=exampl ++ e,dc=com");) ++aci: (targetattr="uid || description || displayName || loginShell || uidNumber ++ || gidNumber || gecos || homeDirectory || cn || memberOf || mail || legalNam ++ e || telephoneNumber || mobile")(targetfilter="(&(objectClass=nsPerson)(objec ++ tClass=nsAccount))")(version 3.0; acl "Enable user modify to change users"; a ++ llow (write, read)(groupdn="ldap:///cn=user_modify,ou=permissions,dc=example, ++ dc=com");) ++aci: (targetattr="userPassword || nsAccountLock || userCertificate || nsSshPub ++ licKey")(targetfilter="(objectClass=nsAccount)")(version 3.0; acl "Enable use ++ r password reset"; allow (write, read)(groupdn="ldap:///cn=user_passwd_reset, ++ ou=permissions,dc=example,dc=com");) ++creatorsName: cn=Directory Manager ++modifiersName: cn=Directory Manager ++createTimestamp: 20200325015543Z ++modifyTimestamp: 20200325015543Z ++nsUniqueId: a2b3322c-6e3b11ea-8de0c78c-83e27eda ++ ++# entry-id: 5 ++dn: ou=permissions,dc=example,dc=com ++objectClass: top ++objectClass: organizationalunit ++ou: permissions ++creatorsName: cn=Directory Manager ++modifiersName: cn=Directory Manager ++createTimestamp: 20200325015543Z ++modifyTimestamp: 20200325015543Z ++nsUniqueId: a2b3322d-6e3b11ea-8de0c78c-83e27eda ++ ++# entry-id: 6 ++dn: ou=services,dc=example,dc=com ++objectClass: top ++objectClass: organizationalunit ++ou: services ++aci: (targetattr="objectClass || description || nsUniqueId || cn || memberOf | ++ | nsAccountLock ")(targetfilter="(objectClass=netscapeServer)")(version 3.0; ++ acl "Enable anyone service account read"; allow (read, search, compare)(userd ++ n="ldap:///anyone");) ++creatorsName: cn=Directory Manager ++modifiersName: cn=Directory Manager ++createTimestamp: 20200325015544Z ++modifyTimestamp: 20200325015544Z ++nsUniqueId: a2b3322e-6e3b11ea-8de0c78c-83e27eda ++ ++# entry-id: 7 ++dn: uid=demo_user,ou=people,dc=example,dc=com ++objectClass: top ++objectClass: nsPerson ++objectClass: nsAccount ++objectClass: nsOrgPerson ++objectClass: posixAccount ++uid: demo_user ++cn: Demo User ++displayName: Demo User ++legalName: Demo User Name ++uidNumber: 99998 ++gidNumber: 99998 ++homeDirectory: /var/empty ++loginShell: /bin/false ++nsAccountLock: true ++creatorsName: cn=Directory Manager ++modifiersName: cn=Directory Manager ++createTimestamp: 20200325015544Z ++modifyTimestamp: 20200325061615Z ++nsUniqueId: a2b3322f-6e3b11ea-8de0c78c-83e27eda ++entryUUID: INVALID_UUID ++ ++# entry-id: 8 ++dn: cn=demo_group,ou=groups,dc=example,dc=com ++objectClass: top ++objectClass: groupOfNames ++objectClass: posixGroup ++objectClass: nsMemberOf ++cn: demo_group ++gidNumber: 99999 ++creatorsName: cn=Directory Manager ++modifiersName: cn=Directory Manager ++createTimestamp: 20200325015544Z ++modifyTimestamp: 20200325015544Z ++nsUniqueId: a2b33230-6e3b11ea-8de0c78c-83e27eda ++entryUUID: f6df8fe9-6b30-46aa-aa13-f0bf755371e8 ++ ++# entry-id: 9 ++dn: cn=group_admin,ou=permissions,dc=example,dc=com ++objectClass: top ++objectClass: groupOfNames ++objectClass: nsMemberOf ++cn: group_admin ++creatorsName: cn=Directory Manager ++modifiersName: cn=Directory Manager ++createTimestamp: 20200325015545Z ++modifyTimestamp: 20200325015545Z ++nsUniqueId: a2b33231-6e3b11ea-8de0c78c-83e27eda ++ ++# entry-id: 10 ++dn: cn=group_modify,ou=permissions,dc=example,dc=com ++objectClass: top ++objectClass: groupOfNames ++objectClass: nsMemberOf ++cn: group_modify ++creatorsName: cn=Directory Manager ++modifiersName: cn=Directory Manager ++createTimestamp: 20200325015545Z ++modifyTimestamp: 20200325015545Z ++nsUniqueId: a2b33232-6e3b11ea-8de0c78c-83e27eda ++ ++# entry-id: 11 ++dn: cn=user_admin,ou=permissions,dc=example,dc=com ++objectClass: top ++objectClass: groupOfNames ++objectClass: nsMemberOf ++cn: user_admin ++creatorsName: cn=Directory Manager ++modifiersName: cn=Directory Manager ++createTimestamp: 20200325015545Z ++modifyTimestamp: 20200325015545Z ++nsUniqueId: a2b33233-6e3b11ea-8de0c78c-83e27eda ++ ++# entry-id: 12 ++dn: cn=user_modify,ou=permissions,dc=example,dc=com ++objectClass: top ++objectClass: groupOfNames ++objectClass: nsMemberOf ++cn: user_modify ++creatorsName: cn=Directory Manager ++modifiersName: cn=Directory Manager ++createTimestamp: 20200325015546Z ++modifyTimestamp: 20200325015546Z ++nsUniqueId: a2b33234-6e3b11ea-8de0c78c-83e27eda ++ ++# entry-id: 13 ++dn: cn=user_passwd_reset,ou=permissions,dc=example,dc=com ++objectClass: top ++objectClass: groupOfNames ++objectClass: nsMemberOf ++cn: user_passwd_reset ++creatorsName: cn=Directory Manager ++modifiersName: cn=Directory Manager ++createTimestamp: 20200325015546Z ++modifyTimestamp: 20200325015546Z ++nsUniqueId: a2b33235-6e3b11ea-8de0c78c-83e27eda ++ ++# entry-id: 14 ++dn: cn=user_private_read,ou=permissions,dc=example,dc=com ++objectClass: top ++objectClass: groupOfNames ++objectClass: nsMemberOf ++cn: user_private_read ++creatorsName: cn=Directory Manager ++modifiersName: cn=Directory Manager ++createTimestamp: 20200325015547Z ++modifyTimestamp: 20200325015547Z ++nsUniqueId: a2b33236-6e3b11ea-8de0c78c-83e27eda ++ +diff --git a/dirsrvtests/tests/suites/entryuuid/basic_test.py b/dirsrvtests/tests/suites/entryuuid/basic_test.py +index 4d8a40909..41ffbfe10 100644 +--- a/dirsrvtests/tests/suites/entryuuid/basic_test.py ++++ b/dirsrvtests/tests/suites/entryuuid/basic_test.py +@@ -10,6 +10,7 @@ import ldap + import pytest + import time + import shutil ++import uuid + from lib389.idm.user import nsUserAccounts, UserAccounts + from lib389.idm.account import Accounts + from lib389.idm.domain import Domain +@@ -217,7 +218,7 @@ def test_entryuuid_fixup_task(topology): + + # 4. run the fix up + # For now set the log level to high! +- topology.standalone.config.loglevel(vals=(ErrorLog.DEFAULT,ErrorLog.TRACE)) ++ topology.standalone.config.loglevel(vals=(ErrorLog.DEFAULT,ErrorLog.PLUGIN)) + task = plug.fixup(DEFAULT_SUFFIX) + task.wait() + assert(task.is_complete() and task.get_exit_code() == 0) +@@ -242,3 +243,58 @@ def test_entryuuid_fixup_task(topology): + euuid_domain_2 = domain.get_attr_val_utf8('entryUUID') + assert(euuid_domain_2 == euuid_domain) + ++@pytest.mark.skipif(not default_paths.rust_enabled or ds_is_older('1.4.2.0'), reason="Entryuuid is not available in older versions") ++def test_entryuuid_import_and_fixup_of_invalid_values(topology): ++ """ Test that when we import a database with an invalid entryuuid ++ that it is accepted *and* that subsequently we can fix the invalid ++ entryuuid during a fixup. ++ ++ :id: ec8ef3a7-3cd2-4cbd-b6f1-2449fa17be75 ++ ++ :setup: Standalone instance ++ ++ :steps: ++ 1. Import the db from the ldif ++ 2. Check the entryuuid is invalid ++ 3. Run the fixup ++ 4. Check the entryuuid is now valid (regenerated) ++ ++ :expectedresults: ++ 1. Success ++ 2. The entryuuid is invalid ++ 3. Success ++ 4. The entryuuid is valid ++ """ ++ ++ # 1. Import the db ++ ldif_dir = topology.standalone.get_ldif_dir() ++ target_ldif = os.path.join(ldif_dir, 'localhost-userRoot-invalid.ldif') ++ import_ldif = os.path.join(DATADIR1, 'localhost-userRoot-invalid.ldif') ++ shutil.copyfile(import_ldif, target_ldif) ++ os.chmod(target_ldif, 0o777) ++ ++ be = Backends(topology.standalone).get('userRoot') ++ task = be.import_ldif([target_ldif]) ++ task.wait() ++ assert(task.is_complete() and task.get_exit_code() == 0) ++ ++ # 2. Check the entryuuid is invalid ++ account = nsUserAccounts(topology.standalone, DEFAULT_SUFFIX).get("demo_user") ++ euuid = account.get_attr_val_utf8('entryUUID') ++ assert(euuid == "INVALID_UUID") ++ ++ # 3. Run the fixup ++ topology.standalone.config.loglevel(vals=(ErrorLog.DEFAULT,ErrorLog.PLUGIN)) ++ plug = EntryUUIDPlugin(topology.standalone) ++ task = plug.fixup(DEFAULT_SUFFIX) ++ task.wait() ++ assert(task.is_complete() and task.get_exit_code() == 0) ++ topology.standalone.config.loglevel(vals=(ErrorLog.DEFAULT,)) ++ ++ # 4. Check the entryuuid is valid ++ euuid = account.get_attr_val_utf8('entryUUID') ++ print(f"❄️ account entryUUID -> {euuid}"); ++ assert(euuid != "INVALID_UUID") ++ # Raises an error if invalid ++ uuid.UUID(euuid) ++ +diff --git a/src/plugins/entryuuid/src/lib.rs b/src/plugins/entryuuid/src/lib.rs +index 29a9f1258..ad3faef4b 100644 +--- a/src/plugins/entryuuid/src/lib.rs ++++ b/src/plugins/entryuuid/src/lib.rs +@@ -144,11 +144,17 @@ impl SlapiPlugin3 for EntryUuid { + // Error if the first filter is empty? + + // Now, to make things faster, we wrap the filter in a exclude term. ++ ++ // 2021 - #4877 because we allow entryuuid to be strings, on import these may ++ // be invalid. As a result, we DO need to allow the fixup to check the entryuuid ++ // value is correct, so we can not exclude these during the search. ++ /* + let raw_filter = if !raw_filter.starts_with('(') && !raw_filter.ends_with('(') { + format!("(&({})(!(entryuuid=*)))", raw_filter) + } else { + format!("(&{}(!(entryuuid=*)))", raw_filter) + }; ++ */ + + Ok(FixupData { basedn, raw_filter }) + } +@@ -213,14 +219,20 @@ pub fn entryuuid_fixup_mapfn(e: &EntryRef, _data: &()) -> Result<(), PluginError + /* Supply a modification to the entry. */ + let sdn = e.get_sdnref(); + +- /* Sanity check that entryuuid doesn't already exist */ +- if e.contains_attr("entryUUID") { +- log_error!( +- ErrorLevel::Plugin, +- "skipping fixup for -> {}", +- sdn.to_dn_string() +- ); +- return Ok(()); ++ /* Check that entryuuid doesn't already exist, and is valid */ ++ if let Some(valueset) = e.get_attr("entryUUID") { ++ if valueset.iter().all(|v| { ++ let u: Result = (&v).try_into(); ++ u.is_ok() ++ }) { ++ // All values were valid uuid, move on! ++ log_error!( ++ ErrorLevel::Plugin, ++ "skipping fixup for -> {}", ++ sdn.to_dn_string() ++ ); ++ return Ok(()); ++ } + } + + // Setup the modifications +diff --git a/src/slapi_r_plugin/src/pblock.rs b/src/slapi_r_plugin/src/pblock.rs +index 718ff2ca7..ac1b3c33b 100644 +--- a/src/slapi_r_plugin/src/pblock.rs ++++ b/src/slapi_r_plugin/src/pblock.rs +@@ -281,7 +281,9 @@ impl PblockRef { + } + + pub fn get_is_replicated_operation(&mut self) -> bool { +- let i = self.get_value_i32(PblockType::IsReplicationOperation).unwrap_or(0); ++ let i = self ++ .get_value_i32(PblockType::IsReplicationOperation) ++ .unwrap_or(0); + // Because rust returns the result of the last evaluation, we can + // just return if not equal 0. + i != 0 +diff --git a/src/slapi_r_plugin/src/value.rs b/src/slapi_r_plugin/src/value.rs +index 46246837a..cd565295c 100644 +--- a/src/slapi_r_plugin/src/value.rs ++++ b/src/slapi_r_plugin/src/value.rs +@@ -1,6 +1,6 @@ + use crate::ber::{ol_berval, BerValRef}; + use crate::dn::Sdn; +-use std::convert::{From, TryFrom}; ++use std::convert::{From, TryFrom, TryInto}; + use std::ffi::CString; + use std::iter::once; + use std::iter::FromIterator; +@@ -213,6 +213,14 @@ impl TryFrom<&ValueRef> for String { + } + } + ++impl TryFrom<&ValueRef> for Uuid { ++ type Error = (); ++ ++ fn try_from(value: &ValueRef) -> Result { ++ (&value.bvr).try_into().map_err(|_| ()) ++ } ++} ++ + impl TryFrom<&ValueRef> for Sdn { + type Error = (); + +-- +2.31.1 + diff --git a/SOURCES/0037-Issue-4595-Paged-search-lookthroughlimit-bug-4602.patch b/SOURCES/0037-Issue-4595-Paged-search-lookthroughlimit-bug-4602.patch new file mode 100644 index 0000000..e21a666 --- /dev/null +++ b/SOURCES/0037-Issue-4595-Paged-search-lookthroughlimit-bug-4602.patch @@ -0,0 +1,143 @@ +From 0bdb7445af5be162e18c26f7fc2b75536c467748 Mon Sep 17 00:00:00 2001 +From: James Chapman +Date: Fri, 19 Feb 2021 16:32:22 +0000 +Subject: [PATCH 4/4] Issue 4595 - Paged search lookthroughlimit bug (#4602) + +Bug Description: During a paged search with lookthroughlimit enabled, +lookthroughcount is used to keep track of how many entries are +examined. A paged search reads ahead one entry to catch the end of the +search so it doesn't show the prompt when there are no more entries. +lookthroughcount doesn't take read ahead into account when tracking +how many entries have been examined. + +Fix Description: Keep lookthroughcount in sync with read ahead by +by decrementing it during read ahead roll back. + +Fixes: https://github.com/389ds/389-ds-base/issues/4595 + +Relates: https://github.com/389ds/389-ds-base/issues/4513 + +Reviewed by: droideck, mreynolds389, Firstyear, progier389 (Many thanks) +--- + dirsrvtests/tests/suites/basic/basic_test.py | 85 ++++++++++++++++++++ + ldap/servers/slapd/back-ldbm/ldbm_search.c | 1 + + 2 files changed, 86 insertions(+) + +diff --git a/dirsrvtests/tests/suites/basic/basic_test.py b/dirsrvtests/tests/suites/basic/basic_test.py +index 332f5bb8d..e1a31175a 100644 +--- a/dirsrvtests/tests/suites/basic/basic_test.py ++++ b/dirsrvtests/tests/suites/basic/basic_test.py +@@ -95,6 +95,24 @@ def rootdse_attr(topology_st, request): + return rootdse_attr_name + + ++def change_conf_attr(topology_st, suffix, attr_name, attr_value): ++ """Change configuration attribute in the given suffix. ++ ++ Returns previous attribute value. ++ """ ++ ++ entry = DSLdapObject(topology_st.standalone, suffix) ++ ++ attr_value_bck = entry.get_attr_val_bytes(attr_name) ++ log.info('Set %s to %s. Previous value - %s. Modified suffix - %s.' % ( ++ attr_name, attr_value, attr_value_bck, suffix)) ++ if attr_value is None: ++ entry.remove_all(attr_name) ++ else: ++ entry.replace(attr_name, attr_value) ++ return attr_value_bck ++ ++ + def test_basic_ops(topology_st, import_example_ldif): + """Tests adds, mods, modrdns, and deletes operations + +@@ -607,6 +625,73 @@ def test_basic_searches(topology_st, import_example_ldif): + log.info('test_basic_searches: PASSED') + + ++@pytest.mark.parametrize('limit,resp', ++ ((('200'), 'PASS'), ++ (('50'), ldap.ADMINLIMIT_EXCEEDED))) ++def test_basic_search_lookthroughlimit(topology_st, limit, resp, import_example_ldif): ++ """ ++ Tests normal search with lookthroughlimit set high and low. ++ ++ :id: b5119970-6c9f-41b7-9649-de9233226fec ++ ++ :setup: Standalone instance, add example.ldif to the database, search filter (uid=*). ++ ++ :steps: ++ 1. Import ldif user file. ++ 2. Change lookthroughlimit to 200. ++ 3. Bind to server as low priv user ++ 4. Run search 1 with "high" lookthroughlimit. ++ 5. Change lookthroughlimit to 50. ++ 6. Run search 2 with "low" lookthroughlimit. ++ 8. Delete user from DB. ++ 9. Reset lookthroughlimit to original. ++ ++ :expectedresults: ++ 1. First search should complete with no error. ++ 2. Second search should return ldap.ADMINLIMIT_EXCEEDED error. ++ """ ++ ++ log.info('Running test_basic_search_lookthroughlimit...') ++ ++ search_filter = "(uid=*)" ++ ++ ltl_orig = change_conf_attr(topology_st, 'cn=config,cn=ldbm database,cn=plugins,cn=config', 'nsslapd-lookthroughlimit', limit) ++ ++ try: ++ users = UserAccounts(topology_st.standalone, DEFAULT_SUFFIX, rdn=None) ++ user = users.create_test_user() ++ user.replace('userPassword', PASSWORD) ++ except ldap.LDAPError as e: ++ log.fatal('Failed to create test user: error ' + e.args[0]['desc']) ++ assert False ++ ++ try: ++ conn = UserAccount(topology_st.standalone, user.dn).bind(PASSWORD) ++ except ldap.LDAPError as e: ++ log.fatal('Failed to bind test user: error ' + e.args[0]['desc']) ++ assert False ++ ++ try: ++ if resp == ldap.ADMINLIMIT_EXCEEDED: ++ with pytest.raises(ldap.ADMINLIMIT_EXCEEDED): ++ searchid = conn.search(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE, search_filter) ++ rtype, rdata = conn.result(searchid) ++ else: ++ searchid = conn.search(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE, search_filter) ++ rtype, rdata = conn.result(searchid) ++ assert(len(rdata) == 151) #151 entries in the imported ldif file using "(uid=*)" ++ except ldap.LDAPError as e: ++ log.fatal('Failed to perform search: error ' + e.args[0]['desc']) ++ assert False ++ ++ finally: ++ #Cleanup ++ change_conf_attr(topology_st, 'cn=config,cn=ldbm database,cn=plugins,cn=config', 'nsslapd-lookthroughlimit', ltl_orig) ++ user.delete() ++ ++ log.info('test_basic_search_lookthroughlimit: PASSED') ++ ++ + @pytest.fixture(scope="module") + def add_test_entry(topology_st, request): + # Add test entry +diff --git a/ldap/servers/slapd/back-ldbm/ldbm_search.c b/ldap/servers/slapd/back-ldbm/ldbm_search.c +index 6e22debde..d0f52b6f7 100644 +--- a/ldap/servers/slapd/back-ldbm/ldbm_search.c ++++ b/ldap/servers/slapd/back-ldbm/ldbm_search.c +@@ -1885,6 +1885,7 @@ ldbm_back_prev_search_results(Slapi_PBlock *pb) + sr->sr_entry = NULL; + } + idl_iterator_decrement(&(sr->sr_current)); ++ --sr->sr_lookthroughcount; + } + return; + } +-- +2.31.1 + diff --git a/SPECS/389-ds-base.spec b/SPECS/389-ds-base.spec index fc4e7eb..e2b582f 100644 --- a/SPECS/389-ds-base.spec +++ b/SPECS/389-ds-base.spec @@ -48,7 +48,7 @@ ExcludeArch: i686 Summary: 389 Directory Server (base) Name: 389-ds-base Version: 1.4.3.23 -Release: %{?relprefix}12%{?prerel}%{?dist} +Release: %{?relprefix}14%{?prerel}%{?dist} License: GPLv3+ URL: https://www.port389.org Group: System Environment/Daemons @@ -270,6 +270,10 @@ Patch30: 0030-Issue-4884-server-crashes-when-dnaInterval-attribute.patc Patch31: 0031-Issue-4925-Performance-ACI-targetfilter-evaluation-r.patch Patch32: 0032-Issue-4972-gecos-with-IA5-introduces-a-compatibility.patch Patch33: 0033-Issue-4910-db-reindex-corrupts-RUV-tombstone-nsuique.patch +Patch34: 0034-CVE-2021-4091-BZ-2030367-double-free-of-the-virtual-.patch +Patch35: 0035-Issue-4775-Add-entryuuid-CLI-and-Fixup-4776.patch +Patch36: 0036-Issue-4877-RFE-EntryUUID-to-validate-UUIDs-on-fixup-.patch +Patch37: 0037-Issue-4595-Paged-search-lookthroughlimit-bug-4602.patch %description 389 Directory Server is an LDAPv3 compliant server. The base package includes @@ -888,6 +892,18 @@ exit 0 %doc README.md %changelog +* Wed Mar 2 2022 Thierry Bordaz - 1.4.3.23-14 +- Bump version to 1.4.3.23-14 +- Resolves: Bug 2059893 - Paged search lookthroughlimit counter doesnt take read ahead into account +- Resolves: Bug 2060106 - Based on 1944494 (RFC 4530 entryUUID attribute) - plugin entryuuid failing +- Resolves: Bug 2060110 - double-free of the virtual attribute context in persistent search + +* Mon Feb 21 2022 Thierry Bordaz - 1.4.3.23-13 +- Bump version to 1.4.3.23-13 +- Resolves: Bug 2056488 - Paged search lookthroughlimit counter doesnt take read ahead into account +- Resolves: Bug 2047166 - Based on 1944494 (RFC 4530 entryUUID attribute) - plugin entryuuid failing +- Resolves: Bug 2056481 - double-free of the virtual attribute context in persistent search + * Thu Nov 18 2021 Mark Reynolds - 1.4.3.23-12 - Bump version to 1.4.3.23-12 - Resolves: Bug 2024697 - DB corruption "_entryrdn_insert_key - Same DN (dn: nsuniqueid=ffffffff-ffffffff-ffffffff-ffffffff,) is already in the entryrdn file"