1010
1111import re
1212import xml .etree .ElementTree as ET
13- from typing import Set
13+ from pathlib import Path
14+ from typing import Iterable
1415
1516from packageurl import PackageURL
17+ from univers .version_constraint import VersionConstraint
18+ from univers .version_range import EbuildVersionRange
19+ from univers .versions import GentooVersion
1620
1721from vulnerabilities .importer import AdvisoryData
18- from vulnerabilities .importer import GitImporter
22+ from vulnerabilities .importer import AffectedPackage
23+ from vulnerabilities .importer import Importer
1924from vulnerabilities .importer import Reference
20- from vulnerabilities .utils import nearest_patched_package
2125
2226
23- class GentooImporter (GitImporter ):
24- def __enter__ (self ):
25- super (GentooImporter , self ).__enter__ ()
27+ class GentooImporter (Importer ):
28+ repo_url = "https://anongit.gentoo.org/git/data/glsa.git"
29+ spdx_license_expression = "CC-BY-SA-4.0"
30+ license_url = "https://anongit.gentoo.org/"
2631
27- if not getattr (self , "_added_files" , None ):
28- self ._added_files , self ._updated_files = self .file_changes (
29- recursive = True , file_ext = "xml"
30- )
31-
32- def updated_advisories (self ) -> Set [AdvisoryData ]:
33- files = self ._updated_files .union (self ._added_files )
34- advisories = []
35- for f in files :
36- processed_data = self .process_file (f )
37- advisories .extend (processed_data )
38- return self .batch_advisories (advisories )
32+ def advisory_data (self ) -> Iterable [AdvisoryData ]:
33+ try :
34+ self .clone ()
35+ base_path = Path (self .vcs_response .dest_dir )
36+ for file_path in base_path .glob ("**/*.xml" ):
37+ yield from self .process_file (file_path )
38+ finally :
39+ if self .vcs_response :
40+ self .vcs_response .delete ()
3941
4042 def process_file (self , file ):
41- xml_data = {}
43+ cves = []
44+ summary = ""
45+ vuln_reference = []
4246 xml_root = ET .parse (file ).getroot ()
43- glsa = "GLSA-" + xml_root .attrib ["id" ]
44- vuln_reference = [
45- Reference (
46- reference_id = glsa ,
47- url = "https://security.gentoo.org/glsa/{}" .format (xml_root .attrib ["id" ]),
48- )
49- ]
47+ id = xml_root .attrib .get ("id" )
48+ if id :
49+ glsa = "GLSA-" + id
50+ vuln_reference = [
51+ Reference (
52+ reference_id = glsa ,
53+ url = f"https://security.gentoo.org/glsa/{ id } " ,
54+ )
55+ ]
5056
5157 for child in xml_root :
5258 if child .tag == "references" :
53- xml_data [ " cves" ] = self .cves_from_reference (child )
59+ cves = self .cves_from_reference (child )
5460
5561 if child .tag == "synopsis" :
56- xml_data [ "description" ] = child .text
62+ summary = child .text
5763
5864 if child .tag == "affected" :
59- (
60- xml_data ["affected_purls" ],
61- xml_data ["unaffected_purls" ],
62- ) = self .affected_and_safe_purls (child )
63- xml_data ["unaffected_purls" ] = list (xml_data ["unaffected_purls" ])
64- xml_data ["affected_purls" ] = list (xml_data ["affected_purls" ])
65-
66- advisory_list = []
65+ affected_packages = list (self .affected_and_safe_purls (child ))
66+
6767 # It is very inefficient, to create new Advisory for each CVE
6868 # this way, but there seems no alternative.
69- for cve in xml_data ["cves" ]:
70- advisory = AdvisoryData (
71- vulnerability_id = cve ,
72- summary = xml_data ["description" ],
73- affected_packages = nearest_patched_package (
74- xml_data ["affected_purls" ], xml_data ["unaffected_purls" ]
75- ),
69+ for cve in cves :
70+ yield AdvisoryData (
71+ aliases = [cve ],
72+ summary = summary ,
7673 references = vuln_reference ,
74+ affected_packages = affected_packages ,
7775 )
78- advisory_list .append (advisory )
79- return advisory_list
8076
8177 @staticmethod
8278 def cves_from_reference (reference ):
@@ -91,8 +87,8 @@ def cves_from_reference(reference):
9187
9288 @staticmethod
9389 def affected_and_safe_purls (affected_elem ):
94- safe_purls = set ()
95- affected_purls = set ()
90+ safe_versions = set ()
91+ affected_versions = set ()
9692 skip_versions = {"1.3*" , "7.3*" , "7.4*" }
9793 for pkg in affected_elem :
9894 for info in pkg :
@@ -101,7 +97,7 @@ def affected_and_safe_purls(affected_elem):
10197 pkg_ns , pkg_name , = pkg .attrib [
10298 "name"
10399 ].split ("/" )
104- purl = PackageURL (type = "ebuild" , name = pkg_name , version = info . text , namespace = pkg_ns )
100+ purl = PackageURL (type = "ebuild" , name = pkg_name , namespace = pkg_ns )
105101
106102 if info .attrib .get ("range" ):
107103 if len (info .attrib .get ("range" )) > 2 :
@@ -117,14 +113,28 @@ def affected_and_safe_purls(affected_elem):
117113 # 'release' not the 'version'.
118114
119115 if "e" in info .attrib ["range" ]:
120- safe_purls .add (purl )
116+ safe_versions .add (info . text )
121117 else :
122- affected_purls .add (purl )
118+ affected_versions .add (info . text )
123119
124120 elif info .tag == "vulnerable" :
125121 if "e" in info .attrib ["range" ]:
126- affected_purls .add (purl )
122+ affected_versions .add (info . text )
127123 else :
128- safe_purls .add (purl )
124+ safe_versions .add (info .text )
125+
126+ constraints = []
127+
128+ for version in safe_versions :
129+ constraints .append (
130+ VersionConstraint (version = GentooVersion (version ), comparator = "=" ).invert ()
131+ )
132+
133+ for version in affected_versions :
134+ constraints .append (
135+ VersionConstraint (version = GentooVersion (version ), comparator = "=" )
136+ )
129137
130- return (affected_purls , safe_purls )
138+ yield AffectedPackage (
139+ package = purl , affected_version_range = EbuildVersionRange (constraints = constraints )
140+ )
0 commit comments