Skip to content

Commit c939ffb

Browse files
authored
Merge pull request #1056 from TG1999/migrate/gentoo
Migrate gentoo importer #1055
2 parents 1e2a495 + d5c07d0 commit c939ffb

File tree

6 files changed

+145
-176
lines changed

6 files changed

+145
-176
lines changed

CHANGELOG.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ Next release
66
----------------
77

88
- We re-enabled support for the mozilla vulnerabilities advisories importer.
9+
- We re-enabled support for the gentoo vulnerabilities advisories importer.
910

1011

1112
Version v31.1.1

vulnerabilities/importers/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from vulnerabilities.importers import archlinux
1313
from vulnerabilities.importers import debian
1414
from vulnerabilities.importers import debian_oval
15+
from vulnerabilities.importers import gentoo
1516
from vulnerabilities.importers import github
1617
from vulnerabilities.importers import gitlab
1718
from vulnerabilities.importers import mozilla
@@ -45,6 +46,7 @@
4546
retiredotnet.RetireDotnetImporter,
4647
apache_httpd.ApacheHTTPDImporter,
4748
mozilla.MozillaImporter,
49+
gentoo.GentooImporter,
4850
]
4951

5052
IMPORTERS_REGISTRY = {x.qualified_name: x for x in IMPORTERS_REGISTRY}

vulnerabilities/importers/gentoo.py

Lines changed: 104 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -10,73 +10,72 @@
1010

1111
import re
1212
import xml.etree.ElementTree as ET
13-
from typing import Set
13+
from pathlib import Path
14+
from typing import Iterable
1415

1516
from packageurl import PackageURL
17+
from univers.version_constraint import VersionConstraint
18+
from univers.version_range import EbuildVersionRange
19+
from univers.versions import GentooVersion
1620

1721
from vulnerabilities.importer import AdvisoryData
18-
from vulnerabilities.importer import GitImporter
22+
from vulnerabilities.importer import AffectedPackage
23+
from vulnerabilities.importer import Importer
1924
from 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__()
26-
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)
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+
# the license notice is at this url https://anongit.gentoo.org/ says:
31+
# The contents of this document, unless otherwise expressly stated, are licensed
32+
# under the [CC-BY-SA-4.0](https://creativecommons.org/licenses/by-sa/4.0/) license.
33+
license_url = "https://creativecommons.org/licenses/by-sa/4.0/"
34+
35+
def advisory_data(self) -> Iterable[AdvisoryData]:
36+
try:
37+
self.clone(repo_url=self.repo_url)
38+
base_path = Path(self.vcs_response.dest_dir)
39+
for file_path in base_path.glob("**/*.xml"):
40+
yield from self.process_file(file_path)
41+
finally:
42+
if self.vcs_response:
43+
self.vcs_response.delete()
3944

4045
def process_file(self, file):
41-
xml_data = {}
46+
cves = []
47+
summary = ""
48+
vuln_references = []
4249
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-
]
50+
id = xml_root.attrib.get("id")
51+
if id:
52+
glsa = "GLSA-" + id
53+
vuln_references = [
54+
Reference(
55+
reference_id=glsa,
56+
url=f"https://security.gentoo.org/glsa/{id}",
57+
)
58+
]
5059

5160
for child in xml_root:
5261
if child.tag == "references":
53-
xml_data["cves"] = self.cves_from_reference(child)
62+
cves = self.cves_from_reference(child)
5463

5564
if child.tag == "synopsis":
56-
xml_data["description"] = child.text
65+
summary = child.text
5766

5867
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 = []
68+
affected_packages = list(self.affected_and_safe_purls(child))
69+
6770
# It is very inefficient, to create new Advisory for each CVE
6871
# 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-
),
76-
references=vuln_reference,
72+
for cve in cves:
73+
yield AdvisoryData(
74+
aliases=[cve],
75+
summary=summary,
76+
references=vuln_references,
77+
affected_packages=affected_packages,
7778
)
78-
advisory_list.append(advisory)
79-
return advisory_list
8079

8180
@staticmethod
8281
def cves_from_reference(reference):
@@ -91,40 +90,63 @@ def cves_from_reference(reference):
9190

