2007-08-15 11:28:01 -03:00
|
|
|
:mod:`rexec` --- Restricted execution framework
|
|
|
|
===============================================
|
|
|
|
|
|
|
|
.. module:: rexec
|
|
|
|
:synopsis: Basic restricted execution framework.
|
2008-05-09 23:27:04 -03:00
|
|
|
:deprecated:
|
2009-01-03 16:55:06 -04:00
|
|
|
|
2008-05-09 23:27:04 -03:00
|
|
|
.. deprecated:: 2.6
|
2012-05-03 13:21:40 -03:00
|
|
|
The :mod:`rexec` module has been removed in Python 3.
|
2007-08-15 11:28:01 -03:00
|
|
|
|
|
|
|
.. versionchanged:: 2.3
|
|
|
|
Disabled module.
|
|
|
|
|
|
|
|
.. warning::
|
|
|
|
|
|
|
|
The documentation has been left in place to help in reading old code that uses
|
|
|
|
the module.
|
|
|
|
|
|
|
|
This module contains the :class:`RExec` class, which supports :meth:`r_eval`,
|
|
|
|
:meth:`r_execfile`, :meth:`r_exec`, and :meth:`r_import` methods, which are
|
|
|
|
restricted versions of the standard Python functions :meth:`eval`,
|
|
|
|
:meth:`execfile` and the :keyword:`exec` and :keyword:`import` statements. Code
|
|
|
|
executed in this restricted environment will only have access to modules and
|
|
|
|
functions that are deemed safe; you can subclass :class:`RExec` to add or remove
|
|
|
|
capabilities as desired.
|
|
|
|
|
|
|
|
.. warning::
|
|
|
|
|
|
|
|
While the :mod:`rexec` module is designed to perform as described below, it does
|
|
|
|
have a few known vulnerabilities which could be exploited by carefully written
|
|
|
|
code. Thus it should not be relied upon in situations requiring "production
|
|
|
|
ready" security. In such situations, execution via sub-processes or very
|
|
|
|
careful "cleansing" of both code and data to be processed may be necessary.
|
|
|
|
Alternatively, help in patching known :mod:`rexec` vulnerabilities would be
|
|
|
|
welcomed.
|
|
|
|
|
|
|
|
.. note::
|
|
|
|
|
|
|
|
The :class:`RExec` class can prevent code from performing unsafe operations like
|
|
|
|
reading or writing disk files, or using TCP/IP sockets. However, it does not
|
|
|
|
protect against code using extremely large amounts of memory or processor time.
|
|
|
|
|
|
|
|
|
|
|
|
.. class:: RExec([hooks[, verbose]])
|
|
|
|
|
|
|
|
Returns an instance of the :class:`RExec` class.
|
|
|
|
|
|
|
|
*hooks* is an instance of the :class:`RHooks` class or a subclass of it. If it
|
|
|
|
is omitted or ``None``, the default :class:`RHooks` class is instantiated.
|
|
|
|
Whenever the :mod:`rexec` module searches for a module (even a built-in one) or
|
|
|
|
reads a module's code, it doesn't actually go out to the file system itself.
|
|
|
|
Rather, it calls methods of an :class:`RHooks` instance that was passed to or
|
|
|
|
created by its constructor. (Actually, the :class:`RExec` object doesn't make
|
|
|
|
these calls --- they are made by a module loader object that's part of the
|
|
|
|
:class:`RExec` object. This allows another level of flexibility, which can be
|
|
|
|
useful when changing the mechanics of :keyword:`import` within the restricted
|
|
|
|
environment.)
|
|
|
|
|
|
|
|
By providing an alternate :class:`RHooks` object, we can control the file system
|
|
|
|
accesses made to import a module, without changing the actual algorithm that
|
|
|
|
controls the order in which those accesses are made. For instance, we could
|
|
|
|
substitute an :class:`RHooks` object that passes all filesystem requests to a
|
|
|
|
file server elsewhere, via some RPC mechanism such as ILU. Grail's applet
|
|
|
|
loader uses this to support importing applets from a URL for a directory.
|
|
|
|
|
|
|
|
If *verbose* is true, additional debugging output may be sent to standard
|
|
|
|
output.
|
|
|
|
|
|
|
|
It is important to be aware that code running in a restricted environment can
|
|
|
|
still call the :func:`sys.exit` function. To disallow restricted code from
|
|
|
|
exiting the interpreter, always protect calls that cause restricted code to run
|
|
|
|
with a :keyword:`try`/:keyword:`except` statement that catches the
|
|
|
|
:exc:`SystemExit` exception. Removing the :func:`sys.exit` function from the
|
|
|
|
restricted environment is not sufficient --- the restricted code could still use
|
|
|
|
``raise SystemExit``. Removing :exc:`SystemExit` is not a reasonable option;
|
|
|
|
some library code makes use of this and would break were it not available.
|
|
|
|
|
|
|
|
|
|
|
|
.. seealso::
|
|
|
|
|
|
|
|
`Grail Home Page <http://grail.sourceforge.net/>`_
|
|
|
|
Grail is a Web browser written entirely in Python. It uses the :mod:`rexec`
|
|
|
|
module as a foundation for supporting Python applets, and can be used as an
|
|
|
|
example usage of this module.
|
|
|
|
|
|
|
|
|
|
|
|
.. _rexec-objects:
|
|
|
|
|
|
|
|
RExec Objects
|
|
|
|
-------------
|
|
|
|
|
|
|
|
:class:`RExec` instances support the following methods:
|
|
|
|
|
|
|
|
|
|
|
|
.. method:: RExec.r_eval(code)
|
|
|
|
|
|
|
|
*code* must either be a string containing a Python expression, or a compiled
|
|
|
|
code object, which will be evaluated in the restricted environment's
|
|
|
|
:mod:`__main__` module. The value of the expression or code object will be
|
|
|
|
returned.
|
|
|
|
|
|
|
|
|
|
|
|
.. method:: RExec.r_exec(code)
|
|
|
|
|
|
|
|
*code* must either be a string containing one or more lines of Python code, or a
|
|
|
|
compiled code object, which will be executed in the restricted environment's
|
|
|
|
:mod:`__main__` module.
|
|
|
|
|
|
|
|
|
|
|
|
.. method:: RExec.r_execfile(filename)
|
|
|
|
|
|
|
|
Execute the Python code contained in the file *filename* in the restricted
|
|
|
|
environment's :mod:`__main__` module.
|
|
|
|
|
|
|
|
Methods whose names begin with ``s_`` are similar to the functions beginning
|
|
|
|
with ``r_``, but the code will be granted access to restricted versions of the
|
|
|
|
standard I/O streams ``sys.stdin``, ``sys.stderr``, and ``sys.stdout``.
|
|
|
|
|
|
|
|
|
|
|
|
.. method:: RExec.s_eval(code)
|
|
|
|
|
|
|
|
*code* must be a string containing a Python expression, which will be evaluated
|
|
|
|
in the restricted environment.
|
|
|
|
|
|
|
|
|
|
|
|
.. method:: RExec.s_exec(code)
|
|
|
|
|
|
|
|
*code* must be a string containing one or more lines of Python code, which will
|
|
|
|
be executed in the restricted environment.
|
|
|
|
|
|
|
|
|
|
|
|
.. method:: RExec.s_execfile(code)
|
|
|
|
|
|
|
|
Execute the Python code contained in the file *filename* in the restricted
|
|
|
|
environment.
|
|
|
|
|
|
|
|
:class:`RExec` objects must also support various methods which will be
|
|
|
|
implicitly called by code executing in the restricted environment. Overriding
|
|
|
|
these methods in a subclass is used to change the policies enforced by a
|
|
|
|
restricted environment.
|
|
|
|
|
|
|
|
|
|
|
|
.. method:: RExec.r_import(modulename[, globals[, locals[, fromlist]]])
|
|
|
|
|
|
|
|
Import the module *modulename*, raising an :exc:`ImportError` exception if the
|
|
|
|
module is considered unsafe.
|
|
|
|
|
|
|
|
|
|
|
|
.. method:: RExec.r_open(filename[, mode[, bufsize]])
|
|
|
|
|
|
|
|
Method called when :func:`open` is called in the restricted environment. The
|
|
|
|
arguments are identical to those of :func:`open`, and a file object (or a class
|
|
|
|
instance compatible with file objects) should be returned. :class:`RExec`'s
|
|
|
|
default behaviour is allow opening any file for reading, but forbidding any
|
|
|
|
attempt to write a file. See the example below for an implementation of a less
|
|
|
|
restrictive :meth:`r_open`.
|
|
|
|
|
|
|
|
|
|
|
|
.. method:: RExec.r_reload(module)
|
|
|
|
|
|
|
|
Reload the module object *module*, re-parsing and re-initializing it.
|
|
|
|
|
|
|
|
|
|
|
|
.. method:: RExec.r_unload(module)
|
|
|
|
|
|
|
|
Unload the module object *module* (remove it from the restricted environment's
|
|
|
|
``sys.modules`` dictionary).
|
|
|
|
|
|
|
|
And their equivalents with access to restricted standard I/O streams:
|
|
|
|
|
|
|
|
|
|
|
|
.. method:: RExec.s_import(modulename[, globals[, locals[, fromlist]]])
|
|
|
|
|
|
|
|
Import the module *modulename*, raising an :exc:`ImportError` exception if the
|
|
|
|
module is considered unsafe.
|
|
|
|
|
|
|
|
|
|
|
|
.. method:: RExec.s_reload(module)
|
|
|
|
|
|
|
|
Reload the module object *module*, re-parsing and re-initializing it.
|
|
|
|
|
|
|
|
|
|
|
|
.. method:: RExec.s_unload(module)
|
|
|
|
|
|
|
|
Unload the module object *module*.
|
|
|
|
|
2007-12-29 06:57:00 -04:00
|
|
|
.. XXX what are the semantics of this?
|
2007-08-15 11:28:01 -03:00
|
|
|
|
|
|
|
|
|
|
|
.. _rexec-extension:
|
|
|
|
|
|
|
|
Defining restricted environments
|
|
|
|
--------------------------------
|
|
|
|
|
|
|
|
The :class:`RExec` class has the following class attributes, which are used by
|
|
|
|
the :meth:`__init__` method. Changing them on an existing instance won't have
|
|
|
|
any effect; instead, create a subclass of :class:`RExec` and assign them new
|
|
|
|
values in the class definition. Instances of the new class will then use those
|
|
|
|
new values. All these attributes are tuples of strings.
|
|
|
|
|
|
|
|
|
|
|
|
.. attribute:: RExec.nok_builtin_names
|
|
|
|
|
|
|
|
Contains the names of built-in functions which will *not* be available to
|
|
|
|
programs running in the restricted environment. The value for :class:`RExec` is
|
|
|
|
``('open', 'reload', '__import__')``. (This gives the exceptions, because by far
|
|
|
|
the majority of built-in functions are harmless. A subclass that wants to
|
|
|
|
override this variable should probably start with the value from the base class
|
|
|
|
and concatenate additional forbidden functions --- when new dangerous built-in
|
|
|
|
functions are added to Python, they will also be added to this module.)
|
|
|
|
|
|
|
|
|
|
|
|
.. attribute:: RExec.ok_builtin_modules
|
|
|
|
|
|
|
|
Contains the names of built-in modules which can be safely imported. The value
|
|
|
|
for :class:`RExec` is ``('audioop', 'array', 'binascii', 'cmath', 'errno',
|
|
|
|
'imageop', 'marshal', 'math', 'md5', 'operator', 'parser', 'regex', 'select',
|
|
|
|
'sha', '_sre', 'strop', 'struct', 'time')``. A similar remark about overriding
|
|
|
|
this variable applies --- use the value from the base class as a starting point.
|
|
|
|
|
|
|
|
|
|
|
|
.. attribute:: RExec.ok_path
|
|
|
|
|
|
|
|
Contains the directories which will be searched when an :keyword:`import` is
|
|
|
|
performed in the restricted environment. The value for :class:`RExec` is the
|
|
|
|
same as ``sys.path`` (at the time the module is loaded) for unrestricted code.
|
|
|
|
|
|
|
|
|
|
|
|
.. attribute:: RExec.ok_posix_names
|
|
|
|
|
|
|
|
Contains the names of the functions in the :mod:`os` module which will be
|
|
|
|
available to programs running in the restricted environment. The value for
|
|
|
|
:class:`RExec` is ``('error', 'fstat', 'listdir', 'lstat', 'readlink', 'stat',
|
|
|
|
'times', 'uname', 'getpid', 'getppid', 'getcwd', 'getuid', 'getgid', 'geteuid',
|
|
|
|
'getegid')``.
|
|
|
|
|
2007-12-29 06:57:00 -04:00
|
|
|
.. Should this be called ok_os_names?
|
2007-08-15 11:28:01 -03:00
|
|
|
|
|
|
|
|
|
|
|
.. attribute:: RExec.ok_sys_names
|
|
|
|
|
|
|
|
Contains the names of the functions and variables in the :mod:`sys` module which
|
|
|
|
will be available to programs running in the restricted environment. The value
|
|
|
|
for :class:`RExec` is ``('ps1', 'ps2', 'copyright', 'version', 'platform',
|
|
|
|
'exit', 'maxint')``.
|
|
|
|
|
|
|
|
|
|
|
|
.. attribute:: RExec.ok_file_types
|
|
|
|
|
|
|
|
Contains the file types from which modules are allowed to be loaded. Each file
|
|
|
|
type is an integer constant defined in the :mod:`imp` module. The meaningful
|
|
|
|
values are :const:`PY_SOURCE`, :const:`PY_COMPILED`, and :const:`C_EXTENSION`.
|
|
|
|
The value for :class:`RExec` is ``(C_EXTENSION, PY_SOURCE)``. Adding
|
|
|
|
:const:`PY_COMPILED` in subclasses is not recommended; an attacker could exit
|
|
|
|
the restricted execution mode by putting a forged byte-compiled file
|
|
|
|
(:file:`.pyc`) anywhere in your file system, for example by writing it to
|
|
|
|
:file:`/tmp` or uploading it to the :file:`/incoming` directory of your public
|
|
|
|
FTP server.
|
|
|
|
|
|
|
|
|
|
|
|
An example
|
|
|
|
----------
|
|
|
|
|
|
|
|
Let us say that we want a slightly more relaxed policy than the standard
|
|
|
|
:class:`RExec` class. For example, if we're willing to allow files in
|
|
|
|
:file:`/tmp` to be written, we can subclass the :class:`RExec` class::
|
|
|
|
|
|
|
|
class TmpWriterRExec(rexec.RExec):
|
|
|
|
def r_open(self, file, mode='r', buf=-1):
|
|
|
|
if mode in ('r', 'rb'):
|
|
|
|
pass
|
|
|
|
elif mode in ('w', 'wb', 'a', 'ab'):
|
|
|
|
# check filename : must begin with /tmp/
|
2009-01-03 16:55:06 -04:00
|
|
|
if file[:5]!='/tmp/':
|
2009-06-03 04:25:35 -03:00
|
|
|
raise IOError("can't write outside /tmp")
|
2007-08-15 11:28:01 -03:00
|
|
|
elif (string.find(file, '/../') >= 0 or
|
|
|
|
file[:3] == '../' or file[-3:] == '/..'):
|
2009-06-03 04:25:35 -03:00
|
|
|
raise IOError("'..' in filename forbidden")
|
|
|
|
else: raise IOError("Illegal open() mode")
|
2007-08-15 11:28:01 -03:00
|
|
|
return open(file, mode, buf)
|
|
|
|
|
|
|
|
Notice that the above code will occasionally forbid a perfectly valid filename;
|
|
|
|
for example, code in the restricted environment won't be able to open a file
|
|
|
|
called :file:`/tmp/foo/../bar`. To fix this, the :meth:`r_open` method would
|
|
|
|
have to simplify the filename to :file:`/tmp/bar`, which would require splitting
|
|
|
|
apart the filename and performing various operations on it. In cases where
|
|
|
|
security is at stake, it may be preferable to write simple code which is
|
|
|
|
sometimes overly restrictive, instead of more general code that is also more
|
|
|
|
complex and may harbor a subtle security hole.
|