diff options
author | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2010-01-20 01:29:50 +0000 |
---|---|---|
committer | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2010-01-20 01:29:50 +0000 |
commit | 8362bf63dea22bbf6736609b0f49c152f975eb63 (patch) | |
tree | 0eea3928e39e50fae91d4e68b21b1e6cbae25604 /lib/kross/python/scripts/RestrictedPython | |
download | koffice-8362bf63dea22bbf6736609b0f49c152f975eb63.tar.gz koffice-8362bf63dea22bbf6736609b0f49c152f975eb63.zip |
Added old abandoned KDE3 version of koffice
git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/applications/koffice@1077364 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'lib/kross/python/scripts/RestrictedPython')
11 files changed, 1132 insertions, 0 deletions
diff --git a/lib/kross/python/scripts/RestrictedPython/Eval.py b/lib/kross/python/scripts/RestrictedPython/Eval.py new file mode 100644 index 00000000..841067a1 --- /dev/null +++ b/lib/kross/python/scripts/RestrictedPython/Eval.py @@ -0,0 +1,118 @@ +############################################################################## +# +# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE +# +############################################################################## +"""Restricted Python Expressions + +$Id: Eval.py 24763 2004-05-17 05:59:28Z philikon $ +""" + +__version__='$Revision: 1.6 $'[11:-2] + +from RestrictedPython import compile_restricted_eval + +from string import translate, strip +import string + +nltosp = string.maketrans('\r\n',' ') + +default_guarded_getattr = getattr # No restrictions. + +def default_guarded_getitem(ob, index): + # No restrictions. + return ob[index] + +PROFILE = 0 + +class RestrictionCapableEval: + """A base class for restricted code.""" + + globals = {'__builtins__': None} + rcode = None # restricted + ucode = None # unrestricted + used = None + + def __init__(self, expr): + """Create a restricted expression + + where: + + expr -- a string containing the expression to be evaluated. + """ + expr = strip(expr) + self.__name__ = expr + expr = translate(expr, nltosp) + self.expr = expr + self.prepUnrestrictedCode() # Catch syntax errors. + + def prepRestrictedCode(self): + if self.rcode is None: + if PROFILE: + from time import clock + start = clock() + co, err, warn, used = compile_restricted_eval( + self.expr, '<string>') + if PROFILE: + end = clock() + print 'prepRestrictedCode: %d ms for %s' % ( + (end - start) * 1000, `self.expr`) + if err: + raise SyntaxError, err[0] + self.used = tuple(used.keys()) + self.rcode = co + + def prepUnrestrictedCode(self): + if self.ucode is None: + # Use the standard compiler. + co = compile(self.expr, '<string>', 'eval') + if self.used is None: + # Examine the code object, discovering which names + # the expression needs. + names=list(co.co_names) + used={} + i=0 + code=co.co_code + l=len(code) + LOAD_NAME=101 + HAVE_ARGUMENT=90 + while(i < l): + c=ord(code[i]) + if c==LOAD_NAME: + name=names[ord(code[i+1])+256*ord(code[i+2])] + used[name]=1 + i=i+3 + elif c >= HAVE_ARGUMENT: i=i+3 + else: i=i+1 + self.used=tuple(used.keys()) + self.ucode=co + + def eval(self, mapping): + # This default implementation is probably not very useful. :-( + # This is meant to be overridden. + self.prepRestrictedCode() + code = self.rcode + d = {'_getattr_': default_guarded_getattr, + '_getitem_': default_guarded_getitem} + d.update(self.globals) + has_key = d.has_key + for name in self.used: + try: + if not has_key(name): + d[name] = mapping[name] + except KeyError: + # Swallow KeyErrors since the expression + # might not actually need the name. If it + # does need the name, a NameError will occur. + pass + return eval(code, d) + + def __call__(self, **kw): + return self.eval(kw) diff --git a/lib/kross/python/scripts/RestrictedPython/Guards.py b/lib/kross/python/scripts/RestrictedPython/Guards.py new file mode 100644 index 00000000..4fbdcad1 --- /dev/null +++ b/lib/kross/python/scripts/RestrictedPython/Guards.py @@ -0,0 +1,136 @@ +############################################################################## +# +# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE +# +############################################################################## +__version__ = '$Revision: 1.14 $'[11:-2] + +import exceptions + +# This tiny set of safe builtins is extended by users of the module. +# AccessControl.ZopeGuards contains a large set of wrappers for builtins. +# DocumentTemplate.DT_UTil contains a few. + +safe_builtins = {} + +for name in ['False', 'None', 'True', 'abs', 'basestring', 'bool', 'callable', + 'chr', 'cmp', 'complex', 'divmod', 'float', 'hash', + 'hex', 'id', 'int', 'isinstance', 'issubclass', 'len', + 'long', 'oct', 'ord', 'pow', 'range', 'repr', 'round', + 'str', 'tuple', 'unichr', 'unicode', 'xrange', 'zip']: + + safe_builtins[name] = __builtins__[name] + +# Wrappers provided by this module: +# delattr +# setattr + +# Wrappers provided by ZopeGuards: +# __import__ +# apply +# dict +# enumerate +# filter +# getattr +# hasattr +# iter +# list +# map +# max +# min +# sum + +# Builtins that are intentionally disabled +# compile - don't let them produce new code +# dir - a general purpose introspector, probably hard to wrap +# execfile - no direct I/O +# file - no direct I/O +# globals - uncontrolled namespace access +# input - no direct I/O +# locals - uncontrolled namespace access +# open - no direct I/O +# raw_input - no direct I/O +# vars - uncontrolled namespace access + +# There are several strings that describe Python. I think there's no +# point to including these, although they are obviously safe: +# copyright, credits, exit, help, license, quit + +# Not provided anywhere. Do something about these? Several are +# related to new-style classes, which we are too scared of to support +# <0.3 wink>. coerce, buffer, and reload are esoteric enough that no +# one should care. + +# buffer +# classmethod +# coerce +# eval +# intern +# object +# property +# reload +# slice +# staticmethod +# super +# type + +for name in dir(exceptions): + if name[0] != "_": + safe_builtins[name] = getattr(exceptions, name) + +def _write_wrapper(): + # Construct the write wrapper class + def _handler(secattr, error_msg): + # Make a class method. + def handler(self, *args): + try: + f = getattr(self.ob, secattr) + except AttributeError: + raise TypeError, error_msg + f(*args) + return handler + class Wrapper: + def __len__(self): + # Required for slices with negative bounds. + return len(self.ob) + def __init__(self, ob): + self.__dict__['ob'] = ob + __setitem__ = _handler('__guarded_setitem__', + 'object does not support item or slice assignment') + __delitem__ = _handler('__guarded_delitem__', + 'object does not support item or slice assignment') + __setattr__ = _handler('__guarded_setattr__', + 'attribute-less object (assign or del)') + __delattr__ = _handler('__guarded_delattr__', + 'attribute-less object (assign or del)') + return Wrapper + +def _full_write_guard(): + # Nested scope abuse! + # safetype and Wrapper variables are used by guard() + safetype = {dict: True, list: True}.has_key + Wrapper = _write_wrapper() + def guard(ob): + # Don't bother wrapping simple types, or objects that claim to + # handle their own write security. + if safetype(type(ob)) or hasattr(ob, '_guarded_writes'): + return ob + # Hand the object to the Wrapper instance, then return the instance. + return Wrapper(ob) + return guard +full_write_guard = _full_write_guard() + +def guarded_setattr(object, name, value): + setattr(full_write_guard(object), name, value) +safe_builtins['setattr'] = guarded_setattr + +def guarded_delattr(object, name): + delattr(full_write_guard(object), name) +safe_builtins['delattr'] = guarded_delattr diff --git a/lib/kross/python/scripts/RestrictedPython/Limits.py b/lib/kross/python/scripts/RestrictedPython/Limits.py new file mode 100644 index 00000000..3b782e65 --- /dev/null +++ b/lib/kross/python/scripts/RestrictedPython/Limits.py @@ -0,0 +1,46 @@ +############################################################################## +# +# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE +# +############################################################################## + +__version__='$Revision: 1.5 $'[11:-2] + +limited_builtins = {} + +def limited_range(iFirst, *args): + # limited range function from Martijn Pieters + RANGELIMIT = 1000 + if not len(args): + iStart, iEnd, iStep = 0, iFirst, 1 + elif len(args) == 1: + iStart, iEnd, iStep = iFirst, args[0], 1 + elif len(args) == 2: + iStart, iEnd, iStep = iFirst, args[0], args[1] + else: + raise AttributeError, 'range() requires 1-3 int arguments' + if iStep == 0: raise ValueError, 'zero step for range()' + iLen = int((iEnd - iStart) / iStep) + if iLen < 0: iLen = 0 + if iLen >= RANGELIMIT: raise ValueError, 'range() too large' + return range(iStart, iEnd, iStep) +limited_builtins['range'] = limited_range + +def limited_list(seq): + if isinstance(seq, str): + raise TypeError, 'cannot convert string to list' + return list(seq) +limited_builtins['list'] = limited_list + +def limited_tuple(seq): + if isinstance(seq, str): + raise TypeError, 'cannot convert string to tuple' + return tuple(seq) +limited_builtins['tuple'] = limited_tuple diff --git a/lib/kross/python/scripts/RestrictedPython/Makefile.am b/lib/kross/python/scripts/RestrictedPython/Makefile.am new file mode 100644 index 00000000..3d5b3c73 --- /dev/null +++ b/lib/kross/python/scripts/RestrictedPython/Makefile.am @@ -0,0 +1,4 @@ +restrictedpythondir = $(kde_datadir)/kross/python/RestrictedPython +restrictedpython_SCRIPTS = __init__.py Eval.py Guards.py Limits.py \ + MutatingWalker.py PrintCollector.py RCompile.py RestrictionMutator.py \ + SelectCompiler.py Utilities.py diff --git a/lib/kross/python/scripts/RestrictedPython/MutatingWalker.py b/lib/kross/python/scripts/RestrictedPython/MutatingWalker.py new file mode 100644 index 00000000..b0b8c9ce --- /dev/null +++ b/lib/kross/python/scripts/RestrictedPython/MutatingWalker.py @@ -0,0 +1,74 @@ +############################################################################## +# +# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE +# +############################################################################## + +__version__='$Revision: 1.6 $'[11:-2] + +from SelectCompiler import ast + +ListType = type([]) +TupleType = type(()) +SequenceTypes = (ListType, TupleType) + +class MutatingWalker: + + def __init__(self, visitor): + self.visitor = visitor + self._cache = {} + + def defaultVisitNode(self, node, walker=None, exclude=None): + for name, child in node.__dict__.items(): + if exclude is not None and name in exclude: + continue + v = self.dispatchObject(child) + if v is not child: + # Replace the node. + node.__dict__[name] = v + return node + + def visitSequence(self, seq): + res = seq + for idx in range(len(seq)): + child = seq[idx] + v = self.dispatchObject(child) + if v is not child: + # Change the sequence. + if type(res) is ListType: + res[idx : idx + 1] = [v] + else: + res = res[:idx] + (v,) + res[idx + 1:] + return res + + def dispatchObject(self, ob): + ''' + Expected to return either ob or something that will take + its place. + ''' + if isinstance(ob, ast.Node): + return self.dispatchNode(ob) + elif type(ob) in SequenceTypes: + return self.visitSequence(ob) + else: + return ob + + def dispatchNode(self, node): + klass = node.__class__ + meth = self._cache.get(klass, None) + if meth is None: + className = klass.__name__ + meth = getattr(self.visitor, 'visit' + className, + self.defaultVisitNode) + self._cache[klass] = meth + return meth(node, self) + +def walk(tree, visitor): + return MutatingWalker(visitor).dispatchNode(tree) diff --git a/lib/kross/python/scripts/RestrictedPython/PrintCollector.py b/lib/kross/python/scripts/RestrictedPython/PrintCollector.py new file mode 100644 index 00000000..15a1c180 --- /dev/null +++ b/lib/kross/python/scripts/RestrictedPython/PrintCollector.py @@ -0,0 +1,23 @@ +############################################################################## +# +# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE +# +############################################################################## + +__version__='$Revision: 1.4 $'[11:-2] + +class PrintCollector: + '''Collect written text, and return it when called.''' + def __init__(self): + self.txt = [] + def write(self, text): + self.txt.append(text) + def __call__(self): + return ''.join(self.txt) diff --git a/lib/kross/python/scripts/RestrictedPython/RCompile.py b/lib/kross/python/scripts/RestrictedPython/RCompile.py new file mode 100644 index 00000000..0a538657 --- /dev/null +++ b/lib/kross/python/scripts/RestrictedPython/RCompile.py @@ -0,0 +1,235 @@ +############################################################################## +# +# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE +# +############################################################################## +"""Compiles restricted code using the compiler module from the +Python standard library. +""" + +__version__='$Revision: 1.6 $'[11:-2] + +from compiler import ast, parse, misc, syntax, pycodegen +from compiler.pycodegen import AbstractCompileMode, Expression, \ + Interactive, Module, ModuleCodeGenerator, FunctionCodeGenerator, findOp + +import MutatingWalker +from RestrictionMutator import RestrictionMutator + + +def niceParse(source, filename, mode): + try: + return parse(source, mode) + except: + # Try to make a clean error message using + # the builtin Python compiler. + try: + compile(source, filename, mode) + except SyntaxError: + raise + # Some other error occurred. + raise + +class RestrictedCompileMode(AbstractCompileMode): + """Abstract base class for hooking up custom CodeGenerator.""" + # See concrete subclasses below. + + def __init__(self, source, filename): + self.rm = RestrictionMutator() + AbstractCompileMode.__init__(self, source, filename) + + def parse(self): + return niceParse(self.source, self.filename, self.mode) + + def _get_tree(self): + tree = self.parse() + MutatingWalker.walk(tree, self.rm) + if self.rm.errors: + raise SyntaxError, self.rm.errors[0] + misc.set_filename(self.filename, tree) + syntax.check(tree) + return tree + + def compile(self): + tree = self._get_tree() + gen = self.CodeGeneratorClass(tree) + self.code = gen.getCode() + + +def compileAndTuplize(gen): + try: + gen.compile() + except SyntaxError, v: + return None, (str(v),), gen.rm.warnings, gen.rm.used_names + return gen.getCode(), (), gen.rm.warnings, gen.rm.used_names + +def compile_restricted_function(p, body, name, filename, globalize=None): + """Compiles a restricted code object for a function. + + The function can be reconstituted using the 'new' module: + + new.function(<code>, <globals>) + + The globalize argument, if specified, is a list of variable names to be + treated as globals (code is generated as if each name in the list + appeared in a global statement at the top of the function). + """ + gen = RFunction(p, body, name, filename, globalize) + return compileAndTuplize(gen) + +def compile_restricted_exec(s, filename='<string>'): + """Compiles a restricted code suite.""" + gen = RModule(s, filename) + return compileAndTuplize(gen) + +def compile_restricted_eval(s, filename='<string>'): + """Compiles a restricted expression.""" + gen = RExpression(s, filename) + return compileAndTuplize(gen) + +def compile_restricted(source, filename, mode): + """Replacement for the builtin compile() function.""" + if mode == "single": + gen = RInteractive(source, filename) + elif mode == "exec": + gen = RModule(source, filename) + elif mode == "eval": + gen = RExpression(source, filename) + else: + raise ValueError("compile_restricted() 3rd arg must be 'exec' or " + "'eval' or 'single'") + gen.compile() + return gen.getCode() + +class RestrictedCodeGenerator: + """Mixin for CodeGenerator to replace UNPACK_SEQUENCE bytecodes. + + The UNPACK_SEQUENCE opcode is not safe because it extracts + elements from a sequence without using a safe iterator or + making __getitem__ checks. + + This code generator replaces use of UNPACK_SEQUENCE with calls to + a function that unpacks the sequence, performes the appropriate + security checks, and returns a simple list. + """ + + # Replace the standard code generator for assignments to tuples + # and lists. + + def _gen_safe_unpack_sequence(self, num): + # We're at a place where UNPACK_SEQUENCE should be generated, to + # unpack num items. That's a security hole, since it exposes + # individual items from an arbitrary iterable. We don't remove + # the UNPACK_SEQUENCE, but instead insert a call to our _getiter_() + # wrapper first. That applies security checks to each item as + # it's delivered. codegen is (just) a bit messy because the + # iterable is already on the stack, so we have to do a stack swap + # to get things in the right order. + self.emit('LOAD_GLOBAL', '_getiter_') + self.emit('ROT_TWO') + self.emit('CALL_FUNCTION', 1) + self.emit('UNPACK_SEQUENCE', num) + + def _visitAssSequence(self, node): + if findOp(node) != 'OP_DELETE': + self._gen_safe_unpack_sequence(len(node.nodes)) + for child in node.nodes: + self.visit(child) + + visitAssTuple = _visitAssSequence + visitAssList = _visitAssSequence + + # Call to generate code for unpacking nested tuple arguments + # in function calls. + + def unpackSequence(self, tup): + self._gen_safe_unpack_sequence(len(tup)) + for elt in tup: + if isinstance(elt, tuple): + self.unpackSequence(elt) + else: + self._nameOp('STORE', elt) + +# A collection of code generators that adds the restricted mixin to +# handle unpacking for all the different compilation modes. They +# are defined here (at the end) so that can refer to RestrictedCodeGenerator. + +class RestrictedFunctionCodeGenerator(RestrictedCodeGenerator, + pycodegen.FunctionCodeGenerator): + pass + +class RestrictedExpressionCodeGenerator(RestrictedCodeGenerator, + pycodegen.ExpressionCodeGenerator): + pass + +class RestrictedInteractiveCodeGenerator(RestrictedCodeGenerator, + pycodegen.InteractiveCodeGenerator): + pass + +class RestrictedModuleCodeGenerator(RestrictedCodeGenerator, + pycodegen.ModuleCodeGenerator): + + def initClass(self): + ModuleCodeGenerator.initClass(self) + self.__class__.FunctionGen = RestrictedFunctionCodeGenerator + + +# These subclasses work around the definition of stub compile and mode +# attributes in the common base class AbstractCompileMode. If it +# didn't define new attributes, then the stub code inherited via +# RestrictedCompileMode would override the real definitions in +# Expression. + +class RExpression(RestrictedCompileMode, Expression): + mode = "eval" + CodeGeneratorClass = RestrictedExpressionCodeGenerator + +class RInteractive(RestrictedCompileMode, Interactive): + mode = "single" + CodeGeneratorClass = RestrictedInteractiveCodeGenerator + +class RModule(RestrictedCompileMode, Module): + mode = "exec" + CodeGeneratorClass = RestrictedModuleCodeGenerator + +class RFunction(RModule): + """A restricted Python function built from parts.""" + + CodeGeneratorClass = RestrictedModuleCodeGenerator + + def __init__(self, p, body, name, filename, globals): + self.params = p + self.body = body + self.name = name + self.globals = globals or [] + RModule.__init__(self, None, filename) + + def parse(self): + # Parse the parameters and body, then combine them. + firstline = 'def f(%s): pass' % self.params + tree = niceParse(firstline, '<function parameters>', 'exec') + f = tree.node.nodes[0] + body_code = niceParse(self.body, self.filename, 'exec') + # Stitch the body code into the function. + f.code.nodes = body_code.node.nodes + f.name = self.name + # Look for a docstring. + stmt1 = f.code.nodes[0] + if (isinstance(stmt1, ast.Discard) and + isinstance(stmt1.expr, ast.Const) and + isinstance(stmt1.expr.value, str)): + f.doc = stmt1.expr.value + # The caller may specify that certain variables are globals + # so that they can be referenced before a local assignment. + # The only known example is the variables context, container, + # script, traverse_subpath in PythonScripts. + if self.globals: + f.code.nodes.insert(0, ast.Global(self.globals)) + return tree diff --git a/lib/kross/python/scripts/RestrictedPython/RestrictionMutator.py b/lib/kross/python/scripts/RestrictedPython/RestrictionMutator.py new file mode 100644 index 00000000..a8b3850e --- /dev/null +++ b/lib/kross/python/scripts/RestrictedPython/RestrictionMutator.py @@ -0,0 +1,372 @@ +############################################################################## +# +# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE +# +############################################################################## +"""Modify AST to include security checks. + +RestrictionMutator modifies a tree produced by +compiler.transformer.Transformer, restricting and enhancing the +code in various ways before sending it to pycodegen. + +$Revision: 1.13 $ +""" + +from SelectCompiler import ast, parse, OP_ASSIGN, OP_DELETE, OP_APPLY + +# These utility functions allow us to generate AST subtrees without +# line number attributes. These trees can then be inserted into other +# trees without affecting line numbers shown in tracebacks, etc. +def rmLineno(node): + """Strip lineno attributes from a code tree.""" + if node.__dict__.has_key('lineno'): + del node.lineno + for child in node.getChildren(): + if isinstance(child, ast.Node): + rmLineno(child) + +def stmtNode(txt): + """Make a "clean" statement node.""" + node = parse(txt).node.nodes[0] + rmLineno(node) + return node + +# The security checks are performed by a set of six functions that +# must be provided by the restricted environment. + +_apply_name = ast.Name("_apply_") +_getattr_name = ast.Name("_getattr_") +_getitem_name = ast.Name("_getitem_") +_getiter_name = ast.Name("_getiter_") +_print_target_name = ast.Name("_print") +_write_name = ast.Name("_write_") + +# Constants. +_None_const = ast.Const(None) +_write_const = ast.Const("write") + +_printed_expr = stmtNode("_print()").expr +_print_target_node = stmtNode("_print = _print_()") + +class FuncInfo: + print_used = False + printed_used = False + +class RestrictionMutator: + + def __init__(self): + self.warnings = [] + self.errors = [] + self.used_names = {} + self.funcinfo = FuncInfo() + + 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 name.startswith("_") and name != "_": + # Note: "_" *is* allowed. + self.error(node, '"%s" is an invalid variable name because' + ' it starts with "_"' % name) + if name == "printed": + self.error(node, '"printed" is a reserved name.') + + def checkAttrName(self, node): + """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 name.startswith("_") and name != "_": + # Note: "_" *is* allowed. + self.error(node, '"%s" is an invalid attribute name ' + 'because it starts with "_".' % name) + + def prepBody(self, body): + """Insert code for print at the beginning of the code suite.""" + + if self.funcinfo.print_used or self.funcinfo.printed_used: + # Add code at top for creating _print_target + body.insert(0, _print_target_node) + if not self.funcinfo.printed_used: + self.warnings.append( + "Prints, but never reads 'printed' variable.") + elif not self.funcinfo.print_used: + self.warnings.append( + "Doesn't print, but reads 'printed' variable.") + + 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: + if isinstance(argname, str): + self.checkName(node, argname) + else: + for name in argname: + self.checkName(node, name) + walker.visitSequence(node.defaults) + + former_funcinfo = self.funcinfo + self.funcinfo = FuncInfo() + node = walker.defaultVisitNode(node, exclude=('defaults',)) + self.prepBody(node.code.nodes) + self.funcinfo = former_funcinfo + 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 = True + if node.dest is None: + node.dest = _print_target_name + else: + # Pre-validate access to the "write" attribute. + # "print >> ob, x" becomes + # "print >> (_getattr(ob, 'write') and ob), x" + node.dest = ast.And([ + ast.CallFunc(_getattr_name, [node.dest, _write_const]), + node.dest]) + return node + + 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 = True + return _printed_expr + self.checkName(node, node.name) + self.used_names[node.name] = True + return node + + def visitCallFunc(self, node, walker): + """Checks calls with *-args and **-args. + + That's a way of spelling apply(), and needs to use our safe + _apply_ instead. + """ + walked = walker.defaultVisitNode(node) + if node.star_args is None and node.dstar_args is None: + # This is not an extended function call + return walked + # Otherwise transform foo(a, b, c, d=e, f=g, *args, **kws) into a call + # of _apply_(foo, a, b, c, d=e, f=g, *args, **kws). The interesting + # thing here is that _apply_() is defined with just *args and **kws, + # so it gets Python to collapse all the myriad ways to call functions + # into one manageable form. + # + # From there, _apply_() digs out the first argument of *args (it's the + # function to call), wraps args and kws in guarded accessors, then + # calls the function, returning the value. + # Transform foo(...) to _apply(foo, ...) + walked.args.insert(0, walked.node) + walked.node = _apply_name + return walked + + def visitAssName(self, node, walker): + """Checks a name assignment using checkName().""" + self.checkName(node, node.name) + return node + + def visitFor(self, node, walker): + # convert + # for x in expr: + # to + # for x in _getiter(expr): + # + # Note that visitListCompFor is the same thing. Exactly the same + # transformation is needed to convert + # [... for x in expr ...] + # to + # [... for x in _getiter(expr) ...] + node = walker.defaultVisitNode(node) + node.list = ast.CallFunc(_getiter_name, [node.list]) + return node + + visitListCompFor = visitFor + + 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', False): + # We're in an augmented assignment + # We might support this later... + self.error(node, 'Augmented assignment of ' + 'attributes is not allowed.') + return ast.CallFunc(_getattr_name, + [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: + # Set 'subs' to the node that represents the subscript or slice. + if getattr(node, 'in_aug_assign', False): + # We're in an augmented assignment + # We might support this later... + self.error(node, 'Augmented assignment of ' + 'object items and slices is not allowed.') + if hasattr(node, 'subs'): + # Subscript. + subs = node.subs + if len(subs) > 1: + # example: ob[1,2] + subs = ast.Tuple(subs) + else: + # example: ob[1] + subs = subs[0] + else: + # Slice. + # example: obj[0:2] + lower = node.lower + if lower is None: + lower = _None_const + upper = node.upper + if upper is None: + upper = _None_const + subs = ast.Sliceobj([lower, upper]) + return ast.CallFunc(_getitem_name, [node.expr, subs]) + elif node.flags in (OP_DELETE, OP_ASSIGN): + # set or remove subscript or slice + node.expr = ast.CallFunc(_write_name, [node.expr]) + return node + + 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_name, [node.expr]) + return node + + def visitExec(self, node, walker): + self.error(node, 'Exec statements are not allowed.') + + def visitYield(self, node, walker): + self.error(node, 'Yield statements are not allowed.') + + def visitClass(self, node, walker): + """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. + """ + 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 = True + 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 + diff --git a/lib/kross/python/scripts/RestrictedPython/SelectCompiler.py b/lib/kross/python/scripts/RestrictedPython/SelectCompiler.py new file mode 100644 index 00000000..5f9da698 --- /dev/null +++ b/lib/kross/python/scripts/RestrictedPython/SelectCompiler.py @@ -0,0 +1,28 @@ +############################################################################## +# +# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE +# +############################################################################## +"""Compiler selector. + +$Id: SelectCompiler.py 24763 2004-05-17 05:59:28Z philikon $ +""" + +# 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 diff --git a/lib/kross/python/scripts/RestrictedPython/Utilities.py b/lib/kross/python/scripts/RestrictedPython/Utilities.py new file mode 100644 index 00000000..87eef69b --- /dev/null +++ b/lib/kross/python/scripts/RestrictedPython/Utilities.py @@ -0,0 +1,77 @@ +############################################################################## +# +# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE +# +############################################################################## + +__version__='$Revision: 1.7 $'[11:-2] + +import string, math, random +import DocumentTemplate.sequence + +utility_builtins = {} + +utility_builtins['string'] = string +utility_builtins['math'] = math +utility_builtins['random'] = random +utility_builtins['whrandom'] = random +utility_builtins['sequence'] = DocumentTemplate.sequence + +try: + import DateTime + utility_builtins['DateTime']= DateTime.DateTime +except: pass + +def same_type(arg1, *args): + '''Compares the class or type of two or more objects.''' + t = getattr(arg1, '__class__', type(arg1)) + for arg in args: + if getattr(arg, '__class__', type(arg)) is not t: + return 0 + return 1 +utility_builtins['same_type'] = same_type + +def test(*args): + l=len(args) + for i in range(1, l, 2): + if args[i-1]: return args[i] + + if l%2: return args[-1] +utility_builtins['test'] = test + +def reorder(s, with=None, without=()): + # s, with, and without are sequences treated as sets. + # The result is subtract(intersect(s, with), without), + # unless with is None, in which case it is subtract(s, without). + if with is None: with=s + d={} + tt=type(()) + for i in s: + if type(i) is tt and len(i)==2: k, v = i + else: k= v = i + d[k]=v + r=[] + a=r.append + h=d.has_key + + for i in without: + if type(i) is tt and len(i)==2: k, v = i + else: k= v = i + if h(k): del d[k] + + for i in with: + if type(i) is tt and len(i)==2: k, v = i + else: k= v = i + if h(k): + a((k,d[k])) + del d[k] + + return r +utility_builtins['reorder'] = reorder diff --git a/lib/kross/python/scripts/RestrictedPython/__init__.py b/lib/kross/python/scripts/RestrictedPython/__init__.py new file mode 100644 index 00000000..0c70947e --- /dev/null +++ b/lib/kross/python/scripts/RestrictedPython/__init__.py @@ -0,0 +1,19 @@ +############################################################################## +# +# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE +# +############################################################################## +''' +RestrictedPython package. +$Id: __init__.py 24763 2004-05-17 05:59:28Z philikon $ +''' + +from SelectCompiler import * +from PrintCollector import PrintCollector |