[PATCH v5] sbom-cve-check: Add class for post-build CVE analysis

Benjamin Robin
2026-03-19T14:41:41Z

By default, the sbom-cve-check class generates these export files:
- A JSON in `cve-check` format, named `${IMAGE_NAME}.cve-check.json`
- An SPDX 3.0 SBOM, named `${IMAGE_NAME}.cve-check.spdx.json`.

A user can add or remove export file formats by using the
`SBOM_CVE_CHECK_EXPORT_VARS` variable.

By default, the CVE databases are downloaded using the following
recipes:
- sbom-cve-check-update-cvelist-native.bb
- sbom-cve-check-update-nvd-native.bb

The database fetch and deploy logic is implemented in
sbom-cve-check-update-db.inc. The CVE databases are stored in the
download directory (`DL_DIR`) by default. This can be configured by
the `SBOM_CVE_CHECK_DATABASES_DIR` variable defined in
meta/recipes-core/meta/sbom-cve-check-config.inc.

The CVE git databases are fetched using the Bitbake fetcher. Currently,
Bitbake fetcher does not support a shallow clone that can be updated.
While `BB_GIT_SHALLOW` exists, it creates multiple tarballs in the
download directory, which is inefficient for updates. For now, the git
database is fully fetched.

The `SRCREV` of the git database is set to a fixed version. A user can
override this by specifying any other version, or `AUTOREV` can be
specified.

To simplify the activation and configuration of sbom-cve-check, a
configuration fragment is provided with recommended default values.

`sbom-cve-check` is configured to run without requiring network access.
If a user needs network access during execution (e.g., to download
annotation databases), they can set `SBOM_CVE_CHECK_ALLOW_NETWORK`
to "1".

The CVE analysis runs only if either the original SBOM changes or the
CVE databases are updated. In the two CVE database-fetching recipes, a
file in the sysroot is written, containing the Git revision of the
fetched CVE database.

`sbom-cve-check` is executed with the generated VEX manifest only if
enabled and if `SPDX_INCLUDE_VEX` is set to a value other than "all".
When `SPDX_INCLUDE_VEX=all`, the SPDX 3.0 file already contains all the
necessary information for CVE analysis, making the VEX manifest
redundant.

Signed-off-by: Benjamin Robin <benjamin.robin@bootlin.com>
---
meta/classes-recipe/sbom-cve-check.bbclass | 121 +++++++++++++++++++++
meta/conf/distro/include/maintainers.inc | 2 +
meta/conf/fragments/yocto/sbom-cve-check.conf | 7 ++
meta/recipes-core/meta/sbom-cve-check-config.inc | 4 +
.../meta/sbom-cve-check-update-cvelist-native.bb | 12 ++
.../recipes-core/meta/sbom-cve-check-update-db.inc | 28 +++++
.../meta/sbom-cve-check-update-nvd-native.bb | 12 ++
7 files changed, 186 insertions(+)

