[Zope-Checkins] SVN: Zope/trunk/ Forward-port fix for Collector
#1656 from 2.7 branch.
Tres Seaver
tseaver at zope.com
Tue Apr 5 16:40:42 EDT 2005
Log message for revision 29882:
Forward-port fix for Collector #1656 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/actual_python.py
U Zope/trunk/lib/python/AccessControl/tests/testZopeGuards.py
U Zope/trunk/lib/python/Products/PythonScripts/tests/testPythonScript.py
-=-
Modified: Zope/trunk/doc/CHANGES.txt
===================================================================
--- Zope/trunk/doc/CHANGES.txt 2005-04-05 13:48:49 UTC (rev 29881)
+++ Zope/trunk/doc/CHANGES.txt 2005-04-05 20:40:42 UTC (rev 29882)
@@ -31,6 +31,9 @@
Bugs fixed
+ - Collector #1656: Fixed enumeration within untrusted code
+ (forward-port from 2.7 branch).
+
- Collector #1721: Fixed handling of an empty indexed_attrs parameter
Modified: Zope/trunk/lib/python/AccessControl/ZopeGuards.py
===================================================================
--- Zope/trunk/lib/python/AccessControl/ZopeGuards.py 2005-04-05 13:48:49 UTC (rev 29881)
+++ Zope/trunk/lib/python/AccessControl/ZopeGuards.py 2005-04-05 20:40:42 UTC (rev 29882)
@@ -72,86 +72,44 @@
return v
raise Unauthorized, 'unauthorized access to element %s' % `i`
-if sys.version_info < (2, 2):
- # Can't use nested scopes, so we create callable instances
- class get_dict_get:
- def __init__(self, d, name):
- self.d = d
+# Create functions using nested scope to store state
+# This is less expensive then instantiating and calling instances
+def get_dict_get(d, name):
+ def guarded_get(key, default=None):
+ try:
+ return guarded_getitem(d, key)
+ except KeyError:
+ return default
+ return guarded_get
- def __call__(self, key, default=None):
- try:
- return guarded_getitem(self.d, key)
- except KeyError:
+def get_dict_pop(d, name):
+ def guarded_pop(key, default=_marker):
+ try:
+ v = guarded_getitem(d, key)
+ except KeyError:
+ if default is not _marker:
return default
-
- class get_dict_pop:
- def __init__(self, d, name):
- self.d = d
-
- def __call__(self, key, default=_marker):
- try:
- v = guarded_getitem(self.d, key)
- except KeyError:
- if default is not _marker:
- return default
- raise
- else:
- del self.d[key]
- return v
-
- # Dict methods not in Python 2.1
- get_iter = 0
-
- class get_list_pop:
- def __init__(self, lst, name):
- self.lst = lst
-
- def __call__(self, index=-1):
- # XXX This is not thread safe, but we don't expect
- # XXX thread interactions between python scripts <wink>
- v = guarded_getitem(self.lst, index)
- del self.lst[index]
+ raise
+ else:
+ del d[key]
return v
+ return guarded_pop
-else:
- # Python 2.2 or better: Create functions using nested scope to store state
- # This is less expensive then instantiating and calling instances
- def get_dict_get(d, name):
- def guarded_get(key, default=None):
- try:
- return guarded_getitem(d, key)
- except KeyError:
- return default
- return guarded_get
+def get_iter(c, name):
+ iter = getattr(c, name)
+ def guarded_iter():
+ return SafeIter(iter(), c)
+ return guarded_iter
- def get_dict_pop(d, name):
- def guarded_pop(key, default=_marker):
- try:
- v = guarded_getitem(d, key)
- except KeyError:
- if default is not _marker:
- return default
- raise
- else:
- del d[key]
- return v
- return guarded_pop
+def get_list_pop(lst, name):
+ def guarded_pop(index=-1):
+ # XXX This is not thread safe, but we don't expect
+ # XXX thread interactions between python scripts <wink>
+ v = guarded_getitem(lst, index)
+ del lst[index]
+ return v
+ return guarded_pop
- def get_iter(c, name):
- iter = getattr(c, name)
- def guarded_iter():
- return SafeIter(iter(), c)
- return guarded_iter
-
- def get_list_pop(lst, name):
- def guarded_pop(index=-1):
- # XXX This is not thread safe, but we don't expect
- # XXX thread interactions between python scripts <wink>
- v = guarded_getitem(lst, index)
- del lst[index]
- return v
- return guarded_pop
-
# See comment in SimpleObjectPolicies for an explanation of what the
# dicts below actually mean.
@@ -201,51 +159,51 @@
# machinery on subsequent calls). Use of a method on the SafeIter
# class is avoided to ensure the best performance of the resulting
# function.
+# The NullIter class skips the guard, and can be used to wrap an
+# iterator that is known to be safe (as in guarded_enumerate).
-if sys.version_info < (2, 2):
+class SafeIter(object):
+ #__slots__ = '_next', 'container'
+ __allow_access_to_unprotected_subobjects__ = 1
- class SafeIter:
- def __init__(self, sequence, container=None):
- if container is None:
- container = sequence
- self.container = container
- self.sequence = sequenece
- self.next_index = 0
+ def __init__(self, ob, container=None):
+ self._next = iter(ob).next
+ if container is None:
+ container = ob
+ self.container = container
- def __getitem__(self, index):
- ob = self.sequence[self.next_index]
- self.next_index += 1
- guard(self.container, ob, self.next_index - 1)
- return ob
+ def __iter__(self):
+ return self
- def _error(index):
- raise Unauthorized, 'unauthorized access to element %s' % `index`
+ def next(self):
+ ob = self._next()
+ guard(self.container, ob)
+ return ob
-else:
- class SafeIter(object):
- #__slots__ = '_next', 'container'
+class NullIter(SafeIter):
+ def __init__(self, ob):
+ self._next = ob.next
- def __init__(self, ob, container=None):
- self._next = iter(ob).next
- if container is None:
- container = ob
- self.container = container
+ def next(self):
+ return self._next()
- def __iter__(self):
- return self
+def _error(index):
+ raise Unauthorized, 'unauthorized access to element'
- def next(self):
- ob = self._next()
- guard(self.container, ob)
- return ob
+def guarded_iter(*args):
+ if len(args) == 1:
+ i = args[0]
+ # Don't double-wrap
+ if isinstance(i, SafeIter):
+ return i
+ if not isinstance(i, xrange):
+ return SafeIter(i)
+ # Other call styles / targets don't need to be guarded
+ return NullIter(iter(*args))
- def _error(index):
- raise Unauthorized, 'unauthorized access to element'
+safe_builtins['iter'] = guarded_iter
- safe_builtins['iter'] = SafeIter
-
-
def guard(container, value, index=None):
if Containers(type(container)) and Containers(type(value)):
# Simple type. Short circuit.
@@ -274,23 +232,23 @@
def guarded_reduce(f, seq, initial=_marker):
if initial is _marker:
- return reduce(f, SafeIter(seq))
+ return reduce(f, guarded_iter(seq))
else:
- return reduce(f, SafeIter(seq), initial)
+ return reduce(f, guarded_iter(seq), initial)
safe_builtins['reduce'] = guarded_reduce
def guarded_max(item, *items):
if items:
item = [item]
item.extend(items)
- return max(SafeIter(item))
+ return max(guarded_iter(item))
safe_builtins['max'] = guarded_max
def guarded_min(item, *items):
if items:
item = [item]
item.extend(items)
- return min(SafeIter(item))
+ return min(guarded_iter(item))
safe_builtins['min'] = guarded_min
def guarded_map(f, *seqs):
@@ -346,11 +304,11 @@
safe_builtins['dict'] = GuardedDictType()
def guarded_enumerate(seq):
- return enumerate(SafeIter(seq))
+ return NullIter(enumerate(guarded_iter(seq)))
safe_builtins['enumerate'] = guarded_enumerate
def guarded_sum(sequence, start=0):
- return sum(SafeIter(sequence), start)
+ return sum(guarded_iter(sequence), start)
safe_builtins['sum'] = guarded_sum
def load_module(module, mname, mnameparts, validate, globals, locals):
@@ -406,6 +364,13 @@
safe_builtins['apply'] = builtin_guarded_apply
+# 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):
+ ob = type(name, bases, dict)
+ ob.__allow_access_to_unprotected_subobjects__ = 1
+ ob._guarded_writes = 1
+ return ob
# AccessControl clients generally need to set up a safe globals dict for
# use by restricted code. The get_safe_globals() function returns such
@@ -420,9 +385,10 @@
# dict themselves, with key '_getattr_'.
_safe_globals = {'__builtins__': safe_builtins,
+ '__metaclass__': _metaclass,
'_apply_': guarded_apply,
'_getitem_': guarded_getitem,
- '_getiter_': SafeIter,
+ '_getiter_': guarded_iter,
'_print_': RestrictedPython.PrintCollector,
'_write_': full_write_guard,
# The correct implementation of _getattr_, aka
Modified: Zope/trunk/lib/python/AccessControl/tests/actual_python.py
===================================================================
--- Zope/trunk/lib/python/AccessControl/tests/actual_python.py 2005-04-05 13:48:49 UTC (rev 29881)
+++ Zope/trunk/lib/python/AccessControl/tests/actual_python.py 2005-04-05 20:40:42 UTC (rev 29882)
@@ -45,49 +45,47 @@
return str(self.value)
c1 = C()
c2 = C()
- # XXX Oops -- it's apparently against the rules to create a new
- # XXX attribute. Trying to yields
- # XXX TypeError: attribute-less object (assign or del)
- ## c1.value = 12
- ## assert getattr(c1, 'value') == 12
- ## assert c1.display() == '12'
+ c1.value = 12
+ assert getattr(c1, 'value') == 12
+ assert c1.display() == '12'
assert not hasattr(c2, 'value')
- ## setattr(c2, 'value', 34)
- ## assert c2.value == 34
- ## assert hasattr(c2, 'value')
- ## del c2.value
+ setattr(c2, 'value', 34)
+ assert c2.value == 34
+ assert hasattr(c2, 'value')
+ del c2.value
assert not hasattr(c2, 'value')
# OK, if we can't set new attributes, at least verify that we can't.
- try:
- c1.value = 12
- except TypeError:
- pass
- else:
- assert 0, "expected direct attribute creation to fail"
+ #try:
+ # c1.value = 12
+ #except TypeError:
+ # pass
+ #else:
+ # assert 0, "expected direct attribute creation to fail"
- try:
- setattr(c1, 'value', 12)
- except TypeError:
- pass
- else:
- assert 0, "expected indirect attribute creation to fail"
+ #try:
+ # setattr(c1, 'value', 12)
+ #except TypeError:
+ # pass
+ #else:
+ # assert 0, "expected indirect attribute creation to fail"
assert getattr(C, "display", None) == getattr(C, "display")
+ delattr(C, "display")
- try:
- setattr(C, "display", lambda self: "replaced")
- except TypeError:
- pass
- else:
- assert 0, "expected setattr() attribute replacement to fail"
+ #try:
+ # setattr(C, "display", lambda self: "replaced")
+ #except TypeError:
+ # pass
+ #else:
+ # assert 0, "expected setattr() attribute replacement to fail"
- try:
- delattr(C, "display")
- except TypeError:
- pass
- else:
- assert 0, "expected delattr() attribute deletion to fail"
+ #try:
+ # delattr(C, "display")
+ #except TypeError:
+ # pass
+ #else:
+ # assert 0, "expected delattr() attribute deletion to fail"
f6()
def f7():
@@ -155,3 +153,7 @@
assert same_type(3, 2, 1), 'expected same type'
assert not same_type(3, 2, 'a'), 'expected not same type'
f9()
+
+def f10():
+ assert iter(enumerate(iter(iter(range(9))))).next() == (0, 0)
+f10()
Modified: Zope/trunk/lib/python/AccessControl/tests/testZopeGuards.py
===================================================================
--- Zope/trunk/lib/python/AccessControl/tests/testZopeGuards.py 2005-04-05 13:48:49 UTC (rev 29881)
+++ Zope/trunk/lib/python/AccessControl/tests/testZopeGuards.py 2005-04-05 20:40:42 UTC (rev 29882)
@@ -442,7 +442,11 @@
g['__name__'] = __name__ # so classes can be defined in the script
return code, g
- # Compile code in fname, as restricted Python. Return the
+ def testPythonRealAC(self):
+ code, its_globals = self._compile("actual_python.py")
+ exec code in its_globals
+
+ # Compile code in fname, as restricted Python. Return the
# compiled code, and a safe globals dict for running it in.
# fname is the string name of a Python file; it must be found
# in the same directory as this file.
Modified: Zope/trunk/lib/python/Products/PythonScripts/tests/testPythonScript.py
===================================================================
--- Zope/trunk/lib/python/Products/PythonScripts/tests/testPythonScript.py 2005-04-05 13:48:49 UTC (rev 29881)
+++ Zope/trunk/lib/python/Products/PythonScripts/tests/testPythonScript.py 2005-04-05 20:40:42 UTC (rev 29882)
@@ -202,12 +202,12 @@
self.assertPSRaises(ImportError, body="import mmap")
def testAttributeAssignment(self):
- # It's illegal to assign to attributes of anything except
- # list or dict.
+ # It's illegal to assign to attributes of anything that
+ # doesn't has enabling security declared.
+ # Classes (and their instances) defined by restricted code
+ # are an exception -- they are fully readable and writable.
cases = [("import string", "string"),
- ("class Spam: pass", "Spam"),
("def f(): pass", "f"),
- ("class Spam: pass\nspam = Spam()", "spam"),
]
assigns = ["%s.splat = 'spam'",
"setattr(%s, '_getattr_', lambda x, y: True)",
@@ -236,7 +236,7 @@
def test__name__(self):
f = self._filePS('class.__name__')
- self.assertEqual(f(), ('?.foo', "'string'"))
+ self.assertEqual(f(), ("'foo'>", "'string'"))
def test_filepath(self):
# This test is meant to raise a deprecation warning.
More information about the Zope-Checkins
mailing list