PyXR

c:\python24\lib \ rexec.py



0001 """Restricted execution facilities.
0002 
0003 The class RExec exports methods r_exec(), r_eval(), r_execfile(), and
0004 r_import(), which correspond roughly to the built-in operations
0005 exec, eval(), execfile() and import, but executing the code in an
0006 environment that only exposes those built-in operations that are
0007 deemed safe.  To this end, a modest collection of 'fake' modules is
0008 created which mimics the standard modules by the same names.  It is a
0009 policy decision which built-in modules and operations are made
0010 available; this module provides a reasonable default, but derived
0011 classes can change the policies e.g. by overriding or extending class
0012 variables like ok_builtin_modules or methods like make_sys().
0013 
0014 XXX To do:
0015 - r_open should allow writing tmp dir
0016 - r_exec etc. with explicit globals/locals? (Use rexec("exec ... in ...")?)
0017 
0018 """
0019 
0020 
0021 import sys
0022 import __builtin__
0023 import os
0024 import ihooks
0025 import imp
0026 
0027 __all__ = ["RExec"]
0028 
0029 class FileBase:
0030 
0031     ok_file_methods = ('fileno', 'flush', 'isatty', 'read', 'readline',
0032             'readlines', 'seek', 'tell', 'write', 'writelines', 'xreadlines',
0033             '__iter__')
0034 
0035 
0036 class FileWrapper(FileBase):
0037 
0038     # XXX This is just like a Bastion -- should use that!
0039 
0040     def __init__(self, f):
0041         for m in self.ok_file_methods:
0042             if not hasattr(self, m) and hasattr(f, m):
0043                 setattr(self, m, getattr(f, m))
0044 
0045     def close(self):
0046         self.flush()
0047 
0048 
0049 TEMPLATE = """
0050 def %s(self, *args):
0051         return getattr(self.mod, self.name).%s(*args)
0052 """
0053 
0054 class FileDelegate(FileBase):
0055 
0056     def __init__(self, mod, name):
0057         self.mod = mod
0058         self.name = name
0059 
0060     for m in FileBase.ok_file_methods + ('close',):
0061         exec TEMPLATE % (m, m)
0062 
0063 
0064 class RHooks(ihooks.Hooks):
0065 
0066     def __init__(self, *args):
0067         # Hacks to support both old and new interfaces:
0068         # old interface was RHooks(rexec[, verbose])
0069         # new interface is RHooks([verbose])
0070         verbose = 0
0071         rexec = None
0072         if args and type(args[-1]) == type(0):
0073             verbose = args[-1]
0074             args = args[:-1]
0075         if args and hasattr(args[0], '__class__'):
0076             rexec = args[0]
0077             args = args[1:]
0078         if args:
0079             raise TypeError, "too many arguments"
0080         ihooks.Hooks.__init__(self, verbose)
0081         self.rexec = rexec
0082 
0083     def set_rexec(self, rexec):
0084         # Called by RExec instance to complete initialization
0085         self.rexec = rexec
0086 
0087     def get_suffixes(self):
0088         return self.rexec.get_suffixes()
0089 
0090     def is_builtin(self, name):
0091         return self.rexec.is_builtin(name)
0092 
0093     def init_builtin(self, name):
0094         m = __import__(name)
0095         return self.rexec.copy_except(m, ())
0096 
0097     def init_frozen(self, name): raise SystemError, "don't use this"
0098     def load_source(self, *args): raise SystemError, "don't use this"
0099     def load_compiled(self, *args): raise SystemError, "don't use this"
0100     def load_package(self, *args): raise SystemError, "don't use this"
0101 
0102     def load_dynamic(self, name, filename, file):
0103         return self.rexec.load_dynamic(name, filename, file)
0104 
0105     def add_module(self, name):
0106         return self.rexec.add_module(name)
0107 
0108     def modules_dict(self):
0109         return self.rexec.modules
0110 
0111     def default_path(self):
0112         return self.rexec.modules['sys'].path
0113 
0114 
0115 # XXX Backwards compatibility
0116 RModuleLoader = ihooks.FancyModuleLoader
0117 RModuleImporter = ihooks.ModuleImporter
0118 
0119 
0120 class RExec(ihooks._Verbose):
0121     """Basic restricted execution framework.
0122 
0123     Code executed in this restricted environment will only have access to
0124     modules and functions that are deemed safe; you can subclass RExec to
0125     add or remove capabilities as desired.
0126 
0127     The RExec class can prevent code from performing unsafe operations like
0128     reading or writing disk files, or using TCP/IP sockets.  However, it does
0129     not protect against code using extremely large amounts of memory or
0130     processor time.
0131 
0132     """
0133 
0134     ok_path = tuple(sys.path)           # That's a policy decision
0135 
0136     ok_builtin_modules = ('audioop', 'array', 'binascii',
0137                           'cmath', 'errno', 'imageop',
0138                           'marshal', 'math', 'md5', 'operator',
0139                           'parser', 'regex', 'select',
0140                           'sha', '_sre', 'strop', 'struct', 'time',
0141                           '_weakref')
0142 
0143     ok_posix_names = ('error', 'fstat', 'listdir', 'lstat', 'readlink',
0144                       'stat', 'times', 'uname', 'getpid', 'getppid',
0145                       'getcwd', 'getuid', 'getgid', 'geteuid', 'getegid')
0146 
0147     ok_sys_names = ('byteorder', 'copyright', 'exit', 'getdefaultencoding',
0148                     'getrefcount', 'hexversion', 'maxint', 'maxunicode',
0149                     'platform', 'ps1', 'ps2', 'version', 'version_info')
0150 
0151     nok_builtin_names = ('open', 'file', 'reload', '__import__')
0152 
0153     ok_file_types = (imp.C_EXTENSION, imp.PY_SOURCE)
0154 
0155     def __init__(self, hooks = None, verbose = 0):
0156         """Returns an instance of the RExec class.
0157 
0158         The hooks parameter is an instance of the RHooks class or a subclass
0159         of it.  If it is omitted or None, the default RHooks class is
0160         instantiated.
0161 
0162         Whenever the RExec module searches for a module (even a built-in one)
0163         or reads a module's code, it doesn't actually go out to the file
0164         system itself.  Rather, it calls methods of an RHooks instance that
0165         was passed to or created by its constructor.  (Actually, the RExec
0166         object doesn't make these calls --- they are made by a module loader
0167         object that's part of the RExec object.  This allows another level of
0168         flexibility, which can be useful when changing the mechanics of
0169         import within the restricted environment.)
0170 
0171         By providing an alternate RHooks object, we can control the file
0172         system accesses made to import a module, without changing the
0173         actual algorithm that controls the order in which those accesses are
0174         made.  For instance, we could substitute an RHooks object that
0175         passes all filesystem requests to a file server elsewhere, via some
0176         RPC mechanism such as ILU.  Grail's applet loader uses this to support
0177         importing applets from a URL for a directory.
0178 
0179         If the verbose parameter is true, additional debugging output may be
0180         sent to standard output.
0181 
0182         """
0183 
0184         raise RuntimeError, "This code is not secure in Python 2.2 and 2.3"
0185 
0186         ihooks._Verbose.__init__(self, verbose)
0187         # XXX There's a circular reference here:
0188         self.hooks = hooks or RHooks(verbose)
0189         self.hooks.set_rexec(self)
0190         self.modules = {}
0191         self.ok_dynamic_modules = self.ok_builtin_modules
0192         list = []
0193         for mname in self.ok_builtin_modules:
0194             if mname in sys.builtin_module_names:
0195                 list.append(mname)
0196         self.ok_builtin_modules = tuple(list)
0197         self.set_trusted_path()
0198         self.make_builtin()
0199         self.make_initial_modules()
0200         # make_sys must be last because it adds the already created
0201         # modules to its builtin_module_names
0202         self.make_sys()
0203         self.loader = RModuleLoader(self.hooks, verbose)
0204         self.importer = RModuleImporter(self.loader, verbose)
0205 
0206     def set_trusted_path(self):
0207         # Set the path from which dynamic modules may be loaded.
0208         # Those dynamic modules must also occur in ok_builtin_modules
0209         self.trusted_path = filter(os.path.isabs, sys.path)
0210 
0211     def load_dynamic(self, name, filename, file):
0212         if name not in self.ok_dynamic_modules:
0213             raise ImportError, "untrusted dynamic module: %s" % name
0214         if name in sys.modules:
0215             src = sys.modules[name]
0216         else:
0217             src = imp.load_dynamic(name, filename, file)
0218         dst = self.copy_except(src, [])
0219         return dst
0220 
0221     def make_initial_modules(self):
0222         self.make_main()
0223         self.make_osname()
0224 
0225     # Helpers for RHooks
0226 
0227     def get_suffixes(self):
0228         return [item   # (suff, mode, type)
0229                 for item in imp.get_suffixes()
0230                 if item[2] in self.ok_file_types]
0231 
0232     def is_builtin(self, mname):
0233         return mname in self.ok_builtin_modules
0234 
0235     # The make_* methods create specific built-in modules
0236 
0237     def make_builtin(self):
0238         m = self.copy_except(__builtin__, self.nok_builtin_names)
0239         m.__import__ = self.r_import
0240         m.reload = self.r_reload
0241         m.open = m.file = self.r_open
0242 
0243     def make_main(self):
0244         m = self.add_module('__main__')
0245 
0246     def make_osname(self):
0247         osname = os.name
0248         src = __import__(osname)
0249         dst = self.copy_only(src, self.ok_posix_names)
0250         dst.environ = e = {}
0251         for key, value in os.environ.items():
0252             e[key] = value
0253 
0254     def make_sys(self):
0255         m = self.copy_only(sys, self.ok_sys_names)
0256         m.modules = self.modules
0257         m.argv = ['RESTRICTED']
0258         m.path = map(None, self.ok_path)
0259         m.exc_info = self.r_exc_info
0260         m = self.modules['sys']
0261         l = self.modules.keys() + list(self.ok_builtin_modules)
0262         l.sort()
0263         m.builtin_module_names = tuple(l)
0264 
0265     # The copy_* methods copy existing modules with some changes
0266 
0267     def copy_except(self, src, exceptions):
0268         dst = self.copy_none(src)
0269         for name in dir(src):
0270             setattr(dst, name, getattr(src, name))
0271         for name in exceptions:
0272             try:
0273                 delattr(dst, name)
0274             except AttributeError:
0275                 pass
0276         return dst
0277 
0278     def copy_only(self, src, names):
0279         dst = self.copy_none(src)
0280         for name in names:
0281             try:
0282                 value = getattr(src, name)
0283             except AttributeError:
0284                 continue
0285             setattr(dst, name, value)
0286         return dst
0287 
0288     def copy_none(self, src):
0289         m = self.add_module(src.__name__)
0290         m.__doc__ = src.__doc__
0291         return m
0292 
0293     # Add a module -- return an existing module or create one
0294 
0295     def add_module(self, mname):
0296         m = self.modules.get(mname)
0297         if m is None:
0298             self.modules[mname] = m = self.hooks.new_module(mname)
0299         m.__builtins__ = self.modules['__builtin__']
0300         return m
0301 
0302     # The r* methods are public interfaces
0303 
0304     def r_exec(self, code):
0305         """Execute code within a restricted environment.
0306 
0307         The code parameter must either be a string containing one or more
0308         lines of Python code, or a compiled code object, which will be
0309         executed in the restricted environment's __main__ module.
0310 
0311         """
0312         m = self.add_module('__main__')
0313         exec code in m.__dict__
0314 
0315     def r_eval(self, code):
0316         """Evaluate code within a restricted environment.
0317 
0318         The code parameter must either be a string containing a Python
0319         expression, or a compiled code object, which will be evaluated in
0320         the restricted environment's __main__ module.  The value of the
0321         expression or code object will be returned.
0322 
0323         """
0324         m = self.add_module('__main__')
0325         return eval(code, m.__dict__)
0326 
0327     def r_execfile(self, file):
0328         """Execute the Python code in the file in the restricted
0329         environment's __main__ module.
0330 
0331         """
0332         m = self.add_module('__main__')
0333         execfile(file, m.__dict__)
0334 
0335     def r_import(self, mname, globals={}, locals={}, fromlist=[]):
0336         """Import a module, raising an ImportError exception if the module
0337         is considered unsafe.
0338 
0339         This method is implicitly called by code executing in the
0340         restricted environment.  Overriding this method in a subclass is
0341         used to change the policies enforced by a restricted environment.
0342 
0343         """
0344         return self.importer.import_module(mname, globals, locals, fromlist)
0345 
0346     def r_reload(self, m):
0347         """Reload the module object, re-parsing and re-initializing it.
0348 
0349         This method is implicitly called by code executing in the
0350         restricted environment.  Overriding this method in a subclass is
0351         used to change the policies enforced by a restricted environment.
0352 
0353         """
0354         return self.importer.reload(m)
0355 
0356     def r_unload(self, m):
0357         """Unload the module.
0358 
0359         Removes it from the restricted environment's sys.modules dictionary.
0360 
0361         This method is implicitly called by code executing in the
0362         restricted environment.  Overriding this method in a subclass is
0363         used to change the policies enforced by a restricted environment.
0364 
0365         """
0366         return self.importer.unload(m)
0367 
0368     # The s_* methods are similar but also swap std{in,out,err}
0369 
0370     def make_delegate_files(self):
0371         s = self.modules['sys']
0372         self.delegate_stdin = FileDelegate(s, 'stdin')
0373         self.delegate_stdout = FileDelegate(s, 'stdout')
0374         self.delegate_stderr = FileDelegate(s, 'stderr')
0375         self.restricted_stdin = FileWrapper(sys.stdin)
0376         self.restricted_stdout = FileWrapper(sys.stdout)
0377         self.restricted_stderr = FileWrapper(sys.stderr)
0378 
0379     def set_files(self):
0380         if not hasattr(self, 'save_stdin'):
0381             self.save_files()
0382         if not hasattr(self, 'delegate_stdin'):
0383             self.make_delegate_files()
0384         s = self.modules['sys']
0385         s.stdin = self.restricted_stdin
0386         s.stdout = self.restricted_stdout
0387         s.stderr = self.restricted_stderr
0388         sys.stdin = self.delegate_stdin
0389         sys.stdout = self.delegate_stdout
0390         sys.stderr = self.delegate_stderr
0391 
0392     def reset_files(self):
0393         self.restore_files()
0394         s = self.modules['sys']
0395         self.restricted_stdin = s.stdin
0396         self.restricted_stdout = s.stdout
0397         self.restricted_stderr = s.stderr
0398 
0399 
0400     def save_files(self):
0401         self.save_stdin = sys.stdin
0402         self.save_stdout = sys.stdout
0403         self.save_stderr = sys.stderr
0404 
0405     def restore_files(self):
0406         sys.stdin = self.save_stdin
0407         sys.stdout = self.save_stdout
0408         sys.stderr = self.save_stderr
0409 
0410     def s_apply(self, func, args=(), kw={}):
0411         self.save_files()
0412         try:
0413             self.set_files()
0414             r = func(*args, **kw)
0415         finally:
0416             self.restore_files()
0417         return r
0418 
0419     def s_exec(self, *args):
0420         """Execute code within a restricted environment.
0421 
0422         Similar to the r_exec() method, but the code will be granted access
0423         to restricted versions of the standard I/O streams sys.stdin,
0424         sys.stderr, and sys.stdout.
0425 
0426         The code parameter must either be a string containing one or more
0427         lines of Python code, or a compiled code object, which will be
0428         executed in the restricted environment's __main__ module.
0429 
0430         """
0431         return self.s_apply(self.r_exec, args)
0432 
0433     def s_eval(self, *args):
0434         """Evaluate code within a restricted environment.
0435 
0436         Similar to the r_eval() method, but the code will be granted access
0437         to restricted versions of the standard I/O streams sys.stdin,
0438         sys.stderr, and sys.stdout.
0439 
0440         The code parameter must either be a string containing a Python
0441         expression, or a compiled code object, which will be evaluated in
0442         the restricted environment's __main__ module.  The value of the
0443         expression or code object will be returned.
0444 
0445         """
0446         return self.s_apply(self.r_eval, args)
0447 
0448     def s_execfile(self, *args):
0449         """Execute the Python code in the file in the restricted
0450         environment's __main__ module.
0451 
0452         Similar to the r_execfile() method, but the code will be granted
0453         access to restricted versions of the standard I/O streams sys.stdin,
0454         sys.stderr, and sys.stdout.
0455 
0456         """
0457         return self.s_apply(self.r_execfile, args)
0458 
0459     def s_import(self, *args):
0460         """Import a module, raising an ImportError exception if the module
0461         is considered unsafe.
0462 
0463         This method is implicitly called by code executing in the
0464         restricted environment.  Overriding this method in a subclass is
0465         used to change the policies enforced by a restricted environment.
0466 
0467         Similar to the r_import() method, but has access to restricted
0468         versions of the standard I/O streams sys.stdin, sys.stderr, and
0469         sys.stdout.
0470 
0471         """
0472         return self.s_apply(self.r_import, args)
0473 
0474     def s_reload(self, *args):
0475         """Reload the module object, re-parsing and re-initializing it.
0476 
0477         This method is implicitly called by code executing in the
0478         restricted environment.  Overriding this method in a subclass is
0479         used to change the policies enforced by a restricted environment.
0480 
0481         Similar to the r_reload() method, but has access to restricted
0482         versions of the standard I/O streams sys.stdin, sys.stderr, and
0483         sys.stdout.
0484 
0485         """
0486         return self.s_apply(self.r_reload, args)
0487 
0488     def s_unload(self, *args):
0489         """Unload the module.
0490 
0491         Removes it from the restricted environment's sys.modules dictionary.
0492 
0493         This method is implicitly called by code executing in the
0494         restricted environment.  Overriding this method in a subclass is
0495         used to change the policies enforced by a restricted environment.
0496 
0497         Similar to the r_unload() method, but has access to restricted
0498         versions of the standard I/O streams sys.stdin, sys.stderr, and
0499         sys.stdout.
0500 
0501         """
0502         return self.s_apply(self.r_unload, args)
0503 
0504     # Restricted open(...)
0505 
0506     def r_open(self, file, mode='r', buf=-1):
0507         """Method called when open() is called in the restricted environment.
0508 
0509         The arguments are identical to those of the open() function, and a
0510         file object (or a class instance compatible with file objects)
0511         should be returned.  RExec's default behaviour is allow opening
0512         any file for reading, but forbidding any attempt to write a file.
0513 
0514         This method is implicitly called by code executing in the
0515         restricted environment.  Overriding this method in a subclass is
0516         used to change the policies enforced by a restricted environment.
0517 
0518         """
0519         mode = str(mode)
0520         if mode not in ('r', 'rb'):
0521             raise IOError, "can't open files for writing in restricted mode"
0522         return open(file, mode, buf)
0523 
0524     # Restricted version of sys.exc_info()
0525 
0526     def r_exc_info(self):
0527         ty, va, tr = sys.exc_info()
0528         tr = None
0529         return ty, va, tr
0530 
0531 
0532 def test():
0533     import getopt, traceback
0534     opts, args = getopt.getopt(sys.argv[1:], 'vt:')
0535     verbose = 0
0536     trusted = []
0537     for o, a in opts:
0538         if o == '-v':
0539             verbose = verbose+1
0540         if o == '-t':
0541             trusted.append(a)
0542     r = RExec(verbose=verbose)
0543     if trusted:
0544         r.ok_builtin_modules = r.ok_builtin_modules + tuple(trusted)
0545     if args:
0546         r.modules['sys'].argv = args
0547         r.modules['sys'].path.insert(0, os.path.dirname(args[0]))
0548     else:
0549         r.modules['sys'].path.insert(0, "")
0550     fp = sys.stdin
0551     if args and args[0] != '-':
0552         try:
0553             fp = open(args[0])
0554         except IOError, msg:
0555             print "%s: can't open file %r" % (sys.argv[0], args[0])
0556             return 1
0557     if fp.isatty():
0558         try:
0559             import readline
0560         except ImportError:
0561             pass
0562         import code
0563         class RestrictedConsole(code.InteractiveConsole):
0564             def runcode(self, co):
0565                 self.locals['__builtins__'] = r.modules['__builtin__']
0566                 r.s_apply(code.InteractiveConsole.runcode, (self, co))
0567         try:
0568             RestrictedConsole(r.modules['__main__'].__dict__).interact()
0569         except SystemExit, n:
0570             return n
0571     else:
0572         text = fp.read()
0573         fp.close()
0574         c = compile(text, fp.name, 'exec')
0575         try:
0576             r.s_exec(c)
0577         except SystemExit, n:
0578             return n
0579         except:
0580             traceback.print_exc()
0581             return 1
0582 
0583 
0584 if __name__ == '__main__':
0585     sys.exit(test())
0586 

Generated by PyXR 0.9.4
SourceForge.net Logo