Exemplo n.º 1
0
def main():
    config = process_arguments()

    repo = git.Repo(config.project)
    commit = repo.commit(config.version)
    diff = commit.diff(config.fix_hash, create_patch=True)

    config.patch = util.load_patch(config.patch.read())
    results = {config.version: run(config, diff)}

    from runner import determine_vulnerability_status
    config.additions_threshold = 0.5
    config.deletions_threshold = 0.25
    determine_vulnerability_status(config, results)

    print(json.dumps(results, indent=4))
Exemplo n.º 2
0
def run_git(config, detection_call):
    """
        Run detection method with the provided patch for the provided repo on the provided versions (or all versions
        if none provided).
    :param config: Config object with all parameters
    :param detection_call: Visitor callback method to evaluate each version in a form detection(config, version_diff)
    :return: a results dictionary
    :rtype: dict
    """
    sha1_regex = re.compile('([a-f0-9]{40})')

    full_path = os.path.abspath(config.project)

    try:
        repo = git.Repo(full_path)
    except git.exc.InvalidGitRepositoryError:
        error('project path is not a valid git repository', True)

    try:
        active_branch = repo.active_branch
    except TypeError:
        if len(repo.branches) == 1:
            active_branch = repo.branches[0]
            repo.git.checkout(active_branch, force=True)
        else:
            error('Repository is in a detached HEAD state ' +
                  'and could not determine default branch.', True)

    if config.start_version:
        start_version = pkg_resources.parse_version(config.start_version)
    else:
        start_version = pkg_resources.SetuptoolsLegacyVersion('0.0.0')

    versions = []
    if config.versions:
        try:
            if os.path.exists(config.versions):
                with open(config.versions, 'r') as file:
                    versions = file.read().strip().split('\n')
            else:
                versions = config.versions.strip().split(',')
        except TypeError:
            versions = config.versions.strip().split(',')
    else:
        for tag in repo.tags:
            if start_version <= pkg_resources.parse_version(tag.name):
                versions.append(tag.name)

    if not config.debug:
        versions = tqdm.tqdm(versions)

    version_results = {}
    patch_text = config.patch.read()
    patch = util.load_patch(patch_text)

    match = sha1_regex.match(patch_text.split()[1])

    if match:
        sha = match.group(1)
    else:
        raise Exception('No commit hash found in patch')

    if config.debug:
        print('Starting from commit sha {}'.format(sha))

    try:
        for version in versions:

            if version not in repo.tags:
                raise ValueError('No such version "{}"'.format(version))

            # Get the version commit and diff it with the fixed version (the patch hash)
            # The "a" side is the evaluated version, the "b" side is the patch
            # The diff shows what "a" needs to become "b"
            # The diff is a instance of 'git.diff.DiffIndex'
            # The object 'git.diff.DiffIndex' is an array of objects 'git.diff.Diff'
            # The object 'git.diff.Diff' prints the diff, so I can parse it as a patch
            commit = repo.commit(version)
            version_diff = commit.diff(sha, create_patch=True)

            diffs = []
            for diff in patch:
                result = resolver.resolve_path(
                    repo, sha, version, diff.header.new_path, config.debug
                )

                if config.debug:
                    print(result)

                header = diff.header._replace(path=result[0], status=result[1])

                adjusted_diff = diff._replace(header=header)
                diffs.append(adjusted_diff)

            config.patch = diffs

            # Call visitor detection method on current version with parameters in the config dictionary
            version_results[version] = detection_call(config, version_diff)

            if config.debug:
                print('Removing {0}'.format(version))
    except KeyboardInterrupt:
        pass
    except AssertionError:
        error('assertion failed!')
        version_results = None
    except Exception as e:
        error(str(e))
        version_results = None
    finally:
        print('\r', end='')

    return version_results
Exemplo n.º 3
0
def main():
    config = process_arguments()
    config.patch = util.load_patch(config.patch.read())
    print(json.dumps(run(config), indent=4))
