Exemplo n.º 1
0
def bisect(bisect_type, 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:
    bisect_type: The type of the bisect ('regressed' or 'fixed').
    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.
  """
    try:
        return _bisect(bisect_type, old_commit, new_commit, test_case_path,
                       fuzz_target, build_data)
    finally:
        # Clean up projects/ as _bisect may have modified it.
        oss_fuzz_repo_manager = repo_manager.BaseRepoManager(
            helper.OSS_FUZZ_DIR)
        oss_fuzz_repo_manager.git(['reset', 'projects'])
        oss_fuzz_repo_manager.git(['checkout', 'projects'])
        oss_fuzz_repo_manager.git(['clean', '-fxd', 'projects'])
Exemplo n.º 2
0
def build_fuzzers_from_commit(commit, build_repo_manager, host_src_path,
                              build_data):
  """Builds a OSS-Fuzz fuzzer at a specific commit SHA.

  Args:
    commit: The commit SHA to build the fuzzers at.
    build_repo_manager: The OSS-Fuzz project's repo manager to be built at.
    build_data: A struct containing project build information.
  Returns:
    0 on successful build or error code on failure.
  """
  oss_fuzz_repo_manager = repo_manager.BaseRepoManager(helper.OSS_FUZZ_DIR)
  num_retry = 1

  for i in range(num_retry + 1):
    build_repo_manager.checkout_commit(commit, clean=False)
    result = helper.build_fuzzers_impl(project_name=build_data.project_name,
                                       clean=True,
                                       engine=build_data.engine,
                                       sanitizer=build_data.sanitizer,
                                       architecture=build_data.architecture,
                                       env_to_add=None,
                                       source_path=host_src_path,
                                       mount_location='/src')
    if result == 0 or i == num_retry:
      break

    # Retry with an OSS-Fuzz builder container that's closer to the project
    # commit date.
    commit_date = build_repo_manager.commit_date(commit)
    projects_dir = os.path.join('projects', build_data.project_name)

    # Find first change in the projects/<PROJECT> directory before the project
    # commit date.
    oss_fuzz_commit, _, _ = oss_fuzz_repo_manager.git([
        'log', '--before=' + commit_date.isoformat(), '-n1', '--format=%H',
        projects_dir
    ],
                                                      check_result=True)
    oss_fuzz_commit = oss_fuzz_commit.strip()
    if not oss_fuzz_commit:
      logging.warning('No suitable earlier OSS-Fuzz commit found.')
      break

    logging.info('Build failed. Retrying on earlier OSS-Fuzz commit %s.',
                 oss_fuzz_commit)

    # Check out projects/<PROJECT> dir to the commit that was found.
    oss_fuzz_repo_manager.git(['checkout', oss_fuzz_commit, projects_dir],
                              check_result=True)

    # Rebuild image and re-copy src dir since things in /src could have changed.
    if not helper.build_image_impl(build_data.project_name):
      raise RuntimeError('Failed to rebuild image.')

    shutil.rmtree(host_src_path, ignore_errors=True)
    copy_src_from_docker(build_data.project_name,
                         os.path.dirname(host_src_path))

  return result == 0
Exemplo n.º 3
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])
Exemplo n.º 4
0
def main():
    """Main function."""
    logging.getLogger().setLevel(logging.INFO)

    parser = argparse.ArgumentParser(
        description='Build fuzzers at a specific commit')
    parser.add_argument('--project_name',
                        help='The name of the project where the bug occurred.',
                        required=True)
    parser.add_argument('--commit',
                        help='The newest commit SHA to be bisected.',
                        required=True)
    parser.add_argument('--engine',
                        help='The default is "libfuzzer".',
                        default='libfuzzer')
    parser.add_argument('--sanitizer',
                        default='address',
                        help='The default is "address".')
    parser.add_argument('--architecture', default='x86_64')

    args = parser.parse_args()

    repo_url, repo_path = detect_main_repo(args.project_name,
                                           commit=args.commit)

    if not repo_url or not repo_path:
        raise ValueError('Main git repo can not be determined.')

    with tempfile.TemporaryDirectory() as tmp_dir:
        host_src_dir = copy_src_from_docker(args.project_name, tmp_dir)
        build_repo_manager = repo_manager.BaseRepoManager(
            os.path.join(host_src_dir, os.path.basename(repo_path)))
        base_builder_repo = load_base_builder_repo()

        build_data = BuildData(project_name=args.project_name,
                               engine=args.engine,
                               sanitizer=args.sanitizer,
                               architecture=args.architecture)
        if not build_fuzzers_from_commit(args.commit,
                                         build_repo_manager,
                                         host_src_dir,
                                         build_data,
                                         base_builder_repo=base_builder_repo):
            raise RuntimeError('Failed to build.')
Exemplo n.º 5
0
def build_fuzzers_from_commit(commit,
                              build_repo_manager,
                              host_src_path,
                              build_data,
                              base_builder_repo=None):
    """Builds a OSS-Fuzz fuzzer at a specific commit SHA.

  Args:
    commit: The commit SHA to build the fuzzers at.
    build_repo_manager: The OSS-Fuzz project's repo manager to be built at.
    build_data: A struct containing project build information.
    base_builder_repo: A BaseBuilderRepo.
  Returns:
    0 on successful build or error code on failure.
  """
    oss_fuzz_repo_manager = repo_manager.BaseRepoManager(helper.OSS_FUZZ_DIR)
    num_retry = 1

    def cleanup():
        # Re-copy /src for a clean checkout every time.
        copy_src_from_docker(build_data.project_name,
                             os.path.dirname(host_src_path))

    projects_dir = os.path.join('projects', build_data.project_name)
    dockerfile_path = os.path.join(projects_dir, 'Dockerfile')

    for i in range(num_retry + 1):
        build_repo_manager.checkout_commit(commit, clean=False)

        post_checkout_steps = get_required_post_checkout_steps(dockerfile_path)
        for workdir, post_checkout_step in post_checkout_steps:
            logging.info('Running post-checkout step `%s` in %s.',
                         post_checkout_step, workdir)
            helper.docker_run([
                '-w',
                workdir,
                '-v',
                host_src_path + ':' + '/src',
                'gcr.io/oss-fuzz/' + build_data.project_name,
                '/bin/bash',
                '-c',
                post_checkout_step,
            ])

        result = helper.build_fuzzers_impl(
            project_name=build_data.project_name,
            clean=True,
            engine=build_data.engine,
            sanitizer=build_data.sanitizer,
            architecture=build_data.architecture,
            env_to_add=None,
            source_path=host_src_path,
            mount_location='/src')
        if result == 0 or i == num_retry:
            break

        # Retry with an OSS-Fuzz builder container that's closer to the project
        # commit date.
        commit_date = build_repo_manager.commit_date(commit)

        # Find first change in the projects/<PROJECT> directory before the project
        # commit date.
        oss_fuzz_commit, _, _ = oss_fuzz_repo_manager.git([
            'log', '--before=' + commit_date.isoformat(), '-n1', '--format=%H',
            projects_dir
        ],
                                                          check_result=True)
        oss_fuzz_commit = oss_fuzz_commit.strip()
        if not oss_fuzz_commit:
            logging.warning('No suitable earlier OSS-Fuzz commit found.')
            break

        logging.info('Build failed. Retrying on earlier OSS-Fuzz commit %s.',
                     oss_fuzz_commit)

        # Check out projects/<PROJECT> dir to the commit that was found.
        oss_fuzz_repo_manager.git(['checkout', oss_fuzz_commit, projects_dir],
                                  check_result=True)

        # Also use the closest base-builder we can find.
        if base_builder_repo:
            base_builder_digest = base_builder_repo.find_digest(commit_date)
            logging.info('Using base-builder with digest %s.',
                         base_builder_digest)
            _replace_base_builder_digest(dockerfile_path, base_builder_digest)

        # Rebuild image and re-copy src dir since things in /src could have changed.
        if not _build_image_with_retries(build_data.project_name):
            raise RuntimeError('Failed to rebuild image.')

        cleanup()

    cleanup()
    return result == 0
Exemplo n.º 6
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])
Exemplo n.º 7
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])