Exemplo n.º 1
0
    def run(self, input_directory, output_directory, no_of_files):
        """Run the fuzzer to generate testcases."""

        fuzzer_binary_name, fuzzer_path = self._get_fuzzer_binary_name_and_path(
        )

        project_qualified_name = data_types.fuzz_target_project_qualified_name(
            utils.current_project(), fuzzer_binary_name)

        arguments = self.generate_arguments(fuzzer_path)
        corpus_directory = get_corpus_directory(input_directory,
                                                project_qualified_name)

        # Create fuzz testcases.
        for i in range(no_of_files):
            # Contents of testcase file don't matter at this point. Need to create
            # something non-null so that it is not ignored.
            testcase_file_path = os.path.join(
                output_directory, '%s%d' % (testcase_manager.FUZZ_PREFIX, i))
            utils.write_data_to_file(' ', testcase_file_path)

            # Write the flags file containing command line for running launcher
            # script.
            flags_file_path = os.path.join(
                output_directory, '%s%d' % (testcase_manager.FLAGS_PREFIX, i))
            flags = ['%TESTCASE%', fuzzer_binary_name]
            if arguments:
                flags.append(arguments)

            flags_file_content = ' '.join(flags)
            utils.write_data_to_file(flags_file_content, flags_file_path)

        output = 'Generated %d testcase for fuzzer %s.\n' % (
            no_of_files, fuzzer_binary_name)
        output += 'metadata::fuzzer_binary_name: %s\n' % fuzzer_binary_name

        issue_owners = engine_common.get_issue_owners(fuzzer_path)
        if issue_owners:
            output += 'metadata::issue_owners: %s\n' % ','.join(issue_owners)

        issue_labels = engine_common.get_issue_labels(fuzzer_path)
        if issue_labels:
            output += 'metadata::issue_labels: %s\n' % ','.join(issue_labels)

        issue_components = engine_common.get_issue_components(fuzzer_path)
        if issue_components:
            output += 'metadata::issue_components: %s\n' % ','.join(
                issue_components)

        # Update *SAN_OPTIONS in current environment from .options file. This
        # environment is used in fuzz task later for deriving the environment
        # string in |get_environment_settings_as_string| and embedding this as
        # part of stacktrace.
        engine_common.process_sanitizer_options_overrides(fuzzer_path)

        return BuiltinFuzzerResult(output=output,
                                   corpus_directory=corpus_directory)
Exemplo n.º 2
0
 def test_overridden(self):
     """Test that env variable PROJECT_NAME is used for current project."""
     os.environ['PROJECT_NAME'] = 'other-project'
     self.assertEqual('other-project', utils.current_project())
Exemplo n.º 3
0
 def test_default(self):
     """Test that it returns default project with no environment changes."""
     self.assertEqual('test-project', utils.current_project())
