From: <daniel.turull@ericsson.com>
To: <openembedded-core@lists.openembedded.org>
Cc: <rybczynska@gmail.com>, <steve@sakoman.com>,
<Peter.Marko@siemens.com>, <ross.burton@arm.com>,
<skandigraun@gmail.com>,
Daniel Turull <daniel.turull@ericsson.com>,
Peter Marko <peter.marko@siemens.com>
Subject: [PATCH v2 5/6] cve-check, vex, spdx: use metadata from linux-vulns to enhance CVE reporting
Date: Mon, 28 Apr 2025 15:42:04 +0200 [thread overview]
Message-ID: <20250428134205.900354-6-daniel.turull@ericsson.com> (raw)
In-Reply-To: <20250428134205.900354-1-daniel.turull@ericsson.com>
From: Daniel Turull <daniel.turull@ericsson.com>
Introducing two new options to check kernel CVEs using linux-vulns.
- CVE_CHECK_KERNEL = "1"
To add linux-vulns metadata on cve-report for cve_check, vex and spdx.
This will check for kernel CVEs, and use the metadata to resolve them.
Enabled by default
- CVE_CHECK_KERNEL_CONFIG = "1"
CVE Check using kernel compiled files, disabled by default since it requires
a compiled kernel and it will increase cve-check times.
Metadata in the CVE information includes the affected files, and using the
compiled files by the kernel we can ignore some of the cves.
The above variables are defined in cve_check.bbclass, vex.bbclass and
spdx.bbclass in case not all classes are used at the same time. The ones in
cve_check has priority.
Example of output with CVE_CHECK_KERNEL_CONFIG when using cve-check:
{
"id": "CVE-2024-26710",
"status": "Ignored",
"link": "https://nvd.nist.gov/vuln/detail/CVE-2024-26710",
"summary": "In the Linux kernel, the following vulnerability [...]",
"scorev2": "0.0",
"scorev3": "5.5",
"scorev4": "0.0",
"modified": "2025-03-17T15:36:11.620",
"vector": "LOCAL",
"vectorString": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H",
"detail": "not-applicable-config",
"description": "Source code not compiled by config. ['arch/powerpc/include/asm/thread_info.h']"
},
And same with vex:
{
"id": "CVE-2024-26710",
"status": "Ignored",
"link": "https://nvd.nist.gov/vuln/detail/CVE-2024-26710",
"detail": "not-applicable-config",
"description": "Source code not compiled by config. ['arch/powerpc/include/asm/thread_info.h']"
},
And new information for the Unpatched showing where the fix (if any) is available:
Tested with 6.12.22 kernel
{
"id": "CVE-2025-39728",
"status": "Unpatched",
"link": "https://nvd.nist.gov/vuln/detail/CVE-2025-39728",
"summary": "In the Linux kernel, the following vulnerability has been [...],
"scorev2": "0.0",
"scorev3": "0.0",
"scorev4": "0.0",
"modified": "2025-04-21T14:23:45.950",
"vector": "UNKNOWN",
"vectorString": "UNKNOWN",
"detail": "version-in-range",
"description": "Needs backporting (fixed from 6.12.23)"
},
Tested with cve-check, create-spdx2.2, create-spdx3.0, vex
CC: Peter Marko <peter.marko@siemens.com>
Signed-off-by: Daniel Turull <daniel.turull@ericsson.com>
---
meta/classes/cve-check.bbclass | 19 ++-
meta/classes/spdx-common.bbclass | 7 ++
meta/classes/vex.bbclass | 10 ++
meta/lib/oe/cve_check.py | 210 ++++++++++++++++++++++++++++++-
4 files changed, 244 insertions(+), 2 deletions(-)
diff --git a/meta/classes/cve-check.bbclass b/meta/classes/cve-check.bbclass
index 12159a98d2..02266f171f 100644
--- a/meta/classes/cve-check.bbclass
+++ b/meta/classes/cve-check.bbclass
@@ -34,6 +34,13 @@ CVE_VERSION ??= "${PV}"
# Possible database sources: NVD1, NVD2, FKIE
NVD_DB_VERSION ?= "FKIE"
+# CVE Check using kernel CNA and compiled files
+CVE_CHECK_KERNEL ?= "1"
+# CVE Check using kernel compiled files
+CVE_CHECK_KERNEL_CONFIG ?= "0"
+# Location of the linux-vulns data
+CVE_CHECK_KERNEL_DB_DIR ?= "${DL_DIR}/CVE_CHECK/vulns"
+
# Use different file names for each database source, as they synchronize at different moments, so may be slightly different
CVE_CHECK_DB_FILENAME ?= "${@'nvdcve_2-2.db' if d.getVar('NVD_DB_VERSION') == 'NVD2' else 'nvdcve_1-3.db' if d.getVar('NVD_DB_VERSION') == 'NVD1' else 'nvdfkie_1-1.db'}"
CVE_CHECK_DB_FETCHER ?= "${@'cve-update-nvd2-native' if d.getVar('NVD_DB_VERSION') == 'NVD2' else 'cve-update-db-native'}"
@@ -111,6 +118,9 @@ python () {
if nvd_database_type not in ("NVD1", "NVD2", "FKIE"):
bb.erroronce("Malformed NVD_DB_VERSION, must be one of: NVD1, NVD2, FKIE. Defaulting to NVD2")
d.setVar("NVD_DB_VERSION", "NVD2")
+
+ from oe.cve_check import extend_cve_kernel_config
+ extend_cve_kernel_config(d, "do_cve_check")
}
def generate_json_report(d, out_path, link_path):
@@ -161,7 +171,7 @@ python do_cve_check () {
"""
Check recipe for patched and unpatched CVEs
"""
- from oe.cve_check import get_patched_cves
+ from oe.cve_check import get_patched_cves, get_kernel_cves
with bb.utils.fileslocked([d.getVar("CVE_CHECK_DB_FILE_LOCK")], shared=True):
if os.path.exists(d.getVar("CVE_CHECK_DB_FILE")):
@@ -170,6 +180,13 @@ python do_cve_check () {
except FileNotFoundError:
bb.fatal("Failure in searching patches")
cve_data, status = check_cves(d, patched_cves)
+
+ cve_check_kernel = d.getVar("CVE_CHECK_KERNEL")
+ if "linux_kernel" in d.getVar("CVE_PRODUCT") and cve_check_kernel == "1":
+ kernel_unpatched_cves, _kernel_patched_cves = get_kernel_cves(d)
+ # Patched CVEs are already returned in the get_patched_cves(). Adding the unpatched
+ cve_data.update(kernel_unpatched_cves)
+
if len(cve_data) or (d.getVar("CVE_CHECK_COVERAGE") == "1" and status):
get_cve_info(d, cve_data)
cve_write_data(d, cve_data, status)
diff --git a/meta/classes/spdx-common.bbclass b/meta/classes/spdx-common.bbclass
index 713a7fc651..d2b9c6993a 100644
--- a/meta/classes/spdx-common.bbclass
+++ b/meta/classes/spdx-common.bbclass
@@ -37,9 +37,16 @@ SPDX_CUSTOM_ANNOTATION_VARS ??= ""
SPDX_MULTILIB_SSTATE_ARCHS ??= "${SSTATE_ARCHS}"
+CVE_CHECK_KERNEL ??= "0"
+CVE_CHECK_KERNEL_CONFIG ??= "0"
+CVE_CHECK_KERNEL_DB_DIR ?= "${DL_DIR}/CVE_CHECK/vulns"
+
python () {
from oe.cve_check import extend_cve_status
extend_cve_status(d)
+
+ from oe.cve_check import extend_cve_kernel_config
+ extend_cve_kernel_config(d, "do_create_spdx")
}
def create_spdx_source_deps(d):
diff --git a/meta/classes/vex.bbclass b/meta/classes/vex.bbclass
index 905d67b47d..56ebdb0580 100644
--- a/meta/classes/vex.bbclass
+++ b/meta/classes/vex.bbclass
@@ -26,6 +26,13 @@
CVE_PRODUCT ??= "${BPN}"
CVE_VERSION ??= "${PV}"
+# CVE Check using kernel CNA and compiled files
+CVE_CHECK_KERNEL ?= "0"
+# CVE Check using kernel compiled files
+CVE_CHECK_KERNEL_CONFIG ?= "0"
+
+CVE_CHECK_KERNEL_DB_DIR ?= "${DL_DIR}/CVE_CHECK/vulns"
+
CVE_CHECK_SUMMARY_DIR ?= "${LOG_DIR}/cve"
CVE_CHECK_SUMMARY_FILE_NAME_JSON = "cve-summary.json"
@@ -78,6 +85,9 @@ python () {
from oe.cve_check import extend_cve_status
extend_cve_status(d)
+
+ from oe.cve_check import extend_cve_kernel_config
+ extend_cve_kernel_config(d, "do_generate_vex")
}
def generate_json_report(d, out_path, link_path):
diff --git a/meta/lib/oe/cve_check.py b/meta/lib/oe/cve_check.py
index ae194f27cf..9f34da0e91 100644
--- a/meta/lib/oe/cve_check.py
+++ b/meta/lib/oe/cve_check.py
@@ -47,6 +47,7 @@ class Version():
self._version.pre_l,
self._version.pre_v
)
+ self.version = version
def __eq__(self, other):
if not isinstance(other, Version):
@@ -58,6 +59,9 @@ class Version():
return NotImplemented
return self._key > other._key
+ def __str__(self) -> str:
+ return self.version
+
def _cmpkey(release, patch_l, pre_l, pre_v):
# remove leading 0
_release = tuple(
@@ -202,8 +206,14 @@ def get_patched_cves(d):
"affected-product": decoded_status["product"],
}
- return patched_cves
+ # If we are parsing the kernel, check compiled files
+ cve_check_kernel = d.getVar("CVE_CHECK_KERNEL")
+ if "linux_kernel" in d.getVar("CVE_PRODUCT") and cve_check_kernel == "1":
+ bb.debug(1, "Checking kernel CVEs")
+ _kernel_unpatched_cves, kernel_patched_cves = get_kernel_cves(d)
+ patched_cves.update(kernel_patched_cves)
+ return patched_cves
def get_cpe_ids(cve_product, version):
"""
@@ -376,3 +386,201 @@ def extend_cve_status(d):
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 extend_cve_kernel_config(d, task):
+ pn = d.getVar('PN')
+ # For kernel CVEs, add required dependencies
+ if "linux_kernel" in d.getVar("CVE_PRODUCT"):
+ if d.getVar("CVE_CHECK_KERNEL_CONFIG") == "1":
+ bb.debug(1, "Checking kernel CVEs using kernel config")
+ depends = f" {pn}:do_save_compiled_files "
+ d.appendVarFlag(task, "depends", depends)
+ d.setVar('CVE_CHECK_KERNEL','1')
+ if d.getVar("CVE_CHECK_KERNEL") == "1":
+ bb.debug(1, "Checking kernel CVEs using linux-vulns")
+ d.appendVarFlag(task, "depends", " linux-vulns:do_fetch ")
+
+def get_kernel_cves(d):
+ """
+ Get CVEs for the kernel
+ """
+ import glob
+ import json
+ patched_cves = {}
+ unpatched_cves = {}
+ datadir = f"{d.getVar('CVE_CHECK_KERNEL_DB_DIR')}/cve/published/"
+ version_str = d.getVar("LINUX_VERSION")
+ check_config = d.getVar("CVE_CHECK_KERNEL_CONFIG")
+ version = Version(version_str)
+ base_version = Version(".".join(version_str.split(".")[0:2]))
+
+ # Check all CVES from kernel vulns
+ pattern = os.path.join(datadir, '**', f"CVE-*.json")
+ cve_files = glob.glob(pattern, recursive=True)
+ not_applicable_config = 0
+ fixed_as_later_backport = 0
+ for cve_file in cve_files:
+ cve_info = {}
+ with open(cve_file, "r") as f:
+ cve_info = json.load(f)
+
+ if len(cve_info) == 0:
+ bb.error(f"Not valid data in {cve_file}. Aborting")
+ break
+ cve_id = cve_info["cveMetadata"]["cveID"]
+
+ first_affected, fixed, backport_ver = get_kernel_fixed_versions(cve_info, base_version)
+ if not fixed:
+ if check_config == "1":
+ is_affected, affected_files = check_kernel_compiled_files(d, cve_info)
+ else:
+ is_affected = True
+ if not is_affected and len(affected_files) > 0:
+ bb.debug(1, f"{cve_id} - not applicable configuration since affected files not compiled: {affected_files}")
+ patched_cves[cve_id] = {
+ "abbrev-status": "Ignored",
+ "status": "not-applicable-config",
+ "justification": f"Source code not compiled by config. {affected_files}"
+ }
+ else:
+ bb.debug(1, f"{cve_id} not fixed usptream")
+ description = cve_info["containers"]["cna"]["descriptions"][0]["value"]
+ unpatched_cves[cve_id] = {
+ "abbrev-status": "Unpatched",
+ "status": "version-in-range",
+ "summary": description,
+ "justification": f"No fix available upstream"
+ }
+ elif first_affected and version < first_affected:
+ bb.debug(1, f'{cve_id} - "fixed-version: only affects {first_affected} onwards"')
+ patched_cves[cve_id] = {
+ "abbrev-status": "Patched",
+ "status": "fixed-version",
+ "justification": f"only affects {first_affected} onwards"
+ }
+ elif fixed <= version:
+ bb.debug(1, f'{cve_id} - "fixed-version: Fixed from version {fixed}"')
+ patched_cves[cve_id] = {
+ "abbrev-status": "Patched",
+ "status": "fixed-version",
+ "justification": f"fixed-version: Fixed from version {fixed}"
+ }
+ else:
+ if backport_ver:
+ if backport_ver <= version:
+ bb.debug(1, f'{cve_id} - "cpe-stable-backport: Backported in {backport_ver}"')
+ patched_cves[cve_id] = {
+ "abbrev-status": "Patched",
+ "status": "cpe-stable-backport",
+ "justification": f"Backported in {backport_ver}"
+ }
+ else:
+ bb.debug(1, f"{cve_id}: needs backporting (fixed from {backport_ver})")
+ description = cve_info["containers"]["cna"]["descriptions"][0]["value"]
+ unpatched_cves[cve_id] = {
+ "abbrev-status": "Unpatched",
+ "status": "version-in-range",
+ "summary": description,
+ "justification": f"Needs backporting (fixed from {backport_ver})"
+ }
+ fixed_as_later_backport += 1
+ else:
+ # Check if file affected
+ if check_config == "1":
+ is_affected, affected_files = check_kernel_compiled_files(d, cve_info)
+ else:
+ is_affected = True
+ if not is_affected and len(affected_files) > 0:
+ bb.debug(1, f"{cve_id} - not applicable configuration since affected files not compiled: {affected_files}")
+ patched_cves[cve_id] = {
+ "abbrev-status": "Ignored",
+ "status": "not-applicable-config",
+ "justification": f"Source code not compiled by config. {affected_files}"
+ }
+ not_applicable_config +=1
+ else:
+ bb.debug(1, f"{cve_id}: needs backporting (fixed from {fixed})")
+ description = cve_info["containers"]["cna"]["descriptions"][0]["value"]
+ unpatched_cves[cve_id] = {
+ "abbrev-status": "Unpatched",
+ "status": "version-in-range",
+ "summary": description,
+ "justification": f"Needs backporting (fixed from {fixed})"
+ }
+ if len(cve_files) > 0:
+ bb.debug(1, f"Total CVEs ignored due to not applicable config {not_applicable_config}")
+ bb.debug(1, f"Total vulnerable CVEs: {len(unpatched_cves)}")
+ bb.debug(1, f"Total CVEs already backported in {base_version}: {fixed_as_later_backport}")
+ return unpatched_cves, patched_cves
+
+def check_kernel_compiled_files(d, cve_info):
+ """
+ Return if a CVE affected us depending on compiled files
+ """
+ import json
+ files_affected = []
+ kfiles = []
+ is_affected = False
+ with open(d.getVar('KERNEL_SRC_FILES'), 'r') as file:
+ for item in json.load(file):
+ kfiles.append(item['file'].replace(f"{d.getVar('S')}/",""))
+
+ for item in cve_info['containers']['cna']['affected']:
+ if item["defaultStatus"] == "affected":
+ if "programFiles" in item:
+ files = item['programFiles']
+ files_affected.extend(files)
+
+ if len(files_affected) > 0:
+ for f in files_affected:
+ if f in kfiles:
+ bb.debug(1, f"File match: {f}")
+ is_affected = True
+ return is_affected, files_affected
+
+def get_kernel_fixed_versions(cve_info, base_version):
+ '''
+ Get fixed versions for a given CVE
+ '''
+ first_affected = None
+ fixed = None
+ fixed_backport = None
+ next_version = Version(str(base_version) + ".5000")
+ for affected in cve_info["containers"]["cna"]["affected"]:
+ # In case the CVE info is not complete, it might not have default status and therefore
+ # we don't know the status of this CVE.
+ if not "defaultStatus" in affected:
+ return first_affected, fixed, fixed_backport
+ if affected["defaultStatus"] == "affected":
+ for version in affected["versions"]:
+ v = Version(version["version"])
+ if v == 0:
+ # Skiping non-affected
+ continue
+ if version["status"] == "affected" and not first_affected:
+ first_affected = v
+ elif (version["status"] == "unaffected" and
+ version['versionType'] == "original_commit_for_fix"):
+ fixed = v
+ elif base_version < v and v < next_version:
+ fixed_backport = v
+ elif affected["defaultStatus"] == "unaffected":
+ # Only specific versions are affected. We care only about our base version
+ if "versions" not in affected:
+ continue
+ for version in affected["versions"]:
+ if "versionType" not in version:
+ continue
+ if version["versionType"] == "git":
+ continue
+ v = Version(version["version"])
+ # in case it is not in our base version
+ less_than = Version(version["lessThan"])
+
+ if not first_affected:
+ first_affected = v
+ fixed = less_than
+ if base_version < v and v < next_version:
+ fixed_backport = less_than
+
+ return first_affected, fixed, fixed_backport
next prev parent reply other threads:[~2025-04-28 13:42 UTC|newest]
Thread overview: 14+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-04-28 13:41 [PATCH v2 0/6] Check compiled files to filter kernel CVEs daniel.turull
2025-04-28 13:42 ` [PATCH v2 1/6] linux-vulns: fetch kernel.org CNA info daniel.turull
2025-04-28 13:42 ` [PATCH v2 2/6] cve-check: fix debug message daniel.turull
2025-04-28 13:42 ` [PATCH v2 3/6] kernel: add support to extract compiled files daniel.turull
2025-04-28 14:24 ` [OE-core] " Bruce Ashfield
2025-04-28 14:28 ` Daniel Turull
2025-04-28 13:42 ` [PATCH v2 4/6] cve-check: move message outsite check_cves and sort daniel.turull
2025-04-28 13:42 ` daniel.turull [this message]
2025-04-28 13:42 ` [PATCH v2 6/6] spdx: add option to include only compiled kernel files daniel.turull
2025-04-28 13:57 ` Joshua Watt
2025-04-28 13:59 ` Marko, Peter
2025-04-28 14:12 ` Daniel Turull
2025-04-28 14:44 ` Joshua Watt
2025-04-28 14:25 ` Daniel Turull
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20250428134205.900354-6-daniel.turull@ericsson.com \
--to=daniel.turull@ericsson.com \
--cc=Peter.Marko@siemens.com \
--cc=openembedded-core@lists.openembedded.org \
--cc=ross.burton@arm.com \
--cc=rybczynska@gmail.com \
--cc=skandigraun@gmail.com \
--cc=steve@sakoman.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox