Esempio n. 1
0
def remove_fuzzing_arguments(arguments):
  """Remove arguments used during fuzzing."""
  for argument in [
      constants.DICT_FLAG,  # User for fuzzing only.
      constants.MAX_LEN_FLAG,  # This may shrink the testcases.
      constants.RUNS_FLAG,  # Make sure we don't have any '-runs' argument.
      constants.FORK_FLAG,  # It overrides `-merge` argument.
      constants.COLLECT_DATA_FLOW_FLAG,  # Used for fuzzing only.
  ]:
    fuzzer_utils.extract_argument(arguments, argument)
Esempio n. 2
0
def expand_with_common_arguments(arguments):
  """Return list of arguments expanded by necessary common options."""
  # We prefer to add common arguments (either default or custom) in run.py, but
  # in order to maintain compatibility with previously found testcases, we do
  # add necessary common arguments here as well.
  common_arguments = []

  if not fuzzer_utils.extract_argument(
      arguments, constants.RSS_LIMIT_FLAG, remove=False):
    common_arguments.append(
        '%s%d' % (constants.RSS_LIMIT_FLAG, constants.DEFAULT_RSS_LIMIT_MB))

  if not fuzzer_utils.extract_argument(
      arguments, constants.TIMEOUT_FLAG, remove=False):
    common_arguments.append(
        '%s%d' % (constants.TIMEOUT_FLAG, constants.DEFAULT_TIMEOUT_LIMIT))

  return arguments + common_arguments
Esempio n. 3
0
    def from_target_path(cls, target_path):
        """Instantiates and returns an AFLConfig object. The object is configured
    based on |target_path|."""
        config = cls()
        config.parse_options(target_path)
        config.dict_path = fuzzer_utils.extract_argument(
            config.additional_afl_arguments, constants.DICT_FLAG, remove=False)

        config.use_default_dict(target_path)
        return config
Esempio n. 4
0
def remove_fuzzing_arguments(arguments):
    """Remove arguments used during fuzzing."""
    # Remove un-needed dictionary argument.
    fuzzer_utils.extract_argument(arguments, constants.DICT_FLAG)

    # 'max_len' option may shrink the testcase if its size greater than 'max_len'.
    fuzzer_utils.extract_argument(arguments, constants.MAX_LEN_FLAG)

    # Remove custom '-runs' argument.
    fuzzer_utils.extract_argument(arguments, constants.RUNS_FLAG)

    # Remove `-fork' argument since it overrides '-merge' argument.
    fuzzer_utils.extract_argument(arguments, constants.FORK_FLAG)
Esempio n. 5
0
def add_recommended_dictionary(arguments, fuzzer_name, fuzzer_path):
    """Add recommended dictionary from GCS to existing .dict file or create
  a new one and update the arguments as needed.
  This function modifies |arguments| list in some cases."""
    recommended_dictionary_path = os.path.join(
        fuzzer_utils.get_temp_dir(),
        dictionary_manager.RECOMMENDED_DICTIONARY_FILENAME)

    dict_manager = dictionary_manager.DictionaryManager(fuzzer_name)

    try:
        # Bail out if cannot download recommended dictionary from GCS.
        if not dict_manager.download_recommended_dictionary_from_gcs(
                recommended_dictionary_path):
            return False
    except Exception as ex:
        logs.log_error('Exception downloading recommended dictionary:\n%s.' %
                       str(ex))
        return False

    # Bail out if the downloaded dictionary is empty.
    if not os.path.getsize(recommended_dictionary_path):
        return False

    # Check if there is an existing dictionary file in arguments.
    original_dictionary_path = fuzzer_utils.extract_argument(
        arguments, constants.DICT_FLAG)
    merged_dictionary_path = (
        original_dictionary_path
        or dictionary_manager.get_default_dictionary_path(fuzzer_path))
    merged_dictionary_path += MERGED_DICT_SUFFIX

    dictionary_manager.merge_dictionary_files(original_dictionary_path,
                                              recommended_dictionary_path,
                                              merged_dictionary_path)
    arguments.append(constants.DICT_FLAG + merged_dictionary_path)
    return True
