Пример #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)
Пример #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
Пример #3
0
  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)
Пример #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)
Пример #5
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:
            raise TimeoutError('Merging new testcases timed out\n' +
                               result.output)

        if result.return_code != 0:
            raise MergeError('Merging new testcases failed: ' + result.output)

        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)
Пример #6
0
    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)
Пример #7
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:
      raise TimeoutError('Merging new testcases timed out\n' + result_1.logs)

    # 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)
Пример #8
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.
    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)