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 ...")?)
19 from warnings
import warnpy3k
20 warnpy3k("the rexec module has been removed in Python 3.0", stacklevel
=2)
34 ok_file_methods
= ('fileno', 'flush', 'isatty', 'read', 'readline',
35 'readlines', 'seek', 'tell', 'write', 'writelines', 'xreadlines',
39 class FileWrapper(FileBase
):
41 # XXX This is just like a Bastion -- should use that!
43 def __init__(self
, f
):
44 for m
in self
.ok_file_methods
:
45 if not hasattr(self
, m
) and hasattr(f
, m
):
46 setattr(self
, m
, getattr(f
, m
))
54 return getattr(self.mod, self.name).%s(*args)
57 class FileDelegate(FileBase
):
59 def __init__(self
, mod
, name
):
63 for m
in FileBase
.ok_file_methods
+ ('close',):
64 exec TEMPLATE
% (m
, m
)
67 class RHooks(ihooks
.Hooks
):
69 def __init__(self
, *args
):
70 # Hacks to support both old and new interfaces:
71 # old interface was RHooks(rexec[, verbose])
72 # new interface is RHooks([verbose])
75 if args
and type(args
[-1]) == type(0):
78 if args
and hasattr(args
[0], '__class__'):
82 raise TypeError, "too many arguments"
83 ihooks
.Hooks
.__init
__(self
, verbose
)
86 def set_rexec(self
, rexec
):
87 # Called by RExec instance to complete initialization
90 def get_suffixes(self
):
91 return self
.rexec
.get_suffixes()
93 def is_builtin(self
, name
):
94 return self
.rexec
.is_builtin(name
)
96 def init_builtin(self
, name
):
98 return self
.rexec
.copy_except(m
, ())
100 def init_frozen(self
, name
): raise SystemError, "don't use this"
101 def load_source(self
, *args
): raise SystemError, "don't use this"
102 def load_compiled(self
, *args
): raise SystemError, "don't use this"
103 def load_package(self
, *args
): raise SystemError, "don't use this"
105 def load_dynamic(self
, name
, filename
, file):
106 return self
.rexec
.load_dynamic(name
, filename
, file)
108 def add_module(self
, name
):
109 return self
.rexec
.add_module(name
)
111 def modules_dict(self
):
112 return self
.rexec
.modules
114 def default_path(self
):
115 return self
.rexec
.modules
['sys'].path
118 # XXX Backwards compatibility
119 RModuleLoader
= ihooks
.FancyModuleLoader
120 RModuleImporter
= ihooks
.ModuleImporter
123 class RExec(ihooks
._Verbose
):
124 """Basic restricted execution framework.
126 Code executed in this restricted environment will only have access to
127 modules and functions that are deemed safe; you can subclass RExec to
128 add or remove capabilities as desired.
130 The RExec class can prevent code from performing unsafe operations like
131 reading or writing disk files, or using TCP/IP sockets. However, it does
132 not protect against code using extremely large amounts of memory or
137 ok_path
= tuple(sys
.path
) # That's a policy decision
139 ok_builtin_modules
= ('audioop', 'array', 'binascii',
140 'cmath', 'errno', 'imageop',
141 'marshal', 'math', 'md5', 'operator',
143 'sha', '_sre', 'strop', 'struct', 'time',
146 ok_posix_names
= ('error', 'fstat', 'listdir', 'lstat', 'readlink',
147 'stat', 'times', 'uname', 'getpid', 'getppid',
148 'getcwd', 'getuid', 'getgid', 'geteuid', 'getegid')
150 ok_sys_names
= ('byteorder', 'copyright', 'exit', 'getdefaultencoding',
151 'getrefcount', 'hexversion', 'maxint', 'maxunicode',
152 'platform', 'ps1', 'ps2', 'version', 'version_info')
154 nok_builtin_names
= ('open', 'file', 'reload', '__import__')
156 ok_file_types
= (imp
.C_EXTENSION
, imp
.PY_SOURCE
)
158 def __init__(self
, hooks
= None, verbose
= 0):
159 """Returns an instance of the RExec class.
161 The hooks parameter is an instance of the RHooks class or a subclass
162 of it. If it is omitted or None, the default RHooks class is
165 Whenever the RExec module searches for a module (even a built-in one)
166 or reads a module's code, it doesn't actually go out to the file
167 system itself. Rather, it calls methods of an RHooks instance that
168 was passed to or created by its constructor. (Actually, the RExec
169 object doesn't make these calls --- they are made by a module loader
170 object that's part of the RExec object. This allows another level of
171 flexibility, which can be useful when changing the mechanics of
172 import within the restricted environment.)
174 By providing an alternate RHooks object, we can control the file
175 system accesses made to import a module, without changing the
176 actual algorithm that controls the order in which those accesses are
177 made. For instance, we could substitute an RHooks object that
178 passes all filesystem requests to a file server elsewhere, via some
179 RPC mechanism such as ILU. Grail's applet loader uses this to support
180 importing applets from a URL for a directory.
182 If the verbose parameter is true, additional debugging output may be
183 sent to standard output.
187 raise RuntimeError, "This code is not secure in Python 2.2 and later"
189 ihooks
._Verbose
.__init
__(self
, verbose
)
190 # XXX There's a circular reference here:
191 self
.hooks
= hooks
or RHooks(verbose
)
192 self
.hooks
.set_rexec(self
)
194 self
.ok_dynamic_modules
= self
.ok_builtin_modules
196 for mname
in self
.ok_builtin_modules
:
197 if mname
in sys
.builtin_module_names
:
199 self
.ok_builtin_modules
= tuple(list)
200 self
.set_trusted_path()
202 self
.make_initial_modules()
203 # make_sys must be last because it adds the already created
204 # modules to its builtin_module_names
206 self
.loader
= RModuleLoader(self
.hooks
, verbose
)
207 self
.importer
= RModuleImporter(self
.loader
, verbose
)
209 def set_trusted_path(self
):
210 # Set the path from which dynamic modules may be loaded.
211 # Those dynamic modules must also occur in ok_builtin_modules
212 self
.trusted_path
= filter(os
.path
.isabs
, sys
.path
)
214 def load_dynamic(self
, name
, filename
, file):
215 if name
not in self
.ok_dynamic_modules
:
216 raise ImportError, "untrusted dynamic module: %s" % name
217 if name
in sys
.modules
:
218 src
= sys
.modules
[name
]
220 src
= imp
.load_dynamic(name
, filename
, file)
221 dst
= self
.copy_except(src
, [])
224 def make_initial_modules(self
):
230 def get_suffixes(self
):
231 return [item
# (suff, mode, type)
232 for item
in imp
.get_suffixes()
233 if item
[2] in self
.ok_file_types
]
235 def is_builtin(self
, mname
):
236 return mname
in self
.ok_builtin_modules
238 # The make_* methods create specific built-in modules
240 def make_builtin(self
):
241 m
= self
.copy_except(__builtin__
, self
.nok_builtin_names
)
242 m
.__import
__ = self
.r_import
243 m
.reload = self
.r_reload
244 m
.open = m
.file = self
.r_open
247 m
= self
.add_module('__main__')
249 def make_osname(self
):
251 src
= __import__(osname
)
252 dst
= self
.copy_only(src
, self
.ok_posix_names
)
254 for key
, value
in os
.environ
.items():
258 m
= self
.copy_only(sys
, self
.ok_sys_names
)
259 m
.modules
= self
.modules
260 m
.argv
= ['RESTRICTED']
261 m
.path
= map(None, self
.ok_path
)
262 m
.exc_info
= self
.r_exc_info
263 m
= self
.modules
['sys']
264 l
= self
.modules
.keys() + list(self
.ok_builtin_modules
)
266 m
.builtin_module_names
= tuple(l
)
268 # The copy_* methods copy existing modules with some changes
270 def copy_except(self
, src
, exceptions
):
271 dst
= self
.copy_none(src
)
272 for name
in dir(src
):
273 setattr(dst
, name
, getattr(src
, name
))
274 for name
in exceptions
:
277 except AttributeError:
281 def copy_only(self
, src
, names
):
282 dst
= self
.copy_none(src
)
285 value
= getattr(src
, name
)
286 except AttributeError:
288 setattr(dst
, name
, value
)
291 def copy_none(self
, src
):
292 m
= self
.add_module(src
.__name
__)
293 m
.__doc
__ = src
.__doc
__
296 # Add a module -- return an existing module or create one
298 def add_module(self
, mname
):
299 m
= self
.modules
.get(mname
)
301 self
.modules
[mname
] = m
= self
.hooks
.new_module(mname
)
302 m
.__builtins
__ = self
.modules
['__builtin__']
305 # The r* methods are public interfaces
307 def r_exec(self
, code
):
308 """Execute code within a restricted environment.
310 The code parameter must either be a string containing one or more
311 lines of Python code, or a compiled code object, which will be
312 executed in the restricted environment's __main__ module.
315 m
= self
.add_module('__main__')
316 exec code
in m
.__dict
__
318 def r_eval(self
, code
):
319 """Evaluate code within a restricted environment.
321 The code parameter must either be a string containing a Python
322 expression, or a compiled code object, which will be evaluated in
323 the restricted environment's __main__ module. The value of the
324 expression or code object will be returned.
327 m
= self
.add_module('__main__')
328 return eval(code
, m
.__dict
__)
330 def r_execfile(self
, file):
331 """Execute the Python code in the file in the restricted
332 environment's __main__ module.
335 m
= self
.add_module('__main__')
336 execfile(file, m
.__dict
__)
338 def r_import(self
, mname
, globals={}, locals={}, fromlist
=[]):
339 """Import a module, raising an ImportError exception if the module
340 is considered unsafe.
342 This method is implicitly called by code executing in the
343 restricted environment. Overriding this method in a subclass is
344 used to change the policies enforced by a restricted environment.
347 return self
.importer
.import_module(mname
, globals, locals, fromlist
)
349 def r_reload(self
, m
):
350 """Reload the module object, re-parsing and re-initializing it.
352 This method is implicitly called by code executing in the
353 restricted environment. Overriding this method in a subclass is
354 used to change the policies enforced by a restricted environment.
357 return self
.importer
.reload(m
)
359 def r_unload(self
, m
):
360 """Unload the module.
362 Removes it from the restricted environment's sys.modules dictionary.
364 This method is implicitly called by code executing in the
365 restricted environment. Overriding this method in a subclass is
366 used to change the policies enforced by a restricted environment.
369 return self
.importer
.unload(m
)
371 # The s_* methods are similar but also swap std{in,out,err}
373 def make_delegate_files(self
):
374 s
= self
.modules
['sys']
375 self
.delegate_stdin
= FileDelegate(s
, 'stdin')
376 self
.delegate_stdout
= FileDelegate(s
, 'stdout')
377 self
.delegate_stderr
= FileDelegate(s
, 'stderr')
378 self
.restricted_stdin
= FileWrapper(sys
.stdin
)
379 self
.restricted_stdout
= FileWrapper(sys
.stdout
)
380 self
.restricted_stderr
= FileWrapper(sys
.stderr
)
383 if not hasattr(self
, 'save_stdin'):
385 if not hasattr(self
, 'delegate_stdin'):
386 self
.make_delegate_files()
387 s
= self
.modules
['sys']
388 s
.stdin
= self
.restricted_stdin
389 s
.stdout
= self
.restricted_stdout
390 s
.stderr
= self
.restricted_stderr
391 sys
.stdin
= self
.delegate_stdin
392 sys
.stdout
= self
.delegate_stdout
393 sys
.stderr
= self
.delegate_stderr
395 def reset_files(self
):
397 s
= self
.modules
['sys']
398 self
.restricted_stdin
= s
.stdin
399 self
.restricted_stdout
= s
.stdout
400 self
.restricted_stderr
= s
.stderr
403 def save_files(self
):
404 self
.save_stdin
= sys
.stdin
405 self
.save_stdout
= sys
.stdout
406 self
.save_stderr
= sys
.stderr
408 def restore_files(self
):
409 sys
.stdin
= self
.save_stdin
410 sys
.stdout
= self
.save_stdout
411 sys
.stderr
= self
.save_stderr
413 def s_apply(self
, func
, args
=(), kw
={}):
417 r
= func(*args
, **kw
)
422 def s_exec(self
, *args
):
423 """Execute code within a restricted environment.
425 Similar to the r_exec() method, but the code will be granted access
426 to restricted versions of the standard I/O streams sys.stdin,
427 sys.stderr, and sys.stdout.
429 The code parameter must either be a string containing one or more
430 lines of Python code, or a compiled code object, which will be
431 executed in the restricted environment's __main__ module.
434 return self
.s_apply(self
.r_exec
, args
)
436 def s_eval(self
, *args
):
437 """Evaluate code within a restricted environment.
439 Similar to the r_eval() method, but the code will be granted access
440 to restricted versions of the standard I/O streams sys.stdin,
441 sys.stderr, and sys.stdout.
443 The code parameter must either be a string containing a Python
444 expression, or a compiled code object, which will be evaluated in
445 the restricted environment's __main__ module. The value of the
446 expression or code object will be returned.
449 return self
.s_apply(self
.r_eval
, args
)
451 def s_execfile(self
, *args
):
452 """Execute the Python code in the file in the restricted
453 environment's __main__ module.
455 Similar to the r_execfile() method, but the code will be granted
456 access to restricted versions of the standard I/O streams sys.stdin,
457 sys.stderr, and sys.stdout.
460 return self
.s_apply(self
.r_execfile
, args
)
462 def s_import(self
, *args
):
463 """Import a module, raising an ImportError exception if the module
464 is considered unsafe.
466 This method is implicitly called by code executing in the
467 restricted environment. Overriding this method in a subclass is
468 used to change the policies enforced by a restricted environment.
470 Similar to the r_import() method, but has access to restricted
471 versions of the standard I/O streams sys.stdin, sys.stderr, and
475 return self
.s_apply(self
.r_import
, args
)
477 def s_reload(self
, *args
):
478 """Reload the module object, re-parsing and re-initializing it.
480 This method is implicitly called by code executing in the
481 restricted environment. Overriding this method in a subclass is
482 used to change the policies enforced by a restricted environment.
484 Similar to the r_reload() method, but has access to restricted
485 versions of the standard I/O streams sys.stdin, sys.stderr, and
489 return self
.s_apply(self
.r_reload
, args
)
491 def s_unload(self
, *args
):
492 """Unload the module.
494 Removes it from the restricted environment's sys.modules dictionary.
496 This method is implicitly called by code executing in the
497 restricted environment. Overriding this method in a subclass is
498 used to change the policies enforced by a restricted environment.
500 Similar to the r_unload() method, but has access to restricted
501 versions of the standard I/O streams sys.stdin, sys.stderr, and
505 return self
.s_apply(self
.r_unload
, args
)
507 # Restricted open(...)
509 def r_open(self
, file, mode
='r', buf
=-1):
510 """Method called when open() is called in the restricted environment.
512 The arguments are identical to those of the open() function, and a
513 file object (or a class instance compatible with file objects)
514 should be returned. RExec's default behaviour is allow opening
515 any file for reading, but forbidding any attempt to write a file.
517 This method is implicitly called by code executing in the
518 restricted environment. Overriding this method in a subclass is
519 used to change the policies enforced by a restricted environment.
523 if mode
not in ('r', 'rb'):
524 raise IOError, "can't open files for writing in restricted mode"
525 return open(file, mode
, buf
)
527 # Restricted version of sys.exc_info()
529 def r_exc_info(self
):
530 ty
, va
, tr
= sys
.exc_info()
536 import getopt
, traceback
537 opts
, args
= getopt
.getopt(sys
.argv
[1:], 'vt:')
545 r
= RExec(verbose
=verbose
)
547 r
.ok_builtin_modules
= r
.ok_builtin_modules
+ tuple(trusted
)
549 r
.modules
['sys'].argv
= args
550 r
.modules
['sys'].path
.insert(0, os
.path
.dirname(args
[0]))
552 r
.modules
['sys'].path
.insert(0, "")
554 if args
and args
[0] != '-':
558 print "%s: can't open file %r" % (sys
.argv
[0], args
[0])
566 class RestrictedConsole(code
.InteractiveConsole
):
567 def runcode(self
, co
):
568 self
.locals['__builtins__'] = r
.modules
['__builtin__']
569 r
.s_apply(code
.InteractiveConsole
.runcode
, (self
, co
))
571 RestrictedConsole(r
.modules
['__main__'].__dict
__).interact()
572 except SystemExit, n
:
577 c
= compile(text
, fp
.name
, 'exec')
580 except SystemExit, n
:
583 traceback
.print_exc()
587 if __name__
== '__main__':