Esempio n. 6
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)
Esempio n. 7
0
    def prepare(self, corpus_dir, target_path, _):
        """Prepare for a fuzzing session, by generating options. Returns a
    FuzzOptions object.

    Args:
      corpus_dir: The main corpus directory.
      target_path: Path to the target.
      build_dir: Path to the build directory.

    Returns:
      A FuzzOptions object.
    """
        arguments = fuzzer.get_arguments(target_path)
        strategy_pool = strategy_selection.generate_weighted_strategy_pool(
            strategy_list=strategy.LIBFUZZER_STRATEGY_LIST,
            use_generator=True,
            engine_name=self.name)
        strategy_info = launcher.pick_strategies(strategy_pool, target_path,
                                                 corpus_dir, arguments)

        arguments.extend(strategy_info.arguments)

        # Check for seed corpus and add it into corpus directory.
        engine_common.unpack_seed_corpus_if_needed(target_path, corpus_dir)

        # Pick a few testcases from our corpus to use as the initial corpus.
        subset_size = engine_common.random_choice(
            engine_common.CORPUS_SUBSET_NUM_TESTCASES)

        if (not strategy_info.use_dataflow_tracing
                and strategy_pool.do_strategy(strategy.CORPUS_SUBSET_STRATEGY)
                and shell.get_directory_file_count(corpus_dir) > subset_size):
            # Copy |subset_size| testcases into 'subset' directory.
            corpus_subset_dir = self._create_temp_corpus_dir('subset')
            launcher.copy_from_corpus(corpus_subset_dir, corpus_dir,
                                      subset_size)
            strategy_info.fuzzing_strategies.append(
                strategy.CORPUS_SUBSET_STRATEGY.name + '_' + str(subset_size))
            strategy_info.additional_corpus_dirs.append(corpus_subset_dir)
        else:
            strategy_info.additional_corpus_dirs.append(corpus_dir)

        # 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, target_path))
            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(
                target_path)
            if os.path.exists(default_dict_path):
                arguments.append(constants.DICT_FLAG + default_dict_path)

        return LibFuzzerOptions(corpus_dir, arguments,
                                strategy_info.fuzzing_strategies,
                                strategy_info.additional_corpus_dirs,
                                strategy_info.extra_env,
                                strategy_info.use_dataflow_tracing,
                                strategy_info.is_mutations_run)
