Пример #1
0
    def cleanse(self, target_path, arguments, input_path, output_path,
                max_time):
        """Optional (but recommended): Cleanse a testcase.

    Args:
      target_path: Path to the target.
      arguments: Additional arguments needed for testcase cleanse.
      input_path: Path to the reproducer input.
      output_path: Path to the cleansed output.
      max_time: Maximum allowed time for the cleanse.

    Returns:
      A ReproduceResult.

    Raises:
      TimeoutError: If the cleanse exceeds max_time.
    """
        runner = libfuzzer.get_runner(target_path)
        libfuzzer.set_sanitizer_options(target_path)

        cleanse_tmp_dir = self._create_temp_corpus_dir('cleanse-workdir')
        result = runner.cleanse_crash(input_path,
                                      output_path,
                                      max_time,
                                      artifact_prefix=cleanse_tmp_dir,
                                      additional_args=arguments)

        if result.timed_out:
            raise engine.TimeoutError('Cleanse timed out\n' + result.output)

        return engine.ReproduceResult(result.command, result.return_code,
                                      result.time_executed, result.output)
Пример #2
0
  def reproduce(self, target_path, input_path, arguments, max_time):
    """Reproduce a crash given an input.

    Args:
      target_path: Path to the target.
      input_path: Path to the reproducer input.
      arguments: Additional arguments needed for reproduction.
      max_time: Maximum allowed time for the reproduction.

    Returns:
      A ReproduceResult.

    Raises:
      TimeoutError: If the reproduction exceeds max_time.
    """
    runner = libfuzzer.get_runner(target_path)
    libfuzzer.set_sanitizer_options(target_path)

    # Remove fuzzing specific arguments. This is only really needed for legacy
    # testcases, and can be removed in the distant future.
    arguments = arguments[:]
    libfuzzer.remove_fuzzing_arguments(arguments)

    runs_argument = constants.RUNS_FLAG + str(constants.RUNS_TO_REPRODUCE)
    arguments.append(runs_argument)

    result = runner.run_single_testcase(
        input_path, timeout=max_time, additional_args=arguments)

    if result.timed_out:
      raise engine.TimeoutError('Reproducing timed out\n' + result.output)

    return engine.ReproduceResult(result.command, result.return_code,
                                  result.time_executed,
                                  utils.decode_to_unicode(result.output))
Пример #3
0
    def wrapped(*args, **kwargs):
        """Wrapper for adding retry logic."""
        for retry_attempt in range(num_retries + 1):
            # Wait for channel to (re)connect if necessary.
            state = _check_channel_state(config.RECONNECT_TIMEOUT_SECONDS)

            if state == ChannelState.INCONSISTENT:
                # No point retrying if the worker is inconsistent.
                monitoring_metrics.HOST_INCONSISTENT_COUNT.increment()
                logs.log_warn('Worker got into an inconsistent state.')
                host_exit_no_return(return_code=0)

            if state == ChannelState.NOT_READY:
                # Channel still isn't ready.
                logs.log_warn(
                    'Channel failed to become ready within reconnect timeout.')
                if retry_attempt == num_retries:
                    # Last attempt.
                    host_exit_no_return()

                continue

            try:
                return func(*args, **kwargs)
            except grpc.RpcError as e:
                # For timeouts, which aren't fatal errors, resurface the right
                # exception.
                # TODO(mbarbella): Ignoring errors on the next line fixes an issue while
                # trying to support this code in both Python 2 and 3, but may not be
                # necessary in Python 3 since presumably the exception class will
                # allow us to properly convert it to a string. Delete after migrating.
                exception_message = e.message.decode('utf-8', errors='ignore')
                if 'TimeoutError' in exception_message:
                    # TODO(ochang): Replace with generic TimeoutError in Python 3.
                    raise engine.TimeoutError(e.message)

                if num_retries == 0:
                    # Just re-raise the original exception if this RPC is not configured
                    # for retries.
                    raise

                logs.log_warn('Failed RPC: ' + exception_message)
                if retry_attempt == num_retries:
                    # Last attempt.
                    host_exit_no_return()

                time.sleep(RPC_FAIL_WAIT_TIME)
