|
|
61f723 |
From 710b0a6aaf1c648bc8fd33d4ab5bcc859a0ed851 Mon Sep 17 00:00:00 2001
|
|
|
61f723 |
From: Thierry Bordaz <tbordaz@redhat.com>
|
|
|
61f723 |
Date: Thu, 13 Apr 2017 15:21:49 +0200
|
|
|
61f723 |
Subject: [PATCH] Ticket 49184 - Overflow in memberof
|
|
|
61f723 |
|
|
|
61f723 |
Bug Description:
|
|
|
61f723 |
The function memberof_call_foreach_dn can be used to retrieve ancestors of a
|
|
|
61f723 |
given entry. (ancestors are groups owning directly or indirectly a given entry).
|
|
|
61f723 |
|
|
|
61f723 |
With the use of group cache in memberof, at the entrance of memberof_call_foreach_dn
|
|
|
61f723 |
there is an attempt to get the entry ancestors from the cache.
|
|
|
61f723 |
|
|
|
61f723 |
Before doing so it needs to test if the cache is safe. In fact in case of
|
|
|
61f723 |
circular groups the use of the cache is disabled and lookup in the cache should not
|
|
|
61f723 |
happend.
|
|
|
61f723 |
|
|
|
61f723 |
To know if the cache is safe it needs to access a flag (use_cache) in callback_data.
|
|
|
61f723 |
The callback_data structure is opaque at this level. So accessing it
|
|
|
61f723 |
while its structure is unknown is dangerous.
|
|
|
61f723 |
|
|
|
61f723 |
The bug is that we may read an 'int' at an offset that overflow the actual structure.
|
|
|
61f723 |
This is just a test and should not trigger a crash.
|
|
|
61f723 |
|
|
|
61f723 |
Fix Description:
|
|
|
61f723 |
Add a flag to call memberof_call_foreach_dn so that, that indicates if
|
|
|
61f723 |
it is valid to use the group cache.
|
|
|
61f723 |
|
|
|
61f723 |
https://pagure.io/389-ds-base/issue/49184
|
|
|
61f723 |
|
|
|
61f723 |
Reviewed by: William Brown and Mark Reynolds (thanks to you !!)
|
|
|
61f723 |
|
|
|
61f723 |
Platforms tested: F23
|
|
|
61f723 |
|
|
|
61f723 |
Flag Day: no
|
|
|
61f723 |
|
|
|
61f723 |
Doc impact: no
|
|
|
61f723 |
---
|
|
|
61f723 |
dirsrvtests/tests/tickets/ticket49184_test.py | 146 ++++++++++++++++++++++++++
|
|
|
61f723 |
ldap/servers/plugins/memberof/memberof.c | 38 ++++---
|
|
|
61f723 |
2 files changed, 167 insertions(+), 17 deletions(-)
|
|
|
61f723 |
create mode 100644 dirsrvtests/tests/tickets/ticket49184_test.py
|
|
|
61f723 |
|
|
|
61f723 |
diff --git a/dirsrvtests/tests/tickets/ticket49184_test.py b/dirsrvtests/tests/tickets/ticket49184_test.py
|
|
|
61f723 |
new file mode 100644
|
|
|
61f723 |
index 0000000..20edfde
|
|
|
61f723 |
--- /dev/null
|
|
|
61f723 |
+++ b/dirsrvtests/tests/tickets/ticket49184_test.py
|
|
|
61f723 |
@@ -0,0 +1,146 @@
|
|
|
61f723 |
+import time
|
|
|
61f723 |
+import ldap
|
|
|
61f723 |
+import logging
|
|
|
61f723 |
+import pytest
|
|
|
61f723 |
+from lib389 import DirSrv, Entry, tools, tasks
|
|
|
61f723 |
+from lib389.tools import DirSrvTools
|
|
|
61f723 |
+from lib389._constants import *
|
|
|
61f723 |
+from lib389.properties import *
|
|
|
61f723 |
+from lib389.tasks import *
|
|
|
61f723 |
+from lib389.utils import *
|
|
|
61f723 |
+from lib389.topologies import topology_st as topo
|
|
|
61f723 |
+
|
|
|
61f723 |
+DEBUGGING = os.getenv("DEBUGGING", default=False)
|
|
|
61f723 |
+GROUP_DN_1 = ("cn=group1," + DEFAULT_SUFFIX)
|
|
|
61f723 |
+GROUP_DN_2 = ("cn=group2," + DEFAULT_SUFFIX)
|
|
|
61f723 |
+SUPER_GRP1 = ("cn=super_grp1," + DEFAULT_SUFFIX)
|
|
|
61f723 |
+SUPER_GRP2 = ("cn=super_grp2," + DEFAULT_SUFFIX)
|
|
|
61f723 |
+SUPER_GRP3 = ("cn=super_grp3," + DEFAULT_SUFFIX)
|
|
|
61f723 |
+
|
|
|
61f723 |
+if DEBUGGING:
|
|
|
61f723 |
+ logging.getLogger(__name__).setLevel(logging.DEBUG)
|
|
|
61f723 |
+else:
|
|
|
61f723 |
+ logging.getLogger(__name__).setLevel(logging.INFO)
|
|
|
61f723 |
+log = logging.getLogger(__name__)
|
|
|
61f723 |
+
|
|
|
61f723 |
+def _add_group_with_members(topo, group_dn):
|
|
|
61f723 |
+ # Create group
|
|
|
61f723 |
+ try:
|
|
|
61f723 |
+ topo.standalone.add_s(Entry((group_dn,
|
|
|
61f723 |
+ {'objectclass': 'top groupofnames extensibleObject'.split(),
|
|
|
61f723 |
+ 'cn': 'group'})))
|
|
|
61f723 |
+ except ldap.LDAPError as e:
|
|
|
61f723 |
+ log.fatal('Failed to add group: error ' + e.message['desc'])
|
|
|
61f723 |
+ assert False
|
|
|
61f723 |
+
|
|
|
61f723 |
+ # Add members to the group - set timeout
|
|
|
61f723 |
+ log.info('Adding members to the group...')
|
|
|
61f723 |
+ for idx in range(1, 5):
|
|
|
61f723 |
+ try:
|
|
|
61f723 |
+ MEMBER_VAL = ("uid=member%d,%s" % (idx, DEFAULT_SUFFIX))
|
|
|
61f723 |
+ topo.standalone.modify_s(group_dn,
|
|
|
61f723 |
+ [(ldap.MOD_ADD,
|
|
|
61f723 |
+ 'member',
|
|
|
61f723 |
+ MEMBER_VAL)])
|
|
|
61f723 |
+ except ldap.LDAPError as e:
|
|
|
61f723 |
+ log.fatal('Failed to update group: member (%s) - error: %s' %
|
|
|
61f723 |
+ (MEMBER_VAL, e.message['desc']))
|
|
|
61f723 |
+ assert False
|
|
|
61f723 |
+
|
|
|
61f723 |
+def _check_memberof(topo, member=None, memberof=True, group_dn=None):
|
|
|
61f723 |
+ # Check that members have memberof attribute on M1
|
|
|
61f723 |
+ for idx in range(1, 5):
|
|
|
61f723 |
+ try:
|
|
|
61f723 |
+ USER_DN = ("uid=member%d,%s" % (idx, DEFAULT_SUFFIX))
|
|
|
61f723 |
+ ent = topo.standalone.getEntry(USER_DN, ldap.SCOPE_BASE, "(objectclass=*)")
|
|
|
61f723 |
+ if presence_flag:
|
|
|
61f723 |
+ assert ent.hasAttr('memberof') and ent.getValue('memberof') == group_dn
|
|
|
61f723 |
+ else:
|
|
|
61f723 |
+ assert not ent.hasAttr('memberof')
|
|
|
61f723 |
+ except ldap.LDAPError as e:
|
|
|
61f723 |
+ log.fatal('Failed to retrieve user (%s): error %s' % (USER_DN, e.message['desc']))
|
|
|
61f723 |
+ assert False
|
|
|
61f723 |
+
|
|
|
61f723 |
+def _check_memberof(topo, member=None, memberof=True, group_dn=None):
|
|
|
61f723 |
+ ent = topo.standalone.getEntry(member, ldap.SCOPE_BASE, "(objectclass=*)")
|
|
|
61f723 |
+ if memberof:
|
|
|
61f723 |
+ assert group_dn
|
|
|
61f723 |
+ assert ent.hasAttr('memberof') and group_dn in ent.getValues('memberof')
|
|
|
61f723 |
+ else:
|
|
|
61f723 |
+ if ent.hasAttr('memberof'):
|
|
|
61f723 |
+ assert group_dn not in ent.getValues('memberof')
|
|
|
61f723 |
+
|
|
|
61f723 |
+
|
|
|
61f723 |
+def test_ticket49184(topo):
|
|
|
61f723 |
+ """Write your testcase here...
|
|
|
61f723 |
+
|
|
|
61f723 |
+ Also, if you need any testcase initialization,
|
|
|
61f723 |
+ please, write additional fixture for that(include finalizer).
|
|
|
61f723 |
+ """
|
|
|
61f723 |
+
|
|
|
61f723 |
+ topo.standalone.plugins.enable(name=PLUGIN_MEMBER_OF)
|
|
|
61f723 |
+ topo.standalone.restart(timeout=10)
|
|
|
61f723 |
+
|
|
|
61f723 |
+ #
|
|
|
61f723 |
+ # create some users and a group
|
|
|
61f723 |
+ #
|
|
|
61f723 |
+ log.info('create users and group...')
|
|
|
61f723 |
+ for idx in range(1, 5):
|
|
|
61f723 |
+ try:
|
|
|
61f723 |
+ USER_DN = ("uid=member%d,%s" % (idx, DEFAULT_SUFFIX))
|
|
|
61f723 |
+ topo.standalone.add_s(Entry((USER_DN,
|
|
|
61f723 |
+ {'objectclass': 'top extensibleObject'.split(),
|
|
|
61f723 |
+ 'uid': 'member%d' % (idx)})))
|
|
|
61f723 |
+ except ldap.LDAPError as e:
|
|
|
61f723 |
+ log.fatal('Failed to add user (%s): error %s' % (USER_DN, e.message['desc']))
|
|
|
61f723 |
+ assert False
|
|
|
61f723 |
+
|
|
|
61f723 |
+ # add all users in GROUP_DN_1 and checks each users is memberof GROUP_DN_1
|
|
|
61f723 |
+ _add_group_with_members(topo, GROUP_DN_1)
|
|
|
61f723 |
+ for idx in range(1, 5):
|
|
|
61f723 |
+ USER_DN = ("uid=member%d,%s" % (idx, DEFAULT_SUFFIX))
|
|
|
61f723 |
+ _check_memberof(topo, member=USER_DN, memberof=True, group_dn=GROUP_DN_1 )
|
|
|
61f723 |
+
|
|
|
61f723 |
+ # add all users in GROUP_DN_2 and checks each users is memberof GROUP_DN_2
|
|
|
61f723 |
+ _add_group_with_members(topo, GROUP_DN_2)
|
|
|
61f723 |
+ for idx in range(1, 5):
|
|
|
61f723 |
+ USER_DN = ("uid=member%d,%s" % (idx, DEFAULT_SUFFIX))
|
|
|
61f723 |
+ _check_memberof(topo, member=USER_DN, memberof=True, group_dn=GROUP_DN_2 )
|
|
|
61f723 |
+
|
|
|
61f723 |
+ # add the level 2, 3 and 4 group
|
|
|
61f723 |
+ for super_grp in (SUPER_GRP1, SUPER_GRP2, SUPER_GRP3):
|
|
|
61f723 |
+ topo.standalone.add_s(Entry((super_grp,
|
|
|
61f723 |
+ {'objectclass': 'top groupofnames extensibleObject'.split(),
|
|
|
61f723 |
+ 'cn': 'super_grp'})))
|
|
|
61f723 |
+ topo.standalone.modify_s(SUPER_GRP1,
|
|
|
61f723 |
+ [(ldap.MOD_ADD,
|
|
|
61f723 |
+ 'member',
|
|
|
61f723 |
+ GROUP_DN_1),
|
|
|
61f723 |
+ (ldap.MOD_ADD,
|
|
|
61f723 |
+ 'member',
|
|
|
61f723 |
+ GROUP_DN_2)])
|
|
|
61f723 |
+ topo.standalone.modify_s(SUPER_GRP2,
|
|
|
61f723 |
+ [(ldap.MOD_ADD,
|
|
|
61f723 |
+ 'member',
|
|
|
61f723 |
+ GROUP_DN_1),
|
|
|
61f723 |
+ (ldap.MOD_ADD,
|
|
|
61f723 |
+ 'member',
|
|
|
61f723 |
+ GROUP_DN_2)])
|
|
|
61f723 |
+ return
|
|
|
61f723 |
+ topo.standalone.delete_s(GROUP_DN_2)
|
|
|
61f723 |
+ for idx in range(1, 5):
|
|
|
61f723 |
+ USER_DN = ("uid=member%d,%s" % (idx, DEFAULT_SUFFIX))
|
|
|
61f723 |
+ _check_memberof(topo, member=USER_DN, memberof=True, group_dn=GROUP_DN_1 )
|
|
|
61f723 |
+ _check_memberof(topo, member=USER_DN, memberof=False, group_dn=GROUP_DN_2 )
|
|
|
61f723 |
+
|
|
|
61f723 |
+ if DEBUGGING:
|
|
|
61f723 |
+ # Add debugging steps(if any)...
|
|
|
61f723 |
+ pass
|
|
|
61f723 |
+
|
|
|
61f723 |
+
|
|
|
61f723 |
+if __name__ == '__main__':
|
|
|
61f723 |
+ # Run isolated
|
|
|
61f723 |
+ # -s for DEBUG mode
|
|
|
61f723 |
+ CURRENT_FILE = os.path.realpath(__file__)
|
|
|
61f723 |
+ pytest.main("-s %s" % CURRENT_FILE)
|
|
|
61f723 |
+
|
|
|
61f723 |
diff --git a/ldap/servers/plugins/memberof/memberof.c b/ldap/servers/plugins/memberof/memberof.c
|
|
|
61f723 |
index 81ef092..5cd2c01 100644
|
|
|
61f723 |
--- a/ldap/servers/plugins/memberof/memberof.c
|
|
|
61f723 |
+++ b/ldap/servers/plugins/memberof/memberof.c
|
|
|
61f723 |
@@ -159,7 +159,7 @@ static int memberof_qsort_compare(const void *a, const void *b);
|
|
|
61f723 |
static void memberof_load_array(Slapi_Value **array, Slapi_Attr *attr);
|
|
|
61f723 |
static int memberof_del_dn_from_groups(Slapi_PBlock *pb, MemberOfConfig *config, Slapi_DN *sdn);
|
|
|
61f723 |
static int memberof_call_foreach_dn(Slapi_PBlock *pb, Slapi_DN *sdn, MemberOfConfig *config,
|
|
|
61f723 |
- char **types, plugin_search_entry_callback callback, void *callback_data, int *cached);
|
|
|
61f723 |
+ char **types, plugin_search_entry_callback callback, void *callback_data, int *cached, PRBool use_grp_cache);
|
|
|
61f723 |
static int memberof_is_direct_member(MemberOfConfig *config, Slapi_Value *groupdn,
|
|
|
61f723 |
Slapi_Value *memberdn);
|
|
|
61f723 |
static int memberof_is_grouping_attr(char *type, MemberOfConfig *config);
|
|
|
61f723 |
@@ -659,7 +659,7 @@ memberof_del_dn_from_groups(Slapi_PBlock *pb, MemberOfConfig *config, Slapi_DN *
|
|
|
61f723 |
|
|
|
61f723 |
slapi_log_err(SLAPI_LOG_PLUGIN, MEMBEROF_PLUGIN_SUBSYSTEM, "memberof_del_dn_from_groups: Ancestors of %s\n", slapi_sdn_get_dn(sdn));
|
|
|
61f723 |
rc = memberof_call_foreach_dn(pb, sdn, config, groupattrs,
|
|
|
61f723 |
- memberof_del_dn_type_callback, &data, &cached);
|
|
|
61f723 |
+ memberof_del_dn_type_callback, &data, &cached, PR_FALSE);
|
|
|
61f723 |
}
|
|
|
61f723 |
|
|
|
61f723 |
return rc;
|
|
|
61f723 |
@@ -776,8 +776,8 @@ add_ancestors_cbdata(memberof_cached_value *ancestors, void *callback_data)
|
|
|
61f723 |
* could want type to be either "member" or "memberOf" depending on the case.
|
|
|
61f723 |
*/
|
|
|
61f723 |
int
|
|
|
61f723 |
-memberof_call_foreach_dn(Slapi_PBlock *pb, Slapi_DN *sdn,
|
|
|
61f723 |
- MemberOfConfig *config, char **types, plugin_search_entry_callback callback, void *callback_data, int *cached)
|
|
|
61f723 |
+memberof_call_foreach_dn(Slapi_PBlock *pb __attribute__((unused)), Slapi_DN *sdn,
|
|
|
61f723 |
+ MemberOfConfig *config, char **types, plugin_search_entry_callback callback, void *callback_data, int *cached, PRBool use_grp_cache)
|
|
|
61f723 |
{
|
|
|
61f723 |
Slapi_PBlock *search_pb = NULL;
|
|
|
61f723 |
Slapi_DN *base_sdn = NULL;
|
|
|
61f723 |
@@ -792,9 +792,6 @@ memberof_call_foreach_dn(Slapi_PBlock *pb, Slapi_DN *sdn,
|
|
|
61f723 |
int free_it = 0;
|
|
|
61f723 |
int rc = 0;
|
|
|
61f723 |
int i = 0;
|
|
|
61f723 |
- memberof_cached_value *ht_grp = NULL;
|
|
|
61f723 |
- memberof_get_groups_data *data = (memberof_get_groups_data*) callback_data;
|
|
|
61f723 |
- const char *ndn = slapi_sdn_get_ndn(sdn);
|
|
|
61f723 |
|
|
|
61f723 |
*cached = 0;
|
|
|
61f723 |
|
|
|
61f723 |
@@ -802,17 +799,24 @@ memberof_call_foreach_dn(Slapi_PBlock *pb, Slapi_DN *sdn,
|
|
|
61f723 |
return (rc);
|
|
|
61f723 |
}
|
|
|
61f723 |
|
|
|
61f723 |
- /* Here we will retrieve the ancestor of sdn.
|
|
|
61f723 |
- * The key access is the normalized sdn
|
|
|
61f723 |
- * This is done through recursive internal searches of parents
|
|
|
61f723 |
- * If the ancestors of sdn are already cached, just use
|
|
|
61f723 |
- * this value
|
|
|
61f723 |
+ /* This flags indicates memberof_call_foreach_dn is called to retrieve ancestors (groups).
|
|
|
61f723 |
+ * To improve performance, it can use a cache. (it will not in case of circular groups)
|
|
|
61f723 |
+ * When this flag is true it means no circular group are detected (so far) so we can use the cache
|
|
|
61f723 |
*/
|
|
|
61f723 |
- if (data && data->use_cache) {
|
|
|
61f723 |
+ if (use_grp_cache) {
|
|
|
61f723 |
+ /* Here we will retrieve the ancestor of sdn.
|
|
|
61f723 |
+ * The key access is the normalized sdn
|
|
|
61f723 |
+ * This is done through recursive internal searches of parents
|
|
|
61f723 |
+ * If the ancestors of sdn are already cached, just use
|
|
|
61f723 |
+ * this value
|
|
|
61f723 |
+ */
|
|
|
61f723 |
+ memberof_cached_value *ht_grp = NULL;
|
|
|
61f723 |
+ const char *ndn = slapi_sdn_get_ndn(sdn);
|
|
|
61f723 |
+
|
|
|
61f723 |
ht_grp = ancestors_cache_lookup((const void *) ndn);
|
|
|
61f723 |
if (ht_grp) {
|
|
|
61f723 |
#if MEMBEROF_CACHE_DEBUG
|
|
|
61f723 |
- slapi_log_err(SLAPI_LOG_PLUGIN, MEMBEROF_PLUGIN_SUBSYSTEM, "memberof_call_foreach_dn: Ancestors of %s already cached (%x)\n", ndn, ht_grp);
|
|
|
61f723 |
+ slapi_log_err(SLAPI_LOG_PLUGIN, MEMBEROF_PLUGIN_SUBSYSTEM, "memberof_call_foreach_dn: Ancestors of %s already cached (%x)\n", ndn, ht_grp);
|
|
|
61f723 |
#endif
|
|
|
61f723 |
add_ancestors_cbdata(ht_grp, callback_data);
|
|
|
61f723 |
*cached = 1;
|
|
|
61f723 |
@@ -1106,7 +1110,7 @@ memberof_replace_dn_from_groups(Slapi_PBlock *pb, MemberOfConfig *config,
|
|
|
61f723 |
slapi_log_err(SLAPI_LOG_PLUGIN, MEMBEROF_PLUGIN_SUBSYSTEM, "memberof_replace_dn_from_groups: Ancestors of %s\n", slapi_sdn_get_dn(post_sdn));
|
|
|
61f723 |
if((ret = memberof_call_foreach_dn(pb, pre_sdn, config, groupattrs,
|
|
|
61f723 |
memberof_replace_dn_type_callback,
|
|
|
61f723 |
- &data, &cached)))
|
|
|
61f723 |
+ &data, &cached, PR_FALSE)))
|
|
|
61f723 |
{
|
|
|
61f723 |
break;
|
|
|
61f723 |
}
|
|
|
61f723 |
@@ -2383,7 +2387,7 @@ memberof_get_groups_r(MemberOfConfig *config, Slapi_DN *member_sdn,
|
|
|
61f723 |
slapi_log_err(SLAPI_LOG_PLUGIN, MEMBEROF_PLUGIN_SUBSYSTEM, "memberof_get_groups_r: Ancestors of %s\n", slapi_sdn_get_dn(member_sdn));
|
|
|
61f723 |
#endif
|
|
|
61f723 |
rc = memberof_call_foreach_dn(NULL, member_sdn, config, config->groupattrs,
|
|
|
61f723 |
- memberof_get_groups_callback, &member_data, &cached);
|
|
|
61f723 |
+ memberof_get_groups_callback, &member_data, &cached, member_data.use_cache);
|
|
|
61f723 |
|
|
|
61f723 |
merge_ancestors(&member_ndn_val, &member_data, data);
|
|
|
61f723 |
if (!cached && member_data.use_cache)
|
|
|
61f723 |
@@ -2578,7 +2582,7 @@ memberof_test_membership(Slapi_PBlock *pb, MemberOfConfig *config,
|
|
|
61f723 |
int cached = 0;
|
|
|
61f723 |
|
|
|
61f723 |
return memberof_call_foreach_dn(pb, group_sdn, config, attrs,
|
|
|
61f723 |
- memberof_test_membership_callback, config, &cached);
|
|
|
61f723 |
+ memberof_test_membership_callback, config, &cached, PR_FALSE);
|
|
|
61f723 |
}
|
|
|
61f723 |
|
|
|
61f723 |
/*
|
|
|
61f723 |
--
|
|
|
61f723 |
2.9.3
|
|
|
61f723 |
|