Exemple #1
0
 def test_valid_filepath(self):
     """Tests that fuzz targets can be retrieved once the fuzzers are built."""
     fuzz_targets = utils.get_fuzz_targets(TEST_OUT_DIR)
     self.assertCountEqual(fuzz_targets,
                           [os.path.join(TEST_OUT_DIR, 'do_stuff_fuzzer')])
     fuzz_targets = utils.get_fuzz_targets(
         os.path.join(helper.OSSFUZZ_DIR, 'infra'))
     self.assertFalse(fuzz_targets)
 def test_valid_filepath(self):
   """Tests that fuzz targets can be retrieved once the fuzzers are built."""
   fuzz_targets = utils.get_fuzz_targets(TEST_OUT_DIR)
   crash_fuzzer_path = os.path.join(TEST_OUT_DIR, 'example_crash_fuzzer')
   nocrash_fuzzer_path = os.path.join(TEST_OUT_DIR, 'example_nocrash_fuzzer')
   self.assertCountEqual(fuzz_targets,
                         [crash_fuzzer_path, nocrash_fuzzer_path])
   fuzz_targets = utils.get_fuzz_targets(
       os.path.join(helper.OSSFUZZ_DIR, 'infra'))
   self.assertFalse(fuzz_targets)
Exemple #3
0
  def test_valid_filepath(self):
    """Tests that fuzz targets can be retrieved once the fuzzers are built."""
    fuzz_targets = utils.get_fuzz_targets(TEST_OUT_DIR)
    crash_fuzzer_path = os.path.join(TEST_OUT_DIR, 'example_crash_fuzzer')
    nocrash_fuzzer_path = os.path.join(TEST_OUT_DIR, 'example_nocrash_fuzzer')
    self.assertCountEqual(fuzz_targets,
                          [crash_fuzzer_path, nocrash_fuzzer_path])

    # Testing on a arbitrary directory with no fuzz targets in it.
    fuzz_targets = utils.get_fuzz_targets(
        os.path.join(helper.OSS_FUZZ_DIR, 'infra', 'travis'))
    self.assertFalse(fuzz_targets)
Exemple #4
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
Exemple #5
0
 def get_fuzz_targets(self):
   """Returns fuzz targets in out directory."""
   # We only want fuzz targets from the root because during the coverage build,
   # a lot of the image's filesystem is copied into /out for the purpose of
   # generating coverage reports.
   # TOOD(metzman): Figure out if top_level_only should be the only behavior
   # for this function.
   return utils.get_fuzz_targets(self.workspace.out, top_level_only=True)
Exemple #6
0
def remove_unaffected_fuzzers(project_name, out_dir, files_changed,
                              oss_fuzz_repo_path):
    """Removes all non affected fuzzers in the out directory.

  Args:
    project_name: The name of the relevant OSS-Fuzz project.
    out_dir: The location of the fuzzer binaries.
    files_changed: A list of files changed compared to HEAD.
    oss_fuzz_repo_path: The location of the OSS-Fuzz repo in the docker image.
  """
    if not files_changed:
        logging.info('No files changed compared to HEAD.')
        return
    fuzzer_paths = utils.get_fuzz_targets(out_dir)
    if not fuzzer_paths:
        logging.error('No fuzzers found in out dir.')
        return

    latest_cov_report_info = get_latest_cov_report_info(project_name)
    if not latest_cov_report_info:
        logging.error('Could not download latest coverage report.')
        return
    affected_fuzzers = []
    logging.info('Files changed in PR:\n%s', '\n'.join(files_changed))
    for fuzzer in fuzzer_paths:
        fuzzer_name = os.path.basename(fuzzer)
        covered_files = get_files_covered_by_target(latest_cov_report_info,
                                                    fuzzer_name,
                                                    oss_fuzz_repo_path)
        if not covered_files:
            # Assume a fuzzer is affected if we can't get its coverage from OSS-Fuzz.
            affected_fuzzers.append(fuzzer_name)
            continue
        logging.info('Fuzzer %s has affected files:\n%s', fuzzer_name,
                     '\n'.join(covered_files))
        for file in files_changed:
            if file in covered_files:
                affected_fuzzers.append(fuzzer_name)

    if not affected_fuzzers:
        logging.info('No affected fuzzers detected, keeping all as fallback.')
        return
    logging.info(
        'Using affected fuzzers.\n %s fuzzers affected by pull request',
        ' '.join(affected_fuzzers))

    all_fuzzer_names = map(os.path.basename, fuzzer_paths)

    # Remove all the fuzzers that are not affected.
    for fuzzer in all_fuzzer_names:
        if fuzzer not in affected_fuzzers:
            try:
                os.remove(os.path.join(out_dir, fuzzer))
            except OSError as error:
                logging.error('%s occured while removing file %s', error,
                              fuzzer)