diff --git a/meta/classes-recipe/sbom-cve-check.bbclass b/meta/classes-recipe/sbom-cve-check.bbclass
new file mode 100644
index 000000000000..32f92a0bab29
--- /dev/null
+++ b/meta/classes-recipe/sbom-cve-check.bbclass
@@ -0,0 +1,121 @@
+# SPDX-License-Identifier: MIT
+
+# To enable this class, it is recommended to add this to local.conf
+# OE_FRAGMENTS += "core/yocto/sbom-cve-check"
+
+require recipes-core/meta/sbom-cve-check-config.inc
+
+SBOM_CVE_CHECK_DEPLOYDIR = "${WORKDIR}/sbom_cve_check/image-deploy"
+
+SBOM_CVE_CHECK_EXTRA_ARGS[doc] = "Allow to specify extra arguments to sbom-cve-check. \
+ For example to add export flags for filtering (e.g., only export vulnerable CVEs). \
+"
+SBOM_CVE_CHECK_EXTRA_ARGS ??= ""
+
+SBOM_CVE_CHECK_EXPORT_VARS[doc] = "List of variables that declare export files to generate. \
+ Each variable must have a 'type' and an 'ext' flag set. \
+ The 'type' flag contains the value that is passed to the --export-type command flags. \
+ The 'ext' flag contains the filename extension (suffix). The output filename is going \
+ to be ${IMAGE_NAME}${ext} \
+"
+SBOM_CVE_CHECK_EXPORT_VARS ?= "SBOM_CVE_CHECK_EXPORT_SPDX3 SBOM_CVE_CHECK_EXPORT_CVECHECK"
+
+SBOM_CVE_CHECK_EXPORT_SPDX3[doc] = "Export configuration to generate an SPDX3 SBOM file, \
+ with the following name: ${IMAGE_NAME}.cve-check.spdx.json \
+"
+SBOM_CVE_CHECK_EXPORT_SPDX3[type] ?= "spdx3"
+SBOM_CVE_CHECK_EXPORT_SPDX3[ext] ?= ".cve-check.spdx.json"
+
+SBOM_CVE_CHECK_EXPORT_CVECHECK[doc] = "Export configuration to generate a JSON manifest \
+ in the same format as the cve-check class, with the following name: \
+ ${IMAGE_NAME}.cve-check.json \
+"
+SBOM_CVE_CHECK_EXPORT_CVECHECK[type] ?= "yocto-cve-check-manifest"
+SBOM_CVE_CHECK_EXPORT_CVECHECK[ext] ?= ".cve-check.json"
+
+SBOM_CVE_CHECK_ALLOW_NETWORK[doc] = "Set to 1 to enable network usage."
+SBOM_CVE_CHECK_ALLOW_NETWORK ?= "0"
+
+python do_sbom_cve_check() {
+ """
+ Task: Run sbom-cve-check analysis on SBOM.
+ """
+ import os
+ import bb
+ from oe.cve_check import update_symlinks
+
+ if not bb.data.inherits_class("create-spdx-3.0", d):
+ bb.fatal("Cannot execute sbom-cve-check missing create-spdx-3.0 inherit.")
+
+ sbom_path = d.expand("${DEPLOY_DIR_IMAGE}/${IMAGE_LINK_NAME}.spdx.json")
+ vex_manifest_path = d.expand("${DEPLOY_DIR_IMAGE}/${IMAGE_LINK_NAME}.json")
+ dl_db_dir = d.getVar("SBOM_CVE_CHECK_DATABASES_DIR")
+ deploy_dir = d.getVar("SBOM_CVE_CHECK_DEPLOYDIR")
+ img_link_name = d.getVar("IMAGE_LINK_NAME")
+ img_name = d.getVar("IMAGE_NAME")
+
+ export_files = []
+ for export_var in d.getVar("SBOM_CVE_CHECK_EXPORT_VARS").split():
+ export_ext = d.getVarFlag(export_var, "ext")
+ export_path = f"{deploy_dir}/{img_name}{export_ext}"
+ export_link = f"{deploy_dir}/{img_link_name}{export_ext}"
+ export_type = d.getVarFlag(export_var, "type")
+ export_files.append((export_type, export_path, export_link))
+
+ cmd_env = os.environ.copy()
+ cmd_env["SBOM_CVE_CHECK_DATABASES_DIR"] = dl_db_dir
+
+ cmd_args = [
+ d.expand("${STAGING_BINDIR_NATIVE}/sbom-cve-check"),
+ "--sbom-path",
+ sbom_path,
+ ]
+
+ if not int(d.getVar("SBOM_CVE_CHECK_ALLOW_NETWORK")):
+ cmd_args.append("--disable-auto-updates")
+
+ # Assume that SPDX_INCLUDE_VEX is set globally to "all", and not only for the
+ # image recipe, which is very unlikely. This is not an issue to include the
+ # VEX manifest even if not needed.
+ if bb.data.inherits_class("vex", d) and d.getVar("SPDX_INCLUDE_VEX") != "all":
+ cmd_args.extend(["--yocto-vex-manifest", vex_manifest_path])
+
+ for export_file in export_files:
+ cmd_args.extend(
+ ["--export-type", export_file[0], "--export-path", export_file[1]]
+ )
+
+ cmd_args.extend(d.getVar("SBOM_CVE_CHECK_EXTRA_ARGS").split())
+
+ try:
+ bb.note("Running: {}".format(" ".join(cmd_args)))
+ bb.process.run(cmd_args, env=cmd_env)
+ except bb.process.ExecutionError as e:
+ bb.error(f"sbom-cve-check failed: {e}")
+ return
+
+ for export_file in export_files:
+ bb.note(f"sbom-cve-check exported: {export_file[1]}")
+ update_symlinks(export_file[1], export_file[2])
+}
+
+addtask do_sbom_cve_check after do_create_image_sbom_spdx before do_build
+
+SSTATETASKS += "do_sbom_cve_check"
+do_sbom_cve_check[cleandirs] = "${SBOM_CVE_CHECK_DEPLOYDIR}"
+do_sbom_cve_check[sstate-inputdirs] = "${SBOM_CVE_CHECK_DEPLOYDIR}"
+do_sbom_cve_check[sstate-outputdirs] = "${DEPLOY_DIR_IMAGE}"
+do_sbom_cve_check[depends] += " \
+ python3-sbom-cve-check-native:do_populate_sysroot \
+ ${@oe.utils.conditional('SBOM_CVE_CHECK_ALLOW_NETWORK','0',' \
+ sbom-cve-check-update-cvelist-native:do_populate_sysroot \
+ sbom-cve-check-update-nvd-native:do_populate_sysroot \
+ ','',d)} \
+"
+
+do_sbom_cve_check[network] = "${SBOM_CVE_CHECK_ALLOW_NETWORK}"
+
+python do_sbom_cve_check_setscene() {
+ sstate_setscene(d)
+}
+addtask do_sbom_cve_check_setscene
diff --git a/meta/conf/distro/include/maintainers.inc b/meta/conf/distro/include/maintainers.inc
index c7a646a643e6..c8f215188f83 100644
--- a/meta/conf/distro/include/maintainers.inc
+++ b/meta/conf/distro/include/maintainers.inc
@@ -789,6 +789,8 @@ RECIPE_MAINTAINER:pn-sassc = "Simone Weiß <simone.p.weiss@posteo.com>"
RECIPE_MAINTAINER:pn-sato-icon-theme = "Richard Purdie <richard.purdie@linuxfoundation.org>"
RECIPE_MAINTAINER:pn-sato-screenshot = "Ross Burton <ross.burton@arm.com>"
RECIPE_MAINTAINER:pn-sbc = "Unassigned <unassigned@yoctoproject.org>"
+RECIPE_MAINTAINER:pn-sbom-cve-check-update-cvelist-native = "Benjamin Robin <benjamin.robin@bootlin.com>"
+RECIPE_MAINTAINER:pn-sbom-cve-check-update-nvd-native = "Benjamin Robin <benjamin.robin@bootlin.com>"
RECIPE_MAINTAINER:pn-scdoc = "Alex Kiernan <alex.kiernan@gmail.com>"
RECIPE_MAINTAINER:pn-screen = "Unassigned <unassigned@yoctoproject.org>"
RECIPE_MAINTAINER:pn-seatd = "Unassigned <unassigned@yoctoproject.org>"
diff --git a/meta/conf/fragments/yocto/sbom-cve-check.conf b/meta/conf/fragments/yocto/sbom-cve-check.conf
new file mode 100644
index 000000000000..1cdb83117387
--- /dev/null
+++ b/meta/conf/fragments/yocto/sbom-cve-check.conf
@@ -0,0 +1,7 @@
+# This fragment enable sbom-cve-check with recommended options
+
+IMAGE_CLASSES:append = " sbom-cve-check"
+SRCREV:pn-sbom-cve-check-update-nvd-native = "${AUTOREV}"
+SRCREV:pn-sbom-cve-check-update-cvelist-native = "${AUTOREV}"
+SPDX_INCLUDE_VEX = "all"
+SPDX_INCLUDE_COMPILED_SOURCES:pn-linux-yocto = "1"
diff --git a/meta/recipes-core/meta/sbom-cve-check-config.inc b/meta/recipes-core/meta/sbom-cve-check-config.inc
new file mode 100644
index 000000000000..a1a909e22250
--- /dev/null
+++ b/meta/recipes-core/meta/sbom-cve-check-config.inc
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: MIT
+
+SBOM_CVE_CHECK_DATABASES_DIR ??= "${DEPLOY_DIR}/sbom_cve_check/databases"
+SBOM_CVE_CHECK_DATABASES_DIR[doc] = "Download directory path where to store the CVE databases"
diff --git a/meta/recipes-core/meta/sbom-cve-check-update-cvelist-native.bb b/meta/recipes-core/meta/sbom-cve-check-update-cvelist-native.bb
new file mode 100644
index 000000000000..ce204db6c51a
--- /dev/null
+++ b/meta/recipes-core/meta/sbom-cve-check-update-cvelist-native.bb
@@ -0,0 +1,12 @@
+SUMMARY = "Updates the CVE List database"
+LICENSE = "MIT"
+LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302"
+
+HOMEPAGE = "https://github.com/CVEProject/cvelistV5"
+SRC_URI = "git://github.com/CVEProject/cvelistV5.git;branch=main;protocol=https"
+SBOM_CVE_CHECK_DB_NAME = "cvelist"
+
+# 2026-03-19_baseline
+SRCREV = "ada54ee3cc8380820aa45e4996910bdc9dcb94e7"
+
+require sbom-cve-check-update-db.inc
diff --git a/meta/recipes-core/meta/sbom-cve-check-update-db.inc b/meta/recipes-core/meta/sbom-cve-check-update-db.inc
new file mode 100644
index 000000000000..5ecb79820247
--- /dev/null
+++ b/meta/recipes-core/meta/sbom-cve-check-update-db.inc
@@ -0,0 +1,28 @@
+# SPDX-License-Identifier: MIT
+
+INHIBIT_DEFAULT_DEPS = "1"
+EXCLUDE_FROM_WORLD = "1"
+
+inherit native
+require sbom-cve-check-config.inc
+
+SBOM_CVE_CHECK_DB_NAME[doc] = "Database name, which is the Git repository directory name. \
+ The git repository will be stored in ${SBOM_CVE_CHECK_DATABASES_DIR)/"
+
+DEPENDS += "rsync-native"
+
+# Leverage BitBake's checksum computation for populated sysroot files to determine
+# whether other recipe tasks dependent on this output need to be re-executed.
+do_compile() {
+ git -C "${S}" rev-parse --verify "HEAD^{object}" > "${WORKDIR}/${SBOM_CVE_CHECK_DB_NAME}.rev"
+}
+
+# In the install task, also deploy directly to ${DEPLOY_DIR} using rsync.
+# This is an hack, we are not using do_deploy to prevent multiple unecessary copy of the CVE database.
+do_install() {
+ install -m 644 -D -t "${D}${datadir}/sbom_cve_check/databases/" "${WORKDIR}/${SBOM_CVE_CHECK_DB_NAME}.rev"
+
+ dst="${SBOM_CVE_CHECK_DATABASES_DIR}/${SBOM_CVE_CHECK_DB_NAME}"
+ mkdir -p "$dst"
+ rsync -aH --delete --link-dest="${S}/" "${S}/" "${dst}/"
+}
diff --git a/meta/recipes-core/meta/sbom-cve-check-update-nvd-native.bb b/meta/recipes-core/meta/sbom-cve-check-update-nvd-native.bb
new file mode 100644
index 000000000000..46c86952a164
--- /dev/null
+++ b/meta/recipes-core/meta/sbom-cve-check-update-nvd-native.bb
@@ -0,0 +1,12 @@
+SUMMARY = "Updates the NVD CVE database"
+LICENSE = "cve-tou"
+LIC_FILES_CHKSUM = "file://LICENSES/cve-tou.md;md5=bc5bbf146f01e20ece63d83c8916d8fb"
+
+HOMEPAGE = "https://github.com/fkie-cad/nvd-json-data-feeds"
+SRC_URI = "git://github.com/fkie-cad/nvd-json-data-feeds.git;branch=main;protocol=https"
+SBOM_CVE_CHECK_DB_NAME = "nvd-fkie"
+
+# v2026.03.19-010002
+SRCREV = "49f8bbe1b0b0884e16bdc37ab68db997085570a7"
+
+require sbom-cve-check-update-db.inc

--
2.53.0