Blame SOURCES/0025-Ticket-49184-Overflow-in-memberof.patch

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