diff options
Diffstat (limited to 'Lib/rexec.py')
-rw-r--r-- | Lib/rexec.py | 361 |
1 files changed, 197 insertions, 164 deletions
diff --git a/Lib/rexec.py b/Lib/rexec.py index 52c5eea..3f04391 100644 --- a/Lib/rexec.py +++ b/Lib/rexec.py @@ -1,174 +1,207 @@ -# Implement restricted execution of Python code +"""Restricted execution facilities. +The class RExec exports methods rexec(), reval(), rexecfile(), and +import_module(), 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(). + +""" + + +import sys import __builtin__ -import imp import os -import sys -import types - -def trace(fmt, *args): - if 0: - sys.stderr.write(fmt % args + '\n') - -def copydict(src, dst, exceptions = [], only = None): - if only is None: - for key in src.keys(): - if key not in exceptions: - dst[key] = src[key] - else: - for key in only: - dst[key] = src[key] - -def copymodule(src, dst, exceptions = [], only = None): - copydict(src.__dict__, dst.__dict__, exceptions, only) - -safe_path = ['/usr/local/lib/python'] -safe_modules = ['array', 'math', 'regex', 'strop', 'time'] -unsafe_builtin_names = ['open', 'reload', '__import__', - 'raw_input', 'input'] -safe_posix_names = ['error', 'fstat', 'listdir', 'lstat', 'readlink', 'stat', - 'times', 'uname', 'getpid', 'getppid', 'getcwd', - 'getuid', 'getgid', 'geteuid', 'getegid'] - -safe_sys = imp.new_module('sys') -safe_sys.modules = {} -safe_sys.modules['sys'] = safe_sys -safe_sys.path = safe_path[:] -safe_sys.argv = ['-'] -safe_sys.builtin_module_names = safe_modules[:] + ['posix'] -safe_sys.builtin_module_names.sort() -safe_sys.copyright = sys.copyright -safe_sys.version = sys.version + ' [restricted mode]' -safe_sys.exit = sys.exit - -def new_module(name): - safe_sys.modules[name] = m = imp.new_module(name) +import marshal +import ihooks + + +class RHooks(ihooks.Hooks): + + def __init__(self, rexec, verbose=0): + ihooks.Hooks.__init__(self, verbose) + self.rexec = rexec + + 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_dynamic(self, *args): + raise ImportError, "import of dynamically loaded modules not allowed" + + 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 + + +class RModuleLoader(ihooks.FancyModuleLoader): + + pass + + +class RModuleImporter(ihooks.ModuleImporter): + + pass + + +class RExec(ihooks._Verbose): + + """Restricted Execution environment.""" + + ok_path = tuple(sys.path) # That's a policy decision + + ok_builtin_modules = ('array', 'audioop', 'imageop', 'marshal', 'math', + 'md5', 'parser', 'regex', 'rotor', 'select', + 'strop', 'struct', 'time') + + ok_posix_names = ('error', 'fstat', 'listdir', 'lstat', 'readlink', + 'stat', 'times', 'uname', 'getpid', 'getppid', + 'getcwd', 'getuid', 'getgid', 'geteuid', 'getegid') + + ok_sys_names = ('ps1', 'ps2', 'copyright', 'version', + 'platform', 'exit', 'maxint') + + nok_builtin_names = ('open', 'reload', '__import__', 'raw_input', 'input') + + def __init__(self, hooks = None, verbose = 0): + ihooks._Verbose.__init__(self, verbose) + # XXX There's a circular reference here: + self.hooks = hooks or RHooks(self, verbose) + self.modules = {} + self.ok_builtin_modules = map(None, filter( + lambda mname: mname in sys.builtin_module_names, + self.ok_builtin_modules)) + 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 make_initial_modules(self): + self.make_main() + self.make_osname() + + # Helpers for RHooks + + 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 + + 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 = self.modules['sys'] + m.builtin_module_names = \ + self.modules.keys() + self.ok_builtin_modules + m.builtin_module_names.sort() + + # 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): + if name not in exceptions: + setattr(dst, name, getattr(src, name)) + 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): + return self.add_module(src.__name__) + + # Add a module -- return an existing module or create one + + def add_module(self, mname): + if self.modules.has_key(mname): + return self.modules[mname] + self.modules[mname] = m = self.hooks.new_module(mname) + m.__builtins__ = self.modules['__builtin__'] return m -safe_builtin = new_module('__builtin__') -copymodule(__builtin__, safe_builtin, unsafe_builtin_names) - -safe_main = new_module('__main__') - -safe_posix = new_module('posix') -import posix -copymodule(posix, safe_posix, None, safe_posix_names) -safe_posix.environ = {} -copydict(posix.environ, safe_posix.environ) - -safe_types = new_module('types') -copymodule(types, safe_types) - -def safe_import(name, globals=None, locals=None, fromlist=None): - if '.' in name: - raise ImportError, "import of dotted names not supported" - if safe_sys.modules.has_key(name): - return safe_sys.modules[name] - if name in safe_modules: - temp = {} - exec "import "+name in temp - m = new_module(name) - copymodule(temp[name], m) - return m - for dirname in safe_path: - filename = os.path.join(dirname, name + '.py') - try: - f = open(filename, 'r') - f.close() - except IOError: - continue - m = new_module(name) - rexecfile(filename, m.__dict__) - return m - raise ImportError, name -safe_builtin.__import__ = safe_import - -def safe_open(file, mode = 'r'): - if type(file) != types.StringType or type(mode) != types.StringType: - raise TypeError, 'open argument(s) must be string(s)' - if mode not in ('r', 'rb'): - raise IOError, 'open for writing not allowed' - file = os.path.join(os.getcwd(), file) - file = os.path.normpath(file) - if file[:2] == '//' or file[:5] == '/etc/' or file[:4] == '/../': - raise IOError, 'this path not allowed for reading' - return open(file, mode) -safe_builtin.open = safe_open - - -def exterior(): - """Return env of caller's caller, as triple: (name, locals, globals). - - Name will be None if env is __main__, and locals will be None if same - as globals, ie local env is global env.""" - - import sys, __main__ - - bogus = 'bogus' # A locally usable exception - try: raise bogus # Force an exception - except bogus: - at = sys.exc_traceback.tb_frame.f_back # The external frame. - if at.f_back: at = at.f_back # And further, if any. - where, globals, locals = at.f_code, at.f_globals, at.f_locals - if locals == globals: # Exterior is global? - locals = None - if where: - where = where.co_name - return (where, locals, globals) - - -def rexec(str, globals = None, locals = None): - trace('rexec(%s, ...)', `str`) - if globals is None: - globals = locals = exterior()[2] - elif locals is None: - locals = globals - globals['__builtins__'] = safe_builtin.__dict__ - safe_sys.stdout = sys.stdout - safe_sys.stderr = sys.stderr - exec str in globals, locals - -def rexecfile(file, globals = None, locals = None): - trace('rexecfile(%s, ...)', `file`) - if globals is None: - globals = locals = exterior()[2] - elif locals is None: - locals = globals - globals['__builtins__'] = safe_builtin.__dict__ - safe_sys.stdout = sys.stdout - safe_sys.stderr = sys.stderr - return execfile(file, globals, locals) - -def reval(str, globals = None, locals = None): - trace('reval(%s, ...)', `str`) - if globals is None: - globals = locals = exterior()[2] - elif locals is None: - locals = globals - globals['__builtins__'] = safe_builtin.__dict__ - safe_sys.stdout = sys.stdout - safe_sys.stderr = sys.stderr - return eval(str, globals, locals) -safe_builtin.eval = reval + # The r* methods are public interfaces + + def r_exec(self, code): + m = self.add_module('__main__') + exec code in m.__dict__ + + def r_eval(self, code): + m = self.add_module('__main__') + return eval(code, m.__dict__) + + def r_execfile(self, file): + m = self.add_module('__main__') + return execfile(file, m.__dict__) + + def r_import(self, mname, globals={}, locals={}, fromlist=[]): + return self.importer.import_module(mname, globals, locals, fromlist) def test(): - import traceback - g = {} - while 1: - try: - s = raw_input('--> ') - except EOFError: - break - try: - try: - c = compile(s, '', 'eval') - except: - rexec(s, g) - else: - print reval(c, g) - except: - traceback.print_exc() + import traceback + r = RExec(None, '-v' in sys.argv[1:]) + print "*** RESTRICTED *** Python", sys.version + print sys.copyright + while 1: + try: + try: + s = raw_input('>>> ') + except EOFError: + print + break + if s and s[0] != '#': + s = s + '\n' + c = compile(s, '<stdin>', 'single') + r.r_exec(c) + except SystemExit, n: + sys.exit(n) + except: + traceback.print_exc() + if __name__ == '__main__': - test() + test() |