Linux/Unix > Python Essential Reference, Second Edition > A. The Python Library > Restricted Execution | See All Titles

< BACK | Make Note | Bookmark | CONTINUE >

155117184014003188065099048180054212144238241179195140058238111161105081080058112185106252

### Restricted Execution

Normally, a Python program has complete access to the machine on which it runs. In particular, it can open files and network connections, and perform other potentially sensitive operations. In certain applications, however, this is undesirable—especially in Internet applications, in which a program may be subject to attackers or when code from an untrusted source is executed.

To provide some measure of safety, Python provides support for restricted execution. Restricted execution is based on the notion of separating trusted and untrusted code. In particular, a program running in trusted mode (a supervisor) can create an execution environment (or sandbox) in which untrusted code can be executed with limited privileges. The capabilities of the untrusted code are tightly controlled by the supervisor, which can restrict the set of objects that can be accessed as well as the behavior of individual functions.

Python’s restricted execution mode is implemented by playing a number of tricks with dictionaries, namespaces, and the environment in which untrusted code executes. As a result, untrusted code uses the same set of function names and modules that would be used in a normal program (as opposed to separate, secure APIs). The only difference is that certain modules and built-in functions may be unavailable (or redefined to secure versions).

Internally, the interpreter determines whether a piece of code is restricted by looking at the identity of the `_ _builtins_ _` object in its global namespace. If it’s the same as the standard `_ _builtin_ _` module, the code is unrestricted. Otherwise, it’s restricted. When running in restricted mode, the interpreter imposes a number of further restrictions that are designed to prevent untrusted code from becoming privileged:

- The `_ _dict_ _` attribute of classes and instances is not accessible.

- The `func_globals` attribute of functions is not accessible.

These restrictions are imposed to prevent untrusted code from altering its global namespace (which is used by the supervisor to restrict the set of objects that are accessible).

Finally, it should be noted that although the Python restricted execution environment prevents access to critical operations, it doesn’t prevent denial-of-service attacks, in which an untrusted program might try to exhaust memory or use an unlimited amount of CPU time.

Restricted execution is supported through the use of two modules: `rexec` and `Bastion`. `rexec` restricts the environment in which code runs. `Bastion` restricts the access that untrusted code has to objects created by the supervisor.

#### `rexec`

The `rexec` module is used to run code in a restricted environment. The environment is encapsulated in a class `RExec` that contains attributes specifying the capabilities for the code to execute.

    RExec([hooks [, verbose]])

Creates an instance of the `RExec` class that represents a restricted environment. `hooks` is an instance of a class used to implement nonstandard methods for importing modules and is not described here. `verbose` is a flag that causes some debugging output to be printed to standard output.

The following class variables are used by the `_ _init_ _()` method when an instance of the `RExec` class is created. Changing them on an instance has no effect, so it’s better to create a subclass of `RExec` that modifies their values.

    RExec.nok_builtin_names

A tuple of strings containing the names of built-in functions not available to restricted programs. The default value is (`'open', 'reload', '_ _import_ _'`).

    RExec.ok_builtin_modules

A tuple of strings containing the names of built-in modules that can be safely imported. The default value is as follows:

    ('audioop', 'array', 'binascii', 'cmath', 'errno', 'imageop', 'marshal',
    'math', 'md5', 'operator', 'parser', 'pcre', 'regex', 'rotor',
    'select', 'strop', 'struct', 'time')

    RExec.ok_path

The list of directories that are searched when an import is performed in the restricted environment. The default value is the same as `sys.path`.

    RExec.ok_posix_names

A tuple of names for functions in the `os` module that are available to restricted programs. The default value is as follows:

    ('error', 'fstat', 'listdir', 'lstat', 'readlink',
    'stat', 'times', 'uname', 'getpid', 'getppid',
    'getcwd', 'getuid', 'getgid', 'geteuid', 'getegid')

    RExec.ok_sys_names

A tuple of names for functions and variables in the `sys` module that are available to restricted programs. The default value is as follows:

    ('ps1', 'ps2', 'copyright', 'version', 'platform', 'exit', 'maxint')

An instance `r` of `RExec` uses the following methods to execute restricted code:

    r.r_eval(code)

