From 1a1ffa8967e27ad7f10c059b39c047e648086705 Mon Sep 17 00:00:00 2001 From: Harsh Mishra Date: Sun, 25 Feb 2024 13:23:28 +0530 Subject: [PATCH 1/3] Add First Draft for GlibcImporter Signed-off-by: Harsh Mishra --- vulnerabilities/importers/__init__.py | 2 + vulnerabilities/importers/glibc.py | 188 ++++++++++++++++++++++++++ vulnerabilities/tests/test_glibc.py | 68 ++++++++++ 3 files changed, 258 insertions(+) create mode 100644 vulnerabilities/importers/glibc.py create mode 100644 vulnerabilities/tests/test_glibc.py diff --git a/vulnerabilities/importers/__init__.py b/vulnerabilities/importers/__init__.py index cedd8902b..ec2bd3089 100644 --- a/vulnerabilities/importers/__init__.py +++ b/vulnerabilities/importers/__init__.py @@ -20,6 +20,7 @@ from vulnerabilities.importers import github from vulnerabilities.importers import github_osv from vulnerabilities.importers import gitlab +from vulnerabilities.importers import glibc from vulnerabilities.importers import istio from vulnerabilities.importers import mozilla from vulnerabilities.importers import nginx @@ -71,6 +72,7 @@ oss_fuzz.OSSFuzzImporter, ruby.RubyImporter, github_osv.GithubOSVImporter, + glibc.GlibcImporter ] IMPORTERS_REGISTRY = {x.qualified_name: x for x in IMPORTERS_REGISTRY} diff --git a/vulnerabilities/importers/glibc.py b/vulnerabilities/importers/glibc.py new file mode 100644 index 000000000..ef5e9e24d --- /dev/null +++ b/vulnerabilities/importers/glibc.py @@ -0,0 +1,188 @@ +# +# Copyright (c) nexB Inc. and others. All rights reserved. +# VulnerableCode is a trademark of nexB Inc. +# SPDX-License-Identifier: Apache-2.0 +# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. +# See https://github.com/nexB/vulnerablecode for support or download. +# See https://aboutcode.org for more information about nexB OSS projects. +# +from datetime import datetime +from pathlib import Path +from typing import Any +from typing import Dict +from typing import Iterable +from typing import Optional + +from packageurl import PackageURL +from univers.version_range import PURL_TYPE_BY_GITLAB_SCHEME +from univers.version_range import RANGE_CLASS_BY_SCHEMES +from univers.version_range import VersionRange +from univers.versions import SemverVersion + +from vulnerabilities.importer import AdvisoryData +from vulnerabilities.importer import AffectedPackage +from vulnerabilities.importer import Importer +from vulnerabilities.importer import Reference + + +class GNUVersion(VersionRange): + # TODO: Open PR for this in univers + scheme = "gnu" + version_class = SemverVersion + + +RANGE_CLASS_BY_SCHEMES["gnu"] = GNUVersion +PURL_TYPE_BY_GITLAB_SCHEME["gnu"] = "gnu" + +RANGE_CLASS_BY_SCHEMES["gnu"] = GNUVersion + + +class GlibcImporter(Importer): + repo_url = "git+https://sourceware.org/git/glibc.git" + license_url = "https://sourceware.org/git/?p=glibc.git;a=blob_plain;f=LICENSES" + spdx_license_expression = "LGPL-2.1-only" + importer_name = "Glibc Importer" + + def advisory_data(self) -> Iterable[AdvisoryData]: + try: + self.vcs_response = self.clone(repo_url=self.repo_url) + base_path = Path(self.vcs_response.dest_dir) / "advisories" + readme_path = base_path / "README" + files = [path for path in base_path.glob("*") if path != readme_path] + for file in files: + with open(file, "r") as f: + advisory = parse_advisory_data(f.read(), str(file.relative_to(base_path))) + if advisory: + yield advisory + finally: + if self.vcs_response: + self.vcs_response.delete() + + +def parse_advisory_data(glibc_advisory, file_name) -> AdvisoryData: + """ + Parses the provided GLIBC advisory data from the specified file and returns a structured representation containing the essential information. + + Args: + glibc_advisory (str): The raw GLIBC advisory data to be parsed. + file_name (str): The name of the file containing the advisory data. + + Returns: + AdvisoryData: A dictionary-like object encapsulating the parsed advisory data. + + """ + content = glibc_advisory.split("\n") + if content: + subject = content[0] + line_counter = 2 + description = "" + date = "" + cve_id = "" + vulnerable_commits = [] + fix_commits = [] + for line in content[line_counter:]: + if not line.strip(): + break + description += line.strip() + " " + line_counter += 1 + description = description.strip() + for line in content[line_counter + 1 :]: + if not line.strip(): + break + tag, content = list(x.strip() for x in line.split(":")) + match tag: + case "CVE-Id": + cve_id = content + case "Public-Date": + date = content + case "Vulnerable-Commit": + commit, release = content.split("(") + release = release.strip(")").strip() + commit = commit.strip() + vulnerable_commits.append((commit, release)) + case "Fix-Commit": + commit, release = content.split("(") + release = release.strip(")").strip() + commit = commit.strip() + fix_commits.append((commit, release)) + + advisory_dict = { + "aliases": [cve_id], + "affected_packages": "", + "date_published": datetime.strptime(date, "%Y-%m-%d"), + "summary": description, + "references": [ + Reference( + url="https://sourceware.org/git/?p=glibc.git;a=blob_plain;f=advisories/" + file_name + ) + ], + "url": "https://sourceware.org/git/?p=glibc.git;a=blob_plain;f=advisories/" + file_name, + } + + purl = PackageURL(type="gnu", name="glibc") + min_affected_version: Optional[SemverVersion, str] = "" + max_affected_version: Optional[SemverVersion, str] = "" + for _, release in vulnerable_commits: + if min_affected_version == "" or max_affected_version == "": + min_affected_version = SemverVersion(sanitize_version(release)) + max_affected_version = SemverVersion(sanitize_version(release)) + else: + min_affected_version = ( + SemverVersion(sanitize_version(release)) + if SemverVersion(sanitize_version(release)) < min_affected_version + else min_affected_version + ) + max_affected_version = ( + SemverVersion(sanitize_version(release)) + if SemverVersion(sanitize_version(release)) > max_affected_version + else max_affected_version + ) + _, min_fixed_version = min(fix_commits, key=lambda x: SemverVersion(sanitize_version(x[1]))) + min_fixed_version = SemverVersion(sanitize_version(min_fixed_version)) + affected_version_range = None + if max_affected_version == "" and min_affected_version == "": + affected_version_range = None + elif max_affected_version == min_affected_version: + affected_version_range = VersionRange.from_string(f"vers:gnu/{str(max_affected_version)}") + else: + affected_version_range = VersionRange.from_string( + f"vers:gnu/<={str(max_affected_version)}|>={min_affected_version}" + ) + affected_packages = AffectedPackage( + package=purl, + affected_version_range=affected_version_range, + fixed_version=min_fixed_version, + ) + advisory_dict["affected_packages"] = [affected_packages] + resolved_advisory = to_advisory(advisory_dict) + return resolved_advisory + + +def sanitize_version(version: str): + """ + Returns the version in Semver Format from Glibc Advisory + + Args: + version (str): Version string from advisory + + Returns: + str: Version string in Semver Format + + >>> sanitize_version('2.45-12') + '2.45.12' + """ + return version.replace("-", ".") + + +def to_advisory(advisory_data: Dict[str, Any]) -> AdvisoryData: + """ + Returns the AdvisoryData object for a given dictionary containing advisory info + + Args: + advisory_data: Dict[str, Any]: contains all fields to be passed to the constructor of AdvisoryData + + Returns: + AdvisoryData: converted object into AdvisoryData format + + """ + return AdvisoryData(**advisory_data) diff --git a/vulnerabilities/tests/test_glibc.py b/vulnerabilities/tests/test_glibc.py new file mode 100644 index 000000000..54650f7a6 --- /dev/null +++ b/vulnerabilities/tests/test_glibc.py @@ -0,0 +1,68 @@ +import os +from datetime import datetime +from textwrap import dedent +from unittest import TestCase + +from packageurl import PackageURL +from univers.version_range import VersionRange +from univers.versions import SemverVersion + +from vulnerabilities.importer import AdvisoryData +from vulnerabilities.importer import AffectedPackage +from vulnerabilities.importer import Reference +from vulnerabilities.importers.glibc import parse_advisory_data + +BASE_DIR = os.path.dirname(os.path.abspath(__file__)) +TEST_DATA = os.path.join(BASE_DIR, "test_data/glibc") + + +class TestGlibcImporter(TestCase): + def test_parse_advisory_data_1(self): + test_data = parse_advisory_data( + dedent( + """syslog: Heap buffer overflow in __vsyslog_internal + + __vsyslog_internal did not handle a case where printing a SYSLOG_HEADER + containing a long program name failed to update the required buffer + size, leading to the allocation and overflow of a too-small buffer on + the heap. + + CVE-Id: CVE-2023-6246 + Public-Date: 2024-01-30 + Vulnerable-Commit: 52a5be0df411ef3ff45c10c7c308cb92993d15b1 (2.37) + Fix-Commit: 6bd0e4efcc78f3c0115e5ea9739a1642807450da (2.39) + Fix-Commit: d1a83b6767f68b3cb5b4b4ea2617254acd040c82 (2.36-126) + Fix-Commit: 23514c72b780f3da097ecf33a793b7ba9c2070d2 (2.38-42) + Fix-Commit: 97a4292aa4a2642e251472b878d0ec4c46a0e59a (2.37-57) + Vulnerable-Commit: b0e7888d1fa2dbd2d9e1645ec8c796abf78880b9 (2.36-16) + """ + ), + "GLIBC-SA-2023-0001", + ) + + expected_output = AdvisoryData( + **{ + "aliases": ["CVE-2023-6246"], + "affected_packages": [ + AffectedPackage( + package=PackageURL(type="gnu", name="glibc"), + affected_version_range=VersionRange.from_string( + f'vers:gnu/>={str(SemverVersion(string="2.36.16"))}|<={SemverVersion(string="2.37")}' + ), + fixed_version=SemverVersion(string="2.36.126"), + ) + ], + "date_published": datetime(2024, 1, 30, 0, 0), + "summary": "__vsyslog_internal did not handle a case where printing a SYSLOG_HEADER containing a long program name failed to update the required buffer size, leading to the allocation and overflow of a too-small buffer on the heap.", + "references": [ + Reference( + reference_id="", + url="https://sourceware.org/git/?p=glibc.git;a=blob_plain;f=advisories/GLIBC-SA-2023-0001", + severities=[], + ) + ], + "url": "https://sourceware.org/git/?p=glibc.git;a=blob_plain;f=advisories/GLIBC-SA-2023-0001", + } + ) + + assert expected_output == test_data From cda6b0644de2de075c7077b4132506a4a58d54dd Mon Sep 17 00:00:00 2001 From: Harsh Mishra Date: Sun, 25 Feb 2024 16:31:58 +0530 Subject: [PATCH 2/3] Add Missing Headers Signed-off-by: Harsh Mishra --- vulnerabilities/importers/__init__.py | 2 +- vulnerabilities/tests/test_glibc.py | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/vulnerabilities/importers/__init__.py b/vulnerabilities/importers/__init__.py index ec2bd3089..6495f0c57 100644 --- a/vulnerabilities/importers/__init__.py +++ b/vulnerabilities/importers/__init__.py @@ -72,7 +72,7 @@ oss_fuzz.OSSFuzzImporter, ruby.RubyImporter, github_osv.GithubOSVImporter, - glibc.GlibcImporter + glibc.GlibcImporter, ] IMPORTERS_REGISTRY = {x.qualified_name: x for x in IMPORTERS_REGISTRY} diff --git a/vulnerabilities/tests/test_glibc.py b/vulnerabilities/tests/test_glibc.py index 54650f7a6..fb954b3bb 100644 --- a/vulnerabilities/tests/test_glibc.py +++ b/vulnerabilities/tests/test_glibc.py @@ -1,3 +1,11 @@ +# +# Copyright (c) nexB Inc. and others. All rights reserved. +# VulnerableCode is a trademark of nexB Inc. +# SPDX-License-Identifier: Apache-2.0 +# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. +# See https://github.com/nexB/vulnerablecode for support or download. +# See https://aboutcode.org for more information about nexB OSS projects. +# import os from datetime import datetime from textwrap import dedent From 708ff77829cec6f4fd0f6faf3d9e2db77c01e484 Mon Sep 17 00:00:00 2001 From: Harsh Mishra Date: Mon, 26 Feb 2024 21:11:48 +0530 Subject: [PATCH 3/3] Add Sample Advisory Signed-off-by: Harsh Mishra --- vulnerabilities/importers/glibc.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/vulnerabilities/importers/glibc.py b/vulnerabilities/importers/glibc.py index ef5e9e24d..d8d35aefc 100644 --- a/vulnerabilities/importers/glibc.py +++ b/vulnerabilities/importers/glibc.py @@ -70,6 +70,22 @@ def parse_advisory_data(glibc_advisory, file_name) -> AdvisoryData: Returns: AdvisoryData: A dictionary-like object encapsulating the parsed advisory data. + Sample Advisory: + printf: incorrect output for integers with thousands separator and width field + + When the printf family of functions is called with a format specifier + that uses an (enable grouping) and a minimum width + specifier, the resulting output could be larger than reasonably expected + by a caller that computed a tight bound on the buffer size. The + resulting larger than expected output could result in a buffer overflow + in the printf family of functions. + + CVE-Id: CVE-2023-25139 + Public-Date: 2023-02-02 + Vulnerable-Commit: e88b9f0e5cc50cab57a299dc7efe1a4eb385161d (2.37) + Fix-Commit: c980549cc6a1c03c23cc2fe3e7b0fe626a0364b0 (2.38) + Fix-Commit: 07b9521fc6369d000216b96562ff7c0ed32a16c4 (2.37-4) + """ content = glibc_advisory.split("\n") if content: