Blame SOURCES/0062-CVE-2015-1854-389ds-base-access-control-bypass-with-.patch

309aa9
From 205bce153c7db8258a8a28498cf54e7374dca588 Mon Sep 17 00:00:00 2001
309aa9
From: Thierry Bordaz <tbordaz@redhat.com>
309aa9
Date: Tue, 14 Apr 2015 16:24:44 +0200
309aa9
Subject: [PATCH] CVE-2015-1854 389ds-base: access control bypass with modrdn
309aa9
309aa9
Bug Description:
309aa9
	47553 fix checks the write right access only if the RDN is
309aa9
	modified. This allows to rename entries even if the
309aa9
	authenticated user is not allowed of that.
309aa9
309aa9
Fix Description:
309aa9
	Roll back a wrong optimization that tested the write access
309aa9
	only if RDN value was changed.
309aa9
309aa9
https://fedorahosted.org/389/ticket/47553
309aa9
309aa9
Reviewed by: ?
309aa9
309aa9
Platforms tested: F17 (upstream test)
309aa9
309aa9
Flag Day: no
309aa9
309aa9
Doc impact: no
309aa9
309aa9
(cherry picked from commit 44e5c0998bdf7dcb167e8472713ff393b776e4e3)
309aa9
309aa9
Conflicts:
309aa9
	dirsrvtests/tickets/ticket47553_single_aci_test.py
309aa9
---
309aa9
 dirsrvtests/tickets/ticket47553_rdn_write_test.py  | 132 +++++++++++++++++++++
309aa9
 dirsrvtests/tickets/ticket47553_single_aci_test.py |  52 ++++++--
309aa9
 ldap/servers/slapd/back-ldbm/ldbm_modrdn.c         |  32 ++---
309aa9
 3 files changed, 184 insertions(+), 32 deletions(-)
309aa9
 create mode 100644 dirsrvtests/tickets/ticket47553_rdn_write_test.py
