summaryrefslogtreecommitdiffstats
path: root/meta/classes/cve-check.bbclass
diff options
context:
space:
mode:
Diffstat (limited to 'meta/classes/cve-check.bbclass')
-rw-r--r--meta/classes/cve-check.bbclass345
1 files changed, 127 insertions, 218 deletions
diff --git a/meta/classes/cve-check.bbclass b/meta/classes/cve-check.bbclass
index 56ba8bceef..c63ebd56e1 100644
--- a/meta/classes/cve-check.bbclass
+++ b/meta/classes/cve-check.bbclass
@@ -31,25 +31,27 @@
31CVE_PRODUCT ??= "${BPN}" 31CVE_PRODUCT ??= "${BPN}"
32CVE_VERSION ??= "${PV}" 32CVE_VERSION ??= "${PV}"
33 33
34CVE_CHECK_DB_DIR ?= "${DL_DIR}/CVE_CHECK" 34# Possible database sources: NVD1, NVD2, FKIE
35CVE_CHECK_DB_FILE ?= "${CVE_CHECK_DB_DIR}/nvdcve_2-1.db" 35NVD_DB_VERSION ?= "FKIE"
36
37# Use different file names for each database source, as they synchronize at different moments, so may be slightly different
38CVE_CHECK_DB_FILENAME ?= "${@'nvdcve_2-2.db' if d.getVar('NVD_DB_VERSION') == 'NVD2' else 'nvdcve_1-3.db' if d.getVar('NVD_DB_VERSION') == 'NVD1' else 'nvdfkie_1-1.db'}"
39CVE_CHECK_DB_FETCHER ?= "${@'cve-update-nvd2-native' if d.getVar('NVD_DB_VERSION') == 'NVD2' else 'cve-update-db-native'}"
40CVE_CHECK_DB_DIR ?= "${STAGING_DIR}/CVE_CHECK"
41CVE_CHECK_DB_FILE ?= "${CVE_CHECK_DB_DIR}/${CVE_CHECK_DB_FILENAME}"
36CVE_CHECK_DB_FILE_LOCK ?= "${CVE_CHECK_DB_FILE}.lock" 42CVE_CHECK_DB_FILE_LOCK ?= "${CVE_CHECK_DB_FILE}.lock"
37 43
38CVE_CHECK_LOG ?= "${T}/cve.log"
39CVE_CHECK_TMP_FILE ?= "${TMPDIR}/cve_check"
40CVE_CHECK_SUMMARY_DIR ?= "${LOG_DIR}/cve" 44CVE_CHECK_SUMMARY_DIR ?= "${LOG_DIR}/cve"
41CVE_CHECK_SUMMARY_FILE_NAME ?= "cve-summary" 45CVE_CHECK_SUMMARY_FILE_NAME ?= "cve-summary"
42CVE_CHECK_SUMMARY_FILE ?= "${CVE_CHECK_SUMMARY_DIR}/${CVE_CHECK_SUMMARY_FILE_NAME}"
43CVE_CHECK_SUMMARY_FILE_NAME_JSON = "cve-summary.json" 46CVE_CHECK_SUMMARY_FILE_NAME_JSON = "cve-summary.json"
44CVE_CHECK_SUMMARY_INDEX_PATH = "${CVE_CHECK_SUMMARY_DIR}/cve-summary-index.txt" 47CVE_CHECK_SUMMARY_INDEX_PATH = "${CVE_CHECK_SUMMARY_DIR}/cve-summary-index.txt"
45 48
46CVE_CHECK_LOG_JSON ?= "${T}/cve.json" 49CVE_CHECK_LOG_JSON ?= "${T}/cve.json"
47 50
48CVE_CHECK_DIR ??= "${DEPLOY_DIR}/cve" 51CVE_CHECK_DIR ??= "${DEPLOY_DIR}/cve"
49CVE_CHECK_RECIPE_FILE ?= "${CVE_CHECK_DIR}/${PN}"
50CVE_CHECK_RECIPE_FILE_JSON ?= "${CVE_CHECK_DIR}/${PN}_cve.json" 52CVE_CHECK_RECIPE_FILE_JSON ?= "${CVE_CHECK_DIR}/${PN}_cve.json"
51CVE_CHECK_MANIFEST ?= "${IMGDEPLOYDIR}/${IMAGE_NAME}.cve" 53CVE_CHECK_MANIFEST_JSON_SUFFIX ?= "json"
52CVE_CHECK_MANIFEST_JSON ?= "${IMGDEPLOYDIR}/${IMAGE_NAME}.json" 54CVE_CHECK_MANIFEST_JSON ?= "${IMGDEPLOYDIR}/${IMAGE_NAME}.${CVE_CHECK_MANIFEST_JSON_SUFFIX}"
53CVE_CHECK_COPY_FILES ??= "1" 55CVE_CHECK_COPY_FILES ??= "1"
54CVE_CHECK_CREATE_MANIFEST ??= "1" 56CVE_CHECK_CREATE_MANIFEST ??= "1"
55 57
@@ -58,9 +60,6 @@ CVE_CHECK_REPORT_PATCHED ??= "1"
58 60
59CVE_CHECK_SHOW_WARNINGS ??= "1" 61CVE_CHECK_SHOW_WARNINGS ??= "1"
60 62
61# Provide text output
62CVE_CHECK_FORMAT_TEXT ??= "1"
63
64# Provide JSON output 63# Provide JSON output
65CVE_CHECK_FORMAT_JSON ??= "1" 64CVE_CHECK_FORMAT_JSON ??= "1"
66 65
@@ -105,21 +104,13 @@ CVE_CHECK_LAYER_INCLUDELIST ??= ""
105CVE_VERSION_SUFFIX ??= "" 104CVE_VERSION_SUFFIX ??= ""
106 105
107python () { 106python () {
108 # Fallback all CVEs from CVE_CHECK_IGNORE to CVE_STATUS 107 from oe.cve_check import extend_cve_status
109 cve_check_ignore = d.getVar("CVE_CHECK_IGNORE") 108 extend_cve_status(d)
110 if cve_check_ignore: 109
111 bb.warn("CVE_CHECK_IGNORE is deprecated in favor of CVE_STATUS") 110 nvd_database_type = d.getVar("NVD_DB_VERSION")
112 for cve in (d.getVar("CVE_CHECK_IGNORE") or "").split(): 111 if nvd_database_type not in ("NVD1", "NVD2", "FKIE"):
113 d.setVarFlag("CVE_STATUS", cve, "ignored") 112 bb.erroronce("Malformed NVD_DB_VERSION, must be one of: NVD1, NVD2, FKIE. Defaulting to NVD2")
114 113 d.setVar("NVD_DB_VERSION", "NVD2")
115 # Process CVE_STATUS_GROUPS to set multiple statuses and optional detail or description at once
116 for cve_status_group in (d.getVar("CVE_STATUS_GROUPS") or "").split():
117 cve_group = d.getVar(cve_status_group)
118 if cve_group is not None:
119 for cve in cve_group.split():
120 d.setVarFlag("CVE_STATUS", cve, d.getVarFlag(cve_status_group, "status"))
121 else:
122 bb.warn("CVE_STATUS_GROUPS contains undefined variable %s" % cve_status_group)
123} 114}
124 115
125def generate_json_report(d, out_path, link_path): 116def generate_json_report(d, out_path, link_path):
@@ -150,20 +141,11 @@ python cve_save_summary_handler () {
150 import datetime 141 import datetime
151 from oe.cve_check import update_symlinks 142 from oe.cve_check import update_symlinks
152 143
153 cve_tmp_file = d.getVar("CVE_CHECK_TMP_FILE")
154
155 cve_summary_name = d.getVar("CVE_CHECK_SUMMARY_FILE_NAME") 144 cve_summary_name = d.getVar("CVE_CHECK_SUMMARY_FILE_NAME")
156 cvelogpath = d.getVar("CVE_CHECK_SUMMARY_DIR") 145 cvelogpath = d.getVar("CVE_CHECK_SUMMARY_DIR")
157 bb.utils.mkdirhier(cvelogpath) 146 bb.utils.mkdirhier(cvelogpath)
158 147
159 timestamp = datetime.datetime.now().strftime('%Y%m%d%H%M%S') 148 timestamp = datetime.datetime.now().strftime('%Y%m%d%H%M%S')
160 cve_summary_file = os.path.join(cvelogpath, "%s-%s.txt" % (cve_summary_name, timestamp))
161
162 if os.path.exists(cve_tmp_file):
163 shutil.copyfile(cve_tmp_file, cve_summary_file)
164 cvefile_link = os.path.join(cvelogpath, cve_summary_name)
165 update_symlinks(cve_summary_file, cvefile_link)
166 bb.plain("Complete CVE report summary created at: %s" % cvefile_link)
167 149
168 if d.getVar("CVE_CHECK_FORMAT_JSON") == "1": 150 if d.getVar("CVE_CHECK_FORMAT_JSON") == "1":
169 json_summary_link_name = os.path.join(cvelogpath, d.getVar("CVE_CHECK_SUMMARY_FILE_NAME_JSON")) 151 json_summary_link_name = os.path.join(cvelogpath, d.getVar("CVE_CHECK_SUMMARY_FILE_NAME_JSON"))
@@ -187,24 +169,23 @@ python do_cve_check () {
187 patched_cves = get_patched_cves(d) 169 patched_cves = get_patched_cves(d)
188 except FileNotFoundError: 170 except FileNotFoundError:
189 bb.fatal("Failure in searching patches") 171 bb.fatal("Failure in searching patches")
190 ignored, patched, unpatched, status = check_cves(d, patched_cves) 172 cve_data, status = check_cves(d, patched_cves)
191 if patched or unpatched or (d.getVar("CVE_CHECK_COVERAGE") == "1" and status): 173 if len(cve_data) or (d.getVar("CVE_CHECK_COVERAGE") == "1" and status):
192 cve_data = get_cve_info(d, patched + unpatched + ignored) 174 get_cve_info(d, cve_data)
193 cve_write_data(d, patched, unpatched, ignored, cve_data, status) 175 cve_write_data(d, cve_data, status)
194 else: 176 else:
195 bb.note("No CVE database found, skipping CVE check") 177 bb.note("No CVE database found, skipping CVE check")
196 178
197} 179}
198 180
199addtask cve_check before do_build 181addtask cve_check before do_build
200do_cve_check[depends] = "cve-update-nvd2-native:do_fetch" 182do_cve_check[depends] = "${CVE_CHECK_DB_FETCHER}:do_unpack"
201do_cve_check[nostamp] = "1" 183do_cve_check[nostamp] = "1"
202 184
203python cve_check_cleanup () { 185python cve_check_cleanup () {
204 """ 186 """
205 Delete the file used to gather all the CVE information. 187 Delete the file used to gather all the CVE information.
206 """ 188 """
207 bb.utils.remove(e.data.getVar("CVE_CHECK_TMP_FILE"))
208 bb.utils.remove(e.data.getVar("CVE_CHECK_SUMMARY_INDEX_PATH")) 189 bb.utils.remove(e.data.getVar("CVE_CHECK_SUMMARY_INDEX_PATH"))
209} 190}
210 191
@@ -222,9 +203,6 @@ python cve_check_write_rootfs_manifest () {
222 from oe.cve_check import cve_check_merge_jsons, update_symlinks 203 from oe.cve_check import cve_check_merge_jsons, update_symlinks
223 204
224 if d.getVar("CVE_CHECK_COPY_FILES") == "1": 205 if d.getVar("CVE_CHECK_COPY_FILES") == "1":
225 deploy_file = d.getVar("CVE_CHECK_RECIPE_FILE")
226 if os.path.exists(deploy_file):
227 bb.utils.remove(deploy_file)
228 deploy_file_json = d.getVar("CVE_CHECK_RECIPE_FILE_JSON") 206 deploy_file_json = d.getVar("CVE_CHECK_RECIPE_FILE_JSON")
229 if os.path.exists(deploy_file_json): 207 if os.path.exists(deploy_file_json):
230 bb.utils.remove(deploy_file_json) 208 bb.utils.remove(deploy_file_json)
@@ -244,19 +222,13 @@ python cve_check_write_rootfs_manifest () {
244 json_data = {"version":"1", "package": []} 222 json_data = {"version":"1", "package": []}
245 text_data = "" 223 text_data = ""
246 enable_json = d.getVar("CVE_CHECK_FORMAT_JSON") == "1" 224 enable_json = d.getVar("CVE_CHECK_FORMAT_JSON") == "1"
247 enable_text = d.getVar("CVE_CHECK_FORMAT_TEXT") == "1"
248 225
249 save_pn = d.getVar("PN") 226 save_pn = d.getVar("PN")
250 227
251 for pkg in recipies: 228 for pkg in recipies:
252 # To be able to use the CVE_CHECK_RECIPE_FILE variable we have to evaluate 229 # To be able to use the CVE_CHECK_RECIPE_FILE_JSON variable we have to evaluate
253 # it with the different PN names set each time. 230 # it with the different PN names set each time.
254 d.setVar("PN", pkg) 231 d.setVar("PN", pkg)
255 if enable_text:
256 pkgfilepath = d.getVar("CVE_CHECK_RECIPE_FILE")
257 if os.path.exists(pkgfilepath):
258 with open(pkgfilepath) as pfile:
259 text_data += pfile.read()
260 232
261 if enable_json: 233 if enable_json:
262 pkgfilepath = d.getVar("CVE_CHECK_RECIPE_FILE_JSON") 234 pkgfilepath = d.getVar("CVE_CHECK_RECIPE_FILE_JSON")
@@ -267,24 +239,17 @@ python cve_check_write_rootfs_manifest () {
267 239
268 d.setVar("PN", save_pn) 240 d.setVar("PN", save_pn)
269 241
270 if enable_text:
271 link_path = os.path.join(deploy_dir, "%s.cve" % link_name)
272 manifest_name = d.getVar("CVE_CHECK_MANIFEST")
273
274 with open(manifest_name, "w") as f:
275 f.write(text_data)
276
277 update_symlinks(manifest_name, link_path)
278 bb.plain("Image CVE report stored in: %s" % manifest_name)
279
280 if enable_json: 242 if enable_json:
281 link_path = os.path.join(deploy_dir, "%s.json" % link_name) 243 manifest_name_suffix = d.getVar("CVE_CHECK_MANIFEST_JSON_SUFFIX")
282 manifest_name = d.getVar("CVE_CHECK_MANIFEST_JSON") 244 manifest_name = d.getVar("CVE_CHECK_MANIFEST_JSON")
283 245
284 with open(manifest_name, "w") as f: 246 with open(manifest_name, "w") as f:
285 json.dump(json_data, f, indent=2) 247 json.dump(json_data, f, indent=2)
286 248
287 update_symlinks(manifest_name, link_path) 249 if link_name:
250 link_path = os.path.join(deploy_dir, "%s.%s" % (link_name, manifest_name_suffix))
251 update_symlinks(manifest_name, link_path)
252
288 bb.plain("Image CVE JSON report stored in: %s" % manifest_name) 253 bb.plain("Image CVE JSON report stored in: %s" % manifest_name)
289} 254}
290 255
@@ -292,7 +257,51 @@ ROOTFS_POSTPROCESS_COMMAND:prepend = "${@'cve_check_write_rootfs_manifest ' if d
292do_rootfs[recrdeptask] += "${@'do_cve_check' if d.getVar('CVE_CHECK_CREATE_MANIFEST') == '1' else ''}" 257do_rootfs[recrdeptask] += "${@'do_cve_check' if d.getVar('CVE_CHECK_CREATE_MANIFEST') == '1' else ''}"
293do_populate_sdk[recrdeptask] += "${@'do_cve_check' if d.getVar('CVE_CHECK_CREATE_MANIFEST') == '1' else ''}" 258do_populate_sdk[recrdeptask] += "${@'do_cve_check' if d.getVar('CVE_CHECK_CREATE_MANIFEST') == '1' else ''}"
294 259
295def check_cves(d, patched_cves): 260def cve_is_ignored(d, cve_data, cve):
261 if cve not in cve_data:
262 return False
263 if cve_data[cve]['abbrev-status'] == "Ignored":
264 return True
265 return False
266
267def cve_is_patched(d, cve_data, cve):
268 if cve not in cve_data:
269 return False
270 if cve_data[cve]['abbrev-status'] == "Patched":
271 return True
272 return False
273
274def cve_update(d, cve_data, cve, entry):
275 # If no entry, just add it
276 if cve not in cve_data:
277 cve_data[cve] = entry
278 return
279 # If we are updating, there might be change in the status
280 bb.debug(1, "Trying CVE entry update for %s from %s to %s" % (cve, cve_data[cve]['abbrev-status'], entry['abbrev-status']))
281 if cve_data[cve]['abbrev-status'] == "Unknown":
282 cve_data[cve] = entry
283 return
284 if cve_data[cve]['abbrev-status'] == entry['abbrev-status']:
285 return
286 # Update like in {'abbrev-status': 'Patched', 'status': 'version-not-in-range'} to {'abbrev-status': 'Unpatched', 'status': 'version-in-range'}
287 if entry['abbrev-status'] == "Unpatched" and cve_data[cve]['abbrev-status'] == "Patched":
288 if entry['status'] == "version-in-range" and cve_data[cve]['status'] == "version-not-in-range":
289 # New result from the scan, vulnerable
290 cve_data[cve] = entry
291 bb.debug(1, "CVE entry %s update from Patched to Unpatched from the scan result" % cve)
292 return
293 if entry['abbrev-status'] == "Patched" and cve_data[cve]['abbrev-status'] == "Unpatched":
294 if entry['status'] == "version-not-in-range" and cve_data[cve]['status'] == "version-in-range":
295 # Range does not match the scan, but we already have a vulnerable match, ignore
296 bb.debug(1, "CVE entry %s update from Patched to Unpatched from the scan result - not applying" % cve)
297 return
298 # If we have an "Ignored", it has a priority
299 if cve_data[cve]['abbrev-status'] == "Ignored":
300 bb.debug(1, "CVE %s not updating because Ignored" % cve)
301 return
302 bb.warn("Unhandled CVE entry update for %s from %s to %s" % (cve, cve_data[cve], entry))
303
304def check_cves(d, cve_data):
296 """ 305 """
297 Connect to the NVD database and find unpatched cves. 306 Connect to the NVD database and find unpatched cves.
298 """ 307 """
@@ -302,28 +311,19 @@ def check_cves(d, patched_cves):
302 real_pv = d.getVar("PV") 311 real_pv = d.getVar("PV")
303 suffix = d.getVar("CVE_VERSION_SUFFIX") 312 suffix = d.getVar("CVE_VERSION_SUFFIX")
304 313
305 cves_unpatched = []
306 cves_ignored = []
307 cves_status = [] 314 cves_status = []
308 cves_in_recipe = False 315 cves_in_recipe = False
309 # CVE_PRODUCT can contain more than one product (eg. curl/libcurl) 316 # CVE_PRODUCT can contain more than one product (eg. curl/libcurl)
310 products = d.getVar("CVE_PRODUCT").split() 317 products = d.getVar("CVE_PRODUCT").split()
311 # If this has been unset then we're not scanning for CVEs here (for example, image recipes) 318 # If this has been unset then we're not scanning for CVEs here (for example, image recipes)
312 if not products: 319 if not products:
313 return ([], [], [], []) 320 return ([], [])
314 pv = d.getVar("CVE_VERSION").split("+git")[0] 321 pv = d.getVar("CVE_VERSION").split("+git")[0]
315 322
316 # If the recipe has been skipped/ignored we return empty lists 323 # If the recipe has been skipped/ignored we return empty lists
317 if pn in d.getVar("CVE_CHECK_SKIP_RECIPE").split(): 324 if pn in d.getVar("CVE_CHECK_SKIP_RECIPE").split():
318 bb.note("Recipe has been skipped by cve-check") 325 bb.note("Recipe has been skipped by cve-check")
319 return ([], [], [], []) 326 return ([], [])
320
321 # Convert CVE_STATUS into ignored CVEs and check validity
322 cve_ignore = []
323 for cve in (d.getVarFlags("CVE_STATUS") or {}):
324 decoded_status, _, _ = decode_cve_status(d, cve)
325 if decoded_status == "Ignored":
326 cve_ignore.append(cve)
327 327
328 import sqlite3 328 import sqlite3
329 db_file = d.expand("file:${CVE_CHECK_DB_FILE}?mode=ro") 329 db_file = d.expand("file:${CVE_CHECK_DB_FILE}?mode=ro")
@@ -342,19 +342,19 @@ def check_cves(d, patched_cves):
342 for cverow in cve_cursor: 342 for cverow in cve_cursor:
343 cve = cverow[0] 343 cve = cverow[0]
344 344
345 if cve in cve_ignore:
346 bb.note("%s-%s ignores %s" % (product, pv, cve))
347 cves_ignored.append(cve)
348 continue
349 elif cve in patched_cves:
350 bb.note("%s has been patched" % (cve))
351 continue
352 # Write status once only for each product 345 # Write status once only for each product
353 if not cves_in_product: 346 if not cves_in_product:
354 cves_status.append([product, True]) 347 cves_status.append([product, True])
355 cves_in_product = True 348 cves_in_product = True
356 cves_in_recipe = True 349 cves_in_recipe = True
357 350
351 if cve_is_ignored(d, cve_data, cve):
352 bb.note("%s-%s ignores %s" % (product, pv, cve))
353 continue
354 elif cve_is_patched(d, cve_data, cve):
355 bb.note("%s has been patched" % (cve))
356 continue
357
358 vulnerable = False 358 vulnerable = False
359 ignored = False 359 ignored = False
360 360
@@ -362,7 +362,7 @@ def check_cves(d, patched_cves):
362 for row in product_cursor: 362 for row in product_cursor:
363 (_, _, _, version_start, operator_start, version_end, operator_end) = row 363 (_, _, _, version_start, operator_start, version_end, operator_end) = row
364 #bb.debug(2, "Evaluating row " + str(row)) 364 #bb.debug(2, "Evaluating row " + str(row))
365 if cve in cve_ignore: 365 if cve_is_ignored(d, cve_data, cve):
366 ignored = True 366 ignored = True
367 367
368 version_start = convert_cve_version(version_start) 368 version_start = convert_cve_version(version_start)
@@ -401,16 +401,16 @@ def check_cves(d, patched_cves):
401 if vulnerable: 401 if vulnerable:
402 if ignored: 402 if ignored:
403 bb.note("%s is ignored in %s-%s" % (cve, pn, real_pv)) 403 bb.note("%s is ignored in %s-%s" % (cve, pn, real_pv))
404 cves_ignored.append(cve) 404 cve_update(d, cve_data, cve, {"abbrev-status": "Ignored"})
405 else: 405 else:
406 bb.note("%s-%s is vulnerable to %s" % (pn, real_pv, cve)) 406 bb.note("%s-%s is vulnerable to %s" % (pn, real_pv, cve))
407 cves_unpatched.append(cve) 407 cve_update(d, cve_data, cve, {"abbrev-status": "Unpatched", "status": "version-in-range"})
408 break 408 break
409 product_cursor.close() 409 product_cursor.close()
410 410
411 if not vulnerable: 411 if not vulnerable:
412 bb.note("%s-%s is not vulnerable to %s" % (pn, real_pv, cve)) 412 bb.note("%s-%s is not vulnerable to %s" % (pn, real_pv, cve))
413 patched_cves.add(cve) 413 cve_update(d, cve_data, cve, {"abbrev-status": "Patched", "status": "version-not-in-range"})
414 cve_cursor.close() 414 cve_cursor.close()
415 415
416 if not cves_in_product: 416 if not cves_in_product:
@@ -418,123 +418,44 @@ def check_cves(d, patched_cves):
418 cves_status.append([product, False]) 418 cves_status.append([product, False])
419 419
420 conn.close() 420 conn.close()
421 diff_ignore = list(set(cve_ignore) - set(cves_ignored))
422 if diff_ignore:
423 oe.qa.handle_error("cve_status_not_in_db", "Found CVE (%s) with CVE_STATUS set that are not found in database for this component" % " ".join(diff_ignore), d)
424 421
425 if not cves_in_recipe: 422 if not cves_in_recipe:
426 bb.note("No CVE records for products in recipe %s" % (pn)) 423 bb.note("No CVE records for products in recipe %s" % (pn))
427 424
428 return (list(cves_ignored), list(patched_cves), cves_unpatched, cves_status) 425 if d.getVar("CVE_CHECK_SHOW_WARNINGS") == "1":
426 unpatched_cves = [cve for cve in cve_data if cve_data[cve]["abbrev-status"] == "Unpatched"]
427 if unpatched_cves:
428 bb.warn("Found unpatched CVE (%s)" % " ".join(unpatched_cves))
429
430 return (cve_data, cves_status)
429 431
430def get_cve_info(d, cves): 432def get_cve_info(d, cve_data):
431 """ 433 """
432 Get CVE information from the database. 434 Get CVE information from the database.
433 """ 435 """
434 436
435 import sqlite3 437 import sqlite3
436 438
437 cve_data = {}
438 db_file = d.expand("file:${CVE_CHECK_DB_FILE}?mode=ro") 439 db_file = d.expand("file:${CVE_CHECK_DB_FILE}?mode=ro")
439 conn = sqlite3.connect(db_file, uri=True) 440 conn = sqlite3.connect(db_file, uri=True)
440 441
441 for cve in cves: 442 for cve in cve_data:
442 cursor = conn.execute("SELECT * FROM NVD WHERE ID IS ?", (cve,)) 443 cursor = conn.execute("SELECT * FROM NVD WHERE ID IS ?", (cve,))
443 for row in cursor: 444 for row in cursor:
444 cve_data[row[0]] = {} 445 # The CVE itdelf has been added already
445 cve_data[row[0]]["summary"] = row[1] 446 if row[0] not in cve_data:
446 cve_data[row[0]]["scorev2"] = row[2] 447 bb.note("CVE record %s not present" % row[0])
447 cve_data[row[0]]["scorev3"] = row[3] 448 continue
448 cve_data[row[0]]["modified"] = row[4] 449 #cve_data[row[0]] = {}
449 cve_data[row[0]]["vector"] = row[5] 450 cve_data[row[0]]["NVD-summary"] = row[1]
450 cve_data[row[0]]["vectorString"] = row[6] 451 cve_data[row[0]]["NVD-scorev2"] = row[2]
452 cve_data[row[0]]["NVD-scorev3"] = row[3]
453 cve_data[row[0]]["NVD-scorev4"] = row[4]
454 cve_data[row[0]]["NVD-modified"] = row[5]
455 cve_data[row[0]]["NVD-vector"] = row[6]
456 cve_data[row[0]]["NVD-vectorString"] = row[7]
451 cursor.close() 457 cursor.close()
452 conn.close() 458 conn.close()
453 return cve_data
454
455def cve_write_data_text(d, patched, unpatched, ignored, cve_data):
456 """
457 Write CVE information in WORKDIR; and to CVE_CHECK_DIR, and
458 CVE manifest if enabled.
459 """
460
461 from oe.cve_check import decode_cve_status
462
463 cve_file = d.getVar("CVE_CHECK_LOG")
464 fdir_name = d.getVar("FILE_DIRNAME")
465 layer = fdir_name.split("/")[-3]
466
467 include_layers = d.getVar("CVE_CHECK_LAYER_INCLUDELIST").split()
468 exclude_layers = d.getVar("CVE_CHECK_LAYER_EXCLUDELIST").split()
469
470 report_all = d.getVar("CVE_CHECK_REPORT_PATCHED") == "1"
471
472 if exclude_layers and layer in exclude_layers:
473 return
474
475 if include_layers and layer not in include_layers:
476 return
477
478 # Early exit, the text format does not report packages without CVEs
479 if not patched+unpatched+ignored:
480 return
481
482 nvd_link = "https://nvd.nist.gov/vuln/detail/"
483 write_string = ""
484 unpatched_cves = []
485 bb.utils.mkdirhier(os.path.dirname(cve_file))
486
487 for cve in sorted(cve_data):
488 is_patched = cve in patched
489 is_ignored = cve in ignored
490
491 status = "Unpatched"
492 if (is_patched or is_ignored) and not report_all:
493 continue
494 if is_ignored:
495 status = "Ignored"
496 elif is_patched:
497 status = "Patched"
498 else:
499 # default value of status is Unpatched
500 unpatched_cves.append(cve)
501
502 write_string += "LAYER: %s\n" % layer
503 write_string += "PACKAGE NAME: %s\n" % d.getVar("PN")
504 write_string += "PACKAGE VERSION: %s%s\n" % (d.getVar("EXTENDPE"), d.getVar("PV"))
505 write_string += "CVE: %s\n" % cve
506 write_string += "CVE STATUS: %s\n" % status
507 _, detail, description = decode_cve_status(d, cve)
508 if detail:
509 write_string += "CVE DETAIL: %s\n" % detail
510 if description:
511 write_string += "CVE DESCRIPTION: %s\n" % description
512 write_string += "CVE SUMMARY: %s\n" % cve_data[cve]["summary"]
513 write_string += "CVSS v2 BASE SCORE: %s\n" % cve_data[cve]["scorev2"]
514 write_string += "CVSS v3 BASE SCORE: %s\n" % cve_data[cve]["scorev3"]
515 write_string += "VECTOR: %s\n" % cve_data[cve]["vector"]
516 write_string += "VECTORSTRING: %s\n" % cve_data[cve]["vectorString"]
517 write_string += "MORE INFORMATION: %s%s\n\n" % (nvd_link, cve)
518
519 if unpatched_cves and d.getVar("CVE_CHECK_SHOW_WARNINGS") == "1":
520 bb.warn("Found unpatched CVE (%s), for more information check %s" % (" ".join(unpatched_cves),cve_file))
521
522 with open(cve_file, "w") as f:
523 bb.note("Writing file %s with CVE information" % cve_file)
524 f.write(write_string)
525
526 if d.getVar("CVE_CHECK_COPY_FILES") == "1":
527 deploy_file = d.getVar("CVE_CHECK_RECIPE_FILE")
528 bb.utils.mkdirhier(os.path.dirname(deploy_file))
529 with open(deploy_file, "w") as f:
530 f.write(write_string)
531
532 if d.getVar("CVE_CHECK_CREATE_MANIFEST") == "1":
533 cvelogpath = d.getVar("CVE_CHECK_SUMMARY_DIR")
534 bb.utils.mkdirhier(cvelogpath)
535
536 with open(d.getVar("CVE_CHECK_TMP_FILE"), "a") as f:
537 f.write("%s" % write_string)
538 459
539def cve_check_write_json_output(d, output, direct_file, deploy_file, manifest_file): 460def cve_check_write_json_output(d, output, direct_file, deploy_file, manifest_file):
540 """ 461 """
@@ -566,13 +487,11 @@ def cve_check_write_json_output(d, output, direct_file, deploy_file, manifest_fi
566 with open(index_path, "a+") as f: 487 with open(index_path, "a+") as f:
567 f.write("%s\n" % fragment_path) 488 f.write("%s\n" % fragment_path)
568 489
569def cve_write_data_json(d, patched, unpatched, ignored, cve_data, cve_status): 490def cve_write_data_json(d, cve_data, cve_status):
570 """ 491 """
571 Prepare CVE data for the JSON format, then write it. 492 Prepare CVE data for the JSON format, then write it.
572 """ 493 """
573 494
574 from oe.cve_check import decode_cve_status
575
576 output = {"version":"1", "package": []} 495 output = {"version":"1", "package": []}
577 nvd_link = "https://nvd.nist.gov/vuln/detail/" 496 nvd_link = "https://nvd.nist.gov/vuln/detail/"
578 497
@@ -590,8 +509,6 @@ def cve_write_data_json(d, patched, unpatched, ignored, cve_data, cve_status):
590 if include_layers and layer not in include_layers: 509 if include_layers and layer not in include_layers:
591 return 510 return
592 511
593 unpatched_cves = []
594
595 product_data = [] 512 product_data = []
596 for s in cve_status: 513 for s in cve_status:
597 p = {"product": s[0], "cvesInRecord": "Yes"} 514 p = {"product": s[0], "cvesInRecord": "Yes"}
@@ -606,39 +523,33 @@ def cve_write_data_json(d, patched, unpatched, ignored, cve_data, cve_status):
606 "version" : package_version, 523 "version" : package_version,
607 "products": product_data 524 "products": product_data
608 } 525 }
526
609 cve_list = [] 527 cve_list = []
610 528
611 for cve in sorted(cve_data): 529 for cve in sorted(cve_data):
612 is_patched = cve in patched 530 if not report_all and (cve_data[cve]["abbrev-status"] == "Patched" or cve_data[cve]["abbrev-status"] == "Ignored"):
613 is_ignored = cve in ignored
614 status = "Unpatched"
615 if (is_patched or is_ignored) and not report_all:
616 continue 531 continue
617 if is_ignored:
618 status = "Ignored"
619 elif is_patched:
620 status = "Patched"
621 else:
622 # default value of status is Unpatched
623 unpatched_cves.append(cve)
624
625 issue_link = "%s%s" % (nvd_link, cve) 532 issue_link = "%s%s" % (nvd_link, cve)
626 533
627 cve_item = { 534 cve_item = {
628 "id" : cve, 535 "id" : cve,
629 "summary" : cve_data[cve]["summary"], 536 "status" : cve_data[cve]["abbrev-status"],
630 "scorev2" : cve_data[cve]["scorev2"], 537 "link": issue_link,
631 "scorev3" : cve_data[cve]["scorev3"],
632 "vector" : cve_data[cve]["vector"],
633 "vectorString" : cve_data[cve]["vectorString"],
634 "status" : status,
635 "link": issue_link
636 } 538 }
637 _, detail, description = decode_cve_status(d, cve) 539 if 'NVD-summary' in cve_data[cve]:
638 if detail: 540 cve_item["summary"] = cve_data[cve]["NVD-summary"]
639 cve_item["detail"] = detail 541 cve_item["scorev2"] = cve_data[cve]["NVD-scorev2"]
640 if description: 542 cve_item["scorev3"] = cve_data[cve]["NVD-scorev3"]
641 cve_item["description"] = description 543 cve_item["scorev4"] = cve_data[cve]["NVD-scorev4"]
544 cve_item["modified"] = cve_data[cve]["NVD-modified"]
545 cve_item["vector"] = cve_data[cve]["NVD-vector"]
546 cve_item["vectorString"] = cve_data[cve]["NVD-vectorString"]
547 if 'status' in cve_data[cve]:
548 cve_item["detail"] = cve_data[cve]["status"]
549 if 'justification' in cve_data[cve]:
550 cve_item["description"] = cve_data[cve]["justification"]
551 if 'resource' in cve_data[cve]:
552 cve_item["patch-file"] = cve_data[cve]["resource"]
642 cve_list.append(cve_item) 553 cve_list.append(cve_item)
643 554
644 package_data["issue"] = cve_list 555 package_data["issue"] = cve_list
@@ -650,12 +561,10 @@ def cve_write_data_json(d, patched, unpatched, ignored, cve_data, cve_status):
650 561
651 cve_check_write_json_output(d, output, direct_file, deploy_file, manifest_file) 562 cve_check_write_json_output(d, output, direct_file, deploy_file, manifest_file)
652 563
653def cve_write_data(d, patched, unpatched, ignored, cve_data, status): 564def cve_write_data(d, cve_data, status):
654 """ 565 """
655 Write CVE data in each enabled format. 566 Write CVE data in each enabled format.
656 """ 567 """
657 568
658 if d.getVar("CVE_CHECK_FORMAT_TEXT") == "1":
659 cve_write_data_text(d, patched, unpatched, ignored, cve_data)
660 if d.getVar("CVE_CHECK_FORMAT_JSON") == "1": 569 if d.getVar("CVE_CHECK_FORMAT_JSON") == "1":
661 cve_write_data_json(d, patched, unpatched, ignored, cve_data, status) 570 cve_write_data_json(d, cve_data, status)