Пример #4
0
    def minimize_corpus(self, target_path, arguments, input_dirs, output_dir,
                        reproducers_dir, max_time):
        """Optional (but recommended): run corpus minimization.

        Args:
          target_path: Path to the target.
          arguments: Additional arguments needed for corpus minimization.
          input_dirs: Input corpora.
          output_dir: Output directory to place minimized corpus.
          reproducers_dir: The directory to put reproducers in when crashes are
              found.
          max_time: Maximum allowed time for the minimization.

        Returns:
          A Result object.

        Raises:
          TimeoutError: If the corpus minimization exceeds max_time.
          Error: If the merge failed in some other way.
        """
        runner = libfuzzer.get_runner(target_path)
        libfuzzer.set_sanitizer_options(target_path)
        merge_tmp_dir = self._create_temp_corpus_dir("merge-workdir")

        result = runner.merge(
            [output_dir] + input_dirs,
            merge_timeout=max_time,
            tmp_dir=merge_tmp_dir,
            additional_args=arguments,
            artifact_prefix=reproducers_dir,
            merge_control_file=getattr(self, "_merge_control_file", None),
        )

        if result.timed_out:
            raise engine.TimeoutError("Merging new testcases timed out\n" +
                                      result.output)

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

        merge_stats = stats.parse_stats_from_merge_log(
            result.output.splitlines())

        # TODO(ochang): Get crashes found during merge.
        return engine.FuzzResult(result.output, result.command, [],
                                 merge_stats, result.time_executed)
Пример #5
0
    def wrapped(*args, **kwargs):
        """Wrapper for adding retry logic."""
        for retry_attempt in range(num_retries + 1):
            # Wait for channel to (re)connect if necessary.
            state = _check_channel_state(config.RECONNECT_TIMEOUT_SECONDS)

            if state == ChannelState.INCONSISTENT:
                # No point retrying if the worker is inconsistent.
                monitoring_metrics.HOST_INCONSISTENT_COUNT.increment()
                logs.log_warn('Worker got into an inconsistent state.')
                host_exit_no_return(return_code=0)

            if state == ChannelState.NOT_READY:
                # Channel still isn't ready.
                logs.log_warn(
                    'Channel failed to become ready within reconnect timeout.')
                if retry_attempt == num_retries:
                    # Last attempt.
                    host_exit_no_return()

                continue

            try:
                return func(*args, **kwargs)
            except grpc.RpcError as e:
                # For timeouts, which aren't fatal errors, resurface the right
                # exception.
                if 'TimeoutError' in repr(e):
                    # TODO(ochang): Replace with generic TimeoutError in Python 3.
                    # Use __str__ to avoid newstrs. TODO(ochang): Use plain str() once
                    # migrated to Python 3.
                    raise engine.TimeoutError(e.__str__())

                if num_retries == 0:
                    # Just re-raise the original exception if this RPC is not configured
                    # for retries.
                    raise

                logs.log_warn('Failed RPC: ' + repr(e))
                if retry_attempt == num_retries:
                    # Last attempt.
                    host_exit_no_return()

                time.sleep(RPC_FAIL_WAIT_TIME)
Пример #6
0
    def _minimize_corpus_two_step(self, target_path, arguments,
                                  existing_corpus_dirs, new_corpus_dir,
                                  output_corpus_dir, reproducers_dir,
                                  max_time):
        """Optional (but recommended): run corpus minimization.

    Args:
      target_path: Path to the target.
      arguments: Additional arguments needed for corpus minimization.
      existing_corpus_dirs: Input corpora that existed before the fuzzing run.
      new_corpus_dir: Input corpus that was generated during the fuzzing run.
          Must have at least one new file.
      output_corpus_dir: Output directory to place minimized corpus.
      reproducers_dir: The directory to put reproducers in when crashes are
          found.
      max_time: Maximum allowed time for the minimization.

    Returns:
      A Result object.
    """
        if not _is_multistep_merge_supported(target_path):
            # Fallback to the old single step merge. It does not support incremental
            # stats and provides only `edge_coverage` and `feature_coverage` stats.
            logs.log(
                'Old version of libFuzzer is used. Using single step merge.')
            return self.minimize_corpus(
                target_path, arguments,
                existing_corpus_dirs + [new_corpus_dir], output_corpus_dir,
                reproducers_dir, max_time)

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

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

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

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

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

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

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

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

        self._merge_control_file = None

        # TODO(ochang): Get crashes found during merge.
        return engine.FuzzResult(
            output, result_2.command, [], merge_stats,
            result_1.time_executed + result_2.time_executed)