2121# VulnerableCode is a free software tool from nexB Inc. and others.
2222# Visit https://github.com/nexB/vulnerablecode/ for support and download.
2323
24- import dataclasses
24+ import logging
2525from typing import Any
26+ from typing import Iterable
2627from typing import List
2728from typing import Mapping
28- from typing import Set
2929
3030import requests
31- from dateutil import parser as dateparser
31+ from django . db . models . query import QuerySet
3232from packageurl import PackageURL
33+ from univers .version_range import DebianVersionRange
3334
35+ from vulnerabilities .helpers import AffectedPackage as LegacyAffectedPackage
36+ from vulnerabilities .helpers import get_item
3437from vulnerabilities .helpers import nearest_patched_package
3538from vulnerabilities .importer import AdvisoryData
39+ from vulnerabilities .importer import AffectedPackage
3640from vulnerabilities .importer import Importer
3741from vulnerabilities .importer import Reference
42+ from vulnerabilities .importer import UnMergeablePackageError
43+ from vulnerabilities .improver import MAX_CONFIDENCE
44+ from vulnerabilities .improver import Improver
45+ from vulnerabilities .improver import Inference
46+ from vulnerabilities .models import Advisory
3847
48+ logger = logging .getLogger (__name__ )
3949
40- class DebianImporter (Importer ):
41- def __enter__ (self ):
42- if self .response_is_new ():
43- self ._api_response = self ._fetch ()
4450
45- else :
46- self ._api_response = {}
51+ class DebianImporter (Importer ):
4752
48- def updated_advisories ( self ) -> Set [ AdvisoryData ]:
49- advisories = []
53+ spdx_license_expression = "MIT"
54+ license_url = "https://www.debian.org/license"
5055
51- for pkg_name , records in self ._api_response .items ():
52- advisories .extend (self ._parse (pkg_name , records ))
56+ api_url = "https://security-tracker.debian.org/tracker/data/json"
5357
54- return self .batch_advisories (advisories )
58+ def get_response (self ):
59+ return requests .get (self .api_url ).json ()
5560
56- def _fetch (self ) -> Mapping [str , Any ]:
57- return requests .get (self .config .debian_tracker_url ).json ()
61+ def advisory_data (self ) -> Iterable [AdvisoryData ]:
62+ response = self .get_response ()
63+ print ("got response" )
64+ for pkg_name , records in response .items ():
65+ yield from self .parse (pkg_name , records )
5866
59- def _parse (self , pkg_name : str , records : Mapping [str , Any ]) -> List [AdvisoryData ]:
60- advisories = []
67+ def parse (self , pkg_name : str , records : Mapping [str , Any ]) -> Iterable [AdvisoryData ]:
68+ print ( "parsing" )
6169 ignored_versions = {"3.8.20-4." }
6270
6371 for cve_id , record in records .items ():
64- impacted_purls , resolved_purls = [], []
72+ affected_versions , fixed_versions = set (), set ()
6573 if not cve_id .startswith ("CVE" ):
6674 continue
6775
@@ -70,10 +78,10 @@ def _parse(self, pkg_name: str, records: Mapping[str, Any]) -> List[AdvisoryData
7078 # TODO: this would need to be revisited though to ensure we are not missing out on anything
7179
7280 for release_name , release_record in record ["releases" ].items ():
73- if not release_record .get ("repositories" , {}).get (release_name ):
74- continue
81+ version = get_item (release_record , "repositories" , release_name )
7582
76- version = release_record ["repositories" ][release_name ]
83+ if not version :
84+ continue
7785
7886 if version in ignored_versions :
7987 continue
@@ -82,54 +90,109 @@ def _parse(self, pkg_name: str, records: Mapping[str, Any]) -> List[AdvisoryData
8290 name = pkg_name ,
8391 type = "deb" ,
8492 namespace = "debian" ,
85- version = version ,
8693 qualifiers = {"distro" : release_name },
8794 )
8895
8996 if release_record .get ("status" , "" ) == "resolved" :
90- resolved_purls . append ( purl )
97+ fixed_versions . add ( version )
9198 else :
92- impacted_purls . append ( purl )
99+ affected_versions . add ( version )
93100
94101 if (
95102 "fixed_version" in release_record
96103 and release_record ["fixed_version" ] not in ignored_versions
97104 ):
98- resolved_purls .append (
99- PackageURL (
100- name = pkg_name ,
101- type = "deb" ,
102- namespace = "debian" ,
103- version = release_record ["fixed_version" ],
104- qualifiers = {"distro" : release_name },
105- )
106- )
105+ fixed_versions .add (release_record ["fixed_version" ])
107106
108107 references = []
109108 debianbug = record .get ("debianbug" )
110109 if debianbug :
111110 bug_url = f"https://bugs.debian.org/cgi-bin/bugreport.cgi?bug={ debianbug } "
112111 references .append (Reference (url = bug_url , reference_id = debianbug ))
113- advisories .append (
114- AdvisoryData (
115- vulnerability_id = cve_id ,
116- affected_packages = nearest_patched_package (impacted_purls , resolved_purls ),
117- summary = record .get ("description" , "" ),
118- references = references ,
112+ affected_version_range = (
113+ DebianVersionRange .from_versions (list (affected_versions ))
114+ if affected_versions
115+ else None
116+ )
117+ affected_packages = []
118+ for fixed_version in fixed_versions :
119+ affected_packages .append (
120+ AffectedPackage (
121+ package = purl ,
122+ affected_version_range = affected_version_range ,
123+ fixed_version = fixed_version ,
124+ )
119125 )
126+ yield AdvisoryData (
127+ aliases = [cve_id ],
128+ summary = record .get ("description" , "" ),
129+ affected_packages = affected_packages ,
130+ references = references ,
120131 )
121132
122- return advisories
123133
124- def response_is_new (self ):
134+ class DebianBasicImprover (Improver ):
135+ @property
136+ def interesting_advisories (self ) -> QuerySet :
137+ return Advisory .objects .filter (created_by = DebianImporter .qualified_name )
138+
139+ def get_inferences (self , advisory_data : AdvisoryData ) -> Iterable [Inference ]:
125140 """
126- Return True if a request response is for new data likely changed or
127- updated since we last checked.
141+ Yield Inferences for the given advisory data
128142 """
129- head = requests .head (self .config .debian_tracker_url )
130- date_str = head .headers .get ("last-modified" )
131- last_modified_date = dateparser .parse (date_str )
132- if self .config .last_run_date :
133- return self .config .last_run_date < last_modified_date
143+ if not advisory_data .affected_packages :
144+ return Inference (
145+ aliases = advisory_data .aliases ,
146+ confidence = MAX_CONFIDENCE ,
147+ summary = advisory_data .summary ,
148+ affected_purls = [],
149+ fixed_purl = None ,
150+ references = advisory_data .references ,
151+ )
152+ try :
153+ purl , affected_version_ranges , fixed_versions = AffectedPackage .merge (
154+ advisory_data .affected_packages
155+ )
156+ except UnMergeablePackageError :
157+ logger .error (f"Cannot merge with different purls { advisory_data .affected_packages !r} " )
158+ return iter ([])
159+
160+ pkg_type = purl .type
161+ pkg_namespace = purl .namespace
162+ pkg_name = purl .name
163+ aff_versions = set ()
164+ for affected_version_range in affected_version_ranges :
165+ for constraint in affected_version_range .constraints :
166+ aff_versions .add (constraint .version .string )
167+ affected_purls = [
168+ PackageURL (type = pkg_type , namespace = pkg_namespace , name = pkg_name , version = version )
169+ for version in aff_versions
170+ ]
171+
172+ unaffected_purls = [
173+ PackageURL (type = pkg_type , namespace = pkg_namespace , name = pkg_name , version = str (version ))
174+ for version in fixed_versions
175+ ]
176+
177+ affected_packages : List [LegacyAffectedPackage ] = nearest_patched_package (
178+ vulnerable_packages = affected_purls , resolved_packages = unaffected_purls
179+ )
180+
181+ unique_patched_packages_with_affected_packages = {}
182+ for package in affected_packages :
183+ if package .patched_package not in unique_patched_packages_with_affected_packages :
184+ unique_patched_packages_with_affected_packages [package .patched_package ] = []
185+ unique_patched_packages_with_affected_packages [package .patched_package ].append (
186+ package .vulnerable_package
187+ )
134188
135- return True
189+ for (
190+ fixed_package ,
191+ affected_packages ,
192+ ) in unique_patched_packages_with_affected_packages .items ():
193+ yield Inference .from_advisory_data (
194+ advisory_data ,
195+ confidence = MAX_CONFIDENCE , # We are getting all valid versions to get this inference
196+ affected_purls = affected_packages ,
197+ fixed_purl = fixed_package ,
198+ )
0 commit comments