def _runscript(self, script_str): self.executed_script = script_str self.executed_script_lines = self.executed_script.splitlines() for (i, line) in enumerate(self.executed_script_lines): line_no = i + 1 if line.endswith(BREAKPOINT_STR): self.breakpoints.append(line_no) # populate an extent map to get more accurate ranges from code if self.crazy_mode: # in Py2crazy standard library as Python-2.7.5/Lib/super_dis.py import super_dis try: self.bytecode_map = super_dis.get_bytecode_map( self.executed_script) except: # failure oblivious self.bytecode_map = {} # When bdb sets tracing, a number of call and line events happens # BEFORE debugger even reaches user's code (and the exact sequence of # events depends on python version). So we take special measures to # avoid stopping before we reach the main script (see user_line and # user_call for details). self._wait_for_mainpyfile = 1 # ok, let's try to sorta 'sandbox' the user script by not # allowing certain potentially dangerous operations. user_builtins = {} # ugh, I can't figure out why in Python 2, __builtins__ seems to # be a dict, but in Python 3, __builtins__ seems to be a module, # so just handle both cases ... UGLY! if type(__builtins__) is dict: builtin_items = __builtins__.items() else: assert type(__builtins__) is types.ModuleType builtin_items = [] for k in dir(__builtins__): builtin_items.append((k, getattr(__builtins__, k))) for (k, v) in builtin_items: if k in BANNED_BUILTINS: continue elif k == '__import__': user_builtins[k] = __restricted_import__ else: if k == 'raw_input': user_builtins[k] = raw_input_wrapper elif k == 'input' and is_python3: # Python 3 input() is Python 2 raw_input() user_builtins[k] = raw_input_wrapper else: user_builtins[k] = v user_builtins['mouse_input'] = mouse_input_wrapper # TODO: we can disable these imports here, but a crafty user can # always get a hold of them by importing one of the external # modules, so there's no point in trying security by obscurity user_builtins['setHTML'] = setHTML user_builtins['setCSS'] = setCSS user_builtins['setJS'] = setJS user_stdout = cStringIO.StringIO() sys.stdout = user_stdout user_globals = { "__name__": "__main__", "__builtins__": user_builtins, "__user_stdout__": user_stdout } try: # enforce resource limits RIGHT BEFORE running script_str # set ~200MB virtual memory limit AND a 5-second CPU time # limit (tuned for Webfaction shared hosting) to protect against # memory bombs such as: # x = 2 # while True: x = x*x if resource_module_loaded and (not self.disable_security_checks): resource.setrlimit(resource.RLIMIT_AS, (200000000, 200000000)) resource.setrlimit(resource.RLIMIT_CPU, (5, 5)) # protect against unauthorized filesystem accesses ... resource.setrlimit(resource.RLIMIT_NOFILE, (0, 0)) # no opened files allowed # VERY WEIRD. If you activate this resource limitation, it # ends up generating an EMPTY trace for the following program: # "x = 0\nfor i in range(10):\n x += 1\n print x\n x += 1\n" # (at least on my Webfaction hosting with Python 2.7) #resource.setrlimit(resource.RLIMIT_FSIZE, (0, 0)) # (redundancy for paranoia) # sys.modules contains an in-memory cache of already-loaded # modules, so if you delete modules from here, they will # need to be re-loaded from the filesystem. # # Thus, as an extra precaution, remove these modules so that # they can't be re-imported without opening a new file, # which is disallowed by resource.RLIMIT_NOFILE # # Of course, this isn't a foolproof solution by any means, # and it might lead to UNEXPECTED FAILURES later in execution. del sys.modules['os'] del sys.modules['sys'] self.run(script_str, user_globals, user_globals) # sys.exit ... except SystemExit: #sys.exit(0) raise bdb.BdbQuit except: if DEBUG: traceback.print_exc() trace_entry = dict(event='uncaught_exception') (exc_type, exc_val, exc_tb) = sys.exc_info() if hasattr(exc_val, 'lineno'): trace_entry['line'] = exc_val.lineno if hasattr(exc_val, 'offset'): trace_entry['offset'] = exc_val.offset trace_entry['exception_msg'] = type(exc_val).__name__ + ": " + str( exc_val) # SUPER SUBTLE! if this exact same exception has already been # recorded by the program, then DON'T record it again as an # uncaught_exception already_caught = False for e in self.trace: if e['event'] == 'exception' and e[ 'exception_msg'] == trace_entry['exception_msg']: already_caught = True break if not already_caught: if not self.done: self.trace.append(trace_entry) raise bdb.BdbQuit # need to forceably STOP execution
def _runscript(self, script_str, custom_globals=None): self.executed_script = script_str self.executed_script_lines = self.executed_script.splitlines() for (i, line) in enumerate(self.executed_script_lines): line_no = i + 1 if line.endswith(BREAKPOINT_STR): pass self.breakpoints.append(line_no) # populate an extent map to get more accurate ranges from code if self.crazy_mode: # in Py2crazy standard library as Python-2.7.5/Lib/super_dis.py import super_dis try: self.bytecode_map = super_dis.get_bytecode_map(self.executed_script) except: # failure oblivious self.bytecode_map = {} # When bdb sets tracing, a number of call and line events happens # BEFORE debugger even reaches user's code (and the exact sequence of # events depends on python version). So we take special measures to # avoid stopping before we reach the main script (see user_line and # user_call for details). self._wait_for_mainpyfile = 1 # ok, let's try to sorta 'sandbox' the user script by not # allowing certain potentially dangerous operations. user_builtins = {} # ugh, I can't figure out why in Python 2, __builtins__ seems to # be a dict, but in Python 3, __builtins__ seems to be a module, # so just handle both cases ... UGLY! if type(__builtins__) is dict: builtin_items = __builtins__.items() else: assert type(__builtins__) is types.ModuleType builtin_items = [] for k in dir(__builtins__): builtin_items.append((k, getattr(__builtins__, k))) for (k, v) in builtin_items: if k in BANNED_BUILTINS: continue elif k == '__import__': user_builtins[k] = __restricted_import__ else: if k == 'raw_input': user_builtins[k] = raw_input_wrapper elif k == 'input' and is_python3: # Python 3 input() is Python 2 raw_input() user_builtins[k] = raw_input_wrapper else: user_builtins[k] = v user_builtins['mouse_input'] = mouse_input_wrapper user_builtins['open'] = open_wrapper # TODO: we can disable these imports here, but a crafty user can # always get a hold of them by importing one of the external # modules, so there's no point in trying security by obscurity user_builtins['setHTML'] = setHTML user_builtins['setCSS'] = setCSS user_builtins['setJS'] = setJS user_stdout = cStringIO.StringIO() sys.stdout = user_stdout self.ORIGINAL_STDERR = sys.stderr # don't do this, or else certain kinds of errors, such as syntax # errors, will be silently ignored. WEIRD! #sys.stderr = NullDevice # silence errors user_globals = {"__name__" : "__main__", "__builtins__" : user_builtins, "__user_stdout__" : user_stdout, # sentinel value for frames deriving from a top-level module "__OPT_toplevel__": True} if custom_globals: user_globals.update(custom_globals) try: # enforce resource limits RIGHT BEFORE running script_str # set ~200MB virtual memory limit AND a 5-second CPU time # limit (tuned for Webfaction shared hosting) to protect against # memory bombs such as: # x = 2 # while True: x = x*x if resource_module_loaded and (not self.disable_security_checks): resource.setrlimit(resource.RLIMIT_AS, (200000000, 200000000)) resource.setrlimit(resource.RLIMIT_CPU, (10, 10)) # protect against unauthorized filesystem accesses ... # resource.setrlimit(resource.RLIMIT_NOFILE, (0, 0)) # no opened files allowed # VERY WEIRD. If you activate this resource limitation, it # ends up generating an EMPTY trace for the following program: # "x = 0\nfor i in range(10):\n x += 1\n print x\n x += 1\n" # (at least on my Webfaction hosting with Python 2.7) #resource.setrlimit(resource.RLIMIT_FSIZE, (0, 0)) # (redundancy for paranoia) # The posix module is a built-in and has a ton of OS access # facilities ... if you delete those functions from # sys.modules['posix'], it seems like they're gone EVEN IF # someone else imports posix in a roundabout way. Of course, # I don't know how foolproof this scheme is, though. # (It's not sufficient to just "del sys.modules['posix']"; # it can just be reimported without accessing an external # file and tripping RLIMIT_NOFILE, since the posix module # is baked into the python executable, ergh. Actually DON'T # "del sys.modules['posix']", since re-importing it will # refresh all of the attributes. ergh^2) for a in dir(sys.modules['posix']): delattr(sys.modules['posix'], a) # do the same with os for a in dir(sys.modules['os']): # 'path' is needed for __restricted_import__ to work # and 'stat' is needed for some errors to be reported properly if a not in ('path', 'stat'): delattr(sys.modules['os'], a) # ppl can dig up trashed objects with gc.get_objects() import gc for a in dir(sys.modules['gc']): delattr(sys.modules['gc'], a) del sys.modules['gc'] # sys.modules contains an in-memory cache of already-loaded # modules, so if you delete modules from here, they will # need to be re-loaded from the filesystem. # # Thus, as an extra precaution, remove these modules so that # they can't be re-imported without opening a new file, # which is disallowed by resource.RLIMIT_NOFILE # # Of course, this isn't a foolproof solution by any means, # and it might lead to UNEXPECTED FAILURES later in execution. del sys.modules['os'] del sys.modules['os.path'] del sys.modules['sys'] self.run(script_str, user_globals, user_globals) # sys.exit ... except SystemExit: #sys.exit(0) raise bdb.BdbQuit except: if DEBUG: traceback.print_exc() trace_entry = dict(event='uncaught_exception') (exc_type, exc_val, exc_tb) = sys.exc_info() if hasattr(exc_val, 'lineno'): trace_entry['line'] = exc_val.lineno if hasattr(exc_val, 'offset'): trace_entry['offset'] = exc_val.offset trace_entry['exception_msg'] = type(exc_val).__name__ + ": " + str(exc_val) # SUPER SUBTLE! if this exact same exception has already been # recorded by the program, then DON'T record it again as an # uncaught_exception already_caught = False for e in self.trace: if e['event'] == 'exception' and e['exception_msg'] == trace_entry['exception_msg']: already_caught = True break if not already_caught: if not self.done: self.trace.append(trace_entry) raise bdb.BdbQuit # need to forceably STOP execution
def _runscript(self, script_str): self.executed_script = script_str self.executed_script_lines = self.executed_script.splitlines() for (i, line) in enumerate(self.executed_script_lines): line_no = i + 1 if line.endswith(BREAKPOINT_STR): self.breakpoints.append(line_no) # populate an extent map to get more accurate ranges from code if self.crazy_mode: # in Py2crazy standard library as Python-2.7.5/Lib/super_dis.py import super_dis try: self.bytecode_map = super_dis.get_bytecode_map(self.executed_script) except: # failure oblivious self.bytecode_map = {} # When bdb sets tracing, a number of call and line events happens # BEFORE debugger even reaches user's code (and the exact sequence of # events depends on python version). So we take special measures to # avoid stopping before we reach the main script (see user_line and # user_call for details). self._wait_for_mainpyfile = 1 # ok, let's try to sorta 'sandbox' the user script by not # allowing certain potentially dangerous operations. user_builtins = {} # ugh, I can't figure out why in Python 2, __builtins__ seems to # be a dict, but in Python 3, __builtins__ seems to be a module, # so just handle both cases ... UGLY! if type(__builtins__) is dict: builtin_items = __builtins__.items() else: assert type(__builtins__) is types.ModuleType builtin_items = [] for k in dir(__builtins__): builtin_items.append((k, getattr(__builtins__, k))) for (k, v) in builtin_items: if k in BANNED_BUILTINS: continue elif k == '__import__': user_builtins[k] = __restricted_import__ else: if k == 'raw_input': user_builtins[k] = raw_input_wrapper elif k == 'input' and is_python3: # Python 3 input() is Python 2 raw_input() user_builtins[k] = raw_input_wrapper else: user_builtins[k] = v user_builtins['mouse_input'] = mouse_input_wrapper # TODO: we can disable these imports here, but a crafty user can # always get a hold of them by importing one of the external # modules, so there's no point in trying security by obscurity user_builtins['setHTML'] = setHTML user_builtins['setCSS'] = setCSS user_builtins['setJS'] = setJS user_stdout = cStringIO.StringIO() sys.stdout = user_stdout user_globals = {"__name__" : "__main__", "__builtins__" : user_builtins, "__user_stdout__" : user_stdout} try: # enforce resource limits RIGHT BEFORE running script_str # set ~200MB virtual memory limit AND a 5-second CPU time # limit (tuned for Webfaction shared hosting) to protect against # memory bombs such as: # x = 2 # while True: x = x*x if resource_module_loaded and (not self.disable_security_checks): resource.setrlimit(resource.RLIMIT_AS, (200000000, 200000000)) resource.setrlimit(resource.RLIMIT_CPU, (5, 5)) # protect against unauthorized filesystem accesses ... resource.setrlimit(resource.RLIMIT_NOFILE, (0, 0)) # no opened files allowed # VERY WEIRD. If you activate this resource limitation, it # ends up generating an EMPTY trace for the following program: # "x = 0\nfor i in range(10):\n x += 1\n print x\n x += 1\n" # (at least on my Webfaction hosting with Python 2.7) #resource.setrlimit(resource.RLIMIT_FSIZE, (0, 0)) # (redundancy for paranoia) # sys.modules contains an in-memory cache of already-loaded # modules, so if you delete modules from here, they will # need to be re-loaded from the filesystem. # # Thus, as an extra precaution, remove these modules so that # they can't be re-imported without opening a new file, # which is disallowed by resource.RLIMIT_NOFILE # # Of course, this isn't a foolproof solution by any means, # and it might lead to UNEXPECTED FAILURES later in execution. del sys.modules['os'] del sys.modules['sys'] self.run(script_str, user_globals, user_globals) # sys.exit ... except SystemExit: #sys.exit(0) raise bdb.BdbQuit except: if DEBUG: traceback.print_exc() trace_entry = dict(event='uncaught_exception') (exc_type, exc_val, exc_tb) = sys.exc_info() if hasattr(exc_val, 'lineno'): trace_entry['line'] = exc_val.lineno if hasattr(exc_val, 'offset'): trace_entry['offset'] = exc_val.offset trace_entry['exception_msg'] = type(exc_val).__name__ + ": " + str(exc_val) # SUPER SUBTLE! if this exact same exception has already been # recorded by the program, then DON'T record it again as an # uncaught_exception already_caught = False for e in self.trace: if e['event'] == 'exception' and e['exception_msg'] == trace_entry['exception_msg']: already_caught = True break if not already_caught: if not self.done: self.trace.append(trace_entry) raise bdb.BdbQuit # need to forceably STOP execution
def _runscript(self, script_str, custom_globals=None): self.executed_script = script_str self.executed_script_lines = self.executed_script.splitlines() for (i, line) in enumerate(self.executed_script_lines): line_no = i + 1 if line.endswith(BREAKPOINT_STR): self.breakpoints.append(line_no) # populate an extent map to get more accurate ranges from code if self.crazy_mode: # in Py2crazy standard library as Python-2.7.5/Lib/super_dis.py import super_dis try: self.bytecode_map = super_dis.get_bytecode_map( self.executed_script) except: # failure oblivious self.bytecode_map = {} # When bdb sets tracing, a number of call and line events happens # BEFORE debugger even reaches user's code (and the exact sequence of # events depends on python version). So we take special measures to # avoid stopping before we reach the main script (see user_line and # user_call for details). self._wait_for_mainpyfile = 1 # ok, let's try to sorta 'sandbox' the user script by not # allowing certain potentially dangerous operations. user_builtins = {} # ugh, I can't figure out why in Python 2, __builtins__ seems to # be a dict, but in Python 3, __builtins__ seems to be a module, # so just handle both cases ... UGLY! if type(__builtins__) is dict: builtin_items = __builtins__.items() else: assert type(__builtins__) is types.ModuleType builtin_items = [] for k in dir(__builtins__): builtin_items.append((k, getattr(__builtins__, k))) for (k, v) in builtin_items: if k == 'raw_input': user_builtins[k] = raw_input_wrapper elif k == 'input' and is_python3: # Python 3 input() is Python 2 raw_input() user_builtins[k] = raw_input_wrapper else: user_builtins[k] = v user_builtins['mouse_input'] = mouse_input_wrapper # TODO: we can disable these imports here, but a crafty user can # always get a hold of them by importing one of the external # modules, so there's no point in trying security by obscurity user_builtins['setHTML'] = setHTML user_builtins['setCSS'] = setCSS user_builtins['setJS'] = setJS user_stdout = StringIO.StringIO() sys.stdout = user_stdout self.ORIGINAL_STDERR = sys.stderr # don't do this, or else certain kinds of errors, such as syntax # errors, will be silently ignored. WEIRD! #sys.stderr = NullDevice # silence errors user_globals = { "__name__": "__main__", "__builtins__": user_builtins, "__user_stdout__": user_stdout, # sentinel value for frames deriving from a top-level module "__OPT_toplevel__": True } if custom_globals: user_globals.update(custom_globals) try: self.run(script_str, user_globals, user_globals) # sys.exit ... except SystemExit: #sys.exit(0) raise bdb.BdbQuit except: if DEBUG: traceback.print_exc() trace_entry = dict(event='uncaught_exception') (exc_type, exc_val, exc_tb) = sys.exc_info() if hasattr(exc_val, 'lineno'): trace_entry['line'] = exc_val.lineno if hasattr(exc_val, 'offset'): trace_entry['offset'] = exc_val.offset trace_entry['exception_msg'] = type(exc_val).__name__ + ": " + str( exc_val) # SUPER SUBTLE! if ANY exception has already been recorded by # the program, then DON'T record it again as an uncaught_exception. # This looks kinda weird since the exact exception message doesn't # need to match up, but in practice, there should be at most only # ONE exception per trace. already_caught = False for e in self.trace: if e['event'] == 'exception': already_caught = True break if not already_caught: if not self.done: self.trace.append(trace_entry) raise bdb.BdbQuit # need to forceably STOP execution
def _runscript(self, script_str, custom_globals=None): self.executed_script = script_str self.executed_script_lines = self.executed_script.splitlines() for (i, line) in enumerate(self.executed_script_lines): line_no = i + 1 if line.endswith(BREAKPOINT_STR): self.breakpoints.append(line_no) # populate an extent map to get more accurate ranges from code if self.crazy_mode: # in Py2crazy standard library as Python-2.7.5/Lib/super_dis.py import super_dis try: self.bytecode_map = super_dis.get_bytecode_map( self.executed_script) except: # failure oblivious self.bytecode_map = {} # When bdb sets tracing, a number of call and line events happens # BEFORE debugger even reaches user's code (and the exact sequence of # events depends on python version). So we take special measures to # avoid stopping before we reach the main script (see user_line and # user_call for details). self._wait_for_mainpyfile = 1 user_stdout = StringIO.StringIO() sys.stdout = user_stdout self.ORIGINAL_STDERR = sys.stderr user_globals = { "__name__": "__main__", "__user_stdout__": user_stdout, # sentinel value for frames deriving from a top-level module "__OPT_toplevel__": True } if custom_globals: user_globals.update(custom_globals) try: self.run(script_str, user_globals, user_globals) # sys.exit ... except SystemExit: #sys.exit(0) raise bdb.BdbQuit except: if DEBUG: traceback.print_exc() trace_entry = dict(event='uncaught_exception') (exc_type, exc_val, exc_tb) = sys.exc_info() if hasattr(exc_val, 'lineno'): trace_entry['line'] = exc_val.lineno if hasattr(exc_val, 'offset'): trace_entry['offset'] = exc_val.offset trace_entry['exception_msg'] = type(exc_val).__name__ + ": " + str( exc_val) # SUPER SUBTLE! if ANY exception has already been recorded by # the program, then DON'T record it again as an uncaught_exception. # This looks kinda weird since the exact exception message doesn't # need to match up, but in practice, there should be at most only # ONE exception per trace. already_caught = False for e in self.trace: if e['event'] == 'exception': already_caught = True break if not already_caught: if not self.done: self.trace.append(trace_entry) raise bdb.BdbQuit # need to forceably STOP execution
def _runscript(self, script_str, custom_globals=None): self.executed_script = script_str self.executed_script_lines = self.executed_script.splitlines() for (i, line) in enumerate(self.executed_script_lines): line_no = i + 1 if line.endswith(BREAKPOINT_STR): self.breakpoints.append(line_no) # populate an extent map to get more accurate ranges from code if self.crazy_mode: # in Py2crazy standard library as Python-2.7.5/Lib/super_dis.py import super_dis try: self.bytecode_map = super_dis.get_bytecode_map(self.executed_script) except: # failure oblivious self.bytecode_map = {} # When bdb sets tracing, a number of call and line events happens # BEFORE debugger even reaches user's code (and the exact sequence of # events depends on python version). So we take special measures to # avoid stopping before we reach the main script (see user_line and # user_call for details). self._wait_for_mainpyfile = 1 user_stdout = StringIO.StringIO() sys.stdout = user_stdout self.ORIGINAL_STDERR = sys.stderr user_globals = {"__name__": "__main__", "__user_stdout__": user_stdout, # sentinel value for frames deriving from a top-level module "__OPT_toplevel__": True} if custom_globals: user_globals.update(custom_globals) try: self.run(script_str, user_globals, user_globals) # sys.exit ... except SystemExit: #sys.exit(0) raise bdb.BdbQuit except: if DEBUG: traceback.print_exc() trace_entry = dict(event='uncaught_exception') (exc_type, exc_val, exc_tb) = sys.exc_info() if hasattr(exc_val, 'lineno'): trace_entry['line'] = exc_val.lineno if hasattr(exc_val, 'offset'): trace_entry['offset'] = exc_val.offset trace_entry['exception_msg'] = type(exc_val).__name__ + ": " + str(exc_val) # SUPER SUBTLE! if ANY exception has already been recorded by # the program, then DON'T record it again as an uncaught_exception. # This looks kinda weird since the exact exception message doesn't # need to match up, but in practice, there should be at most only # ONE exception per trace. already_caught = False for e in self.trace: if e['event'] == 'exception': already_caught = True break if not already_caught: if not self.done: self.trace.append(trace_entry) raise bdb.BdbQuit # need to forceably STOP execution
def _runscript(self, script_str, custom_globals=None): self.executed_script = script_str self.executed_script_lines = self.executed_script.splitlines() for (i, line) in enumerate(self.executed_script_lines): line_no = i + 1 if line.endswith(BREAKPOINT_STR): self.breakpoints.append(line_no) # populate an extent map to get more accurate ranges from code if self.crazy_mode: # in Py2crazy standard library as Python-2.7.5/Lib/super_dis.py import super_dis try: self.bytecode_map = super_dis.get_bytecode_map(self.executed_script) except: # failure oblivious self.bytecode_map = {} # When bdb sets tracing, a number of call and line events happens # BEFORE debugger even reaches user's code (and the exact sequence of # events depends on python version). So we take special measures to # avoid stopping before we reach the main script (see user_line and # user_call for details). self._wait_for_mainpyfile = 1 # ok, let's try to sorta 'sandbox' the user script by not # allowing certain potentially dangerous operations. user_builtins = {} # ugh, I can't figure out why in Python 2, __builtins__ seems to # be a dict, but in Python 3, __builtins__ seems to be a module, # so just handle both cases ... UGLY! if type(__builtins__) is dict: builtin_items = __builtins__.items() else: assert type(__builtins__) is types.ModuleType builtin_items = [] for k in dir(__builtins__): builtin_items.append((k, getattr(__builtins__, k))) for (k, v) in builtin_items: if k == 'raw_input': user_builtins[k] = raw_input_wrapper elif k == 'input' and is_python3: # Python 3 input() is Python 2 raw_input() user_builtins[k] = raw_input_wrapper else: user_builtins[k] = v user_builtins['mouse_input'] = mouse_input_wrapper # TODO: we can disable these imports here, but a crafty user can # always get a hold of them by importing one of the external # modules, so there's no point in trying security by obscurity user_builtins['setHTML'] = setHTML user_builtins['setCSS'] = setCSS user_builtins['setJS'] = setJS user_stdout = StringIO.StringIO() sys.stdout = user_stdout self.ORIGINAL_STDERR = sys.stderr # don't do this, or else certain kinds of errors, such as syntax # errors, will be silently ignored. WEIRD! #sys.stderr = NullDevice # silence errors user_globals = {"__name__" : "__main__", "__builtins__" : user_builtins, "__user_stdout__" : user_stdout, # sentinel value for frames deriving from a top-level module "__OPT_toplevel__": True} if custom_globals: user_globals.update(custom_globals) try: self.run(script_str, user_globals, user_globals) # sys.exit ... except SystemExit: #sys.exit(0) raise bdb.BdbQuit except: if DEBUG: traceback.print_exc() trace_entry = dict(event='uncaught_exception') (exc_type, exc_val, exc_tb) = sys.exc_info() if hasattr(exc_val, 'lineno'): trace_entry['line'] = exc_val.lineno if hasattr(exc_val, 'offset'): trace_entry['offset'] = exc_val.offset trace_entry['exception_msg'] = type(exc_val).__name__ + ": " + str(exc_val) # SUPER SUBTLE! if ANY exception has already been recorded by # the program, then DON'T record it again as an uncaught_exception. # This looks kinda weird since the exact exception message doesn't # need to match up, but in practice, there should be at most only # ONE exception per trace. already_caught = False for e in self.trace: if e['event'] == 'exception': already_caught = True break if not already_caught: if not self.done: self.trace.append(trace_entry) raise bdb.BdbQuit # need to forceably STOP execution
def _runscript(self, script_str, custom_globals=None): self.executed_script = script_str self.executed_script_lines = self.executed_script.splitlines() for (i, line) in enumerate(self.executed_script_lines): line_no = i + 1 if line.endswith(BREAKPOINT_STR): pass self.breakpoints.append(line_no) # populate an extent map to get more accurate ranges from code if self.crazy_mode: # in Py2crazy standard library as Python-2.7.5/Lib/super_dis.py import super_dis try: self.bytecode_map = super_dis.get_bytecode_map( self.executed_script) except: # failure oblivious self.bytecode_map = {} # When bdb sets tracing, a number of call and line events happens # BEFORE debugger even reaches user's code (and the exact sequence of # events depends on python version). So we take special measures to # avoid stopping before we reach the main script (see user_line and # user_call for details). self._wait_for_mainpyfile = 1 # ok, let's try to sorta 'sandbox' the user script by not # allowing certain potentially dangerous operations. user_builtins = {} # ugh, I can't figure out why in Python 2, __builtins__ seems to # be a dict, but in Python 3, __builtins__ seems to be a module, # so just handle both cases ... UGLY! if type(__builtins__) is dict: builtin_items = __builtins__.items() else: assert type(__builtins__) is types.ModuleType builtin_items = [] for k in dir(__builtins__): builtin_items.append((k, getattr(__builtins__, k))) for (k, v) in builtin_items: if k in BANNED_BUILTINS: continue elif k == '__import__': user_builtins[k] = __restricted_import__ else: if k == 'raw_input': user_builtins[k] = raw_input_wrapper elif k == 'input' and is_python3: # Python 3 input() is Python 2 raw_input() user_builtins[k] = raw_input_wrapper else: user_builtins[k] = v user_builtins['mouse_input'] = mouse_input_wrapper user_builtins['open'] = open_wrapper # TODO: we can disable these imports here, but a crafty user can # always get a hold of them by importing one of the external # modules, so there's no point in trying security by obscurity user_builtins['setHTML'] = setHTML user_builtins['setCSS'] = setCSS user_builtins['setJS'] = setJS user_stdout = cStringIO.StringIO() sys.stdout = user_stdout self.ORIGINAL_STDERR = sys.stderr # don't do this, or else certain kinds of errors, such as syntax # errors, will be silently ignored. WEIRD! #sys.stderr = NullDevice # silence errors user_globals = { "__name__": "__main__", "__builtins__": user_builtins, "__user_stdout__": user_stdout, # sentinel value for frames deriving from a top-level module "__OPT_toplevel__": True } if custom_globals: user_globals.update(custom_globals) try: # enforce resource limits RIGHT BEFORE running script_str # set ~200MB virtual memory limit AND a 5-second CPU time # limit (tuned for Webfaction shared hosting) to protect against # memory bombs such as: # x = 2 # while True: x = x*x if resource_module_loaded and (not self.disable_security_checks): resource.setrlimit(resource.RLIMIT_AS, (200000000, 200000000)) resource.setrlimit(resource.RLIMIT_CPU, (10, 10)) # protect against unauthorized filesystem accesses ... # resource.setrlimit(resource.RLIMIT_NOFILE, (0, 0)) # no opened files allowed # VERY WEIRD. If you activate this resource limitation, it # ends up generating an EMPTY trace for the following program: # "x = 0\nfor i in range(10):\n x += 1\n print x\n x += 1\n" # (at least on my Webfaction hosting with Python 2.7) #resource.setrlimit(resource.RLIMIT_FSIZE, (0, 0)) # (redundancy for paranoia) # The posix module is a built-in and has a ton of OS access # facilities ... if you delete those functions from # sys.modules['posix'], it seems like they're gone EVEN IF # someone else imports posix in a roundabout way. Of course, # I don't know how foolproof this scheme is, though. # (It's not sufficient to just "del sys.modules['posix']"; # it can just be reimported without accessing an external # file and tripping RLIMIT_NOFILE, since the posix module # is baked into the python executable, ergh. Actually DON'T # "del sys.modules['posix']", since re-importing it will # refresh all of the attributes. ergh^2) for a in dir(sys.modules['posix']): delattr(sys.modules['posix'], a) # do the same with os for a in dir(sys.modules['os']): # 'path' is needed for __restricted_import__ to work # and 'stat' is needed for some errors to be reported properly if a not in ('path', 'stat'): delattr(sys.modules['os'], a) # ppl can dig up trashed objects with gc.get_objects() import gc for a in dir(sys.modules['gc']): delattr(sys.modules['gc'], a) del sys.modules['gc'] # sys.modules contains an in-memory cache of already-loaded # modules, so if you delete modules from here, they will # need to be re-loaded from the filesystem. # # Thus, as an extra precaution, remove these modules so that # they can't be re-imported without opening a new file, # which is disallowed by resource.RLIMIT_NOFILE # # Of course, this isn't a foolproof solution by any means, # and it might lead to UNEXPECTED FAILURES later in execution. del sys.modules['os'] del sys.modules['os.path'] del sys.modules['sys'] self.run(script_str, user_globals, user_globals) # sys.exit ... except SystemExit: #sys.exit(0) raise bdb.BdbQuit except: if DEBUG: traceback.print_exc() trace_entry = dict(event='uncaught_exception') (exc_type, exc_val, exc_tb) = sys.exc_info() if hasattr(exc_val, 'lineno'): trace_entry['line'] = exc_val.lineno if hasattr(exc_val, 'offset'): trace_entry['offset'] = exc_val.offset trace_entry['exception_msg'] = type(exc_val).__name__ + ": " + str( exc_val) # SUPER SUBTLE! if this exact same exception has already been # recorded by the program, then DON'T record it again as an # uncaught_exception already_caught = False for e in self.trace: if e['event'] == 'exception' and e[ 'exception_msg'] == trace_entry['exception_msg']: already_caught = True break if not already_caught: if not self.done: self.trace.append(trace_entry) raise bdb.BdbQuit # need to forceably STOP execution