Esempio n. 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 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)
Esempio n. 9
0
def parse_performance_features(log_lines, strategies, arguments):
  """Extract stats for performance analysis."""
  # Initialize stats with default values.
  stats = {
      'bad_instrumentation': 0,
      'corpus_crash_count': 0,
      'corpus_size': 0,
      'crash_count': 0,
      'dict_used': 0,
      'edge_coverage': 0,
      'edges_total': 0,
      'feature_coverage': 0,
      'initial_edge_coverage': 0,
      'initial_feature_coverage': 0,
      'leak_count': 0,
      'log_lines_unwanted': 0,
      'log_lines_from_engine': 0,
      'log_lines_ignored': 0,
      'max_len': 0,
      'manual_dict_size': 0,
      'merge_edge_coverage': 0,
      'merge_new_files': 0,
      'merge_new_features': 0,
      'oom_count': 0,
      'recommended_dict_size': 0,
      'slow_unit_count': 0,
      'slow_units_count': 0,
      'startup_crash_count': 1,
      'strategy_corpus_mutations_radamsa': 0,
      'strategy_corpus_mutations_ml_rnn': 0,
      'strategy_corpus_subset': 0,
      'strategy_fork': 0,
      'strategy_mutator_plugin': 0,
      'strategy_random_max_len': 0,
      'strategy_recommended_dict': 0,
      'strategy_value_profile': 0,
      'timeout_count': 0,
  }

  # Process fuzzing strategies used.
  stats.update(parse_fuzzing_strategies(log_lines, strategies))

  # Corpus rss is only applicable when using the full corpus and not
  # in the corpus subset strategy run.
  if not stats['strategy_corpus_subset']:
    stats['corpus_rss_mb'] = 0

  (stats['log_lines_unwanted'], stats['log_lines_from_engine'],
   stats['log_lines_ignored']) = calculate_log_lines(log_lines)

  # Extract '-max_len' value from arguments, if possible.
  stats['max_len'] = int(
      fuzzer_utils.extract_argument(
          arguments, constants.MAX_LEN_FLAG, remove=False) or stats['max_len'])

  # Extract sizes of manual and recommended dictionary used for fuzzing.
  dictionary_path = fuzzer_utils.extract_argument(
      arguments, constants.DICT_FLAG, remove=False)
  stats['manual_dict_size'], stats['recommended_dict_size'] = (
      dictionary_manager.get_stats_for_dictionary_file(dictionary_path))

  # Different crashes and other flags extracted via regexp match.
  has_corpus = False
  for line in log_lines:
    if LIBFUZZER_BAD_INSTRUMENTATION_REGEX.match(line):
      stats['bad_instrumentation'] = 1
      continue

    if LIBFUZZER_CRASH_TESTCASE_REGEX.match(line):
      stats['crash_count'] = 1
      continue

    if LIBFUZZER_LOG_DICTIONARY_REGEX.match(line):
      stats['dict_used'] = 1
      continue

    if LEAK_TESTCASE_REGEX.match(line):
      stats['leak_count'] = 1
      continue

    if (LIBFUZZER_OOM_TESTCASE_REGEX.match(line) or
        stack_analyzer.OUT_OF_MEMORY_REGEX.match(line)):
      stats['oom_count'] = 1
      continue

    if LIBFUZZER_SLOW_UNIT_TESTCASE_REGEX.match(line):
      # Use |slow_unit_count| to track if this run had any slow units at all.
      # and use |slow_units_count| to track the actual number of slow units in
      # this run (used by performance analyzer).
      stats['slow_unit_count'] = 1
      stats['slow_units_count'] += 1
      continue

    match = LIBFUZZER_LOG_SEED_CORPUS_INFO_REGEX.match(line)
    if match:
      has_corpus = True
      if not stats['strategy_corpus_subset']:
        stats['corpus_rss_mb'] = int(match.group(2))

    match = LIBFUZZER_MODULES_LOADED_REGEX.match(line)
    if match:
      stats['startup_crash_count'] = 0
      stats['edges_total'] = int(match.group(2))

    match = LIBFUZZER_LOG_START_INITED_REGEX.match(line)
    if match:
      stats['initial_edge_coverage'] = int(match.group(1))
      stats['initial_feature_coverage'] = int(match.group(2))
      continue

    # This regexp will match multiple lines and will be overwriting the stats.
    # This is done on purpose, as the last line in the log may have different
    # format, e.g. 'DONE' without a crash and 'NEW' or 'pulse' with a crash.
    match = LIBFUZZER_LOG_COVERAGE_REGEX.match(line)
    if match:
      stats['edge_coverage'] = int(match.group(1))
      stats['feature_coverage'] = int(match.group(2))
      continue

    if (LIBFUZZER_TIMEOUT_TESTCASE_REGEX.match(line) or
        stack_analyzer.LIBFUZZER_TIMEOUT_REGEX.match(line)):
      stats['timeout_count'] = 1
      continue

    if not stats['max_len']:
      # Get "max_len" value from the log, if it has not been found in arguments.
      match = LIBFUZZER_LOG_MAX_LEN_REGEX.match(line)
      if match:
        stats['max_len'] = int(match.group(1))
        continue

  if has_corpus and not stats['log_lines_from_engine']:
    stats['corpus_crash_count'] = 1

  return stats
