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 = "git+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 (repo_url = self .repo_url )
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,17 +87,20 @@ 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 :
9995 if info .text in skip_versions :
10096 continue
101- pkg_ns , pkg_name , = pkg .attrib [
102- "name"
103- ].split ("/" )
104- purl = PackageURL (type = "ebuild" , name = pkg_name , version = info .text , namespace = pkg_ns )
97+ name = pkg .attrib .get ("name" )
98+ if name :
99+ (
100+ pkg_ns ,
101+ pkg_name ,
102+ ) = name .split ("/" )
103+ purl = PackageURL (type = "ebuild" , name = pkg_name , namespace = pkg_ns )
105104
106105 if info .attrib .get ("range" ):
107106 if len (info .attrib .get ("range" )) > 2 :
@@ -117,14 +116,28 @@ def affected_and_safe_purls(affected_elem):
117116 # 'release' not the 'version'.
118117
119118 if "e" in info .attrib ["range" ]:
120- safe_purls .add (purl )
119+ safe_versions .add (info . text )
121120 else :
122- affected_purls .add (purl )
121+ affected_versions .add (info . text )
123122
124123 elif info .tag == "vulnerable" :
125124 if "e" in info .attrib ["range" ]:
126- affected_purls .add (purl )
125+ affected_versions .add (info . text )
127126 else :
128- safe_purls .add (purl )
127+ safe_versions .add (info .text )
128+
129+ constraints = []
130+
131+ for version in safe_versions :
132+ constraints .append (
133+ VersionConstraint (version = GentooVersion (version ), comparator = "=" ).invert ()
134+ )
135+
136+ for version in affected_versions :
137+ constraints .append (
138+ VersionConstraint (version = GentooVersion (version ), comparator = "=" )
139+ )
129140
130- return (affected_purls , safe_purls )
141+ yield AffectedPackage (
142+ package = purl , affected_version_range = EbuildVersionRange (constraints = constraints )
143+ )
0 commit comments