[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