Exemplo n.º 4
0
def run(config, version_diffs):
    total_patch_additions = 0
    total_patch_deletions = 0

    detected_patch_additions = 0
    detected_patch_deletions = 0

    ratios = {}

    confident = True

    for patch_diff in config.patch:
        new_source_path = patch_diff.header.path
        old_source_path = patch_diff.header.old_path

        full_path = new_source_path
        if full_path in [d.b_path for d in version_diffs]:
            if config.debug:
                print('Found {0}'.format(full_path))
        else:
            full_path = old_source_path
            if full_path in [d.b_path for d in version_diffs]:
                if config.debug:
                    print('WARNING: Found file at old path: {0}'.format(
                        full_path))

        if blacklisted(full_path) or not whitelisted(full_path):
            continue

        ratios[patch_diff.header.path] = {}

        detected_file_deletions = 0
        detected_file_additions = 0

        patch_deletions, patch_additions, _, _, _ = split_changes(patch_diff)

        found = False

        for version_diff in version_diffs:

            # b_path is the path that will become the fix_patch after applying the diff to the evaluated version
            if full_path == version_diff.b_path:

                found = True

                # Need to format the diff as a string parseable by whatthepatch
                stripped_changes = str(version_diff.diff).replace(
                    "b'", "", 1).replace("b\"", "", 1).replace("\\n", "\n")
                version_patch = "--- {}\n+++ {}\n{}".format(
                    full_path, full_path, stripped_changes)

                version_diff_patch = util.load_patch(version_patch)

                version_deletions, version_additions, _, _, _ = split_changes(
                    version_diff_patch[0])

                # Keep a search index so it always searches forward
                search_index = 0
                for deletion in patch_deletions:
                    for i in range(search_index, len(version_deletions)):
                        if deletion == version_deletions[i]:
                            search_index = i
                            detected_file_deletions += 1
                            break

                # Keep a search index so it always searches forward
                search_index = 0
                for addition in patch_additions:
                    for i in range(search_index, len(version_additions)):
                        if addition == version_additions[i]:
                            search_index = i
                            detected_file_additions += 1
                            break

                break

        if not found:
            if config.debug:
                print('WARNING: File {0} does not exist'.format(full_path))

        total_file_additions = len(patch_additions)
        total_file_deletions = len(patch_deletions)

        detected_patch_additions += detected_file_additions
        detected_patch_deletions += detected_file_deletions

        total_patch_additions += total_file_additions
        total_patch_deletions += total_file_deletions

        # Detected means things that are missing from the evaluated version.
        # So the ratio comes from the difference from the total (original).
        added_ratio = (
            total_file_additions - detected_file_additions
        ) / total_file_additions if total_file_additions > 0 else None
        deleted_ratio = (
            total_file_deletions - detected_file_deletions
        ) / total_file_deletions if total_file_deletions > 0 else None

        ratios[patch_diff.header.path]['additions'] = added_ratio
        ratios[patch_diff.header.path]['deletions'] = deleted_ratio
        ratios[patch_diff.header.path]['status'] = patch_diff.header.status

    result = {
        'overall': {
            'additions': (total_patch_additions - detected_patch_additions) /
            total_patch_additions if total_patch_additions > 0 else None,
            'deletions': (total_patch_deletions - detected_patch_deletions) /
            total_patch_deletions if total_patch_deletions > 0 else None,
            'confident':
            confident
        },
        'breakdown': ratios
    }

    return result
