def fuzz(self, fuzz_timeout, additional_args, unused_additional_args=None, unused_extra_env=None): """This is where actual syzkaller fuzzing is done.""" additional_args = copy.copy(additional_args) fuzz_result = self.run_and_wait(additional_args, timeout=fuzz_timeout) log_lines = utils.decode_to_unicode(fuzz_result.output).splitlines() fuzz_result.output = None crash_testcase_file_path = self.get_testcase_path(log_lines) #TODO(hzawawy): remove once syzkaller code is completed. if not crash_testcase_file_path and fuzz_result.return_code: crash_testcase_file_path = self._create_empty_testcase_file() fuzz_logs = '\n'.join(log_lines) # TODO(hzawawy): Parse stats information and add them to FuzzResult parsed_stats = [] crashes = [] if crash_testcase_file_path: #TODO(hzawawy): add repro arguments reproduce_arguments = [] 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, fuzz_logs, reproduce_arguments, actual_duration)) return engine.FuzzResult(fuzz_logs, fuzz_result.command, crashes, parsed_stats, fuzz_result.time_executed)
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 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, [], 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, 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, 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. """ 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 = '' 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_lines = utils.read_data_from_file( os.path.join(subdir, file), eval_data=False).decode('utf-8') fuzz_logs += log_lines + '\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_lines, reproduce_arguments, actual_duration)) return engine.FuzzResult(fuzz_logs, fuzz_result.command, crashes, parsed_stats, fuzz_result.time_executed)
def test_basic(self): """Test basic fuzzing session.""" session = fuzz_task.FuzzingSession('libFuzzer', 'libfuzzer_asan_test', 60) session.testcase_directory = os.environ['FUZZ_INPUTS'] session.data_directory = '/data_dir' os.environ['FUZZ_TARGET'] = 'test_target' os.environ['APP_REVISION'] = '1' expected_crashes = [engine.Crash('/input', 'stack', ['args'], 1.0)] engine_impl = mock.Mock() engine_impl.name = 'libFuzzer' engine_impl.prepare.return_value = engine.FuzzOptions( '/corpus', ['arg'], ['strategy_1', 'strategy_2']) engine_impl.fuzz.return_value = engine.Result( 'logs', ['cmd'], expected_crashes, {'stat': 1}, 42.0) crashes, fuzzer_metadata = session.do_engine_fuzzing(engine_impl) self.assertDictEqual({ 'issue_components': 'component1,component2', 'issue_labels': 'label1,label2', 'issue_owners': '*****@*****.**', }, fuzzer_metadata) log_time = datetime.datetime(1970, 1, 1, 0, 0) self.mock.upload_log.assert_called_with( 'Component revisions (build r1):\n' 'component: rev\n\n' 'Return code: 1\n\n' 'Command: cmd\nBot: None\nTime ran: 42.0\n\n' 'logs\n' 'cf::fuzzing_strategies: strategy_1,strategy_2', log_time) self.mock.upload_testcase.assert_called_with('/input', log_time) self.assertEqual(1, len(crashes)) self.assertEqual('/input', crashes[0].file_path) self.assertEqual(1, crashes[0].return_code) self.assertEqual('stack', crashes[0].unsymbolized_crash_stacktrace) self.assertEqual(1.0, crashes[0].crash_time) self.assertListEqual(['test_target', 'args'], crashes[0].arguments) upload_args = self.mock.upload_stats.call_args[0][0] testcase_run = upload_args[0] self.assertDictEqual({ 'build_revision': 1, 'command': ['cmd'], 'fuzzer': u'libFuzzer_test_target', 'job': 'libfuzzer_asan_test', 'kind': 'TestcaseRun', 'stat': 1, 'timestamp': 0.0, }, testcase_run.data)
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=utils.decode_to_unicode(crash.stacktrace), reproduce_args=crash.reproduce_args, crash_time=crash.crash_time) for crash in response.crashes ] unpacked_stats = {} for key, packed_value in six.iteritems(response.stats): if packed_value.Is(wrappers_pb2.DoubleValue.DESCRIPTOR): value = wrappers_pb2.DoubleValue() elif packed_value.Is(wrappers_pb2.Int32Value.DESCRIPTOR): value = wrappers_pb2.Int32Value() elif packed_value.Is(wrappers_pb2.StringValue.DESCRIPTOR): value = wrappers_pb2.StringValue() else: raise ValueError('Unknown stat type for ' + key) packed_value.Unpack(value) unpacked_stats[key] = value.value result = engine.FuzzResult( logs=utils.decode_to_unicode(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)
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 Result object. """ profiler.start_if_needed('libfuzzer_fuzz') runner = libfuzzer.get_runner(target_path) launcher.set_sanitizer_options(target_path) artifact_prefix = self._artifact_prefix( os.path.abspath(reproducers_dir)) # 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 = launcher.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], extra_env=options.extra_env) 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 = None for line in log_lines: match = re.match(launcher.CRASH_TESTCASE_REGEX, line) if match: crash_testcase_file_path = match.group(1) break # Parse stats information based on libFuzzer output. parsed_stats = launcher.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[:] launcher.remove_fuzzing_arguments(arguments) self._merge_new_units(target_path, options.corpus_dir, new_corpus_dir, options.fuzz_corpus_dirs, arguments, parsed_stats) # Add custom crash state based on fuzzer name (if needed). project_qualified_fuzzer_name = ( data_types.fuzz_target_project_qualified_name( utils.current_project(), os.path.basename(target_path))) launcher.add_custom_crash_state_if_needed( project_qualified_fuzzer_name, log_lines, parsed_stats) fuzz_logs = '\n'.join(log_lines) crashes = [] if crash_testcase_file_path: # Write the new testcase. # Copy crash testcase contents into the main testcase path. crashes.append( engine.Crash(crash_testcase_file_path, fuzz_logs, arguments, actual_duration)) launcher.analyze_and_update_recommended_dictionary( runner, project_qualified_fuzzer_name, log_lines, options.corpus_dir, arguments) return engine.Result(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) 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, )