Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 127 additions & 3 deletions vulnerabilities/api_v3.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

from django.contrib.postgres.aggregates import ArrayAgg
from django.db.models import Exists
from django.db.models import F
from django.db.models import Max
from django.db.models import OuterRef
from django.db.models import Prefetch
Expand Down Expand Up @@ -46,6 +47,7 @@ class PackageQuerySerializer(serializers.Serializer):
details = serializers.BooleanField(default=False)
ignore_qualifiers_subpath = serializers.BooleanField(default=False)
max_advisories = serializers.IntegerField(default=100, min_value=1, max_value=10000)
reachability = serializers.BooleanField(default=False)

def validate(self, data):
if not data["purls"]:
Expand Down Expand Up @@ -258,6 +260,7 @@ def create(self, request, *args, **kwargs):

purls = serializer.validated_data["purls"]
details = serializer.validated_data["details"]
reachability = serializer.validated_data["reachability"]
ignore_qualifiers_subpath = serializer.validated_data["ignore_qualifiers_subpath"]
max_advisories = serializer.validated_data["max_advisories"]

Expand Down Expand Up @@ -316,7 +319,9 @@ def create(self, request, *args, **kwargs):
if request:
base_url = request.build_absolute_uri("/")[:-1]
page = self.paginate_queryset(query)
affected_advisory_map = get_affected_advisories_bulk(page, max_advisories, base_url)
affected_advisory_map = get_affected_advisories_bulk(
page, max_advisories, base_url, reachability
)
fixing_advisory_map = get_fixing_advisories_bulk(page, max_advisories, base_url)
serializer = self.get_serializer(
page,
Expand Down Expand Up @@ -449,7 +454,99 @@ class AffectedByAdvisoriesViewSet(PackageAdvisoriesViewSet):
serializer_class = AffectedByAdvisoryV3Serializer


def get_affected_advisories_bulk(packages, max_advisories, base_url):
def get_patches_bulk(package_ids, advisory_ids):
"""Get introduced and fixed patches"""

base_qs = ImpactedPackageAffecting.objects.filter(
package_id__in=package_ids,
impacted_package__advisory_id__in=advisory_ids,
impacted_package__advisory__is_latest=True,
impacted_package__advisory___all_impacts_unfurled_at__isnull=False,
)

introduced_rows = base_qs.filter(
impacted_package__introduced_by_package_commit_patches__isnull=False
).values(
"package_id",
"impacted_package__advisory_id",
commit_hash=F("impacted_package__introduced_by_package_commit_patches__commit_hash"),
vcs_url=F("impacted_package__introduced_by_package_commit_patches__vcs_url"),
)

fixed_rows = base_qs.filter(
impacted_package__fixed_by_package_commit_patches__isnull=False
).values(
"package_id",
"impacted_package__advisory_id",
commit_hash=F("impacted_package__fixed_by_package_commit_patches__commit_hash"),
vcs_url=F("impacted_package__fixed_by_package_commit_patches__vcs_url"),
)

introduced_patches_map = defaultdict(list)
fixed_patches_map = defaultdict(list)

for row in introduced_rows:
if row["commit_hash"] or row["vcs_url"]:
key = (row["package_id"], row["impacted_package__advisory_id"])
introduced_patches_map[key].append(
{
"commit_hash": row["commit_hash"],
"vcs_url": row["vcs_url"],
}
)

for row in fixed_rows:
if row["commit_hash"] or row["vcs_url"]:
key = (row["package_id"], row["impacted_package__advisory_id"])
fixed_patches_map[key].append(
{
"commit_hash": row["commit_hash"],
"vcs_url": row["vcs_url"],
}
)

return introduced_patches_map, fixed_patches_map


def build_patch_set_map(patches_map, advisory_ids_by_set):
"""
Returns:
{
advisory_set_id: [
{
"commit_hash": "...",
"vcs_url": "...",
}
]
}
"""
result = {}

for advisory_set_id, advisory_ids in advisory_ids_by_set.items():
seen = set()
collected = []

for (package_id, advisory_id), patches in patches_map.items():
if advisory_id not in advisory_ids:
continue

for patch in patches:
key = (
patch["commit_hash"],
patch["vcs_url"],
)

if key in seen:
continue

seen.add(key)
collected.append(patch)

result[advisory_set_id] = collected
return result


def get_affected_advisories_bulk(packages, max_advisories, base_url, reachability=False):
package_ids = [p.id for p in packages]

package_ids_with_multiple_importers = PackageV2.objects.filter(
Expand Down Expand Up @@ -542,6 +639,24 @@ def get_affected_advisories_bulk(packages, max_advisories, base_url):
advisory_ids_by_set[advisory_set_id].add(advisory_id)
all_advisory_ids.add(advisory_id)

introduced_patches_by_set = {}
fixed_patches_by_set = {}
if reachability:
introduced_patches_map, fixed_patches_map = get_patches_bulk(
package_ids,
advisory_ids=all_advisory_ids,
)

introduced_patches_by_set = build_patch_set_map(
introduced_patches_map,
advisory_ids_by_set,
)

fixed_patches_by_set = build_patch_set_map(
fixed_patches_map,
advisory_ids_by_set,
)

ssvc_rows = (
SSVC.objects.filter(
related_advisories__id__in=all_advisory_ids,
Expand Down Expand Up @@ -613,7 +728,6 @@ def get_affected_advisories_bulk(packages, max_advisories, base_url):

for adv in groups:
primary = adv.primary_advisory

fixed_by_packages = impact_by_package_and_advisory.get(
(package.id, primary.id),
[],
Expand Down Expand Up @@ -647,6 +761,14 @@ def get_affected_advisories_bulk(packages, max_advisories, base_url):
"exploitability": exploitability,
"risk_score": risk_score,
"fixed_by_packages": fixed_by_packages,
"introduced_in_patch": introduced_patches_by_set.get(
adv.id,
[],
),
"fixed_in_patch": fixed_patches_by_set.get(
adv.id,
[],
),
"ssvc_trees": adv.ssvc_trees,
"resource_url": resource_url,
}
Expand Down Expand Up @@ -742,6 +864,8 @@ def get_affected_advisories_bulk(packages, max_advisories, base_url):
"exploitability": advisory.exploitability,
"risk_score": advisory.risk_score,
"fixed_by_packages": fixed_by_packages,
"introduced_in_patch": introduced_patches_by_set.get(advisory_id, []),
"fixed_in_patch": introduced_patches_by_set.get(advisory_id, []),
"ssvc_trees": [
{
"vector": ssvc.vector,
Expand Down
46 changes: 44 additions & 2 deletions vulnerabilities/templates/advisory_detail.html
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,16 @@
</a>
</li>
{% endif %}


<li data-tab="patch-url">
<a>
<span>
{% with pcp_length=package_commit_patches|length %}
Patches: ({{ advisory.patches.count|add:pcp_length }})
{% endwith %}
</span>
</a>
</li>
<!-- <li data-tab="history">
<a>
<span>
Expand Down Expand Up @@ -184,6 +193,18 @@
</a>
</td>
</tr>
<tr>
<td class="two-col-left"
data-tooltip="Risk expressed as a number ranging from 0 to 10. It is calculated by multiplying
the weighted severity and exploitability values, capped at a maximum of 10.
"
>Introduced and Fixed Package Commit Patches</td>
<td class="two-col-right wrap-strings">
<a href="/advisories/commits/{{ advisory.avid }}">
Package Commit Patches Details
</a>
</td>
</tr>
</tbody>
</table>
<div class="has-text-weight-bold tab-nested-div ml-1 mb-1 mt-6">
Expand Down Expand Up @@ -436,7 +457,6 @@
</tr>
{% endfor %}
</div>


<div class="tab-div content" data-content="epss">
{% if epss_data %}
Expand Down Expand Up @@ -503,6 +523,28 @@
{% endif %}
</div>

<div class="tab-div content" data-content="patch-url">
<table class="table is-bordered is-striped is-narrow is-hoverable is-fullwidth">
<thead>
<tr>
<th style="width: 250px;"> Patch URL </th>
</tr>
</thead>
{% for patch in patches %}
<tr>
<td class="wrap-strings"><a href="{{ patch.patch_url }}" target="_blank">{{ patch.patch_url }}<i
class="fa fa-external-link fa_link_custom"></i></a></td>
</tr>
{% empty %}
<tr>
<td colspan="2">
There are no known patches.
</td>
</tr>
{% endfor %}
</table>
</div>

<div class="tab-div content" data-content="severities-vectors">
{% for severity_vector in severity_vectors %}
{% if severity_vector.vector.version == '2.0' %}
Expand Down
75 changes: 75 additions & 0 deletions vulnerabilities/templates/advisory_package_commit_details.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
{% extends "base.html" %}
{% load humanize %}
{% load widget_tweaks %}
{% load static %}
{% load show_cvss %}
{% load url_filters %}

{% block title %}
VulnerableCode Advisory Package Commit Patch Details - {{ advisoryv2.advisory_id }}
{% endblock %}

{% block content %}

{% if advisoryv2 %}
<section class="section pt-4">
<div class="details-container">
<article class="panel is-info panel-header-only">
<div class="panel-heading py-2 is-size-6">
Introduce and Fixing Package Commit Patch details for Advisory:
<span class="tag is-white custom">
{{ advisoryv2.advisory_id }}
</span>
</div>
</article>

<div id="tab-content">
<table class="table vcio-table width-100-pct mt-2">
<thead>
<tr>
<th style="width: 50%;">Introduced in</th>
<th>Fixed by</th>
</tr>
</thead>
<tbody>
{% for impact in advisoryv2.impacted_packages.all %}
{% for pkg_commit_patch in impact.introduced_by_package_commit_patches.all %}
<tr>
<td>
<a href="{{ pkg_commit_patch.vcs_url }}" target="_self">
{{ pkg_commit_patch.base_purl }}@{{ pkg_commit_patch.commit_hash }}
</a>
</td>
<td></td>
</tr>
{% endfor %}

{% for pkg_commit_patch in impact.fixed_by_package_commit_patches.all %}
<tr>
<td></td>
<td>
<a href="{{ pkg_commit_patch.vcs_url }}" target="_self">
{{ impact.base_purl }}@{{ pkg_commit_patch.commit_hash }}
</a>
</td>
</tr>
{% endfor %}

{% empty %}
<tr>
<td colspan="2">
This vulnerability is not known to affect any package commits.
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>

</div>
</section>
{% endif %}

<script src="{% static 'js/main.js' %}" crossorigin="anonymous"></script>

{% endblock %}
Loading