Skip to content

Commit cfe397d

Browse files
Tomtomdottom
authored andcommitted
fix(get-cves): Store cpe update values and use to correctly fetch cves
Signed-off-by: Tom Marks <thomas.marks@palindrometech.com>
1 parent c1ecffa commit cfe397d

4 files changed

Lines changed: 136 additions & 5 deletions

File tree

cve_bin_tool/cve_scanner.py

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Copyright (C) 2021 Intel Corporation
22
# SPDX-License-Identifier: GPL-3.0-or-later
33

4+
# import itertools
45
import sqlite3
56
import sys
67
from collections import defaultdict
@@ -143,15 +144,39 @@ def get_cves(self, product_info: ProductInfo, triage_data: TriageData):
143144
# Check for anything directly marked
144145
query = """
145146
SELECT CVE_number FROM cve_range
146-
WHERE vendor=? AND product=? AND version=?
147+
WHERE (
148+
vendor=:vendor
149+
AND product=:product
150+
AND version=:version
151+
AND data_source != "NVD"
152+
) OR (
153+
vendor=:vendor
154+
AND product=:product
155+
AND (version || versionUpdate) = :version
156+
AND data_source = "NVD"
157+
)
147158
"""
148159
# Removing * from vendors that are guessed by the package list parser
149160
vendor = product_info.vendor.replace("*", "")
150161

151162
# Use our Version class to do version compares
152163
parsed_version = Version(product_info.version)
153164

154-
self.cursor.execute(query, [vendor, product_info.product, str(parsed_version)])
165+
version = str(parsed_version)
166+
# numeric = "0123456789."
167+
# numeric_version = "".join(itertools.takewhile(lambda i: i in numeric, parsed_version))
168+
# version_update = product_info.version
169+
170+
self.cursor.execute(
171+
query,
172+
{
173+
"vendor": vendor,
174+
"product": product_info.product,
175+
"version": version,
176+
# "numericVersion": numeric_version,
177+
# "versionUpdate": version_update,
178+
},
179+
)
155180

156181
cve_list = list(map(lambda x: x[0], self.cursor.fetchall()))
157182