Exemplo n.º 4
0
def main(argv):
    """Run libFuzzer as specified by argv."""
    atexit.register(fuzzer_utils.cleanup)

    # Initialize variables.
    arguments = argv[1:]
    testcase_file_path = arguments.pop(0)
    target_name = arguments.pop(0)
    fuzzer_name = data_types.fuzz_target_project_qualified_name(
        utils.current_project(), target_name)

    # Initialize log handler.
    logs.configure(
        'run_fuzzer', {
            'fuzzer': fuzzer_name,
            'engine': 'libFuzzer',
            'job_name': environment.get_value('JOB_NAME')
        })

    profiler.start_if_needed('libfuzzer_launcher')

    # Make sure that the fuzzer binary exists.
    build_directory = environment.get_value('BUILD_DIR')
    fuzzer_path = engine_common.find_fuzzer_path(build_directory, target_name)
    if not fuzzer_path:
        # This is an expected case when doing regression testing with old builds
        # that do not have that fuzz target. It can also happen when a host sends a
        # message to an untrusted worker that just restarted and lost information on
        # build directory.
        logs.log_warn('Could not find fuzz target %s.' % target_name)
        return

    # Install signal handler.
    signal.signal(signal.SIGTERM, engine_common.signal_term_handler)

    # Set up temp dir.
    engine_common.recreate_directory(fuzzer_utils.get_temp_dir())

    # Setup minijail if needed.
    use_minijail = environment.get_value('USE_MINIJAIL')
    runner = libfuzzer.get_runner(fuzzer_path,
                                  temp_dir=fuzzer_utils.get_temp_dir())

    if use_minijail:
        minijail_chroot = runner.chroot
    else:
        minijail_chroot = None

    # Get corpus directory.
    corpus_directory = environment.get_value('FUZZ_CORPUS_DIR')

    # Add common arguments which are necessary to be used for every run.
    arguments = expand_with_common_arguments(arguments)

    # Add sanitizer options to environment that were specified in the .options
    # file and options that this script requires.
    set_sanitizer_options(fuzzer_path)

    # Minimize test argument.
    minimize_to = fuzzer_utils.extract_argument(arguments,
                                                MINIMIZE_TO_ARGUMENT)
    minimize_timeout = fuzzer_utils.extract_argument(
        arguments, MINIMIZE_TIMEOUT_ARGUMENT)

    if minimize_to and minimize_timeout:
        minimize_testcase(runner, testcase_file_path, minimize_to,
                          int(minimize_timeout), arguments, use_minijail)
        return

    # Cleanse argument.
    cleanse_to = fuzzer_utils.extract_argument(arguments, CLEANSE_TO_ARGUMENT)
    cleanse_timeout = fuzzer_utils.extract_argument(arguments,
                                                    CLEANSE_TIMEOUT_ARGUMENT)

    if cleanse_to and cleanse_timeout:
        cleanse_testcase(runner, testcase_file_path, cleanse_to,
                         int(cleanse_timeout), arguments, use_minijail)
        return

    # If we don't have a corpus, then that means this is not a fuzzing run.
    if not corpus_directory:
        load_testcase_if_exists(runner, testcase_file_path, fuzzer_name,
                                use_minijail, arguments)
        return

    # We don't have a crash testcase, fuzz.

    # Check dict argument to make sure that it's valid.
    dict_argument = fuzzer_utils.extract_argument(arguments,
                                                  constants.DICT_FLAG,
                                                  remove=False)
    if dict_argument and not os.path.exists(dict_argument):
        logs.log_error('Invalid dict %s for %s.' %
                       (dict_argument, fuzzer_name))
        fuzzer_utils.extract_argument(arguments, constants.DICT_FLAG)

    # If there's no dict argument, check for %target_binary_name%.dict file.
    if (not fuzzer_utils.extract_argument(
            arguments, constants.DICT_FLAG, remove=False)):
        default_dict_path = dictionary_manager.get_default_dictionary_path(
            fuzzer_path)
        if os.path.exists(default_dict_path):
            arguments.append(constants.DICT_FLAG + default_dict_path)

    fuzzing_strategies = []

    # Select a generator to use for existing testcase mutations.
    generator = _select_generator()
    is_mutations_run = generator != Generator.NONE

    # Timeout for fuzzer run.
    fuzz_timeout = get_fuzz_timeout(is_mutations_run)

    # Set up scratch directory for writing new units.
    new_testcases_directory = create_corpus_directory('new')

    # Get list of corpus directories.
    corpus_directories = get_corpus_directories(corpus_directory,
                                                new_testcases_directory,
                                                fuzzer_path,
                                                fuzzing_strategies,
                                                minijail_chroot)

    # Bind corpus directories in minijail.
    if use_minijail:
        artifact_prefix = constants.ARTIFACT_PREFIX_FLAG + '/'
    else:
        artifact_prefix = '%s%s/' % (constants.ARTIFACT_PREFIX_FLAG,
                                     os.path.abspath(
                                         os.path.dirname(testcase_file_path)))

    # Generate new testcase mutations using radamsa, etc.
    if is_mutations_run:
        new_testcase_mutations_directory = generate_new_testcase_mutations(
            corpus_directory, fuzzer_name, generator, fuzzing_strategies)
        corpus_directories.append(new_testcase_mutations_directory)
        if use_minijail:
            bind_corpus_dirs(minijail_chroot,
                             [new_testcase_mutations_directory])

    max_len_argument = fuzzer_utils.extract_argument(arguments,
                                                     constants.MAX_LEN_FLAG,
                                                     remove=False)
    if not max_len_argument and do_random_max_length():
        max_length = random.SystemRandom().randint(1, MAX_VALUE_FOR_MAX_LENGTH)
        arguments.append('%s%d' % (constants.MAX_LEN_FLAG, max_length))
        fuzzing_strategies.append(strategy.RANDOM_MAX_LENGTH_STRATEGY)

    if do_recommended_dictionary():
        if add_recommended_dictionary(arguments, fuzzer_name, fuzzer_path):
            fuzzing_strategies.append(strategy.RECOMMENDED_DICTIONARY_STRATEGY)

    if do_value_profile():
        arguments.append(constants.VALUE_PROFILE_ARGUMENT)
        fuzzing_strategies.append(strategy.VALUE_PROFILE_STRATEGY)

    if do_fork():
        max_fuzz_threads = environment.get_value('MAX_FUZZ_THREADS', 1)
        num_fuzz_processes = max(
            1,
            multiprocessing.cpu_count() // max_fuzz_threads)
        arguments.append('%s%d' % (constants.FORK_FLAG, num_fuzz_processes))
        fuzzing_strategies.append('%s_%d' %
                                  (strategy.FORK_STRATEGY, num_fuzz_processes))

    extra_env = {}
    if do_mutator_plugin():
        if use_mutator_plugin(target_name, extra_env, minijail_chroot):
            fuzzing_strategies.append(strategy.MUTATOR_PLUGIN_STRATEGY)

    # Execute the fuzzer binary with original arguments.
    fuzz_result = runner.fuzz(corpus_directories,
                              fuzz_timeout=fuzz_timeout,
                              additional_args=arguments + [artifact_prefix],
                              extra_env=extra_env)

    if (not 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, 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 = None
    for line in log_lines:
        match = re.match(CRASH_TESTCASE_REGEX, line)
        if match:
            crash_testcase_file_path = match.group(1)
            break

    if crash_testcase_file_path:
        # Write the new testcase.
        if use_minijail:
            # Convert chroot relative path to host path. Remove the leading '/' before
            # joining.
            crash_testcase_file_path = os.path.join(
                minijail_chroot.directory, crash_testcase_file_path[1:])

        # Copy crash testcase contents into the main testcase path.
        shutil.move(crash_testcase_file_path, testcase_file_path)

    # Print the command output.
    log_header_format = ('Command: %s\n' 'Bot: %s\n' 'Time ran: %f\n')
    bot_name = environment.get_value('BOT_NAME', '')
    command = fuzz_result.command
    if use_minijail:
        # Remove minijail prefix.
        command = engine_common.strip_minijail_command(command, fuzzer_path)
    print(log_header_format % (engine_common.get_command_quoted(command),
                               bot_name, fuzz_result.time_executed))

    # Parse stats information based on libFuzzer output.
    parsed_stats = parse_log_stats(log_lines)

    # Extend parsed stats by additional performance features.
    parsed_stats.update(
        stats.parse_performance_features(log_lines, fuzzing_strategies,
                                         arguments))

    # Set some initial stat overrides.
    timeout_limit = fuzzer_utils.extract_argument(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)
    stat_overrides = {
        '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.
    remove_fuzzing_arguments(arguments)

    # Make a decision on whether merge step is needed at all. If there are no
    # new units added by libFuzzer run, then no need to do merge at all.
    new_units_added = shell.get_directory_file_count(new_testcases_directory)
    merge_error = None
    if new_units_added:
        # Merge the new units with the initial corpus.
        if corpus_directory not in corpus_directories:
            corpus_directories.append(corpus_directory)

        # If this times out, it's possible that we will miss some units. However, if
        # we're taking >10 minutes to load/merge the corpus something is going very
        # wrong and we probably don't want to make things worse by adding units
        # anyway.

        merge_tmp_dir = None
        if not use_minijail:
            merge_tmp_dir = os.path.join(fuzzer_utils.get_temp_dir(),
                                         'merge_workdir')
            engine_common.recreate_directory(merge_tmp_dir)

        old_corpus_len = shell.get_directory_file_count(corpus_directory)
        merge_directory = create_merge_directory()
        corpus_directories.insert(0, merge_directory)

        if use_minijail:
            bind_corpus_dirs(minijail_chroot, [merge_directory])

        merge_result = runner.merge(
            corpus_directories,
            merge_timeout=engine_common.get_merge_timeout(
                DEFAULT_MERGE_TIMEOUT),
            tmp_dir=merge_tmp_dir,
            additional_args=arguments)

        move_mergeable_units(merge_directory, corpus_directory)
        new_corpus_len = shell.get_directory_file_count(corpus_directory)
        new_units_added = 0

        merge_error = None
        if merge_result.timed_out:
            merge_error = 'Merging new testcases timed out:'
        elif merge_result.return_code != 0:
            merge_error = 'Merging new testcases failed:'
        else:
            new_units_added = new_corpus_len - old_corpus_len

        stat_overrides['new_units_added'] = new_units_added

        if merge_result.output:
            stat_overrides.update(
                stats.parse_stats_from_merge_log(
                    merge_result.output.splitlines()))
    else:
        stat_overrides['new_units_added'] = 0
        logs.log('Skipped corpus merge since no new units added by fuzzing.')

    # Get corpus size after merge. This removes the duplicate units that were
    # created during this fuzzing session.
    stat_overrides['corpus_size'] = shell.get_directory_file_count(
        corpus_directory)

    # Delete all corpus directories except for the main one. These were temporary
    # directories to store new testcase mutations and have already been merged to
    # main corpus directory.
    if corpus_directory in corpus_directories:
        corpus_directories.remove(corpus_directory)
    for directory in corpus_directories:
        shutil.rmtree(directory, ignore_errors=True)

    if use_minijail:
        unbind_corpus_dirs(minijail_chroot, corpus_directories)

    # Apply overridden stats to the parsed stats prior to dumping.
    parsed_stats.update(stat_overrides)

    # Dump stats data for further uploading to BigQuery.
    engine_common.dump_big_query_data(parsed_stats, testcase_file_path,
                                      LIBFUZZER_PREFIX, fuzzer_name, command)

    # Add custom crash state based on fuzzer name (if needed).
    add_custom_crash_state_if_needed(fuzzer_name, log_lines, parsed_stats)
    for line in log_lines:
        print(line)

    # Add fuzzing strategies used.
    engine_common.print_fuzzing_strategies(fuzzing_strategies)

    # Add merge error (if any).
    if merge_error:
        print(data_types.CRASH_STACKTRACE_END_MARKER)
        print(merge_error)
        print(
            'Command:',
            get_printable_command(merge_result.command, fuzzer_path,
                                  use_minijail))
        print(merge_result.output)

    analyze_and_update_recommended_dictionary(runner, fuzzer_name, log_lines,
                                              corpus_directory, arguments)

    # Close minijail chroot.
    if use_minijail:
        minijail_chroot.close()

    # Record the stats to make them easily searchable in stackdriver.
    if new_units_added:
        logs.log('New units added to corpus: %d.' % new_units_added,
                 stats=parsed_stats)
    else:
        logs.log('No new units found.', stats=parsed_stats)
Exemplo n.º 5
0
    def run(self, input_directory, output_directory, no_of_files):
        """Run the fuzzer to generate testcases."""
        build_directory = environment.get_value('BUILD_DIR')

        if not build_directory:
            raise BuiltinFuzzerException(
                'BUILD_DIR environment variable is not set.')

        fuzzers = fuzzers_utils.get_fuzz_targets(build_directory)

        if not fuzzers:
            raise BuiltinFuzzerException(
                'No fuzzer binaries found in |BUILD_DIR| directory.')

        fuzzer_binary_name = environment.get_value('FUZZ_TARGET')
        if fuzzer_binary_name:
            fuzzer_path = _get_fuzzer_path(fuzzers, fuzzer_binary_name)
        else:
            fuzzer_path = random.SystemRandom().choice(fuzzers)
            fuzzer_binary_name = os.path.basename(fuzzer_path)

        project_qualified_name = data_types.fuzz_target_project_qualified_name(
            utils.current_project(), fuzzer_binary_name)

        corpus_directory = os.path.join(input_directory,
                                        project_qualified_name)
        if environment.is_trusted_host():
            from bot.untrusted_runner import file_host
            corpus_directory = file_host.rebase_to_worker_root(
                corpus_directory)

        arguments = self.generate_arguments(fuzzer_path)

        # Create corpus directory if it does not exist already.
        if environment.is_trusted_host():
            from bot.untrusted_runner import file_host
            file_host.create_directory(corpus_directory,
                                       create_intermediates=True)
        else:
            if not os.path.exists(corpus_directory):
                os.mkdir(corpus_directory)

        # Create fuzz testcases.
        for i in range(no_of_files):
            # Contents of testcase file don't matter at this point. Need to create
            # something non-null so that it is not ignored.
            testcase_file_path = os.path.join(output_directory,
                                              '%s%d' % (tests.FUZZ_PREFIX, i))
            utils.write_data_to_file(' ', testcase_file_path)

            # Write the flags file containing command line for running launcher
            # script.
            flags_file_path = os.path.join(output_directory,
                                           '%s%d' % (tests.FLAGS_PREFIX, i))
            flags = ['%TESTCASE%', fuzzer_binary_name]
            if arguments:
                flags.append(arguments)

            flags_file_content = ' '.join(flags)
            utils.write_data_to_file(flags_file_content, flags_file_path)

        output = 'Generated %d testcase for fuzzer %s.\n' % (
            no_of_files, fuzzer_binary_name)
        output += 'metadata::fuzzer_binary_name: %s\n' % fuzzer_binary_name

        issue_owners = engine_common.get_issue_owners(fuzzer_path)
        if issue_owners:
            output += 'metadata::issue_owners: %s\n' % ','.join(issue_owners)

        issue_labels = engine_common.get_issue_labels(fuzzer_path)
        if issue_labels:
            output += 'metadata::issue_labels: %s\n' % ','.join(issue_labels)

        # Update *SAN_OPTIONS in current environment from .options file. This
        # environment is used in fuzz task later for deriving the environment
        # string in |get_environment_settings_as_string| and embedding this as
        # part of stacktrace.
        engine_common.process_sanitizer_options_overrides(fuzzer_path)

        return BuiltinFuzzerResult(output=output,
                                   corpus_directory=corpus_directory)
Exemplo n.º 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 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)
Exemplo n.º 7
0
def get_project_qualified_fuzzer_name(target_path):
    """Return project qualified fuzzer name for a given target path."""
    return data_types.fuzz_target_project_qualified_name(
        utils.current_project(), os.path.basename(target_path))
Exemplo n.º 8
0
def main(argv):
  """Run libFuzzer as specified by argv."""
  atexit.register(fuzzer_utils.cleanup)

  # Initialize variables.
  arguments = argv[1:]
  testcase_file_path = arguments.pop(0)

  target_name = environment.get_value('FUZZ_TARGET')
  if arguments and arguments[0] == target_name:
    # Pop legacy fuzz target argument.
    arguments.pop(0)

  fuzzer_name = data_types.fuzz_target_project_qualified_name(
      utils.current_project(), target_name)

  # Initialize log handler.
  logs.configure(
      'run_fuzzer', {
          'fuzzer': fuzzer_name,
          'engine': 'libFuzzer',
          'job_name': environment.get_value('JOB_NAME')
      })

  profiler.start_if_needed('libfuzzer_launcher')

  # Make sure that the fuzzer binary exists.
  build_directory = environment.get_value('BUILD_DIR')
  fuzzer_path = engine_common.find_fuzzer_path(build_directory, target_name)
  if not fuzzer_path:
    return

  # Install signal handler.
  signal.signal(signal.SIGTERM, engine_common.signal_term_handler)

  # Set up temp dir.
  engine_common.recreate_directory(fuzzer_utils.get_temp_dir())

  # Setup minijail if needed.
  use_minijail = environment.get_value('USE_MINIJAIL')
  runner = libfuzzer.get_runner(
      fuzzer_path, temp_dir=fuzzer_utils.get_temp_dir())

  if use_minijail:
    minijail_chroot = runner.chroot
  else:
    minijail_chroot = None

  # Get corpus directory.
  corpus_directory = environment.get_value('FUZZ_CORPUS_DIR')

  # Add common arguments which are necessary to be used for every run.
  arguments = expand_with_common_arguments(arguments)

  # Add sanitizer options to environment that were specified in the .options
  # file and options that this script requires.
  set_sanitizer_options(fuzzer_path)

  # If we don't have a corpus, then that means this is not a fuzzing run.
  if not corpus_directory:
    load_testcase_if_exists(runner, testcase_file_path, fuzzer_name,
                            use_minijail, arguments)
    return

  # We don't have a crash testcase, fuzz.

  # Check dict argument to make sure that it's valid.
  dict_argument = fuzzer_utils.extract_argument(
      arguments, constants.DICT_FLAG, remove=False)
  if dict_argument and not os.path.exists(dict_argument):
    logs.log_error('Invalid dict %s for %s.' % (dict_argument, fuzzer_name))
    fuzzer_utils.extract_argument(arguments, constants.DICT_FLAG)

  # If there's no dict argument, check for %target_binary_name%.dict file.
  if (not fuzzer_utils.extract_argument(
      arguments, constants.DICT_FLAG, remove=False)):
    default_dict_path = dictionary_manager.get_default_dictionary_path(
        fuzzer_path)
    if os.path.exists(default_dict_path):
      arguments.append(constants.DICT_FLAG + default_dict_path)

  # Set up scratch directory for writing new units.
  new_testcases_directory = create_corpus_directory('new')

  # Strategy pool is the list of strategies that we attempt to enable, whereas
  # fuzzing strategies is the list of strategies that are enabled. (e.g. if
  # mutator is selected in the pool, but not available for a given target, it
  # would not be added to fuzzing strategies.)
  strategy_pool = strategy_selection.generate_weighted_strategy_pool(
      strategy_list=strategy.LIBFUZZER_STRATEGY_LIST,
      use_generator=True,
      engine_name='libFuzzer')
  strategy_info = pick_strategies(
      strategy_pool,
      fuzzer_path,
      corpus_directory,
      arguments,
      minijail_chroot=minijail_chroot)
  arguments.extend(strategy_info.arguments)

  # Timeout for fuzzer run.
  fuzz_timeout = get_fuzz_timeout(strategy_info.is_mutations_run)

  # Get list of corpus directories.
  # TODO(flowerhack): Implement this to handle corpus sync'ing.
  if environment.platform() == 'FUCHSIA':
    corpus_directories = []
  else:
    corpus_directories = get_corpus_directories(
        corpus_directory,
        new_testcases_directory,
        fuzzer_path,
        strategy_info.fuzzing_strategies,
        strategy_pool,
        minijail_chroot=minijail_chroot,
        allow_corpus_subset=not strategy_info.use_dataflow_tracing)

  corpus_directories.extend(strategy_info.additional_corpus_dirs)

  artifact_prefix = os.path.abspath(os.path.dirname(testcase_file_path))
  # Execute the fuzzer binary with original arguments.
  fuzz_result = runner.fuzz(
      corpus_directories,
      fuzz_timeout=fuzz_timeout,
      artifact_prefix=artifact_prefix,
      additional_args=arguments,
      extra_env=strategy_info.extra_env)

  if (not 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, 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 crash_testcase_file_path:
    # Copy crash testcase contents into the main testcase path.
    shutil.move(crash_testcase_file_path, testcase_file_path)

  # Print the command output.
  bot_name = environment.get_value('BOT_NAME', '')
  command = fuzz_result.command
  if use_minijail:
    # Remove minijail prefix.
    command = engine_common.strip_minijail_command(command, fuzzer_path)
  print(engine_common.get_log_header(command, bot_name,
                                     fuzz_result.time_executed))

  # Parse stats information based on libFuzzer output.
  parsed_stats = parse_log_stats(log_lines)

  # Extend parsed stats by additional performance features.
  parsed_stats.update(
      stats.parse_performance_features(
          log_lines, strategy_info.fuzzing_strategies, arguments))

  # Set some initial stat overrides.
  timeout_limit = fuzzer_utils.extract_argument(
      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)
  stat_overrides = {
      '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.
  remove_fuzzing_arguments(arguments)

  # Make a decision on whether merge step is needed at all. If there are no
  # new units added by libFuzzer run, then no need to do merge at all.
  new_units_added = shell.get_directory_file_count(new_testcases_directory)
  merge_error = None
  if new_units_added:
    # Merge the new units with the initial corpus.
    if corpus_directory not in corpus_directories:
      corpus_directories.append(corpus_directory)

    # If this times out, it's possible that we will miss some units. However, if
    # we're taking >10 minutes to load/merge the corpus something is going very
    # wrong and we probably don't want to make things worse by adding units
    # anyway.

    merge_tmp_dir = None
    if not use_minijail:
      merge_tmp_dir = os.path.join(fuzzer_utils.get_temp_dir(), 'merge_workdir')
      engine_common.recreate_directory(merge_tmp_dir)

    old_corpus_len = shell.get_directory_file_count(corpus_directory)
    merge_directory = create_merge_directory()
    corpus_directories.insert(0, merge_directory)

    if use_minijail:
      bind_corpus_dirs(minijail_chroot, [merge_directory])

    merge_result = runner.merge(
        corpus_directories,
        merge_timeout=engine_common.get_merge_timeout(DEFAULT_MERGE_TIMEOUT),
        tmp_dir=merge_tmp_dir,
        additional_args=arguments)

    move_mergeable_units(merge_directory, corpus_directory)
    new_corpus_len = shell.get_directory_file_count(corpus_directory)
    new_units_added = 0

    merge_error = None
    if merge_result.timed_out:
      merge_error = 'Merging new testcases timed out:'
    elif merge_result.return_code != 0:
      merge_error = 'Merging new testcases failed:'
    else:
      new_units_added = new_corpus_len - old_corpus_len

    stat_overrides['new_units_added'] = new_units_added

    if merge_result.output:
      stat_overrides.update(
          stats.parse_stats_from_merge_log(merge_result.output.splitlines()))
  else:
    stat_overrides['new_units_added'] = 0
    logs.log('Skipped corpus merge since no new units added by fuzzing.')

  # Get corpus size after merge. This removes the duplicate units that were
  # created during this fuzzing session.
  # TODO(flowerhack): Remove this workaround once we can handle corpus sync.
  if environment.platform() != 'FUCHSIA':
    stat_overrides['corpus_size'] = shell.get_directory_file_count(
        corpus_directory)

  # Delete all corpus directories except for the main one. These were temporary
  # directories to store new testcase mutations and have already been merged to
  # main corpus directory.
  if corpus_directory in corpus_directories:
    corpus_directories.remove(corpus_directory)
  for directory in corpus_directories:
    shutil.rmtree(directory, ignore_errors=True)

  if use_minijail:
    unbind_corpus_dirs(minijail_chroot, corpus_directories)

  # Apply overridden stats to the parsed stats prior to dumping.
  parsed_stats.update(stat_overrides)

  # Dump stats data for further uploading to BigQuery.
  engine_common.dump_big_query_data(parsed_stats, testcase_file_path, command)

  # Add custom crash state based on fuzzer name (if needed).
  add_custom_crash_state_if_needed(fuzzer_name, log_lines, parsed_stats)
  for line in log_lines:
    print(line)

  # Add fuzzing strategies used.
  print(engine_common.format_fuzzing_strategies(
      strategy_info.fuzzing_strategies))

  # Add merge error (if any).
  if merge_error:
    print(data_types.CRASH_STACKTRACE_END_MARKER)
    print(merge_error)
    print('Command:',
          get_printable_command(merge_result.command, fuzzer_path,
                                use_minijail))
    print(merge_result.output)

  analyze_and_update_recommended_dictionary(runner, fuzzer_name, log_lines,
                                            corpus_directory, arguments)

  # Close minijail chroot.
  if use_minijail:
    minijail_chroot.close()

  # Record the stats to make them easily searchable in stackdriver.
  if new_units_added:
    logs.log(
        'New units added to corpus: %d.' % new_units_added, stats=parsed_stats)
  else:
    logs.log('No new units found.', stats=parsed_stats)
Exemplo n.º 9
0
def pick_strategies(strategy_pool,
                    fuzzer_path,
                    corpus_directory,
                    existing_arguments,
                    minijail_chroot=None):
  """Pick strategies."""
  build_directory = environment.get_value('BUILD_DIR')
  target_name = os.path.basename(fuzzer_path)
  project_qualified_fuzzer_name = data_types.fuzz_target_project_qualified_name(
      utils.current_project(), target_name)

  fuzzing_strategies = []
  arguments = []
  additional_corpus_dirs = []

  # Select a generator to attempt to use for existing testcase mutations.
  candidate_generator = engine_common.select_generator(strategy_pool,
                                                       fuzzer_path)
  is_mutations_run = candidate_generator != engine_common.Generator.NONE

  # Depends on the presense of DFSan instrumented build.
  dataflow_build_dir = environment.get_value('DATAFLOW_BUILD_DIR')
  use_dataflow_tracing = (
      dataflow_build_dir and
      strategy_pool.do_strategy(strategy.DATAFLOW_TRACING_STRATEGY))
  if use_dataflow_tracing:
    dataflow_binary_path = os.path.join(
        dataflow_build_dir, os.path.relpath(fuzzer_path, build_directory))
    if os.path.exists(dataflow_binary_path):
      arguments.append(
          '%s%s' % (constants.COLLECT_DATA_FLOW_FLAG, dataflow_binary_path))
      fuzzing_strategies.append(strategy.DATAFLOW_TRACING_STRATEGY.name)
    else:
      logs.log_error(
          'Fuzz target is not found in dataflow build, skiping strategy.')
      use_dataflow_tracing = False

  # Generate new testcase mutations using radamsa, etc.
  if is_mutations_run:
    new_testcase_mutations_directory = create_corpus_directory('mutations')
    generator_used = engine_common.generate_new_testcase_mutations(
        corpus_directory, new_testcase_mutations_directory,
        project_qualified_fuzzer_name, candidate_generator)

    # Add the used generator strategy to our fuzzing strategies list.
    if generator_used:
      if candidate_generator == engine_common.Generator.RADAMSA:
        fuzzing_strategies.append(
            strategy.CORPUS_MUTATION_RADAMSA_STRATEGY.name)
      elif candidate_generator == engine_common.Generator.ML_RNN:
        fuzzing_strategies.append(strategy.CORPUS_MUTATION_ML_RNN_STRATEGY.name)

    additional_corpus_dirs.append(new_testcase_mutations_directory)
    if minijail_chroot:
      bind_corpus_dirs(minijail_chroot, [new_testcase_mutations_directory])

  if strategy_pool.do_strategy(strategy.RANDOM_MAX_LENGTH_STRATEGY):
    max_len_argument = fuzzer_utils.extract_argument(
        existing_arguments, constants.MAX_LEN_FLAG, remove=False)
    if not max_len_argument:
      max_length = random.SystemRandom().randint(1, MAX_VALUE_FOR_MAX_LENGTH)
      arguments.append('%s%d' % (constants.MAX_LEN_FLAG, max_length))
      fuzzing_strategies.append(strategy.RANDOM_MAX_LENGTH_STRATEGY.name)

  if (strategy_pool.do_strategy(strategy.RECOMMENDED_DICTIONARY_STRATEGY) and
      add_recommended_dictionary(arguments, project_qualified_fuzzer_name,
                                 fuzzer_path)):
    fuzzing_strategies.append(strategy.RECOMMENDED_DICTIONARY_STRATEGY.name)

  if strategy_pool.do_strategy(strategy.VALUE_PROFILE_STRATEGY):
    arguments.append(constants.VALUE_PROFILE_ARGUMENT)
    fuzzing_strategies.append(strategy.VALUE_PROFILE_STRATEGY.name)

  # DataFlow Tracing requires fork mode, always use it with DFT strategy.
  if use_dataflow_tracing or strategy_pool.do_strategy(strategy.FORK_STRATEGY):
    max_fuzz_threads = environment.get_value('MAX_FUZZ_THREADS', 1)
    num_fuzz_processes = max(1, multiprocessing.cpu_count() // max_fuzz_threads)
    arguments.append('%s%d' % (constants.FORK_FLAG, num_fuzz_processes))
    fuzzing_strategies.append(
        '%s_%d' % (strategy.FORK_STRATEGY.name, num_fuzz_processes))

  extra_env = {}
  if (strategy_pool.do_strategy(strategy.MUTATOR_PLUGIN_STRATEGY) and
      use_mutator_plugin(target_name, extra_env, minijail_chroot)):
    fuzzing_strategies.append(strategy.MUTATOR_PLUGIN_STRATEGY.name)

  return StrategyInfo(fuzzing_strategies, arguments, additional_corpus_dirs,
                      extra_env, use_dataflow_tracing, is_mutations_run)
Exemplo n.º 10
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)

        # 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 = libfuzzer.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=reproducers_dir,
                                  extra_env=options.extra_env)

        if (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,
                           engine_output=fuzz_result.output)

        log_lines = utils.decode_to_unicode(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)

        # 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,
                                             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[:]
        libfuzzer.remove_fuzzing_arguments(arguments)

        self._merge_new_units(target_path, options.corpus_dir, new_corpus_dir,
                              options.fuzz_corpus_dirs, arguments,
                              parsed_stats)

        fuzz_logs = '\n'.join(log_lines)
        crashes = []
        if crash_testcase_file_path:
            # Use higher timeout for reproduction.
            reproduce_arguments = arguments[:]
            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))

        project_qualified_fuzzer_name = (
            data_types.fuzz_target_project_qualified_name(
                utils.current_project(), os.path.basename(target_path)))
        libfuzzer.analyze_and_update_recommended_dictionary(
            runner, project_qualified_fuzzer_name, log_lines,
            options.corpus_dir, arguments)

        return engine.FuzzResult(fuzz_logs, fuzz_result.command, crashes,
                                 parsed_stats, fuzz_result.time_executed)
Exemplo n.º 11
0
def main(argv):
    """Run afl as specified by argv."""
    atexit.register(fuzzer_utils.cleanup)

    # Initialize variables.
    _, testcase_file_path, target_name = argv[:3]
    input_directory = environment.get_value('FUZZ_CORPUS_DIR')
    fuzzer_name = data_types.fuzz_target_project_qualified_name(
        utils.current_project(), target_name)

    # Initialize log handler.
    logs.configure(
        'run_fuzzer', {
            'fuzzer': fuzzer_name,
            'engine': 'afl',
            'job_name': environment.get_value('JOB_NAME')
        })

    build_directory = environment.get_value('BUILD_DIR')
    fuzzer_path = engine_common.find_fuzzer_path(build_directory, target_name)
    if not fuzzer_path:
        # This is an expected case when doing regression testing with old builds
        # that do not have that fuzz target. It can also happen when a host sends a
        # message to an untrusted worker that just restarted and lost information on
        # build directory.
        logs.log_warn('Could not find fuzz target %s.' % target_name)
        return

    # Install signal handler.
    signal.signal(signal.SIGTERM, engine_common.signal_term_handler)

    # Set up temp dir.
    engine_common.recreate_directory(fuzzer_utils.get_temp_dir())

    config = AflConfig.from_target_path(fuzzer_path)

    runner = AflRunner(fuzzer_path, config, testcase_file_path,
                       input_directory)

    # Add *SAN_OPTIONS overrides from .options file.
    engine_common.process_sanitizer_options_overrides(fuzzer_path)

    # If we don't have a corpus, then that means this is not a fuzzing run.
    if not input_directory:
        load_testcase_if_exists(runner, testcase_file_path)
        return

    # Make sure afl won't exit because of bad sanitizer options.
    set_additional_sanitizer_options_for_afl_fuzz()

    # Execute afl-fuzz on the fuzzing target.
    fuzz_result = runner.fuzz()

    # Print info for the fuzzer logs.
    command = fuzz_result.command
    print('Command: {0}\n'
          'Bot: {1}\n'
          'Time ran: {2}\n').format(engine_common.get_command_quoted(command),
                                    BOT_NAME, fuzz_result.time_executed)

    print fuzz_result.output
    runner.strategies.print_strategies()

    if fuzz_result.return_code:
        # If AFL returned a non-zero return code quit now without getting stats,
        # since they would be meaningless.
        print runner.fuzzer_stderr
        return

    stats_getter = stats.StatsGetter(runner.afl_output.stats_path,
                                     config.dict_path)
    try:
        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)

        engine_common.dump_big_query_data(stats_getter.stats,
                                          testcase_file_path, AFL_PREFIX,
                                          fuzzer_name, command)

    finally:
        print runner.fuzzer_stderr

    # Whenever new units are added to corpus, record the stats to make them
    # easily searchable in stackdriver.
    if new_units_added:
        logs.log('New units added to corpus: %d.' % new_units_added,
                 stats=stats_getter.stats)