Commit cfafb185 authored by Tres Seaver's avatar Tres Seaver

Forward-port fix for Collector #1656 from 2.7 branch.

parent da6c8a94
...@@ -31,6 +31,9 @@ Zope Changes ...@@ -31,6 +31,9 @@ Zope Changes
Bugs fixed 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 - Collector #1721: Fixed handling of an empty indexed_attrs parameter
......
...@@ -72,85 +72,43 @@ def guarded_getitem(object, index): ...@@ -72,85 +72,43 @@ def guarded_getitem(object, index):
return v return v
raise Unauthorized, 'unauthorized access to element %s' % `i` raise Unauthorized, 'unauthorized access to element %s' % `i`
if sys.version_info < (2, 2): # Create functions using nested scope to store state
# Can't use nested scopes, so we create callable instances # This is less expensive then instantiating and calling instances
class get_dict_get: def get_dict_get(d, name):
def __init__(self, d, name): def guarded_get(key, default=None):
self.d = d try:
return guarded_getitem(d, key)
def __call__(self, key, default=None): except KeyError:
try: return default
return guarded_getitem(self.d, key) return guarded_get
except KeyError:
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]
return v
else: def get_dict_pop(d, name):
# Python 2.2 or better: Create functions using nested scope to store state def guarded_pop(key, default=_marker):
# This is less expensive then instantiating and calling instances try:
def get_dict_get(d, name): v = guarded_getitem(d, key)
def guarded_get(key, default=None): except KeyError:
try: if default is not _marker:
return guarded_getitem(d, key)
except KeyError:
return default return default
return guarded_get raise
else:
def get_dict_pop(d, name): del d[key]
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_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 v
return guarded_pop 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 # See comment in SimpleObjectPolicies for an explanation of what the
# dicts below actually mean. # dicts below actually mean.
...@@ -201,50 +159,50 @@ ContainerAssertions[type([])] = _check_list_access ...@@ -201,50 +159,50 @@ ContainerAssertions[type([])] = _check_list_access
# machinery on subsequent calls). Use of a method on the SafeIter # machinery on subsequent calls). Use of a method on the SafeIter
# class is avoided to ensure the best performance of the resulting # class is avoided to ensure the best performance of the resulting
# function. # 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'
class SafeIter: __allow_access_to_unprotected_subobjects__ = 1
def __init__(self, sequence, container=None):
if container is None:
container = sequence
self.container = container
self.sequence = sequenece
self.next_index = 0
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 _error(index): def __init__(self, ob, container=None):
raise Unauthorized, 'unauthorized access to element %s' % `index` self._next = iter(ob).next
if container is None:
container = ob
self.container = container
else: def __iter__(self):
class SafeIter(object): return self
#__slots__ = '_next', 'container'
def __init__(self, ob, container=None): def next(self):
self._next = iter(ob).next ob = self._next()
if container is None: guard(self.container, ob)
container = ob return ob
self.container = container
def __iter__(self): class NullIter(SafeIter):
return self def __init__(self, ob):
self._next = ob.next
def next(self): def next(self):
ob = self._next() return self._next()
guard(self.container, ob)
return ob
def _error(index): def _error(index):
raise Unauthorized, 'unauthorized access to element' raise Unauthorized, 'unauthorized access to element'
safe_builtins['iter'] = SafeIter 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))
safe_builtins['iter'] = guarded_iter
def guard(container, value, index=None): def guard(container, value, index=None):
if Containers(type(container)) and Containers(type(value)): if Containers(type(container)) and Containers(type(value)):
...@@ -274,23 +232,23 @@ safe_builtins['filter'] = guarded_filter ...@@ -274,23 +232,23 @@ safe_builtins['filter'] = guarded_filter
def guarded_reduce(f, seq, initial=_marker): def guarded_reduce(f, seq, initial=_marker):
if initial is _marker: if initial is _marker:
return reduce(f, SafeIter(seq)) return reduce(f, guarded_iter(seq))
else: else:
return reduce(f, SafeIter(seq), initial) return reduce(f, guarded_iter(seq), initial)
safe_builtins['reduce'] = guarded_reduce safe_builtins['reduce'] = guarded_reduce
def guarded_max(item, *items): def guarded_max(item, *items):
if items: if items:
item = [item] item = [item]
item.extend(items) item.extend(items)
return max(SafeIter(item)) return max(guarded_iter(item))
safe_builtins['max'] = guarded_max safe_builtins['max'] = guarded_max
def guarded_min(item, *items): def guarded_min(item, *items):
if items: if items:
item = [item] item = [item]
item.extend(items) item.extend(items)
return min(SafeIter(item)) return min(guarded_iter(item))
safe_builtins['min'] = guarded_min safe_builtins['min'] = guarded_min
def guarded_map(f, *seqs): def guarded_map(f, *seqs):
...@@ -346,11 +304,11 @@ class GuardedDictType: ...@@ -346,11 +304,11 @@ class GuardedDictType:
safe_builtins['dict'] = GuardedDictType() safe_builtins['dict'] = GuardedDictType()
def guarded_enumerate(seq): def guarded_enumerate(seq):
return enumerate(SafeIter(seq)) return NullIter(enumerate(guarded_iter(seq)))
safe_builtins['enumerate'] = guarded_enumerate safe_builtins['enumerate'] = guarded_enumerate
def guarded_sum(sequence, start=0): def guarded_sum(sequence, start=0):
return sum(SafeIter(sequence), start) return sum(guarded_iter(sequence), start)
safe_builtins['sum'] = guarded_sum safe_builtins['sum'] = guarded_sum
def load_module(module, mname, mnameparts, validate, globals, locals): def load_module(module, mname, mnameparts, validate, globals, locals):
...@@ -406,6 +364,13 @@ def builtin_guarded_apply(func, args=(), kws={}): ...@@ -406,6 +364,13 @@ def builtin_guarded_apply(func, args=(), kws={}):
safe_builtins['apply'] = builtin_guarded_apply 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 # AccessControl clients generally need to set up a safe globals dict for
# use by restricted code. The get_safe_globals() function returns such # use by restricted code. The get_safe_globals() function returns such
...@@ -420,9 +385,10 @@ safe_builtins['apply'] = builtin_guarded_apply ...@@ -420,9 +385,10 @@ safe_builtins['apply'] = builtin_guarded_apply
# dict themselves, with key '_getattr_'. # dict themselves, with key '_getattr_'.
_safe_globals = {'__builtins__': safe_builtins, _safe_globals = {'__builtins__': safe_builtins,
'__metaclass__': _metaclass,
'_apply_': guarded_apply, '_apply_': guarded_apply,
'_getitem_': guarded_getitem, '_getitem_': guarded_getitem,
'_getiter_': SafeIter, '_getiter_': guarded_iter,
'_print_': RestrictedPython.PrintCollector, '_print_': RestrictedPython.PrintCollector,
'_write_': full_write_guard, '_write_': full_write_guard,
# The correct implementation of _getattr_, aka # The correct implementation of _getattr_, aka
......
...@@ -45,49 +45,47 @@ def f6(): ...@@ -45,49 +45,47 @@ def f6():
return str(self.value) return str(self.value)
c1 = C() c1 = C()
c2 = C() c2 = C()
# XXX Oops -- it's apparently against the rules to create a new c1.value = 12
# XXX attribute. Trying to yields assert getattr(c1, 'value') == 12
# XXX TypeError: attribute-less object (assign or del) assert c1.display() == '12'
## c1.value = 12
## assert getattr(c1, 'value') == 12
## assert c1.display() == '12'
assert not hasattr(c2, 'value') assert not hasattr(c2, 'value')
## setattr(c2, 'value', 34) setattr(c2, 'value', 34)
## assert c2.value == 34 assert c2.value == 34
## assert hasattr(c2, 'value') assert hasattr(c2, 'value')
## del c2.value del c2.value
assert not hasattr(c2, 'value') assert not hasattr(c2, 'value')
# OK, if we can't set new attributes, at least verify that we can't. # OK, if we can't set new attributes, at least verify that we can't.
try: #try:
c1.value = 12 # c1.value = 12
except TypeError: #except TypeError:
pass # pass
else: #else:
assert 0, "expected direct attribute creation to fail" # assert 0, "expected direct attribute creation to fail"
try: #try:
setattr(c1, 'value', 12) # setattr(c1, 'value', 12)
except TypeError: #except TypeError:
pass # pass
else: #else:
assert 0, "expected indirect attribute creation to fail" # assert 0, "expected indirect attribute creation to fail"
assert getattr(C, "display", None) == getattr(C, "display") assert getattr(C, "display", None) == getattr(C, "display")
delattr(C, "display")
try:
setattr(C, "display", lambda self: "replaced") #try:
except TypeError: # setattr(C, "display", lambda self: "replaced")
pass #except TypeError:
else: # pass
assert 0, "expected setattr() attribute replacement to fail" #else:
# assert 0, "expected setattr() attribute replacement to fail"
try:
delattr(C, "display") #try:
except TypeError: # delattr(C, "display")
pass #except TypeError:
else: # pass
assert 0, "expected delattr() attribute deletion to fail" #else:
# assert 0, "expected delattr() attribute deletion to fail"
f6() f6()
def f7(): def f7():
...@@ -155,3 +153,7 @@ def f9(): ...@@ -155,3 +153,7 @@ def f9():
assert same_type(3, 2, 1), 'expected same type' assert same_type(3, 2, 1), 'expected same type'
assert not same_type(3, 2, 'a'), 'expected not same type' assert not same_type(3, 2, 'a'), 'expected not same type'
f9() f9()
def f10():
assert iter(enumerate(iter(iter(range(9))))).next() == (0, 0)
f10()
...@@ -442,7 +442,11 @@ print foo(**kw) ...@@ -442,7 +442,11 @@ print foo(**kw)
g['__name__'] = __name__ # so classes can be defined in the script g['__name__'] = __name__ # so classes can be defined in the script
return code, g 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. # compiled code, and a safe globals dict for running it in.
# fname is the string name of a Python file; it must be found # fname is the string name of a Python file; it must be found
# in the same directory as this file. # in the same directory as this file.
......
...@@ -202,12 +202,12 @@ class TestPythonScriptErrors(PythonScriptTestBase): ...@@ -202,12 +202,12 @@ class TestPythonScriptErrors(PythonScriptTestBase):
self.assertPSRaises(ImportError, body="import mmap") self.assertPSRaises(ImportError, body="import mmap")
def testAttributeAssignment(self): def testAttributeAssignment(self):
# It's illegal to assign to attributes of anything except # It's illegal to assign to attributes of anything that
# list or dict. # 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"), cases = [("import string", "string"),
("class Spam: pass", "Spam"),
("def f(): pass", "f"), ("def f(): pass", "f"),
("class Spam: pass\nspam = Spam()", "spam"),
] ]
assigns = ["%s.splat = 'spam'", assigns = ["%s.splat = 'spam'",
"setattr(%s, '_getattr_', lambda x, y: True)", "setattr(%s, '_getattr_', lambda x, y: True)",
...@@ -236,7 +236,7 @@ class TestPythonScriptGlobals(PythonScriptTestBase): ...@@ -236,7 +236,7 @@ class TestPythonScriptGlobals(PythonScriptTestBase):
def test__name__(self): def test__name__(self):
f = self._filePS('class.__name__') f = self._filePS('class.__name__')
self.assertEqual(f(), ('?.foo', "'string'")) self.assertEqual(f(), ("'foo'>", "'string'"))
def test_filepath(self): def test_filepath(self):
# This test is meant to raise a deprecation warning. # This test is meant to raise a deprecation warning.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment