Home | History | Annotate | Download | only in python2.7
      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 from warnings import warnpy3k
     20 warnpy3k("the rexec module has been removed in Python 3.0", stacklevel=2)
     21 del warnpy3k
     22 
     23 
     24 import sys
     25 import __builtin__
     26 import os
     27 import ihooks
     28 import imp
     29 
     30 __all__ = ["RExec"]
     31 
     32 class FileBase:
     33 
     34     ok_file_methods = ('fileno', 'flush', 'isatty', 'read', 'readline',
     35             'readlines', 'seek', 'tell', 'write', 'writelines', 'xreadlines',
     36             '__iter__')
     37 
     38 
     39 class FileWrapper(FileBase):
     40 
     41     # XXX This is just like a Bastion -- should use that!
     42 
     43     def __init__(self, f):
     44         for m in self.ok_file_methods:
     45             if not hasattr(self, m) and hasattr(f, m):
     46                 setattr(self, m, getattr(f, m))
     47 
     48     def close(self):
     49         self.flush()
     50 
     51 
     52 TEMPLATE = """
     53 def %s(self, *args):
     54         return getattr(self.mod, self.name).%s(*args)
     55 """
     56 
     57 class FileDelegate(FileBase):
     58 
     59     def __init__(self, mod, name):
     60         self.mod = mod
     61         self.name = name
     62 
     63     for m in FileBase.ok_file_methods + ('close',):
     64         exec TEMPLATE % (m, m)
     65 
     66 
     67 class RHooks(ihooks.Hooks):
     68 
     69     def __init__(self, *args):
     70         # Hacks to support both old and new interfaces:
     71         # old interface was RHooks(rexec[, verbose])
     72         # new interface is RHooks([verbose])
     73         verbose = 0
     74         rexec = None
     75         if args and type(args[-1]) == type(0):
     76             verbose = args[-1]
     77             args = args[:-1]
     78         if args and hasattr(args[0], '__class__'):
     79             rexec = args[0]
     80             args = args[1:]
     81         if args:
     82             raise TypeError, "too many arguments"
     83         ihooks.Hooks.__init__(self, verbose)
     84         self.rexec = rexec
     85 
     86     def set_rexec(self, rexec):
     87         # Called by RExec instance to complete initialization
     88         self.rexec = rexec
     89 
     90     def get_suffixes(self):
     91         return self.rexec.get_suffixes()
     92 
     93     def is_builtin(self, name):
     94         return self.rexec.is_builtin(name)
     95 
     96     def init_builtin(self, name):
     97         m = __import__(name)
     98         return self.rexec.copy_except(m, ())
     99 
    100     def init_frozen(self, name): raise SystemError, "don't use this"
    101     def load_source(self, *args): raise SystemError, "don't use this"
    102     def load_compiled(self, *args): raise SystemError, "don't use this"
    103     def load_package(self, *args): raise SystemError, "don't use this"
    104 
    105     def load_dynamic(self, name, filename, file):
    106         return self.rexec.load_dynamic(name, filename, file)
    107 
    108     def add_module(self, name):
    109         return self.rexec.add_module(name)
    110 
    111     def modules_dict(self):
    112         return self.rexec.modules
    113 
    114     def default_path(self):
    115         return self.rexec.modules['sys'].path
    116 
    117 
    118 # XXX Backwards compatibility
    119 RModuleLoader = ihooks.FancyModuleLoader
    120 RModuleImporter = ihooks.ModuleImporter
    121 
    122 
    123 class RExec(ihooks._Verbose):
    124     """Basic restricted execution framework.
    125 
    126     Code executed in this restricted environment will only have access to
    127     modules and functions that are deemed safe; you can subclass RExec to
    128     add or remove capabilities as desired.
    129 
    130     The RExec class can prevent code from performing unsafe operations like
    131     reading or writing disk files, or using TCP/IP sockets.  However, it does
    132     not protect against code using extremely large amounts of memory or
    133     processor time.
    134 
    135     """
    136 
    137     ok_path = tuple(sys.path)           # That's a policy decision
    138 
    139     ok_builtin_modules = ('audioop', 'array', 'binascii',
    140                           'cmath', 'errno', 'imageop',
    141                           'marshal', 'math', 'md5', 'operator',
    142                           'parser', 'select',
    143                           'sha', '_sre', 'strop', 'struct', 'time',
    144                           '_weakref')
    145 
    146     ok_posix_names = ('error', 'fstat', 'listdir', 'lstat', 'readlink',
    147                       'stat', 'times', 'uname', 'getpid', 'getppid',
    148                       'getcwd', 'getuid', 'getgid', 'geteuid', 'getegid')
    149 
    150     ok_sys_names = ('byteorder', 'copyright', 'exit', 'getdefaultencoding',
    151                     'getrefcount', 'hexversion', 'maxint', 'maxunicode',
    152                     'platform', 'ps1', 'ps2', 'version', 'version_info')
    153 
    154     nok_builtin_names = ('open', 'file', 'reload', '__import__')
    155 
    156     ok_file_types = (imp.C_EXTENSION, imp.PY_SOURCE)
    157 
    158     def __init__(self, hooks = None, verbose = 0):
    159         """Returns an instance of the RExec class.
    160 
    161         The hooks parameter is an instance of the RHooks class or a subclass
    162         of it.  If it is omitted or None, the default RHooks class is
    163         instantiated.
    164 
    165         Whenever the RExec module searches for a module (even a built-in one)
    166         or reads a module's code, it doesn't actually go out to the file
    167         system itself.  Rather, it calls methods of an RHooks instance that
    168         was passed to or created by its constructor.  (Actually, the RExec
    169         object doesn't make these calls --- they are made by a module loader
    170         object that's part of the RExec object.  This allows another level of
    171         flexibility, which can be useful when changing the mechanics of
    172         import within the restricted environment.)
    173 
    174         By providing an alternate RHooks object, we can control the file
    175         system accesses made to import a module, without changing the
    176         actual algorithm that controls the order in which those accesses are
    177         made.  For instance, we could substitute an RHooks object that
    178         passes all filesystem requests to a file server elsewhere, via some
    179         RPC mechanism such as ILU.  Grail's applet loader uses this to support
    180         importing applets from a URL for a directory.
    181 
    182         If the verbose parameter is true, additional debugging output may be
    183         sent to standard output.
    184 
    185         """
    186 
    187         raise RuntimeError, "This code is not secure in Python 2.2 and later"
    188 
    189         ihooks._Verbose.__init__(self, verbose)
    190         # XXX There's a circular reference here:
    191         self.hooks = hooks or RHooks(verbose)
    192         self.hooks.set_rexec(self)
    193         self.modules = {}
    194         self.ok_dynamic_modules = self.ok_builtin_modules
    195         list = []
    196         for mname in self.ok_builtin_modules:
    197             if mname in sys.builtin_module_names:
    198                 list.append(mname)
    199         self.ok_builtin_modules = tuple(list)
    200         self.set_trusted_path()
    201         self.make_builtin()
    202         self.make_initial_modules()
    203         # make_sys must be last because it adds the already created
    204         # modules to its builtin_module_names
    205         self.make_sys()
    206         self.loader = RModuleLoader(self.hooks, verbose)
    207         self.importer = RModuleImporter(self.loader, verbose)
    208 
    209     def set_trusted_path(self):
    210         # Set the path from which dynamic modules may be loaded.
    211         # Those dynamic modules must also occur in ok_builtin_modules
    212         self.trusted_path = filter(os.path.isabs, sys.path)
    213 
    214     def load_dynamic(self, name, filename, file):
    215         if name not in self.ok_dynamic_modules:
    216             raise ImportError, "untrusted dynamic module: %s" % name
    217         if name in sys.modules:
    218             src = sys.modules[name]
    219         else:
    220             src = imp.load_dynamic(name, filename, file)
    221         dst = self.copy_except(src, [])
    222         return dst
    223 
    224     def make_initial_modules(self):
    225         self.make_main()
    226         self.make_osname()
    227 
    228     # Helpers for RHooks
    229 
    230     def get_suffixes(self):
    231         return [item   # (suff, mode, type)
    232                 for item in imp.get_suffixes()
    233                 if item[2] in self.ok_file_types]
    234 
    235     def is_builtin(self, mname):
    236         return mname in self.ok_builtin_modules
    237 
    238     # The make_* methods create specific built-in modules
    239 
    240     def make_builtin(self):
    241         m = self.copy_except(__builtin__, self.nok_builtin_names)
    242         m.__import__ = self.r_import
    243         m.reload = self.r_reload
    244         m.open = m.file = self.r_open
    245 
    246     def make_main(self):
    247         self.add_module('__main__')
    248 
    249     def make_osname(self):
    250         osname = os.name
    251         src = __import__(osname)
    252         dst = self.copy_only(src, self.ok_posix_names)
    253         dst.environ = e = {}
    254         for key, value in os.environ.items():
    255             e[key] = value
    256 
    257     def make_sys(self):
    258         m = self.copy_only(sys, self.ok_sys_names)
    259         m.modules = self.modules
    260         m.argv = ['RESTRICTED']
    261         m.path = map(None, self.ok_path)
    262         m.exc_info = self.r_exc_info
    263         m = self.modules['sys']
    264         l = self.modules.keys() + list(self.ok_builtin_modules)
    265         l.sort()
    266         m.builtin_module_names = tuple(l)
    267 
    268     # The copy_* methods copy existing modules with some changes
    269 
    270     def copy_except(self, src, exceptions):
    271         dst = self.copy_none(src)
    272         for name in dir(src):
    273             setattr(dst, name, getattr(src, name))
    274         for name in exceptions:
    275             try:
    276                 delattr(dst, name)
    277             except AttributeError:
    278                 pass
    279         return dst
    280 
    281     def copy_only(self, src, names):
    282         dst = self.copy_none(src)
    283         for name in names:
    284             try:
    285                 value = getattr(src, name)
    286             except AttributeError:
    287                 continue
    288             setattr(dst, name, value)
    289         return dst
    290 
    291     def copy_none(self, src):
    292         m = self.add_module(src.__name__)
    293         m.__doc__ = src.__doc__
    294         return m
    295 
    296     # Add a module -- return an existing module or create one
    297 
    298     def add_module(self, mname):
    299         m = self.modules.get(mname)
    300         if m is None:
    301             self.modules[mname] = m = self.hooks.new_module(mname)
    302         m.__builtins__ = self.modules['__builtin__']
    303         return m
    304 
    305     # The r* methods are public interfaces
    306 
    307     def r_exec(self, code):
    308         """Execute code within a restricted environment.
    309 
    310         The code parameter must either be a string containing one or more
    311         lines of Python code, or a compiled code object, which will be
    312         executed in the restricted environment's __main__ module.
    313 
    314         """
    315         m = self.add_module('__main__')
    316         exec code in m.__dict__
    317 
    318     def r_eval(self, code):
    319         """Evaluate code within a restricted environment.
    320 
    321         The code parameter must either be a string containing a Python
    322         expression, or a compiled code object, which will be evaluated in
    323         the restricted environment's __main__ module.  The value of the
    324         expression or code object will be returned.
    325 
    326         """
    327         m = self.add_module('__main__')
    328         return eval(code, m.__dict__)
    329 
    330     def r_execfile(self, file):
    331         """Execute the Python code in the file in the restricted
    332         environment's __main__ module.
    333 
    334         """
    335         m = self.add_module('__main__')
    336         execfile(file, m.__dict__)
    337 
    338     def r_import(self, mname, globals={}, locals={}, fromlist=[]):
    339         """Import a module, raising an ImportError exception if the module
    340         is considered unsafe.
    341 
    342         This method is implicitly called by code executing in the
    343         restricted environment.  Overriding this method in a subclass is
    344         used to change the policies enforced by a restricted environment.
    345 
    346         """
    347         return self.importer.import_module(mname, globals, locals, fromlist)
    348 
    349     def r_reload(self, m):
    350         """Reload the module object, re-parsing and re-initializing it.
    351 
    352         This method is implicitly called by code executing in the
    353         restricted environment.  Overriding this method in a subclass is
    354         used to change the policies enforced by a restricted environment.
    355 
    356         """
    357         return self.importer.reload(m)
    358 
    359     def r_unload(self, m):
    360         """Unload the module.
    361 
    362         Removes it from the restricted environment's sys.modules dictionary.
    363 
    364         This method is implicitly called by code executing in the
    365         restricted environment.  Overriding this method in a subclass is
    366         used to change the policies enforced by a restricted environment.
    367 
    368         """
    369         return self.importer.unload(m)
    370 
    371     # The s_* methods are similar but also swap std{in,out,err}
    372 
    373     def make_delegate_files(self):
    374         s = self.modules['sys']
    375         self.delegate_stdin = FileDelegate(s, 'stdin')
    376         self.delegate_stdout = FileDelegate(s, 'stdout')
    377         self.delegate_stderr = FileDelegate(s, 'stderr')
    378         self.restricted_stdin = FileWrapper(sys.stdin)
    379         self.restricted_stdout = FileWrapper(sys.stdout)
    380         self.restricted_stderr = FileWrapper(sys.stderr)
    381 
    382     def set_files(self):
    383         if not hasattr(self, 'save_stdin'):
    384             self.save_files()
    385         if not hasattr(self, 'delegate_stdin'):
    386             self.make_delegate_files()
    387         s = self.modules['sys']
    388         s.stdin = self.restricted_stdin
    389         s.stdout = self.restricted_stdout
    390         s.stderr = self.restricted_stderr
    391         sys.stdin = self.delegate_stdin
    392         sys.stdout = self.delegate_stdout
    393         sys.stderr = self.delegate_stderr
    394 
    395     def reset_files(self):
    396         self.restore_files()
    397         s = self.modules['sys']
    398         self.restricted_stdin = s.stdin
    399         self.restricted_stdout = s.stdout
    400         self.restricted_stderr = s.stderr
    401 
    402 
    403     def save_files(self):
    404         self.save_stdin = sys.stdin
    405         self.save_stdout = sys.stdout
    406         self.save_stderr = sys.stderr
    407 
    408     def restore_files(self):
    409         sys.stdin = self.save_stdin
    410         sys.stdout = self.save_stdout
    411         sys.stderr = self.save_stderr
    412 
    413     def s_apply(self, func, args=(), kw={}):
    414         self.save_files()
    415         try:
    416             self.set_files()
    417             r = func(*args, **kw)
    418         finally:
    419             self.restore_files()
    420         return r
    421 
    422     def s_exec(self, *args):
    423         """Execute code within a restricted environment.
    424 
    425         Similar to the r_exec() method, but the code will be granted access
    426         to restricted versions of the standard I/O streams sys.stdin,
    427         sys.stderr, and sys.stdout.
    428 
    429         The code parameter must either be a string containing one or more
    430         lines of Python code, or a compiled code object, which will be
    431         executed in the restricted environment's __main__ module.
    432 
    433         """
    434         return self.s_apply(self.r_exec, args)
    435 
    436     def s_eval(self, *args):
    437         """Evaluate code within a restricted environment.
    438 
    439         Similar to the r_eval() method, but the code will be granted access
    440         to restricted versions of the standard I/O streams sys.stdin,
    441         sys.stderr, and sys.stdout.
    442 
    443         The code parameter must either be a string containing a Python
    444         expression, or a compiled code object, which will be evaluated in
    445         the restricted environment's __main__ module.  The value of the
    446         expression or code object will be returned.
    447 
    448         """
    449         return self.s_apply(self.r_eval, args)
    450 
    451     def s_execfile(self, *args):
    452         """Execute the Python code in the file in the restricted
    453         environment's __main__ module.
    454 
    455         Similar to the r_execfile() method, but the code will be granted
    456         access to restricted versions of the standard I/O streams sys.stdin,
    457         sys.stderr, and sys.stdout.
    458 
    459         """
    460         return self.s_apply(self.r_execfile, args)
    461 
    462     def s_import(self, *args):
    463         """Import a module, raising an ImportError exception if the module
    464         is considered unsafe.
    465 
    466         This method is implicitly called by code executing in the
    467         restricted environment.  Overriding this method in a subclass is
    468         used to change the policies enforced by a restricted environment.
    469 
    470         Similar to the r_import() method, but has access to restricted
    471         versions of the standard I/O streams sys.stdin, sys.stderr, and
    472         sys.stdout.
    473 
    474         """
    475         return self.s_apply(self.r_import, args)
    476 
    477     def s_reload(self, *args):
    478         """Reload the module object, re-parsing and re-initializing it.
    479 
    480         This method is implicitly called by code executing in the
    481         restricted environment.  Overriding this method in a subclass is
    482         used to change the policies enforced by a restricted environment.
    483 
    484         Similar to the r_reload() method, but has access to restricted
    485         versions of the standard I/O streams sys.stdin, sys.stderr, and
    486         sys.stdout.
    487 
    488         """
    489         return self.s_apply(self.r_reload, args)
    490 
    491     def s_unload(self, *args):
    492         """Unload the module.
    493 
    494         Removes it from the restricted environment's sys.modules dictionary.
    495 
    496         This method is implicitly called by code executing in the
    497         restricted environment.  Overriding this method in a subclass is
    498         used to change the policies enforced by a restricted environment.
    499 
    500         Similar to the r_unload() method, but has access to restricted
    501         versions of the standard I/O streams sys.stdin, sys.stderr, and
    502         sys.stdout.
    503 
    504         """
    505         return self.s_apply(self.r_unload, args)
    506 
    507     # Restricted open(...)
    508 
    509     def r_open(self, file, mode='r', buf=-1):
    510         """Method called when open() is called in the restricted environment.
    511 
    512         The arguments are identical to those of the open() function, and a
    513         file object (or a class instance compatible with file objects)
    514         should be returned.  RExec's default behaviour is allow opening
    515         any file for reading, but forbidding any attempt to write a file.
    516 
    517         This method is implicitly called by code executing in the
    518         restricted environment.  Overriding this method in a subclass is
    519         used to change the policies enforced by a restricted environment.
    520 
    521         """
    522         mode = str(mode)
    523         if mode not in ('r', 'rb'):
    524             raise IOError, "can't open files for writing in restricted mode"
    525         return open(file, mode, buf)
    526 
    527     # Restricted version of sys.exc_info()
    528 
    529     def r_exc_info(self):
    530         ty, va, tr = sys.exc_info()
    531         tr = None
    532         return ty, va, tr
    533 
    534 
    535 def test():
    536     import getopt, traceback
    537     opts, args = getopt.getopt(sys.argv[1:], 'vt:')
    538     verbose = 0
    539     trusted = []
    540     for o, a in opts:
    541         if o == '-v':
    542             verbose = verbose+1
    543         if o == '-t':
    544             trusted.append(a)
    545     r = RExec(verbose=verbose)
    546     if trusted:
    547         r.ok_builtin_modules = r.ok_builtin_modules + tuple(trusted)
    548     if args:
    549         r.modules['sys'].argv = args
    550         r.modules['sys'].path.insert(0, os.path.dirname(args[0]))
    551     else:
    552         r.modules['sys'].path.insert(0, "")
    553     fp = sys.stdin
    554     if args and args[0] != '-':
    555         try:
    556             fp = open(args[0])
    557         except IOError, msg:
    558             print "%s: can't open file %r" % (sys.argv[0], args[0])
    559             return 1
    560     if fp.isatty():
    561         try:
    562             import readline
    563         except ImportError:
    564             pass
    565         import code
    566         class RestrictedConsole(code.InteractiveConsole):
    567             def runcode(self, co):
    568                 self.locals['__builtins__'] = r.modules['__builtin__']
    569                 r.s_apply(code.InteractiveConsole.runcode, (self, co))
    570         try:
    571             RestrictedConsole(r.modules['__main__'].__dict__).interact()
    572         except SystemExit, n:
    573             return n
    574     else:
    575         text = fp.read()
    576         fp.close()
    577         c = compile(text, fp.name, 'exec')
    578         try:
    579             r.s_exec(c)
    580         except SystemExit, n:
    581             return n
    582         except:
    583             traceback.print_exc()
    584             return 1
    585 
    586 
    587 if __name__ == '__main__':
    588     sys.exit(test())
    589