python-2.5.2/win32/Lib/rexec.py
changeset 0 ae805ac0140d
equal deleted inserted replaced
-1:000000000000 0:ae805ac0140d
       
     1 """Restricted execution facilities.
       
     2 
       
     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().
       
    13 
       
    14 XXX To do:
       
    15 - r_open should allow writing tmp dir
       
    16 - r_exec etc. with explicit globals/locals? (Use rexec("exec ... in ...")?)
       
    17 
       
    18 """
       
    19 
       
    20 
       
    21 import sys
       
    22 import __builtin__
       
    23 import os
       
    24 import ihooks
       
    25 import imp
       
    26 
       
    27 __all__ = ["RExec"]
       
    28 
       
    29 class FileBase:
       
    30 
       
    31     ok_file_methods = ('fileno', 'flush', 'isatty', 'read', 'readline',
       
    32             'readlines', 'seek', 'tell', 'write', 'writelines', 'xreadlines',
       
    33             '__iter__')
       
    34 
       
    35 
       
    36 class FileWrapper(FileBase):
       
    37 
       
    38     # XXX This is just like a Bastion -- should use that!
       
    39 
       
    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))
       
    44 
       
    45     def close(self):
       
    46         self.flush()
       
    47 
       
    48 
       
    49 TEMPLATE = """
       
    50 def %s(self, *args):
       
    51         return getattr(self.mod, self.name).%s(*args)
       
    52 """
       
    53 
       
    54 class FileDelegate(FileBase):
       
    55 
       
    56     def __init__(self, mod, name):
       
    57         self.mod = mod
       
    58         self.name = name
       
    59 
       
    60     for m in FileBase.ok_file_methods + ('close',):
       
    61         exec TEMPLATE % (m, m)
       
    62 
       
    63 
       
    64 class RHooks(ihooks.Hooks):
       
    65 
       
    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
       
    82 
       
    83     def set_rexec(self, rexec):
       
    84         # Called by RExec instance to complete initialization
       
    85         self.rexec = rexec
       
    86 
       
    87     def get_suffixes(self):
       
    88         return self.rexec.get_suffixes()
       
    89 
       
    90     def is_builtin(self, name):
       
    91         return self.rexec.is_builtin(name)
       
    92 
       
    93     def init_builtin(self, name):
       
    94         m = __import__(name)
       
    95         return self.rexec.copy_except(m, ())
       
    96 
       
    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"
       
   101 
       
   102     def load_dynamic(self, name, filename, file):
       
   103         return self.rexec.load_dynamic(name, filename, file)
       
   104 
       
   105     def add_module(self, name):
       
   106         return self.rexec.add_module(name)
       
   107 
       
   108     def modules_dict(self):
       
   109         return self.rexec.modules
       
   110 
       
   111     def default_path(self):
       
   112         return self.rexec.modules['sys'].path
       
   113 
       
   114 
       
   115 # XXX Backwards compatibility
       
   116 RModuleLoader = ihooks.FancyModuleLoader
       
   117 RModuleImporter = ihooks.ModuleImporter
       
   118 
       
   119 
       
   120 class RExec(ihooks._Verbose):
       
   121     """Basic restricted execution framework.
       
   122 
       
   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.
       
   126 
       
   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.
       
   131 
       
   132     """
       
   133 
       
   134     ok_path = tuple(sys.path)           # That's a policy decision
       
   135 
       
   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')
       
   142 
       
   143     ok_posix_names = ('error', 'fstat', 'listdir', 'lstat', 'readlink',
       
   144                       'stat', 'times', 'uname', 'getpid', 'getppid',
       
   145                       'getcwd', 'getuid', 'getgid', 'geteuid', 'getegid')
       
   146 
       
   147     ok_sys_names = ('byteorder', 'copyright', 'exit', 'getdefaultencoding',
       
   148                     'getrefcount', 'hexversion', 'maxint', 'maxunicode',
       
   149                     'platform', 'ps1', 'ps2', 'version', 'version_info')
       
   150 
       
   151     nok_builtin_names = ('open', 'file', 'reload', '__import__')
       
   152 
       
   153     ok_file_types = (imp.C_EXTENSION, imp.PY_SOURCE)
       
   154 
       
   155     def __init__(self, hooks = None, verbose = 0):
       
   156         """Returns an instance of the RExec class.
       
   157 
       
   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.
       
   161 
       
   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.)
       
   170 
       
   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.
       
   178 
       
   179         If the verbose parameter is true, additional debugging output may be
       
   180         sent to standard output.
       
   181 
       
   182         """
       
   183 
       
   184         raise RuntimeError, "This code is not secure in Python 2.2 and later"
       
   185 
       
   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)
       
   205 
       
   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)
       
   210 
       
   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
       
   220 
       
   221     def make_initial_modules(self):
       
   222         self.make_main()
       
   223         self.make_osname()
       
   224 
       
   225     # Helpers for RHooks
       
   226 
       
   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]
       
   231 
       
   232     def is_builtin(self, mname):
       
   233         return mname in self.ok_builtin_modules
       
   234 
       
   235     # The make_* methods create specific built-in modules
       
   236 
       
   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
       
   242 
       
   243     def make_main(self):
       
   244         m = self.add_module('__main__')
       
   245 
       
   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
       
   253 
       
   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)
       
   264 
       
   265     # The copy_* methods copy existing modules with some changes
       
   266 
       
   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
       
   277 
       
   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
       
   287 
       
   288     def copy_none(self, src):
       
   289         m = self.add_module(src.__name__)
       
   290         m.__doc__ = src.__doc__
       
   291         return m
       
   292 
       
   293     # Add a module -- return an existing module or create one
       
   294 
       
   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
       
   301 
       
   302     # The r* methods are public interfaces
       
   303 
       
   304     def r_exec(self, code):
       
   305         """Execute code within a restricted environment.
       
   306 
       
   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.
       
   310 
       
   311         """
       
   312         m = self.add_module('__main__')
       
   313         exec code in m.__dict__
       
   314 
       
   315     def r_eval(self, code):
       
   316         """Evaluate code within a restricted environment.
       
   317 
       
   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.
       
   322 
       
   323         """
       
   324         m = self.add_module('__main__')
       
   325         return eval(code, m.__dict__)
       
   326 
       
   327     def r_execfile(self, file):
       
   328         """Execute the Python code in the file in the restricted
       
   329         environment's __main__ module.
       
   330 
       
   331         """
       
   332         m = self.add_module('__main__')
       
   333         execfile(file, m.__dict__)
       
   334 
       
   335     def r_import(self, mname, globals={}, locals={}, fromlist=[]):
       
   336         """Import a module, raising an ImportError exception if the module
       
   337         is considered unsafe.
       
   338 
       
   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.
       
   342 
       
   343         """
       
   344         return self.importer.import_module(mname, globals, locals, fromlist)
       
   345 
       
   346     def r_reload(self, m):
       
   347         """Reload the module object, re-parsing and re-initializing it.
       
   348 
       
   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.
       
   352 
       
   353         """
       
   354         return self.importer.reload(m)
       
   355 
       
   356     def r_unload(self, m):
       
   357         """Unload the module.
       
   358 
       
   359         Removes it from the restricted environment's sys.modules dictionary.
       
   360 
       
   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.
       
   364 
       
   365         """
       
   366         return self.importer.unload(m)
       
   367 
       
   368     # The s_* methods are similar but also swap std{in,out,err}
       
   369 
       
   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)
       
   378 
       
   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
       
   391 
       
   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
       
   398 
       
   399 
       
   400     def save_files(self):
       
   401         self.save_stdin = sys.stdin
       
   402         self.save_stdout = sys.stdout
       
   403         self.save_stderr = sys.stderr
       
   404 
       
   405     def restore_files(self):
       
   406         sys.stdin = self.save_stdin
       
   407         sys.stdout = self.save_stdout
       
   408         sys.stderr = self.save_stderr
       
   409 
       
   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
       
   418 
       
   419     def s_exec(self, *args):
       
   420         """Execute code within a restricted environment.
       
   421 
       
   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.
       
   425 
       
   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.
       
   429 
       
   430         """
       
   431         return self.s_apply(self.r_exec, args)
       
   432 
       
   433     def s_eval(self, *args):
       
   434         """Evaluate code within a restricted environment.
       
   435 
       
   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.
       
   439 
       
   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.
       
   444 
       
   445         """
       
   446         return self.s_apply(self.r_eval, args)
       
   447 
       
   448     def s_execfile(self, *args):
       
   449         """Execute the Python code in the file in the restricted
       
   450         environment's __main__ module.
       
   451 
       
   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.
       
   455 
       
   456         """
       
   457         return self.s_apply(self.r_execfile, args)
       
   458 
       
   459     def s_import(self, *args):
       
   460         """Import a module, raising an ImportError exception if the module
       
   461         is considered unsafe.
       
   462 
       
   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.
       
   466 
       
   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.
       
   470 
       
   471         """
       
   472         return self.s_apply(self.r_import, args)
       
   473 
       
   474     def s_reload(self, *args):
       
   475         """Reload the module object, re-parsing and re-initializing it.
       
   476 
       
   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.
       
   480 
       
   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.
       
   484 
       
   485         """
       
   486         return self.s_apply(self.r_reload, args)
       
   487 
       
   488     def s_unload(self, *args):
       
   489         """Unload the module.
       
   490 
       
   491         Removes it from the restricted environment's sys.modules dictionary.
       
   492 
       
   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.
       
   496 
       
   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.
       
   500 
       
   501         """
       
   502         return self.s_apply(self.r_unload, args)
       
   503 
       
   504     # Restricted open(...)
       
   505 
       
   506     def r_open(self, file, mode='r', buf=-1):
       
   507         """Method called when open() is called in the restricted environment.
       
   508 
       
   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.
       
   513 
       
   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.
       
   517 
       
   518         """
       
   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)
       
   523 
       
   524     # Restricted version of sys.exc_info()
       
   525 
       
   526     def r_exc_info(self):
       
   527         ty, va, tr = sys.exc_info()
       
   528         tr = None
       
   529         return ty, va, tr
       
   530 
       
   531 
       
   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
       
   582 
       
   583 
       
   584 if __name__ == '__main__':
       
   585     sys.exit(test())