Skip to content

Commit 8d9cbdd

Browse files
committed
Simplify patch inference, fix arch importer bug and remove useless code
Signed-off-by: Shivam Sandbhor <shivam.sandbhor@gmail.com>
1 parent 6749804 commit 8d9cbdd

5 files changed

Lines changed: 55 additions & 116 deletions

File tree

vulnerabilities/helpers.py

Lines changed: 31 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -119,71 +119,40 @@ def requests_with_5xx_retry(max_retries=5, backoff_factor=0.5):
119119

120120

121121
def nearest_patched_package(vulnerable_packages, resolved_packages):
122-
if not vulnerable_packages:
123-
return []
124-
125-
def create_package_by_version_obj_mapping(packages, overwrite=False):
126-
# overwrite=True, returns version->PackageURL mapping.
127-
# overwrite=False, returns version->[PackageURL] mapping.
128-
if not packages:
129-
return {}
130-
131-
package_by_version_obj_mapping = {}
132-
version_class = version_class_by_package_type[packages[0].type]
133-
for package in packages:
134-
version_object = version_class(package.version)
135-
if not overwrite:
136-
if version_object in package_by_version_obj_mapping:
137-
package_by_version_obj_mapping[version_object].append(package)
138-
else:
139-
package_by_version_obj_mapping[version_object] = [package]
140-
else:
141-
package_by_version_obj_mapping[version_object] = package
142-
143-
return package_by_version_obj_mapping
144-
145-
vulnerable_packages_by_version_obj = create_package_by_version_obj_mapping(vulnerable_packages)
146-
resolved_package_by_version_obj = create_package_by_version_obj_mapping(
147-
resolved_packages, overwrite=True
122+
# This class is used to get around bisect module's lack of supplying custom
123+
# compartor. Get rid of this once we use python 3.10 which supports this.
124+
# See https://github.com/python/cpython/pull/20556
125+
class PackageURLWithVersionComparator:
126+
def __init__(self, package):
127+
self.package = package
128+
self.version_object = version_class_by_package_type[package.type](package.version)
129+
130+
def __eq__(self, other):
131+
return self.version_object == other.version_object
132+
133+
def __lt__(self, other):
134+
return self.version_object < other.version_object
135+
136+
vulnerable_packages = sorted(
137+
[PackageURLWithVersionComparator(package) for package in vulnerable_packages]
148138
)
149-
150-
vulnerable_versions = list(vulnerable_packages_by_version_obj.keys())
151-
resolved_versions = list(resolved_package_by_version_obj.keys())
152-
153-
patched_version_by_vulnerable_versions = nearest_patched_versions(
154-
vulnerable_versions, resolved_versions
139+
resolved_packages = sorted(
140+
[PackageURLWithVersionComparator(package) for package in resolved_packages]
155141
)
156142

157-
affected_packages_with_patched_package = []
158-
for vulnerable_version, patched_version in patched_version_by_vulnerable_versions.items():
159-
for vulnerable_package in vulnerable_packages_by_version_obj[vulnerable_version]:
160-
affected_packages_with_patched_package.append(
161-
AffectedPackageWithPatchedPackage(
162-
vulnerable_package=vulnerable_package,
163-
patched_package=resolved_package_by_version_obj.get(patched_version),
164-
)
165-
)
166-
167-
return affected_packages_with_patched_package
143+
resolved_package_count = len(resolved_packages)
144+
affected_package_with_patched_package_objects = []
168145

146+
for vulnerable_package in vulnerable_packages:
147+
patched_package_index = bisect.bisect_right(resolved_packages, vulnerable_package)
148+
patched_package = None
149+
if patched_package_index < resolved_package_count:
150+
patched_package = resolved_packages[patched_package_index].package
169151

170-
def nearest_patched_versions(vulnerable_versions, resolved_versions):
171-
"""
172-
Returns a mapping of vulnerable_version -> nearest_safe_version
173-
"""
152+
affected_package_with_patched_package_objects.append(
153+
AffectedPackageWithPatchedPackage(
154+
vulnerable_package=vulnerable_package.package, patched_package=patched_package
155+
)
156+
)
174157

175-
vulnerable_versions = sorted(vulnerable_versions)
176-
resolved_versions = sorted(resolved_versions)
177-
resolved_version_count = len(resolved_versions)
178-
nearest_patch_for_version = {}
179-
for vulnerable_version in vulnerable_versions:
180-
nearest_patch_for_version[vulnerable_version] = None
181-
if not resolved_versions:
182-
continue
183-
184-
patched_version_index = bisect.bisect_right(resolved_versions, vulnerable_version)
185-
if patched_version_index >= resolved_version_count:
186-
continue
187-
nearest_patch_for_version[vulnerable_version] = resolved_versions[patched_version_index]
188-
189-
return nearest_patch_for_version
158+
return affected_package_with_patched_package_objects

vulnerabilities/import_runner.py

Lines changed: 1 addition & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -158,59 +158,10 @@ def process_advisories(data_source: DataSource) -> None:
158158
f"Failed to process advisory: {advisory!r}:\n" + traceback.format_exc()
159159
)
160160

161-
# find_conflicting_relations handles in-memory conflicts
162-
# conflicts = find_conflicting_relations(bulk_create_vuln_pkg_refs)
163-
164161
models.PackageRelatedVulnerability.objects.bulk_create(
165-
[i.to_model_object() for i in bulk_create_vuln_pkg_refs if i not in conflicts]
162+
[i.to_model_object() for i in bulk_create_vuln_pkg_refs]
166163
)
167164

168-
# handle_conflicts([i.to_model_object() for i in conflicts])
169-
170-
171-
# def find_conflicting_relations(
172-
# relations: Set[Set[PackageRelatedVulnerabilityInserter]],
173-
# ) -> Set[PackageRelatedVulnerabilityInserter]:
174-
175-
# # Chop off `is_vulnerable` flag from PackageRelatedVulnerabilityInserter and create a list of
176-
# # tuples of format (rel.package, rel.vulnerability)
177-
178-
# relation_tuples = [(rel.package, rel.vulnerability) for rel in relations]
179-
# relation_counter = Counter(relation_tuples).most_common()
180-
181-
# # If a (rel.package, rel.vulnerability) occurs twice then that means the
182-
# # PackageRelatedVulnerabilityInserter objects
183-
# # (rel.package, rel.vulnerability, is_vulnerable=True) and
184-
# # (rel.package, rel.vulnerability, is_vulnerable=False) both existed which is conflicting data.
185-
# # We detect and return these conflicts.
186-
187-
# conflicts = set()
188-
# for rel, count in relation_counter:
189-
# if count < 2:
190-
# # All the subsequent entries from here on would have count == 1 which is of no interest
191-
# # since conflicts exist in pairs with `is_vulnerable=True` and `is_vulnerable=False`.
192-
# break
193-
194-
# # `rel` is of format (pkg, vuln)
195-
# conflicts.add(
196-
# PackageRelatedVulnerabilityInserter(
197-
# vulnerability=rel[1], package=rel[0], is_vulnerable=True
198-
# )
199-
# )
200-
201-
# conflicts.add(
202-
# PackageRelatedVulnerabilityInserter(
203-
# vulnerability=rel[1], package=rel[0], is_vulnerable=False
204-
# )
205-
# )
206-
207-
# return conflicts
208-
209-
210-
# def handle_conflicts(conflicts):
211-
# conflicts = serializers.serialize("json", [i for i in conflicts])
212-
# models.ImportProblem.objects.create(conflicting_model=conflicts)
213-
214165

215166
def _get_or_create_vulnerability(
216167
advisory: Advisory,

vulnerabilities/importers/archlinux.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,9 +88,9 @@ def _parse(self, record) -> List[Advisory]:
8888
advisories = []
8989

9090
for cve_id in record["issues"]:
91-
impacted_purls, resolved_purls = [], []
9291
affected_packages_with_patched_package = []
9392
for name in record["packages"]:
93+
impacted_purls, resolved_purls = [], []
9494
impacted_purls.append(
9595
PackageURL(
9696
name=name,

vulnerabilities/migrations/0003_populate_patched_package.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,33 @@
11
# Generated by Django 3.0.13 on 2021-04-04 06:40
22

3+
import bisect
34
from sys import stdout
4-
from multiprocessing import Pool
55

66
from django.db import migrations
77
from univers.versions import version_class_by_package_type
8-
from vulnerabilities.helpers import nearest_patched_versions
98

109

10+
def nearest_patched_versions(vulnerable_versions, resolved_versions):
11+
"""
12+
Returns a mapping of vulnerable_version -> nearest_safe_version
13+
"""
14+
15+
vulnerable_versions = sorted(vulnerable_versions)
16+
resolved_versions = sorted(resolved_versions)
17+
resolved_version_count = len(resolved_versions)
18+
nearest_patch_for_version = {}
19+
for vulnerable_version in vulnerable_versions:
20+
nearest_patch_for_version[vulnerable_version] = None
21+
if not resolved_versions:
22+
continue
23+
24+
patched_version_index = bisect.bisect_right(resolved_versions, vulnerable_version)
25+
if patched_version_index >= resolved_version_count:
26+
continue
27+
nearest_patch_for_version[vulnerable_version] = resolved_versions[patched_version_index]
28+
29+
return nearest_patch_for_version
30+
1131
def _get_tuple_key(packagerelatedvulnerability):
1232
return (
1333
packagerelatedvulnerability.vulnerability,

vulnerabilities/tests/test_archlinux.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,6 @@ def test_import(self):
6868
models.PackageRelatedVulnerability.objects.filter(patched_package__isnull=False).count()
6969
== 8
7070
)
71-
print(models.Package.objects.all())
7271
assert models.Package.objects.count() == 10
7372

7473
self.assert_for_package(

0 commit comments

Comments
 (0)