示例#1
0
    def test_detect_main_repo_from_name(self):
        """Test the detect main repo function from build specific commit module."""
        repo_origin, repo_name = build_specified_commit.detect_main_repo(
            'curl', repo_name='curl')
        self.assertEqual(repo_origin, 'https://github.com/curl/curl.git')
        self.assertEqual(repo_name, 'curl')

        repo_origin, repo_name = build_specified_commit.detect_main_repo(
            'yara', repo_name='yara')
        self.assertEqual(repo_origin, 'https://github.com/VirusTotal/yara.git')
        self.assertEqual(repo_name, 'yara')

        repo_origin, repo_name = build_specified_commit.detect_main_repo(
            'usrsctp', repo_name='usrsctp')
        self.assertEqual(repo_origin, 'https://github.com/weinrank/usrsctp')
        self.assertEqual(repo_name, 'usrsctp')

        repo_origin, repo_name = build_specified_commit.detect_main_repo(
            'ndpi', repo_name='nDPI')
        self.assertEqual(repo_origin, 'https://github.com/ntop/nDPI.git')
        self.assertEqual(repo_name, 'ndpi')

        repo_origin, repo_name = build_specified_commit.detect_main_repo(
            'notproj', repo_name='aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
        self.assertIsNone(repo_origin)
        self.assertIsNone(repo_name)
    def test_detect_main_repo_from_commit(self):
        """Test the detect main repo function from build specific commit module."""
        for example_repo in test_repos.TEST_REPOS:
            repo_origin, repo_name = build_specified_commit.detect_main_repo(
                example_repo.project_name, commit=example_repo.new_commit)
            self.assertEqual(repo_origin, example_repo.git_url)
            self.assertEqual(repo_name,
                             os.path.join('/src', example_repo.oss_repo_name))

        repo_origin, repo_name = build_specified_commit.detect_main_repo(
            test_repos.INVALID_REPO.project_name,
            test_repos.INVALID_REPO.new_commit)
        self.assertIsNone(repo_origin)
        self.assertIsNone(repo_name)
示例#3
0
    def build_image_and_checkout_src(self):
        """Builds the project builder image for a non-OSS-Fuzz project. Sets the
    repo manager and host_repo_path. Checks out source code of project with
    patch under test. Returns True on success."""
        logging.info('Building OSS-Fuzz project on Github Actions.')
        # detect_main_repo builds the image as a side effect.
        inferred_url, self.image_repo_path = (
            build_specified_commit.detect_main_repo(
                self.project_name, repo_name=self.project_repo_name))

        if not inferred_url or not self.image_repo_path:
            logging.error('Could not detect repo from project %s.',
                          self.project_name)
            return False

        git_workspace = os.path.join(self.workspace, 'storage')
        os.makedirs(git_workspace, exist_ok=True)

        # Use the same name used in the docker image so we can overwrite it.
        image_repo_name = os.path.basename(self.image_repo_path)

        # Checkout project's repo in the shared volume.
        self.repo_manager = repo_manager.clone_repo_and_get_manager(
            inferred_url, git_workspace, repo_name=image_repo_name)

        self.host_repo_path = self.repo_manager.repo_dir

        checkout_specified_commit(self.repo_manager, self.pr_ref,
                                  self.commit_sha)
        return True
    def prepare_for_fuzzer_build(self):
        """Builds the fuzzer builder image, checks out the pull request/commit and
    returns the BuildPreparationResult."""
        logging.info('Building OSS-Fuzz project on Github Actions.')
        assert self.config.pr_ref or self.config.commit_sha
        # detect_main_repo builds the image as a side effect.
        inferred_url, image_repo_path = (
            build_specified_commit.detect_main_repo(
                self.config.oss_fuzz_project_name,
                repo_name=self.config.project_repo_name))

        if not inferred_url or not image_repo_path:
            logging.error('Could not detect repo.')
            return BuildPreparationResult(success=False,
                                          image_repo_path=None,
                                          repo_manager=None)

        os.makedirs(self.workspace.repo_storage, exist_ok=True)

        # Use the same name used in the docker image so we can overwrite it.
        image_repo_name = os.path.basename(image_repo_path)

        # Checkout project's repo in the shared volume.
        manager = repo_manager.clone_repo_and_get_manager(
            inferred_url,
            self.workspace.repo_storage,
            repo_name=image_repo_name)
        checkout_specified_commit(manager, self.config.pr_ref,
                                  self.config.commit_sha)

        return BuildPreparationResult(success=True,
                                      image_repo_path=image_repo_path,
                                      repo_manager=manager)
示例#5
0
def bisect(old_commit, new_commit, test_case_path, fuzz_target, build_data):
    """From a commit range, this function caluclates which introduced a
  specific error from a fuzz test_case_path.

  Args:
    old_commit: The oldest commit in the error regression range.
    new_commit: The newest commit in the error regression range.
    test_case_path: The file path of the test case that triggers the error
    fuzz_target: The name of the fuzzer to be tested.
    build_data: a class holding all of the input parameters for bisection.

  Returns:
    The commit SHA that introduced the error or None.

  Raises:
    ValueError: when a repo url can't be determine from the project.
  """
    with tempfile.TemporaryDirectory() as tmp_dir:
        repo_url, repo_name = build_specified_commit.detect_main_repo(
            build_data.project_name, commit=old_commit)
        if not repo_url or not repo_name:
            raise ValueError('Main git repo can not be determined.')
        bisect_repo_manager = repo_manager.RepoManager(repo_url,
                                                       tmp_dir,
                                                       repo_name=repo_name)
        commit_list = bisect_repo_manager.get_commit_list(
            old_commit, new_commit)
        old_idx = len(commit_list) - 1
        new_idx = 0
        build_specified_commit.build_fuzzers_from_commit(
            commit_list[new_idx], bisect_repo_manager, build_data)
        expected_error_code = helper.reproduce_impl(build_data.project_name,
                                                    fuzz_target, False, [], [],
                                                    test_case_path)

        # Check if the error is persistent through the commit range
        build_specified_commit.build_fuzzers_from_commit(
            commit_list[old_idx],
            bisect_repo_manager,
            build_data,
        )

        if expected_error_code == helper.reproduce_impl(
                build_data.project_name, fuzz_target, False, [], [],
                test_case_path):
            return commit_list[old_idx]

        while old_idx - new_idx > 1:
            curr_idx = (old_idx + new_idx) // 2
            build_specified_commit.build_fuzzers_from_commit(
                commit_list[curr_idx], bisect_repo_manager, build_data)
            error_code = helper.reproduce_impl(build_data.project_name,
                                               fuzz_target, False, [], [],
                                               test_case_path)
            if expected_error_code == error_code:
                new_idx = curr_idx
            else:
                old_idx = curr_idx
        return commit_list[new_idx]
示例#6
0
def _bisect(old_commit, new_commit, test_case_path, fuzz_target, build_data):  # pylint: disable=too-many-locals
  """Perform the bisect."""
  with tempfile.TemporaryDirectory() as tmp_dir:
    repo_url, repo_path = build_specified_commit.detect_main_repo(
        build_data.project_name, commit=new_commit)
    if not repo_url or not repo_path:
      raise ValueError('Main git repo can not be determined.')

    # Copy /src from the built Docker container to ensure all dependencies
    # exist. This will be mounted when running them.
    host_src_dir = build_specified_commit.copy_src_from_docker(
        build_data.project_name, tmp_dir)

    bisect_repo_manager = repo_manager.BaseRepoManager(
        os.path.join(host_src_dir, os.path.basename(repo_path)))
    commit_list = bisect_repo_manager.get_commit_list(new_commit, old_commit)

    old_idx = len(commit_list) - 1
    new_idx = 0
    logging.info('Testing against new_commit (%s)', commit_list[new_idx])
    if not build_specified_commit.build_fuzzers_from_commit(
        commit_list[new_idx], bisect_repo_manager, host_src_dir, build_data):
      raise RuntimeError('Failed to build new_commit')

    expected_error_code = helper.reproduce_impl(build_data.project_name,
                                                fuzz_target, False, [], [],
                                                test_case_path)

    # Check if the error is persistent through the commit range
    if old_commit:
      logging.info('Testing against old_commit (%s)', commit_list[old_idx])
      if not build_specified_commit.build_fuzzers_from_commit(
          commit_list[old_idx],
          bisect_repo_manager,
          host_src_dir,
          build_data,
      ):
        raise RuntimeError('Failed to build old_commit')

      if expected_error_code == helper.reproduce_impl(build_data.project_name,
                                                      fuzz_target, False, [],
                                                      [], test_case_path):
        return Result(repo_url, commit_list[old_idx])

    while old_idx - new_idx > 1:
      curr_idx = (old_idx + new_idx) // 2
      logging.info('Testing against %s (idx=%d)', commit_list[curr_idx],
                   curr_idx)
      build_specified_commit.build_fuzzers_from_commit(commit_list[curr_idx],
                                                       bisect_repo_manager,
                                                       host_src_dir, build_data)
      error_code = helper.reproduce_impl(build_data.project_name, fuzz_target,
                                         False, [], [], test_case_path)
      if expected_error_code == error_code:
        new_idx = curr_idx
      else:
        old_idx = curr_idx
    return Result(repo_url, commit_list[new_idx])
示例#7
0
 def _detect_main_repo(self):
     """Helper for child classes that detects the main repo and returns a tuple
 containing the inffered url and path to the repo in the image."""
     inferred_url, image_repo_path = build_specified_commit.detect_main_repo(
         self.config.oss_fuzz_project_name,
         repo_name=self.config.project_repo_name)
     if not inferred_url or not image_repo_path:
         logging.error('Could not detect repo.')
     return inferred_url, image_repo_path
示例#8
0
  def test_detect_main_repo_from_name(self):
    """Test the detect main repo function from build specific commit module."""
    for example_repo in test_repos.TEST_REPOS:
      if example_repo.project_name == 'gonids':
        # It's unclear how this test ever passed, but we can't infer the repo
        # because gonids doesn't really check it out, it uses "go get".
        continue
      repo_origin, repo_name = build_specified_commit.detect_main_repo(
          example_repo.project_name, repo_name=example_repo.git_repo_name)
      self.assertEqual(repo_origin, example_repo.git_url)
      self.assertEqual(
          repo_name,
          os.path.join(example_repo.image_location, example_repo.oss_repo_name))

    repo_origin, repo_name = build_specified_commit.detect_main_repo(
        test_repos.INVALID_REPO.project_name,
        test_repos.INVALID_REPO.oss_repo_name)
    self.assertIsNone(repo_origin)
    self.assertIsNone(repo_name)
示例#9
0
  def test_detect_main_repo_from_commit(self):
    """Test the detect main repo function from build specific commit module."""
    # TODO(metzman): Fix these tests so they don't randomly break because of
    # changes in the outside world.
    for example_repo in test_repos.TEST_REPOS:
      if example_repo.new_commit:
        # TODO(metzman): This function calls _build_image_with_retries which
        # has a long delay (30 seconds). Figure out how to make this quicker.
        repo_origin, repo_name = build_specified_commit.detect_main_repo(
            example_repo.project_name, commit=example_repo.new_commit)
        self.assertEqual(repo_origin, example_repo.git_url)
        self.assertEqual(repo_name,
                         os.path.join('/src', example_repo.oss_repo_name))

    repo_origin, repo_name = build_specified_commit.detect_main_repo(
        test_repos.INVALID_REPO.project_name,
        test_repos.INVALID_REPO.new_commit)
    self.assertIsNone(repo_origin)
    self.assertIsNone(repo_name)
示例#10
0
    def test_detect_main_repo_from_commit(self):
        """Test the detect main repo function from build specific commit module."""
        repo_origin, repo_name = build_specified_commit.detect_main_repo(
            'curl', commit='bc5d22c3dede2f04870c37aec9a50474c4b888ad')
        self.assertEqual(repo_origin, 'https://github.com/curl/curl.git')
        self.assertEqual(repo_name, 'curl')

        repo_origin, repo_name = build_specified_commit.detect_main_repo(
            'usrsctp', commit='4886aaa49fb90e479226fcfc3241d74208908232')
        self.assertEqual(repo_origin, 'https://github.com/weinrank/usrsctp')
        self.assertEqual(repo_name, 'usrsctp')

        repo_origin, repo_name = build_specified_commit.detect_main_repo(
            'ndpi', commit='c4d476cc583a2ef1e9814134efa4fbf484564ed7')
        self.assertEqual(repo_origin, 'https://github.com/ntop/nDPI.git')
        self.assertEqual(repo_name, 'ndpi')

        repo_origin, repo_name = build_specified_commit.detect_main_repo(
            'notproj', commit='aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
        self.assertIsNone(repo_origin)
        self.assertIsNone(repo_name)
示例#11
0
  def prepare_for_fuzzer_build(self):
    """Builds the project builder image for an OSS-Fuzz project outside of
    GitHub actions. Returns the repo_manager. Does not checkout source code
    since external projects are expected to bring their own source code to
    CIFuzz."""
    logging.info('Building OSS-Fuzz project.')
    # detect_main_repo builds the image as a side effect.
    _, image_repo_path = (build_specified_commit.detect_main_repo(
        self.config.project_name, repo_name=self.config.project_repo_name))

    if not image_repo_path:
      logging.error('Could not detect repo from project %s.',
                    self.config.project_name)
      return BuildPreparationResult(False, None, None)

    manager = repo_manager.RepoManager(self.config.project_src_path)
    return BuildPreparationResult(True, image_repo_path, manager)
示例#12
0
  def build_image_and_checkout_src(self):
    """Builds the project builder image for a non-OSS-Fuzz project. Sets the
    repo manager. Does not checkout source code since external projects are
    expected to bring their own source code to CIFuzz. Returns True on
    success."""
    logging.info('Building OSS-Fuzz project.')
    # detect_main_repo builds the image as a side effect.
    _, self.image_repo_path = (build_specified_commit.detect_main_repo(
        self.project_name, repo_name=self.project_repo_name))

    if not self.image_repo_path:
      logging.error('Could not detect repo from project %s.', self.project_name)
      return False

    # Checkout project's repo in the shared volume.
    self.repo_manager = repo_manager.RepoManager(self.host_repo_path)
    return True
示例#13
0
文件: cifuzz.py 项目: yang-g/oss-fuzz
def build_fuzzers(args):
    """Builds all of the fuzzers for a specific OSS-Fuzz project.

  Returns:
    True on success False on failure.
  """

    # TODO: Fix return value bubble to actually handle errors.
    with tempfile.TemporaryDirectory() as tmp_dir:
        inferred_url, repo_name = build_specified_commit.detect_main_repo(
            args.project_name, repo_name=args.repo_name)
        build_repo_manager = repo_manager.RepoManager(inferred_url,
                                                      tmp_dir,
                                                      repo_name=repo_name)
        build_data = build_specified_commit.BuildData()
        build_data.project_name = args.project_name
        build_data.sanitizer = 'address'
        build_data.engine = 'libfuzzer'
        build_data.architecture = 'x86_64'
        return build_specified_commit.build_fuzzers_from_commit(
            args.commit_sha, build_repo_manager, build_data) == 0
示例#14
0
def build_fuzzers(project_name,
                  project_repo_name,
                  workspace,
                  pr_ref=None,
                  commit_sha=None,
                  sanitizer='address'):
    """Builds all of the fuzzers for a specific OSS-Fuzz project.

  Args:
    project_name: The name of the OSS-Fuzz project being built.
    project_repo_name: The name of the projects repo.
    workspace: The location in a shared volume to store a git repo and build
      artifacts.
    pr_ref: The pull request reference to be built.
    commit_sha: The commit sha for the project to be built at.
    sanitizer: The sanitizer the fuzzers should be built with.

  Returns:
    True if build succeeded or False on failure.
  """
    # Validate inputs.
    assert pr_ref or commit_sha
    if not os.path.exists(workspace):
        logging.error('Invalid workspace: %s.', workspace)
        return False

    logging.info("Using %s sanitizer.", sanitizer)

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

    # Build Fuzzers using docker run.
    inferred_url, project_builder_repo_path = (
        build_specified_commit.detect_main_repo(project_name,
                                                repo_name=project_repo_name))
    if not inferred_url or not project_builder_repo_path:
        logging.error('Could not detect repo from project %s.', project_name)
        return False
    project_repo_name = os.path.basename(project_builder_repo_path)
    src_in_project_builder = os.path.dirname(project_builder_repo_path)

    manual_src_path = os.getenv('MANUAL_SRC_PATH')
    if manual_src_path:
        if not os.path.exists(manual_src_path):
            logging.error(
                'MANUAL_SRC_PATH: %s does not exist. '
                'Are you mounting it correctly?', manual_src_path)
            return False
        # This is the path taken outside of GitHub actions.
        git_workspace = os.path.dirname(manual_src_path)
    else:
        git_workspace = os.path.join(workspace, 'storage')
        os.makedirs(git_workspace, exist_ok=True)

    # Checkout projects repo in the shared volume.
    build_repo_manager = repo_manager.RepoManager(inferred_url,
                                                  git_workspace,
                                                  repo_name=project_repo_name)

    if not manual_src_path:
        checkout_specified_commit(build_repo_manager, pr_ref, commit_sha)

    command = [
        '--cap-add',
        'SYS_PTRACE',
        '-e',
        'FUZZING_ENGINE=' + DEFAULT_ENGINE,
        '-e',
        'SANITIZER=' + sanitizer,
        '-e',
        'ARCHITECTURE=' + DEFAULT_ARCHITECTURE,
        '-e',
        'FUZZING_LANGUAGE=c++',  # FIXME: Add proper support.
    ]
    container = utils.get_container_name()
    if container:
        command += ['-e', 'OUT=' + out_dir, '--volumes-from', container]
        bash_command = 'rm -rf {0} && cp -r {1} {2} && compile'.format(
            os.path.join(src_in_project_builder, project_repo_name, '*'),
            os.path.join(git_workspace, project_repo_name),
            src_in_project_builder)
    else:
        command += [
            '-e', 'OUT=' + '/out', '-v',
            '%s:%s' %
            (os.path.join(git_workspace, project_repo_name),
             os.path.join(src_in_project_builder, project_repo_name)), '-v',
            '%s:%s' % (out_dir, '/out')
        ]
        bash_command = 'compile'

    command.extend([
        'gcr.io/oss-fuzz/' + project_name,
        '/bin/bash',
        '-c',
    ])
    command.append(bash_command)
    if helper.docker_run(command):
        logging.error('Building fuzzers failed.')
        return False
    remove_unaffected_fuzzers(project_name, out_dir,
                              build_repo_manager.get_git_diff(),
                              project_builder_repo_path)
    return True
示例#15
0
def build_fuzzers(project_name,
                  project_repo_name,
                  workspace,
                  pr_ref=None,
                  commit_sha=None):
    """Builds all of the fuzzers for a specific OSS-Fuzz project.

  Args:
    project_name: The name of the OSS-Fuzz project being built.
    project_repo_name: The name of the projects repo.
    workspace: The location in a shared volume to store a git repo and build
      artifacts.
    pr_ref: The pull request reference to be built.
    commit_sha: The commit sha for the project to be built at.

  Returns:
    True if build succeeded or False on failure.
  """
    # Validate inputs.
    assert pr_ref or commit_sha
    if not os.path.exists(workspace):
        logging.error('Invalid workspace: %s.', workspace)
        return False

    git_workspace = os.path.join(workspace, 'storage')
    os.makedirs(git_workspace, exist_ok=True)
    out_dir = os.path.join(workspace, 'out')
    os.makedirs(out_dir, exist_ok=True)

    # Detect repo information.
    inferred_url, oss_fuzz_repo_path = build_specified_commit.detect_main_repo(
        project_name, repo_name=project_repo_name)
    if not inferred_url or not oss_fuzz_repo_path:
        logging.error('Could not detect repo from project %s.', project_name)
        return False
    src_in_docker = os.path.dirname(oss_fuzz_repo_path)
    oss_fuzz_repo_name = os.path.basename(oss_fuzz_repo_path)

    # Checkout projects repo in the shared volume.
    build_repo_manager = repo_manager.RepoManager(inferred_url,
                                                  git_workspace,
                                                  repo_name=oss_fuzz_repo_name)
    try:
        if pr_ref:
            build_repo_manager.checkout_pr(pr_ref)
        else:
            build_repo_manager.checkout_commit(commit_sha)
    except RuntimeError:
        logging.error('Can not check out requested state.')
        return False
    except ValueError:
        logging.error('Invalid commit SHA requested %s.', commit_sha)
        return False

    # Build Fuzzers using docker run.
    command = [
        '--cap-add', 'SYS_PTRACE', '-e', 'FUZZING_ENGINE=' + DEFAULT_ENGINE,
        '-e', 'SANITIZER=' + DEFAULT_SANITIZER, '-e',
        'ARCHITECTURE=' + DEFAULT_ARCHITECTURE
    ]
    container = utils.get_container_name()
    if container:
        command += ['-e', 'OUT=' + out_dir, '--volumes-from', container]
        bash_command = 'rm -rf {0} && cp -r {1} {2} && compile'.format(
            os.path.join(src_in_docker, oss_fuzz_repo_name, '*'),
            os.path.join(git_workspace, oss_fuzz_repo_name), src_in_docker)
    else:
        command += [
            '-e', 'OUT=' + '/out', '-v',
            '%s:%s' % (os.path.join(git_workspace, oss_fuzz_repo_name),
                       os.path.join(src_in_docker, oss_fuzz_repo_name)), '-v',
            '%s:%s' % (out_dir, '/out')
        ]
        bash_command = 'compile'

    command.extend([
        'gcr.io/oss-fuzz/' + project_name,
        '/bin/bash',
        '-c',
    ])
    command.append(bash_command)
    if helper.docker_run(command):
        logging.error('Building fuzzers failed.')
        return False
    remove_unaffected_fuzzers(project_name, out_dir,
                              build_repo_manager.get_git_diff(),
                              oss_fuzz_repo_path)
    return True
示例#16
0
def build_fuzzers(project_name, project_repo_name, commit_sha, git_workspace,
                  out_dir):
    """Builds all of the fuzzers for a specific OSS-Fuzz project.

  Args:
    project_name: The name of the OSS-Fuzz project being built.
    project_repo_name: The name of the projects repo.
    commit_sha: The commit SHA to be checked out and fuzzed.
    git_workspace: The location in the shared volume to store git repos.
    out_dir: The location in the shared volume to store output artifacts.

  Returns:
    True if build succeeded or False on failure.
  """
    if not os.path.exists(git_workspace):
        logging.error('Invalid git workspace: %s.', format(git_workspace))
        return False
    if not os.path.exists(out_dir):
        logging.error('Invalid out directory %s.', format(out_dir))
        return False

    inferred_url, oss_fuzz_repo_path = build_specified_commit.detect_main_repo(
        project_name, repo_name=project_repo_name)
    if not inferred_url or not oss_fuzz_repo_path:
        logging.error('Could not detect repo from project %s.', project_name)
        return False
    src_in_docker = os.path.dirname(oss_fuzz_repo_path)
    oss_fuzz_repo_name = os.path.basename(oss_fuzz_repo_path)

    # Checkout projects repo in the shared volume.
    build_repo_manager = repo_manager.RepoManager(inferred_url,
                                                  git_workspace,
                                                  repo_name=oss_fuzz_repo_name)
    try:
        build_repo_manager.checkout_commit(commit_sha)
    except repo_manager.RepoManagerError:
        logging.error('Specified commit does not exist.')
        # NOTE: Remove return statement for testing.
        return False

    command = [
        '--cap-add', 'SYS_PTRACE', '-e', 'FUZZING_ENGINE=libfuzzer', '-e',
        'SANITIZER=address', '-e', 'ARCHITECTURE=x86_64'
    ]
    container = utils.get_container_name()
    if container:
        command += ['-e', 'OUT=' + out_dir, '--volumes-from', container]
        bash_command = 'rm -rf {0} && cp -r {1} {2} && compile'.format(
            os.path.join(src_in_docker, oss_fuzz_repo_name, '*'),
            os.path.join(git_workspace, oss_fuzz_repo_name), src_in_docker)
    else:
        command += [
            '-e', 'OUT=' + '/out', '-v',
            '%s:%s' % (os.path.join(git_workspace, oss_fuzz_repo_name),
                       os.path.join(src_in_docker, oss_fuzz_repo_name)), '-v',
            '%s:%s' % (out_dir, '/out')
        ]
        bash_command = 'compile'

    command.extend([
        'gcr.io/oss-fuzz/' + project_name,
        '/bin/bash',
        '-c',
    ])
    command.append(bash_command)

    if helper.docker_run(command):
        logging.error('Building fuzzers failed.')
        return False
    return True
示例#17
0
def _bisect(bisect_type, old_commit, new_commit, test_case_path, fuzz_target,
            build_data):
    """Perform the bisect."""
    # pylint: disable=too-many-branches
    base_builder_repo = build_specified_commit.load_base_builder_repo()

    with tempfile.TemporaryDirectory() as tmp_dir:
        repo_url, repo_path = build_specified_commit.detect_main_repo(
            build_data.project_name, commit=new_commit)
        if not repo_url or not repo_path:
            raise ValueError('Main git repo can not be determined.')

        if old_commit == new_commit:
            raise BisectError('old_commit is the same as new_commit', repo_url)

        # Copy /src from the built Docker container to ensure all dependencies
        # exist. This will be mounted when running them.
        host_src_dir = build_specified_commit.copy_src_from_docker(
            build_data.project_name, tmp_dir)

        bisect_repo_manager = repo_manager.BaseRepoManager(
            os.path.join(host_src_dir, os.path.basename(repo_path)))
        commit_list = bisect_repo_manager.get_commit_list(
            new_commit, old_commit)

        old_idx = len(commit_list) - 1
        new_idx = 0
        logging.info('Testing against new_commit (%s)', commit_list[new_idx])
        if not build_specified_commit.build_fuzzers_from_commit(
                commit_list[new_idx],
                bisect_repo_manager,
                host_src_dir,
                build_data,
                base_builder_repo=base_builder_repo):
            raise BisectError('Failed to build new_commit', repo_url)

        if bisect_type == 'fixed':
            should_crash = False
        elif bisect_type == 'regressed':
            should_crash = True
        else:
            raise BisectError('Invalid bisect type ' + bisect_type, repo_url)

        expected_error = _check_for_crash(build_data.project_name, fuzz_target,
                                          test_case_path)
        logging.info('new_commit result = %s', expected_error)

        if not should_crash and expected_error:
            logging.warning('new_commit crashed but not shouldn\'t. '
                            'Continuing to see if stack changes.')

        range_valid = False
        for _ in range(2):
            logging.info('Testing against old_commit (%s)',
                         commit_list[old_idx])
            if not build_specified_commit.build_fuzzers_from_commit(
                    commit_list[old_idx],
                    bisect_repo_manager,
                    host_src_dir,
                    build_data,
                    base_builder_repo=base_builder_repo):
                raise BisectError('Failed to build old_commit', repo_url)

            if _check_for_crash(build_data.project_name, fuzz_target,
                                test_case_path) == expected_error:
                logging.warning(
                    'old_commit %s had same result as new_commit %s',
                    old_commit, new_commit)
                # Try again on an slightly older commit.
                old_commit = bisect_repo_manager.get_parent(old_commit, 64)
                if not old_commit:
                    break

                commit_list = bisect_repo_manager.get_commit_list(
                    new_commit, old_commit)
                old_idx = len(commit_list) - 1
                continue

            range_valid = True
            break

        if not range_valid:
            raise BisectError('old_commit had same result as new_commit',
                              repo_url)

        while old_idx - new_idx > 1:
            curr_idx = (old_idx + new_idx) // 2
            logging.info('Testing against %s (idx=%d)', commit_list[curr_idx],
                         curr_idx)
            if not build_specified_commit.build_fuzzers_from_commit(
                    commit_list[curr_idx],
                    bisect_repo_manager,
                    host_src_dir,
                    build_data,
                    base_builder_repo=base_builder_repo):
                # Treat build failures as if we couldn't repo.
                # TODO(ochang): retry nearby commits?
                old_idx = curr_idx
                continue

            current_error = _check_for_crash(build_data.project_name,
                                             fuzz_target, test_case_path)
            logging.info('Current result = %s', current_error)
            if expected_error == current_error:
                new_idx = curr_idx
            else:
                old_idx = curr_idx
        return Result(repo_url, commit_list[new_idx])
示例#18
0
def _bisect(bisect_type, old_commit, new_commit, test_case_path, fuzz_target,
            build_data):
    """Perform the bisect."""
    # pylint: disable=too-many-branches
    base_builder_repo = _load_base_builder_repo()

    with tempfile.TemporaryDirectory() as tmp_dir:
        repo_url, repo_path = build_specified_commit.detect_main_repo(
            build_data.project_name, commit=new_commit)
        if not repo_url or not repo_path:
            raise ValueError('Main git repo can not be determined.')

        # Copy /src from the built Docker container to ensure all dependencies
        # exist. This will be mounted when running them.
        host_src_dir = build_specified_commit.copy_src_from_docker(
            build_data.project_name, tmp_dir)

        bisect_repo_manager = repo_manager.BaseRepoManager(
            os.path.join(host_src_dir, os.path.basename(repo_path)))
        commit_list = bisect_repo_manager.get_commit_list(
            new_commit, old_commit)

        old_idx = len(commit_list) - 1
        new_idx = 0
        logging.info('Testing against new_commit (%s)', commit_list[new_idx])
        if not build_specified_commit.build_fuzzers_from_commit(
                commit_list[new_idx],
                bisect_repo_manager,
                host_src_dir,
                build_data,
                base_builder_repo=base_builder_repo):
            raise RuntimeError('Failed to build new_commit')

        if bisect_type == 'fixed':
            expected_error = False
        elif bisect_type == 'regressed':
            expected_error = True
        else:
            raise ValueError('Invalid bisect type ' + bisect_type)

        if _check_for_crash(build_data.project_name, fuzz_target,
                            test_case_path) != expected_error:
            raise RuntimeError('new_commit didn\'t have expected result.')

        # Check if the error is persistent through the commit range
        if old_commit:
            logging.info('Testing against old_commit (%s)',
                         commit_list[old_idx])
            if not build_specified_commit.build_fuzzers_from_commit(
                    commit_list[old_idx],
                    bisect_repo_manager,
                    host_src_dir,
                    build_data,
                    base_builder_repo=base_builder_repo):
                raise RuntimeError('Failed to build old_commit')

            if _check_for_crash(build_data.project_name, fuzz_target,
                                test_case_path) == expected_error:
                raise RuntimeError('old_commit had same result as new_commit')

        while old_idx - new_idx > 1:
            curr_idx = (old_idx + new_idx) // 2
            logging.info('Testing against %s (idx=%d)', commit_list[curr_idx],
                         curr_idx)
            if not build_specified_commit.build_fuzzers_from_commit(
                    commit_list[curr_idx],
                    bisect_repo_manager,
                    host_src_dir,
                    build_data,
                    base_builder_repo=base_builder_repo):
                # Treat build failures as if we couldn't repo.
                # TODO(ochang): retry nearby commits?
                old_idx = curr_idx
                continue

            is_error = _check_for_crash(build_data.project_name, fuzz_target,
                                        test_case_path)
            if expected_error == is_error:
                new_idx = curr_idx
            else:
                old_idx = curr_idx
        return Result(repo_url, commit_list[new_idx])
示例#19
0
def bisect(old_commit, new_commit, test_case_path, fuzz_target, build_data):  # pylint: disable=too-many-locals
  """From a commit range, this function caluclates which introduced a
  specific error from a fuzz test_case_path.

  Args:
    old_commit: The oldest commit in the error regression range.
    new_commit: The newest commit in the error regression range.
    test_case_path: The file path of the test case that triggers the error
    fuzz_target: The name of the fuzzer to be tested.
    build_data: a class holding all of the input parameters for bisection.

  Returns:
    The commit SHA that introduced the error or None.

  Raises:
    ValueError: when a repo url can't be determine from the project.
  """
  with tempfile.TemporaryDirectory() as tmp_dir:
    repo_url, repo_path = build_specified_commit.detect_main_repo(
        build_data.project_name, commit=new_commit)
    if not repo_url or not repo_path:
      raise ValueError('Main git repo can not be determined.')

    host_src_dir = build_specified_commit.copy_src_from_docker(
        build_data.project_name, tmp_dir)

    bisect_repo_manager = repo_manager.RepoManager(
        repo_url, host_src_dir, repo_name=os.path.basename(repo_path))
    commit_list = bisect_repo_manager.get_commit_list(new_commit, old_commit)
    old_idx = len(commit_list) - 1
    new_idx = 0
    logging.info('Testing against new_commit (%s)', commit_list[new_idx])
    if not build_specified_commit.build_fuzzers_from_commit(
        commit_list[new_idx], bisect_repo_manager, host_src_dir, build_data):
      raise RuntimeError('Failed to build new_commit')

    expected_error_code = helper.reproduce_impl(build_data.project_name,
                                                fuzz_target, False, [], [],
                                                test_case_path)

    # Check if the error is persistent through the commit range
    if old_commit:
      logging.info('Testing against old_commit (%s)', commit_list[old_idx])
      if not build_specified_commit.build_fuzzers_from_commit(
          commit_list[old_idx],
          bisect_repo_manager,
          host_src_dir,
          build_data,
      ):
        raise RuntimeError('Failed to build old_commit')

      if expected_error_code == helper.reproduce_impl(build_data.project_name,
                                                      fuzz_target, False, [],
                                                      [], test_case_path):
        return commit_list[old_idx]

    while old_idx - new_idx > 1:
      curr_idx = (old_idx + new_idx) // 2
      logging.info('Testing against %s (idx=%d)', commit_list[curr_idx],
                   curr_idx)
      build_specified_commit.build_fuzzers_from_commit(commit_list[curr_idx],
                                                       bisect_repo_manager,
                                                       host_src_dir, build_data)
      error_code = helper.reproduce_impl(build_data.project_name, fuzz_target,
                                         False, [], [], test_case_path)
      if expected_error_code == error_code:
        new_idx = curr_idx
      else:
        old_idx = curr_idx
    return commit_list[new_idx]