< BACKMake Note | BookmarkCONTINUE >
156135250194107072078175030179198180025031194137176049106218111004228145120024041136114244

Restricted Execution

Throughout this text, we have used only normal, unrestricted execution which provides general access to all resources available to the Python interpreter. This includes, but is not limited to: disk file or database access, establishing network connections, invoking other programs, etc.

There may be circumstances in which you want to impose restrictions on Python programs which execute on your system. Scenarios which may require you to impose restrictions on some or all of the above include: Python CGI applications, environments which allow for upload and execution of Python scripts, anonymous FTP access which has installed the Python interpreter in the /bin directory, etc.

There are two primary modules which aid in setting up restricted environments. The first is Bastion, which provides restricted access to your data. The primary method of utilizing Bastion is to instantiate the Bastion class around your object, providing an attribute filter with which to provide or deny access to your object's attributes.

We will not discuss Bastion in this text, but you may refer to the Python documentation or Beazley for more information. Instead, we will focus our efforts primarily on the rexec module which creates the restricted environment with which to execute untrusted Python code. We conclude our discussion of Bastion by stating that it can be used in conjunction with rexec to provide a complete and secure execution mechanism, restricting access to data as well as the run-time environment. (Both modules are installed with Python as part of the standard library.)

The rexec module has a primary mission to restrict the execution environment of a Python script. This module allows for a limited number of built-ins (functions and/or data attributes), imposes restrictions on which modules can imported, which attributes can and cannot be accessed from the sys and os modules, and wraps the most critical built-ins [i.e., open(), reload(), and __import__()] with imposed restrictions.

You will recall that the __builtins__ module consists of all the attributes from the __builtin__ module. If __builtins__ is the __builtin__ module, then this constitutes an unrestricted environment:

					
>>> __builtins__
<module '__builtin__' (built-in)>

				

When we impose a restricted environment, __builtins__ will actually be a subset of the __builtin__ module that is handpicked for the restricted environment and even becomes "inaccessible" in that environment:

					
>>> __builtins__
<module '?' (built-in)>

				

The rexec module implements an RExec class with which to subclass and create your restricted environment. This class has static members, which you override, that dictate what is and what is not allowed in the "caged" or "sandboxed" environment. We present the static data attributes of the RExec class in Table 14.9.

Table 14-9. RExec Class Attributes
Attribute Name Description
nok_builtin_names attributes that are not ok to include in __builtins__
ok_builtin_modules modules that can be imported
ok_path list of directories accessible in restricted environment
ok_posix_names attributes that are ok to import from os module
ok_sys_names attributes that are ok to import from sys module

Instances of your RExec subclass have a number of methods with which to execute restricted code with. These are listed in Table 14.10.

Table 14-10. RExec Class Methods
Method Name Description
r_eval() restricted version of eval()
r_exc_info() restricted version of sys.exc_info()
r_exec() restricted version of exec
r_execfile() restricted version of execfile()
r_import() restricted version of __import__()
r_open() restricted version of open()
r_reload() restricted version of reload()
r_unload() restricted version of del module

All of the r_*() methods except for r_exc_info() and r_open() are available as s_*(), which behaves exactly the same as their r_*() counterparts with the exception of being granted access to the standard files (standard input, output, and error). There are other methods available in rexec, and we recommend that you refer to the Python documentation for more information.

We present a small example below consisting of a pair of files. The cager.py is the program responsible for creating a restricted environment with which to safely execute another script, caged.py. The restriction we are imposing in this example is to remove all the built-in attributes and allow only a handful of them to be accessible in our restricted environment. To accomplish this, we override the nok_builtin_names attribute, a tuple listing which attributes are not okay in the restricted environment.

The code for cager.py is given in Example 14.2. And the code for caged.py is shown in Example 14.3.

Upon execution, we will see the output of caged.py, giving a list of built-in attributes that it does have access to (compare this list with the code in cager.py), as well as an erroneous example of what happens if we call a function that we do not have access to, such as eval():

Example 14.2. Creating a Restricted Environment (cager.py)
						 <$nopage>
001 1  #!/usr/bin/env python
002 2
003 3  import rexec
004 4
005 5  class YourSandbox(rexec.RExec):
006 6      nok_builtin_names = dir(__builtins__)
007 7      nok_builtin_names.remove('dir')
008 8      nok_builtin_names.remove('str')
009 9      nok_builtin_names.remove('vars')
010 10
011 11 r = YourSandbox()
012 12 r.r_execfile("caged.py")
013  <$nopage>
					
Example 14.3. Executing Within a Restricted Environment (caged.py)
						 <$nopage>
001 1  #!/usr/bin/env python
002 2
003 3  print 'Restricted to these built-in attributes:'
004 4  for eachBI in dir(__builtin__):
005 5      print '\t', each BI:
006 6  print '\nAll others inaccessible, i.e. eval():\n'
007 7  eval (123)
008  <$nopage>
					
						
% cager.py 
Restricted to these built-in attributes:
        __builtins__
        __import__
        dir
        open
        reload
        str
        vars
All others inaccessible, i.e. eval():


Traceback (most recent call last):
  File "cager.py", line 12, in ?
    r.r_execfile("caged.py")
  File "/usr/lib/python2.0/rexec.py", line 261, in r_execfile
    return execfile(file, m.__dict__)
  File "caged.py", line 7, in ?
    eval(123)
NameError: There is no variable named 'eval'

					

r_*() functions such as r_open() (and their s_*() equivalents) automatically direct all calls to to special wrappers which execute these functions with additional restrictions. For example, a call to open() calls r_open() which allows only read mode. Attempting to open() a file for write would result in an IOError exception:

						
IOError: can't open files for writing in restricted mode

					

You may even disallow this by overriding r_open(). Let us add the following method definition to our YourSandbox class definition in cager.py:

						
def r_open(f, m='r', b=-1):
          raise IOError, 'sorry, no file access period'

					

When we try to open a file for reading or writing this time, we get:

						
IOError: sorry, no file access period
					


Last updated on 9/14/2001
Core Python Programming, © 2002 Prentice Hall PTR

< BACKMake Note | BookmarkCONTINUE >

© 2002, O'Reilly & Associates, Inc.