Like `eval()` except that code is executed in the restricted environment. `code` is a string or a compiled code object. Returns the value of the resulting expression.

    r.r_exec(code)

Like the `exec` statement except that execution is performed in the restricted environment. `code` is a string or a compiled code object.

    r.r_execfile(filename)

Like `execfile()` except that code is executed in the restricted environment.

    r.s_eval(code)

Like `r_eval()` except that access to `sys.stdin`, `sys.stdout`, and `sys.stderr` is allowed.

    r.s_exec(code)

Like `r_exec()` except that access to `sys.stdin`, `sys.stdout`, and `sys.stderr` is allowed.

    r.s_execfile(code)

Like `r_execfile()` except that access to `sys.stdin`, `sys.stdout`, and `sys.stderr` is allowed.

The following methods are called implicitly by code executing in the restricted environment and can be redefined in subclasses of `RExec`:

    r.r_import(modulename [, globals [, locals [, fromlist]]])

Imports a module `modulename` . An `ImportError` exception should be raised if the module is unsafe.

    r.r_open(filename [, mode [, bufsize]])

Opens a file in the restricted environment. The arguments are the same as the built-in `open()` function. By default, files can be opened for reading, but not for writing.

    r.r_reload(module)

Reloads the module object `module` .

    r.r_unload(module)

Unloads the module object `module` .

    r.s_import(modulename [, globals [, locals [, fromlist]]])

Like `r_import()`, but with access to standard I/O streams.

    r.s_reload(module)

Like `r_reload()`, but with access to standard I/O streams.

    r.s_unload(module)

Like `r_unload()`, but with access to standard I/O streams.

##### Example

The following program executes Python code submitted through a CGI script in a restricted environment along with limits on CPU and memory usage:

    #!/usr/local/bin/python
    import rexec
    import cgi, StringIO, sys, string, resource
    form = cgi.FieldStorage()
    code = form["code"].value         # Get some arbitrary code to execute
    code = string.replace(code,"\015","")
    sys.stderr = sys.stdout    # Make error messages appear

    print "Content-type: text/plain\n\n"
    print "The output of your program is : \n\n"
    class CGIExec(rexec.RExec):
          def r_open(*args):
               raise SystemError, "open not supported"
    r = CGIExec()              # Create sandbox
    # Restrict memory usage to 4 Mbytes
    resource.setrlimit(resource.RLIMIT_DATA,(4000000,4000000))
    # Set CPU time limit to 10 seconds
    resource.setrlimit(resource.RLIMIT_CPU,(10,10))
    # Go run the code
    r.s_exec(code)             # Execute the untrusted code

? See Also Bastion (283).

#### `Bastion`

The `Bastion` module restricts access to attributes of objects. It’s primarily used in conjunction with the `rexec` module when a privileged program wants to allow restricted programs to access attributes of unrestricted objects. The idea behind a `Bastion` is simple—a wrapper is placed around an object, causing every method access to be redirected through a filter function that’s responsible for accepting or rejecting the access. Furthermore, all access to data attributes (non-methods) is prohibited.

    Bastion(object [, filter [, name [, class]]])

Returns a bastion for the object `object` . `filter` is a function that accepts a string containing a method name and returns true or false if access to the method is permitted or denied, respectively. `name` is the name of the object that’s printed by the bastion’s `str()` method. `class` is the class object that implements Bastion objects and is not described here (it’s rarely necessary to supply this).

##### Example

In this example, you want to restrict access to a `StringIO` object so that only read operations are permitted (see the StringIO module):

    import StringIO, Bastion

    str = StringIO("")
    ...
    strbast = Bastion.Bastion(str, lambda x: x in ['read','readline','readlines'])
    strbast.readline()           # Okay
    strbast.write("Ha ha")       # Fails. AttributeError : write

##### Notes

- If the `filter` function is omitted, a bastion limits access to all methods beginning with an underscore.

- Bastions cannot be placed around built-in types such as files and sockets.

| Last updated on 3/28/2002 Python Essential Reference, Second Edition, © 2002 New Riders Publishing

< BACK | Make Note | Bookmark | CONTINUE >

© 2002, O'Reilly & Associates, Inc.