def config_server(self, iteration_timeout): assert self.server is None log.debug("starting sapphire server") # have client error pages (code 4XX) call window.close() after a few seconds sapphire.Sapphire.CLOSE_CLIENT_ERROR = 1 # launch http server used to serve test cases self.server = sapphire.Sapphire(timeout=iteration_timeout) # add include paths to server for url_path, target_path in self.iomanager.server_map.includes: self.server.add_include(url_path, target_path) # add dynamic responses to the server for dyn_rsp in self.iomanager.server_map.dynamic_responses: self.server.add_dynamic_response(dyn_rsp["url"], dyn_rsp["callback"], mime_type=dyn_rsp["mime"]) def _dyn_resp_close(): self.target.close() return b"<h1>Close Browser</h1>" self.server.add_dynamic_response("/close_browser", _dyn_resp_close, mime_type="text/html")
def _run(self, testcase, temp_prefix): """Run a single iteration against the target and determine if it is interesting. This is the low-level iteration function used by `interesting`. Args: testcase (TestCase): The testcase to serve temp_prefix (str): A unique prefix for any files written during this iteration. Returns: Report: Report from reduced testcase if still interesting else None. """ interesting = None # if target is closed and server is alive, we should restart it or else the first request # against /first_test will 404 if self._target.closed and self._server is not None: self._server.close() self._server = None self._server_map.dynamic.clear() self._server_map.redirect.clear() # launch sapphire if needed if self._server is None: # have client error pages (code 4XX) call window.close() after a few seconds self._server = sapphire.Sapphire(auto_close=2) if not self._no_harness: harness = os.path.join(os.path.dirname(__file__), '..', 'common', 'harness.html') with open(harness, 'rb') as harness_fp: harness = harness_fp.read() self._server_map.set_dynamic_response("grz_harness", lambda: harness, mime_type="text/html") self._server_map.set_redirect("grz_current_test", str(self.landing_page), required=False) runner = Runner(self._server, self._target, self._idle_threshold, self._idle_timeout) if self._no_harness: self._server.timeout = self._iter_timeout else: # wait a few extra seconds to avoid races between the harness & sapphire timing out self._server.timeout = self._iter_timeout + 10 # (re)launch Target if self._target.closed: if self._no_harness: location = runner.location( "/grz_current_test", self._server.port) else: location = runner.location( "/grz_harness", self._server.port, close_after=self._target.rl_reset, forced_close=self._target.forced_close, timeout=self._iter_timeout) # Try to launch the browser, retry 4 times at most runner.launch(location, env_mod=self._env_mod, max_retries=4, retry_delay=15) self._target.step() if not self._no_harness: def _dyn_resp_close(): # pragma: no cover if self.target.monitor.is_healthy(): # delay to help catch window close/shutdown related crashes time.sleep(0.1) self.target.close() return b"<h1>Close Browser</h1>" self._server_map.set_dynamic_response("grz_close_browser", _dyn_resp_close, mime_type="text/html") self._server_map.set_redirect("grz_next_test", str(self.landing_page), required=True) # run test case result = runner.run(self._ignore, self._server_map, testcase, wait_for_callback=self._no_harness) testcase.duration = result.duration # handle failure if detected if result.status == RunResult.FAILED: self._target.close() testcase.purge_optional(result.served) # save logs result_logs = temp_prefix + "_logs" if not os.path.exists(result_logs): os.mkdir(result_logs) self._target.save_logs(result_logs) # create report report = Report(result_logs, self._target.binary) short_sig = report.crash_info.createShortSignature() if short_sig == "No crash detected": # XXX: need to change this to support reducing timeouts? LOG.info("Uninteresting: no crash detected") elif self._orig_sig is None or self._orig_sig.matches(report.crash_info): interesting = report LOG.info("Interesting: %s", short_sig) if self._orig_sig is None and not self._any_crash: self._orig_sig = report.crash_signature else: LOG.info("Uninteresting: different signature: %s", short_sig) self.on_other_crash_found(testcase, report) elif result.status == RunResult.IGNORED: LOG.info("Uninteresting: ignored") self._target.close() else: LOG.info("Uninteresting: no failure detected") # trigger relaunch by closing the browser if needed self._target.check_relaunch() return interesting
def _run(self, testcase, temp_prefix): """Run a single iteration against the target and determine if it is interesting. This is the low-level iteration function used by `interesting`. Args: testcase (TestCase): The testcase to serve temp_prefix (str): A unique prefix for any files written during this iteration. Returns: bool: True if reduced testcase is still interesting. """ result = False # if target is closed and server is alive, we should restart it or else the first request # against /first_test will 404 if self.target.closed and self.server is not None: self.server.close() self.server = None # launch sapphire if needed if self.server is None: # have client error pages (code 4XX) call window.close() after a few seconds sapphire.Sapphire.CLOSE_CLIENT_ERROR = 2 self.server = sapphire.Sapphire() if not self.no_harness: harness = os.path.join(os.path.dirname(__file__), '..', 'common', 'harness.html') with open(harness, 'rb') as harness_fp: harness = harness_fp.read() def _dyn_resp_close(): self.target.close() return b"<h1>Close Browser</h1>" self.server.add_dynamic_response("/close_browser", _dyn_resp_close, mime_type="text/html") self.server.add_dynamic_response("/harness", lambda: harness, mime_type="text/html") self.server.set_redirect("/first_test", str(self.landing_page), required=True) if self.no_harness: self.server.timeout = self.iter_timeout else: # wait a few extra seconds to avoid races between the harness & sapphire timing out self.server.timeout = self.iter_timeout + 10 # (re)launch Target if self.target.closed: # Try to launch the browser at most, 4 times for retries in reversed(range(4)): try: self.target.launch(self.location, env_mod=self.env_mod) break except ffpuppet.LaunchError as exc: if retries: LOG.warning(str(exc)) time.sleep(15) else: raise self.target.step() try: idle_timeout_event = threading.Event() iteration_done_event = threading.Event() if self.idle_poll: monitor_launched = threading.Event() poll = threading.Thread(target=self.monitor_process, args=(iteration_done_event, idle_timeout_event, monitor_launched)) poll.start() assert monitor_launched.wait( 30), "Failed to launch monitoring thread" def keep_waiting(): return self.target.monitor.is_healthy( ) and not idle_timeout_event.is_set() if not self.no_harness: self.server.set_redirect("/next_test", str(self.landing_page), required=True) # serve the testcase server_status, files_served = self.server.serve_testcase( testcase, continue_cb=keep_waiting, forever=self.no_harness) # attempt to detect a failure failure_detected = self.target.detect_failure( self.ignore, server_status == sapphire.SERVED_TIMEOUT) # handle failure if detected if failure_detected == Target.RESULT_FAILURE: self.target.close() testcase.purge_optional(files_served) # save logs result_logs = temp_prefix + "_logs" if not os.path.exists(result_logs): os.mkdir(result_logs) self.target.save_logs(result_logs, meta=True) # create a CrashInfo crash = FuzzManagerReporter.create_crash_info( Report.from_path(result_logs), self.target.binary) short_sig = crash.createShortSignature() if short_sig == "No crash detected": # XXX: need to change this to support reducing timeouts? LOG.info("Uninteresting: no crash detected") elif self.orig_sig is None or self.orig_sig.matches(crash): result = True LOG.info("Interesting: %s", short_sig) if self.orig_sig is None and not self.any_crash: max_frames = FuzzManagerReporter.signature_max_frames( crash, 5) self.orig_sig = crash.createCrashSignature( maxFrames=max_frames) else: LOG.info("Uninteresting: different signature: %s", short_sig) if self.alt_crash_cb is not None: self.alt_crash_cb(testcase, temp_prefix) # pylint: disable=not-callable elif failure_detected == Target.RESULT_IGNORED: LOG.info("Uninteresting: ignored") self.target.close() # save logs result_logs = temp_prefix + "_logs" os.mkdir(result_logs) self.target.save_logs(result_logs, meta=True) else: LOG.info("Uninteresting: no failure detected") # trigger relaunch by closing the browser if needed self.target.check_relaunch() finally: iteration_done_event.set() if self.idle_poll: poll.join() return result