Esempio n. 10
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)
Esempio n. 11
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)
Esempio n. 12
0
def parse_performance_features(log_lines, strategies, arguments):
  """Extract stats for performance analysis."""
  # Initialize stats with default values.
  stats = {
      'bad_instrumentation': 0,
      'corpus_crash_count': 0,
      'corpus_size': 0,
      'crash_count': 0,
      'dict_used': 0,
      'edge_coverage': 0,
      'edges_total': 0,
      'feature_coverage': 0,
      'initial_edge_coverage': 0,
      'initial_feature_coverage': 0,
      'leak_count': 0,
      'log_lines_unwanted': 0,
      'log_lines_from_engine': 0,
      'log_lines_ignored': 0,
      'max_len': 0,
      'manual_dict_size': 0,
      'merge_edge_coverage': 0,
      'new_edges': 0,
      'new_features': 0,
      'oom_count': 0,
      'recommended_dict_size': 0,
      'slow_unit_count': 0,
      'slow_units_count': 0,
      'startup_crash_count': 1,
      'timeout_count': 0,
  }

  # Extract strategy selection method.
  stats['strategy_selection_method'] = environment.get_value(
      'STRATEGY_SELECTION_METHOD', default_value='default')

  # Initialize all strategy stats as disabled by default.
  for strategy_type in strategy.strategy_list:
    stats[strategy_column_name(strategy_type.name)] = 0

  # Process fuzzing strategies used.
  stats.update(parse_fuzzing_strategies(log_lines, strategies))

  (stats['log_lines_unwanted'], stats['log_lines_from_engine'],
   stats['log_lines_ignored']) = calculate_log_lines(log_lines)

  # Extract '-max_len' value from arguments, if possible.
  stats['max_len'] = int(
      fuzzer_utils.extract_argument(
          arguments, constants.MAX_LEN_FLAG, remove=False) or stats['max_len'])

  # Extract sizes of manual and recommended dictionary used for fuzzing.
  dictionary_path = fuzzer_utils.extract_argument(
      arguments, constants.DICT_FLAG, remove=False)
  stats['manual_dict_size'], stats['recommended_dict_size'] = (
      dictionary_manager.get_stats_for_dictionary_file(dictionary_path))

  # Different crashes and other flags extracted via regexp match.
  has_corpus = False
  libfuzzer_inited = False
  for line in log_lines:
    if LIBFUZZER_BAD_INSTRUMENTATION_REGEX.match(line):
      stats['bad_instrumentation'] = 1
      continue

    if LIBFUZZER_CRASH_TESTCASE_REGEX.match(line):
      stats['crash_count'] = 1
      continue

    if LIBFUZZER_LOG_DICTIONARY_REGEX.match(line):
      stats['dict_used'] = 1
      continue

    if LEAK_TESTCASE_REGEX.match(line):
      stats['leak_count'] = 1
      continue

    if (LIBFUZZER_OOM_TESTCASE_REGEX.match(line) or
        stack_analyzer.OUT_OF_MEMORY_REGEX.match(line)):
      stats['oom_count'] = 1
      continue

    if LIBFUZZER_SLOW_UNIT_TESTCASE_REGEX.match(line):
      # Use |slow_unit_count| to track if this run had any slow units at all.
      # and use |slow_units_count| to track the actual number of slow units in
      # this run (used by performance analyzer).
      stats['slow_unit_count'] = 1
      stats['slow_units_count'] += 1
      continue

    match = LIBFUZZER_LOG_SEED_CORPUS_INFO_REGEX.match(line)
    if match:
      has_corpus = True

    match = LIBFUZZER_MODULES_LOADED_REGEX.match(line)
    if match:
      stats['startup_crash_count'] = 0
      stats['edges_total'] = int(match.group(2))

    match = LIBFUZZER_LOG_START_INITED_REGEX.match(line)
    if match:
      stats['initial_edge_coverage'] = stats['edge_coverage'] = int(
          match.group(1))
      stats['initial_feature_coverage'] = stats['feature_coverage'] = int(
          match.group(2))
      libfuzzer_inited = True
      continue

    # This regexp will match multiple lines and will be overwriting the stats.
    # This is done on purpose, as the last line in the log may have different
    # format, e.g. 'DONE' without a crash and 'NEW' or 'pulse' with a crash.
    # Also, ignore values before INITED i.e. while seed corpus is being read.
    match = LIBFUZZER_LOG_COVERAGE_REGEX.match(line)
    if match and libfuzzer_inited:
      stats['edge_coverage'] = int(match.group(1))
      stats['feature_coverage'] = int(match.group(2))
      continue

    if (LIBFUZZER_TIMEOUT_TESTCASE_REGEX.match(line) or
        stack_analyzer.LIBFUZZER_TIMEOUT_REGEX.match(line)):
      stats['timeout_count'] = 1
      continue

    if not stats['max_len']:
      # Get "max_len" value from the log, if it has not been found in arguments.
      match = LIBFUZZER_LOG_MAX_LEN_REGEX.match(line)
      if match:
        stats['max_len'] = int(match.group(1))
        continue

  if has_corpus and not stats['log_lines_from_engine']:
    stats['corpus_crash_count'] = 1

  # new_cov_* is a reliable metric when corpus subset strategy is not used.
  if not stats['strategy_corpus_subset']:
    assert stats['edge_coverage'] >= stats['initial_edge_coverage']
    stats['new_edges'] = (
        stats['edge_coverage'] - stats['initial_edge_coverage'])

    assert stats['feature_coverage'] >= stats['initial_feature_coverage']
    stats['new_features'] = (
        stats['feature_coverage'] - stats['initial_feature_coverage'])

  return stats
