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