@@ -70,12 +70,16 @@ 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 detail and description for this status.
#
-# CVE_CHECK_IGNORE = 'CVE-2014-2524 CVE-2018-1234'
+# CVE_STATUS[CVE-1234-0001] = "Ignored" # or "Patched" or "Unpatched"
+# CVE_STATUS[CVE-1234-0002] = "Ignored"
+# CVE_STATUS_DETAIL[CVE-1234-0002] = "not-applicable-platform"
+# CVE_STATUS_DESCRIPTION[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 +92,47 @@ 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 is deprecated in favor of CVE_STATUS")
+ set_cves_statuses(d, d.getVar("CVE_CHECK_IGNORE"), "Ignored")
+
+ # Process CVE_STATUS_GROUPS to set multiple statuses and optional detail or description at once
+ for cve_status_group in (d.getVar("CVE_STATUS_GROUPS") or "").split():
+ cve_group = d.getVar(cve_status_group)
+ if cve_group is not None:
+ set_cves_statuses(d, cve_group,
+ d.getVarFlag(cve_status_group, "status"),
+ d.getVarFlag(cve_status_group, "detail"),
+ d.getVarFlag(cve_status_group, "description"))
+ else:
+ bb.warn("CVE_STATUS_GROUPS contains undefined variable %s" % cve_status_group)
+}
+
+def set_cves_statuses(d, cves, status, detail="", description=""):
+ for cve in cves.split():
+ d.setVarFlag("CVE_STATUS", cve, status)
+ d.setVarFlag("CVE_STATUS_DETAIL", cve, detail)
+ d.setVarFlag("CVE_STATUS_DESCRIPTION", cve, description)
+
+def get_cve_detail(d, cve, status):
+ detail = d.getVarFlag("CVE_STATUS_DETAIL", cve)
+ if detail is not None:
+ if status == "Patched":
+ if detail in ["fixed-version", "backported-patch", "cpe-stable-backport", "other"]:
+ return detail
+ elif status == "Unpatched":
+ if detail in ["vulnerable-investigating", "other"]:
+ return detail
+ else:
+ if detail in ["cpe-incorrect", "not-applicable-platform", "upstream-wontfix",
+ "not-applicable-config", "not-affected", "other"]:
+ return detail
+ bb.warn('Invalid detail %s for CVE_STATUS[%s] = "%s"' % (detail, cve, status))
+ return ""
+
def generate_json_report(d, out_path, link_path):
if os.path.exists(d.getVar("CVE_CHECK_SUMMARY_INDEX_PATH")):
import json
@@ -282,7 +327,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 == "Ignored":
+ cve_ignore.append(cve)
+ elif status not in ["Patched", "Unpatched"]:
+ bb.error("Unsupported status %s in CVE_STATUS[%s]" % (status, cve))
import sqlite3
db_file = d.expand("file:${CVE_CHECK_DB_FILE}?mode=ro")
@@ -441,20 +492,28 @@ def cve_write_data_text(d, patched, unpatched, ignored, cve_data):
is_patched = cve in patched
is_ignored = cve in ignored
+ status = "Unpatched"
if (is_patched or is_ignored) and not report_all:
continue
+ if is_ignored:
+ status = "Ignored"
+ elif is_patched:
+ status = "Patched"
+ else:
+ # default value of status is Unpatched
+ unpatched_cves.append(cve)
write_string += "LAYER: %s\n" % layer
write_string += "PACKAGE NAME: %s\n" % d.getVar("PN")
write_string += "PACKAGE VERSION: %s%s\n" % (d.getVar("EXTENDPE"), d.getVar("PV"))
write_string += "CVE: %s\n" % cve
- if is_ignored:
- write_string += "CVE STATUS: Ignored\n"
- elif is_patched:
- write_string += "CVE STATUS: Patched\n"
- else:
- unpatched_cves.append(cve)
- write_string += "CVE STATUS: Unpatched\n"
+ write_string += "CVE STATUS: %s\n" % status
+ detail = get_cve_detail(d, cve, status)
+ if detail:
+ write_string += "CVE DETAIL: %s\n" % detail
+ description = d.getVarFlag("CVE_STATUS_DESCRIPTION", cve)
+ if description:
+ write_string += "CVE DESCRIPTION: %s\n" % description
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 +635,12 @@ def cve_write_data_json(d, patched, unpatched, ignored, cve_data, cve_status):
"status" : status,
"link": issue_link
}
+ detail = get_cve_detail(d, cve, status)
+ if detail:
+ cve_item["detail"] = detail
+ description = d.getVarFlag("CVE_STATUS_DESCRIPTION", cve)
+ if description:
+ cve_item["description"] = description
cve_list.append(cve_item)
package_data["issue"] = cve_list
@@ -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