Esempio n. 13
0
def parse_performance_features(log_lines, strategies, arguments):
    """Extract stats for performance analysis."""
    # TODO(ochang): Remove include_strategies once refactor is complete.
    # Initialize stats with default values.
    stats = {
        'bad_instrumentation': 0,
        'corpus_crash_count': 0,
        'corpus_size': 0,
        'crash_count': 0,
        'dict_used': 0,
        'edge_coverage': 0,
        'edges_total': 0,
        'feature_coverage': 0,
        'initial_edge_coverage': 0,
        'initial_feature_coverage': 0,
        'leak_count': 0,
        'log_lines_unwanted': 0,
        'log_lines_from_engine': 0,
        'log_lines_ignored': 0,
        'max_len': 0,
        'manual_dict_size': 0,
        'merge_edge_coverage': 0,
        'new_edges': 0,
        'new_features': 0,
        'oom_count': 0,
        'recommended_dict_size': 0,
        'slow_unit_count': 0,
        'slow_units_count': 0,
        'startup_crash_count': 1,
        'timeout_count': 0,
    }

    # Extract strategy selection method.
    # TODO(ochang): Move to more general place?
    stats['strategy_selection_method'] = environment.get_value(
        'STRATEGY_SELECTION_METHOD', default_value='default')

    # Initialize all strategy stats as disabled by default.
    for strategy_type in strategy.LIBFUZZER_STRATEGY_LIST:
        stats[strategy_column_name(strategy_type.name)] = 0

    # Process fuzzing strategies used.
    stats.update(parse_fuzzing_strategies(log_lines, strategies))

    (stats['log_lines_unwanted'], stats['log_lines_from_engine'],
     stats['log_lines_ignored']) = calculate_log_lines(log_lines)

    if stats['log_lines_from_engine'] > 0:
        stats['startup_crash_count'] = 0

    # Extract '-max_len' value from arguments, if possible.
    stats['max_len'] = int(
        fuzzer_utils.extract_argument(
            arguments, constants.MAX_LEN_FLAG, remove=False)
        or stats['max_len'])

    # Extract sizes of manual and recommended dictionary used for fuzzing.
    dictionary_path = fuzzer_utils.extract_argument(arguments,
                                                    constants.DICT_FLAG,
                                                    remove=False)
    stats['manual_dict_size'], stats['recommended_dict_size'] = (
        dictionary_manager.get_stats_for_dictionary_file(dictionary_path))

    # Different crashes and other flags extracted via regexp match.
    has_corpus = False
    for line in log_lines:
        if LIBFUZZER_BAD_INSTRUMENTATION_REGEX.match(line):
            stats['bad_instrumentation'] = 1
            continue

        if LIBFUZZER_CRASH_TESTCASE_REGEX.match(line):
            stats['crash_count'] = 1
            continue

        if LIBFUZZER_LOG_DICTIONARY_REGEX.match(line):
            stats['dict_used'] = 1
            continue

        if LEAK_TESTCASE_REGEX.match(line):
            stats['leak_count'] = 1
            continue

        if (LIBFUZZER_OOM_TESTCASE_REGEX.match(line)
                or stack_analyzer.OUT_OF_MEMORY_REGEX.match(line)):
            stats['oom_count'] = 1
            continue

        if LIBFUZZER_SLOW_UNIT_TESTCASE_REGEX.match(line):
            # Use |slow_unit_count| to track if this run had any slow units at all.
            # and use |slow_units_count| to track the actual number of slow units in
            # this run (used by performance analyzer).
            stats['slow_unit_count'] = 1
            stats['slow_units_count'] += 1
            continue

        match = LIBFUZZER_LOG_SEED_CORPUS_INFO_REGEX.match(line)
        if match:
            has_corpus = True

        match = LIBFUZZER_MODULES_LOADED_REGEX.match(line)
        if match:
            stats['startup_crash_count'] = 0
            stats['edges_total'] = int(match.group(2))

        if (LIBFUZZER_TIMEOUT_TESTCASE_REGEX.match(line)
                or stack_analyzer.LIBFUZZER_TIMEOUT_REGEX.match(line)):
            stats['timeout_count'] = 1
            continue

        if not stats['max_len']:
            # Get "max_len" value from the log, if it has not been found in arguments.
            match = LIBFUZZER_LOG_MAX_LEN_REGEX.match(line)
            if match:
                stats['max_len'] = int(match.group(1))
                continue

    if has_corpus and not stats['log_lines_from_engine']:
        stats['corpus_crash_count'] = 1

    return stats
