Commit ac521c5d authored by Jim Fulton's avatar Jim Fulton

Added protection against the (small) risk that someone could mitate an

object through an augmented assignment (aka inplace) operator.
parent ceef435d
...@@ -375,6 +375,112 @@ def _metaclass(name, bases, dict): ...@@ -375,6 +375,112 @@ def _metaclass(name, bases, dict):
ob._guarded_writes = 1 ob._guarded_writes = 1
return ob return ob
try:
valid_inplace_types = list, set
except NameError:
# Python 2.3
valid_inplace_types = list
inplace_slots = {
'+=': '__iadd__',
'-=': '__isub__',
'*=': '__imul__',
'/=': (1/2 == 0) and '__idiv__' or '__itruediv__',
'//=': '__ifloordiv__',
'%=': '__imod__',
'**=': '__ipow__',
'<<=': '__ilshift__',
'>>=': '__irshift__',
'&=': '__iand__',
'^=': '__ixor__',
'|=': '__ior_',
}
def __iadd__(x, y):
x += y
return x
def __isub__(x, y):
x -= y
return x
def __imul__(x, y):
x *= y
return x
def __idiv__(x, y):
x /= y
return x
def __ifloordiv__(x, y):
x //= y
return x
def __imod__(x, y):
x %= y
return x
def __ipow__(x, y):
x **= y
return x
def __ilshift__(x, y):
x <<= y
return x
def __irshift__(x, y):
x >>= y
return x
def __iand__(x, y):
x &= y
return x
def __ixor__(x, y):
x ^= y
return x
def __ior__(x, y):
x |= y
return x
inplace_ops = {
'+=': __iadd__,
'-=': __isub__,
'*=': __imul__,
'/=': __idiv__,
'//=': __ifloordiv__,
'%=': __imod__,
'**=': __ipow__,
'<<=': __ilshift__,
'>>=': __irshift__,
'&=': __iand__,
'^=': __ixor__,
'|=': __ior__,
}
def protected_inplacevar(op, var, expr):
"""Do an inplace operation
If the var has an inplace slot, then disallow the operation
unless the var is a list.
"""
if (hasattr(var, inplace_slots[op])
and not isinstance(var, valid_inplace_types)
):
try:
cls = var.__class__
except AttributeError:
cls = type(var)
raise TypeError(
"Augmented assignment to %s objects is not allowed"
" in untrusted code" % cls.__name__
)
return inplace_ops[op](var, expr)
# 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
# a dict, containing '__builtins__' mapped to our safe bulitins, and # a dict, containing '__builtins__' mapped to our safe bulitins, and
...@@ -394,6 +500,7 @@ _safe_globals = {'__builtins__': safe_builtins, ...@@ -394,6 +500,7 @@ _safe_globals = {'__builtins__': safe_builtins,
'_getiter_': guarded_iter, '_getiter_': guarded_iter,
'_print_': RestrictedPython.PrintCollector, '_print_': RestrictedPython.PrintCollector,
'_write_': full_write_guard, '_write_': full_write_guard,
'_inplacevar_': protected_inplacevar,
# The correct implementation of _getattr_, aka # The correct implementation of _getattr_, aka
# guarded_getattr, isn't known until # guarded_getattr, isn't known until
# AccessControl.Implementation figures that out, then # AccessControl.Implementation figures that out, then
......
...@@ -157,3 +157,9 @@ f9() ...@@ -157,3 +157,9 @@ f9()
def f10(): def f10():
assert iter(enumerate(iter(iter(range(9))))).next() == (0, 0) assert iter(enumerate(iter(iter(range(9))))).next() == (0, 0)
f10() f10()
def f11():
x = 1
x += 1
f11()
...@@ -20,6 +20,7 @@ $Id$ ...@@ -20,6 +20,7 @@ $Id$
import os, sys import os, sys
import unittest import unittest
from zope.testing import doctest
import ZODB import ZODB
import AccessControl.SecurityManagement import AccessControl.SecurityManagement
from AccessControl.SimpleObjectPolicies import ContainerAssertions from AccessControl.SimpleObjectPolicies import ContainerAssertions
...@@ -671,8 +672,90 @@ print foo(**kw) ...@@ -671,8 +672,90 @@ print foo(**kw)
if callable(v) and v is not getattr(__builtin__, k, None): if callable(v) and v is not getattr(__builtin__, k, None):
d[k] = FuncWrapper(k, v) d[k] = FuncWrapper(k, v)
def test_inplacevar():
"""
Verify the correct behavior of protected_inplacevar.
>>> from AccessControl.ZopeGuards import protected_inplacevar
Basic operations on objects without inplace slots work as expected:
>>> protected_inplacevar('+=', 1, 2)
3
>>> protected_inplacevar('-=', 5, 2)
3
>>> protected_inplacevar('*=', 5, 2)
10
>>> protected_inplacevar('/=', 6, 2)
3
>>> protected_inplacevar('%=', 5, 2)
1
>>> protected_inplacevar('**=', 5, 2)
25
>>> protected_inplacevar('<<=', 5, 2)
20
>>> protected_inplacevar('>>=', 5, 2)
1
>>> protected_inplacevar('&=', 5, 2)
0
>>> protected_inplacevar('^=', 7, 2)
5
>>> protected_inplacevar('|=', 5, 2)
7
Inplace operations are allowed on lists:
>>> protected_inplacevar('+=', [1], [2])
[1, 2]
>>> protected_inplacevar('*=', [1], 2)
[1, 1]
But not on custom objects:
>>> class C:
... def __iadd__(self, other):
... return 42
>>> protected_inplacevar('+=', C(), 2) # doctest: +NORMALIZE_WHITESPACE
Traceback (most recent call last):
...
TypeError: Augmented assignment to C objects is not allowed in
untrusted code
"""
if sys.version_info[:2] >= (2, 4):
def test_inplacevar_for_py24():
"""
protected_inplacevar allows inplce ops on sets:
>>> from AccessControl.ZopeGuards import protected_inplacevar
>>> s = set((1,2,3,4))
>>> sorted(protected_inplacevar('-=', s, set((1, 3))))
[2, 4]
>>> sorted(s)
[2, 4]
>>> sorted(protected_inplacevar('|=', s, set((1, 3, 9))))
[1, 2, 3, 4, 9]
>>> sorted(s)
[1, 2, 3, 4, 9]
>>> sorted(protected_inplacevar('&=', s, set((1, 2, 3, 9))))
[1, 2, 3, 9]
>>> sorted(s)
[1, 2, 3, 9]
>>> sorted(protected_inplacevar('^=', s, set((1, 3, 7, 8))))
[2, 7, 8, 9]
>>> sorted(s)
[2, 7, 8, 9]
"""
def test_suite(): def test_suite():
suite = unittest.TestSuite() suite = unittest.TestSuite([
doctest.DocTestSuite(),
])
for cls in (TestGuardedGetattr, for cls in (TestGuardedGetattr,
TestGuardedGetitem, TestGuardedGetitem,
TestDictGuards, TestDictGuards,
......
...@@ -75,7 +75,7 @@ def nested_list_comprehension_before(): ...@@ -75,7 +75,7 @@ def nested_list_comprehension_before():
def nested_list_comprehension_after(): def nested_list_comprehension_after():
x = [x**2 + y**2 for x in _getiter_(whatever1) if x >= 0 x = [x**2 + y**2 for x in _getiter_(whatever1) if x >= 0
for y in _getiter_(whatever2) if y >= x] for y in _getiter_(whatever2) if y >= x]
# print # print
def simple_print_before(): def simple_print_before():
...@@ -244,3 +244,18 @@ def lambda_with_getattr_in_defaults_before(): ...@@ -244,3 +244,18 @@ def lambda_with_getattr_in_defaults_before():
def lambda_with_getattr_in_defaults_after(): def lambda_with_getattr_in_defaults_after():
f = lambda x=_getattr_(y, "z"): x f = lambda x=_getattr_(y, "z"): x
# augmented operators
# Note that we don't have to worry about item, attr, or slice assignment,
# as they are disallowed. Yay!
## def inplace_id_add_before():
## x += y+z
## def inplace_id_add_after():
## x = _inplacevar_('+=', x, y+z)
...@@ -40,6 +40,10 @@ def try_apply(): ...@@ -40,6 +40,10 @@ def try_apply():
print f(*(300, 20), **{'z': 1}), print f(*(300, 20), **{'z': 1}),
return printed return printed
def try_inplace():
x = 1
x += 3
def primes(): def primes():
# Somewhat obfuscated code on purpose # Somewhat obfuscated code on purpose
print filter(None,map(lambda y:y*reduce(lambda x,y:x*y!=0, print filter(None,map(lambda y:y*reduce(lambda x,y:x*y!=0,
......
...@@ -54,3 +54,16 @@ def except_using_bad_name(): ...@@ -54,3 +54,16 @@ def except_using_bad_name():
def keyword_arg_with_bad_name(): def keyword_arg_with_bad_name():
def f(okname=1, __badname=2): def f(okname=1, __badname=2):
pass pass
def no_augmeneted_assignment_to_sub():
a[b] += c
def no_augmeneted_assignment_to_attr():
a.b += c
def no_augmeneted_assignment_to_slice():
a[x:y] += c
def no_augmeneted_assignment_to_slice2():
a[x:y:z] += c
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