309aa9
309aa9
diff --git a/dirsrvtests/tickets/ticket47553_rdn_write_test.py b/dirsrvtests/tickets/ticket47553_rdn_write_test.py
309aa9
new file mode 100644
309aa9
index 0000000..f15d9b3
309aa9
--- /dev/null
309aa9
+++ b/dirsrvtests/tickets/ticket47553_rdn_write_test.py
309aa9
@@ -0,0 +1,132 @@
309aa9
+import os
309aa9
+import sys
309aa9
+import time
309aa9
+import ldap
309aa9
+import logging
309aa9
+import pytest
309aa9
+from lib389 import DirSrv, Entry, tools, tasks
309aa9
+from lib389.tools import DirSrvTools
309aa9
+from lib389._constants import *
309aa9
+from lib389.properties import *
309aa9
+from lib389.tasks import *
309aa9
+from lib389.utils import *
309aa9
+from ldap.controls.simple import GetEffectiveRightsControl
309aa9
+
309aa9
+logging.getLogger(__name__).setLevel(logging.DEBUG)
309aa9
+log = logging.getLogger(__name__)
309aa9
+
309aa9
+installation1_prefix = None
309aa9
+
309aa9
+SRC_ENTRY_CN = "tuser"
309aa9
+EXT_RDN = "01"
309aa9
+DST_ENTRY_CN = SRC_ENTRY_CN + EXT_RDN
309aa9
+
309aa9
+SRC_ENTRY_DN = "cn=%s,%s" % (SRC_ENTRY_CN, SUFFIX)
309aa9
+DST_ENTRY_DN = "cn=%s,%s" % (DST_ENTRY_CN, SUFFIX)
309aa9
+
309aa9
+class TopologyStandalone(object):
309aa9
+    def __init__(self, standalone):
309aa9
+        standalone.open()
309aa9
+        self.standalone = standalone
309aa9
+
309aa9
+
309aa9
+#@pytest.fixture(scope="module")
309aa9
+def topology(request):
309aa9
+    global installation1_prefix
309aa9
+
309aa9
+    # Creating standalone instance ...
309aa9
+    standalone = DirSrv(verbose=False)
309aa9
+    if installation1_prefix:
309aa9
+        args_instance[SER_DEPLOYED_DIR] = installation1_prefix
309aa9
+    args_instance[SER_HOST] = HOST_STANDALONE
309aa9
+    args_instance[SER_PORT] = PORT_STANDALONE
309aa9
+    args_instance[SER_SERVERID_PROP] = SERVERID_STANDALONE
309aa9
+    args_instance[SER_CREATION_SUFFIX] = DEFAULT_SUFFIX
309aa9
+    args_standalone = args_instance.copy()
309aa9
+    standalone.allocate(args_standalone)
309aa9
+    instance_standalone = standalone.exists()
309aa9
+    if instance_standalone:
309aa9
+        standalone.delete()
309aa9
+    standalone.create()
309aa9
+    standalone.open()
309aa9
+
309aa9
+    # Clear out the tmp dir
309aa9
+    standalone.clearTmpDir(__file__)
309aa9
+
309aa9
+    return TopologyStandalone(standalone)
309aa9
+
309aa9
+def test_ticket47553_rdn_write_init(topology):
309aa9
+    topology.standalone.log.info("\n\n######################### Add entry tuser ######################\n")
309aa9
+    topology.standalone.add_s(Entry((SRC_ENTRY_DN, {
309aa9
+                                                'objectclass': "top person".split(),
309aa9
+                                                'sn': SRC_ENTRY_CN,
309aa9
+                                                'cn': SRC_ENTRY_CN})))
309aa9
+
309aa9
+def test_ticket47553_rdn_write_get_ger(topology):
309aa9
+    ANONYMOUS_DN = ""
309aa9
+    topology.standalone.log.info("\n\n######################### GER rights for anonymous ######################\n")
309aa9
+    request_ctrl = GetEffectiveRightsControl(criticality=True, authzId="dn:" + ANONYMOUS_DN)
309aa9
+    msg_id = topology.standalone.search_ext(SUFFIX, ldap.SCOPE_SUBTREE, "objectclass=*", serverctrls=[request_ctrl])
309aa9
+    rtype, rdata, rmsgid, response_ctrl = topology.standalone.result3(msg_id)
309aa9
+    value = ''
309aa9
+    for dn, attrs in rdata:
309aa9
+        topology.standalone.log.info("dn: %s" % dn)
309aa9
+        for value in attrs['entryLevelRights']:
309aa9
+            topology.standalone.log.info("###############  entryLevelRights: %r" % value)
309aa9
+            assert 'n' not in value
309aa9
+
309aa9
+def test_ticket47553_rdn_write_modrdn_anonymous(topology):
309aa9
+    ANONYMOUS_DN = ""
309aa9
+    topology.standalone.close()
309aa9
+    topology.standalone.binddn = ANONYMOUS_DN
309aa9
+    topology.standalone.open()
309aa9
+    msg_id = topology.standalone.search_ext("", ldap.SCOPE_BASE, "objectclass=*")
309aa9
+    rtype, rdata, rmsgid, response_ctrl = topology.standalone.result3(msg_id)
309aa9
+    value = ''
309aa9
+    for dn, attrs in rdata:
309aa9
+        topology.standalone.log.info("dn: %s" % dn)
309aa9
+        for attr in attrs:
309aa9
+            topology.standalone.log.info("###############  %r: %r" % (attr, attrs[attr]))
309aa9
+
309aa9
+
309aa9
+    try:
309aa9
+        topology.standalone.rename_s(SRC_ENTRY_DN, "cn=%s" % DST_ENTRY_CN, delold=True)
309aa9
+    except Exception as e:
309aa9
+        topology.standalone.log.info("Exception (expected): %s" % type(e).__name__)
309aa9
+        isinstance(e, ldap.INSUFFICIENT_ACCESS)
309aa9
+
309aa9
+    try:
309aa9
+        topology.standalone.getEntry(DST_ENTRY_DN, ldap.SCOPE_BASE, "objectclass=*")
309aa9
+        assert False
309aa9
+    except Exception as e:
309aa9
+        topology.standalone.log.info("The entry was not renamed (expected)")
309aa9
+        isinstance(e, ldap.NO_SUCH_OBJECT)
309aa9
+
309aa9
+def test_ticket47553_rdn_write(topology):
309aa9
+    '''
309aa9
+    Write your testcase here...
309aa9
+    '''
309aa9
+
309aa9
+    log.info('Test complete')
309aa9
+
309aa9
+
309aa9
+def test_ticket47553_rdn_write_final(topology):
309aa9
+    topology.standalone.delete()
309aa9
+    log.info('Testcase PASSED')
309aa9
+
309aa9
+
309aa9
+def run_isolated():
309aa9
+    global installation1_prefix
309aa9
+    installation1_prefix = '/home/tbordaz/install_master'
309aa9
+
309aa9
+    topo = topology(True)
309aa9
+    test_ticket47553_rdn_write_init(topo)
309aa9
+    test_ticket47553_rdn_write_get_ger(topo)
309aa9
+    test_ticket47553_rdn_write(topo)
309aa9
+    test_ticket47553_rdn_write_modrdn_anonymous(topo)
309aa9
+    test_ticket47553_rdn_write_final(topo)
309aa9
+
309aa9
+
309aa9
+if __name__ == '__main__':
309aa9
+    run_isolated()
309aa9
+
309aa9
diff --git a/dirsrvtests/tickets/ticket47553_single_aci_test.py b/dirsrvtests/tickets/ticket47553_single_aci_test.py
309aa9
index 4be2470..0c8d7e9 100644
309aa9
--- a/dirsrvtests/tickets/ticket47553_single_aci_test.py
309aa9
+++ b/dirsrvtests/tickets/ticket47553_single_aci_test.py
309aa9
@@ -276,7 +276,27 @@ def _moddn_aci_deny_tree(topology, mod_type=None, target_from=STAGING_DN, target
309aa9
     #topology.master1.modify_s(SUFFIX, mod)
309aa9
     topology.master1.log.info("Add a DENY aci under %s " % PROD_EXCEPT_DN)
309aa9
     topology.master1.modify_s(PROD_EXCEPT_DN, mod)
309aa9
-    
309aa9
+
309aa9
+def _write_aci_staging(topology, mod_type=None):
309aa9
+    assert mod_type is not None
309aa9
+
309aa9
+    ACI_TARGET = "(targetattr= \"cn\")(target=\"ldap:///cn=*,%s\")" % STAGING_DN
309aa9
+    ACI_ALLOW        = "(version 3.0; acl \"write staging entries\"; allow (write)"
309aa9
+    ACI_SUBJECT      = " userdn = \"ldap:///%s\";)" % BIND_DN
309aa9
+    ACI_BODY         = ACI_TARGET + ACI_ALLOW + ACI_SUBJECT
309aa9
+    mod = [(mod_type, 'aci', ACI_BODY)]
309aa9
+    topology.master1.modify_s(SUFFIX, mod)
309aa9
+
309aa9
+def _write_aci_production(topology, mod_type=None):
309aa9
+    assert mod_type is not None
309aa9
+
309aa9
+    ACI_TARGET = "(targetattr= \"cn\")(target=\"ldap:///cn=*,%s\")" % PRODUCTION_DN
309aa9
+    ACI_ALLOW        = "(version 3.0; acl \"write production entries\"; allow (write)"
309aa9
+    ACI_SUBJECT      = " userdn = \"ldap:///%s\";)" % BIND_DN
309aa9
+    ACI_BODY         = ACI_TARGET + ACI_ALLOW + ACI_SUBJECT
309aa9
+    mod = [(mod_type, 'aci', ACI_BODY)]
309aa9
+    topology.master1.modify_s(SUFFIX, mod)
309aa9
+
309aa9
 def _moddn_aci_staging_to_production(topology, mod_type=None, target_from=STAGING_DN, target_to=PRODUCTION_DN):
309aa9
     assert mod_type != None
309aa9
 
309aa9
@@ -293,6 +313,8 @@ def _moddn_aci_staging_to_production(topology, mod_type=None, target_from=STAGIN
309aa9
     ACI_BODY         = ACI_TARGET_FROM + ACI_TARGET_TO + ACI_ALLOW + ACI_SUBJECT
309aa9
     mod = [(mod_type, 'aci', ACI_BODY)]
309aa9
     topology.master1.modify_s(SUFFIX, mod)
309aa9
+    
309aa9
+    _write_aci_staging(topology, mod_type=mod_type)
309aa9
 
309aa9
 def _moddn_aci_from_production_to_staging(topology, mod_type=None):
309aa9
     assert mod_type != None
309aa9
@@ -303,6 +325,8 @@ def _moddn_aci_from_production_to_staging(topology, mod_type=None):
309aa9
     ACI_BODY         = ACI_TARGET + ACI_ALLOW + ACI_SUBJECT
309aa9
     mod = [(mod_type, 'aci', ACI_BODY)]
309aa9
     topology.master1.modify_s(SUFFIX, mod)
309aa9
+    
309aa9
+    _write_aci_production(topology, mod_type=mod_type)
309aa9
 
309aa9
 
309aa9
 def test_ticket47553_init(topology):
309aa9
@@ -347,12 +371,9 @@ def test_ticket47553_init(topology):
309aa9
                                             'description': "production except DIT"})))
