Log message for revision 29883: - Forward-port fix for vulnerability addressed in Hotfix_20050405 from 2.7 branch.
Changed: U Zope/trunk/doc/CHANGES.txt U Zope/trunk/lib/python/AccessControl/ZopeGuards.py U Zope/trunk/lib/python/AccessControl/tests/testZopeGuards.py U Zope/trunk/lib/python/RestrictedPython/RestrictionMutator.py -=- Modified: Zope/trunk/doc/CHANGES.txt =================================================================== --- Zope/trunk/doc/CHANGES.txt 2005-04-05 20:40:42 UTC (rev 29882) +++ Zope/trunk/doc/CHANGES.txt 2005-04-05 20:58:31 UTC (rev 29883) @@ -30,6 +30,9 @@ Features added Bugs fixed + + - Hotfix_20050405: classes defined in untrusted code could shadow + the roles of methods defined as protected by their bases. - Collector #1656: Fixed enumeration within untrusted code (forward-port from 2.7 branch). Modified: Zope/trunk/lib/python/AccessControl/ZopeGuards.py =================================================================== --- Zope/trunk/lib/python/AccessControl/ZopeGuards.py 2005-04-05 20:40:42 UTC (rev 29882) +++ Zope/trunk/lib/python/AccessControl/ZopeGuards.py 2005-04-05 20:58:31 UTC (rev 29883) @@ -367,6 +367,9 @@ # This metaclass supplies the security declarations that allow all # attributes of a class and its instances to be read and written. def _metaclass(name, bases, dict): + for k, v in dict.items(): + if k.endswith('__roles__') and k[:len('__roles__')] not in dict: + raise Unauthorized, "Can't override security: %s" % k ob = type(name, bases, dict) ob.__allow_access_to_unprotected_subobjects__ = 1 ob._guarded_writes = 1 Modified: Zope/trunk/lib/python/AccessControl/tests/testZopeGuards.py =================================================================== --- Zope/trunk/lib/python/AccessControl/tests/testZopeGuards.py 2005-04-05 20:40:42 UTC (rev 29882) +++ Zope/trunk/lib/python/AccessControl/tests/testZopeGuards.py 2005-04-05 20:58:31 UTC (rev 29883) @@ -58,7 +58,6 @@ self.calls.append(('checkPermission', args)) return not self.reject - class GuardTestCase(unittest.TestCase): def setSecurityManager(self, manager): @@ -378,7 +377,82 @@ # the next one could be called an integration test. But we're simply # trying to run restricted Python with the *intended* implementations of # the special wrappers here, so no apologies. +_ProtectedBase = None + class TestActualPython(GuardTestCase): + + _old_mgr = _old_policy = _marker = [] + + def setUp(self): + pass + + def tearDown( self ): + self._restorePolicyAndManager() + + def _initPolicyAndManager(self, manager=None): + from AccessControl.SecurityManagement import get_ident + from AccessControl.SecurityManagement import _managers + from AccessControl.SecurityManagement import newSecurityManager + from AccessControl.SecurityManager import setSecurityPolicy + from AccessControl.ZopeSecurityPolicy import ZopeSecurityPolicy + + class UnderprivilegedUser: + """ Anonymous USer for unit testing purposes. + """ + def getId(self): + return 'Underprivileged User' + + getUserName = getId + + def allowed(self, object, object_roles=None): + return 0 + + def getRoles(self): + return () + + self._policy = ZopeSecurityPolicy() + self._old_policy = setSecurityPolicy(self._policy) + + if manager is None: + thread_id = get_ident() + self._old_mgr = manager=_managers.get(thread_id, self._marker) + newSecurityManager(None, UnderprivilegedUser()) + else: + self._old_mgr = self.setSecurityManager(manager) + + def _restorePolicyAndManager(self): + from AccessControl.SecurityManagement import noSecurityManager + from AccessControl.SecurityManager import setSecurityPolicy + + if self._old_mgr is not self._marker: + self.setSecurityManager(self._old_mgr) + else: + noSecurityManager() + + if self._old_policy is not self._marker: + setSecurityPolicy(self._old_policy) + + def _getProtectedBaseClass(self): + + from AccessControl.SecurityInfo import ClassSecurityInfo + from ExtensionClass import Base + from Globals import InitializeClass + + global _ProtectedBase + if _ProtectedBase is None: + + class ProtectedBase(Base): + security = ClassSecurityInfo() + + security.declarePrivate('private_method') + def private_method(self): + return 'private_method called' + + InitializeClass(ProtectedBase) + _ProtectedBase = ProtectedBase + + return _ProtectedBase + def testPython(self): from RestrictedPython.tests import verify @@ -404,6 +478,101 @@ untouched.sort() self.fail("Unexercised wrappers: %r" % untouched) + def testPythonRealAC(self): + code, its_globals = self._compile("actual_python.py") + exec code in its_globals + + def test_derived_class_normal(self): + from RestrictedPython.tests import verify + + NORMAL_SCRIPT = """ +class Normal(ProtectedBase): + pass + +normal = Normal() +print normal.private_method() +""" + code, its_globals = self._compile_str(NORMAL_SCRIPT, 'normal_script') + its_globals['ProtectedBase'] = self._getProtectedBaseClass() + verify.verify(code) + + self._initPolicyAndManager() + + try: + exec code in its_globals + except Unauthorized: + pass + else: + self.fail("Didn't raise Unauthorized: \n%s" % + its_globals['_print']()) + + def test_derived_class_sneaky_en_suite(self): + + # Disallow declaration of security-affecting names in classes + # defined in restricted code (compile-time check). + from RestrictedPython.tests import verify + + SNEAKY_SCRIPT = """ +class Sneaky(ProtectedBase): + private_method__roles__ = None + + +sneaky = Sneaky() +print sneaky.private_method() +""" + try: + code, its_globals = self._compile_str(SNEAKY_SCRIPT, + 'sneaky_script') + except SyntaxError: + pass + else: + self.fail("Didn't raise SyntaxError!") + + def test_derived_sneaky_post_facto(self): + + # Assignment to a class outside its suite fails at + # compile time with a SyntaxError. + from RestrictedPython.tests import verify + + SNEAKY_SCRIPT = """ +class Sneaky(ProtectedBase): + pass + +Sneaky.private_method__roles__ = None + +sneaky = Sneaky() +print sneaky.private_method() +""" + try: + code, its_globals = self._compile_str(SNEAKY_SCRIPT, 'sneaky_script') + except SyntaxError: + pass + else: + self.fail("Didn't raise SyntaxError!") + + def test_derived_sneaky_instance(self): + + # Assignment of security-sensitive names to an instance + # fails at compile time with a SyntaxError. + from RestrictedPython.tests import verify + + SNEAKY_SCRIPT = """ +class Sneaky(ProtectedBase): + pass + +sneaky = Sneaky() +sneaky.private_method__roles__ = None +print sneaky.private_method() +""" + try: + code, its_globals = self._compile_str(SNEAKY_SCRIPT, + 'sneaky_script') + except SyntaxError: + pass + else: + self.fail("Didn't raise SyntaxError!") + + def test_dict_access(self): from RestrictedPython.tests import verify Modified: Zope/trunk/lib/python/RestrictedPython/RestrictionMutator.py =================================================================== --- Zope/trunk/lib/python/RestrictedPython/RestrictionMutator.py 2005-04-05 20:40:42 UTC (rev 29882) +++ Zope/trunk/lib/python/RestrictedPython/RestrictionMutator.py 2005-04-05 20:58:31 UTC (rev 29883) @@ -94,6 +94,9 @@ # Note: "_" *is* allowed. self.error(node, '"%s" is an invalid variable name because' ' it starts with "_"' % name) + if name.endswith('__roles__'): + self.error(node, '"%s" is an invalid variable name because ' + 'it ends with "__roles__".' % name) if name == "printed": self.error(node, '"printed" is a reserved name.') @@ -109,6 +112,9 @@ # Note: "_" *is* allowed. self.error(node, '"%s" is an invalid attribute name ' 'because it starts with "_".' % name) + if name.endswith('__roles__'): + self.error(node, '"%s" is an invalid attribute name ' + 'because it ends with "__roles__".' % name) def prepBody(self, body): """Insert code for print at the beginning of the code suite.""" _______________________________________________ Zope-Checkins maillist - Zope-Checkins@zope.org http://mail.zope.org/mailman/listinfo/zope-checkins