13.2 Restricted Execution
Python code executed dynamically normally suffers no special restrictions. Python's general philosophy is to give the programmer tools and mechanisms that make it easy to write good, safe code, and trust the programmer to use them appropriately. Sometimes, however, trust might not be warranted. When code to execute dynamically comes from an untrusted source, the code itself is untrusted. In such cases it's important to selectively restrict the execution environment so that such code cannot
When the
_ _builtins_ _
item in the global namespace isn't the standard
_ _builtin_ _
module (or the latter's dictionary), Python
There is no special protection against restricted code raising exceptions. On the contrary, Python diagnoses any attempt by restricted code to
There is no built-in protection against untrusted code attempting to inflict damage by consuming large amounts of memory or time (so-called denial-of-service attacks). If you need to ward against such attacks, you can run untrusted code in a separate process. The separate process uses the mechanisms described in this section to restrict the untrusted code's execution, while the main process
As a final note, you need to know that there are known, exploitable security weaknesses in the restricted-execution mechanisms, even in the most recent versions of Python. Although restricted execution is better than nothing, at the time of this writing there are no known ways to execute untrusted code that are suitable for security-critical situations. 13.2.1 The rexec Module
The
rexec
module
Returns an instance of the RExec class, which corresponds to a new restricted-execution environment, also known as a sandbox. hooks , if not None , lets you exert fine-grained control on import statements executed in the sandbox. This is an advanced and rarely used functionality, and I do not cover it further in this book. verbose , if true, causes additional debugging output to be sent to standard output for many kinds of operations in the sandbox.
13.2.1.1
|
| r_add_module |
r .r_add_module( modname ) |
Adds and returns a new empty module if no module yet corresponds to
| r_eval, s_eval |
r .r_eval( expr ) r .s_eval( expr ) |
r_eval executes expr , which must be an expression or a code object, in the restricted environment and returns the expression's result.
| r_exec, s_exec |
r .r_exec( code ) r .s_exec( code ) |
r_exec executes code , which must be a string of code or a code object, in the restricted environment.
| r_execfile, s_execfile |
r .r_execfile( filename ) r .s_execfile( filename ) |
r_execfile executes the file identified by filename , which must contain Python code, in the restricted environment.
| r_import, s_import |
r .r_import( modname [, globals [, locals [, fromlist ]]]) r .s_import( modname [, globals [, locals [, fromlist ]]]) |
Imports the module
modname
into the restricted environment. All parameters are just like for built-in function
_ _import_ _
, covered in Chapter 7.
r_import
raises
ImportError
if the module is
| r_open |
r .r_open( filename [, mode [, bufsize ]]) |
Executes when restricted code calls the built-in
| r_reload, s_reload |
r .r_reload( module ) r .s_reload( module ) |
Reloads the module object module in the restricted-execution environment, similarly to built-in function reload , covered in Chapter 7.
| r_unload, s_unload |
r .r_unload( module ) r .s_unload( module ) |
Unloads the module object
module
from the restricted-execution environment (i.e.,
When
RExec
's defaults don't fully
Built-in functions not to be supplied in the sandbox
Built-in modules that the sandbox can import
Used as
sys.
Attributes of os that the sandbox may import
Attributes of sys that the sandbox may import
When you instantiate RExec , the new instance uses class attributes to prepare the sandbox. If you need to customize the sandbox, subclass RExec and instantiate the subclass. Your subclass can override RExec 's attributes, typically by copying the value that each attribute has in RExec and selectively adding or removing specific items.
In the simplest case, you can instantiate
RExec
and call the instance's
r_exec
and
r_eval
methods instead of using statement
import rexec
rex = rexec.RExec( )
def rexinput(prompt):
expr = raw_input(prompt)
return rex.r_eval(expr)
Function
rexinput
in this example is
This example's
After creating a new restricted-execution environment
r
with
r
=rexec.RExec( )
, you can
import rexec, math
rex = rexec.RExec( )
burex = rex.add_module('_ _builtins_ _')
for function in dir(math):
if function[0] != '_':
setattr(burex, function, getattr(math, function))
def rich_input(prompt):
expr = raw_input(prompt)
return rex.r_eval(expr)
Function rich_input in this example is now both richer and safer than the built-in input . It's richer because the user can now also input expressions such as sin(1.0) . It's safer, just like rexinput in the previous example, because it uses restricted execution to limit untrusted code.
Normally, you use
add_module
, and then add attributes, only for the modules named '
_ _main_ _
' and '
_ _builtins_ _
'. If the untrusted code needs other modules that it is allowed to import (based on the
ok_builtin_modules
and
ok_path
attributes of the
RExec
subclass you
Once you have
The Bastion module supplies a class, each of whose instances wraps an object and selectively exposes some of the wrapped object's methods, but no other attributes.
| Bastion |
class Bastion( obj , filter =lambda n : n [:1]!='_', name =None) |
A Bastion instance b wrapping object obj exposes only those methods of obj for whose name filter returns true. An access b . attr works like:
if
filter
('
attr
'): return
obj
.
attr
else: raise AttributeError, '
attr
'
plus a check that b . attr is a method, not an attribute of any other type.
The default
filter
accepts all method names that do not start with an
Suppose, for example, that your application supplies a class MyClass whose public methods are all safe, while private and special methods, as well as attributes that are not methods, should be hidden from untrusted code. In the sandbox, you can provide a factory function that supplies safely wrapped instances of MyClass to untrusted code as follows:
import rexec, Bastion
rex = rexec.RExec( )
burex = rex.add_module('_ _builtins_ _')
def SafeMyClassFactory(*args, **kwds):
return Bastion.Bastion(MyClass(*args, **kwds))
burex.MyClass = SafeMyClassFactory
Now, untrusted code that you run with rex.r_exec can instantiate and use safely wrapped instances of MyClass :
m = MyClass(1,2,3) m.somemethod(4,5)
However, any attempt by the untrusted code to access private or special methods, even indirectly (e.g.,
m
[6]=7
indirectly
import rexec, Bastion
rex = rexec.RExec( )
burex = rex.add_module('_ _builtins_ _')
def SafeMyClassFactory(*args, **kwds):
def is_safe(n): n= ='_ _getitem_ _' or n[0]!='_'
return Bastion.Bastion(MyClass(*args, **kwds), is_safe)
burex.MyClass = SafeMyClassFactory
Now, untrusted code that is run in sandbox
rex
can get, but not set, items of the instances of
MyClass
it builds with the factory function (