diff options
| -rw-r--r-- | meta/classes/cve-check.bbclass | 51 |
1 files changed, 40 insertions, 11 deletions
diff --git a/meta/classes/cve-check.bbclass b/meta/classes/cve-check.bbclass index f574f5daa4..78516d0bb6 100644 --- a/meta/classes/cve-check.bbclass +++ b/meta/classes/cve-check.bbclass | |||
| @@ -55,6 +55,9 @@ CVE_CHECK_FORMAT_TEXT ??= "1" | |||
| 55 | # Provide JSON output | 55 | # Provide JSON output |
| 56 | CVE_CHECK_FORMAT_JSON ??= "1" | 56 | CVE_CHECK_FORMAT_JSON ??= "1" |
| 57 | 57 | ||
| 58 | # Check for packages without CVEs (no issues or missing product name) | ||
| 59 | CVE_CHECK_COVERAGE ??= "1" | ||
| 60 | |||
| 58 | # Skip CVE Check for packages (PN) | 61 | # Skip CVE Check for packages (PN) |
| 59 | CVE_CHECK_SKIP_RECIPE ?= "" | 62 | CVE_CHECK_SKIP_RECIPE ?= "" |
| 60 | 63 | ||
| @@ -114,10 +117,10 @@ python do_cve_check () { | |||
| 114 | patched_cves = get_patched_cves(d) | 117 | patched_cves = get_patched_cves(d) |
| 115 | except FileNotFoundError: | 118 | except FileNotFoundError: |
| 116 | bb.fatal("Failure in searching patches") | 119 | bb.fatal("Failure in searching patches") |
| 117 | ignored, patched, unpatched = check_cves(d, patched_cves) | 120 | ignored, patched, unpatched, status = check_cves(d, patched_cves) |
| 118 | if patched or unpatched: | 121 | if patched or unpatched or (d.getVar("CVE_CHECK_COVERAGE") == "1" and status): |
| 119 | cve_data = get_cve_info(d, patched + unpatched) | 122 | cve_data = get_cve_info(d, patched + unpatched) |
| 120 | cve_write_data(d, patched, unpatched, ignored, cve_data) | 123 | cve_write_data(d, patched, unpatched, ignored, cve_data, status) |
| 121 | else: | 124 | else: |
| 122 | bb.note("No CVE database found, skipping CVE check") | 125 | bb.note("No CVE database found, skipping CVE check") |
| 123 | 126 | ||
| @@ -207,17 +210,19 @@ def check_cves(d, patched_cves): | |||
| 207 | suffix = d.getVar("CVE_VERSION_SUFFIX") | 210 | suffix = d.getVar("CVE_VERSION_SUFFIX") |
| 208 | 211 | ||
| 209 | cves_unpatched = [] | 212 | cves_unpatched = [] |
| 213 | cves_status = [] | ||
| 214 | cves_in_recipe = False | ||
| 210 | # CVE_PRODUCT can contain more than one product (eg. curl/libcurl) | 215 | # CVE_PRODUCT can contain more than one product (eg. curl/libcurl) |
| 211 | products = d.getVar("CVE_PRODUCT").split() | 216 | products = d.getVar("CVE_PRODUCT").split() |
| 212 | # If this has been unset then we're not scanning for CVEs here (for example, image recipes) | 217 | # If this has been unset then we're not scanning for CVEs here (for example, image recipes) |
| 213 | if not products: | 218 | if not products: |
| 214 | return ([], [], []) | 219 | return ([], [], [], {}) |
| 215 | pv = d.getVar("CVE_VERSION").split("+git")[0] | 220 | pv = d.getVar("CVE_VERSION").split("+git")[0] |
| 216 | 221 | ||
| 217 | # If the recipe has been skipped/ignored we return empty lists | 222 | # If the recipe has been skipped/ignored we return empty lists |
| 218 | if pn in d.getVar("CVE_CHECK_SKIP_RECIPE").split(): | 223 | if pn in d.getVar("CVE_CHECK_SKIP_RECIPE").split(): |
| 219 | bb.note("Recipe has been skipped by cve-check") | 224 | bb.note("Recipe has been skipped by cve-check") |
| 220 | return ([], [], []) | 225 | return ([], [], [], []) |
| 221 | 226 | ||
| 222 | cve_ignore = d.getVar("CVE_CHECK_IGNORE").split() | 227 | cve_ignore = d.getVar("CVE_CHECK_IGNORE").split() |
| 223 | 228 | ||
| @@ -227,6 +232,7 @@ def check_cves(d, patched_cves): | |||
| 227 | 232 | ||
| 228 | # For each of the known product names (e.g. curl has CPEs using curl and libcurl)... | 233 | # For each of the known product names (e.g. curl has CPEs using curl and libcurl)... |
| 229 | for product in products: | 234 | for product in products: |
| 235 | cves_in_product = False | ||
| 230 | if ":" in product: | 236 | if ":" in product: |
| 231 | vendor, product = product.split(":", 1) | 237 | vendor, product = product.split(":", 1) |
| 232 | else: | 238 | else: |
| @@ -244,6 +250,11 @@ def check_cves(d, patched_cves): | |||
| 244 | elif cve in patched_cves: | 250 | elif cve in patched_cves: |
| 245 | bb.note("%s has been patched" % (cve)) | 251 | bb.note("%s has been patched" % (cve)) |
| 246 | continue | 252 | continue |
| 253 | # Write status once only for each product | ||
| 254 | if not cves_in_product: | ||
| 255 | cves_status.append([product, True]) | ||
| 256 | cves_in_product = True | ||
| 257 | cves_in_recipe = True | ||
| 247 | 258 | ||
| 248 | vulnerable = False | 259 | vulnerable = False |
| 249 | for row in conn.execute("SELECT * FROM PRODUCTS WHERE ID IS ? AND PRODUCT IS ? AND VENDOR LIKE ?", (cve, product, vendor)): | 260 | for row in conn.execute("SELECT * FROM PRODUCTS WHERE ID IS ? AND PRODUCT IS ? AND VENDOR LIKE ?", (cve, product, vendor)): |
| @@ -290,9 +301,16 @@ def check_cves(d, patched_cves): | |||
| 290 | # TODO: not patched but not vulnerable | 301 | # TODO: not patched but not vulnerable |
| 291 | patched_cves.add(cve) | 302 | patched_cves.add(cve) |
| 292 | 303 | ||
| 304 | if not cves_in_product: | ||
| 305 | bb.note("No CVE records found for product %s, pn %s" % (product, pn)) | ||
| 306 | cves_status.append([product, False]) | ||
| 307 | |||
| 293 | conn.close() | 308 | conn.close() |
| 294 | 309 | ||
| 295 | return (list(cve_ignore), list(patched_cves), cves_unpatched) | 310 | if not cves_in_recipe: |
| 311 | bb.note("No CVE records for products in recipe %s" % (pn)) | ||
| 312 | |||
| 313 | return (list(cve_ignore), list(patched_cves), cves_unpatched, cves_status) | ||
| 296 | 314 | ||
| 297 | def get_cve_info(d, cves): | 315 | def get_cve_info(d, cves): |
| 298 | """ | 316 | """ |
| @@ -323,7 +341,6 @@ def cve_write_data_text(d, patched, unpatched, ignored, cve_data): | |||
| 323 | CVE manifest if enabled. | 341 | CVE manifest if enabled. |
| 324 | """ | 342 | """ |
| 325 | 343 | ||
| 326 | |||
| 327 | cve_file = d.getVar("CVE_CHECK_LOG") | 344 | cve_file = d.getVar("CVE_CHECK_LOG") |
| 328 | fdir_name = d.getVar("FILE_DIRNAME") | 345 | fdir_name = d.getVar("FILE_DIRNAME") |
| 329 | layer = fdir_name.split("/")[-3] | 346 | layer = fdir_name.split("/")[-3] |
| @@ -337,6 +354,10 @@ def cve_write_data_text(d, patched, unpatched, ignored, cve_data): | |||
| 337 | if include_layers and layer not in include_layers: | 354 | if include_layers and layer not in include_layers: |
| 338 | return | 355 | return |
| 339 | 356 | ||
| 357 | # Early exit, the text format does not report packages without CVEs | ||
| 358 | if not patched+unpatched: | ||
| 359 | return | ||
| 360 | |||
| 340 | nvd_link = "https://nvd.nist.gov/vuln/detail/" | 361 | nvd_link = "https://nvd.nist.gov/vuln/detail/" |
| 341 | write_string = "" | 362 | write_string = "" |
| 342 | unpatched_cves = [] | 363 | unpatched_cves = [] |
| @@ -414,7 +435,7 @@ def cve_check_write_json_output(d, output, direct_file, deploy_file, manifest_fi | |||
| 414 | with open(index_path, "a+") as f: | 435 | with open(index_path, "a+") as f: |
| 415 | f.write("%s\n" % fragment_path) | 436 | f.write("%s\n" % fragment_path) |
| 416 | 437 | ||
| 417 | def cve_write_data_json(d, patched, unpatched, ignored, cve_data): | 438 | def cve_write_data_json(d, patched, unpatched, ignored, cve_data, cve_status): |
| 418 | """ | 439 | """ |
| 419 | Prepare CVE data for the JSON format, then write it. | 440 | Prepare CVE data for the JSON format, then write it. |
| 420 | """ | 441 | """ |
| @@ -436,11 +457,19 @@ def cve_write_data_json(d, patched, unpatched, ignored, cve_data): | |||
| 436 | 457 | ||
| 437 | unpatched_cves = [] | 458 | unpatched_cves = [] |
| 438 | 459 | ||
| 460 | product_data = [] | ||
| 461 | for s in cve_status: | ||
| 462 | p = {"product": s[0], "cvesInRecord": "Yes"} | ||
| 463 | if s[1] == False: | ||
| 464 | p["cvesInRecord"] = "No" | ||
| 465 | product_data.append(p) | ||
| 466 | |||
| 439 | package_version = "%s%s" % (d.getVar("EXTENDPE"), d.getVar("PV")) | 467 | package_version = "%s%s" % (d.getVar("EXTENDPE"), d.getVar("PV")) |
| 440 | package_data = { | 468 | package_data = { |
| 441 | "name" : d.getVar("PN"), | 469 | "name" : d.getVar("PN"), |
| 442 | "layer" : layer, | 470 | "layer" : layer, |
| 443 | "version" : package_version | 471 | "version" : package_version, |
| 472 | "products": product_data | ||
| 444 | } | 473 | } |
| 445 | cve_list = [] | 474 | cve_list = [] |
| 446 | 475 | ||
| @@ -479,7 +508,7 @@ def cve_write_data_json(d, patched, unpatched, ignored, cve_data): | |||
| 479 | 508 | ||
| 480 | cve_check_write_json_output(d, output, direct_file, deploy_file, manifest_file) | 509 | cve_check_write_json_output(d, output, direct_file, deploy_file, manifest_file) |
| 481 | 510 | ||
| 482 | def cve_write_data(d, patched, unpatched, ignored, cve_data): | 511 | def cve_write_data(d, patched, unpatched, ignored, cve_data, status): |
| 483 | """ | 512 | """ |
| 484 | Write CVE data in each enabled format. | 513 | Write CVE data in each enabled format. |
| 485 | """ | 514 | """ |
| @@ -487,4 +516,4 @@ def cve_write_data(d, patched, unpatched, ignored, cve_data): | |||
| 487 | if d.getVar("CVE_CHECK_FORMAT_TEXT") == "1": | 516 | if d.getVar("CVE_CHECK_FORMAT_TEXT") == "1": |
| 488 | cve_write_data_text(d, patched, unpatched, ignored, cve_data) | 517 | cve_write_data_text(d, patched, unpatched, ignored, cve_data) |
| 489 | if d.getVar("CVE_CHECK_FORMAT_JSON") == "1": | 518 | if d.getVar("CVE_CHECK_FORMAT_JSON") == "1": |
| 490 | cve_write_data_json(d, patched, unpatched, ignored, cve_data) | 519 | cve_write_data_json(d, patched, unpatched, ignored, cve_data, status) |
