Skip to content

Commit 5d24fb6

Browse files
committed
Add heuristics to get vulnerability without aliases
Signed-off-by: Tushar Goel <tushar.goel.dav@gmail.com>
1 parent 4e55bbe commit 5d24fb6

3 files changed

Lines changed: 90 additions & 6 deletions

File tree

vulnerabilities/improve_runner.py

Lines changed: 81 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -68,11 +68,14 @@ def process_inferences(inferences: List[Inference], advisory: Advisory, improver
6868
logger.info(f"Improving advisory id: {advisory.id}")
6969

7070
for inference in inferences:
71-
vulnerability = get_or_create_vulnerability_and_aliases(
72-
vulnerability_id=inference.vulnerability_id,
73-
alias_names=inference.aliases,
74-
summary=inference.summary,
75-
)
71+
if not inference.aliases:
72+
vulnerability = get_or_create_vulnerability_without_aliases(inference)
73+
else:
74+
vulnerability = get_or_create_vulnerability_and_aliases(
75+
vulnerability_id=inference.vulnerability_id,
76+
alias_names=inference.aliases,
77+
summary=inference.summary,
78+
)
7679

7780
if not vulnerability:
7881
logger.warn(f"Unable to get vulnerability for inference: {inference!r}")
@@ -218,3 +221,76 @@ def get_or_create_vulnerability_and_aliases(vulnerability_id, alias_names, summa
218221
logger.info(f"New alias for {vulnerability!r}: {alias_name}")
219222

220223
return vulnerability
224+
225+
226+
def get_or_create_vulnerability_without_aliases(inference):
227+
"""
228+
Get or create vulnerabilitiy without aliases
229+
230+
Try to get vulnerability by matching references,
231+
summary and packages. If no vulnerability is found,
232+
create a new one.
233+
"""
234+
235+
refs_are_exact_match = True
236+
237+
vuln_by_refs = {}
238+
239+
for ref in inference.references:
240+
try:
241+
reference = VulnerabilityReference.objects.get(url=ref.url)
242+
vuln_by_refs[ref.url] = set(reference.vulnerabilities.all())
243+
except VulnerabilityReference.DoesNotExist:
244+
refs_are_exact_match = False
245+
pass
246+
247+
if refs_are_exact_match:
248+
common_vulns = set.intersection(*vuln_by_refs.values())
249+
250+
if len(common_vulns) == 1:
251+
return common_vulns.pop()
252+
elif len(common_vulns) > 1:
253+
for vuln in common_vulns:
254+
if vuln.summary == inference.summary:
255+
if match_packages(inference, vuln):
256+
return vuln
257+
258+
vulnerability = Vulnerability(summary=inference.summary)
259+
vulnerability.save()
260+
261+
return vulnerability
262+
263+
264+
def match_packages(inference, vuln):
265+
"""
266+
Check if the packages in the inference match the packages in the vulnerability
267+
"""
268+
for affected_purl in inference.affected_purls:
269+
if not find_package_and_check_related_to_vuln(purl=affected_purl, fix=False, vuln=vuln):
270+
return False
271+
if inference.fixed_purl and not find_package_and_check_related_to_vuln(
272+
purl=inference.fixed_purl, fix=True, vuln=vuln
273+
):
274+
return False
275+
return True
276+
277+
278+
def find_package_and_check_related_to_vuln(purl, fix, vuln):
279+
"""
280+
Find package in the database and check if it is associated
281+
with the vulnerability.
282+
If package is not found, return False
283+
If package is found, but not associated with the vulnerability, return False
284+
If package is found and associated with the vulnerability, return True
285+
"""
286+
try:
287+
package = Package.objects.get_from_purl(purl=purl)
288+
if not PackageRelatedVulnerability.objects.exists(
289+
vulnerability=vuln,
290+
package=package,
291+
fix=fix,
292+
):
293+
return False
294+
except Package.DoesNotExist:
295+
return False
296+
return True

vulnerabilities/models.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,14 @@ def get_or_create_from_purl(self, purl: PackageURL):
327327
package, _ = Package.objects.get_or_create(**purl_fields)
328328
return package
329329

330+
def get_from_purl(self, purl: PackageURL):
331+
"""
332+
Return an existing or new Package (created if neeed) given a
333+
``purl`` PackageURL.
334+
"""
335+
purl_fields = without_empty_values(purl.to_dict(encode=True))
336+
return Package.objects.get(**purl_fields)
337+
330338
def for_package_url_object(self, purl):
331339
"""
332340
Filter the QuerySet with the provided Package URL object or string. The

vulnerabilities/tests/test_single_advisory_multiple_vulns.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def setUp(self):
3232
]
3333
refs = [
3434
Reference(
35-
url="test-url",
35+
url="https://www.djangoproject.com/weblog/2015/sep/25/security-releases/",
3636
reference_id="test-id",
3737
)
3838
]

0 commit comments

Comments
 (0)