diff options
author | Andrej Valek <andrej.valek@siemens.com> | 2023-06-23 13:14:56 +0200 |
---|---|---|
committer | Richard Purdie <richard.purdie@linuxfoundation.org> | 2023-07-19 23:25:01 +0100 |
commit | be9883a92bad0fe4c1e9c7302c93dea4ac680f8c (patch) | |
tree | 6d9d35acbb91f98016956168b4ea90f9b9ce0764 /meta/classes/cve-check.bbclass | |
parent | ebb8b39463cef3c3d0f90f054c433b2f5256cb1a (diff) | |
download | poky-be9883a92bad0fe4c1e9c7302c93dea4ac680f8c.tar.gz |
cve-check: add option to add additional patched CVEs
- Replace CVE_CHECK_IGNORE with CVE_STATUS to be more flexible.
The CVE_STATUS should contain an information about status wich
is decoded in 3 items:
- generic status: "Ignored", "Patched" or "Unpatched"
- more detailed status enum
- description: free text describing reason for status
Examples of usage:
CVE_STATUS[CVE-1234-0001] = "not-applicable-platform: Issue only applies on Windows"
CVE_STATUS[CVE-1234-0002] = "fixed-version: Fixed externally"
CVE_CHECK_STATUSMAP[not-applicable-platform] = "Ignored"
CVE_CHECK_STATUSMAP[fixed-version] = "Patched"
(From OE-Core rev: 34f682a24b7075b12ec308154b937ad118d69fe5)
Signed-off-by: Andrej Valek <andrej.valek@siemens.com>
Signed-off-by: Peter Marko <peter.marko@siemens.com>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
Diffstat (limited to 'meta/classes/cve-check.bbclass')
-rw-r--r-- | meta/classes/cve-check.bbclass | 81 |
1 files changed, 68 insertions, 13 deletions
diff --git a/meta/classes/cve-check.bbclass b/meta/classes/cve-check.bbclass index f7abaf4f0c..c1f1ea0fd6 100644 --- a/meta/classes/cve-check.bbclass +++ b/meta/classes/cve-check.bbclass | |||
@@ -70,12 +70,28 @@ CVE_CHECK_COVERAGE ??= "1" | |||
70 | # Skip CVE Check for packages (PN) | 70 | # Skip CVE Check for packages (PN) |
71 | CVE_CHECK_SKIP_RECIPE ?= "" | 71 | CVE_CHECK_SKIP_RECIPE ?= "" |
72 | 72 | ||
73 | # Ingore the check for a given list of CVEs. If a CVE is found, | 73 | # Replace NVD DB check status for a given CVE. Each of CVE has to be mentioned |
74 | # then it is considered patched. The value is a string containing | 74 | # separately with optional detail and description for this status. |
75 | # space separated CVE values: | ||
76 | # | 75 | # |
77 | # CVE_CHECK_IGNORE = 'CVE-2014-2524 CVE-2018-1234' | 76 | # CVE_STATUS[CVE-1234-0001] = "not-applicable-platform: Issue only applies on Windows" |
77 | # CVE_STATUS[CVE-1234-0002] = "fixed-version: Fixed externally" | ||
78 | # | 78 | # |
79 | # Settings the same status and reason for multiple CVEs is possible | ||
80 | # via CVE_STATUS_GROUPS variable. | ||
81 | # | ||
82 | # CVE_STATUS_GROUPS = "CVE_STATUS_WIN CVE_STATUS_PATCHED" | ||
83 | # | ||
84 | # CVE_STATUS_WIN = "CVE-1234-0001 CVE-1234-0003" | ||
85 | # CVE_STATUS_WIN[status] = "not-applicable-platform: Issue only applies on Windows" | ||
86 | # CVE_STATUS_PATCHED = "CVE-1234-0002 CVE-1234-0004" | ||
87 | # CVE_STATUS_PATCHED[status] = "fixed-version: Fixed externally" | ||
88 | # | ||
89 | # All possible CVE statuses could be found in cve-check-map.conf | ||
90 | # CVE_CHECK_STATUSMAP[not-applicable-platform] = "Ignored" | ||
91 | # CVE_CHECK_STATUSMAP[fixed-version] = "Patched" | ||
92 | # | ||
93 | # CVE_CHECK_IGNORE is deprecated and CVE_STATUS has to be used instead. | ||
94 | # Keep CVE_CHECK_IGNORE until other layers migrate to new variables | ||
79 | CVE_CHECK_IGNORE ?= "" | 95 | CVE_CHECK_IGNORE ?= "" |
80 | 96 | ||
81 | # Layers to be excluded | 97 | # Layers to be excluded |
@@ -88,6 +104,24 @@ CVE_CHECK_LAYER_INCLUDELIST ??= "" | |||
88 | # set to "alphabetical" for version using single alphabetical character as increment release | 104 | # set to "alphabetical" for version using single alphabetical character as increment release |
89 | CVE_VERSION_SUFFIX ??= "" | 105 | CVE_VERSION_SUFFIX ??= "" |
90 | 106 | ||
107 | python () { | ||
108 | # Fallback all CVEs from CVE_CHECK_IGNORE to CVE_STATUS | ||
109 | cve_check_ignore = d.getVar("CVE_CHECK_IGNORE") | ||
110 | if cve_check_ignore: | ||
111 | bb.warn("CVE_CHECK_IGNORE is deprecated in favor of CVE_STATUS") | ||
112 | for cve in (d.getVar("CVE_CHECK_IGNORE") or "").split(): | ||
113 | d.setVarFlag("CVE_STATUS", cve, "ignored") | ||
114 | |||
115 | # Process CVE_STATUS_GROUPS to set multiple statuses and optional detail or description at once | ||
116 | for cve_status_group in (d.getVar("CVE_STATUS_GROUPS") or "").split(): | ||
117 | cve_group = d.getVar(cve_status_group) | ||
118 | if cve_group is not None: | ||
119 | for cve in cve_group.split(): | ||
120 | d.setVarFlag("CVE_STATUS", cve, d.getVarFlag(cve_status_group, "status")) | ||
121 | else: | ||
122 | bb.warn("CVE_STATUS_GROUPS contains undefined variable %s" % cve_status_group) | ||
123 | } | ||
124 | |||
91 | def generate_json_report(d, out_path, link_path): | 125 | def generate_json_report(d, out_path, link_path): |
92 | if os.path.exists(d.getVar("CVE_CHECK_SUMMARY_INDEX_PATH")): | 126 | if os.path.exists(d.getVar("CVE_CHECK_SUMMARY_INDEX_PATH")): |
93 | import json | 127 | import json |
@@ -260,7 +294,7 @@ def check_cves(d, patched_cves): | |||
260 | """ | 294 | """ |
261 | Connect to the NVD database and find unpatched cves. | 295 | Connect to the NVD database and find unpatched cves. |
262 | """ | 296 | """ |
263 | from oe.cve_check import Version, convert_cve_version | 297 | from oe.cve_check import Version, convert_cve_version, decode_cve_status |
264 | 298 | ||
265 | pn = d.getVar("PN") | 299 | pn = d.getVar("PN") |
266 | real_pv = d.getVar("PV") | 300 | real_pv = d.getVar("PV") |
@@ -282,7 +316,12 @@ def check_cves(d, patched_cves): | |||
282 | bb.note("Recipe has been skipped by cve-check") | 316 | bb.note("Recipe has been skipped by cve-check") |
283 | return ([], [], [], []) | 317 | return ([], [], [], []) |
284 | 318 | ||
285 | cve_ignore = d.getVar("CVE_CHECK_IGNORE").split() | 319 | # Convert CVE_STATUS into ignored CVEs and check validity |
320 | cve_ignore = [] | ||
321 | for cve in (d.getVarFlags("CVE_STATUS") or {}): | ||
322 | decoded_status, _, _ = decode_cve_status(d, cve) | ||
323 | if decoded_status == "Ignored": | ||
324 | cve_ignore.append(cve) | ||
286 | 325 | ||
287 | import sqlite3 | 326 | import sqlite3 |
288 | db_file = d.expand("file:${CVE_CHECK_DB_FILE}?mode=ro") | 327 | db_file = d.expand("file:${CVE_CHECK_DB_FILE}?mode=ro") |
@@ -413,6 +452,8 @@ def cve_write_data_text(d, patched, unpatched, ignored, cve_data): | |||
413 | CVE manifest if enabled. | 452 | CVE manifest if enabled. |
414 | """ | 453 | """ |
415 | 454 | ||
455 | from oe.cve_check import decode_cve_status | ||
456 | |||
416 | cve_file = d.getVar("CVE_CHECK_LOG") | 457 | cve_file = d.getVar("CVE_CHECK_LOG") |
417 | fdir_name = d.getVar("FILE_DIRNAME") | 458 | fdir_name = d.getVar("FILE_DIRNAME") |
418 | layer = fdir_name.split("/")[-3] | 459 | layer = fdir_name.split("/")[-3] |
@@ -441,20 +482,27 @@ def cve_write_data_text(d, patched, unpatched, ignored, cve_data): | |||
441 | is_patched = cve in patched | 482 | is_patched = cve in patched |
442 | is_ignored = cve in ignored | 483 | is_ignored = cve in ignored |
443 | 484 | ||
485 | status = "Unpatched" | ||
444 | if (is_patched or is_ignored) and not report_all: | 486 | if (is_patched or is_ignored) and not report_all: |
445 | continue | 487 | continue |
488 | if is_ignored: | ||
489 | status = "Ignored" | ||
490 | elif is_patched: | ||
491 | status = "Patched" | ||
492 | else: | ||
493 | # default value of status is Unpatched | ||
494 | unpatched_cves.append(cve) | ||
446 | 495 | ||
447 | write_string += "LAYER: %s\n" % layer | 496 | write_string += "LAYER: %s\n" % layer |
448 | write_string += "PACKAGE NAME: %s\n" % d.getVar("PN") | 497 | write_string += "PACKAGE NAME: %s\n" % d.getVar("PN") |
449 | write_string += "PACKAGE VERSION: %s%s\n" % (d.getVar("EXTENDPE"), d.getVar("PV")) | 498 | write_string += "PACKAGE VERSION: %s%s\n" % (d.getVar("EXTENDPE"), d.getVar("PV")) |
450 | write_string += "CVE: %s\n" % cve | 499 | write_string += "CVE: %s\n" % cve |
451 | if is_ignored: | 500 | write_string += "CVE STATUS: %s\n" % status |
452 | write_string += "CVE STATUS: Ignored\n" | 501 | _, detail, description = decode_cve_status(d, cve) |
453 | elif is_patched: | 502 | if detail: |
454 | write_string += "CVE STATUS: Patched\n" | 503 | write_string += "CVE DETAIL: %s\n" % detail |
455 | else: | 504 | if description: |
456 | unpatched_cves.append(cve) | 505 | write_string += "CVE DESCRIPTION: %s\n" % description |
457 | write_string += "CVE STATUS: Unpatched\n" | ||
458 | write_string += "CVE SUMMARY: %s\n" % cve_data[cve]["summary"] | 506 | write_string += "CVE SUMMARY: %s\n" % cve_data[cve]["summary"] |
459 | write_string += "CVSS v2 BASE SCORE: %s\n" % cve_data[cve]["scorev2"] | 507 | write_string += "CVSS v2 BASE SCORE: %s\n" % cve_data[cve]["scorev2"] |
460 | write_string += "CVSS v3 BASE SCORE: %s\n" % cve_data[cve]["scorev3"] | 508 | write_string += "CVSS v3 BASE SCORE: %s\n" % cve_data[cve]["scorev3"] |
@@ -516,6 +564,8 @@ def cve_write_data_json(d, patched, unpatched, ignored, cve_data, cve_status): | |||
516 | Prepare CVE data for the JSON format, then write it. | 564 | Prepare CVE data for the JSON format, then write it. |
517 | """ | 565 | """ |
518 | 566 | ||
567 | from oe.cve_check import decode_cve_status | ||
568 | |||
519 | output = {"version":"1", "package": []} | 569 | output = {"version":"1", "package": []} |
520 | nvd_link = "https://nvd.nist.gov/vuln/detail/" | 570 | nvd_link = "https://nvd.nist.gov/vuln/detail/" |
521 | 571 | ||
@@ -576,6 +626,11 @@ def cve_write_data_json(d, patched, unpatched, ignored, cve_data, cve_status): | |||
576 | "status" : status, | 626 | "status" : status, |
577 | "link": issue_link | 627 | "link": issue_link |
578 | } | 628 | } |
629 | _, detail, description = decode_cve_status(d, cve) | ||
630 | if detail: | ||
631 | cve_item["detail"] = detail | ||
632 | if description: | ||
633 | cve_item["description"] = description | ||
579 | cve_list.append(cve_item) | 634 | cve_list.append(cve_item) |
580 | 635 | ||
581 | package_data["issue"] = cve_list | 636 | package_data["issue"] = cve_list |