diff options
| -rw-r--r-- | meta/classes/cve-check.bbclass | 43 |
1 files changed, 30 insertions, 13 deletions
diff --git a/meta/classes/cve-check.bbclass b/meta/classes/cve-check.bbclass index 1b4910f737..50b9247f46 100644 --- a/meta/classes/cve-check.bbclass +++ b/meta/classes/cve-check.bbclass | |||
| @@ -47,7 +47,9 @@ CVE_CHECK_MANIFEST_JSON ?= "${DEPLOY_DIR_IMAGE}/${IMAGE_NAME}${IMAGE_NAME_SUFFIX | |||
| 47 | CVE_CHECK_COPY_FILES ??= "1" | 47 | CVE_CHECK_COPY_FILES ??= "1" |
| 48 | CVE_CHECK_CREATE_MANIFEST ??= "1" | 48 | CVE_CHECK_CREATE_MANIFEST ??= "1" |
| 49 | 49 | ||
| 50 | # Report Patched or Ignored CVEs | ||
| 50 | CVE_CHECK_REPORT_PATCHED ??= "1" | 51 | CVE_CHECK_REPORT_PATCHED ??= "1" |
| 52 | |||
| 51 | CVE_CHECK_SHOW_WARNINGS ??= "1" | 53 | CVE_CHECK_SHOW_WARNINGS ??= "1" |
| 52 | 54 | ||
| 53 | # Provide text output | 55 | # Provide text output |
| @@ -144,7 +146,7 @@ python do_cve_check () { | |||
| 144 | bb.fatal("Failure in searching patches") | 146 | bb.fatal("Failure in searching patches") |
| 145 | ignored, patched, unpatched, status = check_cves(d, patched_cves) | 147 | ignored, patched, unpatched, status = check_cves(d, patched_cves) |
| 146 | if patched or unpatched or (d.getVar("CVE_CHECK_COVERAGE") == "1" and status): | 148 | if patched or unpatched or (d.getVar("CVE_CHECK_COVERAGE") == "1" and status): |
| 147 | cve_data = get_cve_info(d, patched + unpatched) | 149 | cve_data = get_cve_info(d, patched + unpatched + ignored) |
| 148 | cve_write_data(d, patched, unpatched, ignored, cve_data, status) | 150 | cve_write_data(d, patched, unpatched, ignored, cve_data, status) |
| 149 | else: | 151 | else: |
| 150 | bb.note("No CVE database found, skipping CVE check") | 152 | bb.note("No CVE database found, skipping CVE check") |
| @@ -258,6 +260,7 @@ def check_cves(d, patched_cves): | |||
| 258 | suffix = d.getVar("CVE_VERSION_SUFFIX") | 260 | suffix = d.getVar("CVE_VERSION_SUFFIX") |
| 259 | 261 | ||
| 260 | cves_unpatched = [] | 262 | cves_unpatched = [] |
| 263 | cves_ignored = [] | ||
| 261 | cves_status = [] | 264 | cves_status = [] |
| 262 | cves_in_recipe = False | 265 | cves_in_recipe = False |
| 263 | # CVE_PRODUCT can contain more than one product (eg. curl/libcurl) | 266 | # CVE_PRODUCT can contain more than one product (eg. curl/libcurl) |
| @@ -291,9 +294,8 @@ def check_cves(d, patched_cves): | |||
| 291 | cve = cverow[0] | 294 | cve = cverow[0] |
| 292 | 295 | ||
| 293 | if cve in cve_ignore: | 296 | if cve in cve_ignore: |
| 294 | bb.note("%s-%s has been ignored for %s" % (product, pv, cve)) | 297 | bb.note("%s-%s ignores %s" % (product, pv, cve)) |
| 295 | # TODO: this should be in the report as 'ignored' | 298 | cves_ignored.append(cve) |
| 296 | patched_cves.add(cve) | ||
| 297 | continue | 299 | continue |
| 298 | elif cve in patched_cves: | 300 | elif cve in patched_cves: |
| 299 | bb.note("%s has been patched" % (cve)) | 301 | bb.note("%s has been patched" % (cve)) |
| @@ -305,9 +307,13 @@ def check_cves(d, patched_cves): | |||
| 305 | cves_in_recipe = True | 307 | cves_in_recipe = True |
| 306 | 308 | ||
| 307 | vulnerable = False | 309 | vulnerable = False |
| 310 | ignored = False | ||
| 311 | |||
| 308 | for row in conn.execute("SELECT * FROM PRODUCTS WHERE ID IS ? AND PRODUCT IS ? AND VENDOR LIKE ?", (cve, product, vendor)): | 312 | for row in conn.execute("SELECT * FROM PRODUCTS WHERE ID IS ? AND PRODUCT IS ? AND VENDOR LIKE ?", (cve, product, vendor)): |
| 309 | (_, _, _, version_start, operator_start, version_end, operator_end) = row | 313 | (_, _, _, version_start, operator_start, version_end, operator_end) = row |
| 310 | #bb.debug(2, "Evaluating row " + str(row)) | 314 | #bb.debug(2, "Evaluating row " + str(row)) |
| 315 | if cve in cve_ignore: | ||
| 316 | ignored = True | ||
| 311 | 317 | ||
| 312 | if (operator_start == '=' and pv == version_start) or version_start == '-': | 318 | if (operator_start == '=' and pv == version_start) or version_start == '-': |
| 313 | vulnerable = True | 319 | vulnerable = True |
| @@ -340,13 +346,16 @@ def check_cves(d, patched_cves): | |||
| 340 | vulnerable = vulnerable_start or vulnerable_end | 346 | vulnerable = vulnerable_start or vulnerable_end |
| 341 | 347 | ||
| 342 | if vulnerable: | 348 | if vulnerable: |
| 343 | bb.note("%s-%s is vulnerable to %s" % (pn, real_pv, cve)) | 349 | if ignored: |
| 344 | cves_unpatched.append(cve) | 350 | bb.note("%s is ignored in %s-%s" % (cve, pn, real_pv)) |
| 351 | cves_ignored.append(cve) | ||
| 352 | else: | ||
| 353 | bb.note("%s-%s is vulnerable to %s" % (pn, real_pv, cve)) | ||
| 354 | cves_unpatched.append(cve) | ||
| 345 | break | 355 | break |
| 346 | 356 | ||
| 347 | if not vulnerable: | 357 | if not vulnerable: |
| 348 | bb.note("%s-%s is not vulnerable to %s" % (pn, real_pv, cve)) | 358 | bb.note("%s-%s is not vulnerable to %s" % (pn, real_pv, cve)) |
| 349 | # TODO: not patched but not vulnerable | ||
| 350 | patched_cves.add(cve) | 359 | patched_cves.add(cve) |
| 351 | 360 | ||
| 352 | if not cves_in_product: | 361 | if not cves_in_product: |
| @@ -358,7 +367,7 @@ def check_cves(d, patched_cves): | |||
| 358 | if not cves_in_recipe: | 367 | if not cves_in_recipe: |
| 359 | bb.note("No CVE records for products in recipe %s" % (pn)) | 368 | bb.note("No CVE records for products in recipe %s" % (pn)) |
| 360 | 369 | ||
| 361 | return (list(cve_ignore), list(patched_cves), cves_unpatched, cves_status) | 370 | return (list(cves_ignored), list(patched_cves), cves_unpatched, cves_status) |
| 362 | 371 | ||
| 363 | def get_cve_info(d, cves): | 372 | def get_cve_info(d, cves): |
| 364 | """ | 373 | """ |
| @@ -396,6 +405,8 @@ def cve_write_data_text(d, patched, unpatched, ignored, cve_data): | |||
| 396 | include_layers = d.getVar("CVE_CHECK_LAYER_INCLUDELIST").split() | 405 | include_layers = d.getVar("CVE_CHECK_LAYER_INCLUDELIST").split() |
| 397 | exclude_layers = d.getVar("CVE_CHECK_LAYER_EXCLUDELIST").split() | 406 | exclude_layers = d.getVar("CVE_CHECK_LAYER_EXCLUDELIST").split() |
| 398 | 407 | ||
| 408 | report_all = d.getVar("CVE_CHECK_REPORT_PATCHED") == "1" | ||
| 409 | |||
| 399 | if exclude_layers and layer in exclude_layers: | 410 | if exclude_layers and layer in exclude_layers: |
| 400 | return | 411 | return |
| 401 | 412 | ||
| @@ -403,7 +414,7 @@ def cve_write_data_text(d, patched, unpatched, ignored, cve_data): | |||
| 403 | return | 414 | return |
| 404 | 415 | ||
| 405 | # Early exit, the text format does not report packages without CVEs | 416 | # Early exit, the text format does not report packages without CVEs |
| 406 | if not patched+unpatched: | 417 | if not patched+unpatched+ignored: |
| 407 | return | 418 | return |
| 408 | 419 | ||
| 409 | nvd_link = "https://nvd.nist.gov/vuln/detail/" | 420 | nvd_link = "https://nvd.nist.gov/vuln/detail/" |
| @@ -413,13 +424,16 @@ def cve_write_data_text(d, patched, unpatched, ignored, cve_data): | |||
| 413 | 424 | ||
| 414 | for cve in sorted(cve_data): | 425 | for cve in sorted(cve_data): |
| 415 | is_patched = cve in patched | 426 | is_patched = cve in patched |
| 416 | if is_patched and (d.getVar("CVE_CHECK_REPORT_PATCHED") != "1"): | 427 | is_ignored = cve in ignored |
| 428 | |||
| 429 | if (is_patched or is_ignored) and not report_all: | ||
| 417 | continue | 430 | continue |
| 431 | |||
| 418 | write_string += "LAYER: %s\n" % layer | 432 | write_string += "LAYER: %s\n" % layer |
| 419 | write_string += "PACKAGE NAME: %s\n" % d.getVar("PN") | 433 | write_string += "PACKAGE NAME: %s\n" % d.getVar("PN") |
| 420 | write_string += "PACKAGE VERSION: %s%s\n" % (d.getVar("EXTENDPE"), d.getVar("PV")) | 434 | write_string += "PACKAGE VERSION: %s%s\n" % (d.getVar("EXTENDPE"), d.getVar("PV")) |
| 421 | write_string += "CVE: %s\n" % cve | 435 | write_string += "CVE: %s\n" % cve |
| 422 | if cve in ignored: | 436 | if is_ignored: |
| 423 | write_string += "CVE STATUS: Ignored\n" | 437 | write_string += "CVE STATUS: Ignored\n" |
| 424 | elif is_patched: | 438 | elif is_patched: |
| 425 | write_string += "CVE STATUS: Patched\n" | 439 | write_string += "CVE STATUS: Patched\n" |
| @@ -496,6 +510,8 @@ def cve_write_data_json(d, patched, unpatched, ignored, cve_data, cve_status): | |||
| 496 | include_layers = d.getVar("CVE_CHECK_LAYER_INCLUDELIST").split() | 510 | include_layers = d.getVar("CVE_CHECK_LAYER_INCLUDELIST").split() |
| 497 | exclude_layers = d.getVar("CVE_CHECK_LAYER_EXCLUDELIST").split() | 511 | exclude_layers = d.getVar("CVE_CHECK_LAYER_EXCLUDELIST").split() |
| 498 | 512 | ||
| 513 | report_all = d.getVar("CVE_CHECK_REPORT_PATCHED") == "1" | ||
| 514 | |||
| 499 | if exclude_layers and layer in exclude_layers: | 515 | if exclude_layers and layer in exclude_layers: |
| 500 | return | 516 | return |
| 501 | 517 | ||
| @@ -522,10 +538,11 @@ def cve_write_data_json(d, patched, unpatched, ignored, cve_data, cve_status): | |||
| 522 | 538 | ||
| 523 | for cve in sorted(cve_data): | 539 | for cve in sorted(cve_data): |
| 524 | is_patched = cve in patched | 540 | is_patched = cve in patched |
| 541 | is_ignored = cve in ignored | ||
| 525 | status = "Unpatched" | 542 | status = "Unpatched" |
| 526 | if is_patched and (d.getVar("CVE_CHECK_REPORT_PATCHED") != "1"): | 543 | if (is_patched or is_ignored) and not report_all: |
| 527 | continue | 544 | continue |
| 528 | if cve in ignored: | 545 | if is_ignored: |
| 529 | status = "Ignored" | 546 | status = "Ignored" |
| 530 | elif is_patched: | 547 | elif is_patched: |
| 531 | status = "Patched" | 548 | status = "Patched" |
