1# This class is used to check recipes against public CVEs. 2# 3# In order to use this class just inherit the class in the 4# local.conf file and it will add the cve_check task for 5# every recipe. The task can be used per recipe, per image, 6# or using the special cases "world" and "universe". The 7# cve_check task will print a warning for every unpatched 8# CVE found and generate a file in the recipe WORKDIR/cve 9# directory. If an image is build it will generate a report 10# in DEPLOY_DIR_IMAGE for all the packages used. 11# 12# Example: 13# bitbake -c cve_check openssl 14# bitbake core-image-sato 15# bitbake -k -c cve_check universe 16# 17# DISCLAIMER 18# 19# This class/tool is meant to be used as support and not 20# the only method to check against CVEs. Running this tool 21# doesn't guarantee your packages are free of CVEs. 22 23# The product name that the CVE database uses defaults to BPN, but may need to 24# be overriden per recipe (for example tiff.bb sets CVE_PRODUCT=libtiff). 25CVE_PRODUCT ??= "${BPN}" 26CVE_VERSION ??= "${PV}" 27 28CVE_CHECK_DB_DIR ?= "${DL_DIR}/CVE_CHECK" 29CVE_CHECK_DB_FILE ?= "${CVE_CHECK_DB_DIR}/nvdcve_1.1.db" 30CVE_CHECK_DB_FILE_LOCK ?= "${CVE_CHECK_DB_FILE}.lock" 31 32CVE_CHECK_LOG ?= "${T}/cve.log" 33CVE_CHECK_TMP_FILE ?= "${TMPDIR}/cve_check" 34CVE_CHECK_SUMMARY_DIR ?= "${LOG_DIR}/cve" 35CVE_CHECK_SUMMARY_FILE_NAME ?= "cve-summary" 36CVE_CHECK_SUMMARY_FILE ?= "${CVE_CHECK_SUMMARY_DIR}/${CVE_CHECK_SUMMARY_FILE_NAME}" 37CVE_CHECK_SUMMARY_FILE_NAME_JSON = "cve-summary.json" 38CVE_CHECK_SUMMARY_INDEX_PATH = "${CVE_CHECK_SUMMARY_DIR}/cve-summary-index.txt" 39 40CVE_CHECK_LOG_JSON ?= "${T}/cve.json" 41 42CVE_CHECK_DIR ??= "${DEPLOY_DIR}/cve" 43CVE_CHECK_RECIPE_FILE ?= "${CVE_CHECK_DIR}/${PN}" 44CVE_CHECK_RECIPE_FILE_JSON ?= "${CVE_CHECK_DIR}/${PN}_cve.json" 45CVE_CHECK_MANIFEST ?= "${IMGDEPLOYDIR}/${IMAGE_NAME}${IMAGE_NAME_SUFFIX}.cve" 46CVE_CHECK_MANIFEST_JSON ?= "${IMGDEPLOYDIR}/${IMAGE_NAME}${IMAGE_NAME_SUFFIX}.json" 47CVE_CHECK_COPY_FILES ??= "1" 48CVE_CHECK_CREATE_MANIFEST ??= "1" 49 50# Report Patched or Ignored CVEs 51CVE_CHECK_REPORT_PATCHED ??= "1" 52 53CVE_CHECK_SHOW_WARNINGS ??= "1" 54 55# Provide text output 56CVE_CHECK_FORMAT_TEXT ??= "1" 57 58# Provide JSON output 59CVE_CHECK_FORMAT_JSON ??= "1" 60 61# Check for packages without CVEs (no issues or missing product name) 62CVE_CHECK_COVERAGE ??= "1" 63 64# Skip CVE Check for packages (PN) 65CVE_CHECK_SKIP_RECIPE ?= "" 66 67# Ingore the check for a given list of CVEs. If a CVE is found, 68# then it is considered patched. The value is a string containing 69# space separated CVE values: 70# 71# CVE_CHECK_IGNORE = 'CVE-2014-2524 CVE-2018-1234' 72# 73CVE_CHECK_IGNORE ?= "" 74 75# Layers to be excluded 76CVE_CHECK_LAYER_EXCLUDELIST ??= "" 77 78# Layers to be included 79CVE_CHECK_LAYER_INCLUDELIST ??= "" 80 81 82# set to "alphabetical" for version using single alphabetical character as increment release 83CVE_VERSION_SUFFIX ??= "" 84 85def generate_json_report(d, out_path, link_path): 86 if os.path.exists(d.getVar("CVE_CHECK_SUMMARY_INDEX_PATH")): 87 import json 88 from oe.cve_check import cve_check_merge_jsons, update_symlinks 89 90 bb.note("Generating JSON CVE summary") 91 index_file = d.getVar("CVE_CHECK_SUMMARY_INDEX_PATH") 92 summary = {"version":"1", "package": []} 93 with open(index_file) as f: 94 filename = f.readline() 95 while filename: 96 with open(filename.rstrip()) as j: 97 data = json.load(j) 98 cve_check_merge_jsons(summary, data) 99 filename = f.readline() 100 101 with open(out_path, "w") as f: 102 json.dump(summary, f, indent=2) 103 104 update_symlinks(out_path, link_path) 105 106python cve_save_summary_handler () { 107 import shutil 108 import datetime 109 from oe.cve_check import update_symlinks 110 111 cve_tmp_file = d.getVar("CVE_CHECK_TMP_FILE") 112 113 cve_summary_name = d.getVar("CVE_CHECK_SUMMARY_FILE_NAME") 114 cvelogpath = d.getVar("CVE_CHECK_SUMMARY_DIR") 115 bb.utils.mkdirhier(cvelogpath) 116 117 timestamp = datetime.datetime.now().strftime('%Y%m%d%H%M%S') 118 cve_summary_file = os.path.join(cvelogpath, "%s-%s.txt" % (cve_summary_name, timestamp)) 119 120 if os.path.exists(cve_tmp_file): 121 shutil.copyfile(cve_tmp_file, cve_summary_file) 122 cvefile_link = os.path.join(cvelogpath, cve_summary_name) 123 update_symlinks(cve_summary_file, cvefile_link) 124 bb.plain("Complete CVE report summary created at: %s" % cvefile_link) 125 126 if d.getVar("CVE_CHECK_FORMAT_JSON") == "1": 127 json_summary_link_name = os.path.join(cvelogpath, d.getVar("CVE_CHECK_SUMMARY_FILE_NAME_JSON")) 128 json_summary_name = os.path.join(cvelogpath, "%s-%s.json" % (cve_summary_name, timestamp)) 129 generate_json_report(d, json_summary_name, json_summary_link_name) 130 bb.plain("Complete CVE JSON report summary created at: %s" % json_summary_link_name) 131} 132 133addhandler cve_save_summary_handler 134cve_save_summary_handler[eventmask] = "bb.event.BuildCompleted" 135 136python do_cve_check () { 137 """ 138 Check recipe for patched and unpatched CVEs 139 """ 140 from oe.cve_check import get_patched_cves 141 142 with bb.utils.fileslocked([d.getVar("CVE_CHECK_DB_FILE_LOCK")], shared=True): 143 if os.path.exists(d.getVar("CVE_CHECK_DB_FILE")): 144 try: 145 patched_cves = get_patched_cves(d) 146 except FileNotFoundError: 147 bb.fatal("Failure in searching patches") 148 ignored, patched, unpatched, status = check_cves(d, patched_cves) 149 if patched or unpatched or (d.getVar("CVE_CHECK_COVERAGE") == "1" and status): 150 cve_data = get_cve_info(d, patched + unpatched + ignored) 151 cve_write_data(d, patched, unpatched, ignored, cve_data, status) 152 else: 153 bb.note("No CVE database found, skipping CVE check") 154 155} 156 157addtask cve_check before do_build 158do_cve_check[depends] = "cve-update-db-native:do_fetch" 159do_cve_check[nostamp] = "1" 160 161python cve_check_cleanup () { 162 """ 163 Delete the file used to gather all the CVE information. 164 """ 165 bb.utils.remove(e.data.getVar("CVE_CHECK_TMP_FILE")) 166 bb.utils.remove(e.data.getVar("CVE_CHECK_SUMMARY_INDEX_PATH")) 167} 168 169addhandler cve_check_cleanup 170cve_check_cleanup[eventmask] = "bb.event.BuildCompleted" 171 172python cve_check_write_rootfs_manifest () { 173 """ 174 Create CVE manifest when building an image 175 """ 176 177 import shutil 178 import json 179 from oe.rootfs import image_list_installed_packages 180 from oe.cve_check import cve_check_merge_jsons, update_symlinks 181 182 if d.getVar("CVE_CHECK_COPY_FILES") == "1": 183 deploy_file = d.getVar("CVE_CHECK_RECIPE_FILE") 184 if os.path.exists(deploy_file): 185 bb.utils.remove(deploy_file) 186 deploy_file_json = d.getVar("CVE_CHECK_RECIPE_FILE_JSON") 187 if os.path.exists(deploy_file_json): 188 bb.utils.remove(deploy_file_json) 189 190 # Create a list of relevant recipies 191 recipies = set() 192 for pkg in list(image_list_installed_packages(d)): 193 pkg_info = os.path.join(d.getVar('PKGDATA_DIR'), 194 'runtime-reverse', pkg) 195 pkg_data = oe.packagedata.read_pkgdatafile(pkg_info) 196 recipies.add(pkg_data["PN"]) 197 198 bb.note("Writing rootfs CVE manifest") 199 deploy_dir = d.getVar("IMGDEPLOYDIR") 200 link_name = d.getVar("IMAGE_LINK_NAME") 201 202 json_data = {"version":"1", "package": []} 203 text_data = "" 204 enable_json = d.getVar("CVE_CHECK_FORMAT_JSON") == "1" 205 enable_text = d.getVar("CVE_CHECK_FORMAT_TEXT") == "1" 206 207 save_pn = d.getVar("PN") 208 209 for pkg in recipies: 210 # To be able to use the CVE_CHECK_RECIPE_FILE variable we have to evaluate 211 # it with the different PN names set each time. 212 d.setVar("PN", pkg) 213 if enable_text: 214 pkgfilepath = d.getVar("CVE_CHECK_RECIPE_FILE") 215 if os.path.exists(pkgfilepath): 216 with open(pkgfilepath) as pfile: 217 text_data += pfile.read() 218 219 if enable_json: 220 pkgfilepath = d.getVar("CVE_CHECK_RECIPE_FILE_JSON") 221 if os.path.exists(pkgfilepath): 222 with open(pkgfilepath) as j: 223 data = json.load(j) 224 cve_check_merge_jsons(json_data, data) 225 226 d.setVar("PN", save_pn) 227 228 if enable_text: 229 link_path = os.path.join(deploy_dir, "%s.cve" % link_name) 230 manifest_name = d.getVar("CVE_CHECK_MANIFEST") 231 232 with open(manifest_name, "w") as f: 233 f.write(text_data) 234 235 update_symlinks(manifest_name, link_path) 236 bb.plain("Image CVE report stored in: %s" % manifest_name) 237 238 if enable_json: 239 link_path = os.path.join(deploy_dir, "%s.json" % link_name) 240 manifest_name = d.getVar("CVE_CHECK_MANIFEST_JSON") 241 242 with open(manifest_name, "w") as f: 243 json.dump(json_data, f, indent=2) 244 245 update_symlinks(manifest_name, link_path) 246 bb.plain("Image CVE JSON report stored in: %s" % manifest_name) 247} 248 249ROOTFS_POSTPROCESS_COMMAND:prepend = "${@'cve_check_write_rootfs_manifest; ' if d.getVar('CVE_CHECK_CREATE_MANIFEST') == '1' else ''}" 250do_rootfs[recrdeptask] += "${@'do_cve_check' if d.getVar('CVE_CHECK_CREATE_MANIFEST') == '1' else ''}" 251do_populate_sdk[recrdeptask] += "${@'do_cve_check' if d.getVar('CVE_CHECK_CREATE_MANIFEST') == '1' else ''}" 252 253def check_cves(d, patched_cves): 254 """ 255 Connect to the NVD database and find unpatched cves. 256 """ 257 from oe.cve_check import Version, convert_cve_version 258 259 pn = d.getVar("PN") 260 real_pv = d.getVar("PV") 261 suffix = d.getVar("CVE_VERSION_SUFFIX") 262 263 cves_unpatched = [] 264 cves_ignored = [] 265 cves_status = [] 266 cves_in_recipe = False 267 # CVE_PRODUCT can contain more than one product (eg. curl/libcurl) 268 products = d.getVar("CVE_PRODUCT").split() 269 # If this has been unset then we're not scanning for CVEs here (for example, image recipes) 270 if not products: 271 return ([], [], [], []) 272 pv = d.getVar("CVE_VERSION").split("+git")[0] 273 274 # If the recipe has been skipped/ignored we return empty lists 275 if pn in d.getVar("CVE_CHECK_SKIP_RECIPE").split(): 276 bb.note("Recipe has been skipped by cve-check") 277 return ([], [], [], []) 278 279 cve_ignore = d.getVar("CVE_CHECK_IGNORE").split() 280 281 import sqlite3 282 db_file = d.expand("file:${CVE_CHECK_DB_FILE}?mode=ro") 283 conn = sqlite3.connect(db_file, uri=True) 284 285 # For each of the known product names (e.g. curl has CPEs using curl and libcurl)... 286 for product in products: 287 cves_in_product = False 288 if ":" in product: 289 vendor, product = product.split(":", 1) 290 else: 291 vendor = "%" 292 293 # Find all relevant CVE IDs. 294 cve_cursor = conn.execute("SELECT DISTINCT ID FROM PRODUCTS WHERE PRODUCT IS ? AND VENDOR LIKE ?", (product, vendor)) 295 for cverow in cve_cursor: 296 cve = cverow[0] 297 298 if cve in cve_ignore: 299 bb.note("%s-%s ignores %s" % (product, pv, cve)) 300 cves_ignored.append(cve) 301 continue 302 elif cve in patched_cves: 303 bb.note("%s has been patched" % (cve)) 304 continue 305 # Write status once only for each product 306 if not cves_in_product: 307 cves_status.append([product, True]) 308 cves_in_product = True 309 cves_in_recipe = True 310 311 vulnerable = False 312 ignored = False 313 314 product_cursor = conn.execute("SELECT * FROM PRODUCTS WHERE ID IS ? AND PRODUCT IS ? AND VENDOR LIKE ?", (cve, product, vendor)) 315 for row in product_cursor: 316 (_, _, _, version_start, operator_start, version_end, operator_end) = row 317 #bb.debug(2, "Evaluating row " + str(row)) 318 if cve in cve_ignore: 319 ignored = True 320 321 version_start = convert_cve_version(version_start) 322 version_end = convert_cve_version(version_end) 323 324 if (operator_start == '=' and pv == version_start) or version_start == '-': 325 vulnerable = True 326 else: 327 if operator_start: 328 try: 329 vulnerable_start = (operator_start == '>=' and Version(pv,suffix) >= Version(version_start,suffix)) 330 vulnerable_start |= (operator_start == '>' and Version(pv,suffix) > Version(version_start,suffix)) 331 except: 332 bb.warn("%s: Failed to compare %s %s %s for %s" % 333 (product, pv, operator_start, version_start, cve)) 334 vulnerable_start = False 335 else: 336 vulnerable_start = False 337 338 if operator_end: 339 try: 340 vulnerable_end = (operator_end == '<=' and Version(pv,suffix) <= Version(version_end,suffix) ) 341 vulnerable_end |= (operator_end == '<' and Version(pv,suffix) < Version(version_end,suffix) ) 342 except: 343 bb.warn("%s: Failed to compare %s %s %s for %s" % 344 (product, pv, operator_end, version_end, cve)) 345 vulnerable_end = False 346 else: 347 vulnerable_end = False 348 349 if operator_start and operator_end: 350 vulnerable = vulnerable_start and vulnerable_end 351 else: 352 vulnerable = vulnerable_start or vulnerable_end 353 354 if vulnerable: 355 if ignored: 356 bb.note("%s is ignored in %s-%s" % (cve, pn, real_pv)) 357 cves_ignored.append(cve) 358 else: 359 bb.note("%s-%s is vulnerable to %s" % (pn, real_pv, cve)) 360 cves_unpatched.append(cve) 361 break 362 product_cursor.close() 363 364 if not vulnerable: 365 bb.note("%s-%s is not vulnerable to %s" % (pn, real_pv, cve)) 366 patched_cves.add(cve) 367 cve_cursor.close() 368 369 if not cves_in_product: 370 bb.note("No CVE records found for product %s, pn %s" % (product, pn)) 371 cves_status.append([product, False]) 372 373 conn.close() 374 375 if not cves_in_recipe: 376 bb.note("No CVE records for products in recipe %s" % (pn)) 377 378 return (list(cves_ignored), list(patched_cves), cves_unpatched, cves_status) 379 380def get_cve_info(d, cves): 381 """ 382 Get CVE information from the database. 383 """ 384 385 import sqlite3 386 387 cve_data = {} 388 db_file = d.expand("file:${CVE_CHECK_DB_FILE}?mode=ro") 389 conn = sqlite3.connect(db_file, uri=True) 390 391 for cve in cves: 392 cursor = conn.execute("SELECT * FROM NVD WHERE ID IS ?", (cve,)) 393 for row in cursor: 394 cve_data[row[0]] = {} 395 cve_data[row[0]]["summary"] = row[1] 396 cve_data[row[0]]["scorev2"] = row[2] 397 cve_data[row[0]]["scorev3"] = row[3] 398 cve_data[row[0]]["modified"] = row[4] 399 cve_data[row[0]]["vector"] = row[5] 400 cursor.close() 401 conn.close() 402 return cve_data 403 404def cve_write_data_text(d, patched, unpatched, ignored, cve_data): 405 """ 406 Write CVE information in WORKDIR; and to CVE_CHECK_DIR, and 407 CVE manifest if enabled. 408 """ 409 410 cve_file = d.getVar("CVE_CHECK_LOG") 411 fdir_name = d.getVar("FILE_DIRNAME") 412 layer = fdir_name.split("/")[-3] 413 414 include_layers = d.getVar("CVE_CHECK_LAYER_INCLUDELIST").split() 415 exclude_layers = d.getVar("CVE_CHECK_LAYER_EXCLUDELIST").split() 416 417 report_all = d.getVar("CVE_CHECK_REPORT_PATCHED") == "1" 418 419 if exclude_layers and layer in exclude_layers: 420 return 421 422 if include_layers and layer not in include_layers: 423 return 424 425 # Early exit, the text format does not report packages without CVEs 426 if not patched+unpatched+ignored: 427 return 428 429 nvd_link = "https://nvd.nist.gov/vuln/detail/" 430 write_string = "" 431 unpatched_cves = [] 432 bb.utils.mkdirhier(os.path.dirname(cve_file)) 433 434 for cve in sorted(cve_data): 435 is_patched = cve in patched 436 is_ignored = cve in ignored 437 438 if (is_patched or is_ignored) and not report_all: 439 continue 440 441 write_string += "LAYER: %s\n" % layer 442 write_string += "PACKAGE NAME: %s\n" % d.getVar("PN") 443 write_string += "PACKAGE VERSION: %s%s\n" % (d.getVar("EXTENDPE"), d.getVar("PV")) 444 write_string += "CVE: %s\n" % cve 445 if is_ignored: 446 write_string += "CVE STATUS: Ignored\n" 447 elif is_patched: 448 write_string += "CVE STATUS: Patched\n" 449 else: 450 unpatched_cves.append(cve) 451 write_string += "CVE STATUS: Unpatched\n" 452 write_string += "CVE SUMMARY: %s\n" % cve_data[cve]["summary"] 453 write_string += "CVSS v2 BASE SCORE: %s\n" % cve_data[cve]["scorev2"] 454 write_string += "CVSS v3 BASE SCORE: %s\n" % cve_data[cve]["scorev3"] 455 write_string += "VECTOR: %s\n" % cve_data[cve]["vector"] 456 write_string += "MORE INFORMATION: %s%s\n\n" % (nvd_link, cve) 457 458 if unpatched_cves and d.getVar("CVE_CHECK_SHOW_WARNINGS") == "1": 459 bb.warn("Found unpatched CVE (%s), for more information check %s" % (" ".join(unpatched_cves),cve_file)) 460 461 with open(cve_file, "w") as f: 462 bb.note("Writing file %s with CVE information" % cve_file) 463 f.write(write_string) 464 465 if d.getVar("CVE_CHECK_COPY_FILES") == "1": 466 deploy_file = d.getVar("CVE_CHECK_RECIPE_FILE") 467 bb.utils.mkdirhier(os.path.dirname(deploy_file)) 468 with open(deploy_file, "w") as f: 469 f.write(write_string) 470 471 if d.getVar("CVE_CHECK_CREATE_MANIFEST") == "1": 472 cvelogpath = d.getVar("CVE_CHECK_SUMMARY_DIR") 473 bb.utils.mkdirhier(cvelogpath) 474 475 with open(d.getVar("CVE_CHECK_TMP_FILE"), "a") as f: 476 f.write("%s" % write_string) 477 478def cve_check_write_json_output(d, output, direct_file, deploy_file, manifest_file): 479 """ 480 Write CVE information in the JSON format: to WORKDIR; and to 481 CVE_CHECK_DIR, if CVE manifest if enabled, write fragment 482 files that will be assembled at the end in cve_check_write_rootfs_manifest. 483 """ 484 485 import json 486 487 write_string = json.dumps(output, indent=2) 488 with open(direct_file, "w") as f: 489 bb.note("Writing file %s with CVE information" % direct_file) 490 f.write(write_string) 491 492 if d.getVar("CVE_CHECK_COPY_FILES") == "1": 493 bb.utils.mkdirhier(os.path.dirname(deploy_file)) 494 with open(deploy_file, "w") as f: 495 f.write(write_string) 496 497 if d.getVar("CVE_CHECK_CREATE_MANIFEST") == "1": 498 cvelogpath = d.getVar("CVE_CHECK_SUMMARY_DIR") 499 index_path = d.getVar("CVE_CHECK_SUMMARY_INDEX_PATH") 500 bb.utils.mkdirhier(cvelogpath) 501 fragment_file = os.path.basename(deploy_file) 502 fragment_path = os.path.join(cvelogpath, fragment_file) 503 with open(fragment_path, "w") as f: 504 f.write(write_string) 505 with open(index_path, "a+") as f: 506 f.write("%s\n" % fragment_path) 507 508def cve_write_data_json(d, patched, unpatched, ignored, cve_data, cve_status): 509 """ 510 Prepare CVE data for the JSON format, then write it. 511 """ 512 513 output = {"version":"1", "package": []} 514 nvd_link = "https://nvd.nist.gov/vuln/detail/" 515 516 fdir_name = d.getVar("FILE_DIRNAME") 517 layer = fdir_name.split("/")[-3] 518 519 include_layers = d.getVar("CVE_CHECK_LAYER_INCLUDELIST").split() 520 exclude_layers = d.getVar("CVE_CHECK_LAYER_EXCLUDELIST").split() 521 522 report_all = d.getVar("CVE_CHECK_REPORT_PATCHED") == "1" 523 524 if exclude_layers and layer in exclude_layers: 525 return 526 527 if include_layers and layer not in include_layers: 528 return 529 530 unpatched_cves = [] 531 532 product_data = [] 533 for s in cve_status: 534 p = {"product": s[0], "cvesInRecord": "Yes"} 535 if s[1] == False: 536 p["cvesInRecord"] = "No" 537 product_data.append(p) 538 539 package_version = "%s%s" % (d.getVar("EXTENDPE"), d.getVar("PV")) 540 package_data = { 541 "name" : d.getVar("PN"), 542 "layer" : layer, 543 "version" : package_version, 544 "products": product_data 545 } 546 cve_list = [] 547 548 for cve in sorted(cve_data): 549 is_patched = cve in patched 550 is_ignored = cve in ignored 551 status = "Unpatched" 552 if (is_patched or is_ignored) and not report_all: 553 continue 554 if is_ignored: 555 status = "Ignored" 556 elif is_patched: 557 status = "Patched" 558 else: 559 # default value of status is Unpatched 560 unpatched_cves.append(cve) 561 562 issue_link = "%s%s" % (nvd_link, cve) 563 564 cve_item = { 565 "id" : cve, 566 "summary" : cve_data[cve]["summary"], 567 "scorev2" : cve_data[cve]["scorev2"], 568 "scorev3" : cve_data[cve]["scorev3"], 569 "vector" : cve_data[cve]["vector"], 570 "status" : status, 571 "link": issue_link 572 } 573 cve_list.append(cve_item) 574 575 package_data["issue"] = cve_list 576 output["package"].append(package_data) 577 578 direct_file = d.getVar("CVE_CHECK_LOG_JSON") 579 deploy_file = d.getVar("CVE_CHECK_RECIPE_FILE_JSON") 580 manifest_file = d.getVar("CVE_CHECK_SUMMARY_FILE_NAME_JSON") 581 582 cve_check_write_json_output(d, output, direct_file, deploy_file, manifest_file) 583 584def cve_write_data(d, patched, unpatched, ignored, cve_data, status): 585 """ 586 Write CVE data in each enabled format. 587 """ 588 589 if d.getVar("CVE_CHECK_FORMAT_TEXT") == "1": 590 cve_write_data_text(d, patched, unpatched, ignored, cve_data) 591 if d.getVar("CVE_CHECK_FORMAT_JSON") == "1": 592 cve_write_data_json(d, patched, unpatched, ignored, cve_data, status) 593