Example #1
0
def run_fuzzers(config):  # pylint: disable=too-many-locals
    """Runs fuzzers for a specific OSS-Fuzz project.

  Args:
    fuzz_seconds: The total time allotted for fuzzing.
    workspace: The location in a shared volume to store a git repo and build
      artifacts.
    project_name: The name of the relevant OSS-Fuzz project.
    sanitizer: The sanitizer the fuzzers should be run with.

  Returns:
    (True if run was successful, True if bug was found).
  """
    # Validate inputs.
    logging.info('Using %s sanitizer.', config.sanitizer)

    out_dir = os.path.join(config.workspace, 'out')
    artifacts_dir = os.path.join(out_dir, 'artifacts')
    os.makedirs(artifacts_dir, exist_ok=True)

    if not config.fuzz_seconds or config.fuzz_seconds < 1:
        logging.error(
            'Fuzz_seconds argument must be greater than 1, but was: %s.',
            config.fuzz_seconds)
        return False, False

    # Get fuzzer information.
    fuzzer_paths = utils.get_fuzz_targets(out_dir)
    if not fuzzer_paths:
        logging.error('No fuzzers were found in out directory: %s.', out_dir)
        return False, False

    # Run fuzzers for allotted time.
    total_num_fuzzers = len(fuzzer_paths)
    fuzzers_left_to_run = total_num_fuzzers
    min_seconds_per_fuzzer = config.fuzz_seconds // total_num_fuzzers
    for fuzzer_path in fuzzer_paths:
        run_seconds = max(config.fuzz_seconds // fuzzers_left_to_run,
                          min_seconds_per_fuzzer)

        target = fuzz_target.FuzzTarget(fuzzer_path,
                                        run_seconds,
                                        out_dir,
                                        config.project_name,
                                        sanitizer=config.sanitizer)
        start_time = time.time()
        testcase, stacktrace = target.fuzz()
        config.fuzz_seconds -= (time.time() - start_time)
        if not testcase or not stacktrace:
            logging.info('Fuzzer %s, finished running.', target.target_name)
        else:
            utils.binary_print(b'Fuzzer %s, detected error:\n%s' %
                               (target.target_name.encode(), stacktrace))
            shutil.move(testcase, os.path.join(artifacts_dir, 'test_case'))
            stack_parser.parse_fuzzer_output(stacktrace, artifacts_dir)
            return True, True
        fuzzers_left_to_run -= 1

    return True, False
Example #2
0
  def fuzz(self, use_corpus=True, extra_libfuzzer_options=None):
    """Starts the fuzz target run for the length of time specified by duration.

    Returns:
      FuzzResult namedtuple with stacktrace and testcase if applicable.
    """
    logging.info('Running fuzzer: %s.', self.target_name)
    if extra_libfuzzer_options is None:
      extra_libfuzzer_options = []
    env = base_runner_utils.get_env(self.config, self.workspace)
    # TODO(metzman): Is this needed?
    env['RUN_FUZZER_MODE'] = 'interactive'

    if use_corpus:
      # If corpus can be downloaded, use it for fuzzing.
      self._download_corpus()
      env['CORPUS_DIR'] = self.latest_corpus_path

    options = LIBFUZZER_OPTIONS.copy() + [
        f'-max_total_time={self.duration}',
        # Make sure libFuzzer artifact files don't pollute $OUT.
        f'-artifact_prefix={self.workspace.artifacts}/'
    ] + extra_libfuzzer_options
    command = ['run_fuzzer', self.target_name] + options

    logging.info('Running command: %s', command)
    process = subprocess.Popen(command,
                               env=env,
                               stdout=subprocess.PIPE,
                               stderr=subprocess.PIPE)

    try:
      _, stderr = process.communicate(timeout=self.duration + BUFFER_TIME)
    except subprocess.TimeoutExpired:
      logging.error('Fuzzer %s timed out, ending fuzzing.', self.target_name)
      return FuzzResult(None, None, self.latest_corpus_path)

    # Libfuzzer timeout was reached.
    if not process.returncode:
      logging.info('Fuzzer %s finished with no crashes discovered.',
                   self.target_name)
      return FuzzResult(None, None, self.latest_corpus_path)

    # Crash was discovered.
    logging.info('Fuzzer %s, ended before timeout.', self.target_name)
    testcase = get_testcase(stderr)
    if not testcase:
      logging.error(b'No testcase found in stacktrace: %s.', stderr)
      return FuzzResult(None, None, self.latest_corpus_path)

    utils.binary_print(b'Fuzzer: %s. Detected bug:\n%s' %
                       (self.target_name.encode(), stderr))
    if self.is_crash_reportable(testcase):
      # We found a bug in the fuzz target and we will report it.
      return FuzzResult(testcase, stderr, self.latest_corpus_path)

    # We found a bug but we won't report it.
    return FuzzResult(None, None, self.latest_corpus_path)