309aa9
     
309aa9
     # enable acl error logging
309aa9
-    #mod = [(ldap.MOD_REPLACE, 'nsslapd-errorlog-level', '128')]
309aa9
-    #topology.master1.modify_s(DN_CONFIG, mod)
309aa9
-    #topology.master2.modify_s(DN_CONFIG, mod)
309aa9
-    
309aa9
-
309aa9
-    
309aa9
+    mod = [(ldap.MOD_REPLACE, 'nsslapd-errorlog-level', str(128+262144))]
309aa9
+    topology.master1.modify_s(DN_CONFIG, mod)
309aa9
+    topology.master2.modify_s(DN_CONFIG, mod)
309aa9
     
309aa9
     
309aa9
     # add dummy entries in the staging DIT
309aa9
@@ -883,6 +904,7 @@ def test_ticket47553_moddn_staging_prod_9(topology):
309aa9
     _bind_manager(topology)
309aa9
     mod = [(ldap.MOD_ADD, 'aci', ACI_BODY)]
309aa9
     topology.master1.modify_s(PRODUCTION_DN, mod)
309aa9
+    _write_aci_staging(topology, mod_type=ldap.MOD_ADD)
309aa9
     _bind_normal(topology)
309aa9
     
309aa9
     topology.master1.log.info("Try to MODDN %s -> %s,%s" % (old_dn, new_rdn, new_superior))
