diff options
Diffstat (limited to 'meta/classes/cve-check.bbclass')
-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) |