def engine_fuzz(engine_impl, target_name, sync_corpus_directory, testcase_directory): """Run engine fuzzer on untrusted worker.""" request = untrusted_runner_pb2.EngineFuzzRequest( engine=engine_impl.name, target_name=target_name, sync_corpus_directory=file_host.rebase_to_worker_root( sync_corpus_directory), testcase_directory=file_host.rebase_to_worker_root(testcase_directory)) response = host.stub().EngineFuzz(request) crashes = [ engine.Crash(input_path=file_host.rebase_to_host_root( crash.input_path), stacktrace=crash.stacktrace, reproduce_args=crash.reproduce_args, crash_time=crash.crash_time) for crash in response.crashes ] unpacked_stats = _unpack_values(response.stats) unpacked_strategies = _unpack_values(response.strategies) result = engine.FuzzResult(logs=response.logs, command=list(response.command), crashes=crashes, stats=unpacked_stats, time_executed=response.time_executed) file_host.pull_testcases_from_worker() return result, dict(response.fuzzer_metadata), unpacked_strategies
def fuzz(self, target_path, options, reproducers_dir, max_time): """Run a fuzzing session. Args: target_path: Path to the fuzzer script or binary. 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. """ # For blackbox fuzzers, |target_path| supplies the path to the fuzzer script # rather than a target in the build archive. fuzzer_path = target_path os.chmod(fuzzer_path, 0o775) app_path = environment.get_value('APP_PATH') app_args = testcase_manager.get_command_line_for_application( get_arguments_only=True).strip() corpus_dir = options.corpus_dir command_line_args = _get_arguments(app_path, app_args) command_line_args.append(f'--input_dir={corpus_dir}') result = _run_with_interpreter_if_needed(fuzzer_path, command_line_args, max_time) crashes = [] for testcase_path in os.listdir(reproducers_dir): if not testcase_path.startswith(TESTCASE_PREFIX): continue output_path = OUTPUT_PREFIX + testcase_path[len(TESTCASE_PREFIX):] absolute_output_path = os.path.join(reproducers_dir, output_path) # If no output was written for a test case, skip it. if not os.path.exists(absolute_output_path): continue with open(absolute_output_path, 'r', errors='replace') as handle: output = handle.read() # Filter obviously non-crashing test cases. Crashes still follow the # normal flow in fuzz task to ensure that the state should not be ignored # for other reasons, but we don't want to log every test case for the # fuzzers that don't do their own crash processing. state = stack_analyzer.get_crash_data(output) if not state.crash_type: continue full_testcase_path = os.path.join(reproducers_dir, testcase_path) crash = engine.Crash(full_testcase_path, output, options.arguments, int(result.time_executed)) crashes.append(crash) # TODO(mbarbella): Support stats. stats = {} return engine.FuzzResult(result.output, result.command, crashes, 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. """ config = launcher.AflConfig.from_target_path(target_path) config.additional_afl_arguments = options.arguments testcase_file_path = os.path.join(reproducers_dir, 'crash') runner = launcher.prepare_runner(target_path, config, testcase_file_path, options.corpus_dir, max_time, strategy_dict=options.strategies) fuzz_result = runner.fuzz() command = fuzz_result.command time_executed = fuzz_result.time_executed fuzzing_logs = fuzz_result.output + runner.fuzzer_stderr # Bail out if AFL returns a nonzero status code. if fuzz_result.return_code: target = engine_common.get_project_qualified_fuzzer_name( target_path) logs.log_error( f'afl: engine encountered an error (target={target})', engine_output=fuzz_result.output) return engine.FuzzResult(fuzzing_logs, command, [], {}, time_executed) stats_getter = stats.StatsGetter(runner.afl_output.stats_path, config.dict_path) new_units_generated, new_units_added, corpus_size = ( runner.libfuzzerize_corpus()) stats_getter.set_stats(fuzz_result.time_executed, new_units_generated, new_units_added, corpus_size, runner.strategies, runner.fuzzer_stderr, fuzz_result.output) crashes = [] if os.path.exists(testcase_file_path): crash = engine.Crash(testcase_file_path, runner.fuzzer_stderr, [], fuzz_result.time_executed) crashes.append(crash) return engine.FuzzResult(fuzzing_logs, command, crashes, stats_getter.stats, 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. """ runner = _get_runner() arguments = _DEFAULT_ARGUMENTS[:] arguments.extend(options.arguments) arguments.extend([ '--input', options.corpus_dir, '--workspace', reproducers_dir, '--run_time', str(max_time), '--', target_path, ]) fuzz_result = runner.run_and_wait( additional_args=arguments, timeout=max_time + _CLEAN_EXIT_SECS) log_lines = fuzz_result.output.splitlines() sanitizer_stacktrace = _find_sanitizer_stacktrace(reproducers_dir) crashes = [] stats = None for line in log_lines: reproducer_path = _get_reproducer_path(line) if reproducer_path: crashes.append( engine.Crash(reproducer_path, sanitizer_stacktrace or '', [], int(fuzz_result.time_executed))) continue stats = _get_stats(line) if stats is None: stats = {} return engine.FuzzResult(fuzz_result.output, fuzz_result.command, crashes, stats, fuzz_result.time_executed)
def fuzz(self, fuzz_timeout, additional_args, unused_additional_args=None, unused_extra_env=None): """This is where actual syzkaller fuzzing is done. Args: fuzz_timeout: The maximum time in seconds that fuzz job is allowed to run for. additional_args: A sequence of additional arguments to be passed to the executable. """ def _filter_log(content): """Filter unneeded content from log.""" result = '' strip_regex = re.compile(r'^c\d+\s+\d+\s') for line in content.splitlines(): result += strip_regex.sub('', line) + '\n' return result logs.log('Running Syzkaller.') additional_args = copy.copy(additional_args) fuzz_result = self.run_and_wait(additional_args, timeout=fuzz_timeout) logs.log('Syzkaller stopped, fuzzing timed out: {}'.format( fuzz_result.time_executed)) fuzz_logs = (fuzz_result.output or '') + '\n' crashes = [] parsed_stats = {} visited = set() for subdir, _, files in os.walk(get_work_dir()): for file in files: # Each crash typically have 2 files: reportN and logN. Similar crashes # are grouped together in subfolders. unique_crash puts together the # subfolder name and reportN. unique_crash = os.path.join(subdir, file) if fnmatch.fnmatch(file, 'report*') and unique_crash not in visited: visited.add(unique_crash) log_content = _filter_log( utils.read_data_from_file( os.path.join(subdir, file), eval_data=False).decode('utf-8')) fuzz_logs += log_content + '\n' # Since each crash (report file) has a corresponding log file # that contains the syscalls that caused the crash. This file is # located in the same subfolder and has the same number. # E.g. ./439c37d288d4f26a33a6c7e5c57a97791453a447/report15 and # ./439c37d288d4f26a33a6c7e5c57a97791453a447/log15. crash_testcase_file_path = os.path.join( subdir, 'log' + file[len('report'):]) # TODO(hzawawy): Parse stats information and add them to FuzzResult. if crash_testcase_file_path: reproduce_arguments = [unique_crash] actual_duration = int(fuzz_result.time_executed) # Write the new testcase. # Copy crash testcase contents into the main testcase path. crashes.append( engine.Crash(crash_testcase_file_path, log_content, reproduce_arguments, actual_duration)) 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, fuzz_options=options) # Directory to place new units. new_corpus_dir = self._create_temp_corpus_dir('new') corpus_directories = [new_corpus_dir] + options.fuzz_corpus_dirs fuzz_result = runner.fuzz( corpus_directories, fuzz_timeout=max_time, additional_args=options.arguments, artifact_prefix=reproducers_dir, extra_env=options.extra_env) project_qualified_fuzzer_name = ( engine_common.get_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 = 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( reproducers_dir) # 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(max_time) 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. merge_arguments = options.arguments[:] libfuzzer.remove_fuzzing_arguments(merge_arguments, is_merge=True) self._merge_new_units(target_path, options.corpus_dir, new_corpus_dir, options.fuzz_corpus_dirs, merge_arguments, parsed_stats) fuzz_logs = '\n'.join(log_lines) crashes = [] if crash_testcase_file_path: reproduce_arguments = options.arguments[:] libfuzzer.remove_fuzzing_arguments(reproduce_arguments) # Use higher timeout for reproduction. 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, merge_arguments) return engine.FuzzResult(fuzz_logs, fuzz_result.command, crashes, parsed_stats, fuzz_result.time_executed)