9291
@staticmethod
9392
def affected_and_safe_purls(affected_elem):
94-
safe_purls = set()
95-
affected_purls = set()
96-
skip_versions = {"1.3*", "7.3*", "7.4*"}
93+
constraints = []
9794
for pkg in affected_elem:
98-
for info in pkg:
99-
if info.text in skip_versions:
95+
name = pkg.attrib.get("name")
96+
if not name:
97+
continue
98+
pkg_ns, _, pkg_name = name.rpartition("/")
99+
purl = PackageURL(type="ebuild", name=pkg_name, namespace=pkg_ns)
100+
safe_versions, affected_versions = GentooImporter.get_safe_and_affected_versions(pkg)
101+
102+
for version in safe_versions:
103+
constraints.append(
104+
VersionConstraint(version=GentooVersion(version), comparator="=").invert()
105+
)
106+
107+
for version in affected_versions:
108+
constraints.append(
109+
VersionConstraint(version=GentooVersion(version), comparator="=")
110+
)
111+
112+
if not constraints:
113+
continue
114+
115+
yield AffectedPackage(
116+
package=purl, affected_version_range=EbuildVersionRange(constraints=constraints)
117+
)
118+
119+
@staticmethod
120+
def get_safe_and_affected_versions(pkg):
121+
# TODO : Revisit why we are skipping some versions in gentoo importer
122+
skip_versions = {"1.3*", "7.3*", "7.4*"}
123+
safe_versions = set()
124+
affected_versions = set()
125+
for info in pkg:
126+
if info.text in skip_versions:
127+
continue
128+
129+
if info.attrib.get("range"):
130+
if len(info.attrib.get("range")) > 2:
100131
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)
105-
106-
if info.attrib.get("range"):
107-
if len(info.attrib.get("range")) > 2:
108-
continue
109-
110-
if info.tag == "unaffected":
111-
# quick hack, to know whether this
112-
# version lies in this range, 'e' stands for
113-
# equal, which is paired with 'greater' or 'less'.
114-
# All possible values of info.attrib['range'] =
115-
# {'gt', 'lt', 'rle', 'rge', 'rgt', 'le', 'ge', 'eq'}, out of
116-
# which ('rle', 'rge', 'rgt') are ignored, because they compare
117-
# 'release' not the 'version'.
118-
119-
if "e" in info.attrib["range"]:
120-
safe_purls.add(purl)
121-
else:
122-
affected_purls.add(purl)
123-
124-
elif info.tag == "vulnerable":
125-
if "e" in info.attrib["range"]:
126-
affected_purls.add(purl)
127-
else:
128-
safe_purls.add(purl)
129-
130-
return (affected_purls, safe_purls)
132+
133+
if info.tag == "unaffected":
134+
# quick hack, to know whether this
135+
# version lies in this range, 'e' stands for
136+
# equal, which is paired with 'greater' or 'less'.
137+
# All possible values of info.attrib['range'] =
138+
# {'gt', 'lt', 'rle', 'rge', 'rgt', 'le', 'ge', 'eq'}, out of
139+
# which ('rle', 'rge', 'rgt') are ignored, because they compare
140+
# 'release' not the 'version'.
141+
if "e" in info.attrib["range"]:
142+
safe_versions.add(info.text)
143+
else:
144+
affected_versions.add(info.text)
145+
146+
elif info.tag == "vulnerable":
147+
if "e" in info.attrib["range"]:
148+
affected_versions.add(info.text)
149+
else:
150+
safe_versions.add(info.text)
151+
152+
return safe_versions, affected_versions

vulnerabilities/tests/conftest.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ def no_rmtree(monkeypatch):
2929
"test_apache_tomcat.py",
3030
"test_api.py",
3131
"test_elixir_security.py",
32-
"test_gentoo.py",
3332
"test_istio.py",
3433
"test_models.py",
3534
"test_msr2019.py",
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
[
2+
{
3+
"aliases": [
4+
"CVE-2017-9800"
5+
],
6+
"summary": "A command injection vulnerability in Subversion may allow remote\n attackers to execute arbitrary code.\n ",
7+
"affected_packages": [
8+
{
9+
"package": {
10+
"type": "ebuild",
11+
"namespace": "dev-vcs",
12+
"name": "subversion",
13+
"version": null,
14+
"qualifiers": null,
15+
"subpath": null
16+
},
17+
"affected_version_range": "vers:ebuild/0.1.1|!=1.9.7",
18+
"fixed_version": null
19+
}
20+
],
21+
"references": [
22+
{
23+
"reference_id": "GLSA-201709-09",
24+
"url": "https://security.gentoo.org/glsa/201709-09",
25+
"severities": []
26+
}
27+
],
28+
"date_published": null
29+
}
30+
]

