diff options
-rw-r--r-- | meta/classes/cve-check.bbclass | 144 | ||||
-rw-r--r-- | meta/lib/oe/cve_check.py | 16 |
2 files changed, 159 insertions, 1 deletions
diff --git a/meta/classes/cve-check.bbclass b/meta/classes/cve-check.bbclass index dfad10c22b..f574f5daa4 100644 --- a/meta/classes/cve-check.bbclass +++ b/meta/classes/cve-check.bbclass | |||
@@ -34,15 +34,27 @@ CVE_CHECK_TMP_FILE ?= "${TMPDIR}/cve_check" | |||
34 | CVE_CHECK_SUMMARY_DIR ?= "${LOG_DIR}/cve" | 34 | CVE_CHECK_SUMMARY_DIR ?= "${LOG_DIR}/cve" |
35 | CVE_CHECK_SUMMARY_FILE_NAME ?= "cve-summary" | 35 | CVE_CHECK_SUMMARY_FILE_NAME ?= "cve-summary" |
36 | CVE_CHECK_SUMMARY_FILE ?= "${CVE_CHECK_SUMMARY_DIR}/${CVE_CHECK_SUMMARY_FILE_NAME}" | 36 | CVE_CHECK_SUMMARY_FILE ?= "${CVE_CHECK_SUMMARY_DIR}/${CVE_CHECK_SUMMARY_FILE_NAME}" |
37 | CVE_CHECK_SUMMARY_FILE_NAME_JSON = "cve-summary.json" | ||
38 | CVE_CHECK_SUMMARY_INDEX_PATH = "${CVE_CHECK_SUMMARY_DIR}/cve-summary-index.txt" | ||
39 | |||
40 | CVE_CHECK_LOG_JSON ?= "${T}/cve.json" | ||
37 | 41 | ||
38 | CVE_CHECK_DIR ??= "${DEPLOY_DIR}/cve" | 42 | CVE_CHECK_DIR ??= "${DEPLOY_DIR}/cve" |
39 | CVE_CHECK_RECIPE_FILE ?= "${CVE_CHECK_DIR}/${PN}" | 43 | CVE_CHECK_RECIPE_FILE ?= "${CVE_CHECK_DIR}/${PN}" |
44 | CVE_CHECK_RECIPE_FILE_JSON ?= "${CVE_CHECK_DIR}/${PN}_cve.json" | ||
40 | CVE_CHECK_MANIFEST ?= "${DEPLOY_DIR_IMAGE}/${IMAGE_NAME}${IMAGE_NAME_SUFFIX}.cve" | 45 | CVE_CHECK_MANIFEST ?= "${DEPLOY_DIR_IMAGE}/${IMAGE_NAME}${IMAGE_NAME_SUFFIX}.cve" |
46 | CVE_CHECK_MANIFEST_JSON ?= "${DEPLOY_DIR_IMAGE}/${IMAGE_NAME}${IMAGE_NAME_SUFFIX}.json" | ||
41 | CVE_CHECK_COPY_FILES ??= "1" | 47 | CVE_CHECK_COPY_FILES ??= "1" |
42 | CVE_CHECK_CREATE_MANIFEST ??= "1" | 48 | CVE_CHECK_CREATE_MANIFEST ??= "1" |
43 | 49 | ||
44 | CVE_CHECK_REPORT_PATCHED ??= "1" | 50 | CVE_CHECK_REPORT_PATCHED ??= "1" |
45 | 51 | ||
52 | # Provide text output | ||
53 | CVE_CHECK_FORMAT_TEXT ??= "1" | ||
54 | |||
55 | # Provide JSON output | ||
56 | CVE_CHECK_FORMAT_JSON ??= "1" | ||
57 | |||
46 | # Skip CVE Check for packages (PN) | 58 | # Skip CVE Check for packages (PN) |
47 | CVE_CHECK_SKIP_RECIPE ?= "" | 59 | CVE_CHECK_SKIP_RECIPE ?= "" |
48 | 60 | ||
@@ -120,6 +132,7 @@ python cve_check_cleanup () { | |||
120 | Delete the file used to gather all the CVE information. | 132 | Delete the file used to gather all the CVE information. |
121 | """ | 133 | """ |
122 | bb.utils.remove(e.data.getVar("CVE_CHECK_TMP_FILE")) | 134 | bb.utils.remove(e.data.getVar("CVE_CHECK_TMP_FILE")) |
135 | bb.utils.remove(e.data.getVar("CVE_CHECK_SUMMARY_INDEX_PATH")) | ||
123 | } | 136 | } |
124 | 137 | ||
125 | addhandler cve_check_cleanup | 138 | addhandler cve_check_cleanup |
@@ -131,11 +144,15 @@ python cve_check_write_rootfs_manifest () { | |||
131 | """ | 144 | """ |
132 | 145 | ||
133 | import shutil | 146 | import shutil |
147 | from oe.cve_check import cve_check_merge_jsons | ||
134 | 148 | ||
135 | if d.getVar("CVE_CHECK_COPY_FILES") == "1": | 149 | if d.getVar("CVE_CHECK_COPY_FILES") == "1": |
136 | deploy_file = d.getVar("CVE_CHECK_RECIPE_FILE") | 150 | deploy_file = d.getVar("CVE_CHECK_RECIPE_FILE") |
137 | if os.path.exists(deploy_file): | 151 | if os.path.exists(deploy_file): |
138 | bb.utils.remove(deploy_file) | 152 | bb.utils.remove(deploy_file) |
153 | deploy_file_json = d.getVar("CVE_CHECK_RECIPE_FILE_JSON") | ||
154 | if os.path.exists(deploy_file_json): | ||
155 | bb.utils.remove(deploy_file_json) | ||
139 | 156 | ||
140 | if os.path.exists(d.getVar("CVE_CHECK_TMP_FILE")): | 157 | if os.path.exists(d.getVar("CVE_CHECK_TMP_FILE")): |
141 | bb.note("Writing rootfs CVE manifest") | 158 | bb.note("Writing rootfs CVE manifest") |
@@ -154,6 +171,26 @@ python cve_check_write_rootfs_manifest () { | |||
154 | os.remove(manifest_link) | 171 | os.remove(manifest_link) |
155 | os.symlink(os.path.basename(manifest_name), manifest_link) | 172 | os.symlink(os.path.basename(manifest_name), manifest_link) |
156 | bb.plain("Image CVE report stored in: %s" % manifest_name) | 173 | bb.plain("Image CVE report stored in: %s" % manifest_name) |
174 | |||
175 | if os.path.exists(d.getVar("CVE_CHECK_SUMMARY_INDEX_PATH")): | ||
176 | import json | ||
177 | bb.note("Generating JSON CVE manifest") | ||
178 | deploy_dir = d.getVar("DEPLOY_DIR_IMAGE") | ||
179 | link_name = d.getVar("IMAGE_LINK_NAME") | ||
180 | manifest_name = d.getVar("CVE_CHECK_MANIFEST_JSON") | ||
181 | index_file = d.getVar("CVE_CHECK_SUMMARY_INDEX_PATH") | ||
182 | manifest = {"version":"1", "package": []} | ||
183 | with open(index_file) as f: | ||
184 | filename = f.readline() | ||
185 | while filename: | ||
186 | with open(filename.rstrip()) as j: | ||
187 | data = json.load(j) | ||
188 | cve_check_merge_jsons(manifest, data) | ||
189 | filename = f.readline() | ||
190 | |||
191 | with open(manifest_name, "w") as f: | ||
192 | json.dump(manifest, f, indent=2) | ||
193 | bb.plain("Image CVE report stored in: %s" % manifest_name) | ||
157 | } | 194 | } |
158 | 195 | ||
159 | ROOTFS_POSTPROCESS_COMMAND:prepend = "${@'cve_check_write_rootfs_manifest; ' if d.getVar('CVE_CHECK_CREATE_MANIFEST') == '1' else ''}" | 196 | ROOTFS_POSTPROCESS_COMMAND:prepend = "${@'cve_check_write_rootfs_manifest; ' if d.getVar('CVE_CHECK_CREATE_MANIFEST') == '1' else ''}" |
@@ -280,7 +317,7 @@ def get_cve_info(d, cves): | |||
280 | conn.close() | 317 | conn.close() |
281 | return cve_data | 318 | return cve_data |
282 | 319 | ||
283 | def cve_write_data(d, patched, unpatched, ignored, cve_data): | 320 | def cve_write_data_text(d, patched, unpatched, ignored, cve_data): |
284 | """ | 321 | """ |
285 | Write CVE information in WORKDIR; and to CVE_CHECK_DIR, and | 322 | Write CVE information in WORKDIR; and to CVE_CHECK_DIR, and |
286 | CVE manifest if enabled. | 323 | CVE manifest if enabled. |
@@ -346,3 +383,108 @@ def cve_write_data(d, patched, unpatched, ignored, cve_data): | |||
346 | 383 | ||
347 | with open(d.getVar("CVE_CHECK_TMP_FILE"), "a") as f: | 384 | with open(d.getVar("CVE_CHECK_TMP_FILE"), "a") as f: |
348 | f.write("%s" % write_string) | 385 | f.write("%s" % write_string) |
386 | |||
387 | def cve_check_write_json_output(d, output, direct_file, deploy_file, manifest_file): | ||
388 | """ | ||
389 | Write CVE information in the JSON format: to WORKDIR; and to | ||
390 | CVE_CHECK_DIR, if CVE manifest if enabled, write fragment | ||
391 | files that will be assembled at the end in cve_check_write_rootfs_manifest. | ||
392 | """ | ||
393 | |||
394 | import json | ||
395 | |||
396 | write_string = json.dumps(output, indent=2) | ||
397 | with open(direct_file, "w") as f: | ||
398 | bb.note("Writing file %s with CVE information" % direct_file) | ||
399 | f.write(write_string) | ||
400 | |||
401 | if d.getVar("CVE_CHECK_COPY_FILES") == "1": | ||
402 | bb.utils.mkdirhier(os.path.dirname(deploy_file)) | ||
403 | with open(deploy_file, "w") as f: | ||
404 | f.write(write_string) | ||
405 | |||
406 | if d.getVar("CVE_CHECK_CREATE_MANIFEST") == "1": | ||
407 | cvelogpath = d.getVar("CVE_CHECK_SUMMARY_DIR") | ||
408 | index_path = d.getVar("CVE_CHECK_SUMMARY_INDEX_PATH") | ||
409 | bb.utils.mkdirhier(cvelogpath) | ||
410 | fragment_file = os.path.basename(deploy_file) | ||
411 | fragment_path = os.path.join(cvelogpath, fragment_file) | ||
412 | with open(fragment_path, "w") as f: | ||
413 | f.write(write_string) | ||
414 | with open(index_path, "a+") as f: | ||
415 | f.write("%s\n" % fragment_path) | ||
416 | |||
417 | def cve_write_data_json(d, patched, unpatched, ignored, cve_data): | ||
418 | """ | ||
419 | Prepare CVE data for the JSON format, then write it. | ||
420 | """ | ||
421 | |||
422 | output = {"version":"1", "package": []} | ||
423 | nvd_link = "https://nvd.nist.gov/vuln/detail/" | ||
424 | |||
425 | fdir_name = d.getVar("FILE_DIRNAME") | ||
426 | layer = fdir_name.split("/")[-3] | ||
427 | |||
428 | include_layers = d.getVar("CVE_CHECK_LAYER_INCLUDELIST").split() | ||
429 | exclude_layers = d.getVar("CVE_CHECK_LAYER_EXCLUDELIST").split() | ||
430 | |||
431 | if exclude_layers and layer in exclude_layers: | ||
432 | return | ||
433 | |||
434 | if include_layers and layer not in include_layers: | ||
435 | return | ||
436 | |||
437 | unpatched_cves = [] | ||
438 | |||
439 | package_version = "%s%s" % (d.getVar("EXTENDPE"), d.getVar("PV")) | ||
440 | package_data = { | ||
441 | "name" : d.getVar("PN"), | ||
442 | "layer" : layer, | ||
443 | "version" : package_version | ||
444 | } | ||
445 | cve_list = [] | ||
446 | |||
447 | for cve in sorted(cve_data): | ||
448 | is_patched = cve in patched | ||
449 | status = "Unpatched" | ||
450 | if is_patched and (d.getVar("CVE_CHECK_REPORT_PATCHED") != "1"): | ||
451 | continue | ||
452 | if cve in ignored: | ||
453 | status = "Ignored" | ||
454 | elif is_patched: | ||
455 | status = "Patched" | ||
456 | else: | ||
457 | # default value of status is Unpatched | ||
458 | unpatched_cves.append(cve) | ||
459 | |||
460 | issue_link = "%s%s" % (nvd_link, cve) | ||
461 | |||
462 | cve_item = { | ||
463 | "id" : cve, | ||
464 | "summary" : cve_data[cve]["summary"], | ||
465 | "scorev2" : cve_data[cve]["scorev2"], | ||
466 | "scorev3" : cve_data[cve]["scorev3"], | ||
467 | "vector" : cve_data[cve]["vector"], | ||
468 | "status" : status, | ||
469 | "link": issue_link | ||
470 | } | ||
471 | cve_list.append(cve_item) | ||
472 | |||
473 | package_data["issue"] = cve_list | ||
474 | output["package"].append(package_data) | ||
475 | |||
476 | direct_file = d.getVar("CVE_CHECK_LOG_JSON") | ||
477 | deploy_file = d.getVar("CVE_CHECK_RECIPE_FILE_JSON") | ||
478 | manifest_file = d.getVar("CVE_CHECK_SUMMARY_FILE_NAME_JSON") | ||
479 | |||
480 | cve_check_write_json_output(d, output, direct_file, deploy_file, manifest_file) | ||
481 | |||
482 | def cve_write_data(d, patched, unpatched, ignored, cve_data): | ||
483 | """ | ||
484 | Write CVE data in each enabled format. | ||
485 | """ | ||
486 | |||
487 | if d.getVar("CVE_CHECK_FORMAT_TEXT") == "1": | ||
488 | cve_write_data_text(d, patched, unpatched, ignored, cve_data) | ||
489 | if d.getVar("CVE_CHECK_FORMAT_JSON") == "1": | ||
490 | cve_write_data_json(d, patched, unpatched, ignored, cve_data) | ||
diff --git a/meta/lib/oe/cve_check.py b/meta/lib/oe/cve_check.py index 0302beeb4a..e445b7a6ae 100644 --- a/meta/lib/oe/cve_check.py +++ b/meta/lib/oe/cve_check.py | |||
@@ -146,3 +146,19 @@ def get_cpe_ids(cve_product, version): | |||
146 | cpe_ids.append(cpe_id) | 146 | cpe_ids.append(cpe_id) |
147 | 147 | ||
148 | return cpe_ids | 148 | return cpe_ids |
149 | |||
150 | def cve_check_merge_jsons(output, data): | ||
151 | """ | ||
152 | Merge the data in the "package" property to the main data file | ||
153 | output | ||
154 | """ | ||
155 | if output["version"] != data["version"]: | ||
156 | bb.error("Version mismatch when merging JSON outputs") | ||
157 | return | ||
158 | |||
159 | for product in output["package"]: | ||
160 | if product["name"] == data["package"][0]["name"]: | ||
161 | bb.error("Error adding the same package twice") | ||
162 | return | ||
163 | |||
164 | output["package"].append(data["package"][0]) | ||