Esempio n. 14
0
    try:
        # Bail out if cannot download recommended dictionary from GCS.
        if not dict_manager.download_recommended_dictionary_from_gcs(
                recommended_dictionary_path):
            return False
    except Exception, ex:
        logs.log_error('Exception downloading recommended dictionary:\n%s.' %
                       str(ex))
        return False

    # Bail out if the downloaded dictionary is empty.
    if not os.path.getsize(recommended_dictionary_path):
        return False

    # Check if there is an existing dictionary file in arguments.
    original_dictionary_path = fuzzer_utils.extract_argument(
        arguments, constants.DICT_FLAG)
    merged_dictionary_path = (
        original_dictionary_path
        or dictionary_manager.get_default_dictionary_path(fuzzer_path))
    merged_dictionary_path += MERGED_DICT_SUFFIX

    dictionary_manager.merge_dictionary_files(original_dictionary_path,
                                              recommended_dictionary_path,
                                              merged_dictionary_path)
    arguments.append(constants.DICT_FLAG + merged_dictionary_path)
    return True


def get_dictionary_analysis_timeout():
    """Get timeout for dictionary analysis."""
    return engine_common.get_overridable_timeout(
Esempio n. 15
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)
Esempio n. 16
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,
        )

        project_qualified_fuzzer_name = _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 = 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)

        # 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()

        # 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(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,
                ))

        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,
        )