Exemple #7
0
def run_fuzzers(fuzz_seconds, workspace, project_name):
    """Runs all 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.

  Returns:
    (True if run was successful, True if bug was found).
  """
    # Validate inputs.
    if not os.path.exists(workspace):
        logging.error('Invalid workspace: %s.', workspace)
        return False, False
    out_dir = os.path.join(workspace, 'out')
    artifacts_dir = os.path.join(out_dir, 'artifacts')
    os.makedirs(artifacts_dir, exist_ok=True)
    if not fuzz_seconds or fuzz_seconds < 1:
        logging.error(
            'Fuzz_seconds argument must be greater than 1, but was: %s.',
            format(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.',
                      format(out_dir))
        return False, False

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

        target = fuzz_target.FuzzTarget(fuzzer_path, run_seconds, out_dir,
                                        project_name)
        start_time = time.time()
        test_case, stack_trace = target.fuzz()
        fuzz_seconds -= (time.time() - start_time)
        if not test_case or not stack_trace:
            logging.info('Fuzzer %s, finished running.', target.target_name)
        else:
            logging.info('Fuzzer %s, detected error: %s.', target.target_name,
                         stack_trace)
            shutil.move(test_case, os.path.join(artifacts_dir, 'test_case'))
            parse_fuzzer_output(stack_trace, artifacts_dir)
            return True, True
        fuzzers_left_to_run -= 1

    return True, False
def remove_unaffected_fuzz_targets(project_name, out_dir, files_changed,
                                   repo_path):
    """Removes all non affected fuzz targets in the out directory.

  Args:
    project_name: The name of the relevant OSS-Fuzz project.
    out_dir: The location of the fuzz target binaries.
    files_changed: A list of files changed compared to HEAD.
    repo_path: The location of the OSS-Fuzz repo in the docker image.

  This function will not delete fuzz targets unless it knows that the fuzz
  targets are unaffected. For example, this means that fuzz targets which don't
  have coverage data on will not be deleted.
  """
    # TODO(metzman): Make this use clusterfuzz deployment.
    if not files_changed:
        # Don't remove any fuzz targets if there is no difference from HEAD.
        logging.info('No files changed compared to HEAD.')
        return

    logging.info('Files changed in PR: %s', files_changed)

    fuzz_target_paths = utils.get_fuzz_targets(out_dir)
    if not fuzz_target_paths:
        # Nothing to remove.
        logging.error('No fuzz targets found in out dir.')
        return

    coverage_getter = get_coverage.OssFuzzCoverageGetter(
        project_name, repo_path)
    if not coverage_getter.fuzzer_stats_url:
        # Don't remove any fuzz targets unless we have data.
        logging.error('Could not find latest coverage report.')
        return

    affected_fuzz_targets = get_affected_fuzz_targets(coverage_getter,
                                                      fuzz_target_paths,
                                                      files_changed)

    if not affected_fuzz_targets:
        logging.info(
            'No affected fuzz targets detected, keeping all as fallback.')
        return

    logging.info('Using affected fuzz targets: %s.', affected_fuzz_targets)
    unaffected_fuzz_targets = set(fuzz_target_paths) - affected_fuzz_targets
    logging.info('Removing unaffected fuzz targets: %s.',
                 unaffected_fuzz_targets)

    # Remove all the targets that are not affected.
    for fuzz_target_path in unaffected_fuzz_targets:
        try:
            os.remove(fuzz_target_path)
        except OSError as error:
            logging.error('%s occurred while removing file %s', error,
                          fuzz_target_path)
Exemple #9
0
 def test_valid_filepath(self):
     """Tests that fuzz targets can be retrieved once the fuzzers are built."""
     utils.chdir_to_root()
     helper.build_fuzzers_impl(EXAMPLE_PROJECT,
                               True,
                               'libfuzzer',
                               'address',
                               'x86_64', [],
                               None,
                               no_cache=False,
                               mount_location=None)
     fuzz_targets = utils.get_fuzz_targets(
         os.path.join(helper.OSSFUZZ_DIR, 'build', 'out', EXAMPLE_PROJECT))
     self.assertCountEqual(fuzz_targets, [
         os.path.join(helper.OSSFUZZ_DIR, 'build', 'out', EXAMPLE_PROJECT,
                      'do_stuff_fuzzer')
     ])
     fuzz_targets = utils.get_fuzz_targets(
         os.path.join(helper.OSSFUZZ_DIR, 'infra'))
     self.assertFalse(fuzz_targets)
Exemple #10
0
 def test_invalid_filepath(self):
     """Tests what get_fuzz_targets return when invalid filepath is used."""
     utils.chdir_to_root()
     helper.build_fuzzers_impl(EXAMPLE_PROJECT,
                               True,
                               'libfuzzer',
                               'address',
                               'x86_64', [],
                               None,
                               no_cache=False,
                               mount_location=None)
     fuzz_targets = utils.get_fuzz_targets('not/a/valid/file/path')
     self.assertFalse(fuzz_targets)
Exemple #11
0
def remove_unaffected_fuzzers(project_name, out_dir, files_changed,
                              oss_fuzz_repo_path):
    """Removes all non affected fuzzers in the out directory.

  Args:
    project_name: The name of the relevant OSS-Fuzz project.
    out_dir: The location of the fuzzer binaries.
    files_changed: A list of files changed compared to HEAD.
    oss_fuzz_repo_path: The location of the OSS-Fuzz repo in the docker image.

  This function will not delete fuzzers unless it knows that the fuzzer is
  unaffected. For example, this means that fuzzers which don't have coverage
  data on will not be deleted.
  """
    if not files_changed:
        # Don't remove any fuzzers if there is no difference from HEAD.
        logging.info('No files changed compared to HEAD.')
        return

    logging.info('Files changed in PR: %s', files_changed)

    fuzzer_paths = utils.get_fuzz_targets(out_dir)
    if not fuzzer_paths:
        # Nothing to remove.
        logging.error('No fuzzers found in out dir.')
        return

    cov_report_info = get_latest_cov_report_info(project_name)
    if not cov_report_info:
        # Don't remove any fuzzers unless we have data.
        logging.error('Could not download latest coverage report.')
        return

    affected_fuzzers = get_affected_fuzzers(fuzzer_paths, files_changed,
                                            cov_report_info,
                                            oss_fuzz_repo_path)

    if not affected_fuzzers:
        logging.info('No affected fuzzers detected, keeping all as fallback.')
        return

    logging.info('Using affected fuzzers: %s.', affected_fuzzers)
    unaffected_fuzzers = set(fuzzer_paths) - affected_fuzzers
    logging.info('Removing unaffected fuzzers: %s.', unaffected_fuzzers)
    # Remove all the fuzzers that are not affected.
    for fuzzer_path in unaffected_fuzzers:
        try:
            os.remove(fuzzer_path)
        except OSError as error:
            logging.error('%s occurred while removing file %s', error,
                          fuzzer_path)
Exemple #12
0
def run_fuzzers(project_name, fuzz_seconds, workspace):
    """Runs all fuzzers for a specific OSS-Fuzz project.

  Args:
    project_name: The name of the OSS-Fuzz project being built.
    fuzz_seconds: The total time allotted for fuzzing.
    workspace: The location in a shared volume to store a git repo and build
      artifacts.

  Returns:
    (True if run was successful, True if bug was found).
  """
    # Validate inputs.
    if not os.path.exists(workspace):
        logging.error('Invalid workspace: %s.', workspace)
        return False, False
    out_dir = os.path.join(workspace, 'out')
    if not fuzz_seconds or fuzz_seconds < 1:
        logging.error(
            'Fuzz_seconds argument must be greater than 1, but was: %s.',
            format(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.',
                      format(out_dir))
        return False, False
    fuzz_seconds_per_target = fuzz_seconds // len(fuzzer_paths)

    # Run fuzzers for alotted time.
    for fuzzer_path in fuzzer_paths:
        target = fuzz_target.FuzzTarget(project_name, fuzzer_path,
                                        fuzz_seconds_per_target, out_dir)
        test_case, stack_trace = target.fuzz()
        if not test_case or not stack_trace:
            logging.info('Fuzzer %s, finished running.', target.target_name)
        else:
            logging.info('Fuzzer %s, detected error: %s.', target.target_name,
                         stack_trace)
            shutil.move(test_case, os.path.join(out_dir, 'testcase'))
            return True, True
    return True, False
Exemple #13
0
    def initialize(self):
        """Initialization method. Must be called before calling run_fuzz_targets.
    Returns True on success."""
        # Use a seperate initialization function so we can return False on failure
        # instead of exceptioning like we need to do if this were done in the
        # __init__ method.

        logging.info('Using %s sanitizer.', self.config.sanitizer)

        # TODO(metzman) Add a check to ensure we aren't over time limit.
        if not self.config.fuzz_seconds or self.config.fuzz_seconds < 1:
            logging.error(
                'Fuzz_seconds argument must be greater than 1, but was: %s.',
                self.config.fuzz_seconds)
            return False

        self.out_dir = os.path.join(self.config.workspace, 'out')
        if not os.path.exists(self.out_dir):
            logging.error('Out directory: %s does not exist.', self.out_dir)
            return False

        self.artifacts_dir = os.path.join(self.out_dir, 'artifacts')
        if not os.path.exists(self.artifacts_dir):
            os.mkdir(self.artifacts_dir)
        elif (not os.path.isdir(self.artifacts_dir)
              or os.listdir(self.artifacts_dir)):
            logging.error(
                'Artifacts path: %s exists and is not an empty directory.',
                self.artifacts_dir)
            return False

        self.fuzz_target_paths = utils.get_fuzz_targets(self.out_dir)
        logging.info('Fuzz targets: %s', self.fuzz_target_paths)
        if not self.fuzz_target_paths:
            logging.error('No fuzz targets were found in out directory: %s.',
                          self.out_dir)
            return False

        return True
Exemple #14
0
 def test_invalid_filepath(self):
   """Tests what get_fuzz_targets return when invalid filepath is used."""
   fuzz_targets = utils.get_fuzz_targets('not/a/valid/file/path')
   self.assertFalse(fuzz_targets)
Exemple #15
0
 def get_fuzz_targets(self):
   """Returns fuzz targets in out directory."""
   return utils.get_fuzz_targets(self.workspace.out)