Exemplo n.º 5
0
def run_git(config):
    sha1_regex = re.compile('([a-f0-9]{40})')

    full_path = os.path.abspath(config.project)

    try:
        repo = git.Repo(full_path)
    except git.exc.InvalidGitRepositoryError:
        error('project path is not a valid git repository', True)

    try:
        active_branch = repo.active_branch
    except TypeError:
        if len(repo.branches) == 1:
            active_branch = repo.branches[0]
            repo.git.checkout(active_branch)
        else:
            error(
                'Repository is in a detatched HEAD state ' +
                'and could not determine default branch.', True)

    if config.start_version:
        start_version = pkg_resources.parse_version(config.start_version)
    else:
        start_version = pkg_resources.SetuptoolsLegacyVersion('0.0.0')

    if config.versions:
        if os.path.exists(config.versions):
            with open(config.versions, 'r') as file:
                versions = file.read().strip().split('\n')
        else:
            versions = config.versions.strip().split(',')
    else:
        versions = []
        for tag in repo.tags:
            if start_version <= pkg_resources.parse_version(tag.name):
                versions.append(tag.name)

    if not config.debug:
        versions = tqdm.tqdm(versions)

    version_results = {}
    patch_text = config.patch.read()
    patch = util.load_patch(patch_text)

    match = sha1_regex.match(patch_text.split()[1])

    if match:
        sha = match.group(1)
    else:
        raise Exception('No commit hash found in patch')

    if config.debug:
        print('Starting from commit sha {}'.format(sha))

    try:
        for version in versions:
            if version in repo.branches:
                repo.git.branch('-D', version)

            if config.debug:
                print('Checking out {0}'.format(version))

            if version not in repo.tags:
                raise ValueError('No such version "{}"'.format(version))
            repo.git.reset('--hard')
            repo.git.clean('-df')
            repo.git.checkout(version)

            diffs = []
            for diff in patch:
                result = resolver.resolve_path(repo, sha, version,
                                               diff.header.new_path,
                                               config.debug)

                if config.debug:
                    print(result)

                header = diff.header._replace(path=result[0], status=result[1])

                adjusted_diff = diff._replace(header=header)
                diffs.append(adjusted_diff)

            config.patch = diffs
            version_results[version] = detector.run(config)
            repo.git.checkout(active_branch)

            if config.debug:
                print('Removing {0}'.format(version))
    except KeyboardInterrupt:
        pass
    except AssertionError:
        error('assertion failed!')
        version_results = None
        raise
    except Exception as e:
        error(str(e))
        version_results = None
        raise
    finally:
        print('\r', end='')
        repo.git.reset('--hard')
        repo.git.clean('-df')
        repo.git.checkout(active_branch)

    return version_results
Exemplo n.º 6
0
def main():
    config = process_arguments()
    config.patch = util.load_patch(config.patch.read())
    print(json.dumps(run(config), indent=4))
Exemplo n.º 7
0
def run_git(config):
    sha1_regex = re.compile('([a-f0-9]{40})')

    full_path = os.path.abspath(config.project)

    try:
        repo = git.Repo(full_path)
    except git.exc.InvalidGitRepositoryError:
        error('project path is not a valid git repository', True)

    try:
        active_branch = repo.active_branch
    except TypeError:
        if len(repo.branches) == 1:
            active_branch = repo.branches[0]
            repo.git.checkout(active_branch)
        else:
            error('Repository is in a detatched HEAD state ' +
                  'and could not determine default branch.', True)

    if config.start_version:
        start_version = pkg_resources.parse_version(config.start_version)
    else:
        start_version = pkg_resources.SetuptoolsLegacyVersion('0.0.0')

    if config.versions:
        if os.path.exists(config.versions):
            with open(config.versions, 'r') as file:
                versions = file.read().strip().split('\n')
        else:
            versions = config.versions.strip().split(',')
    else:
        versions = []
        for tag in repo.tags:
            if start_version <= pkg_resources.parse_version(tag.name):
                versions.append(tag.name)

    if not config.debug:
        versions = tqdm.tqdm(versions)

    version_results = {}
    patch_text = config.patch.read()
    patch = util.load_patch(patch_text)

    match = sha1_regex.match(patch_text.split()[1])

    if match:
        sha = match.group(1)
    else:
        raise Exception('No commit hash found in patch')

    if config.debug:
        print('Starting from commit sha {}'.format(sha))

    try:
        for version in versions:
            if version in repo.branches:
                repo.git.branch('-D', version)

            if config.debug:
                print('Checking out {0}'.format(version))

            if version not in repo.tags:
                raise ValueError('No such version "{}"'.format(version))
            repo.git.reset('--hard')
            repo.git.clean('-df')
            repo.git.checkout(version)

            diffs = []
            for diff in patch:
                result = resolver.resolve_path(
                    repo, sha, version, diff.header.new_path, config.debug
                )

                if config.debug:
                    print(result)

                header = diff.header._replace(path=result[0], status=result[1])

                adjusted_diff = diff._replace(header=header)
                diffs.append(adjusted_diff)

            config.patch = diffs
            version_results[version] = detector.run(config)
            repo.git.checkout(active_branch)

            if config.debug:
                print('Removing {0}'.format(version))
    except KeyboardInterrupt:
        pass
    except AssertionError:
        error('assertion failed!')
        version_results = None
        raise
    except Exception as e:
        error(str(e))
        version_results = None
        raise
    finally:
        print('\r', end='')
        repo.git.reset('--hard')
        repo.git.clean('-df')
        repo.git.checkout(active_branch)

    return version_results