Skip to content

Commit

Permalink
Synced with Zope-2_7-branch.
Browse files Browse the repository at this point in the history
Removed support for Python 2.1, fixed yield test, and added a test for
bad names set by exception handlers.
  • Loading branch information
hathawsh committed Nov 6, 2003
1 parent 5315d80 commit e3260ca
Show file tree
Hide file tree
Showing 18 changed files with 151 additions and 5,183 deletions.
2 changes: 1 addition & 1 deletion Guards.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
##############################################################################
from __future__ import nested_scopes

__version__='$Revision: 1.11 $'[11:-2]
__version__='$Revision: 1.12 $'[11:-2]

import exceptions
import new
Expand Down
2 changes: 1 addition & 1 deletion RCompile.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
Python standard library.
"""

__version__='$Revision: 1.4 $'[11:-2]
__version__='$Revision: 1.5 $'[11:-2]


from compiler import ast, parse, misc, syntax
Expand Down
132 changes: 119 additions & 13 deletions RestrictionMutator.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
compiler.transformer.Transformer, restricting and enhancing the
code in various ways before sending it to pycodegen.
'''
__version__='$Revision: 1.11 $'[11:-2]
__version__='$Revision: 1.12 $'[11:-2]

from SelectCompiler import ast, parse, OP_ASSIGN, OP_DELETE, OP_APPLY

Expand All @@ -40,12 +40,11 @@ def exprNode(txt):
'''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_')
Expand Down Expand Up @@ -90,13 +89,29 @@ def __init__(self):
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))
else:
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'
Expand All @@ -105,17 +120,28 @@ def checkName(self, node, name):
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.
self.error(node, '"%s" is an invalid attribute name '
'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:
Expand All @@ -135,6 +161,12 @@ def prepBody(self, body):
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)
Expand All @@ -149,11 +181,30 @@ def visitFunction(self, node, walker):
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:
Expand All @@ -171,6 +222,10 @@ def visitPrint(self, node, walker):
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
Expand All @@ -180,10 +235,19 @@ def visitName(self, node, walker):
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):
Expand All @@ -195,15 +259,30 @@ def visitGetattr(self, node, walker):
#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...
Expand Down Expand Up @@ -245,6 +324,11 @@ def visitSubscript(self, node, walker):
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])
Expand All @@ -258,23 +342,45 @@ def visitYield(self, node, walker):
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

34 changes: 11 additions & 23 deletions SelectCompiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,31 +12,19 @@
##############################################################################
'''
Compiler selector.
$Id: SelectCompiler.py,v 1.4 2002/08/14 21:44:31 mj Exp $
$Id: SelectCompiler.py,v 1.5 2003/11/06 17:11:49 shane Exp $
'''

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
23 changes: 0 additions & 23 deletions compiler_2_1/__init__.py

This file was deleted.

Loading

0 comments on commit e3260ca

Please sign in to comment.