[OE-core][PATCH v4 1/3] cve-check: add option to add additional patched CVEs

Andrej Valek
2023-05-19T08:19:17Z

- Replace CVE_CHECK_IGNORE with CVE_STATUS + [CVE_STATUS_REASONING] to be
more flexible. CVE_STATUS should contain flag for each CVE with accepted
values "Ignored", "Not applicable" or "Patched". It allows to add
a status for each CVEs.
- Optional CVE_STATUS_REASONING flag variable may contain a reason
why the CVE status was used. It will be added in csv/json report like
a new "reason" entry.
- Settings the same status and reason for multiple CVEs is possible
via CVE_STATUS_GROUPS variable.
- All listed CVEs in CVE_CHECK_IGNORE are copied to CVE_STATUS with
value "Ignored" like a fallback.

Examples of usage:
CVE_STATUS[CVE-1234-0001] = "Ignored" # or "Not applicable" or "Patched"
CVE_STATUS[CVE-1234-0002] = "Not applicable"
CVE_STATUS_REASONING[CVE-1234-0002] = "Issue only applies on Windows"

CVE_STATUS_GROUPS = "CVE_STATUS_WIN CVE_STATUS_PATCHED"
CVE_STATUS_WIN = "CVE-1234-0001 CVE-1234-0002"
CVE_STATUS_WIN[status] = "Not applicable"
CVE_STATUS_WIN[reason] = "Issue only applies on Windows"

CVE_STATUS_PATCHED = "CVE-1234-0003 CVE-1234-0004"
CVE_STATUS_PATCHED[status] = "Patched"
CVE_STATUS_PATCHED[reason] = "Fixed externally"

Signed-off-by: Andrej Valek <andrej.valek@siemens.com>
Signed-off-by: Peter Marko <peter.marko@siemens.com>
---
meta/classes/cve-check.bbclass | 44 ++++++++++++++++++++++++++++++----
meta/lib/oe/cve_check.py | 6 +++++
2 files changed, 45 insertions(+), 5 deletions(-)

diff --git a/meta/classes/cve-check.bbclass b/meta/classes/cve-check.bbclass
index bd9e7e7445c..44462de7445 100644
--- a/meta/classes/cve-check.bbclass
+++ b/meta/classes/cve-check.bbclass
@@ -70,12 +70,15 @@ CVE_CHECK_COVERAGE ??= "1"
# Skip CVE Check for packages (PN)
CVE_CHECK_SKIP_RECIPE ?= ""

-# Ingore the check for a given list of CVEs. If a CVE is found,
-# then it is considered patched. The value is a string containing
-# space separated CVE values:
+# Replace NVD DB check status for a given CVE. Each of CVE has to be mentioned
+# separately with optional reason for this status.
#
-# CVE_CHECK_IGNORE = 'CVE-2014-2524 CVE-2018-1234'
+# CVE_STATUS[CVE-1234-0001] = "Ignored" # or "Not applicable" or "Patched"
+# CVE_STATUS[CVE-1234-0002] = "Not applicable"
+# CVE_STATUS_REASONING[CVE-1234-0002] = "Issue only applies on Windows"
#
+# CVE_CHECK_IGNORE is deprecated and CVE_STATUS has to be used instead.
+# Keep CVE_CHECK_IGNORE until other layers migrate to new variables
CVE_CHECK_IGNORE ?= ""

# Layers to be excluded
@@ -88,6 +91,25 @@ CVE_CHECK_LAYER_INCLUDELIST ??= ""
# set to "alphabetical" for version using single alphabetical character as increment release
CVE_VERSION_SUFFIX ??= ""

+python () {
+ # Fallback all CVEs from CVE_CHECK_IGNORE to CVE_STATUS
+ cve_check_ignore = d.getVar("CVE_CHECK_IGNORE")
+ if cve_check_ignore:
+ bb.warn("CVE_CHECK_IGNORE has been deprecated, use CVE_STATUS instead")
+ set_cves_statuses(d, d.getVar("CVE_CHECK_IGNORE"), "Ignored")
+
+ # Process CVE_STATUS_GROUPS to set multiple statuses and optional reasons at once
+ for cve_status_group in (d.getVar("CVE_STATUS_GROUPS") or "").split():
+ set_cves_statuses(d, d.getVar(cve_status_group) or "",
+ d.getVarFlag(cve_status_group, "status"),
+ d.getVarFlag(cve_status_group, "reason"))
+}
+
+def set_cves_statuses(d, cves, status, reason=""):
+ for cve in cves.split():
+ d.setVarFlag("CVE_STATUS", cve, status)
+ d.setVarFlag("CVE_STATUS_REASONING", cve, reason)
+
def generate_json_report(d, out_path, link_path):
if os.path.exists(d.getVar("CVE_CHECK_SUMMARY_INDEX_PATH")):
import json
@@ -282,7 +304,13 @@ def check_cves(d, patched_cves):
bb.note("Recipe has been skipped by cve-check")
return ([], [], [], [])

- cve_ignore = d.getVar("CVE_CHECK_IGNORE").split()
+ # Convert CVE_STATUS into ignored CVEs and check validity
+ cve_ignore = []
+ for cve, status in (d.getVarFlags("CVE_STATUS") or {}).items():
+ if status in ["Not applicable", "Ignored"]:
+ cve_ignore.append(cve)
+ elif status not in ["Patched"]:
+ bb.error("Unsupported status %s in CVE_STATUS[%s]" % (status, cve))

import sqlite3
db_file = d.expand("file:${CVE_CHECK_DB_FILE}?mode=ro")
@@ -455,6 +483,9 @@ def cve_write_data_text(d, patched, unpatched, ignored, cve_data):
else:
unpatched_cves.append(cve)
write_string += "CVE STATUS: Unpatched\n"
+ reasoning = d.getVarFlag("CVE_STATUS_REASONING", cve)
+ if reasoning:
+ write_string += "CVE REASON: %s\n" % reasoning
write_string += "CVE SUMMARY: %s\n" % cve_data[cve]["summary"]
write_string += "CVSS v2 BASE SCORE: %s\n" % cve_data[cve]["scorev2"]
write_string += "CVSS v3 BASE SCORE: %s\n" % cve_data[cve]["scorev3"]
@@ -576,6 +607,9 @@ def cve_write_data_json(d, patched, unpatched, ignored, cve_data, cve_status):
"status" : status,
"link": issue_link
}
+ reasoning = d.getVarFlag("CVE_STATUS_REASONING", cve)
+ if reasoning:
+ cve_item["reason"] = reasoning
cve_list.append(cve_item)

package_data["issue"] = cve_list
diff --git a/meta/lib/oe/cve_check.py b/meta/lib/oe/cve_check.py
index dbaa0b373a3..f47dd9920ef 100644
--- a/meta/lib/oe/cve_check.py
+++ b/meta/lib/oe/cve_check.py
@@ -130,6 +130,12 @@ def get_patched_cves(d):
if not fname_match and not text_match:
bb.debug(2, "Patch %s doesn't solve CVEs" % patch_file)

+ # Search for additional patched CVEs
+ for cve, status in (d.getVarFlags("CVE_STATUS") or {}).items():
+ if status == "Patched":
+ bb.debug(2, "CVE %s is additionally patched" % cve)
+ patched_cves.add(cve)
+
return patched_cves

--
2.40.1