See All Titles |
![]() ![]() Restricted ExecutionThroughout 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.
Instances of your RExec subclass have a number of methods with which to execute restricted code with. These are listed in Table 14.10.
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
|
© 2002, O'Reilly & Associates, Inc. |