Skip to content

Commit 5faee3f

Browse files
committed
Address review comments
Signed-off-by: Tushar Goel <tushar.goel.dav@gmail.com>
1 parent 372a8cc commit 5faee3f

3 files changed

Lines changed: 66 additions & 55 deletions

File tree

vulnerabilities/import_runner.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ def process_advisories(advisory_datas: Iterable[AdvisoryData], importer_name: st
6666
"""
6767
count = 0
6868
for data in advisory_datas:
69+
# https://nvd.nist.gov/vuln/detail/CVE-2013-4314
70+
# https://github.com/cms-dev/cms/issues/888#issuecomment-516977572
71+
data.summary = data.summary.replace("\x00", "\uFFFD")
6972
obj, created = Advisory.objects.get_or_create(
7073
aliases=data.aliases,
7174
summary=data.summary,

vulnerabilities/importers/gitlab.py

Lines changed: 61 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
from typing import Optional
3131

3232
import pytz
33-
import yaml
33+
import saneyaml
3434
from dateutil import parser as dateparser
3535
from django.db.models.query import QuerySet
3636
from fetchcode.vcs import fetch_via_vcs
@@ -41,6 +41,7 @@
4141
from univers.versions import Version
4242

4343
from vulnerabilities.helpers import AffectedPackage as LegacyAffectedPackage
44+
from vulnerabilities.helpers import get_affected_packages_by_patched_package
4445
from vulnerabilities.helpers import nearest_patched_package
4546
from vulnerabilities.helpers import resolve_version_range
4647
from vulnerabilities.importer import AdvisoryData
@@ -67,6 +68,7 @@
6768
"nuget": "nuget",
6869
"pypi": "pypi",
6970
"packagist": "composer",
71+
# "conan": "conan",
7072
}
7173

7274

@@ -91,32 +93,27 @@ class GitLabAPIImporter(Importer):
9193

9294
def advisory_data(self) -> Iterable[AdvisoryData]:
9395
try:
94-
fork_directory = fork_and_get_dir(self.gitlab_url)
96+
fork_directory = fork_and_get_dir(url = self.gitlab_url)
9597
except Exception as e:
9698
logger.error(f"Can't clone url {self.gitlab_url}")
9799
raise ForkError(self.gitlab_url) from e
98-
ecosystems = ["nuget", "maven", "gem", "npm", "go", "packagist", "pypi"]
99-
for ecosystem in ecosystems:
100-
for file in get_files(os.path.join(fork_directory, ecosystem)):
101-
yield parse_yaml_file(file)
102-
103-
104-
def get_files(dir):
105-
for root, _, files in os.walk(dir):
106-
for file in files:
107-
yield os.path.join(root, file)
108-
109-
110-
def not_empty(value):
111-
return value is not None and value != ""
100+
for root_dir in os.listdir(fork_directory):
101+
# skip well known files and directories that contain no advisory data
102+
if root_dir in ("ci", "CODEOWNERS", "README.md", "LICENSE", ".git"):
103+
continue
104+
if root_dir not in PURL_TYPE_BY_GITLAB_SCHEME:
105+
logger.error(f"Unknown package type: {root_dir}")
106+
continue
107+
for root, _, files in os.walk(os.path.join(fork_directory, root_dir)):
108+
for file in files:
109+
yield parse_gitlab_advisory(file = os.path.join(root, file))
112110

113111

114112
def get_purl(package_slug):
115113
"""
116114
Return a PackageURL object from a package slug
117115
"""
118-
parts = package_slug.split("/")
119-
parts = list(filter(not_empty, parts))
116+
parts = [p for p in package_slug.strip("/").split("/") if p]
120117
gitlab_scheme = parts[0]
121118
purl_type = PURL_TYPE_BY_GITLAB_SCHEME[gitlab_scheme]
122119
if gitlab_scheme == "go":
@@ -130,12 +127,11 @@ def get_purl(package_slug):
130127
# if package slug is of the form:
131128
# "nuget/github/user/abc/NuGet.Core"
132129
if len(parts) >= 3:
133-
gitlab_scheme = parts[0]
134130
name = parts[-1]
135131
namespace = "/".join(parts[1:-1])
136132
return PackageURL(type=purl_type, namespace=namespace, name=name)
137133
logger.error(f"get_purl: package_slug can not be parsed: {package_slug!r}")
138-
return None
134+
return
139135

140136

141137
def extract_affected_packages(
@@ -144,7 +140,12 @@ def extract_affected_packages(
144140
purl: PackageURL,
145141
) -> Iterable[AffectedPackage]:
146142
"""
147-
Yield a list of AffectedPackage objects
143+
Yield AffectedPackage objects, one for each fixed_version
144+
145+
In case of gitlab advisory data we get a list of fixed_versions and a affected_version_range.
146+
Since we can not determine which package fixes which range.
147+
We store the all the fixed_versions with the same affected_version_range in the advisory.
148+
Later the advisory data is used to be infered in the GitLabBasicImprover.
148149
"""
149150
for fixed_version in fixed_versions:
150151
yield AffectedPackage(
@@ -154,10 +155,11 @@ def extract_affected_packages(
154155
)
155156

156157

157-
def parse_yaml_file(file):
158+
def parse_gitlab_advisory(file):
158159
"""
159-
Take file name as input and parse the yaml file
160-
to get AdvisoryData object
160+
Parse a Gitlab advisory file and return an AdvisoryData or None.
161+
These files are YAML. There is a JSON schema documented at
162+
https://gitlab.com/gitlab-org/advisories-community/-/blob/main/ci/schema/schema.json
161163
162164
Sample YAML file:
163165
---
@@ -178,7 +180,7 @@ def parse_yaml_file(file):
178180
- "GMS-2018-26"
179181
"""
180182
with open(file, "r") as f:
181-
gitlab_advisory = yaml.safe_load(f)
183+
gitlab_advisory = saneyaml.load(f)
182184
if not isinstance(gitlab_advisory, dict):
183185
logger.error(f"parse_yaml_file: yaml_file is not of type `dict`: {gitlab_advisory!r}")
184186
return
@@ -190,46 +192,52 @@ def parse_yaml_file(file):
190192
references = [Reference.from_url(u) for u in urls]
191193
date_published = dateparser.parse(gitlab_advisory.get("pubdate"))
192194
date_published = pytz.utc.localize(date_published)
193-
fixed_versions = gitlab_advisory.get("fixed_versions")
194-
affected_version_range = None
195-
affected_range = gitlab_advisory.get("affected_range")
196-
purl: PackageURL = get_purl(gitlab_advisory.get("package_slug"))
195+
package_slug = gitlab_advisory.get("package_slug")
196+
purl: PackageURL = get_purl( package_slug= package_slug)
197197
if not purl:
198-
logger.error(f"parse_yaml_file: purl is not valid: {file!r}")
198+
logger.error(f"parse_yaml_file: purl is not valid: {file!r} {package_slug!r}")
199199
return AdvisoryData(
200200
aliases=aliases,
201201
summary=summary,
202202
references=references,
203203
date_published=date_published,
204204
)
205+
affected_version_range = None
206+
fixed_versions = gitlab_advisory.get("fixed_versions") or []
207+
affected_range = gitlab_advisory.get("affected_range")
208+
gitlab_native_schemes = set(["pypi", "gem", "npm", "go", "packagist"])
205209
vrc: VersionRange = RANGE_CLASS_BY_SCHEMES[purl.type]
206-
version_class = vrc.version_class
207-
gitlab_native_schemes = ["pypi", "gem", "npm", "go", "packagist"]
208210
gitlab_scheme = GITLAB_SCHEME_BY_PURL_TYPE[purl.type]
209211
try:
210-
if gitlab_scheme in gitlab_native_schemes:
211-
if affected_range:
212+
if affected_range:
213+
if gitlab_scheme in gitlab_native_schemes:
212214
affected_version_range = from_gitlab_native(
213215
gitlab_scheme=gitlab_scheme, string=affected_range
214216
)
215-
else:
216-
affected_version_range = vrc.from_native(affected_range) if affected_range else None
217+
else:
218+
affected_version_range = vrc.from_native(affected_range)
217219
except Exception as e:
218220
logger.error(
219-
f"parse_yaml_file: affected_range is not parsable: {affected_range!r} type:{purl.type} {e} {traceback.format_exc()}"
221+
f"parse_yaml_file: affected_range is not parsable: {affected_range!r} type:{purl.type!r} error: {e!r}\n {traceback.format_exc()}"
220222
)
221223

222224
parsed_fixed_versions = []
223-
for fixed_version in fixed_versions or []:
225+
for fixed_version in fixed_versions:
224226
try:
225-
fixed_version = version_class(fixed_version)
227+
fixed_version = vrc.version_class(fixed_version)
226228
parsed_fixed_versions.append(fixed_version)
227229
except Exception as e:
228-
logger.error(f"parse_yaml_file: fixed_version is not parsable`: {fixed_version!r}")
230+
logger.error(
231+
f"parse_yaml_file: fixed_version is not parsable`: {fixed_version!r} error: {e!r}\n {traceback.format_exc()}"
232+
)
229233

230234
if parsed_fixed_versions:
231235
affected_packages = list(
232-
extract_affected_packages(affected_version_range, parsed_fixed_versions, purl)
236+
extract_affected_packages(
237+
affected_version_range=affected_version_range,
238+
fixed_versions=parsed_fixed_versions,
239+
purl=purl,
240+
)
233241
)
234242
else:
235243
if not affected_version_range:
@@ -251,6 +259,14 @@ def parse_yaml_file(file):
251259

252260

253261
class GitLabBasicImprover(Improver):
262+
"""
263+
Get the nearest fixed_version and then resolve the version range with the help of all valid versions.
264+
Generate inference between all the affected packages and the fixed_version that fixes all those affected packages.
265+
266+
In case of gitlab advisory data we get a list of fixed_versions and a affected_version_range.
267+
Since we can not determine which package fixes which range.
268+
"""
269+
254270
def __init__(self) -> None:
255271
self.versions_fetcher_by_purl: Mapping[str, VersionAPI] = {}
256272

@@ -264,7 +280,7 @@ def get_package_versions(
264280
"""
265281
Return a list of `valid_versions` for the `package_url`
266282
"""
267-
api_name = get_api_package_name(package_url)
283+
api_name = get_api_package_name(purl = package_url)
268284
if not api_name:
269285
logger.error(f"Could not get versions for {package_url!r}")
270286
return []
@@ -327,21 +343,13 @@ def get_inferences(self, advisory_data: AdvisoryData) -> Iterable[Inference]:
327343
vulnerable_packages=affected_purls, resolved_packages=unaffected_purls
328344
)
329345

330-
unique_patched_packages_with_affected_packages = {}
331-
for package in affected_packages:
332-
if package.patched_package not in unique_patched_packages_with_affected_packages:
333-
unique_patched_packages_with_affected_packages[package.patched_package] = []
334-
unique_patched_packages_with_affected_packages[package.patched_package].append(
335-
package.vulnerable_package
336-
)
337-
338346
for (
339347
fixed_package,
340348
affected_packages,
341-
) in unique_patched_packages_with_affected_packages.items():
349+
) in get_affected_packages_by_patched_package(affected_packages).items():
342350
yield Inference.from_advisory_data(
343-
advisory_data,
344-
confidence=100, # We are getting all valid versions to get this inference
351+
advisory_data, # We are getting all valid versions to get this inference
352+
confidence=100,
345353
affected_purls=affected_packages,
346354
fixed_purl=fixed_package,
347355
)

vulnerabilities/tests/test_gitlab.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828

2929
from vulnerabilities.importer import AdvisoryData
3030
from vulnerabilities.importers.gitlab import GitLabBasicImprover
31-
from vulnerabilities.importers.gitlab import parse_yaml_file
31+
from vulnerabilities.importers.gitlab import parse_gitlab_advisory
3232
from vulnerabilities.improvers.default import DefaultImprover
3333
from vulnerabilities.tests import util_tests
3434

@@ -40,7 +40,7 @@
4040
def test_parse_yaml_file(pkg_type):
4141
response_file = os.path.join(TEST_DATA, f"{pkg_type}.yaml")
4242
expected_file = os.path.join(TEST_DATA, f"{pkg_type}-expected.json")
43-
advisory = parse_yaml_file(response_file)
43+
advisory = parse_gitlab_advisory(response_file)
4444
util_tests.check_results_against_json(advisory.to_dict(), expected_file)
4545

4646

0 commit comments

Comments
 (0)