[Zope3-checkins] SVN: Zope3/trunk/src/zope/ Refactored
untrusted-python support:
Jim Fulton
jim at zope.com
Wed Jul 28 15:37:35 EDT 2004
Log message for revision 26819:
Refactored untrusted-python support:
- collecting restricted builtins, restricted compilation, and
the slightly higher level "interpreter" support in a single
untrustedpython package.
- Changed the way restricted builtins are handled so that we create an
immutable module, rather than a dictionary. This is to make
__builtin__ immutable, to prevent evil code from changing it.
- We now use Fred's restricted compiler rather than the basic Python
compiler so that we can manipulate code to:
- Make sure that the results of getattrs are proxied
- Prevent use of exec, which we don't have code to handle yet.
(Not sure if we'll bother.)
- prevent use of raise or try/except, which we don't support yet,
but will eventually.
- Make sure that prints go to an interpreter-supplied object, rather
than sys.stdout.
- Updated varous clients to reflect new locations and APIs.
- Had to work around some bugs in the Python compiler modules.
Mote that global statements aren't handled correctly.
I did this because the restricted interpreter was to be included in
the X3.0 release and, even though it's not actually used, I was afraid
that someone would try to use it. Now, we've at least made an effort
to get it right, although a more thorough review is needed.
Changed:
U Zope3/trunk/src/zope/app/DEPENDENCIES.cfg
U Zope3/trunk/src/zope/app/interpreter/python.py
U Zope3/trunk/src/zope/app/interpreter/tests/test_pythoninterpreter.py
U Zope3/trunk/src/zope/app/pagetemplate/engine.py
U Zope3/trunk/src/zope/app/pythonpage/__init__.py
D Zope3/trunk/src/zope/restrictedpython/
D Zope3/trunk/src/zope/security/builtins.py
D Zope3/trunk/src/zope/security/interpreter.py
D Zope3/trunk/src/zope/security/tests/test_builtins.py
D Zope3/trunk/src/zope/security/tests/test_interpreter.py
A Zope3/trunk/src/zope/security/untrustedpython/
A Zope3/trunk/src/zope/security/untrustedpython/__init__.py
A Zope3/trunk/src/zope/security/untrustedpython/builtins.py
A Zope3/trunk/src/zope/security/untrustedpython/builtins.txt
A Zope3/trunk/src/zope/security/untrustedpython/interpreter.py
A Zope3/trunk/src/zope/security/untrustedpython/interpreter.txt
A Zope3/trunk/src/zope/security/untrustedpython/rcompile.py
A Zope3/trunk/src/zope/security/untrustedpython/rcompile.txt
A Zope3/trunk/src/zope/security/untrustedpython/tests.py
U Zope3/trunk/src/zope/tales/pythonexpr.py
-=-
Modified: Zope3/trunk/src/zope/app/DEPENDENCIES.cfg
===================================================================
--- Zope3/trunk/src/zope/app/DEPENDENCIES.cfg 2004-07-28 19:35:56 UTC (rev 26818)
+++ Zope3/trunk/src/zope/app/DEPENDENCIES.cfg 2004-07-28 19:37:35 UTC (rev 26819)
@@ -18,7 +18,6 @@
zope.pagetemplate
zope.proxy
zope.publisher
-zope.restrictedpython
zope.schema
zope.security
zope.server
Modified: Zope3/trunk/src/zope/app/interpreter/python.py
===================================================================
--- Zope3/trunk/src/zope/app/interpreter/python.py 2004-07-28 19:35:56 UTC (rev 26818)
+++ Zope3/trunk/src/zope/app/interpreter/python.py 2004-07-28 19:37:35 UTC (rev 26819)
@@ -15,12 +15,12 @@
$Id$
"""
-import StringIO
+from StringIO import StringIO
import sys
from zope.app.interpreter.interfaces import IInterpreter
from zope.interface import implements
-from zope.security.interpreter import RestrictedInterpreter
+from zope.security.untrustedpython.interpreter import exec_src, exec_code
class PythonInterpreter(object):
@@ -28,18 +28,19 @@
def evaluate(self, code, globals):
"""See zope.app.interfaces.IInterpreter"""
- tmp = sys.stdout
- sys.stdout = StringIO.StringIO()
- ri = RestrictedInterpreter()
- ri.globals = globals
- try:
- # This used to add a newline for Python 2.2. As far as
- # I know, we only care about 2.3 and later.
- ri.ri_exec(code)
- finally:
- result = sys.stdout
- sys.stdout = tmp
- return result.getvalue()
+ tmp = StringIO()
+ globals['untrusted_output'] = tmp
+ if isinstance(code, basestring):
+ exec_src(code, globals,
+ {}, # we don't want to get local assignments saved.
+ )
+ else:
+ # XXX There atr no tests for this branch
+ code.exec_(globals,
+ {}, # we don't want to get local assignments saved.
+ )
+
+ return tmp.getvalue()
def evaluateRawCode(self, code, globals):
Modified: Zope3/trunk/src/zope/app/interpreter/tests/test_pythoninterpreter.py
===================================================================
--- Zope3/trunk/src/zope/app/interpreter/tests/test_pythoninterpreter.py 2004-07-28 19:35:56 UTC (rev 26818)
+++ Zope3/trunk/src/zope/app/interpreter/tests/test_pythoninterpreter.py 2004-07-28 19:37:35 UTC (rev 26819)
@@ -62,11 +62,14 @@
self._check(code, '')
self.assert_('x' not in self.globals.keys())
- def test_global_variable_assignment(self):
- code = ('global x\n'
- 'x = "hello"\n')
- self._check(code, '')
- self.assertEqual(self.globals['x'], 'hello')
+# TODO: get global statements working
+# The compiler module, which we now rely on, doesn't handle global
+# statements correctly.
+## def test_global_variable_assignment(self):
+## code = ('global x\n'
+## 'x = "hello"\n')
+## self._check(code, '')
+## self.assertEqual(self.globals['x'], 'hello')
def test_wrapped_by_html_comment(self):
self._check('<!-- print "hello" -->', 'hello\n', True)
Modified: Zope3/trunk/src/zope/app/pagetemplate/engine.py
===================================================================
--- Zope3/trunk/src/zope/app/pagetemplate/engine.py 2004-07-28 19:35:56 UTC (rev 26818)
+++ Zope3/trunk/src/zope/app/pagetemplate/engine.py 2004-07-28 19:37:35 UTC (rev 26819)
@@ -29,9 +29,9 @@
from zope.component.exceptions import ComponentLookupError
from zope.exceptions import NotFoundError
from zope.proxy import removeAllProxies
-from zope.restrictedpython import rcompile
+from zope.security.untrustedpython import rcompile
from zope.security.proxy import ProxyFactory
-from zope.security.builtins import RestrictedBuiltins
+from zope.security.untrustedpython.builtins import SafeBuiltins
from zope.i18n import translate
from zope.app import zapi
@@ -81,23 +81,12 @@
# version of getattr() that wraps values in security proxies where
# appropriate:
-_marker = object()
-def safe_getattr(object, name, default=_marker):
- if default is _marker:
- return ProxyFactory(getattr(object, name))
- else:
- return ProxyFactory(getattr(object, name, default))
-
-RestrictedBuiltins = RestrictedBuiltins.copy()
-RestrictedBuiltins["getattr"] = safe_getattr
-
-
class ZopePythonExpr(PythonExpr):
def __call__(self, econtext):
__traceback_info__ = self.text
- vars = self._bind_used_names(econtext, RestrictedBuiltins)
+ vars = self._bind_used_names(econtext, SafeBuiltins)
return eval(self._code, vars)
def _compile(self, text, filename):
Modified: Zope3/trunk/src/zope/app/pythonpage/__init__.py
===================================================================
--- Zope3/trunk/src/zope/app/pythonpage/__init__.py 2004-07-28 19:35:56 UTC (rev 26818)
+++ Zope3/trunk/src/zope/app/pythonpage/__init__.py 2004-07-28 19:37:35 UTC (rev 26819)
@@ -24,6 +24,7 @@
from zope.interface import Interface, implements
from zope.schema import SourceText, TextLine
from zope.app.i18n import ZopeMessageIDFactory as _
+from zope.security.untrustedpython.interpreter import CompiledProgram
class IPythonPage(Interface):
@@ -172,19 +173,31 @@
self.__prepared_source = self.prepareSource(source)
# Compile objects cannot be pickled
- self._v_compiled = compile(self.__prepared_source,
- self.__filename(), 'exec')
+ self._v_compiled = CompiledProgram(self.__prepared_source,
+ self.__filename())
_tripleQuotedString = re.compile(
- r"^([ \t]*)[uU]?([rR]?)(('''|\"\"\").*?\4)", re.MULTILINE | re.DOTALL)
+ r"^([ \t]*)[uU]?([rR]?)(('''|\"\"\")(.*)\4)", re.MULTILINE | re.DOTALL)
def prepareSource(self, source):
"""Prepare source."""
# compile() don't accept '\r' altogether
source = source.replace("\r\n", "\n")
source = source.replace("\r", "\n")
+
+ if isinstance(source, unicode):
+
+ # Use special conversion function to work around
+ # compiler-module failure to handle unicode in literals
+
+ try:
+ source = source.encode('ascii')
+ except UnicodeEncodeError:
+ return self._tripleQuotedString.sub(_print_usrc, source)
+
return self._tripleQuotedString.sub(r"\1print u\2\3", source)
+
def getSource(self):
"""Get the original source code."""
return self.__source
@@ -198,8 +211,8 @@
# Compile objects cannot be pickled
if not hasattr(self, '_v_compiled'):
- self._v_compiled = compile(self.__prepared_source,
- self.__filename(), 'exec')
+ self._v_compiled = CompiledProgram(self.__prepared_source,
+ self.__filename())
kw['request'] = request
kw['script'] = self
@@ -208,3 +221,13 @@
service = zapi.getService(Utilities)
interpreter = service.queryUtility(IInterpreter, 'text/server-python')
return interpreter.evaluate(self._v_compiled, kw)
+
+def _print_usrc(match):
+ string = match.group(3)
+ raw = match.group(2)
+ if raw:
+ return match.group(1)+'print '+`string`
+ return match.group(1)+'print u'+match.group(3).encode('unicode-escape')
+
+
+
Deleted: Zope3/trunk/src/zope/security/builtins.py
===================================================================
--- Zope3/trunk/src/zope/security/builtins.py 2004-07-28 19:35:56 UTC (rev 26818)
+++ Zope3/trunk/src/zope/security/builtins.py 2004-07-28 19:37:35 UTC (rev 26819)
@@ -1,103 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2002 Zope Corporation and Contributors.
-# All Rights Reserved.
-#
-# This software is subject to the provisions of the Zope Public License,
-# Version 2.1 (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
-#
-##############################################################################
-"""Protection of builtin objects.
-
-$Id$
-"""
-import sys
-
-def RestrictedBuiltins():
-
- from zope.security.proxy import ProxyFactory
- from zope.security.checker import NamesChecker
-
- # It's better to say what is safe than it say what is not safe
- _safe = [
- 'ArithmeticError', 'AssertionError', 'AttributeError',
- 'DeprecationWarning', 'EOFError', 'Ellipsis', 'EnvironmentError',
- 'Exception', 'FloatingPointError', 'IOError', 'ImportError',
- 'IndentationError', 'IndexError', 'KeyError', 'KeyboardInterrupt',
- 'LookupError', 'MemoryError', 'NameError', 'None', 'NotImplemented',
- 'NotImplementedError', 'OSError', 'OverflowError', 'OverflowWarning',
- 'ReferenceError', 'RuntimeError', 'RuntimeWarning', 'StandardError',
- 'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError',
- 'SystemExit', 'TabError', 'TypeError', 'UnboundLocalError',
- 'UnicodeError', 'UserWarning', 'ValueError', 'Warning',
- 'ZeroDivisionError',
- '__debug__', '__doc__', '__name__', 'abs', 'apply', 'bool',
- 'buffer', 'callable', 'chr', 'classmethod', 'cmp', 'coerce',
- 'complex', 'copyright', 'credits', 'delattr',
- 'dict', 'divmod', 'filter', 'float', 'getattr',
- 'hasattr', 'hash', 'hex', 'id', 'int', 'isinstance',
- 'issubclass', 'iter', 'len', 'license', 'list',
- 'long', 'map', 'max', 'min', 'object', 'oct', 'ord', 'pow',
- 'property', 'quit', 'range', 'reduce', 'repr', 'round',
- 'setattr', 'slice', 'staticmethod', 'str', 'super', 'tuple',
- 'type', 'unichr', 'unicode', 'vars', 'xrange', 'zip',
- 'True', 'False'
- ]
-
- # TODO: dir segfaults with a seg fault due to a bas tuple check in
- # merge_class_dict in object.c. The assert macro seems to be doing
- # the wrong think. Basically, if an object has bases, then bases
- # is assumed to be a tuple.
-
- # Anything that accesses an external file is a no no:
- # 'open', 'execfile', 'file'
-
- # We dont want restricted code to call exit: 'SystemExit', 'exit'
-
- # Other no nos:
- # help prints
- # input does I/O
- # raw_input does I/O
- # intern's effect is too global
- # reload does import, TODO: doesn't it use __import__?
-
- _builtinTypeChecker = NamesChecker(
- ['__str__', '__repr__', '__name__', '__module__',
- '__bases__', '__call__'])
-
- import __builtin__
-
- builtins = {}
- for name in _safe:
- value = getattr(__builtin__, name)
- if isinstance(value, type):
- value = ProxyFactory(value, _builtinTypeChecker)
- else:
- value = ProxyFactory(value)
- builtins[name] = value
-
- def __import__(name, globals=None, locals=None, fromlist=()):
- # Waaa, we have to emulate __import__'s weird semantics.
- try:
- module = sys.modules[name]
- if fromlist:
- return module
-
- l = name.find('.')
- if l < 0:
- return module
-
- return sys.modules[name[:l]]
-
- except KeyError:
- raise ImportError(name)
-
- builtins['__import__'] = ProxyFactory(__import__)
-
- return builtins
-
-RestrictedBuiltins = RestrictedBuiltins()
Deleted: Zope3/trunk/src/zope/security/interpreter.py
===================================================================
--- Zope3/trunk/src/zope/security/interpreter.py 2004-07-28 19:35:56 UTC (rev 26818)
+++ Zope3/trunk/src/zope/security/interpreter.py 2004-07-28 19:37:35 UTC (rev 26819)
@@ -1,33 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2002 Zope Corporation and Contributors.
-# All Rights Reserved.
-#
-# This software is subject to the provisions of the Zope Public License,
-# Version 2.1 (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 interpreter.
-
-TODO: This code needs a serious security review!!!
-
-$Id$
-"""
-from zope.security.builtins import RestrictedBuiltins
-
-class RestrictedInterpreter(object):
-
- def __init__(self):
- self.globals = {}
- self.locals = {}
-
- def ri_exec(self, code):
- """Execute Python code in a restricted environment.
-
- The value of code can be either source or binary code."""
- self.globals['__builtins__'] = RestrictedBuiltins
- exec code in self.globals, self.locals
Deleted: Zope3/trunk/src/zope/security/tests/test_builtins.py
===================================================================
--- Zope3/trunk/src/zope/security/tests/test_builtins.py 2004-07-28 19:35:56 UTC (rev 26818)
+++ Zope3/trunk/src/zope/security/tests/test_builtins.py 2004-07-28 19:37:35 UTC (rev 26819)
@@ -1,44 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
-# All Rights Reserved.
-#
-# This software is subject to the provisions of the Zope Public License,
-# Version 2.1 (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 Builtins Tests
-
-$Id$
-"""
-
-from unittest import makeSuite, TestCase, main
-from zope.testing.cleanup import CleanUp # Base class w registry cleanup
-
-class Test(CleanUp, TestCase):
-
- def test(self):
- from zope.security.builtins import RestrictedBuiltins
- from zope.security.interfaces import Forbidden
-
- def e(expr):
- return eval(expr, {'__builtins__': RestrictedBuiltins})
-
- self.assertEqual(e('__import__("sys").__name__'), "sys")
- self.assertEqual(e('__import__("zope.security").__name__'), "zope")
- self.assertEqual(e(
- '__import__("zope.security", {}, None, ["__doc__"]).__name__'),
- "zope.security")
- self.assertRaises(Forbidden, e, '__import__("sys").exit')
-
-
-
-def test_suite():
- return makeSuite(Test)
-
-if __name__=='__main__':
- main(defaultTest='test_suite')
Deleted: Zope3/trunk/src/zope/security/tests/test_interpreter.py
===================================================================
--- Zope3/trunk/src/zope/security/tests/test_interpreter.py 2004-07-28 19:35:56 UTC (rev 26818)
+++ Zope3/trunk/src/zope/security/tests/test_interpreter.py 2004-07-28 19:37:35 UTC (rev 26819)
@@ -1,78 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2003 Zope Corporation and Contributors.
-# All Rights Reserved.
-#
-# This software is subject to the provisions of the Zope Public License,
-# Version 2.1 (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.
-#
-##############################################################################
-"""Test Restricted Python Interpreter
-
-$Id$
-"""
-import unittest
-
-from zope.security.interpreter import RestrictedInterpreter
-from zope.security.checker import defineChecker
-
-from zope.testing.cleanup import CleanUp
-
-class RITests(unittest.TestCase, CleanUp):
-
- def setUp(self):
- CleanUp.setUp(self)
- self.rinterp = RestrictedInterpreter()
-
- def tearDown(self):
- CleanUp.tearDown(self)
-
- def testExec(self):
- self.rinterp.ri_exec("str(type(1))\n")
-
- def testImport(self):
- self.rinterp.ri_exec("import zope.security.proxy")
-
- def testWrapping(self):
- # make sure we've really got proxies
- import types
- from zope.security.checker import NamesChecker
-
- checker = NamesChecker(['Proxy'])
-
- import zope.security.proxy
- defineChecker(zope.security.proxy, checker)
-
- checker = NamesChecker(['BuiltinFunctionType'])
- defineChecker(types, checker)
-
- code = ("from zope.security.proxy import Proxy\n"
- "import types\n"
- "assert type(id) is not types.BuiltinFunctionType\n"
- )
- self.rinterp.ri_exec(code)
-
- def testGlobalVersusLocal(self):
- code = ("global x\n"
- "x = 1\n"
- "y = 2\n")
- self.rinterp.ri_exec(code)
- self.assert_('x' in self.rinterp.globals)
- self.assert_('y' not in self.rinterp.globals)
- self.assertEqual(self.rinterp.globals['x'], 1)
- self.assert_('x' not in self.rinterp.locals)
- self.assert_('y' in self.rinterp.locals)
- self.assertEqual(self.rinterp.locals['y'], 2)
-
-
-def test_suite():
- return unittest.makeSuite(RITests)
-
-
-if __name__=='__main__':
- from unittest import main
- main(defaultTest='test_suite')
Added: Zope3/trunk/src/zope/security/untrustedpython/__init__.py
===================================================================
--- Zope3/trunk/src/zope/security/untrustedpython/__init__.py 2004-07-28 19:35:56 UTC (rev 26818)
+++ Zope3/trunk/src/zope/security/untrustedpython/__init__.py 2004-07-28 19:37:35 UTC (rev 26819)
@@ -0,0 +1 @@
+#
Property changes on: Zope3/trunk/src/zope/security/untrustedpython/__init__.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Copied: Zope3/trunk/src/zope/security/untrustedpython/builtins.py (from rev 26720, Zope3/trunk/src/zope/security/builtins.py)
===================================================================
--- Zope3/trunk/src/zope/security/builtins.py 2004-07-23 19:03:56 UTC (rev 26720)
+++ Zope3/trunk/src/zope/security/untrustedpython/builtins.py 2004-07-28 19:37:35 UTC (rev 26819)
@@ -0,0 +1,122 @@
+##############################################################################
+#
+# Copyright (c) 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (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
+#
+##############################################################################
+"""Protection of builtin objects.
+
+$Id$
+"""
+from zope.security.proxy import ProxyFactory
+import new
+
+def SafeBuiltins():
+
+ builtins = {}
+
+ from zope.security.checker import NamesChecker
+ import __builtin__
+
+ _builtinTypeChecker = NamesChecker(
+ ['__str__', '__repr__', '__name__', '__module__',
+ '__bases__', '__call__'])
+
+ # It's better to say what is safe than it say what is not safe
+ for name in [
+
+ # Names of safe objects. See untrustedinterpreter.txt for a
+ # definition of safe objects.
+
+ 'ArithmeticError', 'AssertionError', 'AttributeError',
+ 'DeprecationWarning', 'EOFError', 'Ellipsis', 'EnvironmentError',
+ 'Exception', 'FloatingPointError', 'IOError', 'ImportError',
+ 'IndentationError', 'IndexError', 'KeyError', 'KeyboardInterrupt',
+ 'LookupError', 'MemoryError', 'NameError', 'None', 'NotImplemented',
+ 'NotImplementedError', 'OSError', 'OverflowError', 'OverflowWarning',
+ 'ReferenceError', 'RuntimeError', 'RuntimeWarning', 'StandardError',
+ 'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError',
+ 'SystemExit', 'TabError', 'TypeError', 'UnboundLocalError',
+ 'UnicodeError', 'UserWarning', 'ValueError', 'Warning',
+ 'ZeroDivisionError',
+ '__debug__', '__name__', '__doc__', 'abs', 'apply', 'bool',
+ 'buffer', 'callable', 'chr', 'classmethod', 'cmp', 'coerce',
+ 'complex', 'copyright', 'credits', 'delattr',
+ 'dict', 'divmod', 'filter', 'float', 'getattr',
+ 'hasattr', 'hash', 'hex', 'id', 'int', 'isinstance',
+ 'issubclass', 'iter', 'len', 'license', 'list',
+ 'long', 'map', 'max', 'min', 'object', 'oct', 'ord', 'pow',
+ 'property', 'quit', 'range', 'reduce', 'repr', 'round',
+ 'setattr', 'slice', 'staticmethod', 'str', 'super', 'tuple',
+ 'type', 'unichr', 'unicode', 'vars', 'xrange', 'zip',
+ 'True', 'False',
+
+ # TODO: dir segfaults with a seg fault due to a bas tuple
+ # check in merge_class_dict in object.c. The assert macro
+ # seems to be doing the wrong think. Basically, if an object
+ # has bases, then bases is assumed to be a tuple.
+ #dir,
+ ]:
+
+ value = getattr(__builtin__, name)
+ if isinstance(value, type):
+ value = ProxyFactory(value, _builtinTypeChecker)
+ else:
+ value = ProxyFactory(value)
+ builtins[name] = value
+
+ from sys import modules
+
+ def _imp(name, fromlist, prefix=''):
+ module = modules.get(prefix+name)
+ if module is not None:
+ if fromlist or ('.' not in name):
+ return module
+ return modules[prefix+name.split('.')[0]]
+
+ def __import__(name, globals=None, locals=None, fromlist=()):
+ # Waaa, we have to emulate __import__'s weird semantics.
+
+ if globals:
+ __name__ = globals.get('__name__')
+ if __name__:
+ # Maybe do a relative import
+ if '__path__' not in globals:
+ # We have an ordinary module, not a package,
+ # so remove last name segment:
+ __name__ = '.'.join(__name__.split('.')[:-1])
+ if __name__:
+ module = _imp(name, fromlist, __name__+'.')
+ if module is not None:
+ return module
+
+ module = _imp(name, fromlist)
+ if module is not None:
+ return module
+
+ raise ImportError(name)
+
+ builtins['__import__'] = ProxyFactory(__import__)
+
+ return builtins
+
+class ImmutableModule(new.module):
+ def __init__(self, name='__builtins__', **kw):
+ new.module.__init__(self, name)
+ self.__dict__.update(kw)
+
+ def __setattr__(self, name, v):
+ raise AttributeError, name
+
+ def __delattr__(self, name):
+ raise AttributeError, name
+
+
+SafeBuiltins = ImmutableModule(**SafeBuiltins())
Added: Zope3/trunk/src/zope/security/untrustedpython/builtins.txt
===================================================================
--- Zope3/trunk/src/zope/security/untrustedpython/builtins.txt 2004-07-28 19:35:56 UTC (rev 26818)
+++ Zope3/trunk/src/zope/security/untrustedpython/builtins.txt 2004-07-28 19:37:35 UTC (rev 26819)
@@ -0,0 +1,114 @@
+Safe Builtins
+=============
+
+When executing untrusted Python code, we need to make sure that we
+only give the code access to safe, basic or proxied objects. This
+included the builtin objects provided to Python code through a special
+__builtins__ module in globals. The `builtins` module provides a
+suitable module object:
+
+ >>> from zope.security.untrustedpython.builtins import SafeBuiltins
+ >>> d = {'__builtins__': SafeBuiltins}
+ >>> exec 'x = str(1)' in d
+ >>> d['x']
+ '1'
+
+The object is immutable:
+
+ >>> SafeBuiltins.foo = 1
+ Traceback (most recent call last):
+ ...
+ AttributeError: foo
+
+ >>> del SafeBuiltins['getattr']
+ Traceback (most recent call last):
+ ...
+ TypeError: object does not support item deletion
+
+
+
+ Exception raised:
+ ...
+ TypeError: object does not support item deletion
+
+(Note that you can mutate it through its `__dict__` attribute,
+ however, when combined with the untrusted code compiler, getting the
+ `__dict__` attribute will return a proxied object that will prevent
+ mutation.)
+
+It contains items with keys that are all strings and values that are
+either proxied or are basic types:
+
+ >>> from zope.security.proxy import Proxy
+ >>> for key, value in SafeBuiltins.__dict__.items():
+ ... if not isinstance(key, str):
+ ... raise TypeError(key)
+ ... if value is not None and not isinstance(value, (Proxy, int, str)):
+ ... raise TypeError(value, key)
+
+It doesn't contain unsafe items, such as eval, globals, etc:
+
+ >>> SafeBuiltins.eval
+ Traceback (most recent call last):
+ ...
+ AttributeError: 'ImmutableModule' object has no attribute 'eval'
+ >>> SafeBuiltins.globals
+ Traceback (most recent call last):
+ ...
+ AttributeError: 'ImmutableModule' object has no attribute 'globals'
+
+The safe builtins also contains a custom __import__ function.
+
+ >>> imp = SafeBuiltins.__import__
+
+As with regular import, it only returns the top-level package if no
+fromlist is specified:
+
+ >>> import zope.security
+ >>> imp('zope.security') == zope
+ True
+ >>> imp('zope.security', {}, {}, ['*']) == zope.security
+ True
+
+Note that the values returned are proxied:
+
+ >>> type(imp('zope.security')) is Proxy
+ True
+
+This means that, having imported a module, you will only be able to
+access attributes for which you are authorized.
+
+Unlike regular __import__, you can nly import modules that have been
+previously imported. This is to prevent unauthorized execution of
+module-initialization code:
+
+ >>> security = zope.security
+ >>> import sys
+ >>> del sys.modules['zope.security']
+ >>> imp('zope.security')
+ Traceback (most recent call last):
+ ...
+ ImportError: zope.security
+
+ >>> sys.modules['zope.security'] = security
+
+Package-relative imports are supported (for now):
+
+ >>> imp('security', {'__name__': 'zope', '__path__': []}) == security
+ True
+ >>> imp('security', {'__name__': 'zope.foo'}) == zope.security
+ True
+
+ >>> imp('security.untrustedpython', {'__name__': 'zope.foo'}) == security
+ True
+ >>> from zope.security import untrustedpython
+ >>> imp('security.untrustedpython', {'__name__': 'zope.foo'}, {}, ['*']
+ ... ) == untrustedpython
+ True
+
+
+
+
+
+
+
Property changes on: Zope3/trunk/src/zope/security/untrustedpython/builtins.txt
___________________________________________________________________
Name: svn:eol-style
+ native
Copied: Zope3/trunk/src/zope/security/untrustedpython/interpreter.py (from rev 26795, Zope3/trunk/src/zope/security/interpreter.py)
===================================================================
--- Zope3/trunk/src/zope/security/interpreter.py 2004-07-27 15:15:58 UTC (rev 26795)
+++ Zope3/trunk/src/zope/security/untrustedpython/interpreter.py 2004-07-28 19:37:35 UTC (rev 26819)
@@ -0,0 +1,78 @@
+##############################################################################
+#
+# Copyright (c) 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (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 interpreter.
+
+TODO: This code needs a serious security review!!!
+
+$Id$
+"""
+from zope.security.untrustedpython.builtins import SafeBuiltins
+from zope.security.untrustedpython.rcompile import compile
+import warnings
+
+class RestrictedInterpreter(object):
+
+ def __init__(self):
+ warnings.warn("RestrictedInterpreter was deprecated 2004/7/27",
+ DeprecationWarning, 2)
+ self.globals = {}
+ self.locals = {}
+
+ def ri_exec(self, code):
+ """Execute Python code in a restricted environment.
+
+ The value of code can be either source or binary code."""
+ if isinstance(code, basestring):
+ code = compile(code, '<string>', 'exec')
+ self.globals['__builtins__'] = SafeBuiltins
+ exec code in self.globals, self.locals
+
+def exec_code(code, globals, locals=None):
+ globals['__builtins__'] = SafeBuiltins
+ exec code in globals, locals
+
+def exec_src(source, globals, locals=None):
+ globals['__builtins__'] = SafeBuiltins
+ code = compile(source, '<string>', 'exec')
+ exec code in globals, locals
+
+
+class CompiledExpression(object):
+ """A compiled expression
+ """
+
+ def __init__(self, source, filename='<string>'):
+ self.source = source
+ self.code = compile(source, filename, 'eval')
+
+ def eval(self, globals, locals=None):
+ globals['__builtins__'] = SafeBuiltins
+ if locals is None:
+ return eval(self.code, globals)
+ else:
+ return eval(self.code, globals)
+
+class CompiledProgram(object):
+ """A compiled expression
+ """
+
+ def __init__(self, source, filename='<string>'):
+ self.source = source
+ self.code = compile(source, filename, 'exec')
+
+ def exec_(self, globals, locals=None, output=None):
+ globals['__builtins__'] = SafeBuiltins
+ if output is not None:
+ globals['untrusted_output'] = output
+ exec self.code in globals, locals
Added: Zope3/trunk/src/zope/security/untrustedpython/interpreter.txt
===================================================================
--- Zope3/trunk/src/zope/security/untrustedpython/interpreter.txt 2004-07-28 19:35:56 UTC (rev 26818)
+++ Zope3/trunk/src/zope/security/untrustedpython/interpreter.txt 2004-07-28 19:37:35 UTC (rev 26819)
@@ -0,0 +1,112 @@
+Untrusted Python interpreter
+============================
+
+The interpreter module provides very basic Python interpreter
+support. It combined untrusted code compilation with safe builtins
+and an exec-like API. The exec_src function can be used to execute
+Python source:
+
+ >>> from zope.security.untrustedpython.interpreter import exec_src
+ >>> d = {}
+ >>> exec_src("x=1", d)
+ >>> d['x']
+ 1
+
+ >>> exec_src("x=getattr", d)
+
+
+Note that the safe builtins dictionary is inserted into the
+dictionary:
+
+ >>> from zope.security.untrustedpython.builtins import SafeBuiltins
+ >>> d['__builtins__'] == SafeBuiltins
+ True
+
+All of the non-basic items in the safe builtins are proxied:
+
+ >>> exec_src('str=str', d)
+ >>> from zope.security.proxy import Proxy
+ >>> type(d['str']) is Proxy
+ True
+
+Note that, while you can get to the safe `__builtins__`'s dictionary,
+you can't use the dictionary to mutate it:
+
+ >>> from zope.security.interfaces import ForbiddenAttribute
+
+ >>> try: exec_src('__builtins__.__dict__["x"] = 1', d)
+ ... except ForbiddenAttribute: print 'Forbidden!'
+ Forbidden!
+
+ >>> try: exec_src('del __builtins__.__dict__["str"]', d)
+ ... except ForbiddenAttribute: print 'Forbidden!'
+ Forbidden!
+
+ >>> try: exec_src('__builtins__.__dict__.update({"x": 1})', d)
+ ... except ForbiddenAttribute: print 'Forbidden!'
+ Forbidden!
+
+Because the untrusted code compiler is used, you can't use exec,
+raise, or try/except statements:
+
+ >>> exec_src("exec 'x=1'", d)
+ Traceback (most recent call last):
+ ...
+ SyntaxError: Line 1: exec statements are not supported
+
+Any attribute-access results will be proxied:
+
+ >>> exec_src("data = {}\nupdate = data.update\nupdate({'x': 'y'})", d)
+ >>> type(d['update']) is Proxy
+ True
+
+In this case, we were able to get to and use the update method because
+the data dictionary itself was created by the untrusted code and was,
+thus, unproxied.
+
+You can compile code yourself and call exec_code instead:
+
+ >>> from zope.security.untrustedpython.rcompile import compile
+ >>> code = compile('x=2', '<mycode>', 'exec')
+ >>> d = {}
+ >>> from zope.security.untrustedpython.interpreter import exec_code
+ >>> exec_code(code, d)
+ >>> d['x']
+ 2
+
+This is useful if you are going to be executing the same expression
+many times, as you can avoid the cost of repeated comilation.
+
+Compiled Programs
+-----------------
+
+A slightly higher-level interface is provided by compiled programs.
+These make it easier to safetly safe the results of compilation:
+
+ >>> from zope.security.untrustedpython.interpreter import CompiledProgram
+ >>> p = CompiledProgram('x=2')
+ >>> d = {}
+ >>> p.exec_(d)
+ >>> d['x']
+ 2
+
+When you execute a compiled program, you can supply an object with a
+write method to get print output:
+
+ >>> p = CompiledProgram('print "Hello world!"')
+ >>> import cStringIO
+ >>> f = cStringIO.StringIO()
+ >>> p.exec_({}, output=f)
+ >>> f.getvalue()
+ 'Hello world!\n'
+
+
+Compiled Expressions
+--------------------
+
+You can also precompile expressions:
+
+ >>> from zope.security.untrustedpython.interpreter import CompiledExpression
+ >>> p = CompiledExpression('x*2')
+ >>> p.eval({'x': 2})
+ 4
Property changes on: Zope3/trunk/src/zope/security/untrustedpython/interpreter.txt
___________________________________________________________________
Name: svn:eol-style
+ native
Copied: Zope3/trunk/src/zope/security/untrustedpython/rcompile.py (from rev 26795, Zope3/trunk/src/zope/restrictedpython/rcompile.py)
===================================================================
--- Zope3/trunk/src/zope/restrictedpython/rcompile.py 2004-07-27 15:15:58 UTC (rev 26795)
+++ Zope3/trunk/src/zope/security/untrustedpython/rcompile.py 2004-07-28 19:37:35 UTC (rev 26819)
@@ -0,0 +1,97 @@
+##############################################################################
+#
+# Copyright (c) 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (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.
+#
+##############################################################################
+"""compile() equivalent that produces restricted code.
+
+Only 'eval' is supported at this time.
+
+$Id$
+"""
+
+import compiler.pycodegen
+
+import RestrictedPython.RCompile
+from RestrictedPython.SelectCompiler import ast, OP_ASSIGN, OP_DELETE, OP_APPLY
+
+def compile(text, filename, mode):
+ if not isinstance(text, basestring):
+ raise TypeError("Compiled source must be string")
+ gen = RExpression(text, str(filename), mode)
+ gen.compile()
+ return gen.getCode()
+
+class RExpression(RestrictedPython.RCompile.RestrictedCompileMode):
+
+ CodeGeneratorClass = compiler.pycodegen.ExpressionCodeGenerator
+
+ def __init__(self, source, filename, mode = "eval"):
+ self.mode = mode
+ RestrictedPython.RCompile.RestrictedCompileMode.__init__(
+ self, source, filename)
+ self.rm = RestrictionMutator()
+
+
+# The security checks are performed by a set of six functions that
+# must be provided by the restricted environment.
+
+_getattr_name = ast.Name("getattr")
+
+
+class RestrictionMutator:
+
+ def __init__(self):
+ self.errors = []
+ self.warnings = []
+ 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 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.
+ """
+ node = walker.defaultVisitNode(node)
+ return ast.CallFunc(_getattr_name,
+ [node.expr, ast.Const(node.attrname)])
+
+ def visitExec(self, node, walker):
+ self.error(node, "exec statements are not supported")
+
+ def visitPrint(self, node, walker):
+ """Make sure prints always have a destination
+
+ If we get a print without a destination, make the default destination
+ untrusted_output.
+ """
+ node = walker.defaultVisitNode(node)
+ if node.dest is None:
+ node.dest = ast.Name('untrusted_output')
+ return node
+ visitPrintnl = visitPrint
+
+ def visitRaise(self, node, walker):
+ self.error(node, "raise statements are not supported")
+
+ def visitTryExcept(self, node, walker):
+ self.error(node, "try/except statements are not supported")
+
Copied: Zope3/trunk/src/zope/security/untrustedpython/rcompile.txt (from rev 26795, Zope3/trunk/src/zope/restrictedpython/README.txt)
===================================================================
--- Zope3/trunk/src/zope/restrictedpython/README.txt 2004-07-27 15:15:58 UTC (rev 26795)
+++ Zope3/trunk/src/zope/security/untrustedpython/rcompile.txt 2004-07-28 19:37:35 UTC (rev 26819)
@@ -0,0 +1,128 @@
+==================================
+Support for Restricted Python Code
+==================================
+
+This package provides a way to compile
+untrusted Python code so that it can be executed safely.
+
+This form of restricted Python assumes that security proxies will be
+used to protect assets. Given this, the only thing that actually
+needs to be done differently by the generated code is to:
+
+- Ensure that all attribute lookups go through a safe version of the getattr()
+ function that's been provided in the built-in functions used in the
+ execution environment.
+
+- Prevent exec statements. (Later, we could possibly make exec safe.)
+
+- Print statements always go to an output that is provided as a
+ global, rather than having an implicit sys.output.
+
+- Prevent try/except and raise statements. This is mainly because they
+ don't work properly in the presense of security proxies. Try/except
+ statements will be made to work in the future.
+
+No other special treatment is needed to support safe expression
+evaluation.
+
+The implementation makes use of the `RestrictedPython` package,
+originally written for Zope 2. There is a new AST re-writer in
+`zope.security.untrustedpython.rcompile` which performs the
+tree-transformation, and a top-level `compile()` function in
+`zope.security.untrustedpython.rcompile`; the later is what client
+applications are expected to use.
+
+The signature of the `compile()` function is very similar to that of
+Python's built-in `compile()` function::
+
+ compile(source, filename, mode)
+
+Using it is equally simple::
+
+ >>> from zope.security.untrustedpython.rcompile import compile
+
+ >>> code = compile("21 * 2", "<string>", "eval")
+ >>> eval(code)
+ 42
+
+What's interesting about the restricted code is that all attribute
+lookups go through the `getattr()` function. This is generally
+provided as a built-in function in the restricted environment::
+
+ >>> def mygetattr(object, name, default="Yahoo!"):
+ ... marker = []
+ ... print "Looking up", name
+ ... if getattr(object, name, marker) is marker:
+ ... return default
+ ... else:
+ ... return "Yeehaw!"
+
+ >>> import __builtin__
+ >>> builtins = __builtin__.__dict__.copy()
+ >>> builtins["getattr"] = mygetattr
+
+ >>> def reval(source):
+ ... code = compile(source, "README.txt", "eval")
+ ... globals = {"__builtins__": builtins}
+ ... return eval(code, globals, {})
+
+ >>> reval("(42).__class__")
+ Looking up __class__
+ 'Yeehaw!'
+ >>> reval("(42).not_really_there")
+ Looking up not_really_there
+ 'Yahoo!'
+ >>> reval("(42).foo.not_really_there")
+ Looking up foo
+ Looking up not_really_there
+ 'Yahoo!'
+
+This allows a `getattr()` to be used that ensures the result of
+evaluation is a security proxy.
+
+To compile code with statements, use exec or single:
+
+ >>> exec compile("x = 1", "<string>", "exec")
+ >>> x
+ 1
+
+Trying to compile exec, raise or try/except sattements gives
+syntax errors:
+
+ >>> compile("exec 'x = 2'", "<string>", "exec")
+ Traceback (most recent call last):
+ ...
+ SyntaxError: Line 1: exec statements are not supported
+
+ >>> compile("raise KeyError('x')", "<string>", "exec")
+ Traceback (most recent call last):
+ ...
+ SyntaxError: Line 1: raise statements are not supported
+
+ >>> compile("try: pass\nexcept: pass", "<string>", "exec")
+ Traceback (most recent call last):
+ ...
+ SyntaxError: Line 1: try/except statements are not supported
+
+Printing to an explicit writable is allowed:
+
+ >>> import StringIO
+ >>> f = StringIO.StringIO()
+ >>> code = compile("print >> f, 'hi',\nprint >> f, 'world'", '', 'exec')
+ >>> exec code in {'f': f}
+ >>> f.getvalue()
+ 'hi world\n'
+
+But if no output is specified, then output will be send to
+`untrusted_output`:
+
+ >>> code = compile("print 'hi',\nprint 'world'", '', 'exec')
+ >>> exec code in {}
+ Traceback (most recent call last):
+ ...
+ NameError: name 'untrusted_output' is not defined
+
+ >>> f = StringIO.StringIO()
+ >>> exec code in {'untrusted_output': f}
+
+
Added: Zope3/trunk/src/zope/security/untrustedpython/tests.py
===================================================================
--- Zope3/trunk/src/zope/security/untrustedpython/tests.py 2004-07-28 19:35:56 UTC (rev 26818)
+++ Zope3/trunk/src/zope/security/untrustedpython/tests.py 2004-07-28 19:37:35 UTC (rev 26819)
@@ -0,0 +1,31 @@
+##############################################################################
+#
+# Copyright (c) 2004 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.
+#
+##############################################################################
+"""Untrusted python tests
+
+$Id$
+"""
+import unittest
+from zope.testing import doctestunit
+
+def test_suite():
+ return unittest.TestSuite((
+ doctestunit.DocFileSuite('builtins.txt',
+ 'rcompile.txt',
+ 'interpreter.txt',
+ ),
+ ))
+
+if __name__ == '__main__':
+ unittest.main(defaultTest='test_suite')
+
Property changes on: Zope3/trunk/src/zope/security/untrustedpython/tests.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Modified: Zope3/trunk/src/zope/tales/pythonexpr.py
===================================================================
--- Zope3/trunk/src/zope/tales/pythonexpr.py 2004-07-28 19:35:56 UTC (rev 26818)
+++ Zope3/trunk/src/zope/tales/pythonexpr.py 2004-07-28 19:37:35 UTC (rev 26819)
@@ -18,7 +18,8 @@
class PythonExpr(object):
def __init__(self, name, expr, engine):
- text = ' '.join(expr.splitlines()).strip()
+ text = '\n'.join(expr.splitlines()) # normalize line endings
+ text = '(' + text + ')' # Put text in parens so newlines don't matter
self.text = text
try:
code = self._compile(text, '<string>')
@@ -36,6 +37,8 @@
names = {}
vars = econtext.vars
marker = self
+ if not isinstance(builtins, dict):
+ builtins = builtins.__dict__
for vname in self._varnames:
val = vars.get(vname, marker)
if val is not marker:
More information about the Zope3-Checkins
mailing list