309aa9
@@ -891,6 +913,7 @@ def test_ticket47553_moddn_staging_prod_9(topology):
309aa9
     _bind_manager(topology)
309aa9
     mod = [(ldap.MOD_DELETE, 'aci', ACI_BODY)]
309aa9
     topology.master1.modify_s(PRODUCTION_DN, mod)
309aa9
+    _write_aci_staging(topology, mod_type=ldap.MOD_DELETE)
309aa9
     _bind_normal(topology)
309aa9
     
309aa9
     
309aa9
@@ -934,6 +957,7 @@ def test_ticket47553_moddn_staging_prod_9(topology):
309aa9
     _bind_manager(topology)
309aa9
     mod = [(ldap.MOD_ADD, 'aci', ACI_BODY)]
309aa9
     topology.master1.modify_s(PRODUCTION_DN, mod)
309aa9
+    _write_aci_staging(topology, mod_type=ldap.MOD_ADD)
309aa9
     _bind_normal(topology)
309aa9
     
309aa9
     try:
309aa9
@@ -949,6 +973,7 @@ def test_ticket47553_moddn_staging_prod_9(topology):
309aa9
     _bind_manager(topology)
309aa9
     mod = [(ldap.MOD_DELETE, 'aci', ACI_BODY)]
309aa9
     topology.master1.modify_s(PRODUCTION_DN, mod)
309aa9
+    _write_aci_staging(topology, mod_type=ldap.MOD_DELETE)
309aa9
     _bind_normal(topology)
309aa9
     
309aa9
     # Add the moddn aci that will be evaluated because of the config flag
309aa9
@@ -1009,7 +1034,12 @@ def test_ticket47553_moddn_prod_staging(topology):
309aa9
     old_dn  = "%s,%s" % (old_rdn, PRODUCTION_DN)
