def minimize_corpus(self, target_path, arguments, output_dir, input_dirs, max_time): """Optional (but recommended): run corpus minimization. Args: target_path: Path to the target. arguments: Additional arguments needed for corpus minimization. output_dir: Output directory to place minimized corpus. input_dirs: Input corpora. max_time: Maximum allowed time for the minimization. Returns: A Result object. """ runner = libfuzzer.get_runner(target_path) launcher.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) 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.Result(merge_result.output, merge_result.command, [], {}, merge_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 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)