Exemple #1
0
def set_bug_attributes(bug, regress_result, fix_result):
    """Set bug attributes from bisection results."""
    repo_url = regress_result.repo_url or fix_result.repo_url
    issue_id = fix_result.issue_id or regress_result.issue_id
    project = fix_result.project or regress_result.project
    ecosystem = fix_result.ecosystem or regress_result.ecosystem
    summary = fix_result.summary or regress_result.summary
    details = fix_result.details or regress_result.details
    severity = fix_result.severity or regress_result.severity
    reference_urls = fix_result.reference_urls or regress_result.reference_urls

    bug.issue_id = issue_id
    bug.project = project
    bug.ecosystem = ecosystem
    bug.summary = summary
    bug.details = details
    bug.severity = severity
    bug.reference_url_types = {}

    for reference_url in reference_urls:
        if OSS_FUZZ_ISSUE_URL in reference_url:
            link_type = 'REPORT'
        else:
            link_type = 'WEB'
        bug.reference_url_types[reference_url] = link_type

    bug.regressed = regress_result.commit or ''
    bug.fixed = fix_result.commit or ''

    bug.affected_ranges = [
        osv.AffectedRange(type='GIT',
                          repo_url=repo_url,
                          introduced=bug.regressed,
                          fixed=bug.fixed)
    ]
Exemple #2
0
def process_impact_task(source_id, message):
    """Process an impact task."""
    logging.info('Processing impact task for %s', source_id)

    regress_result = ndb.Key(osv.RegressResult, source_id).get()
    if not regress_result:
        logging.error('Missing RegressResult for %s', source_id)
        return

    fix_result = ndb.Key(osv.FixResult, source_id).get()
    if not fix_result:
        logging.warning('Missing FixResult for %s', source_id)
        fix_result = osv.FixResult()

    # Check if there is an existing Bug for the same source, but with a different
    # allocated ID. This shouldn't happen.
    allocated_bug_id = message.attributes['allocated_id']

    existing_bug = osv.Bug.query(osv.Bug.source_id == source_id).get()
    if existing_bug and existing_bug.key.id() != allocated_bug_id:
        logging.error('Bug entry already exists for %s with a different ID %s',
                      source_id, existing_bug.key.id())
        return

    if existing_bug and existing_bug.status == osv.BugStatus.INVALID:
        logging.warning('Bug %s already marked as invalid.',
                        existing_bug.key.id())
        return

    if existing_bug:
        public = existing_bug.public
    else:
        raise osv.ImpactError('Task requested without Bug allocated.')

    repo_url = regress_result.repo_url or fix_result.repo_url
    if not repo_url:
        raise osv.ImpactError('No repo_url set')

    # Always populate Bug attributes, even if the remainder of the analysis fails.
    # This does not mark the Bug as being valid.
    set_bug_attributes(existing_bug, regress_result, fix_result)
    existing_bug.put()

    issue_id = fix_result.issue_id or regress_result.issue_id
    fix_commit = fix_result.commit

    with tempfile.TemporaryDirectory() as tmp_dir:
        repo = osv.clone_with_retries(repo_url, tmp_dir)

        # If not a precise fix commit, try to find the exact one by going through
        # commit messages (oss-fuzz only).
        if source_id.startswith(SOURCE_PREFIX) and ':' in fix_commit:
            start_commit, end_commit = fix_commit.split(':')
            commit = find_oss_fuzz_fix_via_commit(repo, start_commit,
                                                  end_commit, source_id,
                                                  issue_id)
            if commit:
                logging.info(
                    'Found exact fix commit %s via commit message (oss-fuzz)',
                    commit)
                fix_commit = commit

        # Actually compute the affected commits/tags.
        repo_analyzer = osv.RepoAnalyzer()
        result = repo_analyzer.get_affected(repo, regress_result.commit,
                                            fix_commit)
        affected_tags = sorted(list(result.tags))
        logging.info('Found affected %s', ', '.join(affected_tags))

    # If the range resolved to a single commit, simplify it.
    if len(result.fix_commits) == 1:
        fix_commit = result.fix_commits[0]
    elif not result.fix_commits:
        # Not fixed.
        fix_commit = ''

    if (len(result.regress_commits) == 1
            and osv.UNKNOWN_COMMIT not in regress_result.commit):
        regress_commit = result.regress_commits[0]
    else:
        regress_commit = regress_result.commit

    project = fix_result.project or regress_result.project
    ecosystem = fix_result.ecosystem or regress_result.ecosystem
    osv.update_affected_commits(allocated_bug_id, result.commits, project,
                                ecosystem, public)

    affected_tags = sorted(list(result.tags))
    existing_bug.fixed = fix_commit
    existing_bug.regressed = regress_commit
    existing_bug.affected = affected_tags
    existing_bug.affected_fuzzy = osv.normalize_tags(affected_tags)
    existing_bug.status = osv.BugStatus.PROCESSED

    # For the AffectedRange, use the first commit in the regress commit range, and
    # the last commit in the fix commit range.
    introduced = result.regress_commits[0] if result.regress_commits else ''
    fixed = result.fix_commits[-1] if result.fix_commits else ''
    existing_bug.affected_ranges = [
        osv.AffectedRange(type='GIT',
                          repo_url=repo_url,
                          introduced=introduced,
                          fixed=fixed),
    ]

    # Expose range data in `database_specific`.
    database_specific = {}
    if ':' in existing_bug.regressed:
        database_specific['introduced_range'] = existing_bug.regressed
    if ':' in existing_bug.fixed:
        database_specific['fixed_range'] = existing_bug.fixed

    if database_specific:
        existing_bug.database_specific = database_specific

    # Don't display additional ranges for imprecise commits, as they can be
    # confusing.
    if ':' in existing_bug.fixed or ':' in existing_bug.regressed:
        existing_bug.put()
        return

    def _sort_key(value):
        # Allow sorting of None values.
        return (value[0] or '', value[1] or '')

    for introduced_in, fixed_in in sorted(result.affected_ranges,
                                          key=_sort_key):
        if not fixed_in:
            fixed_in = ''

        if (introduced_in == existing_bug.regressed
                and fixed_in == existing_bug.fixed):
            # Don't repeat the main range.
            continue

        existing_bug.affected_ranges.append(
            osv.AffectedRange(type='GIT',
                              repo_url=repo_url,
                              introduced=introduced_in,
                              fixed=fixed_in))

    existing_bug.put()