vulnerabilities/tests/test_gentoo.py

Lines changed: 8 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -19,101 +19,16 @@
1919
from vulnerabilities.importer import AdvisoryData
2020
from vulnerabilities.importer import Reference
2121
from vulnerabilities.importers.gentoo import GentooImporter
22+
from vulnerabilities.tests import util_tests
2223
from vulnerabilities.utils import AffectedPackage
2324

2425
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
25-
TEST_DATA = os.path.join(BASE_DIR, "test_data/gentoo/glsa-201709-09.xml")
26+
TEST_DIR = os.path.join(BASE_DIR, "test_data/gentoo")
2627

2728

28-
class TestGentooImporter(unittest.TestCase):
29-
@classmethod
30-
def setUpClass(cls):
31-
data_source_cfg = {
32-
"repository_url": "https://example.git",
33-
}
34-
cls.data_src = GentooImporter(1, config=data_source_cfg)
35-
cls.xml_doc = ET.parse(TEST_DATA)
36-
cls.references = []
37-
for child in cls.xml_doc.getroot():
38-
39-
if child.tag == "references":
40-
cls.references.append(child)
41-
42-
if child.tag == "affected":
43-
cls.affected = child
44-
45-
def test_affected_and_safe_purls(self):
46-
exp_affected = {
47-
PackageURL(
48-
type="ebuild",
49-
namespace="dev-vcs",
50-
name="subversion",
51-
version="0.1.1",
52-
qualifiers=OrderedDict(),
53-
subpath=None,
54-
)
55-
}
56-
exp_safe = {
57-
PackageURL(
58-
type="ebuild",
59-
namespace="dev-vcs",
60-
name="subversion",
61-
version="1.9.7",
62-
qualifiers=OrderedDict(),
63-
subpath=None,
64-
)
65-
}
66-
67-
aff, safe = GentooImporter.affected_and_safe_purls(self.affected)
68-
69-
assert aff == exp_affected
70-
assert safe == exp_safe
71-
72-
def test_cves_from_reference(self):
73-
74-
exp_cves = {"CVE-2017-9800"}
75-
found_cves = set()
76-
for ref in self.references:
77-
found_cves.update(GentooImporter.cves_from_reference(ref))
78-
79-
assert exp_cves == found_cves
80-
81-
def test_process_file(self):
82-
83-
expected_advisories = [
84-
Advisory(
85-
summary=(
86-
"A command injection vulnerability in "
87-
"Subversion may allow remote\n "
88-
"attackers to execute arbitrary code.\n "
89-
),
90-
affected_packages=[
91-
AffectedPackage(
92-
vulnerable_package=PackageURL(
93-
type="ebuild",
94-
namespace="dev-vcs",
95-
name="subversion",
96-
version="0.1.1",
97-
),
98-
patched_package=PackageURL(
99-
type="ebuild",
100-
namespace="dev-vcs",
101-
name="subversion",
102-
version="1.9.7",
103-
),
104-
)
105-
],
106-
references=[
107-
Reference(
108-
url="https://security.gentoo.org/glsa/201709-09",
109-
reference_id="GLSA-201709-09",
110-
)
111-
],
112-
vulnerability_id="CVE-2017-9800",
113-
)
114-
]
115-
116-
found_advisories = self.data_src.process_file(TEST_DATA)
117-
found_advisories = list(map(Advisory.normalized, found_advisories))
118-
expected_advisories = list(map(Advisory.normalized, expected_advisories))
119-
assert sorted(found_advisories) == sorted(expected_advisories)
29+
def test_gentoo_import():
30+
file = os.path.join(TEST_DIR, "glsa-201709-09.xml")
31+
advisories = GentooImporter().process_file(file)
32+
result = [adv.to_dict() for adv in advisories]
33+
expected_file = os.path.join(TEST_DIR, "gentoo-expected.json")
34+
util_tests.check_results_against_json(result, expected_file)

0 commit comments

Comments
 (0)