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