[Zope-Checkins] CVS: Zope/lib/python/RestrictedPython -
Guards.py:1.12 RCompile.py:1.5 RestrictionMutator.py:1.12
SelectCompiler.py:1.5
Shane Hathaway
cvs-admin at zope.org
Thu Nov 6 12:12:20 EST 2003
Update of /cvs-repository/Zope/lib/python/RestrictedPython
In directory cvs.zope.org:/tmp/cvs-serv19542
Modified Files:
Guards.py RCompile.py RestrictionMutator.py SelectCompiler.py
Log Message:
Synced with Zope-2_7-branch.
Removed support for Python 2.1, fixed yield test, and added a test for
bad names set by exception handlers.
=== Zope/lib/python/RestrictedPython/Guards.py 1.11 => 1.12 ===
=== Zope/lib/python/RestrictedPython/RCompile.py 1.4 => 1.5 ===
=== Zope/lib/python/RestrictedPython/RestrictionMutator.py 1.11 => 1.12 ===
--- Zope/lib/python/RestrictedPython/RestrictionMutator.py:1.11 Wed Nov 5 19:37:26 2003
+++ Zope/lib/python/RestrictedPython/RestrictionMutator.py Thu Nov 6 12:11:49 2003
@@ -40,12 +40,11 @@
'''Make a "clean" expression node'''
return stmtNode(txt).expr
-# There should be up to four objects in the global namespace.
-# If a wrapper function or print target is needed in a particular
-# module or function, it is obtained from one of these objects.
-# It is stored in a variable with the same name as the global
-# object, but without a single trailing underscore. This variable is
-# local, and therefore efficient to access, in function scopes.
+# There should be up to four objects in the global namespace. If a
+# wrapper function or print target is needed in a particular module or
+# function, it is obtained from one of these objects. There is a
+# local and a global binding for each object: the global name has a
+# trailing underscore, while the local name does not.
_print_target_name = ast.Name('_print')
_getattr_name = ast.Name('_getattr')
_getattr_name_expr = ast.Name('_getattr_')
@@ -90,6 +89,8 @@
self.used_names = {}
def error(self, node, info):
+ """Records a security error discovered during compilation.
+ """
lineno = getattr(node, 'lineno', None)
if lineno is not None and lineno > 0:
self.errors.append('Line %d: %s' % (lineno, info))
@@ -97,6 +98,20 @@
self.errors.append(info)
def checkName(self, node, name):
+ """Verifies that a name being assigned is safe.
+
+ This is to prevent people from doing things like:
+
+ __metatype__ = mytype (opens up metaclasses, a big unknown
+ in terms of security)
+ __path__ = foo (could this confuse the import machinery?)
+ _getattr = somefunc (not very useful, but could open a hole)
+
+ Note that assigning a variable is not the only way to assign
+ a name. def _badname, class _badname, import foo as _badname,
+ and perhaps other statements assign names. Special case:
+ '_' is allowed.
+ """
if len(name) > 1 and name[0] == '_':
# Note: "_" *is* allowed.
self.error(node, '"%s" is an invalid variable name because'
@@ -105,9 +120,12 @@
self.error(node, '"printed" is a reserved name.')
def checkAttrName(self, node):
- # This prevents access to protected attributes of guards
- # and is thus essential regardless of the security policy,
- # unless some other solution is devised.
+ """Verifies that an attribute name does not start with _.
+
+ As long as guards (security proxies) have underscored names,
+ this underscore protection is important regardless of the
+ security policy. Special case: '_' is allowed.
+ """
name = node.attrname
if len(name) > 1 and name[0] == '_':
# Note: "_" *is* allowed.
@@ -115,7 +133,15 @@
'because it starts with "_".' % name)
def prepBody(self, body):
- """Appends prep code to the beginning of a code suite.
+ """Prepends preparation code to a code suite.
+
+ For example, if a code suite uses getattr operations,
+ this places the following code at the beginning of the suite:
+
+ global _getattr_
+ _getattr = _getattr_
+
+ Similarly for _getitem_, _print_, and _write_.
"""
info = self.funcinfo
if info._print_used or info._printed_used:
@@ -135,6 +161,12 @@
body[0:0] = _prep_code['write']
def visitFunction(self, node, walker):
+ """Checks and mutates a function definition.
+
+ Checks the name of the function and the argument names using
+ checkName(). It also calls prepBody() to prepend code to the
+ beginning of the code suite.
+ """
self.checkName(node, node.name)
for argname in node.argnames:
self.checkName(node, argname)
@@ -149,11 +181,30 @@
return node
def visitLambda(self, node, walker):
+ """Checks and mutates an anonymous function definition.
+
+ Checks the argument names using checkName(). It also calls
+ prepBody() to prepend code to the beginning of the code suite.
+ """
for argname in node.argnames:
self.checkName(node, argname)
return walker.defaultVisitNode(node)
def visitPrint(self, node, walker):
+ """Checks and mutates a print statement.
+
+ Adds a target to all print statements. 'print foo' becomes
+ 'print >> _print, foo', where _print is the default print
+ target defined for this scope.
+
+ Alternatively, if the untrusted code provides its own target,
+ we have to check the 'write' method of the target.
+ 'print >> ob, foo' becomes
+ 'print >> (_getattr(ob, 'write') and ob), foo'.
+ Otherwise, it would be possible to call the write method of
+ templates and scripts; 'write' happens to be the name of the
+ method that changes them.
+ """
node = walker.defaultVisitNode(node)
self.funcinfo._print_used = 1
if node.dest is None:
@@ -171,6 +222,10 @@
visitPrintnl = visitPrint
def visitName(self, node, walker):
+ """Prevents access to protected names as defined by checkName().
+
+ Also converts use of the name 'printed' to an expression.
+ """
if node.name == 'printed':
# Replace name lookup with an expression.
self.funcinfo._printed_used = 1
@@ -180,10 +235,19 @@
return node
def visitAssName(self, node, walker):
+ """Checks a name assignment using checkName().
+ """
self.checkName(node, node.name)
return node
def visitGetattr(self, node, walker):
+ """Converts attribute access to a function call.
+
+ 'foo.bar' becomes '_getattr(foo, "bar")'.
+
+ Also prevents augmented assignment of attributes, which would
+ be difficult to support correctly.
+ """
self.checkAttrName(node)
node = walker.defaultVisitNode(node)
if getattr(node, 'in_aug_assign', 0):
@@ -195,15 +259,30 @@
#self.funcinfo._write_used = 1
self.funcinfo._getattr_used = 1
if self.funcinfo._is_suite:
+ # Use the local function _getattr().
ga = _getattr_name
else:
+ # Use the global function _getattr_().
ga = _getattr_name_expr
return ast.CallFunc(ga, [node.expr, ast.Const(node.attrname)])
def visitSubscript(self, node, walker):
+ """Checks all kinds of subscripts.
+
+ 'foo[bar] += baz' is disallowed.
+ 'a = foo[bar, baz]' becomes 'a = _getitem(foo, (bar, baz))'.
+ 'a = foo[bar]' becomes 'a = _getitem(foo, bar)'.
+ 'a = foo[bar:baz]' becomes 'a = _getitem(foo, slice(bar, baz))'.
+ 'a = foo[:baz]' becomes 'a = _getitem(foo, slice(None, baz))'.
+ 'a = foo[bar:]' becomes 'a = _getitem(foo, slice(bar, None))'.
+ 'del foo[bar]' becomes 'del _write(foo)[bar]'.
+ 'foo[bar] = a' becomes '_write(foo)[bar] = a'.
+
+ The _write function returns a security proxy.
+ """
node = walker.defaultVisitNode(node)
if node.flags == OP_APPLY:
- # get subscript or slice
+ # Set 'subs' to the node that represents the subscript or slice.
if getattr(node, 'in_aug_assign', 0):
# We're in an augmented assignment
# We might support this later...
@@ -245,6 +324,11 @@
visitSlice = visitSubscript
def visitAssAttr(self, node, walker):
+ """Checks and mutates attribute assignment.
+
+ 'a.b = c' becomes '_write(a).b = c'.
+ The _write function returns a security proxy.
+ """
self.checkAttrName(node)
node = walker.defaultVisitNode(node)
node.expr = ast.CallFunc(_write_guard_name, [node.expr])
@@ -258,23 +342,45 @@
self.error(node, 'Yield statements are not allowed.')
def visitClass(self, node, walker):
- # Should classes be allowed at all??
+ """Checks the name of a class using checkName().
+
+ Should classes be allowed at all? They don't cause security
+ issues, but they aren't very useful either since untrusted
+ code can't assign instance attributes.
+ """
self.checkName(node, node.name)
return walker.defaultVisitNode(node)
def visitModule(self, node, walker):
+ """Adds prep code at module scope.
+
+ Zope doesn't make use of this. The body of Python scripts is
+ always at function scope.
+ """
self.funcinfo._is_suite = 1
node = walker.defaultVisitNode(node)
self.prepBody(node.node.nodes)
return node
def visitAugAssign(self, node, walker):
+ """Makes a note that augmented assignment is in use.
+
+ Note that although augmented assignment of attributes and
+ subscripts is disallowed, augmented assignment of names (such
+ as 'n += 1') is allowed.
+
+ This could be a problem if untrusted code got access to a
+ mutable database object that supports augmented assignment.
+ """
node.node.in_aug_assign = 1
return walker.defaultVisitNode(node)
def visitImport(self, node, walker):
+ """Checks names imported using checkName().
+ """
for name, asname in node.names:
self.checkName(node, name)
if asname:
self.checkName(node, asname)
return node
+
=== Zope/lib/python/RestrictedPython/SelectCompiler.py 1.4 => 1.5 ===
--- Zope/lib/python/RestrictedPython/SelectCompiler.py:1.4 Wed Aug 14 17:44:31 2002
+++ Zope/lib/python/RestrictedPython/SelectCompiler.py Thu Nov 6 12:11:49 2003
@@ -17,26 +17,14 @@
import sys
-if sys.version_info[1] < 2:
- # Use the compiler_2_1 package.
- from compiler_2_1 import ast
- from compiler_2_1.transformer import parse
- from compiler_2_1.consts import OP_ASSIGN, OP_DELETE, OP_APPLY
+# Use the compiler from the standard library.
+import compiler
+from compiler import ast
+from compiler.transformer import parse
+from compiler.consts import OP_ASSIGN, OP_DELETE, OP_APPLY
- from RCompile_2_1 import \
- compile_restricted, \
- compile_restricted_function, \
- compile_restricted_exec, \
- compile_restricted_eval
-else:
- # Use the compiler from the standard library.
- import compiler
- from compiler import ast
- from compiler.transformer import parse
- from compiler.consts import OP_ASSIGN, OP_DELETE, OP_APPLY
-
- from RCompile import \
- compile_restricted, \
- compile_restricted_function, \
- compile_restricted_exec, \
- compile_restricted_eval
+from RCompile import \
+ compile_restricted, \
+ compile_restricted_function, \
+ compile_restricted_exec, \
+ compile_restricted_eval
More information about the Zope-Checkins
mailing list