Fix typo. Backport if anyone cares. :-)
[python.git] / Lib / rexec.py
blobd289d6aa1e9608f8e73f4a928febece73d71f75b
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 """
21 import sys
22 import __builtin__
23 import os
24 import ihooks
25 import imp
27 __all__ = ["RExec"]
29 class FileBase:
31 ok_file_methods = ('fileno', 'flush', 'isatty', 'read', 'readline',
32 'readlines', 'seek', 'tell', 'write', 'writelines', 'xreadlines',
33 '__iter__')
36 class FileWrapper(FileBase):
38 # XXX This is just like a Bastion -- should use that!
40 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))
45 def close(self):
46 self.flush()
49 TEMPLATE = """
50 def %s(self, *args):
51 return getattr(self.mod, self.name).%s(*args)
52 """
54 class FileDelegate(FileBase):
56 def __init__(self, mod, name):
57 self.mod = mod
58 self.name = 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])
70 verbose = 0
71 rexec = None
72 if args and type(args[-1]) == type(0):
73 verbose = args[-1]
74 args = args[:-1]
75 if args and hasattr(args[0], '__class__'):
76 rexec = args[0]
77 args = args[1:]
78 if args:
79 raise TypeError, "too many arguments"
80 ihooks.Hooks.__init__(self, verbose)
81 self.rexec = rexec
83 def set_rexec(self, rexec):
84 # Called by RExec instance to complete initialization
85 self.rexec = rexec
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):
94 m = __import__(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
130 processor time.
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', 'select',
140 'sha', '_sre', 'strop', 'struct', 'time',
141 '_weakref')
143 ok_posix_names = ('error', 'fstat', 'listdir', 'lstat', 'readlink',
144 'stat', 'times', 'uname', 'getpid', 'getppid',
145 'getcwd', 'getuid', 'getgid', 'geteuid', 'getegid')
147 ok_sys_names = ('byteorder', 'copyright', 'exit', 'getdefaultencoding',
148 'getrefcount', 'hexversion', 'maxint', 'maxunicode',
149 'platform', 'ps1', 'ps2', 'version', 'version_info')
151 nok_builtin_names = ('open', 'file', 'reload', '__import__')
153 ok_file_types = (imp.C_EXTENSION, imp.PY_SOURCE)
155 def __init__(self, hooks = None, verbose = 0):
156 """Returns an instance of the RExec class.
158 The hooks parameter is an instance of the RHooks class or a subclass
159 of it. If it is omitted or None, the default RHooks class is
160 instantiated.
162 Whenever the RExec module searches for a module (even a built-in one)
163 or reads a module's code, it doesn't actually go out to the file
164 system itself. Rather, it calls methods of an RHooks instance that
165 was passed to or created by its constructor. (Actually, the RExec
166 object doesn't make these calls --- they are made by a module loader
167 object that's part of the RExec object. This allows another level of
168 flexibility, which can be useful when changing the mechanics of
169 import within the restricted environment.)
171 By providing an alternate RHooks object, we can control the file
172 system accesses made to import a module, without changing the
173 actual algorithm that controls the order in which those accesses are
174 made. For instance, we could substitute an RHooks object that
175 passes all filesystem requests to a file server elsewhere, via some
176 RPC mechanism such as ILU. Grail's applet loader uses this to support
177 importing applets from a URL for a directory.
179 If the verbose parameter is true, additional debugging output may be
180 sent to standard output.
184 raise RuntimeError, "This code is not secure in Python 2.2 and 2.3"
186 ihooks._Verbose.__init__(self, verbose)
187 # XXX There's a circular reference here:
188 self.hooks = hooks or RHooks(verbose)
189 self.hooks.set_rexec(self)
190 self.modules = {}
191 self.ok_dynamic_modules = self.ok_builtin_modules
192 list = []
193 for mname in self.ok_builtin_modules:
194 if mname in sys.builtin_module_names:
195 list.append(mname)
196 self.ok_builtin_modules = tuple(list)
197 self.set_trusted_path()
198 self.make_builtin()
199 self.make_initial_modules()
200 # make_sys must be last because it adds the already created
201 # modules to its builtin_module_names
202 self.make_sys()
203 self.loader = RModuleLoader(self.hooks, verbose)
204 self.importer = RModuleImporter(self.loader, verbose)
206 def set_trusted_path(self):
207 # Set the path from which dynamic modules may be loaded.
208 # Those dynamic modules must also occur in ok_builtin_modules
209 self.trusted_path = filter(os.path.isabs, sys.path)
211 def load_dynamic(self, name, filename, file):
212 if name not in self.ok_dynamic_modules:
213 raise ImportError, "untrusted dynamic module: %s" % name
214 if name in sys.modules:
215 src = sys.modules[name]
216 else:
217 src = imp.load_dynamic(name, filename, file)
218 dst = self.copy_except(src, [])
219 return dst
221 def make_initial_modules(self):
222 self.make_main()
223 self.make_osname()
225 # Helpers for RHooks
227 def get_suffixes(self):
228 return [item # (suff, mode, type)
229 for item in imp.get_suffixes()
230 if item[2] in self.ok_file_types]
232 def is_builtin(self, mname):
233 return mname in self.ok_builtin_modules
235 # The make_* methods create specific built-in modules
237 def make_builtin(self):
238 m = self.copy_except(__builtin__, self.nok_builtin_names)
239 m.__import__ = self.r_import
240 m.reload = self.r_reload
241 m.open = m.file = self.r_open
243 def make_main(self):
244 m = self.add_module('__main__')
246 def make_osname(self):
247 osname = os.name
248 src = __import__(osname)
249 dst = self.copy_only(src, self.ok_posix_names)
250 dst.environ = e = {}
251 for key, value in os.environ.items():
252 e[key] = value
254 def make_sys(self):
255 m = self.copy_only(sys, self.ok_sys_names)
256 m.modules = self.modules
257 m.argv = ['RESTRICTED']
258 m.path = map(None, self.ok_path)
259 m.exc_info = self.r_exc_info
260 m = self.modules['sys']
261 l = self.modules.keys() + list(self.ok_builtin_modules)
262 l.sort()
263 m.builtin_module_names = tuple(l)
265 # The copy_* methods copy existing modules with some changes
267 def copy_except(self, src, exceptions):
268 dst = self.copy_none(src)
269 for name in dir(src):
270 setattr(dst, name, getattr(src, name))
271 for name in exceptions:
272 try:
273 delattr(dst, name)
274 except AttributeError:
275 pass
276 return dst
278 def copy_only(self, src, names):
279 dst = self.copy_none(src)
280 for name in names:
281 try:
282 value = getattr(src, name)
283 except AttributeError:
284 continue
285 setattr(dst, name, value)
286 return dst
288 def copy_none(self, src):
289 m = self.add_module(src.__name__)
290 m.__doc__ = src.__doc__
291 return m
293 # Add a module -- return an existing module or create one
295 def add_module(self, mname):
296 m = self.modules.get(mname)
297 if m is None:
298 self.modules[mname] = m = self.hooks.new_module(mname)
299 m.__builtins__ = self.modules['__builtin__']
300 return m
302 # The r* methods are public interfaces
304 def r_exec(self, code):
305 """Execute code within a restricted environment.
307 The code parameter must either be a string containing one or more
308 lines of Python code, or a compiled code object, which will be
309 executed in the restricted environment's __main__ module.
312 m = self.add_module('__main__')
313 exec code in m.__dict__
315 def r_eval(self, code):
316 """Evaluate code within a restricted environment.
318 The code parameter must either be a string containing a Python
319 expression, or a compiled code object, which will be evaluated in
320 the restricted environment's __main__ module. The value of the
321 expression or code object will be returned.
324 m = self.add_module('__main__')
325 return eval(code, m.__dict__)
327 def r_execfile(self, file):
328 """Execute the Python code in the file in the restricted
329 environment's __main__ module.
332 m = self.add_module('__main__')
333 execfile(file, m.__dict__)
335 def r_import(self, mname, globals={}, locals={}, fromlist=[]):
336 """Import a module, raising an ImportError exception if the module
337 is considered unsafe.
339 This method is implicitly called by code executing in the
340 restricted environment. Overriding this method in a subclass is
341 used to change the policies enforced by a restricted environment.
344 return self.importer.import_module(mname, globals, locals, fromlist)
346 def r_reload(self, m):
347 """Reload the module object, re-parsing and re-initializing it.
349 This method is implicitly called by code executing in the
350 restricted environment. Overriding this method in a subclass is
351 used to change the policies enforced by a restricted environment.
354 return self.importer.reload(m)
356 def r_unload(self, m):
357 """Unload the module.
359 Removes it from the restricted environment's sys.modules dictionary.
361 This method is implicitly called by code executing in the
362 restricted environment. Overriding this method in a subclass is
363 used to change the policies enforced by a restricted environment.
366 return self.importer.unload(m)
368 # The s_* methods are similar but also swap std{in,out,err}
370 def make_delegate_files(self):
371 s = self.modules['sys']
372 self.delegate_stdin = FileDelegate(s, 'stdin')
373 self.delegate_stdout = FileDelegate(s, 'stdout')
374 self.delegate_stderr = FileDelegate(s, 'stderr')
375 self.restricted_stdin = FileWrapper(sys.stdin)
376 self.restricted_stdout = FileWrapper(sys.stdout)
377 self.restricted_stderr = FileWrapper(sys.stderr)
379 def set_files(self):
380 if not hasattr(self, 'save_stdin'):
381 self.save_files()
382 if not hasattr(self, 'delegate_stdin'):
383 self.make_delegate_files()
384 s = self.modules['sys']
385 s.stdin = self.restricted_stdin
386 s.stdout = self.restricted_stdout
387 s.stderr = self.restricted_stderr
388 sys.stdin = self.delegate_stdin
389 sys.stdout = self.delegate_stdout
390 sys.stderr = self.delegate_stderr
392 def reset_files(self):
393 self.restore_files()
394 s = self.modules['sys']
395 self.restricted_stdin = s.stdin
396 self.restricted_stdout = s.stdout
397 self.restricted_stderr = s.stderr
400 def save_files(self):
401 self.save_stdin = sys.stdin
402 self.save_stdout = sys.stdout
403 self.save_stderr = sys.stderr
405 def restore_files(self):
406 sys.stdin = self.save_stdin
407 sys.stdout = self.save_stdout
408 sys.stderr = self.save_stderr
410 def s_apply(self, func, args=(), kw={}):
411 self.save_files()
412 try:
413 self.set_files()
414 r = func(*args, **kw)
415 finally:
416 self.restore_files()
417 return r
419 def s_exec(self, *args):
420 """Execute code within a restricted environment.
422 Similar to the r_exec() method, but the code will be granted access
423 to restricted versions of the standard I/O streams sys.stdin,
424 sys.stderr, and sys.stdout.
426 The code parameter must either be a string containing one or more
427 lines of Python code, or a compiled code object, which will be
428 executed in the restricted environment's __main__ module.
431 return self.s_apply(self.r_exec, args)
433 def s_eval(self, *args):
434 """Evaluate code within a restricted environment.
436 Similar to the r_eval() method, but the code will be granted access
437 to restricted versions of the standard I/O streams sys.stdin,
438 sys.stderr, and sys.stdout.
440 The code parameter must either be a string containing a Python
441 expression, or a compiled code object, which will be evaluated in
442 the restricted environment's __main__ module. The value of the
443 expression or code object will be returned.
446 return self.s_apply(self.r_eval, args)
448 def s_execfile(self, *args):
449 """Execute the Python code in the file in the restricted
450 environment's __main__ module.
452 Similar to the r_execfile() method, but the code will be granted
453 access to restricted versions of the standard I/O streams sys.stdin,
454 sys.stderr, and sys.stdout.
457 return self.s_apply(self.r_execfile, args)
459 def s_import(self, *args):
460 """Import a module, raising an ImportError exception if the module
461 is considered unsafe.
463 This method is implicitly called by code executing in the
464 restricted environment. Overriding this method in a subclass is
465 used to change the policies enforced by a restricted environment.
467 Similar to the r_import() method, but has access to restricted
468 versions of the standard I/O streams sys.stdin, sys.stderr, and
469 sys.stdout.
472 return self.s_apply(self.r_import, args)
474 def s_reload(self, *args):
475 """Reload the module object, re-parsing and re-initializing it.
477 This method is implicitly called by code executing in the
478 restricted environment. Overriding this method in a subclass is
479 used to change the policies enforced by a restricted environment.
481 Similar to the r_reload() method, but has access to restricted
482 versions of the standard I/O streams sys.stdin, sys.stderr, and
483 sys.stdout.
486 return self.s_apply(self.r_reload, args)
488 def s_unload(self, *args):
489 """Unload the module.
491 Removes it from the restricted environment's sys.modules dictionary.
493 This method is implicitly called by code executing in the
494 restricted environment. Overriding this method in a subclass is
495 used to change the policies enforced by a restricted environment.
497 Similar to the r_unload() method, but has access to restricted
498 versions of the standard I/O streams sys.stdin, sys.stderr, and
499 sys.stdout.
502 return self.s_apply(self.r_unload, args)
504 # Restricted open(...)
506 def r_open(self, file, mode='r', buf=-1):
507 """Method called when open() is called in the restricted environment.
509 The arguments are identical to those of the open() function, and a
510 file object (or a class instance compatible with file objects)
511 should be returned. RExec's default behaviour is allow opening
512 any file for reading, but forbidding any attempt to write a file.
514 This method is implicitly called by code executing in the
515 restricted environment. Overriding this method in a subclass is
516 used to change the policies enforced by a restricted environment.
519 mode = str(mode)
520 if mode not in ('r', 'rb'):
521 raise IOError, "can't open files for writing in restricted mode"
522 return open(file, mode, buf)
524 # Restricted version of sys.exc_info()
526 def r_exc_info(self):
527 ty, va, tr = sys.exc_info()
528 tr = None
529 return ty, va, tr
532 def test():
533 import getopt, traceback
534 opts, args = getopt.getopt(sys.argv[1:], 'vt:')
535 verbose = 0
536 trusted = []
537 for o, a in opts:
538 if o == '-v':
539 verbose = verbose+1
540 if o == '-t':
541 trusted.append(a)
542 r = RExec(verbose=verbose)
543 if trusted:
544 r.ok_builtin_modules = r.ok_builtin_modules + tuple(trusted)
545 if args:
546 r.modules['sys'].argv = args
547 r.modules['sys'].path.insert(0, os.path.dirname(args[0]))
548 else:
549 r.modules['sys'].path.insert(0, "")
550 fp = sys.stdin
551 if args and args[0] != '-':
552 try:
553 fp = open(args[0])
554 except IOError, msg:
555 print "%s: can't open file %r" % (sys.argv[0], args[0])
556 return 1
557 if fp.isatty():
558 try:
559 import readline
560 except ImportError:
561 pass
562 import code
563 class RestrictedConsole(code.InteractiveConsole):
564 def runcode(self, co):
565 self.locals['__builtins__'] = r.modules['__builtin__']
566 r.s_apply(code.InteractiveConsole.runcode, (self, co))
567 try:
568 RestrictedConsole(r.modules['__main__'].__dict__).interact()
569 except SystemExit, n:
570 return n
571 else:
572 text = fp.read()
573 fp.close()
574 c = compile(text, fp.name, 'exec')
575 try:
576 r.s_exec(c)
577 except SystemExit, n:
578 return n
579 except:
580 traceback.print_exc()
581 return 1
584 if __name__ == '__main__':
585 sys.exit(test())