Example #3
0
    def run_fuzz_targets(self):
        """Runs fuzz targets. Returns True if a bug was found."""
        fuzzers_left_to_run = len(self.fuzz_target_paths)

        # Make a copy since we will mutate it.
        fuzz_seconds = self.config.fuzz_seconds

        min_seconds_per_fuzzer = fuzz_seconds // fuzzers_left_to_run
        bug_found = False
        for target_path in self.fuzz_target_paths:
            # By doing this, we can ensure that every fuzz target runs for at least
            # min_seconds_per_fuzzer, but that other fuzzers will have longer to run
            # if one ends early.
            run_seconds = max(fuzz_seconds // fuzzers_left_to_run,
                              min_seconds_per_fuzzer)

            target = self.create_fuzz_target_obj(target_path, run_seconds)
            start_time = time.time()
            result = self.run_fuzz_target(target)

            # It's OK if this goes negative since we take max when determining
            # run_seconds.
            fuzz_seconds -= time.time() - start_time

            fuzzers_left_to_run -= 1
            if not result.testcase or not result.stacktrace:
                logging.info('Fuzzer %s finished running without crashes.',
                             target.target_name)
                continue

            # We found a bug in the fuzz target.
            utils.binary_print(
                b'Fuzzer: %s. Detected bug:\n%s' %
                (target.target_name.encode(), result.stacktrace))

            # TODO(metzman): Do this with filestore.
            testcase_artifact_path = self.get_fuzz_target_artifact(
                target, 'testcase')
            shutil.move(result.testcase, testcase_artifact_path)
            bug_summary_artifact_path = self.get_fuzz_target_artifact(
                target, 'bug-summary.txt')
            stack_parser.parse_fuzzer_output(result.stacktrace,
                                             bug_summary_artifact_path)

            bug_found = True
            if self.quit_on_bug_found:
                logging.info('Bug found. Stopping fuzzing.')
                return bug_found

        return bug_found
Example #4
0
    def fuzz(self):
        """Starts the fuzz target run for the length of time specified by duration.

    Returns:
      FuzzResult namedtuple with stacktrace and testcase if applicable.
    """
        logging.info('Fuzzer %s, started.', self.target_name)
        docker_container = utils.get_container_name()
        command = ['docker', 'run', '--rm', '--privileged']
        if docker_container:
            command += [
                '--volumes-from', docker_container, '-e', 'OUT=' + self.out_dir
            ]
        else:
            command += ['-v', '%s:%s' % (self.out_dir, '/out')]

        command += [
            '-e', 'FUZZING_ENGINE=libfuzzer', '-e',
            'SANITIZER=' + self.config.sanitizer, '-e', 'CIFUZZ=True', '-e',
            'RUN_FUZZER_MODE=interactive', docker.BASE_RUNNER_TAG, 'bash', '-c'
        ]

        run_fuzzer_command = 'run_fuzzer {fuzz_target} {options}'.format(
            fuzz_target=self.target_name,
            options=LIBFUZZER_OPTIONS + ' -max_total_time=' +
            str(self.duration))

        # If corpus can be downloaded use it for fuzzing.
        self.latest_corpus_path = self.clusterfuzz_deployment.download_corpus(
            self.target_name, self.out_dir)
        if self.latest_corpus_path:
            run_fuzzer_command = run_fuzzer_command + ' ' + self.latest_corpus_path
        command.append(run_fuzzer_command)

        logging.info('Running command: %s', ' '.join(command))
        process = subprocess.Popen(command,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE)

        try:
            _, stderr = process.communicate(timeout=self.duration +
                                            BUFFER_TIME)
        except subprocess.TimeoutExpired:
            logging.error('Fuzzer %s timed out, ending fuzzing.',
                          self.target_name)
            return FuzzResult(None, None)

        # Libfuzzer timeout was reached.
        if not process.returncode:
            logging.info('Fuzzer %s finished with no crashes discovered.',
                         self.target_name)
            return FuzzResult(None, None)

        # Crash was discovered.
        logging.info('Fuzzer %s, ended before timeout.', self.target_name)
        testcase = self.get_testcase(stderr)
        if not testcase:
            logging.error(b'No testcase found in stacktrace: %s.', stderr)
            return FuzzResult(None, None)

        utils.binary_print(b'Fuzzer: %s. Detected bug:\n%s' %
                           (self.target_name.encode(), stderr))
        if self.is_crash_reportable(testcase):
            # We found a bug in the fuzz target and we will report it.
            return FuzzResult(testcase, stderr)

        # We found a bug but we won't report it.
        return FuzzResult(None, None)
