comment out ugly xxx
[python.git] / Lib / rexec.py
blob22b1bb26228897dda6df1439cde6d8aba1e17b66
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().
14 XXX To do:
15 - r_open should allow writing tmp dir
16 - r_exec etc. with explicit globals/locals? (Use rexec("exec ... in ...")?)
18 """
19 from warnings import warnpy3k
20 warnpy3k("the rexec module has been removed in Python 3.0", stacklevel=2)
21 del warnpy3k
24 import sys
25 import __builtin__
26 import os
27 import ihooks
28 import imp
30 __all__ = ["RExec"]
32 class FileBase:
34 ok_file_methods = ('fileno', 'flush', 'isatty', 'read', 'readline',
35 'readlines', 'seek', 'tell', 'write', 'writelines', 'xreadlines',
36 '__iter__')
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))
48 def close(self):
49 self.flush()
52 TEMPLATE = """
53 def %s(self, *args):
54 return getattr(self.mod, self.name).%s(*args)
55 """
57 class FileDelegate(FileBase):
59 def __init__(self, mod, name):
60 self.mod = mod
61 self.name = 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])
73 verbose = 0
74 rexec = None
75 if args and type(args[-1]) == type(0):
76 verbose = args[-1]
77 args = args[:-1]
78 if args and hasattr(args[0], '__class__'):
79 rexec = args[0]
80 args = args[1:]
81 if args:
82 raise TypeError, "too many arguments"
83 ihooks.Hooks.__init__(self, verbose)
84 self.rexec = rexec
86 def set_rexec(self, rexec):
87 # Called by RExec instance to complete initialization
88 self.rexec = rexec
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):
97 m = __import__(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
133 processor time.
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',
142 'parser', 'select',
143 'sha', '_sre', 'strop', 'struct', 'time',
144 '_weakref')
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
163 instantiated.
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)
193 self.modules = {}
194 self.ok_dynamic_modules = self.ok_builtin_modules
195 list = []
196 for mname in self.ok_builtin_modules:
197 if mname in sys.builtin_module_names:
198 list.append(mname)
199 self.ok_builtin_modules = tuple(list)
200 self.set_trusted_path()
201 self.make_builtin()
202 self.make_initial_modules()
203 # make_sys must be last because it adds the already created
204 # modules to its builtin_module_names
205 self.make_sys()
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]
219 else:
220 src = imp.load_dynamic(name, filename, file)
221 dst = self.copy_except(src, [])
222 return dst
224 def make_initial_modules(self):
225 self.make_main()
226 self.make_osname()
228 # Helpers for RHooks
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
246 def make_main(self):
247 m = self.add_module('__main__')
249 def make_osname(self):
250 osname = os.name
251 src = __import__(osname)
252 dst = self.copy_only(src, self.ok_posix_names)
253 dst.environ = e = {}
254 for key, value in os.environ.items():
255 e[key] = value
257 def make_sys(self):
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)
265 l.sort()
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:
275 try:
276 delattr(dst, name)
277 except AttributeError:
278 pass
279 return dst
281 def copy_only(self, src, names):
282 dst = self.copy_none(src)
283 for name in names:
284 try:
285 value = getattr(src, name)
286 except AttributeError:
287 continue
288 setattr(dst, name, value)
289 return dst
291 def copy_none(self, src):
292 m = self.add_module(src.__name__)
293 m.__doc__ = src.__doc__
294 return m
296 # Add a module -- return an existing module or create one
298 def add_module(self, mname):
299 m = self.modules.get(mname)
300 if m is None:
301 self.modules[mname] = m = self.hooks.new_module(mname)
302 m.__builtins__ = self.modules['__builtin__']
303 return m
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)
382 def set_files(self):
383 if not hasattr(self, 'save_stdin'):
384 self.save_files()
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):
396 self.restore_files()
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={}):
414 self.save_files()
415 try:
416 self.set_files()
417 r = func(*args, **kw)
418 finally:
419 self.restore_files()
420 return r
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
472 sys.stdout.
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
486 sys.stdout.
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
502 sys.stdout.
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.
522 mode = str(mode)
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()
531 tr = None
532 return ty, va, tr
535 def test():
536 import getopt, traceback
537 opts, args = getopt.getopt(sys.argv[1:], 'vt:')
538 verbose = 0
539 trusted = []
540 for o, a in opts:
541 if o == '-v':
542 verbose = verbose+1
543 if o == '-t':
544 trusted.append(a)
545 r = RExec(verbose=verbose)
546 if trusted:
547 r.ok_builtin_modules = r.ok_builtin_modules + tuple(trusted)
548 if args:
549 r.modules['sys'].argv = args
550 r.modules['sys'].path.insert(0, os.path.dirname(args[0]))
551 else:
552 r.modules['sys'].path.insert(0, "")
553 fp = sys.stdin
554 if args and args[0] != '-':
555 try:
556 fp = open(args[0])
557 except IOError, msg:
558 print "%s: can't open file %r" % (sys.argv[0], args[0])
559 return 1
560 if fp.isatty():
561 try:
562 import readline
563 except ImportError:
564 pass
565 import code
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))
570 try:
571 RestrictedConsole(r.modules['__main__'].__dict__).interact()
572 except SystemExit, n:
573 return n
574 else:
575 text = fp.read()
576 fp.close()
577 c = compile(text, fp.name, 'exec')
578 try:
579 r.s_exec(c)
580 except SystemExit, n:
581 return n
582 except:
583 traceback.print_exc()
584 return 1
587 if __name__ == '__main__':
588 sys.exit(test())