diff options
author | Marta Rybczynska <rybczynska@gmail.com> | 2025-02-13 06:57:52 +0100 |
---|---|---|
committer | Richard Purdie <richard.purdie@linuxfoundation.org> | 2025-02-18 11:56:04 +0000 |
commit | 0486af6e3cefeeb705ec7c7a0938623fc25d9fee (patch) | |
tree | fd8e6e698f5e9f7f904e5dbcfa6df7609b0783e6 | |
parent | 7a3904c6a730272841941a20531aa1616cc608c5 (diff) | |
download | poky-0486af6e3cefeeb705ec7c7a0938623fc25d9fee.tar.gz |
cve-update-db-native: add the fkie source
Add support for FKIE-CAD reconstruction of NVD feed from
https://github.com/fkie-cad/nvd-json-data-feeds
We download this feed directly from github releases.
(From OE-Core rev: f6253ac8189db09fbe87141aca1733cb37a4d78f)
Signed-off-by: Marta Rybczynska <marta.rybczynska@ygreky.com>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
-rw-r--r-- | meta/recipes-core/meta/cve-update-db-native.bb | 126 |
1 files changed, 113 insertions, 13 deletions
diff --git a/meta/recipes-core/meta/cve-update-db-native.bb b/meta/recipes-core/meta/cve-update-db-native.bb index 3a9d43943c..792252f510 100644 --- a/meta/recipes-core/meta/cve-update-db-native.bb +++ b/meta/recipes-core/meta/cve-update-db-native.bb | |||
@@ -12,6 +12,8 @@ deltask do_install | |||
12 | deltask do_populate_sysroot | 12 | deltask do_populate_sysroot |
13 | 13 | ||
14 | NVDCVE_URL ?= "https://nvd.nist.gov/feeds/json/cve/1.1/nvdcve-1.1-" | 14 | NVDCVE_URL ?= "https://nvd.nist.gov/feeds/json/cve/1.1/nvdcve-1.1-" |
15 | FKIE_URL ?= "https://github.com/fkie-cad/nvd-json-data-feeds/releases/latest/download/CVE-" | ||
16 | |||
15 | # CVE database update interval, in seconds. By default: once a day (24*60*60). | 17 | # CVE database update interval, in seconds. By default: once a day (24*60*60). |
16 | # Use 0 to force the update | 18 | # Use 0 to force the update |
17 | # Use a negative value to skip the update | 19 | # Use a negative value to skip the update |
@@ -109,6 +111,30 @@ def cleanup_db_download(db_file, db_tmp_file): | |||
109 | if os.path.exists(db_tmp_file): | 111 | if os.path.exists(db_tmp_file): |
110 | os.remove(db_tmp_file) | 112 | os.remove(db_tmp_file) |
111 | 113 | ||
114 | def db_file_names(d, year, is_nvd): | ||
115 | if is_nvd: | ||
116 | year_url = d.getVar('NVDCVE_URL') + str(year) | ||
117 | meta_url = year_url + ".meta" | ||
118 | json_url = year_url + ".json.gz" | ||
119 | return json_url, meta_url | ||
120 | year_url = d.getVar('FKIE_URL') + str(year) | ||
121 | meta_url = year_url + ".meta" | ||
122 | json_url = year_url + ".json.xz" | ||
123 | return json_url, meta_url | ||
124 | |||
125 | def host_db_name(d, is_nvd): | ||
126 | if is_nvd: | ||
127 | return "nvd.nist.gov" | ||
128 | return "github.com" | ||
129 | |||
130 | def db_decompress(d, data, is_nvd): | ||
131 | import gzip, lzma | ||
132 | |||
133 | if is_nvd: | ||
134 | return gzip.decompress(data).decode('utf-8') | ||
135 | # otherwise | ||
136 | return lzma.decompress(data) | ||
137 | |||
112 | def update_db_file(db_tmp_file, d): | 138 | def update_db_file(db_tmp_file, d): |
113 | """ | 139 | """ |
114 | Update the given database file | 140 | Update the given database file |
@@ -119,6 +145,7 @@ def update_db_file(db_tmp_file, d): | |||
119 | 145 | ||
120 | YEAR_START = 2002 | 146 | YEAR_START = 2002 |
121 | cve_socket_timeout = int(d.getVar("CVE_SOCKET_TIMEOUT")) | 147 | cve_socket_timeout = int(d.getVar("CVE_SOCKET_TIMEOUT")) |
148 | is_nvd = d.getVar("NVD_DB_VERSION") == "NVD1" | ||
122 | 149 | ||
123 | # Connect to database | 150 | # Connect to database |
124 | conn = sqlite3.connect(db_tmp_file) | 151 | conn = sqlite3.connect(db_tmp_file) |
@@ -129,9 +156,7 @@ def update_db_file(db_tmp_file, d): | |||
129 | for i, year in enumerate(range(YEAR_START, date.today().year + 1)): | 156 | for i, year in enumerate(range(YEAR_START, date.today().year + 1)): |
130 | bb.debug(2, "Updating %d" % year) | 157 | bb.debug(2, "Updating %d" % year) |
131 | ph.update((float(i + 1) / total_years) * 100) | 158 | ph.update((float(i + 1) / total_years) * 100) |
132 | year_url = (d.getVar('NVDCVE_URL')) + str(year) | 159 | json_url, meta_url = db_file_names(d, year, is_nvd) |
133 | meta_url = year_url + ".meta" | ||
134 | json_url = year_url + ".json.gz" | ||
135 | 160 | ||
136 | # Retrieve meta last modified date | 161 | # Retrieve meta last modified date |
137 | try: | 162 | try: |
@@ -140,7 +165,7 @@ def update_db_file(db_tmp_file, d): | |||
140 | cve_f.write('Warning: CVE db update error, Unable to fetch CVE data.\n\n') | 165 | cve_f.write('Warning: CVE db update error, Unable to fetch CVE data.\n\n') |
141 | bb.warn("Failed to fetch CVE data (%s)" % e) | 166 | bb.warn("Failed to fetch CVE data (%s)" % e) |
142 | import socket | 167 | import socket |
143 | result = socket.getaddrinfo("nvd.nist.gov", 443, proto=socket.IPPROTO_TCP) | 168 | result = socket.getaddrinfo(host_db_name(d, is_nvd), 443, proto=socket.IPPROTO_TCP) |
144 | bb.warn("Host IPs are %s" % (", ".join(t[4][0] for t in result))) | 169 | bb.warn("Host IPs are %s" % (", ".join(t[4][0] for t in result))) |
145 | return False | 170 | return False |
146 | 171 | ||
@@ -168,7 +193,7 @@ def update_db_file(db_tmp_file, d): | |||
168 | try: | 193 | try: |
169 | response = urllib.request.urlopen(json_url, timeout=cve_socket_timeout) | 194 | response = urllib.request.urlopen(json_url, timeout=cve_socket_timeout) |
170 | if response: | 195 | if response: |
171 | update_db(conn, gzip.decompress(response.read()).decode('utf-8')) | 196 | update_db(d, conn, db_decompress(d, response.read(), is_nvd)) |
172 | conn.execute("insert or replace into META values (?, ?)", [year, last_modified]).close() | 197 | conn.execute("insert or replace into META values (?, ?)", [year, last_modified]).close() |
173 | except urllib.error.URLError as e: | 198 | except urllib.error.URLError as e: |
174 | cve_f.write('Warning: CVE db update error, CVE data is outdated.\n\n') | 199 | cve_f.write('Warning: CVE db update error, CVE data is outdated.\n\n') |
@@ -200,16 +225,22 @@ def initialize_db(conn): | |||
200 | 225 | ||
201 | c.close() | 226 | c.close() |
202 | 227 | ||
203 | def parse_node_and_insert(conn, node, cveId): | 228 | def parse_node_and_insert(conn, node, cveId, is_nvd): |
204 | # Parse children node if needed | 229 | # Parse children node if needed |
205 | for child in node.get('children', ()): | 230 | for child in node.get('children', ()): |
206 | parse_node_and_insert(conn, child, cveId) | 231 | parse_node_and_insert(conn, child, cveId, is_nvd) |
232 | |||
233 | def cpe_generator(is_nvd): | ||
234 | match_string = "cpeMatch" | ||
235 | cpe_string = 'criteria' | ||
236 | if is_nvd: | ||
237 | match_string = "cpe_match" | ||
238 | cpe_string = 'cpe23Uri' | ||
207 | 239 | ||
208 | def cpe_generator(): | 240 | for cpe in node.get(match_string, ()): |
209 | for cpe in node.get('cpe_match', ()): | ||
210 | if not cpe['vulnerable']: | 241 | if not cpe['vulnerable']: |
211 | return | 242 | return |
212 | cpe23 = cpe.get('cpe23Uri') | 243 | cpe23 = cpe.get(cpe_string) |
213 | if not cpe23: | 244 | if not cpe23: |
214 | return | 245 | return |
215 | cpe23 = cpe23.split(':') | 246 | cpe23 = cpe23.split(':') |
@@ -260,9 +291,9 @@ def parse_node_and_insert(conn, node, cveId): | |||
260 | # Save processing by representing as -. | 291 | # Save processing by representing as -. |
261 | yield [cveId, vendor, product, '-', '', '', ''] | 292 | yield [cveId, vendor, product, '-', '', '', ''] |
262 | 293 | ||
263 | conn.executemany("insert into PRODUCTS values (?, ?, ?, ?, ?, ?, ?)", cpe_generator()).close() | 294 | conn.executemany("insert into PRODUCTS values (?, ?, ?, ?, ?, ?, ?)", cpe_generator(is_nvd)).close() |
264 | 295 | ||
265 | def update_db(conn, jsondata): | 296 | def update_db_nvdjson(conn, jsondata): |
266 | import json | 297 | import json |
267 | root = json.loads(jsondata) | 298 | root = json.loads(jsondata) |
268 | 299 | ||
@@ -297,8 +328,77 @@ def update_db(conn, jsondata): | |||
297 | 328 | ||
298 | configurations = elt['configurations']['nodes'] | 329 | configurations = elt['configurations']['nodes'] |
299 | for config in configurations: | 330 | for config in configurations: |
300 | parse_node_and_insert(conn, config, cveId) | 331 | parse_node_and_insert(conn, config, cveId, True) |
332 | |||
333 | def update_db_fkie(conn, jsondata): | ||
334 | import json | ||
335 | root = json.loads(jsondata) | ||
336 | |||
337 | for elt in root['cve_items']: | ||
338 | if not 'vulnStatus' in elt or elt['vulnStatus'] == 'Rejected': | ||
339 | continue | ||
340 | |||
341 | if not 'configurations' in elt: | ||
342 | continue | ||
343 | |||
344 | accessVector = None | ||
345 | vectorString = None | ||
346 | cvssv2 = 0.0 | ||
347 | cvssv3 = 0.0 | ||
348 | cvssv4 = 0.0 | ||
349 | cveId = elt['id'] | ||
350 | cveDesc = elt['descriptions'][0]['value'] | ||
351 | date = elt['lastModified'] | ||
352 | try: | ||
353 | for m in elt['metrics']['cvssMetricV2']: | ||
354 | if m['type'] == 'Primary': | ||
355 | accessVector = m['cvssData']['accessVector'] | ||
356 | vectorString = m['cvssData']['vectorString'] | ||
357 | cvssv2 = m['cvssData']['baseScore'] | ||
358 | except KeyError: | ||
359 | cvssv2 = 0.0 | ||
360 | try: | ||
361 | for m in elt['metrics']['cvssMetricV30']: | ||
362 | if m['type'] == 'Primary': | ||
363 | accessVector = m['cvssData']['accessVector'] | ||
364 | vectorString = m['cvssData']['vectorString'] | ||
365 | cvssv3 = m['cvssData']['baseScore'] | ||
366 | except KeyError: | ||
367 | accessVector = accessVector or "UNKNOWN" | ||
368 | cvssv3 = 0.0 | ||
369 | try: | ||
370 | for m in elt['metrics']['cvssMetricV31']: | ||
371 | if m['type'] == 'Primary': | ||
372 | accessVector = m['cvssData']['accessVector'] | ||
373 | vectorString = m['cvssData']['vectorString'] | ||
374 | cvssv3 = m['cvssData']['baseScore'] | ||
375 | except KeyError: | ||
376 | accessVector = accessVector or "UNKNOWN" | ||
377 | cvssv3 = 0.0 | ||
378 | try: | ||
379 | for m in elt['metrics']['cvssMetricV40']: | ||
380 | if m['type'] == 'Primary': | ||
381 | accessVector = m['cvssData']['accessVector'] | ||
382 | vectorString = m['cvssData']['vectorString'] | ||
383 | cvssv4 = m['cvssData']['baseScore'] | ||
384 | except KeyError: | ||
385 | accessVector = accessVector or "UNKNOWN" | ||
386 | cvssv4 = 0.0 | ||
301 | 387 | ||
388 | conn.execute("insert or replace into NVD values (?, ?, ?, ?, ?, ?, ?, ?)", | ||
389 | [cveId, cveDesc, cvssv2, cvssv3, cvssv4, date, accessVector, vectorString]).close() | ||
390 | |||
391 | for config in elt['configurations']: | ||
392 | # This is suboptimal as it doesn't handle AND/OR and negate, but is better than nothing | ||
393 | for node in config["nodes"]: | ||
394 | parse_node_and_insert(conn, node, cveId, False) | ||
395 | |||
396 | |||
397 | def update_db(d, conn, jsondata): | ||
398 | if (d.getVar("NVD_DB_VERSION") == "FKIE"): | ||
399 | return update_db_fkie(conn, jsondata) | ||
400 | else: | ||
401 | return update_db_nvdjson(conn, jsondata) | ||
302 | 402 | ||
303 | do_fetch[nostamp] = "1" | 403 | do_fetch[nostamp] = "1" |
304 | 404 | ||