Skip to content

Commit b8f3936

Browse files
authored
Merge pull request #2090 from ziadhany/gentoo-migration
Migrate Gentoo importer to advisory V2
2 parents 485f3e2 + 0ab3419 commit b8f3936

File tree

10 files changed

+686
-8
lines changed

10 files changed

+686
-8
lines changed

vulnerabilities/importers/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
)
5656
from vulnerabilities.pipelines.v2_importers import epss_importer_v2
5757
from vulnerabilities.pipelines.v2_importers import fireeye_importer_v2
58+
from vulnerabilities.pipelines.v2_importers import gentoo_importer as gentoo_importer_v2
5859
from vulnerabilities.pipelines.v2_importers import github_osv_importer as github_osv_importer_v2
5960
from vulnerabilities.pipelines.v2_importers import gitlab_importer as gitlab_importer_v2
6061
from vulnerabilities.pipelines.v2_importers import istio_importer as istio_importer_v2
@@ -108,6 +109,7 @@
108109
project_kb_msr2019_importer_v2.ProjectKBMSR2019Pipeline,
109110
ruby_importer_v2.RubyImporterPipeline,
110111
epss_importer_v2.EPSSImporterPipeline,
112+
gentoo_importer_v2.GentooImporterPipeline,
111113
nginx_importer_v2.NginxImporterPipeline,
112114
debian_importer_v2.DebianImporterPipeline,
113115
mattermost_importer_v2.MattermostImporterPipeline,

vulnerabilities/importers/gentoo.py

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@
66
# See https://github.com/aboutcode-org/vulnerablecode for support or download.
77
# See https://aboutcode.org for more information about nexB OSS projects.
88
#
9-
10-
9+
import logging
1110
import re
1211
import xml.etree.ElementTree as ET
1312
from pathlib import Path
@@ -17,12 +16,15 @@
1716
from univers.version_constraint import VersionConstraint
1817
from univers.version_range import EbuildVersionRange
1918
from univers.versions import GentooVersion
19+
from univers.versions import InvalidVersion
2020

2121
from vulnerabilities.importer import AdvisoryData
2222
from vulnerabilities.importer import AffectedPackage
2323
from vulnerabilities.importer import Importer
2424
from vulnerabilities.importer import Reference
2525

26+
logger = logging.getLogger(__name__)
27+
2628

2729
class GentooImporter(Importer):
2830
repo_url = "git+https://anongit.gentoo.org/git/data/glsa.git"
@@ -104,14 +106,20 @@ def affected_and_safe_purls(affected_elem):
104106
safe_versions, affected_versions = GentooImporter.get_safe_and_affected_versions(pkg)
105107

106108
for version in safe_versions:
107-
constraints.append(
108-
VersionConstraint(version=GentooVersion(version), comparator="=").invert()
109-
)
109+
try:
110+
constraints.append(
111+
VersionConstraint(version=GentooVersion(version), comparator="=").invert()
112+
)
113+
except InvalidVersion as e:
114+
logger.error(f"Invalid safe_version {version} - error: {e}")
110115

111116
for version in affected_versions:
112-
constraints.append(
113-
VersionConstraint(version=GentooVersion(version), comparator="=")
114-
)
117+
try:
118+
constraints.append(
119+
VersionConstraint(version=GentooVersion(version), comparator="=")
120+
)
121+
except InvalidVersion as e:
122+
logger.error(f"Invalid affected_version {version} - error: {e}")
115123

