Skip to content

Commit 916dc0d

Browse files
authored
Merge pull request #227 from sbs2001/stick_ref_ids_with_urls
Stick ref ids with urls
2 parents 6a2a1b6 + 52c7e89 commit 916dc0d

28 files changed

Lines changed: 808 additions & 554 deletions

vulnerabilities/data_source.py

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,17 @@
4343
from vulnerabilities.oval_parser import OvalParser
4444

4545

46+
@dataclasses.dataclass
47+
class Reference:
48+
49+
url: str = ''
50+
reference_id: str = ''
51+
52+
def __post_init__(self):
53+
if not any([self.url, self.reference_id]):
54+
raise TypeError
55+
56+
4657
@dataclasses.dataclass
4758
class Advisory:
4859
"""
@@ -57,16 +68,14 @@ class Advisory:
5768
summary: str
5869
impacted_package_urls: Iterable[PackageURL]
5970
resolved_package_urls: Iterable[PackageURL] = dataclasses.field(default_factory=list)
60-
reference_urls: Sequence[str] = dataclasses.field(default_factory=list)
61-
reference_ids: Sequence[str] = dataclasses.field(default_factory=list)
71+
vuln_references: List[Reference] = dataclasses.field(default_factory=list)
6272
cve_id: Optional[str] = None
6373

6474
def __hash__(self):
65-
s = '{}{}{}{}{}'.format(
75+
s = '{}{}{}{}'.format(
6676
self.summary,
6777
''.join(sorted([str(p) for p in self.impacted_package_urls])),
6878
''.join(sorted([str(p) for p in self.resolved_package_urls])),
69-
''.join(sorted(self.reference_urls)),
7079
self.cve_id,
7180
)
7281
return hash(s)
@@ -472,7 +481,8 @@ def get_data_from_xml_doc(self, xml_doc: ET.ElementTree, pkg_metadata={}) -> Lis
472481
description = definition_data['description']
473482
affected_purls = set()
474483
safe_purls = set()
475-
urls = definition_data['reference_urls']
484+
references = [Reference(url=url)
485+
for url in definition_data['reference_urls']]
476486

477487
for test_data in definition_data['test_data']:
478488
for package in test_data['package_list']:
@@ -511,5 +521,5 @@ def get_data_from_xml_doc(self, xml_doc: ET.ElementTree, pkg_metadata={}) -> Lis
511521
impacted_package_urls=affected_purls,
512522
resolved_package_urls=safe_purls,
513523
cve_id=vuln_id,
514-
reference_urls=urls))
524+
vuln_references=references))
515525
return all_adv

vulnerabilities/import_runner.py

Lines changed: 53 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#
2-
# Copyright (c) 2017 nexB Inc. and others. All rights reserved.
2+
# Copyright (c) nexB Inc. and others. All rights reserved.
33
# http://nexb.com and https://github.com/nexB/vulnerablecode/
44
# The VulnerableCode software is licensed under the Apache License version 2.0.
55
# Data generated with VulnerableCode require an acknowledgment.
@@ -18,7 +18,7 @@
1818
# OR CONDITIONS OF ANY KIND, either express or implied. No content created from
1919
# VulnerableCode should be considered or used as legal advice. Consult an Attorney
2020
# for any legal advice.
21-
# VulnerableCode is a free software code scanning tool from nexB Inc. and others.
21+
# VulnerableCode is a free software tool from nexB Inc. and others.
2222
# Visit https://github.com/nexB/vulnerablecode/ for support and download.
2323

2424
import dataclasses
@@ -51,7 +51,7 @@
5151
class VulnerabilityReferenceInserter:
5252
vulnerability: models.Vulnerability
5353
reference_id: Optional[str] = ''
54-
url: Optional[str] = ''
54+
url: Optional[str] = ''
5555

5656
def __post_init__(self):
5757
if not any([self.reference_id, self.url]):
@@ -165,45 +165,68 @@ def _process_added_advisories(data_source: DataSource) -> None:
165165

166166
def _create_vulnerability_and_references(advisory: Advisory):
167167
vuln, vuln_created = _get_or_create_vulnerability(advisory)
168-
vuln_references = set()
168+
new_vuln_references = set()
169169

170170
if vuln_created:
171171
# This means vulnerability didn't previously exist in the DB, so add
172172
# the references to bulk create queue without any hesitation
173-
for id_ in advisory.reference_ids:
174-
vuln_references.add(VulnerabilityReferenceInserter(vulnerability=vuln, reference_id=id_)) # nopep8
175-
176-
for url in advisory.reference_urls:
177-
vuln_references.add(VulnerabilityReferenceInserter(vulnerability=vuln, url=url))
173+
for ref in advisory.vuln_references:
174+
new_vuln_references.add(
175+
VulnerabilityReferenceInserter(
176+
vulnerability=vuln,
177+
**dataclasses.asdict(ref)))
178178

179179
else:
180-
vuln_refs_qs = models.VulnerabilityReference.objects.filter(vulnerability=vuln)
181-
vuln_ids = {ref.reference_id for ref in vuln_refs_qs}
182-
vuln_urls = {ref.url for ref in vuln_refs_qs}
180+
# build indexes of existing references to either update or create new ones
181+
# and avoid creating duplicates
182+
183+
existing_vuln_references = models.VulnerabilityReference.objects.filter(vulnerability=vuln)
184+
refs_by_id_url = {
185+
(ref.id, ref.url): ref for ref in existing_vuln_references if ref.id and ref.url}
186+
refs_by_id = {ref.id: ref for ref in existing_vuln_references if ref.id}
187+
refs_by_url = {ref.url: ref for ref in existing_vuln_references if ref.url}
188+
189+
for adv_ref in advisory.vuln_references:
190+
if adv_ref.reference_id and adv_ref.url:
191+
existing_ref = refs_by_id_url.get((adv_ref.reference_id, adv_ref.url))
192+
193+
elif adv_ref.reference_id:
194+
existing_ref = refs_by_id.get(adv_ref.reference_id)
195+
196+
elif adv_ref.url:
197+
existing_ref = refs_by_url.get(adv_ref.url)
183198

184-
for id_ in advisory.reference_ids:
185-
# Add the item preventing duplicates pass to through.
186-
if id_ not in vuln_ids:
187-
vuln_ids.add(id_)
188-
vuln_references.add(VulnerabilityReferenceInserter(
189-
vulnerability=vuln, reference_id=id_))
199+
if existing_ref:
200+
# update ref as needed
201+
if not adv_ref.reference_id or not adv_ref.url:
202+
continue
190203

191-
for url in advisory.reference_urls:
192-
# Add the item preventing duplicates pass to through.
193-
if url not in vuln_urls:
194-
vuln_urls.add(url)
195-
vuln_references.add(VulnerabilityReferenceInserter(
196-
vulnerability=vuln, url=url))
204+
if adv_ref.reference_id and not existing_ref.reference_id:
205+
existing_ref.reference_id = adv_ref.reference_id
197206

198-
return vuln, vuln_created, vuln_references
207+
if adv_ref.url and not existing_ref.url:
208+
existing_ref.url = adv_ref.url
209+
210+
existing_ref.save()
211+
212+
else:
213+
new_vuln_references.add(
214+
VulnerabilityReferenceInserter(
215+
vulnerability=vuln,
216+
reference_id=adv_ref.reference_id,
217+
url=adv_ref.url,
218+
)
219+
)
220+
221+
return vuln, vuln_created, new_vuln_references
199222

200223

201224
def _create_pkg_vuln_refs(vuln: models.Vulnerability, vuln_created: bool, purls: Sequence[PackageURL], is_vulnerable: bool): # nopep8
202225
new_refs = set()
203226
for purl in purls:
204227
pkg, pkg_created = _get_or_create_package(purl)
205228
vuln_pkg_ref = PackageRelatedVulnerabilityInserter(
206-
package=pkg, vulnerability=vuln, is_vulnerable=is_vulnerable)
229+
package=pkg, vulnerability=vuln, is_vulnerable=is_vulnerable)
207230

208231
if pkg_created or vuln_created:
209232
new_refs.add(vuln_pkg_ref)
@@ -371,13 +394,12 @@ def _insert_vulnerabilities_and_references(batch: Set[Advisory]) -> Set[models.V
371394

372395
vulnerabilities.add(vuln)
373396

374-
for id_ in advisory.reference_ids:
375-
models.VulnerabilityReference.objects.get_or_create(
376-
vulnerability=vuln, reference_id=id_)
397+
# TODO: Refactor this method to be atleast as efficient as
398+
# one used by _process_updated_advisories
377399

378-
for url in advisory.reference_urls:
400+
for ref in advisory.vuln_references:
379401
models.VulnerabilityReference.objects.get_or_create(
380-
vulnerability=vuln, url=url)
402+
vulnerability=vuln, reference_id=ref.reference_id, url=ref.url)
381403

382404
return vulnerabilities
383405

vulnerabilities/importers/alpine_linux.py

Lines changed: 56 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434

3535
from vulnerabilities.data_source import Advisory
3636
from vulnerabilities.data_source import GitDataSource
37+
from vulnerabilities.data_source import Reference
3738

3839

3940
def validate_schema(advisory_dict):
@@ -53,14 +54,12 @@ def validate_schema(advisory_dict):
5354
Regex(r"XSA-\d{3}"),
5455
Regex(r"ZBX-\d{4}"),
5556
Regex(r"wnpa-sec-\d{4}-\d{2}"),
56-
57-
5857
)
5958
],
6059
"",
6160
# FIXME: Remove the None when below issue gets fixed
6261
# https://gitlab.alpinelinux.org/alpine/infra/alpine-secdb/-/issues/1
63-
None
62+
None,
6463
),
6564
},
6665
}
@@ -72,13 +71,13 @@ def validate_schema(advisory_dict):
7271

7372

7473
class AlpineDataSource(GitDataSource):
75-
7674
def __enter__(self):
7775
super(AlpineDataSource, self).__enter__()
7876

79-
if not getattr(self, '_added_files', None):
77+
if not getattr(self, "_added_files", None):
8078
self._added_files, self._updated_files = self.file_changes(
81-
recursive=True, file_ext='yaml')
79+
recursive=True, file_ext="yaml"
80+
)
8281

8382
def updated_advisories(self) -> Set[Advisory]:
8483
files = self._updated_files.union(self._added_files)
@@ -94,55 +93,80 @@ def _process_file(self, path) -> List[Advisory]:
9493
with open(path) as f:
9594
record = yaml.safe_load(f)
9695

97-
if record['packages'] is None:
96+
if record["packages"] is None:
9897
return advisories
9998
validate_schema(record)
10099

101-
for p in record['packages']:
102-
advisories.extend(self._load_advisories(
103-
p['pkg'],
104-
record['distroversion'],
105-
record['reponame'],
106-
record['archs'],
107-
))
100+
for p in record["packages"]:
101+
advisories.extend(
102+
self._load_advisories(
103+
p["pkg"], record["distroversion"], record["reponame"], record["archs"],
104+
)
105+
)
108106

109107
return advisories
110108

111109
def _load_advisories(
112-
self,
113-
pkg_infos: Mapping[str, Any],
114-
distroversion: str,
115-
reponame: str,
116-
archs: Iterable[str],
110+
self, pkg_infos: Mapping[str, Any], distroversion: str, reponame: str, archs: Iterable[str],
117111
) -> List[Advisory]:
118112

119113
advisories = []
120114

121-
for version, fixed_vulns in pkg_infos['secfixes'].items():
115+
for version, fixed_vulns in pkg_infos["secfixes"].items():
122116

123117
if fixed_vulns is None:
124118
continue
125119

126120
resolved_purls = {
127121
PackageURL(
128-
name=pkg_infos['name'],
129-
type='alpine',
122+
name=pkg_infos["name"],
123+
type="alpine",
130124
version=version,
131-
qualifiers={
132-
'arch': arch, 'distroversion': distroversion, 'reponame': reponame},
125+
qualifiers={"arch": arch, "distroversion": distroversion, "reponame": reponame},
133126
)
134127
for arch in archs
135128
}
136129

137130
for vuln_ids in fixed_vulns:
138131
vuln_ids = vuln_ids.split()
139-
140-
advisories.append(Advisory(
141-
summary='',
142-
impacted_package_urls=[],
143-
resolved_package_urls=resolved_purls,
144-
reference_ids=vuln_ids[1:],
145-
cve_id=vuln_ids[0] if vuln_ids[0] != 'CVE-????-?????' else None,
146-
))
132+
references = []
133+
for reference_id in vuln_ids[1:]:
134+
135+
if reference_id.startswith("XSA"):
136+
xsa_id = reference_id.split("-")[-1]
137+
references.append(
138+
Reference(
139+
reference_id=reference_id,
140+
url="https://xenbits.xen.org/xsa/advisory-{}.html".format(xsa_id),
141+
)
142+
)
143+
144+
elif reference_id.startswith("ZBX"):
145+
references.append(
146+
Reference(
147+
reference_id=reference_id,
148+
url="https://support.zabbix.com/browse/{}".format(reference_id),
149+
)
150+
)
151+
152+
elif reference_id.startswith("wnpa-sec"):
153+
references.append(
154+
Reference(
155+
reference_id=reference_id,
156+
url="https://www.wireshark.org/security/{}.html".format(
157+
reference_id
158+
),
159+
)
160+
)
161+
162+
advisories.append(
163+
Advisory(
164+
summary="",
165+
impacted_package_urls=[],
166+
resolved_package_urls=resolved_purls,
167+
vuln_references=references,
168+
cve_id=vuln_ids[0] if vuln_ids[0] != "CVE-????-?????" else None,
169+
)
170+
)
147171

148172
return advisories

0 commit comments

Comments
 (0)