Example #5
0
  def fuzz(self):
    """Starts the fuzz target run for the length of time specified by duration.

    Returns:
      FuzzResult namedtuple with stacktrace and testcase if applicable.
    """
    logging.info('Running fuzzer: %s.', self.target_name)
    command, _ = docker.get_base_docker_run_command(self.workspace,
                                                    self.config.sanitizer,
                                                    self.config.language)

    # If corpus can be downloaded use it for fuzzing.
    self.latest_corpus_path = self.clusterfuzz_deployment.download_corpus(
        self.target_name)
    command += ['-e', 'CORPUS_DIR=' + self.latest_corpus_path]

    command += [
        '-e', 'RUN_FUZZER_MODE=interactive', docker.BASE_RUNNER_TAG, 'bash',
        '-c'
    ]

    options = LIBFUZZER_OPTIONS.copy() + [
        f'-max_total_time={self.duration}',
        # Make sure libFuzzer artifact files don't pollute $OUT.
        f'-artifact_prefix={self.workspace.artifacts}/'
    ]
    options = ' '.join(options)
    run_fuzzer_command = f'run_fuzzer {self.target_name} {options}'
    command.append(run_fuzzer_command)

    logging.info('Running command: %s', ' '.join(command))
    process = subprocess.Popen(command,
                               stdout=subprocess.PIPE,
                               stderr=subprocess.PIPE)

    try:
      _, stderr = process.communicate(timeout=self.duration + BUFFER_TIME)
    except subprocess.TimeoutExpired:
      logging.error('Fuzzer %s timed out, ending fuzzing.', self.target_name)
      return FuzzResult(None, None, self.latest_corpus_path)

    # Libfuzzer timeout was reached.
    if not process.returncode:
      logging.info('Fuzzer %s finished with no crashes discovered.',
                   self.target_name)
      return FuzzResult(None, None, self.latest_corpus_path)

    # Crash was discovered.
    logging.info('Fuzzer %s, ended before timeout.', self.target_name)
    testcase = get_testcase(stderr)
    if not testcase:
      logging.error(b'No testcase found in stacktrace: %s.', stderr)
      return FuzzResult(None, None, self.latest_corpus_path)

    utils.binary_print(b'Fuzzer: %s. Detected bug:\n%s' %
                       (self.target_name.encode(), stderr))
    if self.is_crash_reportable(testcase):
      # We found a bug in the fuzz target and we will report it.
      return FuzzResult(testcase, stderr, self.latest_corpus_path)

    # We found a bug but we won't report it.
    return FuzzResult(None, None, self.latest_corpus_path)
Example #6
0
    def fuzz(self):
        """Starts the fuzz target run for the length of time specified by duration.

    Returns:
      FuzzResult namedtuple with stacktrace and testcase if applicable.
    """
        logging.info('Fuzzer %s, started.', self.target_name)
        docker_container = utils.get_container_name()
        command_arguments = []
        if docker_container:
            command_arguments += [
                '--volumes-from', docker_container, '-e', 'OUT=' + self.out_dir
            ]
        else:
            command_arguments += ['-v', '%s:%s' % (self.out_dir, '/out')]

        command_arguments += [
            '-e', 'FUZZING_ENGINE=libfuzzer', '-e',
            'SANITIZER=' + self.config.sanitizer, '-e', 'CIFUZZ=True', '-e',
            'RUN_FUZZER_MODE=interactive', docker.BASE_RUNNER_TAG, 'bash', '-c'
        ]

        run_fuzzer_command = 'run_fuzzer {fuzz_target} {options}'.format(
            fuzz_target=self.target_name,
            options=LIBFUZZER_OPTIONS + ' -max_total_time=' +
            str(self.duration))

        # If corpus can be downloaded use it for fuzzing.
        self.latest_corpus_path = self.clusterfuzz_deployment.download_corpus(
            self.target_name, self.out_dir)
        if self.latest_corpus_path:
            run_fuzzer_command = run_fuzzer_command + ' ' + self.latest_corpus_path
        command_arguments.append(run_fuzzer_command)
        result = docker.run_container_command(command_arguments,
                                              timeout=self.duration +
                                              BUFFER_TIME)

        if result.timed_out:
            logging.info('Stopped docker container before timeout.')

        if not result.retcode:
            logging.info('Fuzzer %s finished with no crashes discovered.',
                         self.target_name)
            return FuzzResult(None, None)

        if result.stderr is None:
            return FuzzResult(None, None)

        # Crash was discovered.
        logging.info('Fuzzer %s, ended before timeout.', self.target_name)

        # TODO(metzman): Replace this with artifact_prefix so we don't have to
        # parse.
        testcase = self.get_testcase(result.stderr)

        if not testcase:
            logging.error(b'No testcase found in stacktrace: %s.',
                          result.stderr)
            return FuzzResult(None, None)

        utils.binary_print(b'Fuzzer: %s. Detected bug:\n%s' %
                           (self.target_name.encode(), result.stderr))
        if self.is_crash_reportable(testcase):
            # We found a bug in the fuzz target and we will report it.
            return FuzzResult(testcase, result.stderr)

        # We found a bug but we won't report it.
        return FuzzResult(None, None)
Example #7
0
 def test_binary_string(self):  # pylint: disable=no-self-use
     """Tests that utils.binary_print can print a bianry string."""
     # Should execute without raising any exceptions.
     with mock.patch('sys.stdout.buffer.write') as mock_write:
         utils.binary_print(b'hello')
         mock_write.assert_called_with(b'hello\n')