The CRA demands from manufacturers to address and remediate vulnerabilities (Annex I.II.2) depending on the risk they pose to an embedded Linux device. As manufacturers, we must triage the CVEs affecting the components of the device. It makes quite a difference, whether we must handle 1,500 or 500 CVEs. 75% or more of the 1,500 CVEs come from the Linux kernel.
The Yocto build can generate a CVE report for a Linux image. The CVE report is a list of all the packages installed on the device. Each package contains a list of CVEs affecting it. The problem is that the Linux kernel contains a lot of CVEs for kernel modules that are not even installed on the device.
By combining the CVE report with the kernel vulnerability database and information about the source files compiled into the kernel and its modules, we can reduce the number of unpatched kernel CVEs by 60-90% and the number of unpatched CVEs by 50-70%. The steps for these reductions differ between Yocto 5.0 and 6.0.
- For Yocto 6.0, I used NXP's demo image for the iMX8M Plus evaluation kit as my example. I could reduce the number of unpatched CVEs from 6927 to 2898 or by 58%.
- For Yocto 5.0, I used the Torizon Minimal image for the Verdin iMX8M Plus as my example. I could reduce the number of unpatched CVEs from 1253 to 433 or by 65%.
Taming the CVE Flood in Yocto 6.0
We follow the instruction on the GitHub repository meta-imx to build the demo image imx-image-core for the branch wrynose-6.18.20-2.0.0. We select the distro fsl-imx-wayland and the machine imx8mpevk (NXP's evaluation kit for the iMX8M Plus).
Generating the CVE Report
As described in section Vulnerability check at build time in Yocto's Security Manual, we add the following lines to a configuration file to generate a CVE report. The distro configuration is the preferred option, but local.conf will also do.
OE_FRAGMENTS += "core/yocto/sbom-cve-check"
include conf/distro/include/cve-extra-exclusions.incThe second line removes some old incomplete CVE entries, which are irrelevant for the generated Linux images.
The first line is the important one and enables the class sbom-cve-check. The functions of this class perform the following steps.
- The functions map each package name from the Linux image to a package name in the National Vulnerability Database (NVD).
- They retrieve the CVEs for each package from the NVD and attach them to the corresponding Yocto package.
- They write the image CVE report to the file
${DEPLOY_IMAGE_DIR}/imx-image-core-imx8mpevk.rootfs.sbom-cve-check.yocto.json.
Before we peek into the CVE report, we pretty-print it with the JSON command-line processor jq.
jq "." imx-image-core-imx8mpevk.rootfs.sbom-cve-check.yocto.json > sbom-with-cves.rootfs.sbom-cve-check.yocto.jsonThe format is fairly simple and looks like this - slightly abridged:
{
"package": [
{
"name": "zlib",
"layer": "core",
"version": "1.3.2",
"issue": [
{
"id": "CVE-2026-27171,
"status": "Patched",
"link": "https://nvd.nist.gov/vuln/detail/CVE-2026-27171",
"summary": "Short description of the vulnerability",
...
},
{
"id": "CVE-2026-76844",
"status": "Unpatched",
"link": "https://nvd.nist.gov/vuln/detail/CVE-2026-76844",
"summary": "Short description of the vulnerability",
...
},
...
]
},
...
]
}The status key can have one of three values:
Patched: A patch was applied to fix the CVE.Unpatched: No patch is available to fix the CVE.Ignore: Someone analysed the CVE and concluded that the CVE can be ignored. For example, the CVE only affects Windows but not Linux.
The build prints the unpatched CVEs for each package like this:
WARNING: imx-image-core-1.0-r0 do_sbom_cve_check: zlib-1.3.2: Found unpatched CVEs: CVE-2026-76844, CVE-2026-85091Counting the Unpatched CVEs in the Linux Image
With a slightly more advanced jq filter count_cves.jq, we can count the unpatched CVEs in the Linux image imx-image-core.
def filter_cves:
[
.package[]
| select(true)
| .issue[]
| select(.status == "Unpatched")
];
def count_cves:
filter_cves | length;
count_cvesThe jq command looks like this:
jq -f ./count_cves.jq ./sbom-with-cves.rootfs.sbom-cve-check.yocto.json
If we change true in the first select statement to .name == "linux-imx", the above command will return the number of unpatched CVEs in the Linux kernel linux-imx. The results are: 6927 unpatched CVEs in the Linux image and 6314 unpatched CVEs in the Linux kernel.
As NIST, the maintainer of the NVD, doesn't score the CVSS metrics for the CVEs since April 2026, filtering the CVEs further by critical severity, for example, would be meaningless. This explains the high numbers of CVEs. However, the ratio of unpatched CVEs in the kernel and in the whole image is in the same ballpark as before April 2026.
The 6314 kernel CVEs include CVEs from kernel modules that are not installed in the Linux image. As the kernel accounts for more than 90% of all CVEs, removing the CVEs of not-installed kernel modules would make vulnerability handling easier. Let us find out how much easier.
Removing the CVEs of Not-Installed Kernel Modules
To remove the CVEs of not-installed kernel modules, we need some information which kernel modules were built for the target device and which not (see also Linux kernel vulnerabilities). The kernel debug information is available, if the kernel config contains the setting CONFIG_DEBUG_INFO=y and some related settings. Let us check this.
$ cd tmp/work/imx8mpevk-poky-linux/linux-imx/6.18.20+git
$ grep "^CONFIG_DEBUG_INFO" build/.config
CONFIG_DEBUG_INFO=y
CONFIG_DEBUG_INFO_DWARF_TOOLCHAIN_DEFAULT=y
...Enabling CONFIG_DEBUG_INFO adds debug info to the kernel and its modules. The debug info includes the source files that were used to compile the kernel and its modules. The information is written to the file tmp/pkgdata/imx8mpevk/debugsources/linux-imx-debugsources.json.zstd. We have all the information from the kernel we need and don't have to add any further configurations.
Side Note: Three Solutions to Enable the Kernel Debug Info
- Patching the kernel configuration. For the
linux-imxkernel, the recipemeta-imx/meta-imx-v2x/recipes-kernel/linux/linux-imx_%.bbappendapplies the patchlinux-imx/0010-Add-FTRACE-support.patchto the kernel configarch/arm64/configs/imx_v8_defconfig. This solution feels a bit like a hack, but at least the BSP vendor takes care of the maintenance. - Adding a kernel fragment. For the
linux-toradexkernel below, we enable the kernel debug info by adding the kernel fragmentenable-kernel-debug-info.cfgthrough the recipe extensionlinux-toradex%.bbappend. Now, we are responsible for the maintenance of the extension. We must check with every Yocto release that this solution still works. - Adding a kernel feature. For the
linux-yoctokernel, we addKERNEL_EXTRA_FEATURES:append = " features/debug/debug-kernel.scc"to the distro configuration or tolocal.conf. The variouslinux-yocto*.bbrecipes addKERNEL_EXTRA_FEATUREStoKERNEL_FEATURES. The kernel build adds the settings fromdebug-kernel.sccto the kernel configuration. The Security Manual suggests this solution for kernels based onlinux-yocto.
SPDX_INCLUDE_COMPILED_SOURCES:pn-linux-imx = "1" in a configuration. I cannot confirm this. I succeeded in reducing the kernel CVEs without this setting.One information is still missing. Which sources files are affected by each kernel CVE? The answer is given by the kernel vulnerability database. We clone the git repository of the database into a directory of our choice.
$ git clone https://git.kernel.org/pub/scm/linux/security/vulns.gitThe CVEs in the directories cve/published/<year> list the source files and the kernel versions affected by the CVE.
Conceptually, the following algorithm keeps all kernel CVEs that affect kernel modules installed in the target device and removes all the others.
- For each
cveof the kernellinux-imxlisted inimx-image-core-imx8mpevk.rootfs.sbom-cve-check.yocto.json- Find
cvein the kernel vulnerability database. Ifcveaffects the current kernel version, retrieve the source filessource_filesfor thiscve. Otherwise, continue with the next CVE. - If one of the
source_filescan be found inlinux-imx-debugsources.json.zstd, keepcveinimx8mpevk.rootfs.sbom-cve-check.yocto.json. Otherwise, removecve.
- Find
This algorithm is implemented by the script scripts/contrib/improve_kernel_cve_report.py in the layer openembedded-core. The script leaves the input file imx-image-core-imx8mpevk.rootfs.sbom-cve-check.yocto.json unchanged and copies the result into the file cve-summary-enhance.json.
We must run this script outside the Yocto build environment. We could run the script in the same directory, into which we cloned the kernel vulnerability database. In my working environment, I call the script like this:
$ python3 work/yocto-6.0/layers/openembedded-core/scripts/contrib/improve_kernel_cve_report.py \
--debug-sources work/yocto-6.0/build-imx8mpevk/tmp/pkgdata/imx8mpevk/debugsources/linux-imx-debugsources.json.zstd \
--datadir vulns \
--old-cve-report work/yocto-6.0/build-imx8mpevk/tmp/deploy/images/imx8mpevk/imx-image-core-imx8mpevk.rootfs.sbom-cve-check.yocto.jsonWe re-run the jq filters from above to count the CVEs. Here is the result.
Taming the CVE Flood in Yocto 5.0
Removing the CVEs of not-installed kernel modules is rougher in Yocto 5.0 than in Yocto 6.0. The script improve_kernel_cve_report.py only became available in Yocto 5.3. Fortunately, we can use the script from Yocto 6.0 in 5.0. The chapter Checking for Vulnerabilities from the Yocto 5.3 documentation gives us some guidance. As Yocto 5.3 is closer to Yocto 6.0 than to Yocto 5.0, we must adapt some steps.
This time, we build the image torizon-minimal for the Toradex SoM verdin-imx8mp. The image is part of the torizon distro. As the name suggests, the image has been minimised for production use and should, therefore, have a lot fewer CVEs. The image torizon-minimal is Torizon OS without containers. It fully supports secure OTA updates with Torizon Cloud for fleet management. See my articles Installing Linux Images on Toradex Verdin Boards and Setting Up Yocto Projects with Kas.
Generating the CVE Report (5.0)
We add the following two lines to a configuration to generate the CVE report.
INHERIT += "cve-check"
include conf/distro/include/cve-extra-exclusions.inc
For each image, the build writes a CVE report to ${DEPLOY_DIR_IMAGE}. In our example, it writes two CVE reports: torizon-minimal-verdin-imx8mp.json and initramfs-ostree-torizon-image-verdin-imx8mp.json. Their format is very similar to the format of the CVE reports *.rootfs.sbom-cve-check.yocto.json for Yocto 6.0 described above.
The documentation suggests to use tmp/log/cve/cve-summary.json instead of the image CVE reports. This is not quite right, because cve-summary.json contains CVEs for native packages as well. We are only interested in CVEs for packages that are installed on the device. Hence, we will only look at the image CVE reports for vulnerability. For this article, we'll content ourselves with torizon-minimal-verdin-imx8mp.json.
Note that initramfs-ostree-torizon-image-verdin-imx8mp.json contains a lot of duplicate CVEs, for example, all the CVEs for the kernel. When address and remediate the CVEs for CRA compliance, we should eliminate the duplicates first to avoid double work.
The number of unpatched CVEs in the Linux image torizon-minimal is 1253. The number of unpatched CVEs in the kernel linux-toradex is 916. This is roughly one sixth of the kernel CVEs of NXP's demo image imx-image-core for the imx8mpevk.
Removing the CVEs of Not-Installed Kernel Modules (5.0)
KERNEL_EXTRA_FEATURES contains features/debug/debug-kernel.scc. Nevertheless, the debug info is disabled in the kernel, as our check shows:
$ grep "^CONFIG_DEBUG_INFO" build/.config
CONFIG_DEBUG_INFO_NONE=yThe value of KERNEL_EXTRA_FEATURES should be added to KERNEL_FEATURES, but it isn't. Neither the first nor the third solution to enable the kernel debug info from the side note above works. We resort to the third solution of adding a kernel fragment.
In our own layer, say, meta-b4-torizon, we introduce the file recipes-kernel/linux/linux-toradex%.bbappend with the following contents:
FILESEXTRAPATHS:prepend := "${THISDIR}/files:"
SRC_URI += "\
file://enable-kernel-debug-info.cfg \
"
The configuration fragment recipes-kernel/linux/files/enable-kernel-debug-info.cfg looks like this.
CONFIG_DEBUG_INFO=y
# CONFIG_DEBUG_INFO_NONE is not set
CONFIG_DEBUG_INFO_DWARF_TOOLCHAIN_DEFAULT=yThis workaround enables the debug info, as our check will readily reveal. The build stores the debug info for the kernel in the file tmp/pkgdata/verdin-imx8mp/extended/linux-toradex-src.json.zstd. As with Yocto 6.0, we don't have to enable SPDX_INCLUDE_COMPILED_SOURCES. The file linux-toradex-src.json.zstd has all the information about the compiled kernel sources we need.
Now we have all the information needed to run the script improve_kernel_cve_report.py. We can use the same script as for Yocto 6.0, as it is a standalone script. Again, we run it outside the Yocto build environment.
python3 work/yocto-6.0/layers/openembedded-core/scripts/contrib/improve_kernel_cve_report.py \
--debug-sources work/yocto-5.0/build-verdin-imx8mp/tmp/pkgdata/verdin-imx8mp/extended/linux-toradex-src.json.zstd \
--datadir vulns \
--old-cve-report work/yocto-5.0/build-verdin-imx8mp/tmp/log/cve/cve-summary.jsonThe script writes the result to the file cve-summary-enhance.json. The results are pretty impressive.
Does the Size of the Kernel Increase?
# With debug info
From $DEPLOY_DIR_IMAGE: Image.gz = 11883575, Image = 31162880
# Without debug info
From $DEPLOY_DIR_IMAGE: Image.gz -> 11883370, Image = 31162880No, it doesn't. The size of the uncompressed kernel - ${DEPLOY_DIR_IMAGE}/Image.gz - is exactly the same both for the kernel with debug info enabled and for the kernel with debug info disabled. The kernel has a size of 31,162,880 bytes in both cases. This is hardly surprising, because the linker removes all the debug info before it compresses the kernel image.
This finding is important, because we don't have to build the kernel twice - once with debug info and once without. We build the kernel once with debug info and can use the same kernel in production.
Next Steps to Reduce the Number of CVEs Further
For bloated images like the NXP demo image and many BSP images, the answer is clear. We remove all packages that are not used by the device. This brings us closer to Torizon Minimal, which can use a good minimisation as well. We will feel the positive effects right away: faster builds, less debugging, less flash memory needed, faster CRA compliance and faster license compliance.
The normal next step - especially for tool vendors and process consultants - would be to apply some clever filters over CVSS, EPSS, KEVs or PoC exploits. I have elaborated in a recent article, why clever filtering won't do. These filters are arbitrary and, hence, hard to justify towards the market surveillance authorities. In the same article, I describe a defence-in-depth approach guided by CVSS metrics to make the exploitation of hundreds of CVEs more difficult in one go. The idea is that every security measure applied must improve one or more CVSS exploitability measures. We don't deal with one CVE after the other but we ensure that our security measures are effective.