def minimize_corpus(self, target_path, arguments, input_dirs, output_dir, reproducers_dir, max_time): """Optional (but recommended): run corpus minimization. Args: target_path: Path to the target. arguments: Additional arguments needed for corpus minimization. input_dirs: Input corpora. output_dir: Output directory to place minimized corpus. reproducers_dir: The directory to put reproducers in when crashes are found. max_time: Maximum allowed time for the minimization. Returns: A Result object. """ runner = libfuzzer.get_runner(target_path) libfuzzer.set_sanitizer_options(target_path) merge_tmp_dir = self._create_temp_corpus_dir('merge-workdir') merge_result = runner.merge([output_dir] + input_dirs, merge_timeout=max_time, tmp_dir=merge_tmp_dir, additional_args=arguments, artifact_prefix=reproducers_dir) if merge_result.timed_out: raise MergeError('Merging new testcases timed out') if merge_result.return_code != 0: raise MergeError('Merging new testcases failed') # TODO(ochang): Get crashes found during merge. return engine.FuzzResult(merge_result.output, merge_result.command, [], {}, merge_result.time_executed)
def cleanse(self, target_path, arguments, input_path, output_path, max_time): """Optional (but recommended): Cleanse a testcase. Args: target_path: Path to the target. arguments: Additional arguments needed for testcase cleanse. input_path: Path to the reproducer input. output_path: Path to the cleansed output. max_time: Maximum allowed time for the cleanse. Returns: A ReproduceResult. """ runner = libfuzzer.get_runner(target_path) libfuzzer.set_sanitizer_options(target_path) cleanse_tmp_dir = self._create_temp_corpus_dir('cleanse-workdir') result = runner.cleanse_crash(input_path, output_path, max_time, artifact_prefix=cleanse_tmp_dir, additional_args=arguments) return engine.ReproduceResult(result.command, result.return_code, result.time_executed, result.output)
def reproduce(self, target_path, input_path, arguments, max_time): """Reproduce a crash given an input. Args: target_path: Path to the target. input_path: Path to the reproducer input. arguments: Additional arguments needed for reproduction. max_time: Maximum allowed time for the reproduction. Returns: A ReproduceResult. """ runner = libfuzzer.get_runner(target_path) libfuzzer.set_sanitizer_options(target_path) # Remove fuzzing specific arguments. This is only really needed for legacy # testcases, and can be removed in the distant future. arguments = arguments[:] libfuzzer.remove_fuzzing_arguments(arguments) runs_argument = constants.RUNS_FLAG + str(constants.RUNS_TO_REPRODUCE) arguments.append(runs_argument) result = runner.run_single_testcase(input_path, timeout=max_time, additional_args=arguments) return engine.ReproduceResult(result.command, result.return_code, result.time_executed, result.output)
def minimize_testcase(self, target_path, arguments, input_path, output_path, max_time): """Optional (but recommended): Minimize a testcase. Args: target_path: Path to the target. arguments: Additional arguments needed for testcase minimization. input_path: Path to the reproducer input. output_path: Path to the minimized output. max_time: Maximum allowed time for the minimization. Returns: A ReproduceResult. Raises: TimeoutError: If the testcase minimization exceeds max_time. """ runner = libfuzzer.get_runner(target_path) libfuzzer.set_sanitizer_options(target_path) minimize_tmp_dir = self._create_temp_corpus_dir('minimize-workdir') result = runner.minimize_crash(input_path, output_path, max_time, artifact_prefix=minimize_tmp_dir, additional_args=arguments) if result.timed_out: raise TimeoutError('Minimization timed out\n' + result.output) return engine.ReproduceResult(result.command, result.return_code, result.time_executed, result.output)
def minimize_corpus(self, target_path, arguments, input_dirs, output_dir, reproducers_dir, max_time): """Optional (but recommended): run corpus minimization. Args: target_path: Path to the target. arguments: Additional arguments needed for corpus minimization. input_dirs: Input corpora. output_dir: Output directory to place minimized corpus. reproducers_dir: The directory to put reproducers in when crashes are found. max_time: Maximum allowed time for the minimization. Returns: A Result object. Raises: TimeoutError: If the corpus minimization exceeds max_time. Error: If the merge failed in some other way. """ runner = libfuzzer.get_runner(target_path) libfuzzer.set_sanitizer_options(target_path) merge_tmp_dir = self._create_temp_corpus_dir('merge-workdir') result = runner.merge([output_dir] + input_dirs, merge_timeout=max_time, tmp_dir=merge_tmp_dir, additional_args=arguments, artifact_prefix=reproducers_dir, merge_control_file=getattr( self, '_merge_control_file', None)) if result.timed_out: raise TimeoutError('Merging new testcases timed out\n' + result.output) if result.return_code != 0: raise MergeError('Merging new testcases failed: ' + result.output) merge_output = result.output merge_stats = stats.parse_stats_from_merge_log( merge_output.splitlines()) # TODO(ochang): Get crashes found during merge. return engine.FuzzResult(merge_output, result.command, [], merge_stats, result.time_executed)
def fuzz(self, target_path, options, reproducers_dir, max_time): """Run a fuzz session. Args: target_path: Path to the target. options: The FuzzOptions object returned by prepare(). reproducers_dir: The directory to put reproducers in when crashes are found. max_time: Maximum allowed time for the fuzzing to run. Returns: A FuzzResult object. """ profiler.start_if_needed('libfuzzer_fuzz') runner = libfuzzer.get_runner(target_path) libfuzzer.set_sanitizer_options(target_path) # Directory to place new units. new_corpus_dir = self._create_temp_corpus_dir('new') corpus_directories = [new_corpus_dir] + options.fuzz_corpus_dirs fuzz_timeout = libfuzzer.get_fuzz_timeout(options.is_mutations_run, total_timeout=max_time) fuzz_result = runner.fuzz(corpus_directories, fuzz_timeout=fuzz_timeout, additional_args=options.arguments, artifact_prefix=reproducers_dir, extra_env=options.extra_env) if (not environment.get_value('USE_MINIJAIL') and fuzz_result.return_code == constants.LIBFUZZER_ERROR_EXITCODE): # Minijail returns 1 if the exit code is nonzero. # Otherwise: we can assume that a return code of 1 means that libFuzzer # itself ran into an error. logs.log_error(ENGINE_ERROR_MESSAGE, engine_output=fuzz_result.output) log_lines = utils.decode_to_unicode(fuzz_result.output).splitlines() # Output can be large, so save some memory by removing reference to the # original output which is no longer needed. fuzz_result.output = None # Check if we crashed, and get the crash testcase path. crash_testcase_file_path = runner.get_testcase_path(log_lines) # Parse stats information based on libFuzzer output. parsed_stats = libfuzzer.parse_log_stats(log_lines) # Extend parsed stats by additional performance features. parsed_stats.update( stats.parse_performance_features(log_lines, options.strategies, options.arguments, include_strategies=False)) # Set some initial stat overrides. timeout_limit = fuzzer_utils.extract_argument(options.arguments, constants.TIMEOUT_FLAG, remove=False) expected_duration = runner.get_max_total_time(fuzz_timeout) actual_duration = int(fuzz_result.time_executed) fuzzing_time_percent = 100 * actual_duration / float(expected_duration) parsed_stats.update({ 'timeout_limit': int(timeout_limit), 'expected_duration': expected_duration, 'actual_duration': actual_duration, 'fuzzing_time_percent': fuzzing_time_percent, }) # Remove fuzzing arguments before merge and dictionary analysis step. arguments = options.arguments[:] libfuzzer.remove_fuzzing_arguments(arguments) self._merge_new_units(target_path, options.corpus_dir, new_corpus_dir, options.fuzz_corpus_dirs, arguments, parsed_stats) fuzz_logs = '\n'.join(log_lines) crashes = [] if crash_testcase_file_path: # Use higher timeout for reproduction. reproduce_arguments = arguments[:] libfuzzer.fix_timeout_argument_for_reproduction( reproduce_arguments) # Write the new testcase. # Copy crash testcase contents into the main testcase path. crashes.append( engine.Crash(crash_testcase_file_path, fuzz_logs, reproduce_arguments, actual_duration)) project_qualified_fuzzer_name = ( data_types.fuzz_target_project_qualified_name( utils.current_project(), os.path.basename(target_path))) libfuzzer.analyze_and_update_recommended_dictionary( runner, project_qualified_fuzzer_name, log_lines, options.corpus_dir, arguments) return engine.FuzzResult(fuzz_logs, fuzz_result.command, crashes, parsed_stats, fuzz_result.time_executed)
def fuzz(self, target_path, options, reproducers_dir, max_time): """Run a fuzz session. Args: target_path: Path to the target. options: The FuzzOptions object returned by prepare(). reproducers_dir: The directory to put reproducers in when crashes are found. max_time: Maximum allowed time for the fuzzing to run. Returns: A FuzzResult object. """ profiler.start_if_needed("libfuzzer_fuzz") runner = libfuzzer.get_runner(target_path) libfuzzer.set_sanitizer_options(target_path) # Directory to place new units. new_corpus_dir = self._create_temp_corpus_dir("new") corpus_directories = [new_corpus_dir] + options.fuzz_corpus_dirs fuzz_timeout = libfuzzer.get_fuzz_timeout(options.is_mutations_run, total_timeout=max_time) fuzz_result = runner.fuzz( corpus_directories, fuzz_timeout=fuzz_timeout, additional_args=options.arguments, artifact_prefix=reproducers_dir, extra_env=options.extra_env, ) project_qualified_fuzzer_name = _project_qualified_fuzzer_name( target_path) dict_error_match = DICT_PARSING_FAILED_REGEX.search(fuzz_result.output) if dict_error_match: logs.log_error( "Dictionary parsing failed (target={target}, line={line}).". format(target=project_qualified_fuzzer_name, line=dict_error_match.group(1)), engine_output=fuzz_result.output, ) elif (not environment.get_value("USE_MINIJAIL") and fuzz_result.return_code == constants.LIBFUZZER_ERROR_EXITCODE): # Minijail returns 1 if the exit code is nonzero. # Otherwise: we can assume that a return code of 1 means that libFuzzer # itself ran into an error. logs.log_error( ENGINE_ERROR_MESSAGE + " (target={target}).".format( target=project_qualified_fuzzer_name), engine_output=fuzz_result.output, ) log_lines = utils.decode_to_unicode(fuzz_result.output).splitlines() # Output can be large, so save some memory by removing reference to the # original output which is no longer needed. fuzz_result.output = None # Check if we crashed, and get the crash testcase path. crash_testcase_file_path = runner.get_testcase_path(log_lines) # If we exited with a non-zero return code with no crash file in output from # libFuzzer, this is most likely a startup crash. Use an empty testcase to # to store it as a crash. if not crash_testcase_file_path and fuzz_result.return_code: crash_testcase_file_path = self._create_empty_testcase_file() # Parse stats information based on libFuzzer output. parsed_stats = libfuzzer.parse_log_stats(log_lines) # Extend parsed stats by additional performance features. parsed_stats.update( stats.parse_performance_features(log_lines, options.strategies, options.arguments)) # Set some initial stat overrides. timeout_limit = fuzzer_utils.extract_argument(options.arguments, constants.TIMEOUT_FLAG, remove=False) expected_duration = runner.get_max_total_time(fuzz_timeout) actual_duration = int(fuzz_result.time_executed) fuzzing_time_percent = 100 * actual_duration / float(expected_duration) parsed_stats.update({ "timeout_limit": int(timeout_limit), "expected_duration": expected_duration, "actual_duration": actual_duration, "fuzzing_time_percent": fuzzing_time_percent, }) # Remove fuzzing arguments before merge and dictionary analysis step. arguments = options.arguments[:] libfuzzer.remove_fuzzing_arguments(arguments) self._merge_new_units( target_path, options.corpus_dir, new_corpus_dir, options.fuzz_corpus_dirs, arguments, parsed_stats, ) fuzz_logs = "\n".join(log_lines) crashes = [] if crash_testcase_file_path: # Use higher timeout for reproduction. reproduce_arguments = arguments[:] libfuzzer.fix_timeout_argument_for_reproduction( reproduce_arguments) # Write the new testcase. # Copy crash testcase contents into the main testcase path. crashes.append( engine.Crash( crash_testcase_file_path, fuzz_logs, reproduce_arguments, actual_duration, )) libfuzzer.analyze_and_update_recommended_dictionary( runner, project_qualified_fuzzer_name, log_lines, options.corpus_dir, arguments, ) return engine.FuzzResult( fuzz_logs, fuzz_result.command, crashes, parsed_stats, fuzz_result.time_executed, )