예제 #1
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
예제 #2
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)
예제 #3
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)
예제 #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 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)
예제 #6
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)