Ejemplo n.º 1
0
    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)
Ejemplo n.º 2
0
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
Ejemplo n.º 3
0
    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:
            logs.log_error('Merging new testcases timed out.',
                           fuzzer_output=result.output)
            raise TimeoutError('Merging new testcases timed out.')

        if result.return_code != 0:
            logs.log_error('Merging new testcases timed out.',
                           fuzzer_output=result.output)
            raise MergeError('Merging new testcases failed.')

        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)
Ejemplo n.º 4
0
    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)
Ejemplo n.º 5
0
    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.
    """
        del options  # Unused.
        runner = new_process.UnicodeProcessRunner(target_path)

        fuzz_result = runner.run_and_wait(timeout=max_time,
                                          extra_env={
                                              'FUZZTEST_REPRODUCERS_OUT_DIR':
                                              reproducers_dir,
                                          })
        log_lines = fuzz_result.output.splitlines()

        crashes = []
        for line in log_lines:
            reproducer_path = _get_reproducer_path(line)
            if reproducer_path:
                crashes.append(
                    engine.Crash(reproducer_path,
                                 fuzz_result.output,
                                 reproduce_args=[],
                                 crash_time=int(fuzz_result.time_executed)))
                continue

        # TODO(ochang): Implement stats parsing.
        stats = {}
        return engine.FuzzResult(fuzz_result.output, fuzz_result.command,
                                 crashes, stats, fuzz_result.time_executed)
Ejemplo n.º 6
0
    def _minimize_corpus_two_step(self, target_path, arguments,
                                  existing_corpus_dirs, new_corpus_dir,
                                  output_corpus_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.
      existing_corpus_dirs: Input corpora that existed before the fuzzing run.
      new_corpus_dir: Input corpus that was generated during the fuzzing run.
          Must have at least one new file.
      output_corpus_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.
    """
        if not _is_multistep_merge_supported(target_path):
            # Fallback to the old single step merge. It does not support incremental
            # stats and provides only `edge_coverage` and `feature_coverage` stats.
            logs.log(
                'Old version of libFuzzer is used. Using single step merge.')
            return self.minimize_corpus(
                target_path, arguments,
                existing_corpus_dirs + [new_corpus_dir], output_corpus_dir,
                reproducers_dir, max_time)

        # The dir where merge control file is located must persist for both merge
        # steps. The second step re-uses the MCF produced during the first step.
        merge_control_file_dir = self._create_temp_corpus_dir('mcf_tmp_dir')
        self._merge_control_file = os.path.join(merge_control_file_dir, 'MCF')

        # Two step merge process to obtain accurate stats for the new corpus units.
        # See https://reviews.llvm.org/D66107 for a more detailed description.
        merge_stats = {}

        # Step 1. Use only existing corpus and collect "initial" stats.
        result_1 = self.minimize_corpus(target_path, arguments,
                                        existing_corpus_dirs,
                                        output_corpus_dir, reproducers_dir,
                                        max_time)
        merge_stats['initial_edge_coverage'] = result_1.stats['edge_coverage']
        merge_stats['initial_feature_coverage'] = result_1.stats[
            'feature_coverage']

        # Clear the output dir as it does not have any new units at this point.
        engine_common.recreate_directory(output_corpus_dir)

        # Adjust the time limit for the time we spent on the first merge step.
        max_time -= result_1.time_executed
        if max_time <= 0:
            logs.log_error('Merging new testcases timed out.',
                           fuzzer_output=result_1.logs)
            raise TimeoutError('Merging new testcases timed out.')

        # Step 2. Process the new corpus units as well.
        result_2 = self.minimize_corpus(
            target_path, arguments, existing_corpus_dirs + [new_corpus_dir],
            output_corpus_dir, reproducers_dir, max_time)
        merge_stats['edge_coverage'] = result_2.stats['edge_coverage']
        merge_stats['feature_coverage'] = result_2.stats['feature_coverage']

        # Diff the stats to obtain accurate values for the new corpus units.
        merge_stats['new_edges'] = (merge_stats['edge_coverage'] -
                                    merge_stats['initial_edge_coverage'])
        merge_stats['new_features'] = (merge_stats['feature_coverage'] -
                                       merge_stats['initial_feature_coverage'])

        output = result_1.logs + '\n\n' + result_2.logs
        if (merge_stats['new_edges'] < 0 or merge_stats['new_features'] < 0):
            logs.log_error('Two step merge failed.',
                           merge_stats=merge_stats,
                           output=output)
            merge_stats['new_edges'] = 0
            merge_stats['new_features'] = 0

        self._merge_control_file = None

        # TODO(ochang): Get crashes found during merge.
        return engine.FuzzResult(
            output, result_2.command, [], merge_stats,
            result_1.time_executed + result_2.time_executed)
Ejemplo n.º 7
0
    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.
        if options.merge_back_new_testcases:
            new_corpus_dir = self._create_temp_corpus_dir('new')
            corpus_directories = [new_corpus_dir] + options.fuzz_corpus_dirs
        else:
            corpus_directories = 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 '
                f'(target={project_qualified_fuzzer_name}, '
                f'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 +
                           f' (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
                not in constants.NONCRASH_RETURN_CODES):
            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)

        actual_duration = int(fuzz_result.time_executed)
        fuzzing_time_percent = 100 * actual_duration / float(max_time)
        parsed_stats.update({
            'timeout_limit': int(timeout_limit),
            'expected_duration': int(max_time),
            'actual_duration': actual_duration,
            'fuzzing_time_percent': fuzzing_time_percent,
        })

        # Remove fuzzing arguments before merge and dictionary analysis step.
        non_fuzz_arguments = options.arguments.copy()
        libfuzzer.remove_fuzzing_arguments(non_fuzz_arguments, is_merge=True)

        if options.merge_back_new_testcases:
            self._merge_new_units(target_path, options.corpus_dir,
                                  new_corpus_dir, options.fuzz_corpus_dirs,
                                  non_fuzz_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))

        if options.analyze_dictionary:
            libfuzzer.analyze_and_update_recommended_dictionary(
                runner, project_qualified_fuzzer_name, log_lines,
                options.corpus_dir, non_fuzz_arguments)

        return engine.FuzzResult(fuzz_logs, fuzz_result.command, crashes,
                                 parsed_stats, fuzz_result.time_executed)
Ejemplo n.º 8
0
    def fuzz(
        self,
        fuzz_timeout,
        additional_args,
        unused_additional_args=None,
        unused_extra_env=None,
    ) -> engine.FuzzResult:
        """This is where actual syzkaller fuzzing is done.

    Args:
      fuzz_timeout (float): 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.
    Returns:
      engine.FuzzResult
    """

        logs.log('Running Syzkaller.')
        additional_args = copy.copy(additional_args)

        # Save kernel_bid for later in case the device is down.
        _, kernel_bid = kernel_utils.get_kernel_hash_and_build_id()

        fuzz_result = self.run_and_loop(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 = self._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))

        _upload_kernel_coverage_data(get_cover_file_path(), kernel_bid)
        return engine.FuzzResult(fuzz_logs, fuzz_result.command, crashes,
                                 parsed_stats, fuzz_result.time_executed)