From bc94910128150304fe8d9cba0d4c4c3da53724bf Mon Sep 17 00:00:00 2001 From: Tushar Goel Date: Mon, 18 Apr 2022 19:24:15 +0530 Subject: [PATCH] Allow default improver to improve without affected packages Signed-off-by: Tushar Goel --- vulnerabilities/importers/alpine_linux.py | 18 -- vulnerabilities/importers/nvd.py | 11 -- vulnerabilities/improver.py | 10 +- vulnerabilities/improvers/__init__.py | 2 - vulnerabilities/improvers/default.py | 28 ++- .../default_improver/alpine-expected.json | 184 ++++++++++++++++++ .../default_improver/alpine-input.json | 126 ++++++++++++ .../default_improver/nvd-expected.json | 29 +++ .../test_data/default_improver/nvd-input.json | 25 +++ .../tests/test_default_improver.py | 130 +++++++++++++ 10 files changed, 519 insertions(+), 44 deletions(-) create mode 100644 vulnerabilities/tests/test_data/default_improver/alpine-expected.json create mode 100644 vulnerabilities/tests/test_data/default_improver/alpine-input.json create mode 100644 vulnerabilities/tests/test_data/default_improver/nvd-expected.json create mode 100644 vulnerabilities/tests/test_data/default_improver/nvd-input.json create mode 100644 vulnerabilities/tests/test_default_improver.py diff --git a/vulnerabilities/importers/alpine_linux.py b/vulnerabilities/importers/alpine_linux.py index 350e906f0..069392d1a 100644 --- a/vulnerabilities/importers/alpine_linux.py +++ b/vulnerabilities/importers/alpine_linux.py @@ -245,21 +245,3 @@ def load_advisories( affected_packages=affected_packages, aliases=aliases, ) - - -class AlpineBasicImprover(Improver): - @property - def interesting_advisories(self) -> QuerySet: - return Advisory.objects.filter(created_by=AlpineImporter.qualified_name) - - def get_inferences(self, advisory_data: AdvisoryData) -> Iterable[Inference]: - """ - Generate and return Inferences for the given advisory data - """ - for affected_package in advisory_data.affected_packages: - fixed_purl = affected_package.get_fixed_purl() - yield Inference.from_advisory_data( - advisory_data, - confidence=MAX_CONFIDENCE, - fixed_purl=fixed_purl, - ) diff --git a/vulnerabilities/importers/nvd.py b/vulnerabilities/importers/nvd.py index 75a3a0332..48a2b9c02 100644 --- a/vulnerabilities/importers/nvd.py +++ b/vulnerabilities/importers/nvd.py @@ -195,14 +195,3 @@ def extract_severity_scores(cve_item): system=severity_systems.CVSSV2_VECTOR, value=str(cvss_v2.get("vectorString") or ""), ) - - -class NVDBasicImprover(Improver): - @property - def interesting_advisories(self) -> QuerySet: - return Advisory.objects.filter(created_by=NVDImporter.qualified_name) - - def get_inferences(self, advisory_data: AdvisoryData) -> Iterable[Inference]: - yield Inference.from_advisory_data( - advisory_data=advisory_data, confidence=100, fixed_purl=None - ) diff --git a/vulnerabilities/improver.py b/vulnerabilities/improver.py index 54a0a9b18..4d7ac1c84 100644 --- a/vulnerabilities/improver.py +++ b/vulnerabilities/improver.py @@ -67,12 +67,14 @@ def to_dict(self): """ return { "vulnerability_id": self.vulnerability_id, - "aliases": [alias for alias in self.aliases], + "aliases": [alias for alias in self.aliases or []], "confidence": self.confidence, "summary": self.summary, - "affected_purls": [affected_purl.to_dict() for affected_purl in self.affected_purls], - "fixed_purl": self.fixed_purl.to_dict(), - "references": [ref.to_dict() for ref in self.references], + "affected_purls": [ + affected_purl.to_dict() for affected_purl in self.affected_purls or [] + ], + "fixed_purl": self.fixed_purl.to_dict() if self.fixed_purl else None, + "references": [ref.to_dict() for ref in self.references or []], } @classmethod diff --git a/vulnerabilities/improvers/__init__.py b/vulnerabilities/improvers/__init__.py index fb9b8e145..327e093bf 100644 --- a/vulnerabilities/improvers/__init__.py +++ b/vulnerabilities/improvers/__init__.py @@ -27,9 +27,7 @@ IMPROVERS_REGISTRY = [ default.DefaultImprover, importers.nginx.NginxBasicImprover, - importers.alpine_linux.AlpineBasicImprover, importers.github.GitHubBasicImprover, - importers.nvd.NVDBasicImprover, ] IMPROVERS_REGISTRY = {x.qualified_name: x for x in IMPROVERS_REGISTRY} diff --git a/vulnerabilities/improvers/default.py b/vulnerabilities/improvers/default.py index 176b0bbc6..540eff3a8 100644 --- a/vulnerabilities/improvers/default.py +++ b/vulnerabilities/improvers/default.py @@ -50,15 +50,25 @@ def interesting_advisories(self) -> QuerySet: return Advisory.objects.all() def get_inferences(self, advisory_data: AdvisoryData) -> Iterable[Inference]: - for affected_package in advisory_data.affected_packages: - affected_purls, fixed_purl = get_exact_purls(affected_package) - yield Inference( - aliases=advisory_data.aliases, - confidence=MAX_CONFIDENCE, - summary=advisory_data.summary, - affected_purls=affected_purls, - fixed_purl=fixed_purl, - references=advisory_data.references, + + if not advisory_data: + return None + + if advisory_data.affected_packages: + for affected_package in advisory_data.affected_packages: + affected_purls, fixed_purl = get_exact_purls(affected_package) + yield Inference( + aliases=advisory_data.aliases, + confidence=MAX_CONFIDENCE, + summary=advisory_data.summary, + affected_purls=affected_purls, + fixed_purl=fixed_purl, + references=advisory_data.references, + ) + + else: + yield Inference.from_advisory_data( + advisory_data, confidence=MAX_CONFIDENCE, fixed_purl=None ) diff --git a/vulnerabilities/tests/test_data/default_improver/alpine-expected.json b/vulnerabilities/tests/test_data/default_improver/alpine-expected.json new file mode 100644 index 000000000..454e958c7 --- /dev/null +++ b/vulnerabilities/tests/test_data/default_improver/alpine-expected.json @@ -0,0 +1,184 @@ +[ + { + "vulnerability_id": null, + "aliases": [], + "confidence": 100, + "summary": null, + "affected_purls": [], + "fixed_purl": { + "type": "alpine", + "namespace": null, + "name": "xen", + "version": "4.10.0-r1", + "qualifiers": { + "arch": "aarch64", + "distroversion": "v3.11", + "reponame": "main" + }, + "subpath": null + }, + "references": [ + { + "reference_id": "XSA-248", + "url": "https://xenbits.xen.org/xsa/advisory-248.html", + "severities": [] + } + ] + }, + { + "vulnerability_id": null, + "aliases": [], + "confidence": 100, + "summary": null, + "affected_purls": [], + "fixed_purl": { + "type": "alpine", + "namespace": null, + "name": "xen", + "version": "4.10.0-r1", + "qualifiers": { + "arch": "armhf", + "distroversion": "v3.11", + "reponame": "main" + }, + "subpath": null + }, + "references": [ + { + "reference_id": "XSA-248", + "url": "https://xenbits.xen.org/xsa/advisory-248.html", + "severities": [] + } + ] + }, + { + "vulnerability_id": null, + "aliases": [], + "confidence": 100, + "summary": null, + "affected_purls": [], + "fixed_purl": { + "type": "alpine", + "namespace": null, + "name": "xen", + "version": "4.10.0-r1", + "qualifiers": { + "arch": "armv7", + "distroversion": "v3.11", + "reponame": "main" + }, + "subpath": null + }, + "references": [ + { + "reference_id": "XSA-248", + "url": "https://xenbits.xen.org/xsa/advisory-248.html", + "severities": [] + } + ] + }, + { + "vulnerability_id": null, + "aliases": [], + "confidence": 100, + "summary": null, + "affected_purls": [], + "fixed_purl": { + "type": "alpine", + "namespace": null, + "name": "xen", + "version": "4.10.0-r1", + "qualifiers": { + "arch": "ppc64le", + "distroversion": "v3.11", + "reponame": "main" + }, + "subpath": null + }, + "references": [ + { + "reference_id": "XSA-248", + "url": "https://xenbits.xen.org/xsa/advisory-248.html", + "severities": [] + } + ] + }, + { + "vulnerability_id": null, + "aliases": [], + "confidence": 100, + "summary": null, + "affected_purls": [], + "fixed_purl": { + "type": "alpine", + "namespace": null, + "name": "xen", + "version": "4.10.0-r1", + "qualifiers": { + "arch": "s390x", + "distroversion": "v3.11", + "reponame": "main" + }, + "subpath": null + }, + "references": [ + { + "reference_id": "XSA-248", + "url": "https://xenbits.xen.org/xsa/advisory-248.html", + "severities": [] + } + ] + }, + { + "vulnerability_id": null, + "aliases": [], + "confidence": 100, + "summary": null, + "affected_purls": [], + "fixed_purl": { + "type": "alpine", + "namespace": null, + "name": "xen", + "version": "4.10.0-r1", + "qualifiers": { + "arch": "x86", + "distroversion": "v3.11", + "reponame": "main" + }, + "subpath": null + }, + "references": [ + { + "reference_id": "XSA-248", + "url": "https://xenbits.xen.org/xsa/advisory-248.html", + "severities": [] + } + ] + }, + { + "vulnerability_id": null, + "aliases": [], + "confidence": 100, + "summary": null, + "affected_purls": [], + "fixed_purl": { + "type": "alpine", + "namespace": null, + "name": "xen", + "version": "4.10.0-r1", + "qualifiers": { + "arch": "x86_64", + "distroversion": "v3.11", + "reponame": "main" + }, + "subpath": null + }, + "references": [ + { + "reference_id": "XSA-248", + "url": "https://xenbits.xen.org/xsa/advisory-248.html", + "severities": [] + } + ] + } +] \ No newline at end of file diff --git a/vulnerabilities/tests/test_data/default_improver/alpine-input.json b/vulnerabilities/tests/test_data/default_improver/alpine-input.json new file mode 100644 index 000000000..7ede6c773 --- /dev/null +++ b/vulnerabilities/tests/test_data/default_improver/alpine-input.json @@ -0,0 +1,126 @@ +{ + "aliases": [], + "summary": null, + "affected_packages": [ + { + "package": { + "type": "alpine", + "namespace": null, + "name": "xen", + "version": null, + "qualifiers": { + "arch": "aarch64", + "distroversion": "v3.11", + "reponame": "main" + }, + "subpath": null + }, + "affected_version_range": null, + "fixed_version": "4.10.0-r1" + }, + { + "package": { + "type": "alpine", + "namespace": null, + "name": "xen", + "version": null, + "qualifiers": { + "arch": "armhf", + "distroversion": "v3.11", + "reponame": "main" + }, + "subpath": null + }, + "affected_version_range": null, + "fixed_version": "4.10.0-r1" + }, + { + "package": { + "type": "alpine", + "namespace": null, + "name": "xen", + "version": null, + "qualifiers": { + "arch": "armv7", + "distroversion": "v3.11", + "reponame": "main" + }, + "subpath": null + }, + "affected_version_range": null, + "fixed_version": "4.10.0-r1" + }, + { + "package": { + "type": "alpine", + "namespace": null, + "name": "xen", + "version": null, + "qualifiers": { + "arch": "ppc64le", + "distroversion": "v3.11", + "reponame": "main" + }, + "subpath": null + }, + "affected_version_range": null, + "fixed_version": "4.10.0-r1" + }, + { + "package": { + "type": "alpine", + "namespace": null, + "name": "xen", + "version": null, + "qualifiers": { + "arch": "s390x", + "distroversion": "v3.11", + "reponame": "main" + }, + "subpath": null + }, + "affected_version_range": null, + "fixed_version": "4.10.0-r1" + }, + { + "package": { + "type": "alpine", + "namespace": null, + "name": "xen", + "version": null, + "qualifiers": { + "arch": "x86", + "distroversion": "v3.11", + "reponame": "main" + }, + "subpath": null + }, + "affected_version_range": null, + "fixed_version": "4.10.0-r1" + }, + { + "package": { + "type": "alpine", + "namespace": null, + "name": "xen", + "version": null, + "qualifiers": { + "arch": "x86_64", + "distroversion": "v3.11", + "reponame": "main" + }, + "subpath": null + }, + "affected_version_range": null, + "fixed_version": "4.10.0-r1" + } + ], + "references": [ + { + "reference_id": "XSA-248", + "url": "https://xenbits.xen.org/xsa/advisory-248.html", + "severities": [] + } + ], + "date_published": null +} \ No newline at end of file diff --git a/vulnerabilities/tests/test_data/default_improver/nvd-expected.json b/vulnerabilities/tests/test_data/default_improver/nvd-expected.json new file mode 100644 index 000000000..26d889b3f --- /dev/null +++ b/vulnerabilities/tests/test_data/default_improver/nvd-expected.json @@ -0,0 +1,29 @@ +[ + { + "vulnerability_id": null, + "aliases": [ + "CVE-2005-4895" + ], + "confidence": 100, + "summary": "Multiple integer overflows in TCMalloc (tcmalloc.cc) in gperftools before 0.4 make it easier for context-dependent attackers to perform memory-related attacks such as buffer overflows via a large size value, which causes less memory to be allocated than expected.", + "affected_purls": [], + "fixed_purl": null, + "references": [ + { + "reference_id": "", + "url": "http://code.google.com/p/gperftools/source/browse/tags/perftools-0.4/ChangeLog", + "severities": [] + }, + { + "reference_id": "CVE-2005-4895", + "url": "https://nvd.nist.gov/vuln/detail/CVE-2005-4895", + "severities": [ + { + "system": "cvssv3.1", + "value": "HIGH" + } + ] + } + ] + } +] \ No newline at end of file diff --git a/vulnerabilities/tests/test_data/default_improver/nvd-input.json b/vulnerabilities/tests/test_data/default_improver/nvd-input.json new file mode 100644 index 000000000..80d17cf72 --- /dev/null +++ b/vulnerabilities/tests/test_data/default_improver/nvd-input.json @@ -0,0 +1,25 @@ +{ + "aliases": [ + "CVE-2005-4895" + ], + "summary": "Multiple integer overflows in TCMalloc (tcmalloc.cc) in gperftools before 0.4 make it easier for context-dependent attackers to perform memory-related attacks such as buffer overflows via a large size value, which causes less memory to be allocated than expected.", + "affected_packages": [], + "references": [ + { + "reference_id": "", + "url": "http://code.google.com/p/gperftools/source/browse/tags/perftools-0.4/ChangeLog", + "severities": [] + }, + { + "reference_id": "CVE-2005-4895", + "url": "https://nvd.nist.gov/vuln/detail/CVE-2005-4895", + "severities": [ + { + "system": "cvssv3.1", + "value": "HIGH" + } + ] + } + ], + "date_published": null +} \ No newline at end of file diff --git a/vulnerabilities/tests/test_default_improver.py b/vulnerabilities/tests/test_default_improver.py new file mode 100644 index 000000000..447b367e9 --- /dev/null +++ b/vulnerabilities/tests/test_default_improver.py @@ -0,0 +1,130 @@ +import json +import os + +from packageurl import PackageURL + +from vulnerabilities.importer import AdvisoryData +from vulnerabilities.importer import AffectedPackage +from vulnerabilities.importer import Reference +from vulnerabilities.improver import Inference +from vulnerabilities.improvers.default import DefaultImprover +from vulnerabilities.tests import util_tests + +BASE_DIR = os.path.dirname(os.path.abspath(__file__)) +TEST_DATA = os.path.join(BASE_DIR, "test_data", "default_improver") +VULNERABLECODE_REGEN_TEST_FIXTURES = os.getenv("VULNERABLECODE_REGEN_TEST_FIXTURES", False) + + +def test_default_improver_with_no_data(): + """ + Test that DefaultImprover.get_inferences() returns an empty list when given no data + """ + default_improver = DefaultImprover() + result = list(default_improver.get_inferences(None)) + expected = [] + assert result == expected + + +def test_default_improver_with_empty_affected_packages(): + """ + Test that DefaultImprover.get_inferences() returns an empty list when given an advisory with no affected packages + """ + advisory_data = AdvisoryData( + aliases=["CVE-2020-1234"], + summary="Test summary", + references=[ + Reference( + url="https://www.example.com/", + ) + ], + affected_packages=[], + ) + default_improver = DefaultImprover() + expected_inference = Inference( + vulnerability_id=None, + aliases=["CVE-2020-1234"], + confidence=100, + summary="Test summary", + affected_purls=None, + fixed_purl=None, + references=[Reference(reference_id="", url="https://www.example.com/", severities=[])], + ) + expected = [expected_inference] + result = list(default_improver.get_inferences(advisory_data)) + assert result == expected + + +def test_default_improver_with_affected_packages(): + """ + Test that DefaultImprover.get_inferences() returns an empty list when given an advisory with no affected packages + """ + advisory_data = AdvisoryData( + aliases=["CVE-2020-1234"], + summary="Test summary", + references=[ + Reference( + url="https://www.example.com/", + ) + ], + affected_packages=[ + AffectedPackage( + package=PackageURL( + name="python", + namespace="rpms", + type="rpm", + ), + affected_version_range=None, + fixed_version="3.7.0", + ) + ], + ) + default_improver = DefaultImprover() + expected_inference = Inference( + vulnerability_id=None, + aliases=["CVE-2020-1234"], + confidence=100, + summary="Test summary", + affected_purls=[], + fixed_purl=PackageURL( + type="rpm", + namespace="rpms", + name="python", + version="3.7.0", + qualifiers={}, + subpath=None, + ), + references=[Reference(reference_id="", url="https://www.example.com/", severities=[])], + ) + result = list(default_improver.get_inferences(advisory_data)) + expected = [expected_inference] + assert result == expected + + +def test_default_improver_with_alpine(regen=VULNERABLECODE_REGEN_TEST_FIXTURES): + """ + Test that DefaultImprover.get_inferences() returns an empty list when given an advisory with alpine advisories + """ + default_improver = DefaultImprover() + response_file = os.path.join(TEST_DATA, f"alpine-input.json") + with open(response_file, "r") as f: + advisory_data = json.load(f) + expected_file = os.path.join(TEST_DATA, f"alpine-expected.json") + result = [ + data.to_dict() + for data in list(default_improver.get_inferences(AdvisoryData.from_dict(advisory_data))) + ] + util_tests.check_results_against_json(result, expected_file) + + +def test_default_improver_with_nvd(regen=VULNERABLECODE_REGEN_TEST_FIXTURES): + default_improver = DefaultImprover() + response_file = os.path.join(TEST_DATA, f"nvd-input.json") + + with open(response_file, "r") as f: + advisory_data = json.load(f) + expected_file = os.path.join(TEST_DATA, f"nvd-expected.json") + result = [ + data.to_dict() + for data in list(default_improver.get_inferences(AdvisoryData.from_dict(advisory_data))) + ] + util_tests.check_results_against_json(result, expected_file)