Skip to content

Commit 1a1ffa8

Browse files
committed
Add First Draft for GlibcImporter
Signed-off-by: Harsh Mishra <hmisraji07@gmail.com>
1 parent 4e5ef60 commit 1a1ffa8

3 files changed

Lines changed: 258 additions & 0 deletions

File tree

vulnerabilities/importers/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from vulnerabilities.importers import github
2121
from vulnerabilities.importers import github_osv
2222
from vulnerabilities.importers import gitlab
23+
from vulnerabilities.importers import glibc
2324
from vulnerabilities.importers import istio
2425
from vulnerabilities.importers import mozilla
2526
from vulnerabilities.importers import nginx
@@ -71,6 +72,7 @@
7172
oss_fuzz.OSSFuzzImporter,
7273
ruby.RubyImporter,
7374
github_osv.GithubOSVImporter,
75+
glibc.GlibcImporter
7476
]
7577

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

vulnerabilities/importers/glibc.py

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
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/nexB/vulnerablecode for support or download.
7+
# See https://aboutcode.org for more information about nexB OSS projects.
8+
#
9+
from datetime import datetime
10+
from pathlib import Path
11+
from typing import Any
12+
from typing import Dict
13+
from typing import Iterable
14+
from typing import Optional
15+
16+
from packageurl import PackageURL
17+
from univers.version_range import PURL_TYPE_BY_GITLAB_SCHEME
18+
from univers.version_range import RANGE_CLASS_BY_SCHEMES
19+
from univers.version_range import VersionRange
20+
from univers.versions import SemverVersion
21+
22+
from vulnerabilities.importer import AdvisoryData
23+
from vulnerabilities.importer import AffectedPackage
24+
from vulnerabilities.importer import Importer
25+
from vulnerabilities.importer import Reference
26+
27+
28+
class GNUVersion(VersionRange):
29+
# TODO: Open PR for this in univers
30+
scheme = "gnu"
31+
version_class = SemverVersion
32+
33+
34+
RANGE_CLASS_BY_SCHEMES["gnu"] = GNUVersion
35+
PURL_TYPE_BY_GITLAB_SCHEME["gnu"] = "gnu"
36+
37+
RANGE_CLASS_BY_SCHEMES["gnu"] = GNUVersion
38+
39+
40+
class GlibcImporter(Importer):
41+
repo_url = "git+https://sourceware.org/git/glibc.git"
42+
license_url = "https://sourceware.org/git/?p=glibc.git;a=blob_plain;f=LICENSES"
43+
spdx_license_expression = "LGPL-2.1-only"
44+
importer_name = "Glibc Importer"
45+
46+
def advisory_data(self) -> Iterable[AdvisoryData]:
47+
try:
48+
self.vcs_response = self.clone(repo_url=self.repo_url)
49+
base_path = Path(self.vcs_response.dest_dir) / "advisories"
50+
readme_path = base_path / "README"
51+
files = [path for path in base_path.glob("*") if path != readme_path]
52+
for file in files:
53+
with open(file, "r") as f:
54+
advisory = parse_advisory_data(f.read(), str(file.relative_to(base_path)))
55+
if advisory:
56+
yield advisory
57+
finally:
58+
if self.vcs_response:
59+
self.vcs_response.delete()
60+
61+
62+
def parse_advisory_data(glibc_advisory, file_name) -> AdvisoryData:
63+
"""
64+
Parses the provided GLIBC advisory data from the specified file and returns a structured representation containing the essential information.
65+
66+
Args:
67+
glibc_advisory (str): The raw GLIBC advisory data to be parsed.
68+
file_name (str): The name of the file containing the advisory data.
69+
70+
Returns:
71+
AdvisoryData: A dictionary-like object encapsulating the parsed advisory data.
72+
73+
"""
74+
content = glibc_advisory.split("\n")
75+
if content:
76+
subject = content[0]
77+
line_counter = 2
78+
description = ""
79+
date = ""
80+
cve_id = ""
81+
vulnerable_commits = []
82+
fix_commits = []
83+
for line in content[line_counter:]:
84+
if not line.strip():
85+
break
86+
description += line.strip() + " "
87+
line_counter += 1
88+
description = description.strip()
89+
for line in content[line_counter + 1 :]:
90+
if not line.strip():
91+
break
92+
tag, content = list(x.strip() for x in line.split(":"))
93+
match tag:
94+
case "CVE-Id":
95+
cve_id = content
96+
case "Public-Date":
97+
date = content
98+
case "Vulnerable-Commit":
99+
commit, release = content.split("(")
100+
release = release.strip(")").strip()
101+
commit = commit.strip()
102+
vulnerable_commits.append((commit, release))
103+
case "Fix-Commit":
104+
commit, release = content.split("(")
105+
release = release.strip(")").strip()
106+
commit = commit.strip()
107+
fix_commits.append((commit, release))
108+
109+
advisory_dict = {
110+
"aliases": [cve_id],
111+
"affected_packages": "",
112+
"date_published": datetime.strptime(date, "%Y-%m-%d"),
113+
"summary": description,
114+
"references": [
115+
Reference(
116+
url="https://sourceware.org/git/?p=glibc.git;a=blob_plain;f=advisories/" + file_name
117+
)
118+
],
119+
"url": "https://sourceware.org/git/?p=glibc.git;a=blob_plain;f=advisories/" + file_name,
120+
}
121+
122+
purl = PackageURL(type="gnu", name="glibc")
123+
min_affected_version: Optional[SemverVersion, str] = ""
124+
max_affected_version: Optional[SemverVersion, str] = ""
125+
for _, release in vulnerable_commits:
126+
if min_affected_version == "" or max_affected_version == "":
127+
min_affected_version = SemverVersion(sanitize_version(release))
128+
max_affected_version = SemverVersion(sanitize_version(release))
129+
else:
130+
min_affected_version = (
131+
SemverVersion(sanitize_version(release))
132+
if SemverVersion(sanitize_version(release)) < min_affected_version
133+
else min_affected_version
134+
)
135+
max_affected_version = (
136+
SemverVersion(sanitize_version(release))
137+
if SemverVersion(sanitize_version(release)) > max_affected_version
138+
else max_affected_version
139+
)
140+
_, min_fixed_version = min(fix_commits, key=lambda x: SemverVersion(sanitize_version(x[1])))
141+
min_fixed_version = SemverVersion(sanitize_version(min_fixed_version))
142+
affected_version_range = None
143+
if max_affected_version == "" and min_affected_version == "":
144+
affected_version_range = None
145+
elif max_affected_version == min_affected_version:
146+
affected_version_range = VersionRange.from_string(f"vers:gnu/{str(max_affected_version)}")
147+
else:
148+
affected_version_range = VersionRange.from_string(
149+
f"vers:gnu/<={str(max_affected_version)}|>={min_affected_version}"
150+
)
151+
affected_packages = AffectedPackage(
152+
package=purl,
153+
affected_version_range=affected_version_range,
154+
fixed_version=min_fixed_version,
155+
)
156+
advisory_dict["affected_packages"] = [affected_packages]
157+
resolved_advisory = to_advisory(advisory_dict)
158+
return resolved_advisory
159+
160+
161+
def sanitize_version(version: str):
162+
"""
163+
Returns the version in Semver Format from Glibc Advisory
164+
165+
Args:
166+
version (str): Version string from advisory
167+
168+
Returns:
169+
str: Version string in Semver Format
170+
171+
>>> sanitize_version('2.45-12')
172+
'2.45.12'
173+
"""
174+
return version.replace("-", ".")
175+
176+
177+
def to_advisory(advisory_data: Dict[str, Any]) -> AdvisoryData:
178+
"""
179+
Returns the AdvisoryData object for a given dictionary containing advisory info
180+
181+
Args:
182+
advisory_data: Dict[str, Any]: contains all fields to be passed to the constructor of AdvisoryData
183+
184+
Returns:
185+
AdvisoryData: converted object into AdvisoryData format
186+
187+
"""
188+
return AdvisoryData(**advisory_data)
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import os
2+
from datetime import datetime
3+
from textwrap import dedent
4+
from unittest import TestCase
5+
6+
from packageurl import PackageURL
7+
from univers.version_range import VersionRange
8+
from univers.versions import SemverVersion
9+
10+
from vulnerabilities.importer import AdvisoryData
11+
from vulnerabilities.importer import AffectedPackage
12+
from vulnerabilities.importer import Reference
13+
from vulnerabilities.importers.glibc import parse_advisory_data
14+
15+
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
16+
TEST_DATA = os.path.join(BASE_DIR, "test_data/glibc")
17+
18+
19+
class TestGlibcImporter(TestCase):
20+
def test_parse_advisory_data_1(self):
21+
test_data = parse_advisory_data(
22+
dedent(
23+
"""syslog: Heap buffer overflow in __vsyslog_internal
24+
25+
__vsyslog_internal did not handle a case where printing a SYSLOG_HEADER
26+
containing a long program name failed to update the required buffer
27+
size, leading to the allocation and overflow of a too-small buffer on
28+
the heap.
29+
30+
CVE-Id: CVE-2023-6246
31+
Public-Date: 2024-01-30
32+
Vulnerable-Commit: 52a5be0df411ef3ff45c10c7c308cb92993d15b1 (2.37)
33+
Fix-Commit: 6bd0e4efcc78f3c0115e5ea9739a1642807450da (2.39)
34+
Fix-Commit: d1a83b6767f68b3cb5b4b4ea2617254acd040c82 (2.36-126)
35+
Fix-Commit: 23514c72b780f3da097ecf33a793b7ba9c2070d2 (2.38-42)
36+
Fix-Commit: 97a4292aa4a2642e251472b878d0ec4c46a0e59a (2.37-57)
37+
Vulnerable-Commit: b0e7888d1fa2dbd2d9e1645ec8c796abf78880b9 (2.36-16)
38+
"""
39+
),
40+
"GLIBC-SA-2023-0001",
41+
)
42+
43+
expected_output = AdvisoryData(
44+
**{
45+
"aliases": ["CVE-2023-6246"],
46+
"affected_packages": [
47+
AffectedPackage(
48+
package=PackageURL(type="gnu", name="glibc"),
49+
affected_version_range=VersionRange.from_string(
50+
f'vers:gnu/>={str(SemverVersion(string="2.36.16"))}|<={SemverVersion(string="2.37")}'
51+
),
52+
fixed_version=SemverVersion(string="2.36.126"),
53+
)
54+
],
55+
"date_published": datetime(2024, 1, 30, 0, 0),
56+
"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.",
57+
"references": [
58+
Reference(
59+
reference_id="",
60+
url="https://sourceware.org/git/?p=glibc.git;a=blob_plain;f=advisories/GLIBC-SA-2023-0001",
61+
severities=[],
62+
)
63+
],
64+
"url": "https://sourceware.org/git/?p=glibc.git;a=blob_plain;f=advisories/GLIBC-SA-2023-0001",
65+
}
66+
)
67+
68+
assert expected_output == test_data

0 commit comments

Comments
 (0)