[2] | 1 | :mod:`rexec` --- Restricted execution framework
|
---|
| 2 | ===============================================
|
---|
| 3 |
|
---|
| 4 | .. module:: rexec
|
---|
| 5 | :synopsis: Basic restricted execution framework.
|
---|
| 6 | :deprecated:
|
---|
| 7 |
|
---|
| 8 | .. deprecated:: 2.6
|
---|
[391] | 9 | The :mod:`rexec` module has been removed in Python 3.
|
---|
[2] | 10 |
|
---|
| 11 | .. versionchanged:: 2.3
|
---|
| 12 | Disabled module.
|
---|
| 13 |
|
---|
| 14 | .. warning::
|
---|
| 15 |
|
---|
| 16 | The documentation has been left in place to help in reading old code that uses
|
---|
| 17 | the module.
|
---|
| 18 |
|
---|
| 19 | This module contains the :class:`RExec` class, which supports :meth:`r_eval`,
|
---|
| 20 | :meth:`r_execfile`, :meth:`r_exec`, and :meth:`r_import` methods, which are
|
---|
| 21 | restricted versions of the standard Python functions :meth:`eval`,
|
---|
| 22 | :meth:`execfile` and the :keyword:`exec` and :keyword:`import` statements. Code
|
---|
| 23 | executed in this restricted environment will only have access to modules and
|
---|
| 24 | functions that are deemed safe; you can subclass :class:`RExec` to add or remove
|
---|
| 25 | capabilities as desired.
|
---|
| 26 |
|
---|
| 27 | .. warning::
|
---|
| 28 |
|
---|
| 29 | While the :mod:`rexec` module is designed to perform as described below, it does
|
---|
| 30 | have a few known vulnerabilities which could be exploited by carefully written
|
---|
| 31 | code. Thus it should not be relied upon in situations requiring "production
|
---|
| 32 | ready" security. In such situations, execution via sub-processes or very
|
---|
| 33 | careful "cleansing" of both code and data to be processed may be necessary.
|
---|
| 34 | Alternatively, help in patching known :mod:`rexec` vulnerabilities would be
|
---|
| 35 | welcomed.
|
---|
| 36 |
|
---|
| 37 | .. note::
|
---|
| 38 |
|
---|
| 39 | The :class:`RExec` class can prevent code from performing unsafe operations like
|
---|
| 40 | reading or writing disk files, or using TCP/IP sockets. However, it does not
|
---|
| 41 | protect against code using extremely large amounts of memory or processor time.
|
---|
| 42 |
|
---|
| 43 |
|
---|
| 44 | .. class:: RExec([hooks[, verbose]])
|
---|
| 45 |
|
---|
| 46 | Returns an instance of the :class:`RExec` class.
|
---|
| 47 |
|
---|
| 48 | *hooks* is an instance of the :class:`RHooks` class or a subclass of it. If it
|
---|
| 49 | is omitted or ``None``, the default :class:`RHooks` class is instantiated.
|
---|
| 50 | Whenever the :mod:`rexec` module searches for a module (even a built-in one) or
|
---|
| 51 | reads a module's code, it doesn't actually go out to the file system itself.
|
---|
| 52 | Rather, it calls methods of an :class:`RHooks` instance that was passed to or
|
---|
| 53 | created by its constructor. (Actually, the :class:`RExec` object doesn't make
|
---|
| 54 | these calls --- they are made by a module loader object that's part of the
|
---|
| 55 | :class:`RExec` object. This allows another level of flexibility, which can be
|
---|
| 56 | useful when changing the mechanics of :keyword:`import` within the restricted
|
---|
| 57 | environment.)
|
---|
| 58 |
|
---|
| 59 | By providing an alternate :class:`RHooks` object, we can control the file system
|
---|
| 60 | accesses made to import a module, without changing the actual algorithm that
|
---|
| 61 | controls the order in which those accesses are made. For instance, we could
|
---|
| 62 | substitute an :class:`RHooks` object that passes all filesystem requests to a
|
---|
| 63 | file server elsewhere, via some RPC mechanism such as ILU. Grail's applet
|
---|
| 64 | loader uses this to support importing applets from a URL for a directory.
|
---|
| 65 |
|
---|
| 66 | If *verbose* is true, additional debugging output may be sent to standard
|
---|
| 67 | output.
|
---|
| 68 |
|
---|
| 69 | It is important to be aware that code running in a restricted environment can
|
---|
| 70 | still call the :func:`sys.exit` function. To disallow restricted code from
|
---|
| 71 | exiting the interpreter, always protect calls that cause restricted code to run
|
---|
| 72 | with a :keyword:`try`/:keyword:`except` statement that catches the
|
---|
| 73 | :exc:`SystemExit` exception. Removing the :func:`sys.exit` function from the
|
---|
| 74 | restricted environment is not sufficient --- the restricted code could still use
|
---|
| 75 | ``raise SystemExit``. Removing :exc:`SystemExit` is not a reasonable option;
|
---|
| 76 | some library code makes use of this and would break were it not available.
|
---|
| 77 |
|
---|
| 78 |
|
---|
| 79 | .. seealso::
|
---|
| 80 |
|
---|
| 81 | `Grail Home Page <http://grail.sourceforge.net/>`_
|
---|
| 82 | Grail is a Web browser written entirely in Python. It uses the :mod:`rexec`
|
---|
| 83 | module as a foundation for supporting Python applets, and can be used as an
|
---|
| 84 | example usage of this module.
|
---|
| 85 |
|
---|
| 86 |
|
---|
| 87 | .. _rexec-objects:
|
---|
| 88 |
|
---|
| 89 | RExec Objects
|
---|
| 90 | -------------
|
---|
| 91 |
|
---|
| 92 | :class:`RExec` instances support the following methods:
|
---|
| 93 |
|
---|
| 94 |
|
---|
| 95 | .. method:: RExec.r_eval(code)
|
---|
| 96 |
|
---|
| 97 | *code* must either be a string containing a Python expression, or a compiled
|
---|
| 98 | code object, which will be evaluated in the restricted environment's
|
---|
| 99 | :mod:`__main__` module. The value of the expression or code object will be
|
---|
| 100 | returned.
|
---|
| 101 |
|
---|
| 102 |
|
---|
| 103 | .. method:: RExec.r_exec(code)
|
---|
| 104 |
|
---|
| 105 | *code* must either be a string containing one or more lines of Python code, or a
|
---|
| 106 | compiled code object, which will be executed in the restricted environment's
|
---|
| 107 | :mod:`__main__` module.
|
---|
| 108 |
|
---|
| 109 |
|
---|
| 110 | .. method:: RExec.r_execfile(filename)
|
---|
| 111 |
|
---|
| 112 | Execute the Python code contained in the file *filename* in the restricted
|
---|
| 113 | environment's :mod:`__main__` module.
|
---|
| 114 |
|
---|
| 115 | Methods whose names begin with ``s_`` are similar to the functions beginning
|
---|
| 116 | with ``r_``, but the code will be granted access to restricted versions of the
|
---|
| 117 | standard I/O streams ``sys.stdin``, ``sys.stderr``, and ``sys.stdout``.
|
---|
| 118 |
|
---|
| 119 |
|
---|
| 120 | .. method:: RExec.s_eval(code)
|
---|
| 121 |
|
---|
| 122 | *code* must be a string containing a Python expression, which will be evaluated
|
---|
| 123 | in the restricted environment.
|
---|
| 124 |
|
---|
| 125 |
|
---|
| 126 | .. method:: RExec.s_exec(code)
|
---|
| 127 |
|
---|
| 128 | *code* must be a string containing one or more lines of Python code, which will
|
---|
| 129 | be executed in the restricted environment.
|
---|
| 130 |
|
---|
| 131 |
|
---|
| 132 | .. method:: RExec.s_execfile(code)
|
---|
| 133 |
|
---|
| 134 | Execute the Python code contained in the file *filename* in the restricted
|
---|
| 135 | environment.
|
---|
| 136 |
|
---|
| 137 | :class:`RExec` objects must also support various methods which will be
|
---|
| 138 | implicitly called by code executing in the restricted environment. Overriding
|
---|
| 139 | these methods in a subclass is used to change the policies enforced by a
|
---|
| 140 | restricted environment.
|
---|
| 141 |
|
---|
| 142 |
|
---|
| 143 | .. method:: RExec.r_import(modulename[, globals[, locals[, fromlist]]])
|
---|
| 144 |
|
---|
| 145 | Import the module *modulename*, raising an :exc:`ImportError` exception if the
|
---|
| 146 | module is considered unsafe.
|
---|
| 147 |
|
---|
| 148 |
|
---|
| 149 | .. method:: RExec.r_open(filename[, mode[, bufsize]])
|
---|
| 150 |
|
---|
| 151 | Method called when :func:`open` is called in the restricted environment. The
|
---|
| 152 | arguments are identical to those of :func:`open`, and a file object (or a class
|
---|
| 153 | instance compatible with file objects) should be returned. :class:`RExec`'s
|
---|
| 154 | default behaviour is allow opening any file for reading, but forbidding any
|
---|
| 155 | attempt to write a file. See the example below for an implementation of a less
|
---|
| 156 | restrictive :meth:`r_open`.
|
---|
| 157 |
|
---|
| 158 |
|
---|
| 159 | .. method:: RExec.r_reload(module)
|
---|
| 160 |
|
---|
| 161 | Reload the module object *module*, re-parsing and re-initializing it.
|
---|
| 162 |
|
---|
| 163 |
|
---|
| 164 | .. method:: RExec.r_unload(module)
|
---|
| 165 |
|
---|
| 166 | Unload the module object *module* (remove it from the restricted environment's
|
---|
| 167 | ``sys.modules`` dictionary).
|
---|
| 168 |
|
---|
| 169 | And their equivalents with access to restricted standard I/O streams:
|
---|
| 170 |
|
---|
| 171 |
|
---|
| 172 | .. method:: RExec.s_import(modulename[, globals[, locals[, fromlist]]])
|
---|
| 173 |
|
---|
| 174 | Import the module *modulename*, raising an :exc:`ImportError` exception if the
|
---|
| 175 | module is considered unsafe.
|
---|
| 176 |
|
---|
| 177 |
|
---|
| 178 | .. method:: RExec.s_reload(module)
|
---|
| 179 |
|
---|
| 180 | Reload the module object *module*, re-parsing and re-initializing it.
|
---|
| 181 |
|
---|
| 182 |
|
---|
| 183 | .. method:: RExec.s_unload(module)
|
---|
| 184 |
|
---|
| 185 | Unload the module object *module*.
|
---|
| 186 |
|
---|
| 187 | .. XXX what are the semantics of this?
|
---|
| 188 |
|
---|
| 189 |
|
---|
| 190 | .. _rexec-extension:
|
---|
| 191 |
|
---|
| 192 | Defining restricted environments
|
---|
| 193 | --------------------------------
|
---|
| 194 |
|
---|
| 195 | The :class:`RExec` class has the following class attributes, which are used by
|
---|
| 196 | the :meth:`__init__` method. Changing them on an existing instance won't have
|
---|
| 197 | any effect; instead, create a subclass of :class:`RExec` and assign them new
|
---|
| 198 | values in the class definition. Instances of the new class will then use those
|
---|
| 199 | new values. All these attributes are tuples of strings.
|
---|
| 200 |
|
---|
| 201 |
|
---|
| 202 | .. attribute:: RExec.nok_builtin_names
|
---|
| 203 |
|
---|
| 204 | Contains the names of built-in functions which will *not* be available to
|
---|
| 205 | programs running in the restricted environment. The value for :class:`RExec` is
|
---|
| 206 | ``('open', 'reload', '__import__')``. (This gives the exceptions, because by far
|
---|
| 207 | the majority of built-in functions are harmless. A subclass that wants to
|
---|
| 208 | override this variable should probably start with the value from the base class
|
---|
| 209 | and concatenate additional forbidden functions --- when new dangerous built-in
|
---|
| 210 | functions are added to Python, they will also be added to this module.)
|
---|
| 211 |
|
---|
| 212 |
|
---|
| 213 | .. attribute:: RExec.ok_builtin_modules
|
---|
| 214 |
|
---|
| 215 | Contains the names of built-in modules which can be safely imported. The value
|
---|
| 216 | for :class:`RExec` is ``('audioop', 'array', 'binascii', 'cmath', 'errno',
|
---|
| 217 | 'imageop', 'marshal', 'math', 'md5', 'operator', 'parser', 'regex', 'select',
|
---|
| 218 | 'sha', '_sre', 'strop', 'struct', 'time')``. A similar remark about overriding
|
---|
| 219 | this variable applies --- use the value from the base class as a starting point.
|
---|
| 220 |
|
---|
| 221 |
|
---|
| 222 | .. attribute:: RExec.ok_path
|
---|
| 223 |
|
---|
| 224 | Contains the directories which will be searched when an :keyword:`import` is
|
---|
| 225 | performed in the restricted environment. The value for :class:`RExec` is the
|
---|
| 226 | same as ``sys.path`` (at the time the module is loaded) for unrestricted code.
|
---|
| 227 |
|
---|
| 228 |
|
---|
| 229 | .. attribute:: RExec.ok_posix_names
|
---|
| 230 |
|
---|
| 231 | Contains the names of the functions in the :mod:`os` module which will be
|
---|
| 232 | available to programs running in the restricted environment. The value for
|
---|
| 233 | :class:`RExec` is ``('error', 'fstat', 'listdir', 'lstat', 'readlink', 'stat',
|
---|
| 234 | 'times', 'uname', 'getpid', 'getppid', 'getcwd', 'getuid', 'getgid', 'geteuid',
|
---|
| 235 | 'getegid')``.
|
---|
| 236 |
|
---|
| 237 | .. Should this be called ok_os_names?
|
---|
| 238 |
|
---|
| 239 |
|
---|
| 240 | .. attribute:: RExec.ok_sys_names
|
---|
| 241 |
|
---|
| 242 | Contains the names of the functions and variables in the :mod:`sys` module which
|
---|
| 243 | will be available to programs running in the restricted environment. The value
|
---|
| 244 | for :class:`RExec` is ``('ps1', 'ps2', 'copyright', 'version', 'platform',
|
---|
| 245 | 'exit', 'maxint')``.
|
---|
| 246 |
|
---|
| 247 |
|
---|
| 248 | .. attribute:: RExec.ok_file_types
|
---|
| 249 |
|
---|
| 250 | Contains the file types from which modules are allowed to be loaded. Each file
|
---|
| 251 | type is an integer constant defined in the :mod:`imp` module. The meaningful
|
---|
| 252 | values are :const:`PY_SOURCE`, :const:`PY_COMPILED`, and :const:`C_EXTENSION`.
|
---|
| 253 | The value for :class:`RExec` is ``(C_EXTENSION, PY_SOURCE)``. Adding
|
---|
| 254 | :const:`PY_COMPILED` in subclasses is not recommended; an attacker could exit
|
---|
| 255 | the restricted execution mode by putting a forged byte-compiled file
|
---|
| 256 | (:file:`.pyc`) anywhere in your file system, for example by writing it to
|
---|
| 257 | :file:`/tmp` or uploading it to the :file:`/incoming` directory of your public
|
---|
| 258 | FTP server.
|
---|
| 259 |
|
---|
| 260 |
|
---|
| 261 | An example
|
---|
| 262 | ----------
|
---|
| 263 |
|
---|
| 264 | Let us say that we want a slightly more relaxed policy than the standard
|
---|
| 265 | :class:`RExec` class. For example, if we're willing to allow files in
|
---|
| 266 | :file:`/tmp` to be written, we can subclass the :class:`RExec` class::
|
---|
| 267 |
|
---|
| 268 | class TmpWriterRExec(rexec.RExec):
|
---|
| 269 | def r_open(self, file, mode='r', buf=-1):
|
---|
| 270 | if mode in ('r', 'rb'):
|
---|
| 271 | pass
|
---|
| 272 | elif mode in ('w', 'wb', 'a', 'ab'):
|
---|
| 273 | # check filename : must begin with /tmp/
|
---|
| 274 | if file[:5]!='/tmp/':
|
---|
| 275 | raise IOError("can't write outside /tmp")
|
---|
| 276 | elif (string.find(file, '/../') >= 0 or
|
---|
| 277 | file[:3] == '../' or file[-3:] == '/..'):
|
---|
| 278 | raise IOError("'..' in filename forbidden")
|
---|
| 279 | else: raise IOError("Illegal open() mode")
|
---|
| 280 | return open(file, mode, buf)
|
---|
| 281 |
|
---|
| 282 | Notice that the above code will occasionally forbid a perfectly valid filename;
|
---|
| 283 | for example, code in the restricted environment won't be able to open a file
|
---|
| 284 | called :file:`/tmp/foo/../bar`. To fix this, the :meth:`r_open` method would
|
---|
| 285 | have to simplify the filename to :file:`/tmp/bar`, which would require splitting
|
---|
| 286 | apart the filename and performing various operations on it. In cases where
|
---|
| 287 | security is at stake, it may be preferable to write simple code which is
|
---|
| 288 | sometimes overly restrictive, instead of more general code that is also more
|
---|
| 289 | complex and may harbor a subtle security hole.
|
---|