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

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