def test_server(self, mock_open): with patch('os.path.isfile') as misfile: misfile.return_value = False server = DScanServer((self.settings.host, self.settings.port), AgentHandler, options=self.settings) server_thread = threading.Thread(target=server.serve_forever) # Exit the server thread when the main thread terminates server_thread.daemon = True server_thread.start() log.info(f"Server loop running in thread:{server_thread.name}") context = ssl.create_default_context() context.check_hostname = False context.load_verify_locations(self.settings.sslcert) s = context.wrap_socket(socket(AF_INET, SOCK_STREAM), server_side=False, server_hostname="dscan") s.connect(('127.0.0.1', 9011)) opr = Structure.create(s) hmac_hash = hmac.new(self.settings.secret_key, opr.data, 'sha512') digest = hmac_hash.hexdigest().encode("utf-8") s.sendall(Auth(digest).pack()) s.close() server.shutdown()
def test_breakaway_job(pyfile, target, run): @pyfile def child(): import os from debuggee import backchannel backchannel.send(os.getpid()) backchannel.receive() backchannel.send("ok") @pyfile def parent(): import debuggee import os import subprocess import sys debuggee.setup() argv = [sys.executable, sys.argv[1]] env = os.environ.copy() pipe_in = open(os.devnull, "r") pipe_out = open(os.devnull, "w") CREATE_BREAKAWAY_FROM_JOB = 0x01000000 proc = subprocess.Popen( argv, env=env, stdin=pipe_in, stdout=pipe_out, stderr=pipe_out, creationflags=CREATE_BREAKAWAY_FROM_JOB, ) pipe_in.close() pipe_out.close() proc.wait() with debug.Session() as parent_session: parent_session.config.update({ "redirectOutput": False, "subProcess": False, }) parent_session.expected_exit_code = some.int backchannel = parent_session.open_backchannel() with run(parent_session, target(parent, args=[child])): pass child_pid = backchannel.receive() child_process = psutil.Process(child_pid) parent_session.request("terminate") parent_session.wait_for_exit() # child should still be running backchannel.send("proceed") assert backchannel.receive() == "ok" log.info("Waiting for child process...") child_process.wait()
def test_autokill(daemon, pyfile, target, run): @pyfile def child(): import os from debuggee import backchannel backchannel.send(os.getpid()) while True: pass @pyfile def parent(): import debuggee import os import subprocess import sys debuggee.setup() argv = [sys.executable, sys.argv[1]] env = os.environ.copy() subprocess.Popen( argv, env=env, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, ).wait() with debug.Session() as parent_session: parent_session.expected_exit_code = some.int backchannel = parent_session.open_backchannel() with run(parent_session, target(parent, args=[child])): pass child_config = parent_session.wait_for_next_event("debugpyAttach") parent_session.proceed() with debug.Session(child_config) as child_session: with child_session.start(): pass child_pid = backchannel.receive() assert child_config["subProcessId"] == child_pid child_process = psutil.Process(child_pid) parent_session.request("terminate") child_session.wait_for_exit() log.info("Waiting for child process...") child_process.wait()
def test_flask_template_exception_no_multiproc(start_flask): with debug.Session() as session: with start_flask(session): session.request("setExceptionBreakpoints", {"filters": ["raised", "uncaught"]}) with flask_server: flask_server.get("/badtemplate") stop = session.wait_for_stop( "exception", expected_frames=[ some.dap.frame( some.dap.source(paths.bad_html), name=some.str, # varies depending on Jinja version line=8, ) ], ) exception_info = session.request("exceptionInfo", {"threadId": stop.thread_id}) assert exception_info == some.dict.containing({ "exceptionId": some.str.ending_with("TemplateSyntaxError"), "breakMode": "always", "description": some.str.containing("doesnotexist"), "details": some.dict.containing({ "message": some.str.containing("doesnotexist"), "typeName": some.str.ending_with("TemplateSyntaxError"), }), }) # Exception gets reported again as it is re-raised in every frame between # the template and app.py. The number of frames is a Flask implementation # detail, and varies between versions, so we keep iterating until we see # it reported in app.py. while True: log.info("Exception propagating to next frame...") session.request_continue() stop = session.wait_for_stop("exception") if stop.frames[0] == some.dap.frame(paths.app_py, line=some.int): break # Let the request finish processing and respond with HTTP 500. session.request_continue()
def test_django_template_exception_no_multiproc(start_django): with debug.Session() as session: with start_django(session): session.request("setExceptionBreakpoints", {"filters": ["raised", "uncaught"]}) with django_server: django_server.get("/badtemplate", log_errors=False) stop = session.wait_for_stop( "exception", expected_frames=[ some.dap.frame( some.dap.source(paths.bad_html), line=8, name="Django TemplateSyntaxError", ) ], ) # Will stop once in the plugin exception_info = session.request("exceptionInfo", {"threadId": stop.thread_id}) assert exception_info == some.dict.containing({ "exceptionId": some.str.ending_with("TemplateSyntaxError"), "breakMode": "always", "description": some.str.containing("doesnotexist"), "details": some.dict.containing({ "message": some.str.containing("doesnotexist"), "typeName": some.str.ending_with("TemplateSyntaxError"), }), }) session.request_continue() log.info("Exception will be reported again in {0}", paths.app_py) session.wait_for_stop("exception") session.request_continue()
def test_exceptions_and_exclude_rules(pyfile, target, run, scenario, exc_type): if exc_type == "RuntimeError": @pyfile def code_to_debug(): import debuggee debuggee.setup() raise RuntimeError("unhandled error") # @raise_line elif exc_type == "SystemExit": @pyfile def code_to_debug(): import debuggee import sys debuggee.setup() sys.exit(1) # @raise_line else: pytest.fail(exc_type) if scenario == "exclude_by_name": rules = [{"path": "**/" + code_to_debug.basename, "include": False}] elif scenario == "exclude_by_dir": rules = [{"path": code_to_debug.dirname, "include": False}] else: pytest.fail(scenario) log.info("Rules: {0!j}", rules) with debug.Session() as session: session.expected_exit_code = some.int session.config["rules"] = rules with run(session, target(code_to_debug)): session.request("setExceptionBreakpoints", {"filters": ["raised", "uncaught"]}) # No exceptions should be seen. session.wait_for_next_event("terminated") session.proceed()
def test_client_server_integration(self): server = DScanServer((self.settings_srv.host, self.settings_srv.port), AgentHandler, options=self.settings_srv) server_thread = threading.Thread(target=server.serve_forever) # Exit the server thread when the main thread terminates server_thread.daemon = True agent = Agent(self.settings_agent) try: server_thread.start() log.info(f"Server loop running in thread:{server_thread.name}") out = ContextDisplay(server.ctx) out.show() agent.start() except KeyboardInterrupt: print("asking for shutdown !") server.shutdown() agent.shutdown()
def test_autokill_nodebug(daemon, pyfile, target, run): @pyfile def child(): import os from debuggee import backchannel backchannel.send(os.getpid()) while True: pass @pyfile def parent(): import os import subprocess import sys argv = [sys.executable, sys.argv[1]] env = os.environ.copy() subprocess.Popen( argv, env=env, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, ).wait() with debug.Session() as session: session.expected_exit_code = some.int session.config["noDebug"] = True backchannel = session.open_backchannel() run(session, target(parent, args=[child])) child_pid = backchannel.receive() child_process = psutil.Process(child_pid) session.request("terminate") log.info("Waiting for child process...") child_process.wait()
def callback(pressed): log.info("Pressed: %s", pressed)
def callback(color, unk1, unk2=None): name = COLORS[color] if color is not None else 'NONE' log.info("Color: %s %s %s", name, unk1, unk2)
def test_exceptions_and_partial_exclude_rules(pyfile, target, run, scenario): @pyfile def code_to_debug(): from debug_me import backchannel import sys call_me_back_dir = backchannel.receive() sys.path.insert(0, call_me_back_dir) import call_me_back def call_func(): raise RuntimeError("unhandled error") # @raise call_me_back.call_me_back(call_func) # @call_me_back print("done") call_me_back_dir = test_data / "call_me_back" call_me_back_py = call_me_back_dir / "call_me_back.py" call_me_back_py.lines = code.get_marked_line_numbers(call_me_back_py) if scenario == "exclude_code_to_debug": rules = [{"path": "**/" + code_to_debug.basename, "include": False}] elif scenario == "exclude_callback_dir": rules = [{"path": call_me_back_dir, "include": False}] else: pytest.fail(scenario) log.info("Rules: {0!j}", rules) with debug.Session() as session: session.expected_exit_code = some.int session.config["rules"] = rules backchannel = session.open_backchannel() with run(session, target(code_to_debug)): session.request( "setExceptionBreakpoints", {"filters": ["raised", "uncaught"]} ) backchannel.send(call_me_back_dir) if scenario == "exclude_code_to_debug": # Stop at handled exception, with code_to_debug.py excluded. # # Since the module raising the exception is excluded, it must not stop at # @raise, but rather at @callback (i.e. the closest non-excluded frame). stop = session.wait_for_stop( "exception", expected_frames=[ some.dap.frame( some.dap.source(call_me_back_py), line=call_me_back_py.lines["callback"], ) ], ) assert stop.frames != some.list.containing( [some.dap.frame(some.dap.source(code_to_debug), line=some.int)] ) # As exception unwinds the stack, we shouldn't stop at @call_me_back, # since that line is in the excluded file. Furthermore, although the # exception is unhandled, we shouldn't get a stop for that, either, # because the exception is last seen in an excluded file. session.request_continue() elif scenario == "exclude_callback_dir": # Stop at handled exception, with call_me_back.py excluded. # # Since the module raising the exception is not excluded, it must stop at # @raise. stop = session.wait_for_stop( "exception", expected_frames=[ some.dap.frame( some.dap.source(code_to_debug), name="call_func", line=code_to_debug.lines["raise"], ), some.dap.frame( some.dap.source(code_to_debug), name="<module>", line=code_to_debug.lines["call_me_back"], ), ], ) assert stop.frames != some.list.containing( [some.dap.frame(some.dap.source(call_me_back_py), line=some.int)] ) session.request_continue() # As exception unwinds the stack, it must not stop at @callback, since that # line is in the excluded file. However, it must stop at @call_me_back. stop = session.wait_for_stop( "exception", expected_frames=[ some.dap.frame( some.dap.source(code_to_debug), name="<module>", line=code_to_debug.lines["call_me_back"], ) ], ) assert stop.frames != some.list.containing( [some.dap.frame(some.dap.source(call_me_back_py), line=some.int)] ) session.request_continue() # Now the exception is unhandled, and should be reported as such. stop = session.wait_for_stop( "exception", expected_frames=[ some.dap.frame( some.dap.source(code_to_debug), name="call_func", line=code_to_debug.lines["raise"], ), some.dap.frame( some.dap.source(code_to_debug), name="<module>", line=code_to_debug.lines["call_me_back"], ), ], ) assert stop.frames != some.list.containing( [some.dap.frame(some.dap.source(call_me_back_py), line=some.int)] ) # Let the process crash due to unhandled exception. session.request_continue() else: pytest.fail(scenario)