mirror of
https://git.yoctoproject.org/poky
synced 2026-03-19 13:49:41 +01:00
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>
This commit is contained in:
committed by
Richard Purdie
parent
ebb8b39463
commit
be9883a92b
@@ -70,12 +70,28 @@ 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] = "not-applicable-platform: Issue only applies on Windows"
|
||||
# CVE_STATUS[CVE-1234-0002] = "fixed-version: Fixed externally"
|
||||
#
|
||||
# Settings the same status and reason for multiple CVEs is possible
|
||||
# via CVE_STATUS_GROUPS variable.
|
||||
#
|
||||
# CVE_STATUS_GROUPS = "CVE_STATUS_WIN CVE_STATUS_PATCHED"
|
||||
#
|
||||
# CVE_STATUS_WIN = "CVE-1234-0001 CVE-1234-0003"
|
||||
# CVE_STATUS_WIN[status] = "not-applicable-platform: Issue only applies on Windows"
|
||||
# CVE_STATUS_PATCHED = "CVE-1234-0002 CVE-1234-0004"
|
||||
# CVE_STATUS_PATCHED[status] = "fixed-version: Fixed externally"
|
||||
#
|
||||
# All possible CVE statuses could be found in cve-check-map.conf
|
||||
# CVE_CHECK_STATUSMAP[not-applicable-platform] = "Ignored"
|
||||
# CVE_CHECK_STATUSMAP[fixed-version] = "Patched"
|
||||
#
|
||||
# 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 +104,24 @@ 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")
|
||||
for cve in (d.getVar("CVE_CHECK_IGNORE") or "").split():
|
||||
d.setVarFlag("CVE_STATUS", cve, "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:
|
||||
for cve in cve_group.split():
|
||||
d.setVarFlag("CVE_STATUS", cve, d.getVarFlag(cve_status_group, "status"))
|
||||
else:
|
||||
bb.warn("CVE_STATUS_GROUPS contains undefined variable %s" % cve_status_group)
|
||||
}
|
||||
|
||||
def generate_json_report(d, out_path, link_path):
|
||||
if os.path.exists(d.getVar("CVE_CHECK_SUMMARY_INDEX_PATH")):
|
||||
import json
|
||||
@@ -260,7 +294,7 @@ def check_cves(d, patched_cves):
|
||||
"""
|
||||
Connect to the NVD database and find unpatched cves.
|
||||
"""
|
||||
from oe.cve_check import Version, convert_cve_version
|
||||
from oe.cve_check import Version, convert_cve_version, decode_cve_status
|
||||
|
||||
pn = d.getVar("PN")
|
||||
real_pv = d.getVar("PV")
|
||||
@@ -282,7 +316,12 @@ 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 in (d.getVarFlags("CVE_STATUS") or {}):
|
||||
decoded_status, _, _ = decode_cve_status(d, cve)
|
||||
if decoded_status == "Ignored":
|
||||
cve_ignore.append(cve)
|
||||
|
||||
import sqlite3
|
||||
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):
|
||||
CVE manifest if enabled.
|
||||
"""
|
||||
|
||||
from oe.cve_check import decode_cve_status
|
||||
|
||||
cve_file = d.getVar("CVE_CHECK_LOG")
|
||||
fdir_name = d.getVar("FILE_DIRNAME")
|
||||
layer = fdir_name.split("/")[-3]
|
||||
@@ -441,20 +482,27 @@ 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, description = decode_cve_status(d, cve)
|
||||
if detail:
|
||||
write_string += "CVE DETAIL: %s\n" % detail
|
||||
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"]
|
||||
@@ -516,6 +564,8 @@ def cve_write_data_json(d, patched, unpatched, ignored, cve_data, cve_status):
|
||||
Prepare CVE data for the JSON format, then write it.
|
||||
"""
|
||||
|
||||
from oe.cve_check import decode_cve_status
|
||||
|
||||
output = {"version":"1", "package": []}
|
||||
nvd_link = "https://nvd.nist.gov/vuln/detail/"
|
||||
|
||||
@@ -576,6 +626,11 @@ def cve_write_data_json(d, patched, unpatched, ignored, cve_data, cve_status):
|
||||
"status" : status,
|
||||
"link": issue_link
|
||||
}
|
||||
_, detail, description = decode_cve_status(d, cve)
|
||||
if detail:
|
||||
cve_item["detail"] = detail
|
||||
if description:
|
||||
cve_item["description"] = description
|
||||
cve_list.append(cve_item)
|
||||
|
||||
package_data["issue"] = cve_list
|
||||
|
||||
@@ -831,6 +831,7 @@ include conf/distro/defaultsetup.conf
|
||||
include conf/documentation.conf
|
||||
include conf/licenses.conf
|
||||
require conf/sanity.conf
|
||||
require conf/cve-check-map.conf
|
||||
|
||||
##################################################################
|
||||
# Weak variables (usually to retain backwards compatibility)
|
||||
|
||||
28
meta/conf/cve-check-map.conf
Normal file
28
meta/conf/cve-check-map.conf
Normal file
@@ -0,0 +1,28 @@
|
||||
# Possible options for CVE statuses
|
||||
|
||||
# used by this class internally when fix is detected (NVD DB version check or CVE patch file)
|
||||
CVE_CHECK_STATUSMAP[patched] = "Patched"
|
||||
# use when this class does not detect backported patch (e.g. vendor kernel repo with cherry-picked CVE patch)
|
||||
CVE_CHECK_STATUSMAP[backported-patch] = "Patched"
|
||||
# use when NVD DB does not mention patched versions of stable/LTS branches which have upstream CVE backports
|
||||
CVE_CHECK_STATUSMAP[cpe-stable-backport] = "Patched"
|
||||
# use when NVD DB does not mention correct version or does not mention any verion at all
|
||||
CVE_CHECK_STATUSMAP[fixed-version] = "Patched"
|
||||
|
||||
# used internally by this class if CVE vulnerability is detected which is not marked as fixed or ignored
|
||||
CVE_CHECK_STATUSMAP[unpatched] = "Unpatched"
|
||||
# use when CVE is confirmed by upstream but fix is still not available
|
||||
CVE_CHECK_STATUSMAP[vulnerable-investigating] = "Unpatched"
|
||||
|
||||
# used for migration from old concept, do not use for new vulnerabilities
|
||||
CVE_CHECK_STATUSMAP[ignored] = "Ignored"
|
||||
# use when NVD DB wrongly indicates vulnerability which is actually for a different component
|
||||
CVE_CHECK_STATUSMAP[cpe-incorrect] = "Ignored"
|
||||
# use when upstream does not accept the report as a vulnerability (e.g. works as designed)
|
||||
CVE_CHECK_STATUSMAP[disputed] = "Ignored"
|
||||
# use when vulnerability depends on build or runtime configuration which is not used
|
||||
CVE_CHECK_STATUSMAP[not-applicable-config] = "Ignored"
|
||||
# use when vulnerability affects other platform (e.g. Windows or Debian)
|
||||
CVE_CHECK_STATUSMAP[not-applicable-platform] = "Ignored"
|
||||
# use when upstream acknowledged the vulnerability but does not plan to fix it
|
||||
CVE_CHECK_STATUSMAP[upstream-wontfix] = "Ignored"
|
||||
@@ -130,6 +130,13 @@ 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 in (d.getVarFlags("CVE_STATUS") or {}):
|
||||
decoded_status, _, _ = decode_cve_status(d, cve)
|
||||
if decoded_status == "Patched":
|
||||
bb.debug(2, "CVE %s is additionally patched" % cve)
|
||||
patched_cves.add(cve)
|
||||
|
||||
return patched_cves
|
||||
|
||||
|
||||
@@ -218,3 +225,21 @@ def convert_cve_version(version):
|
||||
|
||||
return version + update
|
||||
|
||||
def decode_cve_status(d, cve):
|
||||
"""
|
||||
Convert CVE_STATUS into status, detail and description.
|
||||
"""
|
||||
status = d.getVarFlag("CVE_STATUS", cve)
|
||||
if status is None:
|
||||
return ("", "", "")
|
||||
|
||||
status_split = status.split(':', 1)
|
||||
detail = status_split[0]
|
||||
description = status_split[1].strip() if (len(status_split) > 1) else ""
|
||||
|
||||
status_mapping = d.getVarFlag("CVE_CHECK_STATUSMAP", detail)
|
||||
if status_mapping is None:
|
||||
bb.warn('Invalid detail %s for CVE_STATUS[%s] = "%s", fallback to Unpatched' % (detail, cve, status))
|
||||
status_mapping = "Unpatched"
|
||||
|
||||
return (status_mapping, detail, description)
|
||||
|
||||
Reference in New Issue
Block a user