Blob Blame Raw
From 2a181763d0cff5f31dc18f3e71f79dd815906c09 Mon Sep 17 00:00:00 2001
From: Mark Reynolds <mreynolds@redhat.com>
Date: Fri, 22 Feb 2019 13:46:48 -0500
Subject: [PATCH] Ticket 50238 - Failed modrdn can corrupt entry cache

Bug Description:  Under certain conditions (found under IPA) when a backend
                  transaction plugin fails and causes a modrdn operation to
                  fail the entry cache no longer contains the original/pre
                  entry, but instead it has the post modrdn'ed entry with
                  the original entry's ID

Fix Description:  Upon failure, if the post entry is in the cache, then swap
                  it out with the original entry.

https://pagure.io/389-ds-base/issue/50238

Reviewed by: firstyear, spichugi, & tboardaz (Thanks!!!)
---
 dirsrvtests/tests/suites/betxns/betxn_test.py | 57 +++++++++++++++++++
 ldap/servers/slapd/back-ldbm/ldbm_modrdn.c    | 16 ++++--
 2 files changed, 68 insertions(+), 5 deletions(-)

diff --git a/dirsrvtests/tests/suites/betxns/betxn_test.py b/dirsrvtests/tests/suites/betxns/betxn_test.py
index 175496495..48181a9ea 100644
--- a/dirsrvtests/tests/suites/betxns/betxn_test.py
+++ b/dirsrvtests/tests/suites/betxns/betxn_test.py
@@ -8,6 +8,7 @@
 #
 import pytest
 import six
+import ldap
 from lib389.tasks import *
 from lib389.utils import *
 from lib389.topologies import topology_st
@@ -248,6 +249,62 @@ def test_betxn_memberof(topology_st, dynamic_plugins):
     log.info('test_betxn_memberof: PASSED')
 
 
+def test_betxn_modrdn_memberof(topology_st):
+    """Test modrdn operartions and memberOf
+
+    :id: 70d0b96e-b693-4bf7-bbf5-102a66ac5994
+
+    :setup: Standalone instance
+
+    :steps: 1. Enable and configure memberOf plugin
+            2. Set memberofgroupattr="member" and memberofAutoAddOC="nsContainer"
+            3. Create group and user outside of memberOf plugin scope
+            4. Do modrdn to move group into scope
+            5. Do modrdn to move group into scope (again)
+
+    :expectedresults:
+            1. memberOf plugin plugin should be ON
+            2. Set memberofgroupattr="member" and memberofAutoAddOC="nsContainer" should PASS
+            3. Creating group and user should PASS
+            4. Modrdn should fail with objectclass violation
+            5. Second modrdn should also fail with objectclass violation
+    """
+
+    peoplebase = 'ou=people,%s' % DEFAULT_SUFFIX
+    memberof = MemberOfPlugin(topology_st.standalone)
+    memberof.enable()
+    memberof.set_autoaddoc('nsContainer')  # Bad OC
+    memberof.set('memberOfEntryScope', peoplebase)
+    memberof.set('memberOfAllBackends', 'on')
+    topology_st.standalone.restart()
+
+    groups = Groups(topology_st.standalone, DEFAULT_SUFFIX)
+    group = groups.create(properties={
+        'cn': 'group',
+    })
+
+    # Create user and add it to group
+    users = UserAccounts(topology_st.standalone, basedn=DEFAULT_SUFFIX)
+    user = users.create(properties=TEST_USER_PROPERTIES)
+    if not ds_is_older('1.3.7'):
+        user.remove('objectClass', 'nsMemberOf')
+
+    group.add_member(user.dn)
+
+    # Attempt modrdn that should fail, but the original entry should stay in the cache
+    with pytest.raises(ldap.OBJECTCLASS_VIOLATION):
+        group.rename('cn=group_to_people', newsuperior=peoplebase)
+
+    # Should fail, but not with NO_SUCH_OBJECT as the original entry should still be in the cache
+    with pytest.raises(ldap.OBJECTCLASS_VIOLATION):
+        group.rename('cn=group_to_people', newsuperior=peoplebase)
+
+    #
+    # Done
+    #
+    log.info('test_betxn_modrdn_memberof: PASSED')
+
+
 if __name__ == '__main__':
     # Run isolated
     # -s for DEBUG mode
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c b/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c
index 684b040b8..e4d0337d4 100644
--- a/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c
+++ b/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c
@@ -1411,14 +1411,20 @@ common_return:
                                       "operation failed, the target entry is cleared from dncache (%s)\n", slapi_entry_get_dn(ec->ep_entry));
             CACHE_REMOVE(&inst->inst_dncache, bdn);
             CACHE_RETURN(&inst->inst_dncache, &bdn);
+            /*
+             * If the new/invalid entry (ec) is in the cache, that means we need to
+             * swap it out with the original entry (e) --> to undo the swap that
+             * modrdn_rename_entry_update_indexes() did.
+             */
+            if (cache_is_in_cache(&inst->inst_cache, ec)) {
+                if (cache_replace(&inst->inst_cache, ec, e) != 0) {
+                        slapi_log_err(SLAPI_LOG_ALERT, "ldbm_back_modrdn",
+                                "failed to replace cache entry after error\n");
+                 }
+            }
         }
 
-        /* remove the new entry from the cache if the op failed -
-           otherwise, leave it in */
         if (ec && inst) {
-            if (retval && cache_is_in_cache(&inst->inst_cache, ec)) {
-                CACHE_REMOVE(&inst->inst_cache, ec);
-            }
             CACHE_RETURN(&inst->inst_cache, &ec);
         }
         ec = NULL;
-- 
2.17.2