Pour faciliter l’analyse des rapports concernant les CVE produits lors d’un build Yocto Project, j’ai développé un petit outil nommé cve-parse. Il permet d’obtenir rapidement une vue d’ensemble des composants d’une image, de consulter les vulnérabilités associées à un composant particulier ou de rechercher directement une CVE.
Dans le précédent article, consacré au Cyber Resilience Act, nous avons vu qu’en cas de découverte d’une vulnérabilité activement exploitée, l’une des premières questions auxquelles le fabricant doit pouvoir répondre est très simple en apparence : mon produit contient-il le composant vulnérable ?
Dans un système Linux embarqué, la réponse est rarement simple à obtenir. Une image contient plusieurs dizaines ou centaines de composants logiciels, auxquels peuvent être associées des centaines, voire des milliers d’entrées CVE.
Le projet Yocto fournit heureusement les outils nécessaires pour produire une SBOM (Software Bill of Materials, « inventaire logiciel » en français) et analyser les vulnérabilités connues associées aux composants d’une image. Les informations produites sont très complètes. Parfois même un peu trop si l’on veut simplement obtenir un premier aperçu de la situation.
C’est pour faciliter cette exploration que j’ai développé (avec pas mal d’aide de Codex), un petit outil nommé cve-parse. Son code est disponible ici : https://github.com/cpb-/yocto-cve-parser. Il est placé sous licence MIT.
Dans cet article, nous allons construire une image Yocto de test, générer son rapport de vulnérabilités puis voir comment cve-parse permet de l’explorer.
Je suis parfaitement conscient qu’il existe probablement d’autres outils permettant d’analyser les CVE et les rapports de Yocto Project, j’avais simplement envie d’un outil adapté à mon besoin (le suivi du socle logiciel offert par Eris Linux) et c’était l’occasion de m’amuser avec Codex pour le développement et la documentation d’un petit outil Open Source.
Produire un rapport CVE avec Yocto
Pour cet exemple, je vais utiliser Yocto Cooker, afin d’accélérer la mise en place du build. Commençons par créer un environnement Python dédié, et installer Cooker :
$ sudo apt install python3.12-venv
[...]
$ python3 -m venv cooker-env
$ . cooker-env/bin/activate
$ python3 -m pip install --upgrade yocto-cooker
[...]
Récupérons ensuite les menus d’exemple fournis dans le dépôt de Yocto Cooker :
$ git clone https://github.com/cpb-/yocto-cooker
$ cp yocto-cooker/sample-menus/qemu-sample-menu-wrynose.json .
Nous allons utiliser une cible QEMU Arm, pour produire rapidement une image, mais ce qui nous intéresse ce sont les rapports de vulnérabilités. Je n’ai même pas l’intention de faire démarrer l’image produite.
Les lignes à ajouter dans le fichier conf/local.conf pour produire le rapport de vulnérabilité ont changé depuis la branche Wrynose. 🙄
Avec le menu utilisant la branche Yocto Wrynose, nous ajoutons au fichier local.conf la ligne OE_FRAGMENTS suivante :
"local.conf": [
"MACHINE = 'qemuarm' ",
"DISTRO = 'poky' ",
"IMAGE_FEATURES += 'empty-root-password' ",
"OE_FRAGMENTS += 'core/yocto/sbom-cve-check' "
]
Alors qu’avec une branche antérieure comme Scarthgap, il fallait ajouter la ligne INHERIT :
"local.conf": [
"MACHINE = 'qemuarm' ",
"DISTRO = 'poky' ",
"IMAGE_FEATURES += 'empty-root-password' ",
"INHERIT += 'cve-check' "
]
Construction de l’image
Nous pouvons construire l’image avec la commande suivante, qui va prendre un peu de temps et qui produira par la même occasion le rapport de vulnérabilités :
$ cooker cook qemu-sample-menu-wrynose.json
[...]
Initialising tasks: 100% |########################################| Time: 0:00:04
NOTE: Executing Tasks
[...]
Une fois la compilation terminée, plusieurs nouveaux éléments apparaissent dans le répertoire tmp/ :
$ ls builds/build-qemuarm/tmp/deploy/
images licenses rpm sbom-cve-check spdx
Notamment, dans le répertoire images/ :
$ ls builds/build-qemuarm/tmp/deploy/images/qemuarm
[...]
core-image-base-qemuarm.rootfs-20261017082851.sbom-cve-check.spdx.json
core-image-base-qemuarm.rootfs-20261017082851.sbom-cve-check.yocto.json
core-image-base-qemuarm.rootfs-20261017082851.spdx.json
[...]
core-image-base-qemuarm.rootfs.sbom-cve-check.spdx.json
core-image-base-qemuarm.rootfs.sbom-cve-check.yocto.json
core-image-base-qemuarm.rootfs.spdx.json
Le fichier qui nous intéresse principalement est core-image-base-qemuarm.rootfs.sbom-cve-check.yocto.json. Il contient le rapport produit par Yocto. En jetant un coup d’œil à son contenu nous voyons qu’il est particulièrement détaillé, et sera difficile à consulter tel quel :
{
"version": "1",
"package": [
{
"name": "acl",
"layer": "core",
"version": "2.3.2",
"products": [
{
"product": "acl",
"cvesInRecord": "Yes"
}
],
"issue": [
{
"id": "CVE-2009-4411",
"status": "Patched",
"link": "https://nvd.nist.gov/vuln/detail/CVE-2009-4411",
"summary": "...",
"scorev2": "3.7",
"scorev3": "0.0",
"scorev4": "0.0",
"modified": "2026-06-16T23:13:36.000",
"vector": "LOCAL",
"vectorString": "AV:L/AC:H/Au:N/C:P/I:P/A:P",
"detail": "version-not-in-range"
},
[...]
Le format est adapté à un traitement automatisé et contient beaucoup d’informations intéressantes : composant, numéro de version, numéro de CVE, présence d’un patch, score CVSS, vecteur d’attaque, description, etc. En revanche, pour un humain il est difficile de savoir quelles vulnérabilités non corrigées sont présentes dans l’image.
On peut évidemment écrire des requêtes jq, mais j’avais envie d’un petit outil spécialisé permettant de naviguer rapidement dans ces informations.
Pour les branches de Yocto antérieures (jusqu’à Scarthgap), le fichier était :
$ ls builds/build-qemuarm-scarthgap/tmp/deploy/images/qemuarm/
core-image-base-qemuarm.rootfs-20261017161823.cve
[...]
core-image-base-qemuarm.rootfs.cve
[...]
$
C’est le lien symbolique core-image-base-qemuarm.rootfs.cve qui nous intéresse. Il contient les vulnérabilités concernant les packages embarqués sur la cible.
À l’inverse, dans le build Wrynose utilisé ici, j’ai constaté que le rapport contient également des composants qui ne sont pas présents dans le système de fichiers final. À titre d’exemple, core-image-base-qemuarm.rootfs.sbom-cve-check.yocto.jsonbash apparaît dans le rapport alors qu’il n’est pas installé sur notre cible.
$ jq -r ".package[].name" core-image-base-qemuarm.rootfs.sbom-cve-check.yocto.json | grep 'bash'
bash
bash-completion
Il faudra donc filtrer les packages pour ne conserver que ceux qui sont dans le manifeste de la cible. Nous utiliserons le second fichier fourni par Yocto (depuis Wrynose) : core-image-base-qemuarm.rootfs.spdx.json et une option spécifique de cve-parse.
Installation de cve-parse
Récupérons le projet :
$ git clone https://github.com/cpb-/yocto-cve-parser
Copions dans notre répertoire de travail les deux fichiers produits par la branche Wrynose :
$ cp builds/build-qemuarm/tmp/deploy/images/qemuarm/core-image-base-qemuarm.rootfs.sbom-cve-check.yocto.json .
$ cp builds/build-qemuarm/tmp/deploy/images/qemuarm/core-image-base-qemuarm.rootfs.spdx.json .
Nous pouvons maintenant commencer l’analyse.
Aperçu de la situation
Sans option supplémentaire et avec uniquement le fichier de CVE, cve-parse affiche un résumé par composant mentionné dans le fichier :
$ ./cve-parse core-image-base-qemuarm.rootfs.sbom-cve-check.yocto.json
COMPONENT VERSION TOTAL PATCHED UNPATCHED IGNORED OTHER
acl 2.3.2 4 2 2 0 0
alsa-lib 1.2.15.3 6 2 4 0 0
alsa-state 0.2.0 0 0 0 0 0
alsa-ucm-conf 1.2.15.3 0 0 0 0 0
alsa-utils 1.2.15.2 0 0 0 0 0
attr 2.5.2 1 0 1 0 0
avahi 0.8 26 24 1 1 0
avahi-libnss-mdns 0.15.1 0 0 0 0 0
base-files 3.0.14 1 0 0 1 0
base-passwd 3.6.8 0 0 0 0 0
bash 5.3 18 18 0 0 0
bash-completion 2.17.0 0 0 0 0 0
bluez5 5.86 43 39 1 3 0
[...]
zlib 1.3.2 16 14 2 0 0
zstd 1.5.7 4 4 0 0 0
TOTAL 22445 18522 1073 2850 0
Components: 93; unique CVEs: 22328
Le total fait un peu peur, mais en filtrant avec l’option -f et la liste des packages réellement embarqués sur la cible, les nombres diminuent :
$ ./cve-parse core-image-base-qemuarm.rootfs.sbom-cve-check.yocto.json -f core-image-base-qemuarm.rootfs.spdx.json
COMPONENT VERSION TOTAL PATCHED UNPATCHED IGNORED OTHER
acl 2.3.2 4 2 2 0 0
alsa-lib 1.2.15.3 6 2 4 0 0
alsa-state 0.2.0 0 0 0 0 0
alsa-ucm-conf 1.2.15.3 0 0 0 0 0
alsa-utils 1.2.15.2 0 0 0 0 0
attr 2.5.2 1 0 1 0 0
avahi 0.8 26 24 1 1 0
avahi-libnss-mdns 0.15.1 0 0 0 0 0
base-files 3.0.14 1 0 0 1 0
base-passwd 3.6.8 0 0 0 0 0
bluez5 5.86 43 39 1 3 0
busybox 1.37.0 51 47 4 0 0
dbus 1.16.2 26 26 0 0 0
dosfstools 4.2 2 2 0 0 0
eudev 3.2.14 0 0 0 0 0
expat 2.7.5 70 59 11 0 0
glib-2.0 2.88.2 35 35 0 0 0
glibc 2.43+git 179 159 16 4 0
init-ifupdown 1.0 0 0 0 0 0
init-system-helpers 1.69 0 0 0 0 0
initscripts 1.0 2 2 0 0 0
kmod 34.2 0 0 0 0 0
libcap 2.77 5 5 0 0 0
libdaemon 0.14 0 0 0 0 0
libffi 3.5.2 1 1 0 0 0
libgcc 15.3.0 16 16 0 0 0
libpcre2 10.47 19 11 8 0 0
libtirpc 1.3.7 5 5 0 0 0
libx11 1.8.13 22 18 4 0 0
libxau 1.0.12 0 0 0 0 0
libxcb 1.17.0 1 1 0 0 0
libxcrypt 4.5.2 0 0 0 0 0
libxdmcp 1.1.5 1 1 0 0 0
libxml2 2.15.2 120 111 9 0 0
linux-yocto 6.18.39+git 20851 17061 952 2838 0
modutils-initscripts 1.0 0 0 0 0 0
ncurses 6.6 31 31 0 0 0
netbase 6.5 0 0 0 0 0
openssl 3.5.7 322 299 23 0 0
opkg-utils 0.7.0 0 0 0 0 0
packagegroup-base 1.0 0 0 0 0 0
packagegroup-core-boot 1.0 0 0 0 0 0
psplash 0.1+git 0 0 0 0 0
readline 8.3 1 1 0 0 0
rpcbind 1.2.8 4 4 0 0 0
shadow 4.19.4 13 12 0 1 0
shadow-securetty 4.6 0 0 0 0 0
shared-mime-info 2.4 0 0 0 0 0
sysvinit 3.14 0 0 0 0 0
sysvinit-inittab 2.88dsf 0 0 0 0 0
tar 1.35 20 17 2 1 0
ttyrun 2.41.0 1 1 0 0 0
update-rc.d 0.8+git 0 0 0 0 0
util-linux 2.41.5 35 28 7 0 0
xz 5.8.2 9 8 0 1 0
zlib 1.3.2 16 14 2 0 0
TOTAL 21939 18042 1047 2850 0
Components: 56; unique CVEs: 21936
Pour chaque composant logiciel, nous obtenons :
- son numéro de version,
- le nombre total d’enregistrements CVE,
- le nombre de CVE auxquelles Yocto attribut l’état « corrigée » (Patched),
- celles encore non corrigées (Unpatched),
- celles explicitement ignorées (Ignored),
- les éventuels autres états (je n’en ai pas rencontré encore).
Le nombre concernant le noyau Linux est particulièrement spectaculaire : plus de 20 000 entrées ! Il ne faut évidemment pas interpréter cette colonne comme signifiant que notre système embarqué contient 20 000 vulnérabilités exploitables. Le nombre de CVE attribuées au noyau Linux a considérablement augmenté ces dernières années et le travail important consiste justement à déterminer quelles vulnérabilités concernent réellement la configuration, les sources compilées et l’utilisation du noyau dans notre produit.
Examiner un composant
Intéressons-nous par exemple au composant acl, pour lequel le résumé indiquait quatre CVE, dont deux non corrigées. L’option -c ou --component permet d’obtenir la liste correspondante :
$ ./yocto-cve-parser/cve-parse core-image-base-qemuarm.rootfs.sbom-cve-check.yocto.json -f core-image-base-qemuarm.rootfs.spdx.json -c acl
COMPONENT VERSION CVE STATUS CVSS SCORE SEVERITY
acl 2.3.2 CVE-2018-25320 Patched 4.0 9.3 Critical
acl 2.3.2 CVE-2026-54369 Unpatched 4.0 8.4 High
acl 2.3.2 CVE-2026-54370 Unpatched 4.0 7.2 High
acl 2.3.2 CVE-2009-4411 Patched 2.0 3.7 Low
On voit immédiatement que les deux entrées qui méritent notre attention sont :
CVE-2026-54369CVE-2026-54370
toutes deux classées en criticité élevée et signalées comme Unpatched. Le tableau indique également la version CVSS utilisée et le score correspondant.
Obtenir tous les détails
L’option -d ou --details permet d’afficher les informations complètes disponibles dans le rapport Yocto :
$ ./cve-parse core-image-base-qemuarm.rootfs.sbom-cve-check.yocto.json -f ~/core-image-base-qemuarm.rootfs.spdx.json -c acl -d
component: acl
version: 2.3.2
layer: core
cve: CVE-2018-25320
status: Patched
cvss: 4.0
score: 9.3
severity: Critical
issue.id: CVE-2018-25320
issue.status: Patched
issue.link: https://nvd.nist.gov/vuln/detail/CVE-2018-25320
issue.summary: ACL Analytics versions 11.x through 13.0.0.579 contain an arbitrary code execution vulnerability that allows attackers to execute arbitrary commands by leveraging the EXECUTE function. Attackers can use bitsadmin to download malicious PowerShell scripts and execute them with system privileges to establish reverse shells and gain complete system control.
issue.scorev2: 0.0
issue.scorev3: 9.8
issue.scorev4: 9.3
issue.modified: 2026-10-01T15:19:15.000
issue.vector: NETWORK
issue.vectorString: CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X
issue.detail: version-not-in-range: Only affects 11.0 onwards
component: acl
version: 2.3.2
layer: core
cve: CVE-2026-54369
status: Unpatched
cvss: 4.0
score: 8.4
severity: High
issue.id: CVE-2026-54369
issue.status: Unpatched
issue.link: https://nvd.nist.gov/vuln/detail/CVE-2026-54369
issue.summary: acl before version 2.4.0 contains a symlink traversal vulnerability in the libacl pathname-based functions acl_get_file(), acl_set_file(), acl_extended_file(), and acl_delete_def_file() that allows local attackers to escalate privileges by replacing any pathname component with a symbolic link. Attackers who control any component of a pathname processed by a privileged caller can redirect ACL read or write operations to arbitrary files or directories, enabling unauthorized manipulation of access control lists and local privilege escalation.
issue.scorev2: 0.0
issue.scorev3: 7.1
issue.scorev4: 8.4
issue.modified: 2026-09-14T13:18:40.000
issue.vector: LOCAL
issue.vectorString: CVSS:4.0/AV:L/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X
issue.detail: version-in-range
issue.description: Needs backporting (fixed from 2.4.0)
component: acl
version: 2.3.2
layer: core
cve: CVE-2026-54370
status: Unpatched
cvss: 4.0
score: 7.2
severity: High
issue.id: CVE-2026-54370
issue.status: Unpatched
issue.link: https://nvd.nist.gov/vuln/detail/CVE-2026-54370
issue.summary: acl before version 2.4.0 contains a time-of-check to time-of-use (TOCTOU) race condition vulnerability that allows local attackers to escalate privileges by replacing a pathname component with a symbolic link between an lstat() check and subsequent symlink-following operations such as stat(), chown(), chmod(), acl_get_file(), and acl_set_file(). Attackers who control a pathname component can redirect file access control list operations to arbitrary files when getfacl, setfacl, or chacl is invoked by a privileged process over an attacker-controlled path, resulting in local privilege escalation.
issue.scorev2: 0.0
issue.scorev3: 6.3
issue.scorev4: 7.2
issue.modified: 2026-07-14T21:33:49.000
issue.vector: LOCAL
issue.vectorString: CVSS:4.0/AV:L/AC:H/AT:N/PR:L/UI:N/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X
issue.detail: version-in-range
issue.description: Needs backporting (fixed from 2.4.0)
[...]
Prenons par exemple la vulnérabilité CVE-2026-54369, on voit que :
- la vulnérabilité est considérée comme applicable à la version utilisée (
issue.detail: version-in-range) - son vecteur d’attaque est local (
issue.vector: LOCAL), - elle peut conduire à un gain de privilège (
issue.summary: [...] to escalate privileges [...]), - une correction existe à partir de la version 2.4.0 mais nécessite un backport.
Ce sont ces données que nous pourrons ensuite utiliser pour déterminer si notre produit est vulnérable ou non en étudiant le contexte d’utilisation de ce composant logiciel. Et faire éventuellement un signalement conforme au Cyber Resilience Act, comme je l’avais développé dans l’article précédent.
Rechercher directement une CVE
Le chemin inverse est également fréquent. Supposons que nous recevions une alerte nous indiquant que CVE-2026-54370 mérite une attention particulière. La question devient « cette vulnérabilité est-elle présente dans mon image, et quel composant concerne-t-elle ? »
L’option cve de cve-parse permet cette recherche directement :
$ ./cve-parse core-image-base-qemuarm.rootfs.sbom-cve-check.yocto.json -f core-image-base-qemuarm.rootfs.spdx.json cve CVE-2026-54370
component: acl
version: 2.3.2
layer: core
cve: CVE-2026-54370
status: Unpatched
cvss: 4.0
score: 7.2
severity: High
issue.id: CVE-2026-54370
issue.status: Unpatched
issue.link: https://nvd.nist.gov/vuln/detail/CVE-2026-54370
issue.summary: acl before version 2.4.0 contains a time-of-check to time-of-use (TOCTOU) race condition vulnerability that allows local attackers to escalate privileges by replacing a pathname component with a symbolic link between an lstat() check and subsequent symlink-following operations such as stat(), chown(), chmod(), acl_get_file(), and acl_set_file(). Attackers who control a pathname component can redirect file access control list operations to arbitrary files when getfacl, setfacl, or chacl is invoked by a privileged process over an attacker-controlled path, resulting in local privilege escalation.
issue.scorev2: 0.0
issue.scorev3: 6.3
issue.scorev4: 7.2
issue.modified: 2026-07-14T21:33:49.000
issue.vector: LOCAL
issue.vectorString: CVSS:4.0/AV:L/AC:H/AT:N/PR:L/UI:N/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X
issue.detail: version-in-range
issue.description: Needs backporting (fixed from 2.4.0)
Ce mode me semble particulièrement intéressant dans le cadre du suivi de sécurité d’un produit. Lorsqu’une nouvelle vulnérabilité est publiée ou qu’une CVE fait l’objet d’une alerte, on peut très rapidement vérifier si elle apparaît dans le rapport de l’image produite.
Les CVE du Kernel
Le cas du noyau Linux mérite un traitement particulier. Plus de 20 000 entrées CVE apparaissent ici ! Ceci ne signifie heureusement pas que notre noyau est affecté par 20 000 vulnérabilités exploitables. La configuration du noyau, les fichiers réellement compilés et les correctifs backportés doivent être pris en compte.
Comment réduire cette liste à quelque chose de réellement exploitable ? Cela fera l’objet d’un prochain article…
Limitations
Il est important de préciser ce que fait et ce que ne fait pas cve-parse. L’outil ne dispose pas de sa propre base de vulnérabilités et ne remplace aucun des mécanismes fournis par Yocto. Il ne permet pas non plus de savoir de manière automatique si une vulnérabilité est exploitable ou non sur un équipement. Il se contente d’un rôle plus simple : présenter de manière pratique les informations déjà produites par Yocto Project pour filtrer celles qui nécessiteront des investigations plus poussées.
Le dépôt est disponible ici : https://github.com/cpb-/yocto-cve-parser
N’hésitez pas à le tester sur vos propres rapports Yocto et à me signaler les cas particuliers, formats ou options qui pourraient être utiles.
Conclusion
Produire une SBOM et un rapport de vulnérabilités est une première étape indispensable pour maîtriser la sécurité d’un système Linux embarqué. Mais collecter les informations ne suffit pas : il faut également être capable de les exploiter rapidement lorsqu’une nouvelle vulnérabilité apparaît. Avec cve-parse, on peut passer en quelques secondes d’un aperçu général des vulnérabilités dans une image, à la liste des CVE associées à un composant, puis au détail d’une vulnérabilité précise.
L’outil évoluera probablement en fonction de mes besoins et des retours d’utilisation. Son objectif restera néanmoins le même : rendre les rapports de sécurité produits par Yocto Project plus faciles à lire et à exploiter.
