def test_test_for_crash_with_retries_greybox_succeed_no_comparison(self): """Test test_for_crash_with_retries reproducing a crash with compare_crash set to False (greybox).""" mock_engine = mock.Mock() mock_engine.reproduce.side_effect = [ engine.ReproduceResult(['cmd'], 0, 0, 'output'), engine.ReproduceResult(['cmd'], 1, 1, 'crash'), ] self.mock.get.return_value = mock_engine crash_result = testcase_manager.test_for_crash_with_retries( self.greybox_testcase, '/fuzz-testcase', 10, compare_crash=False) self.assertEqual(1, crash_result.return_code) self.assertEqual(1, crash_result.crash_time) self.assertEqual(self.GREYBOX_FUZZER_CRASH, crash_result.output) self.assertEqual(2, mock_engine.reproduce.call_count) mock_engine.reproduce.assert_has_calls([ mock.call('/build_dir/target', '/fuzz-testcase', ['-arg1', '-arg2'], 120), mock.call('/build_dir/target', '/fuzz-testcase', ['-arg1', '-arg2'], 10), ]) self.mock.log.assert_has_calls([ mock.call( 'No crash occurred (round 1).', output=self.GREYBOX_FUZZER_NO_CRASH), mock.call( 'Crash occurred in 1 seconds (round 2). State:\nstate', output=self.GREYBOX_FUZZER_CRASH), mock.call('Crash stacktrace comparison skipped.') ])
def test_test_for_reproducibility_greybox_succeed_after_multiple_tries(self): """Test test_for_reproducibility with with failure on first run and then succeed on remaining runs (greybox).""" mock_engine = mock.Mock() mock_engine.reproduce.side_effect = [ engine.ReproduceResult(['cmd'], 0, 0, 'output'), engine.ReproduceResult(['cmd'], 1, 1, 'crash'), engine.ReproduceResult(['cmd'], 1, 1, 'crash'), ] self.mock.get.return_value = mock_engine result = testcase_manager.test_for_reproducibility( 'engine', 'engine_target', '/fuzz-testcase', 'state', expected_security_flag=False, test_timeout=10, http_flag=False, gestures=None) self.assertTrue(result) self.assertEqual(3, mock_engine.reproduce.call_count) self.mock.log.assert_has_calls([ mock.call( 'No crash occurred (round 1).', output=self.GREYBOX_FUZZER_NO_CRASH), mock.call( 'Crash occurred in 1 seconds (round 2). State:\nstate', output=self.GREYBOX_FUZZER_CRASH), mock.call( 'Crash occurred in 1 seconds (round 3). State:\nstate', output=self.GREYBOX_FUZZER_CRASH), mock.call('Crash is reproducible.'), ])
def repro(self, repro_timeout: int, repro_args: List[str]) -> engine.ReproduceResult: """This is where crash repro'ing is done. Args: repro_timeout: The maximum time in seconds that repro job is allowed to run for. repro_args: A sequence of arguments to be passed to the executable. """ logs.log('Running Syzkaller (syz-crush) against testcase.') additional_args = repro_args.copy() for retry_count in range(REPRO_RETRY_MAX): result = self.run_and_wait(additional_args, timeout=repro_timeout) log_location = re.search(REPRODUCE_LOG_LOCATION_PATTERN, result.output) # syz-crush stopped before capturing crash output if not log_location: continue # syz-crush did not reproduce any crash if not self._crash_was_reproducible(result.output): continue _type, log_index, log_dir = log_location.groups() # pylint: disable=invalid-name try: reproducer_log_path = os.path.join(log_dir, f'reproducer{log_index}') with open(reproducer_log_path, 'r') as f: logs.log('Successfully reproduced crash.') return engine.ReproduceResult( command=result.command, return_code=1, time_executed=result.time_executed, output=f.read(), ) except FileNotFoundError as e: logs.log('Reproducer log was not found. Rerunning syz-crush', e) continue logs.log(f'Failed to reproduce crash after {retry_count} attempts.') return engine.ReproduceResult( command=result.command, return_code=0, time_executed=result.time_executed, output=result.output, )
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. Raises: TimeoutError: If the cleanse exceeds max_time. """ 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) if result.timed_out: logs.log_error('Cleanse timed out.', fuzzer_output=result.output) raise TimeoutError('Cleanse timed out.') return engine.ReproduceResult(result.command, result.return_code, result.time_executed, result.output)
def test_test_for_reproducibility_greybox_succeed(self): """Test test_for_reproducibility with success on all runs (greybox).""" mock_engine = mock.Mock() mock_engine.reproduce.return_value = engine.ReproduceResult(['cmd'], 1, 1, 'crash') self.mock.get.return_value = mock_engine result = testcase_manager.test_for_reproducibility( 'engine', 'engine_target', '/fuzz-testcase', 'state', expected_security_flag=False, test_timeout=10, http_flag=False, gestures=None) self.assertTrue(result) # Only 2/3 runs needed to verify reproducibility. self.assertEqual(2, mock_engine.reproduce.call_count) self.mock.log.assert_has_calls([ mock.call( 'Crash occurred in 1 seconds (round 1). State:\nstate', output=self.GREYBOX_FUZZER_CRASH), mock.call( 'Crash occurred in 1 seconds (round 2). State:\nstate', output=self.GREYBOX_FUZZER_CRASH), mock.call('Crash is reproducible.'), ])
def test_test_for_crash_with_retries_greybox_fail(self): """Test test_for_crash_with_retries failing to reproduce a crash (greybox).""" mock_engine = mock.Mock() mock_engine.reproduce.return_value = engine.ReproduceResult(['cmd'], 0, 0, 'output') self.mock.get.return_value = mock_engine crash_result = testcase_manager.test_for_crash_with_retries( self.greybox_testcase, '/fuzz-testcase', 10) self.assertEqual(0, crash_result.return_code) self.assertEqual(0, crash_result.crash_time) self.assertEqual(self.GREYBOX_FUZZER_NO_CRASH, crash_result.output) self.assertEqual(3, mock_engine.reproduce.call_count) mock_engine.reproduce.assert_has_calls([ mock.call('/build_dir/target', '/fuzz-testcase', ['-arg1', '-arg2'], 120), mock.call('/build_dir/target', '/fuzz-testcase', ['-arg1', '-arg2'], 10), mock.call('/build_dir/target', '/fuzz-testcase', ['-arg1', '-arg2'], 10), ]) self.mock.log.assert_has_calls( [ mock.call( 'No crash occurred (round 1).', output=self.GREYBOX_FUZZER_NO_CRASH), mock.call( 'No crash occurred (round 2).', output=self.GREYBOX_FUZZER_NO_CRASH), mock.call( 'No crash occurred (round 3).', output=self.GREYBOX_FUZZER_NO_CRASH), mock.call("Didn't crash at all.") ])
def process_testcase(engine_name, tool_name, target_name, arguments, testcase_path, output_path, timeout): """Process testcase on untrusted worker.""" if tool_name == 'minimize': operation = untrusted_runner_pb2.ProcessTestcaseRequest.MINIMIZE else: operation = untrusted_runner_pb2.ProcessTestcaseRequest.CLEANSE rebased_testcase_path = file_host.rebase_to_worker_root(testcase_path) file_host.copy_file_to_worker(testcase_path, rebased_testcase_path) request = untrusted_runner_pb2.ProcessTestcaseRequest( engine=engine_name, operation=operation, target_name=target_name, arguments=arguments, testcase_path=file_host.rebase_to_worker_root(testcase_path), output_path=file_host.rebase_to_worker_root(output_path), timeout=timeout) response = host.stub().ProcessTestcase(request) rebased_output_path = file_host.rebase_to_worker_root(output_path) file_host.copy_file_from_worker(rebased_output_path, output_path) return engine.ReproduceResult(list(response.command), response.return_code, response.time_executed, response.output)
def reproduce(self, target_path, input_path, arguments, max_time): # pylint: disable=unused-argument """Reproduce a crash given an input. Example: ./syz-crush -config my.cfg -infinite=false -restart_time=20s crash-qemu-1-1455745459265726910 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. """ binary_dir = self.prepare_binary_path() syzkaller_runner = runner.get_runner( os.path.join(binary_dir, constants.SYZ_REPRO)) repro_args = runner.get_config() repro_args.extend([ '-infinite=false', '-restart_time={}s'.format(REPRO_TIME), input_path ]) result = syzkaller_runner.repro(max_time, repro_args=repro_args) 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. Raises: TimeoutError: If the reproduction exceeds max_time. """ 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) if result.timed_out: raise TimeoutError('Reproducing timed out\n' + result.output) 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. """ del arguments del max_time config = launcher.AflConfig.from_target_path(target_path) input_directory = None # Not required for reproduction. runner = launcher.prepare_runner(target_path, config, input_path, input_directory) reproduce_result = _run_single_testcase(runner, input_path) command = reproduce_result.command return_code = reproduce_result.return_code time_executed = reproduce_result.time_executed output = runner.fuzzer_stderr return engine.ReproduceResult(command, return_code, time_executed, output)
def minimize(self, minimize_args: List[str]) -> engine.ReproduceResult: """Minimizing crash testcase. Args: minimize_args: list of arguments to be passed to syz-repro. """ logs.log( 'Running Syzkaller Minimization (syz-repro) against testcase.') additional_args = minimize_args.copy() result = self.run_and_wait(additional_args) if result.return_code: logs.log('Successfully minimized crash.') else: logs.log('Failed to minimize crash.') logs.log('Syzkaller minimize testcase stopped.') return engine.ReproduceResult(result.command, result.return_code, result.time_executed, result.output)
def reproduce(self, target_path, input_path, arguments, max_time): # pylint: disable=unused-argument """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. """ os.chmod(target_path, 0o775) runner = new_process.UnicodeProcessRunner(target_path) with open(input_path) as f: result = runner.run_and_wait(timeout=max_time, stdin=f) return engine.ReproduceResult(result.command, result.return_code, result.time_executed, result.output)
def repro(self, repro_timeout, repro_args): """This is where crash repro'ing is done. Args: repro_timeout: The maximum time in seconds that repro job is allowed to run for. repro_args: A sequence of arguments to be passed to the executable. """ logs.log('Running Syzkaller testcase.') additional_args = copy.copy(repro_args) result = self.run_and_wait(additional_args, timeout=repro_timeout) result.return_code = self._crash_was_reproducible(result.output) if result.return_code: logs.log('Successfully reproduced crash.') else: logs.log('Failed to reproduce crash.') logs.log('Syzkaller repro testcase stopped.') return engine.ReproduceResult(result.command, result.return_code, result.time_executed, result.output)
def test_test_for_crash_with_retries_greybox_legacy(self): """Test test_for_crash_with_retries reproducing a legacy crash (greybox).""" mock_engine = mock.Mock() mock_engine.reproduce.side_effect = [ engine.ReproduceResult(['cmd'], 1, 1, 'crash'), ] self.mock.get.return_value = mock_engine with open('/flags-testcase', 'w', encoding='utf-8') as f: f.write('%TESTCASE% target -arg1 -arg2') testcase_manager.test_for_crash_with_retries(self.greybox_testcase, '/fuzz-testcase', 10) mock_engine.reproduce.assert_has_calls([ mock.call('/build_dir/target', '/fuzz-testcase', ['-arg1', '-arg2'], 120), ]) self.mock.log.assert_has_calls([ mock.call('Crash occurred in 1 seconds (round 1). State:\nstate', output=self.GREYBOX_FUZZER_CRASH), mock.call('Crash stacktrace is similar to original stacktrace.') ])
def engine_reproduce(engine_impl, target_name, testcase_path, arguments, timeout): """Run engine reproduce on untrusted worker.""" rebased_testcase_path = file_host.rebase_to_worker_root(testcase_path) file_host.copy_file_to_worker(testcase_path, rebased_testcase_path) request = untrusted_runner_pb2.EngineReproduceRequest( engine=engine_impl.name, target_name=target_name, testcase_path=rebased_testcase_path, arguments=arguments, timeout=timeout) try: response = host.stub().EngineReproduce(request) except grpc.RpcError as e: if 'TargetNotFoundError' in repr(e): # Resurface the right exception. raise testcase_manager.TargetNotFoundError( 'Failed to find target ' + target_name) raise return engine.ReproduceResult(list(response.command), response.return_code, response.time_executed, response.output)
from clusterfuzz.fuzz import engine import clusterfuzz_deployment import fuzz_target import test_helpers import workspace_utils # NOTE: This integration test relies on # https://github.com/google/oss-fuzz/tree/master/projects/example project. EXAMPLE_PROJECT = 'example' # An example fuzzer that triggers an error. EXAMPLE_FUZZER = 'example_crash_fuzzer' # Mock return values for engine_impl.reproduce. EXECUTE_SUCCESS_RESULT = engine.ReproduceResult([], 0, 0, '') EXECUTE_FAILURE_RESULT = engine.ReproduceResult([], 1, 0, '') TEST_DATA_PATH = os.path.join(os.path.dirname(__file__), 'test_data') def _create_config(**kwargs): """Creates a config object and then sets every attribute that is a key in |kwargs| to the corresponding value. Asserts that each key in |kwargs| is an attribute of Config.""" defaults = { 'cfl_platform': 'github', 'oss_fuzz_project_name': EXAMPLE_PROJECT, 'workspace': '/workspace' } for default_key, default_value in defaults.items():