diff options
Diffstat (limited to 'Lib/rexec.py')
-rw-r--r-- | Lib/rexec.py | 585 |
1 files changed, 0 insertions, 585 deletions
diff --git a/Lib/rexec.py b/Lib/rexec.py deleted file mode 100644 index c4ce1d0..0000000 --- a/Lib/rexec.py +++ /dev/null @@ -1,585 +0,0 @@ -"""Restricted execution facilities. - -The class RExec exports methods r_exec(), r_eval(), r_execfile(), and -r_import(), which correspond roughly to the built-in operations -exec, eval(), execfile() and import, but executing the code in an -environment that only exposes those built-in operations that are -deemed safe. To this end, a modest collection of 'fake' modules is -created which mimics the standard modules by the same names. It is a -policy decision which built-in modules and operations are made -available; this module provides a reasonable default, but derived -classes can change the policies e.g. by overriding or extending class -variables like ok_builtin_modules or methods like make_sys(). - -XXX To do: -- r_open should allow writing tmp dir -- r_exec etc. with explicit globals/locals? (Use rexec("exec ... in ...")?) - -""" - - -import sys -import __builtin__ -import os -import ihooks -import imp - -__all__ = ["RExec"] - -class FileBase: - - ok_file_methods = ('fileno', 'flush', 'isatty', 'read', 'readline', - 'readlines', 'seek', 'tell', 'write', 'writelines', - '__iter__') - - -class FileWrapper(FileBase): - - # XXX This is just like a Bastion -- should use that! - - def __init__(self, f): - for m in self.ok_file_methods: - if not hasattr(self, m) and hasattr(f, m): - setattr(self, m, getattr(f, m)) - - def close(self): - self.flush() - - -TEMPLATE = """ -def %s(self, *args): - return getattr(self.mod, self.name).%s(*args) -""" - -class FileDelegate(FileBase): - - def __init__(self, mod, name): - self.mod = mod - self.name = name - - for m in FileBase.ok_file_methods + ('close',): - exec(TEMPLATE % (m, m)) - - -class RHooks(ihooks.Hooks): - - def __init__(self, *args): - # Hacks to support both old and new interfaces: - # old interface was RHooks(rexec[, verbose]) - # new interface is RHooks([verbose]) - verbose = 0 - rexec = None - if args and type(args[-1]) == type(0): - verbose = args[-1] - args = args[:-1] - if args and hasattr(args[0], '__class__'): - rexec = args[0] - args = args[1:] - if args: - raise TypeError, "too many arguments" - ihooks.Hooks.__init__(self, verbose) - self.rexec = rexec - - def set_rexec(self, rexec): - # Called by RExec instance to complete initialization - self.rexec = rexec - - def get_suffixes(self): - return self.rexec.get_suffixes() - - def is_builtin(self, name): - return self.rexec.is_builtin(name) - - def init_builtin(self, name): - m = __import__(name) - return self.rexec.copy_except(m, ()) - - def init_frozen(self, name): raise SystemError, "don't use this" - def load_source(self, *args): raise SystemError, "don't use this" - def load_compiled(self, *args): raise SystemError, "don't use this" - def load_package(self, *args): raise SystemError, "don't use this" - - def load_dynamic(self, name, filename, file): - return self.rexec.load_dynamic(name, filename, file) - - def add_module(self, name): - return self.rexec.add_module(name) - - def modules_dict(self): - return self.rexec.modules - - def default_path(self): - return self.rexec.modules['sys'].path - - -# XXX Backwards compatibility -RModuleLoader = ihooks.FancyModuleLoader -RModuleImporter = ihooks.ModuleImporter - - -class RExec(ihooks._Verbose): - """Basic restricted execution framework. - - Code executed in this restricted environment will only have access to - modules and functions that are deemed safe; you can subclass RExec to - add or remove capabilities as desired. - - The RExec class can prevent code from performing unsafe operations like - reading or writing disk files, or using TCP/IP sockets. However, it does - not protect against code using extremely large amounts of memory or - processor time. - - """ - - ok_path = tuple(sys.path) # That's a policy decision - - ok_builtin_modules = ('audioop', 'array', 'binascii', - 'cmath', 'errno', 'imageop', - 'marshal', 'math', 'md5', 'operator', - 'parser', 'select', - 'sha', '_sre', 'strop', 'struct', 'time', - '_weakref') - - ok_posix_names = ('error', 'fstat', 'listdir', 'lstat', 'readlink', - 'stat', 'times', 'uname', 'getpid', 'getppid', - 'getcwd', 'getuid', 'getgid', 'geteuid', 'getegid') - - ok_sys_names = ('byteorder', 'copyright', 'exit', 'getdefaultencoding', - 'getrefcount', 'hexversion', 'maxint', 'maxunicode', - 'platform', 'ps1', 'ps2', 'version', 'version_info') - - nok_builtin_names = ('open', 'file', 'reload', '__import__') - - ok_file_types = (imp.C_EXTENSION, imp.PY_SOURCE) - - def __init__(self, hooks = None, verbose = 0): - """Returns an instance of the RExec class. - - The hooks parameter is an instance of the RHooks class or a subclass - of it. If it is omitted or None, the default RHooks class is - instantiated. - - Whenever the RExec module searches for a module (even a built-in one) - or reads a module's code, it doesn't actually go out to the file - system itself. Rather, it calls methods of an RHooks instance that - was passed to or created by its constructor. (Actually, the RExec - object doesn't make these calls --- they are made by a module loader - object that's part of the RExec object. This allows another level of - flexibility, which can be useful when changing the mechanics of - import within the restricted environment.) - - By providing an alternate RHooks object, we can control the file - system accesses made to import a module, without changing the - actual algorithm that controls the order in which those accesses are - made. For instance, we could substitute an RHooks object that - passes all filesystem requests to a file server elsewhere, via some - RPC mechanism such as ILU. Grail's applet loader uses this to support - importing applets from a URL for a directory. - - If the verbose parameter is true, additional debugging output may be - sent to standard output. - - """ - - raise RuntimeError, "This code is not secure in Python 2.2 and later" - - ihooks._Verbose.__init__(self, verbose) - # XXX There's a circular reference here: - self.hooks = hooks or RHooks(verbose) - self.hooks.set_rexec(self) - self.modules = {} - self.ok_dynamic_modules = self.ok_builtin_modules - list = [] - for mname in self.ok_builtin_modules: - if mname in sys.builtin_module_names: - list.append(mname) - self.ok_builtin_modules = tuple(list) - self.set_trusted_path() - self.make_builtin() - self.make_initial_modules() - # make_sys must be last because it adds the already created - # modules to its builtin_module_names - self.make_sys() - self.loader = RModuleLoader(self.hooks, verbose) - self.importer = RModuleImporter(self.loader, verbose) - - def set_trusted_path(self): - # Set the path from which dynamic modules may be loaded. - # Those dynamic modules must also occur in ok_builtin_modules - self.trusted_path = filter(os.path.isabs, sys.path) - - def load_dynamic(self, name, filename, file): - if name not in self.ok_dynamic_modules: - raise ImportError, "untrusted dynamic module: %s" % name - if name in sys.modules: - src = sys.modules[name] - else: - src = imp.load_dynamic(name, filename, file) - dst = self.copy_except(src, []) - return dst - - def make_initial_modules(self): - self.make_main() - self.make_osname() - - # Helpers for RHooks - - def get_suffixes(self): - return [item # (suff, mode, type) - for item in imp.get_suffixes() - if item[2] in self.ok_file_types] - - def is_builtin(self, mname): - return mname in self.ok_builtin_modules - - # The make_* methods create specific built-in modules - - def make_builtin(self): - m = self.copy_except(__builtin__, self.nok_builtin_names) - m.__import__ = self.r_import - m.reload = self.r_reload - m.open = m.file = self.r_open - - def make_main(self): - m = self.add_module('__main__') - - def make_osname(self): - osname = os.name - src = __import__(osname) - dst = self.copy_only(src, self.ok_posix_names) - dst.environ = e = {} - for key, value in os.environ.items(): - e[key] = value - - def make_sys(self): - m = self.copy_only(sys, self.ok_sys_names) - m.modules = self.modules - m.argv = ['RESTRICTED'] - m.path = map(None, self.ok_path) - m.exc_info = self.r_exc_info - m = self.modules['sys'] - l = self.modules.keys() + list(self.ok_builtin_modules) - l.sort() - m.builtin_module_names = tuple(l) - - # The copy_* methods copy existing modules with some changes - - def copy_except(self, src, exceptions): - dst = self.copy_none(src) - for name in dir(src): - setattr(dst, name, getattr(src, name)) - for name in exceptions: - try: - delattr(dst, name) - except AttributeError: - pass - return dst - - def copy_only(self, src, names): - dst = self.copy_none(src) - for name in names: - try: - value = getattr(src, name) - except AttributeError: - continue - setattr(dst, name, value) - return dst - - def copy_none(self, src): - m = self.add_module(src.__name__) - m.__doc__ = src.__doc__ - return m - - # Add a module -- return an existing module or create one - - def add_module(self, mname): - m = self.modules.get(mname) - if m is None: - self.modules[mname] = m = self.hooks.new_module(mname) - m.__builtins__ = self.modules['__builtin__'] - return m - - # The r* methods are public interfaces - - def r_exec(self, code): - """Execute code within a restricted environment. - - The code parameter must either be a string containing one or more - lines of Python code, or a compiled code object, which will be - executed in the restricted environment's __main__ module. - - """ - m = self.add_module('__main__') - exec(code, m.__dict__) - - def r_eval(self, code): - """Evaluate code within a restricted environment. - - The code parameter must either be a string containing a Python - expression, or a compiled code object, which will be evaluated in - the restricted environment's __main__ module. The value of the - expression or code object will be returned. - - """ - m = self.add_module('__main__') - return eval(code, m.__dict__) - - def r_execfile(self, file): - """Execute the Python code in the file in the restricted - environment's __main__ module. - - """ - m = self.add_module('__main__') - execfile(file, m.__dict__) - - def r_import(self, mname, globals={}, locals={}, fromlist=[]): - """Import a module, raising an ImportError exception if the module - is considered unsafe. - - This method is implicitly called by code executing in the - restricted environment. Overriding this method in a subclass is - used to change the policies enforced by a restricted environment. - - """ - return self.importer.import_module(mname, globals, locals, fromlist) - - def r_reload(self, m): - """Reload the module object, re-parsing and re-initializing it. - - This method is implicitly called by code executing in the - restricted environment. Overriding this method in a subclass is - used to change the policies enforced by a restricted environment. - - """ - return self.importer.reload(m) - - def r_unload(self, m): - """Unload the module. - - Removes it from the restricted environment's sys.modules dictionary. - - This method is implicitly called by code executing in the - restricted environment. Overriding this method in a subclass is - used to change the policies enforced by a restricted environment. - - """ - return self.importer.unload(m) - - # The s_* methods are similar but also swap std{in,out,err} - - def make_delegate_files(self): - s = self.modules['sys'] - self.delegate_stdin = FileDelegate(s, 'stdin') - self.delegate_stdout = FileDelegate(s, 'stdout') - self.delegate_stderr = FileDelegate(s, 'stderr') - self.restricted_stdin = FileWrapper(sys.stdin) - self.restricted_stdout = FileWrapper(sys.stdout) - self.restricted_stderr = FileWrapper(sys.stderr) - - def set_files(self): - if not hasattr(self, 'save_stdin'): - self.save_files() - if not hasattr(self, 'delegate_stdin'): - self.make_delegate_files() - s = self.modules['sys'] - s.stdin = self.restricted_stdin - s.stdout = self.restricted_stdout - s.stderr = self.restricted_stderr - sys.stdin = self.delegate_stdin - sys.stdout = self.delegate_stdout - sys.stderr = self.delegate_stderr - - def reset_files(self): - self.restore_files() - s = self.modules['sys'] - self.restricted_stdin = s.stdin - self.restricted_stdout = s.stdout - self.restricted_stderr = s.stderr - - - def save_files(self): - self.save_stdin = sys.stdin - self.save_stdout = sys.stdout - self.save_stderr = sys.stderr - - def restore_files(self): - sys.stdin = self.save_stdin - sys.stdout = self.save_stdout - sys.stderr = self.save_stderr - - def s_apply(self, func, args=(), kw={}): - self.save_files() - try: - self.set_files() - r = func(*args, **kw) - finally: - self.restore_files() - return r - - def s_exec(self, *args): - """Execute code within a restricted environment. - - Similar to the r_exec() method, but the code will be granted access - to restricted versions of the standard I/O streams sys.stdin, - sys.stderr, and sys.stdout. - - The code parameter must either be a string containing one or more - lines of Python code, or a compiled code object, which will be - executed in the restricted environment's __main__ module. - - """ - return self.s_apply(self.r_exec, args) - - def s_eval(self, *args): - """Evaluate code within a restricted environment. - - Similar to the r_eval() method, but the code will be granted access - to restricted versions of the standard I/O streams sys.stdin, - sys.stderr, and sys.stdout. - - The code parameter must either be a string containing a Python - expression, or a compiled code object, which will be evaluated in - the restricted environment's __main__ module. The value of the - expression or code object will be returned. - - """ - return self.s_apply(self.r_eval, args) - - def s_execfile(self, *args): - """Execute the Python code in the file in the restricted - environment's __main__ module. - - Similar to the r_execfile() method, but the code will be granted - access to restricted versions of the standard I/O streams sys.stdin, - sys.stderr, and sys.stdout. - - """ - return self.s_apply(self.r_execfile, args) - - def s_import(self, *args): - """Import a module, raising an ImportError exception if the module - is considered unsafe. - - This method is implicitly called by code executing in the - restricted environment. Overriding this method in a subclass is - used to change the policies enforced by a restricted environment. - - Similar to the r_import() method, but has access to restricted - versions of the standard I/O streams sys.stdin, sys.stderr, and - sys.stdout. - - """ - return self.s_apply(self.r_import, args) - - def s_reload(self, *args): - """Reload the module object, re-parsing and re-initializing it. - - This method is implicitly called by code executing in the - restricted environment. Overriding this method in a subclass is - used to change the policies enforced by a restricted environment. - - Similar to the r_reload() method, but has access to restricted - versions of the standard I/O streams sys.stdin, sys.stderr, and - sys.stdout. - - """ - return self.s_apply(self.r_reload, args) - - def s_unload(self, *args): - """Unload the module. - - Removes it from the restricted environment's sys.modules dictionary. - - This method is implicitly called by code executing in the - restricted environment. Overriding this method in a subclass is - used to change the policies enforced by a restricted environment. - - Similar to the r_unload() method, but has access to restricted - versions of the standard I/O streams sys.stdin, sys.stderr, and - sys.stdout. - - """ - return self.s_apply(self.r_unload, args) - - # Restricted open(...) - - def r_open(self, file, mode='r', buf=-1): - """Method called when open() is called in the restricted environment. - - The arguments are identical to those of the open() function, and a - file object (or a class instance compatible with file objects) - should be returned. RExec's default behaviour is allow opening - any file for reading, but forbidding any attempt to write a file. - - This method is implicitly called by code executing in the - restricted environment. Overriding this method in a subclass is - used to change the policies enforced by a restricted environment. - - """ - mode = str(mode) - if mode not in ('r', 'rb'): - raise IOError, "can't open files for writing in restricted mode" - return open(file, mode, buf) - - # Restricted version of sys.exc_info() - - def r_exc_info(self): - ty, va, tr = sys.exc_info() - tr = None - return ty, va, tr - - -def test(): - import getopt, traceback - opts, args = getopt.getopt(sys.argv[1:], 'vt:') - verbose = 0 - trusted = [] - for o, a in opts: - if o == '-v': - verbose = verbose+1 - if o == '-t': - trusted.append(a) - r = RExec(verbose=verbose) - if trusted: - r.ok_builtin_modules = r.ok_builtin_modules + tuple(trusted) - if args: - r.modules['sys'].argv = args - r.modules['sys'].path.insert(0, os.path.dirname(args[0])) - else: - r.modules['sys'].path.insert(0, "") - fp = sys.stdin - if args and args[0] != '-': - try: - fp = open(args[0]) - except IOError as msg: - print("%s: can't open file %r" % (sys.argv[0], args[0])) - return 1 - if fp.isatty(): - try: - import readline - except ImportError: - pass - import code - class RestrictedConsole(code.InteractiveConsole): - def runcode(self, co): - self.locals['__builtins__'] = r.modules['__builtin__'] - r.s_apply(code.InteractiveConsole.runcode, (self, co)) - try: - RestrictedConsole(r.modules['__main__'].__dict__).interact() - except SystemExit as n: - return n - else: - text = fp.read() - fp.close() - c = compile(text, fp.name, 'exec') - try: - r.s_exec(c) - except SystemExit as n: - return n - except: - traceback.print_exc() - return 1 - - -if __name__ == '__main__': - sys.exit(test()) |