cve_bin_tool/cvedb.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ class CVEDB:
8989
vendor TEXT,
9090
product TEXT,
9191
version TEXT,
92+
versionUpdate TEXT,
9293
versionStartIncluding TEXT,
9394
versionStartExcluding TEXT,
9495
versionEndIncluding TEXT,
@@ -185,13 +186,14 @@ class CVEDB:
185186
vendor,
186187
product,
187188
version,
189+
versionUpdate,
188190
versionStartIncluding,
189191
versionStartExcluding,
190192
versionEndIncluding,
191193
versionEndExcluding,
192194
data_source
193195
)
194-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
196+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
195197
""",
196198
"insert_exploit": """
197199
INSERT or REPLACE INTO cve_exploited (
@@ -611,6 +613,7 @@ def populate_affected(self, affected_data, cursor, data_source):
611613
affected["vendor"],
612614
affected["product"],
613615
affected["version"],
616+
affected["versionUpdate"],
614617
affected["versionStartIncluding"],
615618
affected["versionStartExcluding"],
616619
affected["versionEndIncluding"],

cve_bin_tool/data_sources/nvd_source.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,12 +69,13 @@ def __init__(
6969
nvd_type: str = "json-mirror",
7070
incremental_update: bool = False,
7171
nvd_api_key: str = "",
72+
cachedir: Path | None = None,
7273
):
7374
if feed is None:
7475
self.feed = self.FEED_NVD if nvd_type == "json-nvd" else self.FEED_MIRROR
7576
else:
7677
self.feed = feed
77-
self.cachedir = self.CACHEDIR
78+
self.cachedir = cachedir or self.CACHEDIR
7879
self.backup_cachedir = self.BACKUPCACHEDIR
7980
self.error_mode = error_mode
8081
self.source_name = self.SOURCE
@@ -102,7 +103,10 @@ def __init__(
102103
async def get_cve_data(self):
103104
"""Retrieves the CVE data from the data source."""
104105
await self.fetch_cves()
106+
return self._load_cve_data()
105107

108+
def _load_cve_data(self):
109+
"""Loads the CVE data which has been fetched from the data source."""
106110
if self.nvd_type == "api2":
107111
return self.format_data_api2(self.all_cve_entries), self.source_name
108112

@@ -211,10 +215,17 @@ def parse_node(self, node: dict[str, list[dict[str, str]]]) -> list[dict[str, st
211215
for cpe_match in vulnerable_matches:
212216
# split on `:` only if it's not escaped
213217
cpe_split = re.split(r"(?<!\\):", cpe_match["cpe23Uri"])
218+
219+
if cpe_split[6] in ("-", "*", ""):
220+
versionUpdate = ""
221+
else:
222+
versionUpdate = cpe_split[6]
223+
214224
affects = {
215225
"vendor": cpe_split[3],
216226
"product": cpe_split[4],
217227
"version": cpe_split[5],
228+
"versionUpdate": versionUpdate,
218229
}
219230

220231
# if we have a range (e.g. version is *) fill it out, and put blanks where needed
@@ -395,10 +406,17 @@ def parse_node_api2(
395406
for cpe_match in vulnerable_matches:
396407
# split on `:` only if it's not escaped
397408
cpe_split = re.split(r"(?<!\\):", cpe_match["criteria"])
409+
410+
if cpe_split[6] in ("-", "*", ""):
411+
versionUpdate = ""
412+
else:
413+
versionUpdate = cpe_split[6]
414+
398415
affects = {
399416
"vendor": cpe_split[3],
400417
"product": cpe_split[4],
401418
"version": cpe_split[5],
419+
"versionUpdate": versionUpdate,
402420
}
403421

404422
# if we have a range (e.g. version is *) fill it out, and put blanks where needed

test/test_cvedb.py

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
# SPDX-License-Identifier: GPL-3.0-or-later
33

44
import datetime
5+
import pathlib
56
import shutil
67
import tempfile
78
from test.utils import EXTERNAL_SYSTEM, LONG_TESTS
@@ -16,8 +17,10 @@
1617
class TestCVEDB:
1718
@classmethod
1819
def setup_class(cls):
19-
cls.nvd = nvd_source.NVD_Source(nvd_type="json")
2020
cachedir = tempfile.mkdtemp(prefix="cvedb-")
21+
cls.nvd = nvd_source.NVD_Source(
22+
nvd_type="json", cachedir=pathlib.Path(cachedir)
23+
)
2124
cls.exported_data = tempfile.mkdtemp(prefix="exported-data-")
2225
cls.cvedb = cvedb.CVEDB(sources=[cls.nvd], cachedir=cachedir)
2326
cls.nvd.cachedir = cachedir
@@ -91,3 +94,85 @@ def test_new_database_schema(self):
9194
assert all(column in column_names for column in required_columns[table])
9295

9396
self.cvedb.db_close()
97+
98+
@pytest.mark.asyncio
99+
@pytest.mark.skipif(
100+
not EXTERNAL_SYSTEM(),
101+
reason="NVD tests run only when EXTERNAL_SYSTEM=1",
102+
)
103+
async def test_populate_foo(self):
104+
await self.nvd.get_cve_data()
105+
self.cvedb.data = [self.nvd._load_cve_data()]
106+
self.cvedb.init_database()
107+
self.cvedb.populate_db()
108+
self.cvedb.check_cve_entries()
109+
assert self.cvedb.cve_count > 0
110+
111+
def test_populate_db(self):
112+
"""Test to check whether we are able to fetch and save the nvd entries"""
113+
severity_data = [
114+
{
115+
"ID": "CVE-9999-9999",
116+
"description": "Test Vuln",
117+
"severity": "UNKNOWN",
118+
"score": 7.5,
119+
"CVSS_version": 2,
120+
"CVSS_vector": "AV:N/AC:L/Au:N/C:P/I:P/A:P",
121+
"last_modified": "2025-04-09T00:30:58.490",
122+
}
123+
]
124+
affected_data = [
125+
{
126+
"vendor": "test-vendor",
127+
"product": "test-product",
128+
"version": "6.1",
129+
"versionUpdate": "p1",
130+
"versionStartIncluding": "",
131+
"versionStartExcluding": "",
132+
"versionEndIncluding": "",
133+
"versionEndExcluding": "",
134+
"cve_id": "CVE-9999-9999",
135+
}
136+
]
137+
source_name = "NVD"
138+
test_data = ((severity_data, affected_data), source_name)
139+
self.cvedb.data = [test_data]
140+
141+
self.cvedb.init_database()
142+
self.cvedb.populate_db()
143+
self.cvedb.check_cve_entries()
144+
145+
from cve_bin_tool.cve_scanner import CVEScanner
146+
from cve_bin_tool.util import CVE, ProductInfo
147+
148+
class TestableCVEScanner(CVEScanner):
149+
dbname: str = str(self.cvedb.dbpath)
150+
151+
with TestableCVEScanner() as cve_scanner:
152+
product_info = ProductInfo(
153+
vendor="test-vendor",
154+
product="test-product",
155+
version="6.1p1",
156+
)
157+
triage_data = {"paths": []}
158+
159+
cve_scanner.get_cves(product_info, triage_data)
160+
products_with_cve = cve_scanner.products_with_cve == 1
161+
results = dict(cve_scanner.all_cve_data)
162+
163+
assert products_with_cve == 1
164+
assert results[product_info] == {
165+
"cves": [
166+
CVE(
167+
cve_number="CVE-9999-9999",
168+
severity="UNKNOWN",
169+
description="Test Vuln",
170+
score=7.5,
171+
cvss_version=2,
172+
cvss_vector="AV:N/AC:L/Au:N/C:P/I:P/A:P",
173+
data_source="NVD",
174+
last_modified="",
175+
),
176+
],
177+
"paths": set(),
178+
}

0 commit comments

Comments
 (0)