309aa9
     new_rdn = old_rdn
309aa9
     new_superior = STAGING_DN
309aa9
-    
309aa9
+
309aa9
+    # add the write right because we want to check the moddn
309aa9
+    _bind_manager(topology)
309aa9
+    _write_aci_production(topology, mod_type=ldap.MOD_ADD)
309aa9
+    _bind_normal(topology)
309aa9
+
309aa9
     try:
309aa9
         topology.master1.log.info("Try to move back MODDN %s -> %s,%s" % (old_dn, new_rdn, new_superior))
309aa9
         topology.master1.rename_s(old_dn, new_rdn, newsuperior=new_superior)
309aa9
@@ -1019,7 +1049,11 @@ def test_ticket47553_moddn_prod_staging(topology):
309aa9
     except Exception as e:
309aa9
         topology.master1.log.info("Exception (expected): %s" % type(e).__name__)
309aa9
         assert isinstance(e, ldap.INSUFFICIENT_ACCESS)
309aa9
-    
309aa9
+
309aa9
+    _bind_manager(topology)
309aa9
+    _write_aci_production(topology, mod_type=ldap.MOD_DELETE)
309aa9
+    _bind_normal(topology)
309aa9
+
309aa9
     # successfull MOD with the both ACI
309aa9
     _bind_manager(topology)
309aa9
     _moddn_aci_staging_to_production(topology, mod_type=ldap.MOD_DELETE, target_from=STAGING_DN, target_to=PRODUCTION_DN)
309aa9
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c b/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c
309aa9
index 6a4982c..4129318 100644
309aa9
--- a/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c
309aa9
+++ b/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c
309aa9
@@ -677,31 +677,17 @@ ldbm_back_modrdn( Slapi_PBlock *pb )
309aa9
             
309aa9
             /* JCMACL - Should be performed before the child check. */
309aa9
             /* JCMACL - Why is the check performed against the copy, rather than the existing entry? */
309aa9
+            /* This check must be performed even if the entry is renamed with its own name 
309aa9
+             * No optimization here we need to check we have the write access to the target entry
309aa9
+             */
309aa9
+            ldap_result_code = plugin_call_acl_plugin(pb, ec->ep_entry,
309aa9
+                    NULL /*attr*/, NULL /*value*/, SLAPI_ACL_WRITE,
309aa9
+                    ACLPLUGIN_ACCESS_MODRDN, &errbuf);
309aa9
+            if (ldap_result_code != LDAP_SUCCESS)
309aa9
             {
309aa9
-                Slapi_RDN *new_rdn;
309aa9
-                Slapi_RDN *old_rdn;
309aa9
-
309aa9
-                /* Taken from the entry */
309aa9
-                old_rdn = slapi_entry_get_srdn(ec->ep_entry);
309aa9
-
309aa9
-                /* Taken from the request */
309aa9
-                new_rdn = slapi_rdn_new();
309aa9
-                slapi_sdn_get_rdn(&dn_newrdn, new_rdn);
309aa9
-
309aa9
-                /* Only if we change the RDN value, we need the write access to the entry */
309aa9
-                if (slapi_rdn_compare(old_rdn, new_rdn)) {
309aa9
-                        ldap_result_code = plugin_call_acl_plugin(pb, ec->ep_entry,
309aa9
-                                NULL /*attr*/, NULL /*value*/, SLAPI_ACL_WRITE,
309aa9
-                                ACLPLUGIN_ACCESS_MODRDN, &errbuf);
309aa9
-                }
309aa9
-
309aa9
-                slapi_rdn_free(&new_rdn);
309aa9
+                goto error_return;
309aa9
+            }
309aa9
 
309aa9
-                if (ldap_result_code != LDAP_SUCCESS) {
309aa9
-                        goto error_return;
309aa9
-                }
309aa9
-            } 
309aa9
-        
309aa9
             /* Set the new dn to the copy of the entry */
309aa9
             slapi_entry_set_sdn( ec->ep_entry, &dn_newdn );
309aa9
             if (entryrdn_get_switch()) { /* subtree-rename: on */
309aa9
-- 
309aa9
1.9.3
309aa9