1 """Restricted execution facilities.
3 The class RExec exports methods r_exec(), r_eval(), r_execfile(), and
4 r_import(), which correspond roughly to the built-in operations
5 exec, eval(), execfile() and import, but executing the code in an
6 environment that only exposes those built-in operations that are
7 deemed safe. To this end, a modest collection of 'fake' modules is
8 created which mimics the standard modules by the same names. It is a
9 policy decision which built-in modules and operations are made
10 available; this module provides a reasonable default, but derived
11 classes can change the policies e.g. by overriding or extending class
12 variables like ok_builtin_modules or methods like make_sys().
15 - r_open should allow writing tmp dir
16 - r_exec etc. with explicit globals/locals? (Use rexec("exec ... in ...")?)
31 ok_file_methods
= ('fileno', 'flush', 'isatty', 'read', 'readline',
32 'readlines', 'seek', 'tell', 'write', 'writelines')
35 class FileWrapper(FileBase
):
37 # XXX This is just like a Bastion -- should use that!
39 def __init__(self
, f
):
41 for m
in self
.ok_file_methods
:
42 if not hasattr(self
, m
) and hasattr(f
, m
):
43 setattr(self
, m
, getattr(f
, m
))
51 return apply(getattr(self.mod, self.name).%s, args)
54 class FileDelegate(FileBase
):
56 def __init__(self
, mod
, name
):
60 for m
in FileBase
.ok_file_methods
+ ('close',):
61 exec TEMPLATE
% (m
, m
)
64 class RHooks(ihooks
.Hooks
):
66 def __init__(self
, *args
):
67 # Hacks to support both old and new interfaces:
68 # old interface was RHooks(rexec[, verbose])
69 # new interface is RHooks([verbose])
72 if args
and type(args
[-1]) == type(0):
75 if args
and hasattr(args
[0], '__class__'):
79 raise TypeError, "too many arguments"
80 ihooks
.Hooks
.__init
__(self
, verbose
)
83 def set_rexec(self
, rexec
):
84 # Called by RExec instance to complete initialization
87 def get_suffixes(self
):
88 return self
.rexec
.get_suffixes()
90 def is_builtin(self
, name
):
91 return self
.rexec
.is_builtin(name
)
93 def init_builtin(self
, name
):
95 return self
.rexec
.copy_except(m
, ())
97 def init_frozen(self
, name
): raise SystemError, "don't use this"
98 def load_source(self
, *args
): raise SystemError, "don't use this"
99 def load_compiled(self
, *args
): raise SystemError, "don't use this"
100 def load_package(self
, *args
): raise SystemError, "don't use this"
102 def load_dynamic(self
, name
, filename
, file):
103 return self
.rexec
.load_dynamic(name
, filename
, file)
105 def add_module(self
, name
):
106 return self
.rexec
.add_module(name
)
108 def modules_dict(self
):
109 return self
.rexec
.modules
111 def default_path(self
):
112 return self
.rexec
.modules
['sys'].path
115 # XXX Backwards compatibility
116 RModuleLoader
= ihooks
.FancyModuleLoader
117 RModuleImporter
= ihooks
.ModuleImporter
120 class RExec(ihooks
._Verbose
):
121 """Basic restricted execution framework.
123 Code executed in this restricted environment will only have access to
124 modules and functions that are deemed safe; you can subclass RExec to
125 add or remove capabilities as desired.
127 The RExec class can prevent code from performing unsafe operations like
128 reading or writing disk files, or using TCP/IP sockets. However, it does
129 not protect against code using extremely large amounts of memory or
134 ok_path
= tuple(sys
.path
) # That's a policy decision
136 ok_builtin_modules
= ('audioop', 'array', 'binascii',
137 'cmath', 'errno', 'imageop',
138 'marshal', 'math', 'md5', 'operator',
139 'parser', 'regex', 'pcre', 'rotor', 'select',
140 'sha', '_sre', 'strop', 'struct', 'time')
142 ok_posix_names
= ('error', 'fstat', 'listdir', 'lstat', 'readlink',
143 'stat', 'times', 'uname', 'getpid', 'getppid',
144 'getcwd', 'getuid', 'getgid', 'geteuid', 'getegid')
146 ok_sys_names
= ('ps1', 'ps2', 'copyright', 'version',
147 'platform', 'exit', 'maxint')
149 nok_builtin_names
= ('open', 'file', 'reload', '__import__')
151 ok_file_types
= (imp
.C_EXTENSION
, imp
.PY_SOURCE
)
153 def __init__(self
, hooks
= None, verbose
= 0):
154 """Returns an instance of the RExec class.
156 The hooks parameter is an instance of the RHooks class or a subclass
157 of it. If it is omitted or None, the default RHooks class is
160 Whenever the RExec module searches for a module (even a built-in one)
161 or reads a module's code, it doesn't actually go out to the file
162 system itself. Rather, it calls methods of an RHooks instance that
163 was passed to or created by its constructor. (Actually, the RExec
164 object doesn't make these calls --- they are made by a module loader
165 object that's part of the RExec object. This allows another level of
166 flexibility, which can be useful when changing the mechanics of
167 import within the restricted environment.)
169 By providing an alternate RHooks object, we can control the file
170 system accesses made to import a module, without changing the
171 actual algorithm that controls the order in which those accesses are
172 made. For instance, we could substitute an RHooks object that
173 passes all filesystem requests to a file server elsewhere, via some
174 RPC mechanism such as ILU. Grail's applet loader uses this to support
175 importing applets from a URL for a directory.
177 If the verbose parameter is true, additional debugging output may be
178 sent to standard output.
181 ihooks
._Verbose
.__init
__(self
, verbose
)
182 # XXX There's a circular reference here:
183 self
.hooks
= hooks
or RHooks(verbose
)
184 self
.hooks
.set_rexec(self
)
186 self
.ok_dynamic_modules
= self
.ok_builtin_modules
188 for mname
in self
.ok_builtin_modules
:
189 if mname
in sys
.builtin_module_names
:
191 self
.ok_builtin_modules
= tuple(list)
192 self
.set_trusted_path()
194 self
.make_initial_modules()
195 # make_sys must be last because it adds the already created
196 # modules to its builtin_module_names
198 self
.loader
= RModuleLoader(self
.hooks
, verbose
)
199 self
.importer
= RModuleImporter(self
.loader
, verbose
)
201 def set_trusted_path(self
):
202 # Set the path from which dynamic modules may be loaded.
203 # Those dynamic modules must also occur in ok_builtin_modules
204 self
.trusted_path
= filter(os
.path
.isabs
, sys
.path
)
206 def load_dynamic(self
, name
, filename
, file):
207 if name
not in self
.ok_dynamic_modules
:
208 raise ImportError, "untrusted dynamic module: %s" % name
209 if name
in sys
.modules
:
210 src
= sys
.modules
[name
]
212 src
= imp
.load_dynamic(name
, filename
, file)
213 dst
= self
.copy_except(src
, [])
216 def make_initial_modules(self
):
222 def get_suffixes(self
):
223 return [item
# (suff, mode, type)
224 for item
in imp
.get_suffixes()
225 if item
[2] in self
.ok_file_types
]
227 def is_builtin(self
, mname
):
228 return mname
in self
.ok_builtin_modules
230 # The make_* methods create specific built-in modules
232 def make_builtin(self
):
233 m
= self
.copy_except(__builtin__
, self
.nok_builtin_names
)
234 m
.__import
__ = self
.r_import
235 m
.reload = self
.r_reload
236 m
.open = m
.file = self
.r_open
239 m
= self
.add_module('__main__')
241 def make_osname(self
):
243 src
= __import__(osname
)
244 dst
= self
.copy_only(src
, self
.ok_posix_names
)
246 for key
, value
in os
.environ
.items():
250 m
= self
.copy_only(sys
, self
.ok_sys_names
)
251 m
.modules
= self
.modules
252 m
.argv
= ['RESTRICTED']
253 m
.path
= map(None, self
.ok_path
)
254 m
.exc_info
= self
.r_exc_info
255 m
= self
.modules
['sys']
256 l
= self
.modules
.keys() + list(self
.ok_builtin_modules
)
258 m
.builtin_module_names
= tuple(l
)
260 # The copy_* methods copy existing modules with some changes
262 def copy_except(self
, src
, exceptions
):
263 dst
= self
.copy_none(src
)
264 for name
in dir(src
):
265 setattr(dst
, name
, getattr(src
, name
))
266 for name
in exceptions
:
269 except AttributeError:
273 def copy_only(self
, src
, names
):
274 dst
= self
.copy_none(src
)
277 value
= getattr(src
, name
)
278 except AttributeError:
280 setattr(dst
, name
, value
)
283 def copy_none(self
, src
):
284 m
= self
.add_module(src
.__name
__)
285 m
.__doc
__ = src
.__doc
__
288 # Add a module -- return an existing module or create one
290 def add_module(self
, mname
):
291 if mname
in self
.modules
:
292 return self
.modules
[mname
]
293 self
.modules
[mname
] = m
= self
.hooks
.new_module(mname
)
294 m
.__builtins
__ = self
.modules
['__builtin__']
297 # The r* methods are public interfaces
299 def r_exec(self
, code
):
300 """Execute code within a restricted environment.
302 The code parameter must either be a string containing one or more
303 lines of Python code, or a compiled code object, which will be
304 executed in the restricted environment's __main__ module.
307 m
= self
.add_module('__main__')
308 exec code
in m
.__dict
__
310 def r_eval(self
, code
):
311 """Evaluate code within a restricted environment.
313 The code parameter must either be a string containing a Python
314 expression, or a compiled code object, which will be evaluated in
315 the restricted environment's __main__ module. The value of the
316 expression or code object will be returned.
319 m
= self
.add_module('__main__')
320 return eval(code
, m
.__dict
__)
322 def r_execfile(self
, file):
323 """Execute the Python code in the file in the restricted
324 environment's __main__ module.
327 m
= self
.add_module('__main__')
328 execfile(file, m
.__dict
__)
330 def r_import(self
, mname
, globals={}, locals={}, fromlist
=[]):
331 """Import a module, raising an ImportError exception if the module
332 is considered unsafe.
334 This method is implicitly called by code executing in the
335 restricted environment. Overriding this method in a subclass is
336 used to change the policies enforced by a restricted environment.
339 return self
.importer
.import_module(mname
, globals, locals, fromlist
)
341 def r_reload(self
, m
):
342 """Reload the module object, re-parsing and re-initializing it.
344 This method is implicitly called by code executing in the
345 restricted environment. Overriding this method in a subclass is
346 used to change the policies enforced by a restricted environment.
349 return self
.importer
.reload(m
)
351 def r_unload(self
, m
):
352 """Unload the module.
354 Removes it from the restricted environment's sys.modules dictionary.
356 This method is implicitly called by code executing in the
357 restricted environment. Overriding this method in a subclass is
358 used to change the policies enforced by a restricted environment.
361 return self
.importer
.unload(m
)
363 # The s_* methods are similar but also swap std{in,out,err}
365 def make_delegate_files(self
):
366 s
= self
.modules
['sys']
367 self
.delegate_stdin
= FileDelegate(s
, 'stdin')
368 self
.delegate_stdout
= FileDelegate(s
, 'stdout')
369 self
.delegate_stderr
= FileDelegate(s
, 'stderr')
370 self
.restricted_stdin
= FileWrapper(sys
.stdin
)
371 self
.restricted_stdout
= FileWrapper(sys
.stdout
)
372 self
.restricted_stderr
= FileWrapper(sys
.stderr
)
375 if not hasattr(self
, 'save_stdin'):
377 if not hasattr(self
, 'delegate_stdin'):
378 self
.make_delegate_files()
379 s
= self
.modules
['sys']
380 s
.stdin
= self
.restricted_stdin
381 s
.stdout
= self
.restricted_stdout
382 s
.stderr
= self
.restricted_stderr
383 sys
.stdin
= self
.delegate_stdin
384 sys
.stdout
= self
.delegate_stdout
385 sys
.stderr
= self
.delegate_stderr
387 def reset_files(self
):
389 s
= self
.modules
['sys']
390 self
.restricted_stdin
= s
.stdin
391 self
.restricted_stdout
= s
.stdout
392 self
.restricted_stderr
= s
.stderr
395 def save_files(self
):
396 self
.save_stdin
= sys
.stdin
397 self
.save_stdout
= sys
.stdout
398 self
.save_stderr
= sys
.stderr
400 def restore_files(self
):
401 sys
.stdin
= self
.save_stdin
402 sys
.stdout
= self
.save_stdout
403 sys
.stderr
= self
.save_stderr
405 def s_apply(self
, func
, args
=(), kw
=None):
410 r
= apply(func
, args
, kw
)
412 r
= apply(func
, args
)
417 def s_exec(self
, *args
):
418 """Execute code within a restricted environment.
420 Similar to the r_exec() method, but the code will be granted access
421 to restricted versions of the standard I/O streams sys.stdin,
422 sys.stderr, and sys.stdout.
424 The code parameter must either be a string containing one or more
425 lines of Python code, or a compiled code object, which will be
426 executed in the restricted environment's __main__ module.
429 return self
.s_apply(self
.r_exec
, args
)
431 def s_eval(self
, *args
):
432 """Evaluate code within a restricted environment.
434 Similar to the r_eval() method, but the code will be granted access
435 to restricted versions of the standard I/O streams sys.stdin,
436 sys.stderr, and sys.stdout.
438 The code parameter must either be a string containing a Python
439 expression, or a compiled code object, which will be evaluated in
440 the restricted environment's __main__ module. The value of the
441 expression or code object will be returned.
444 return self
.s_apply(self
.r_eval
, args
)
446 def s_execfile(self
, *args
):
447 """Execute the Python code in the file in the restricted
448 environment's __main__ module.
450 Similar to the r_execfile() method, but the code will be granted
451 access to restricted versions of the standard I/O streams sys.stdin,
452 sys.stderr, and sys.stdout.
455 return self
.s_apply(self
.r_execfile
, args
)
457 def s_import(self
, *args
):
458 """Import a module, raising an ImportError exception if the module
459 is considered unsafe.
461 This method is implicitly called by code executing in the
462 restricted environment. Overriding this method in a subclass is
463 used to change the policies enforced by a restricted environment.
465 Similar to the r_import() method, but has access to restricted
466 versions of the standard I/O streams sys.stdin, sys.stderr, and
470 return self
.s_apply(self
.r_import
, args
)
472 def s_reload(self
, *args
):
473 """Reload the module object, re-parsing and re-initializing it.
475 This method is implicitly called by code executing in the
476 restricted environment. Overriding this method in a subclass is
477 used to change the policies enforced by a restricted environment.
479 Similar to the r_reload() method, but has access to restricted
480 versions of the standard I/O streams sys.stdin, sys.stderr, and
484 return self
.s_apply(self
.r_reload
, args
)
486 def s_unload(self
, *args
):
487 """Unload the module.
489 Removes it from the restricted environment's sys.modules dictionary.
491 This method is implicitly called by code executing in the
492 restricted environment. Overriding this method in a subclass is
493 used to change the policies enforced by a restricted environment.
495 Similar to the r_unload() method, but has access to restricted
496 versions of the standard I/O streams sys.stdin, sys.stderr, and
500 return self
.s_apply(self
.r_unload
, args
)
502 # Restricted open(...)
504 def r_open(self
, file, mode
='r', buf
=-1):
505 """Method called when open() is called in the restricted environment.
507 The arguments are identical to those of the open() function, and a
508 file object (or a class instance compatible with file objects)
509 should be returned. RExec's default behaviour is allow opening
510 any file for reading, but forbidding any attempt to write a file.
512 This method is implicitly called by code executing in the
513 restricted environment. Overriding this method in a subclass is
514 used to change the policies enforced by a restricted environment.
517 if mode
not in ('r', 'rb'):
518 raise IOError, "can't open files for writing in restricted mode"
519 return open(file, mode
, buf
)
521 # Restricted version of sys.exc_info()
523 def r_exc_info(self
):
524 ty
, va
, tr
= sys
.exc_info()
530 import getopt
, traceback
531 opts
, args
= getopt
.getopt(sys
.argv
[1:], 'vt:')
539 r
= RExec(verbose
=verbose
)
541 r
.ok_builtin_modules
= r
.ok_builtin_modules
+ tuple(trusted
)
543 r
.modules
['sys'].argv
= args
544 r
.modules
['sys'].path
.insert(0, os
.path
.dirname(args
[0]))
546 r
.modules
['sys'].path
.insert(0, "")
548 if args
and args
[0] != '-':
552 print "%s: can't open file %s" % (sys
.argv
[0], `args
[0]`
)
558 "*** RESTRICTED *** Python %s on %s\n"
559 'Type "help", "copyright", "credits" or "license" '
560 "for more information." % (sys
.version
, sys
.platform
),
561 local
=r
.modules
['__main__'].__dict
__)
562 except SystemExit, n
:
567 c
= compile(text
, fp
.name
, 'exec')
570 except SystemExit, n
:
573 traceback
.print_exc()
577 if __name__
== '__main__':