116124
if not constraints:
117125
continue
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
#
2+
# Copyright (c) nexB Inc. and others. All rights reserved.
3+
# VulnerableCode is a trademark of nexB Inc.
4+
# SPDX-License-Identifier: Apache-2.0
5+
# See http://www.apache.org/licenses/LICENSE-2.0 for the license text.
6+
# See https://github.com/aboutcode-org/vulnerablecode for support or download.
7+
# See https://aboutcode.org for more information about nexB OSS projects.
8+
#
9+
10+
import re
11+
import xml.etree.ElementTree as ET
12+
from pathlib import Path
13+
from typing import Iterable
14+
15+
from fetchcode.vcs import fetch_via_vcs
16+
from packageurl import PackageURL
17+
from univers.version_constraint import VersionConstraint
18+
from univers.version_range import EbuildVersionRange
19+
from univers.versions import GentooVersion
20+
from univers.versions import InvalidVersion
21+
22+
from vulnerabilities.importer import AdvisoryDataV2
23+
from vulnerabilities.importer import AffectedPackageV2
24+
from vulnerabilities.importer import ReferenceV2
25+
from vulnerabilities.importer import VulnerabilitySeverity
26+
from vulnerabilities.pipelines import VulnerableCodeBaseImporterPipelineV2
27+
from vulnerabilities.severity_systems import GENERIC
28+
29+
30+
class GentooImporterPipeline(VulnerableCodeBaseImporterPipelineV2):
31+
repo_url = "git+https://anongit.gentoo.org/git/data/glsa.git"
32+
spdx_license_expression = "CC-BY-SA-4.0"
33+
# the license notice is at this url https://anongit.gentoo.org/ says:
34+
# The contents of this document, unless otherwise expressly stated, are licensed
35+
# under the [CC-BY-SA-4.0](https://creativecommons.org/licenses/by-sa/4.0/) license.
36+
license_url = "https://creativecommons.org/licenses/by-sa/4.0/"
37+
pipeline_id = "gentoo_importer_v2"
38+
39+
@classmethod
40+
def steps(cls):
41+
return (
42+
cls.clone,
43+
cls.collect_and_store_advisories,
44+
cls.clean_downloads,
45+
)
46+
47+
def clone(self):
48+
self.log(f"Cloning `{self.repo_url}`")
49+
self.vcs_response = fetch_via_vcs(self.repo_url)
50+
51+
def advisories_count(self):
52+
advisory_dir = Path(self.vcs_response.dest_dir)
53+
return sum(1 for _ in advisory_dir.rglob("*.xml"))
54+
55+
def collect_advisories(self) -> Iterable[AdvisoryDataV2]:
56+
base_path = Path(self.vcs_response.dest_dir)
57+
for file_path in base_path.glob("**/*.xml"):
58+
yield from self.process_file(file_path)
59+
60+
def process_file(self, file):
61+
cves = []
62+
summary = ""
63+
xml_root = ET.parse(file).getroot()
64+
id = xml_root.attrib.get("id", "")
65+
glsa = "GLSA-" + id
66+
vuln_references = [
67+
ReferenceV2(
68+
reference_id=glsa,
69+
url=f"https://security.gentoo.org/glsa/{id}",
70+
)
71+
]
72+
73+
severities = []
74+
affected_packages = []
75+
for child in xml_root:
76+
if child.tag == "references":
77+
cves = self.cves_from_reference(child)
78+
79+
if child.tag == "synopsis":
80+
summary = child.text
81+
82+
if child.tag == "affected":
83+
affected_packages = []
84+
for purl, constraints, is_unaffected in get_affected_and_fixed_purls(
85+
child, logger=self.log
86+
):
87+
constraints = build_constraints([constraints], logger=self.log)
88+
version_range = EbuildVersionRange(constraints=constraints)
89+
90+
if is_unaffected:
91+
affected_package = AffectedPackageV2(
92+
package=purl,
93+
fixed_version_range=version_range,
94+
)
95+
else:
96+
affected_package = AffectedPackageV2(
97+
package=purl,
98+
affected_version_range=version_range,
99+
)
100+
101+
affected_packages.append(affected_package)
102+
103+
if child.tag == "impact":
104+
severity_value = child.attrib.get("type")
105+
if severity_value:
106+
severities.append(VulnerabilitySeverity(system=GENERIC, value=severity_value))
107+
108+
yield AdvisoryDataV2(
109+
advisory_id=glsa,
110+
aliases=cves,
111+
summary=summary,
112+
references=vuln_references,
113+
severities=severities,
114+
affected_packages=affected_packages,
115+
url=f"https://security.gentoo.org/glsa/{id}",
116+
original_advisory_text=file,
117+
)
118+
119+
def clean_downloads(self):
120+
if self.vcs_response:
121+
self.log("Removing cloned repository")
122+
self.vcs_response.delete()
123+
124+
def on_failure(self):
125+
self.clean_downloads()
126+
127+
@staticmethod
128+
def cves_from_reference(reference):
129+
cves = []
130+
for child in reference:
131+
txt = child.text.strip()
132+
match = re.match(r"CVE-\d{4}-\d{4,}", txt)
133+
if match:
134+
cves.append(match.group())
135+
return cves
136+
137+
138+
def build_constraints(constraint_pairs, logger):
139+
"""
140+
Build a list of VersionConstraint objects from comparators, versions pairs.
141+
"""
142+
constraints = []
143+
for comparator, version in constraint_pairs:
144+
try:
145+
constraint = VersionConstraint(version=GentooVersion(version), comparator=comparator)
146+
constraints.append(constraint)
147+
except InvalidVersion as e:
148+
logger(f"InvalidVersion constraints version: {version} error:{e}")
149+
return constraints
150+
151+
152+
def get_affected_and_fixed_purls(affected_elem, logger):
153+
"""
154+
Parses XML elements to extract PURLs associated with affected and fixed versions.
155+
"""
156+
157+
for pkg in affected_elem:
158+
name = pkg.attrib.get("name")
159+
if not name:
160+
continue
161+
162+
pkg_ns, _, pkg_name = name.rpartition("/")
163+
for info in pkg:
164+
# All possible values of info.attrib['range'] =
165+
# {'gt', 'lt', 'rle', 'rge', 'rgt', 'le', 'ge', 'eq'}
166+
# rge means revision greater than equals and rgt means revision greater than
167+
# TODO Revisit issue: https://github.com/aboutcode-org/vulnerablecode/issues/2180
168+
range_value = info.attrib.get("range")
169+
slot_value = info.attrib.get("slot")
170+
comparator_dict = {
171+
"gt": ">",
172+
"lt": "<",
173+
"ge": ">=",
174+
"le": "<=",
175+
"eq": "=",
176+
"rle": "<=",
177+
"rge": ">=",
178+
"rgt": ">",
179+
}
180+
comparator = comparator_dict.get(range_value)
181+
if not comparator:
182+
logger(f"Unsupported range value {range_value}:{info.text}")
183+
continue
184+
185+
qualifiers = {"slot": slot_value} if slot_value else {}
186+
purl = PackageURL(type="ebuild", name=pkg_name, namespace=pkg_ns, qualifiers=qualifiers)
187+
yield purl, (comparator, info.text), (info.tag == "unaffected")
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
#
2+
# Copyright (c) nexB Inc. and others. All rights reserved.
3+
# VulnerableCode is a trademark of nexB Inc.
4+
# SPDX-License-Identifier: Apache-2.0
5+
# See http://www.apache.org/licenses/LICENSE-2.0 for the license text.
6+
# See https://github.com/aboutcode-org/vulnerablecode for support or download.
7+
# See https://aboutcode.org for more information about nexB OSS projects.
8+
#
9+
import json
10+
from pathlib import Path
11+
from unittest.mock import Mock
12+
from unittest.mock import patch
13+
14+
import pytest
15+
16+
from vulnerabilities.pipelines.v2_importers.gentoo_importer import GentooImporterPipeline
17+
from vulnerabilities.tests import util_tests
18+
19+
TEST_DATA = Path(__file__).parent.parent.parent / "test_data" / "gentoo_v2"
20+
21+
TEST_CVE_FILES = [
22+
TEST_DATA / "glsa-201709-09.xml",
23+
TEST_DATA / "glsa-202511-02.xml",
24+
TEST_DATA / "glsa-202512-01.xml",
25+
]
26+
27+
28+
@pytest.mark.django_db
29+
@pytest.mark.parametrize("xml_file", TEST_CVE_FILES)
30+
def test_gentoo_advisories_per_file(xml_file):
31+
pipeline = GentooImporterPipeline()
32+
pipeline.vcs_response = Mock(dest_dir=TEST_DATA)
33+
34+
with patch.object(Path, "glob", return_value=[xml_file]):
35+
results = [adv.to_dict() for adv in pipeline.collect_advisories()]
36+
37+
for adv in results:
38+
adv["affected_packages"].sort(key=lambda x: json.dumps(x, sort_keys=True))
39+
40+
expected_file = xml_file.with_name(xml_file.stem + "-expected.json")
41+
util_tests.check_results_against_json(results, expected_file)
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
[
2+
{
3+
"advisory_id": "GLSA-201709-09",
4+
"aliases": [
5+
"CVE-2017-9800"
6+
],
7+
"summary": "A command injection vulnerability in Subversion may allow remote\n attackers to execute arbitrary code.",
8+
"affected_packages": [
9+
{
10+
"package": {
11+
"type": "ebuild",
12+
"namespace": "dev-vcs",
13+
"name": "subversion",
14+
"version": "",
15+
"qualifiers": "",
16+
"subpath": ""
17+
},
18+
"affected_version_range": "vers:ebuild/0.1.1",
19+
"fixed_version_range": null,
20+
"introduced_by_commit_patches": [],
21+
"fixed_by_commit_patches": []
22+
},
23+
{
24+
"package": {
25+
"type": "ebuild",
26+
"namespace": "dev-vcs",
27+
"name": "subversion",
28+
"version": "",
29+
"qualifiers": "",
30+
"subpath": ""
31+
},
32+
"affected_version_range": "vers:ebuild/<1.9.7",
33+
"fixed_version_range": null,
34+
"introduced_by_commit_patches": [],
35+
"fixed_by_commit_patches": []
36+
},
37+
{
38+
"package": {
39+
"type": "ebuild",
40+
"namespace": "dev-vcs",
41+
"name": "subversion",
42+
"version": "",
43+
"qualifiers": "",
44+
"subpath": ""
45+
},
46+
"affected_version_range": null,
47+
"fixed_version_range": "vers:ebuild/>1.8.18",
48+
"introduced_by_commit_patches": [],
49+
"fixed_by_commit_patches": []
50+
},
51+
{
52+
"package": {
53+
"type": "ebuild",
54+
"namespace": "dev-vcs",
55+
"name": "subversion",
56+
"version": "",
57+
"qualifiers": "",
58+
"subpath": ""
59+
},
60+
"affected_version_range": null,
61+
"fixed_version_range": "vers:ebuild/>=1.9.7",
62+
"introduced_by_commit_patches": [],
63+
"fixed_by_commit_patches": []
64+
}
65+
],
66+
"references": [
67+
{
68+
"reference_id": "GLSA-201709-09",
69+
"reference_type": "",
70+
"url": "https://security.gentoo.org/glsa/201709-09"
71+
}
72+
],
73+
"patches": [],
74+
"severities": [
75+
{
76+
"system": "generic_textual",
77+
"value": "normal",
78+
"scoring_elements": ""
79+
}
80+
],
81+
"date_published": null,
82+
"weaknesses": [],
83+
"url": "https://security.gentoo.org/glsa/201709-09"
84+
}
85+
]

0 commit comments

Comments
 (0)