U-Boot : à la découverte du « démarrage vérifié »

Magazine
Marque
GNU/Linux Magazine
Numéro
221
|
Mois de parution
décembre 2018
|
Domaines


Résumé
Sécuriser le processus de démarrage est la première étape afin de garantir qu’un système Linux embarqué est fiable. Cette technique, appelée Secure Boot, permet de s’assurer que seuls les logiciels authentifiés de manière cryptographique (bootloader, image noyau, etc.) pourront s’exécuter sur la cible, ceci afin de certifier par exemple qu’une mise à jour logicielle est sûre, qu’aucune faille de sécurité ne subsiste ni même qu’il existe une quelconque altération provenant d’une attaque externe malveillante.

Body

Nous proposons ici de ne présenter qu’une partie de cette séquence de démarrage sécurisé. Pour ce faire, nous parlerons d’une des nombreuses fonctionnalités que propose le projet U-Boot, « Verified Boot », procédé permettant de garantir l’intégrité et la provenance des images chargées par ce dernier (Noyau Linux). On parlera ici de mécanisme de signature numérique (fonction de hachage et clé asymétrique).

1. Présentation de la plateforme : SAMA5D27-SOM-EK1

Même si l’étude qui suit est entièrement valide sur la carte de développement utilisée habituellement par l’auteur, la fameuse WaRP7, qui rappelons-le embarque le System On Chip NXP i.MX7, nous utiliserons ici le kit d’évaluation Microchip (anciennement Atmel) de référence SAMA5D27-SOM-EK1 (voir figure 1).

sama5d2_sbc

Fig. 1 : SAMA5D27-SOM-EK1 devant l’objectif.

Son nombre important de périphériques (Ethernet, USB, Bus CAN, extension MikroBUS, GPIO, SD/uSD, EEPROM...), ainsi que la présence du System On Module SAMA5D27 embarquant le System On Chip de la famille SAMA5D2 en fait une plateforme très intéressante pour le prototypage rapide et qui plus est industriel (vs Raspberry-Pi/Odroid/...). De plus, cette famille de microprocesseurs intègre des fonctions avancées de sécurité qui nous seront, pour quelques-unes, utiles durant la suite de l’article (Secure Boot, True Random Number Generator, ARM Trust Zone, Tamper Detection (pour se prémunir des attaques physiques au système), chiffrement/déchiffrement à la volée des données en DDR, ...)). Pour les plus curieux, la figure 2 montre le diagramme représentant l’ensemble des fonctionnalités du microprocesseur.

sama5d2

Fig. 2 : Synoptique SAMA5D2.

Enfin, de par sa communauté et sa vocation à être utilisé dans un milieu plutôt industriel, cette plateforme possède un excellent support sur les deux « Build Systems » les plus connus dans le milieu de l’embarqué, à savoir Yocto/Openembedded et Buildroot, deux frameworks ayant déjà fait l’objet de divers articles dans les colonnes de GNU/Linux Magazine et Open Silicium. Pour plus d’informations sur ces systèmes de constructions de distribution/firmware, l’auteur ne peut que renvoyer le lecteur vers le très bon article de Pierre Ficheux en [1].

Concernant l’intégration avec le projet Yocto, une couche BSP (Board Support Package) spécifique (meta-atmel) est mise à disposition sur le dépôt GitHub dédié à la communauté Atmel/Microchip, comme le montre la figure 3.

yocto_atmel

Fig. 3 : Couche BSP « meta-atmel ».

Sur la figure 4, même combat pour le plus français des Build Systems, Buildroot (oui, rappelons ici que Thomas Petazzoni, CTO chez Bootlin est co-mainteneur du projet ! Sans oublier Romain Naour et Yann E. Morin contributeurs majeurs au projet).

buildroot_atmel

Fig. 4 : Configuration de la plateforme au sein de Buildroot.

2. Parlons un peu sécurité

Sécuriser un système embarqué est dans beaucoup de projets industriels une étape primordiale dans le processus de développement. Protéger les logiciels est quelque chose de tout aussi important que la bonne gestion des mises à jour (Over The Air ou non), et c’est d’ailleurs, dans bien des cas, deux mondes qui se rejoignent. Cette partie sera ainsi l’occasion de rappeler quelques concepts liés à notre étude.

2.1 Quels composants protéger ?

En fait, l’ensemble des éléments est susceptible d’être mis à jour par une intervention externe (via Mender par exemple). Ainsi, on retrouvera les composants d’un système Linux embarqué classique, à savoir :

  • le chargeur d’amorçage. Pour notre étude, nous parlerons ici du plus utilisé dans le monde de l’embarqué, U-Boot ;

Pour la petite histoire, attention à bien l’orthographier, sous peine d’irriter la communauté (voir figure 5).

U-Boot

Fig. 5 : U-Boot.

  • on retrouvera l’image noyau ;
  • le système de fichiers racine, ainsi que les différentes applications utilisateurs ;
  • les différentes données liées à l’utilisateur (logs, fichiers de configuration, base de données…) ;
  • enfin, dans certains cas, un applicatif tiers (firmware Cortex M4 pour une utilisation avec un microprocesseur i.MX7, firmware FPGA sur plateforme Xilinx Zynq, et bien d’autres encore).

2.2 Chaîne de confiance ?

Comme présenté en introduction, lors d’un démarrage sécurisé, chaque logiciel se verra être vérifié pour en assurer l’authenticité, ceci dans un seul et unique but, garantir l’intégrité et empêcher un logiciel non autorisé de fonctionner. Pour ce faire, on utilisera des mécanismes à clés publiques (ou cryptographie asymétrique) et plus particulièrement le mécanisme de signature numérique.

Signature numérique

Même si nous parlerons cryptographie durant la suite de l’article, l’idée n’est pas de présenter ici tous les concepts qui y sont liés, mais plutôt d’en faire une utilisation pour les besoins de notre étude. Mais il est quand même bon et intéressant de présenter le concept de signature numérique.

Imaginons ici, une entité Alice (la signataire) souhaitant envoyer des données signées à une entité Bob (vérifieur). Pour ce faire :

  • Alice va utiliser ce qu’on appelle une fonction de hachage afin de générer une « empreinte » ou « hash » permettant d’identifier la donnée initiale. Pour les fonctions de hachage, on retrouve généralement des algorithmes comme MD5, SHA-1, SHA2 (qui est plus sûr) ;
  • Alice va ensuite « chiffrer » ce « hash » par le biais de sa clé privée. C’est ce qu’on appellera la signature ;
  • Alice peut maintenant envoyer les données à Bob ;
  • Bob calcule le « hash » des données signées ;
  • Bob déchiffre la signature pour en extraire le « hash » relatif aux données signées ;
  • Bob compare les deux « hash ». S’ils sont identiques, Bob peut conclure que les données sont intègres et conformes à l’expéditeur.

Dans le principe, tout reposera sur ce qu’on appelle la chaîne de confiance (ou chain-of-trust pour les anglophones), le tout premier logiciel vérifiera la signature du second logiciel, le second logiciel vérifiera la signature du troisième et ainsi de suite. Ainsi, si une étape de vérification d’un des éléments de la chaîne échoue, la phase de démarrage se verra être interrompue de façon immédiate → démarrage impossible (violation du système) ! Dans un contexte embarqué, le schéma sera le suivant :

  • Le ROM Code (appelé RomBOOT pour notre plateforme), qui est le code intégré dans le silicone de la puce en ROM (Read Only Memory ou mémoire morte), vérifiera la signature du Bootloader de second niveau. À savoir qu’il est le tout premier programme à être exécuté lors de la mise sous tension. Du fait de sa présence en zone mémoire protégée, cela nous apporte la garantie qu’il ne pourra jamais être modifié.
  • Le Bootloader de second niveau ou chargeur de programme secondaire (SPL au sens générique) vérifiera la signature du Bootloader de troisième niveau (U-Boot, BareBox, etc.).
  • Le Bootloader de troisième niveau vérifiera la signature de l’image noyau (et device-tree) ainsi que l’image du système de fichiers racine. C’est cette étape qui sera présentée durant l’ensemble de l’article.

La figure 6 permet de représenter le processus avec les différentes phases du démarrage.

chain_of_trust

Fig. 6 : Chaîne de confiance.

Dans le cas de notre plateforme, le Bootloader de second niveau fait référence au logiciel at91bootstrap et le Bootloader de troisième niveau, U-Boot. Voici un exemple de la séquence de démarrage de notre plateforme (RomBOOT → At91Bootstrap → U-Boot) :


RomBOOT


AT91Bootstrap 3.8.10 (Wed Aug 29 11:08:21 UTC 2018)


SD/MMC: Image: Read file u-boot.bin to 0x23f00000

SD: Card Capacity: High or Extended

SD: Specification Version 3.0X

SD/MMC: Done to load image

<debug_uart>


U-Boot 2017.03-linux4sam_5.8-koncepto (Aug 29 2018 - 10:40:08 +0000)


CPU: SAMA5D27 1G bits DDR2 SDRAM

Crystal frequency: 24 MHz

CPU clock : 492 MHz

Master clock : 164 MHz

DRAM: 128 MiB

MMC: sdio-host@a0000000: 0, sdio-host@b0000000: 1

In: serial@f8020000

Out: serial@f8020000

Err: serial@f8020000

Net: eth0: ethernet@f8008000

Autre remarque : durant notre étude, nous n’aborderons pas l’aspect TEE (pour « Trusted Execution Environment ») et des différentes notions liées à l’utilisation d’un environnement sécurisé sur les System On Chip (SoC) offrant cette option (e.g TrustZone pour ARM). Même si en effet dans l’implémentation il faut l’avouer, c’est un environnement qui est chargé et vérifié durant la phase de démarrage. Mais rassurez-vous, cela fera sûrement l’objet d’un prochain article !

2.3 Racine de confiance

Vous l’aurez sans doute remarqué, tout repose sur la vérification et le chargement du tout premier logiciel, l’étape où le Rom Code authentifie le chargeur d’amorçage, car c’est lui qui permettra de garantir la cohérence sur l’ensemble de la chaîne. C’est donc ici que rentre en compte ce qu’on appellera la « Racine de confiance » (ou logiciel de confiance). En effet, de par l’utilisation d’une infrastructure à clés publiques pour la vérification des signatures, il est primordial de disposer d’un moyen de stocker celles-ci et de s’assurer de l’impossibilité d’une quelconque modification externe (changement des clés par un attaquant). Pour garantir cette « Racine de confiance » et implicitement s’assurer de l’authenticité du premier logiciel exécuté, plusieurs moyens sont à disposition :

  • stocker le premier logiciel (e.g U-Boot) et la clé publique en lecture seul ;
  • utiliser un composant intermédiaire, plus connu sous le nom de TPM (Trusted Platform Module), pour la gestion des clés publiques ;
  • injecter les clés publiques (dans certains cas le « hash » (SHA256 par exemple) de celles-ci, car moins coûteux pour le stockage) dans une zone mémoire spécifique de la puce et accessible par le Rom Code pour la vérification de la signature du premier logiciel. C’est ce que l’on retrouve sous la dénomination de mémoire OTP (One-Time Programmable), qui, sans besoin de traduction, signifie qu’une fois les clés installées, il est par la suite impossible de venir les changer.

C’est sur ce dernier mécanisme que de nombreux fondeurs ont basé leur implémentation du « Secure Boot » au sein de leur SoC, pour citer quelques références :

  • NXP, avec son implémentation HAB, pour High Assurance Boot ;
  • Xilinx ;
  • Microchip (voir figure 7) [2] ;
  • Samsung, avec un exemple concernant la famille Artik en [3].

memory

Fig. 7 : Mémoire OTP sur SoC Sama5.

L’authentification du premier logiciel est quelque chose de très spécifique en fonction des différents processeurs à disposition (outils pour injecter les clés, algorithmes, etc.). C’est d’ailleurs pour cette raison que nous aborderons uniquement l’aspect lié à U-Boot et à la vérification de l’image noyau (et device-tree). Mais si nous devions résumer l’étape de vérification du premier logiciel, elle serait la suivante :

  • le ROM Code charge le premier logiciel depuis un espace sécurisé ;
  • celui-ci vérifie que le « hash » de la clé publique contenue dans l’image du Bootloader, correspond au hash » contenu en mémoire OTP ;
  • il déchiffre la signature du Bootloader par le biais de la clé publique préalablement vérifiée, afin d’en extraire le « hash » ;
  • il finit par comparer le « hash » calculé avec le « hash » extrait, si les deux sont égaux, la source est donc authentifiée, et le logiciel peut être exécuté en toute confiance.

2.4 Private Keys

Très peu abordée jusqu’ici, la clé privée permettant de signer le logiciel est un élément critique et lui aussi un maillon essentiel dans le processus d’un démarrage sécurisé. Il n’est donc pas rare d’avoir à mettre en œuvre différentes solutions pour la gestion des clés privées dans un contexte de production. On pourra citer ici les smartcards, clés USB spécifiques ou encore les HSM (Hardware Security Module) qui sont des modules spécifiques pour ce type d’utilisation. Tous disposant d’une norme permettant les différents accès aux périphériques, la norme PKCS#11.

3. « Das U-Boot »

Après avoir parlé des différentes étapes du processus de démarrage sécurisé, il est maintenant temps d’explorer la partie relative à U-Boot afin d’aborder l’authentification de notre image noyau et du fichier dtb associé.

Nous commencerons par introduire les différents éléments et concepts mis à disposition par le projet, ensuite, il conviendra de récupérer les sources de notre chargeur d’amorçage pour par la suite compiler notre première image. Sur notre lancée, nous ferons la même opération, mais pour notre image noyau. Enfin, nous finirons sur différentes étapes liées à la cryptographie (création de clés, signature, etc.).

3.1 Préambule

L’utilisation du démarrage vérifié requiert l’emploi de diverses « configurations ». La principale repose sur l’utilisation du format FIT image, le successeur du format uImage classique.

3.1.1 FIT image : introduction

Comme nous l’avons présenté depuis le début de cet article, la question de la sécurité et de l’intégrité du logiciel est un élément clé dans le processus de développement. C’est pourquoi le format d’image FIT (pour Flattened Image Tree) a été défini afin de répondre aux différentes exigences liées à la sécurité lors du chargement des différents logiciels par le chargeur d’amorçage.

Contrairement à son prédécesseur, ce format se veut être comme une sorte de container pouvant contenir plusieurs types d’images :

  • image Noyau ;
  • fichiers device-tree ;
  • firmwares ou image FPGA ;
  • scripts U-Boot ;
  • et bien d’autres encore ! L’ensemble étant défini dans les sources de U-Boot (common/image.c).

Ainsi, une FIT image permet de ne disposer que d’un seul fichier, qui lui-même encapsule un ensemble de binaires. Donc plus besoin de charger plusieurs fichiers ! Plutôt intéressant quand même.

Par définition, l’extension pour une FIT image non compilée sera sous la forme .its pour Image Tree Source.

À noter que la description du contenu de l’image est entièrement basée sur la syntaxe du device-tree. En effet, au lieu de faire une description de type matérielle, il est question ici de décrire sous forme de nœuds et propriétés, l’ensemble des logiciels nécessaires au démarrage (Linux, firmware, etc.).

Concernant le sujet principal qu’est la gestion de l’intégrité, dans ce « nouveau » type d’image il est possible d’y intégrer des fonctions de hachage de type SHA1, SHA2 et MD5, contrairement au format « legacy » (uImage) qui lui ne peut gérer que des sommes de contrôle de type CRC32.

Et qui dit fonction de hachage dit signature. En effet, de par son implémentation, il nous sera très facile d’intégrer le mécanisme de signature numérique avec l’utilisation du format d’image FIT qui nous permettra ainsi de garantir et protéger l’ensemble des éléments nécessaires lors de la phase de démarrage, éléments contenus dans un seul et unique fichier (itb, Image Tree Blob).

Nous découvrirons la syntaxe et son utilisation dans la suite de l’article, il est maintenant temps pour nous d’aller récupérer les sources du projet U-Boot et du noyau Linux !

3.2 Construction et mise en place...

3.2.1 U-Boot

Commençons par récupérer les sources de U-Boot, et pour être plus précis, le fork mis à disposition par la communauté Atmel/Microchip (linux4sam), qui est d’ailleurs le référentiel git utilisé par les deux Build Systems évoqués plus haut (l’auteur utilise Yocto/OE) :

$ git clone git://github.com/linux4sam/u-boot-at91.git

$ git checkout origin/u-boot-2017.03-at91 -b u-boot-2017.03-at91

$ cd u-boot-at91

Puis il conviendra de choisir la configuration relative à notre plateforme :

$ make sama5d27_som1_ek_mmc_defconfig

Ce qui aura pour effet de créer le fichier .config à la racine.

Il faudra aussi mettre à jour la variable CROSS_COMPILE, afin que celle-ci pointe vers notre chaîne de compilation croisée :

$ export CROSS_COMPILE=arm-linux-gnueabihf-

Enfin, avant de prétendre travailler avec une FIT image, il faudra s’assurer de son support au niveau de notre configuration. Pour ce faire, lançons l’interface ncurses :

$ make menuconfig

Dans Boot images > Support Flattened image tree, il conviendra de sélectionner l’option pour le support du format d’image FIT (voir figure 8).

FIT

Fig. 8 : Ajout du support pour le format d’image FIT.

Il est aussi intéressant de disposer de la commande iminfo qui permet d’afficher les informations concernant l’image chargée en mémoire (header). Pour ce faire, dans Command line interface > Boot commands, il faudra sélectionner celle-ci. Un petit exemple est visible en figure 9.

iminfo

Fig. 9 : Sélection de la commande iminfo.

Nous voilà maintenant avec une configuration nous permettant de démarrer avec une FIT image, mais vous l’aurez sans doute remarqué, sans l’aspect signature à ce niveau-ci de l’article. Nous allons d’abord explorer le fonctionnement de ce format sur notre plateforme.

Avant de lancer la génération de notre image U-Boot, il est aussi nécessaire de compiler l’ensemble des outils essentiels pour la génération/manipulation d’une FIT image :

$ make tools

CHK include/config/uboot.release

CHK include/generated/version_autogenerated.h

CHK include/generated/timestamp_autogenerated.h

UPD include/generated/timestamp_autogenerated.h

CHK include/generated/generic-asm-offsets.h

CHK include/generated/asm-offsets.h

HOSTCC tools/mkenvimage.o

HOSTLD tools/mkenvimage

...

Il est maintenant temps de lancer la génération :

$ make

Et voilà !

3.2.2 Noyau Linux

Maintenant que nous disposons d’un Bootloader prêt à accueillir une FIT image, il nous faut dès à présent être en mesure de disposer des éléments qui seront à charger et vérifier par celui-ci pour être intégré au sein de la FIT image. Pour ce faire, il nous faudra générer image noyau et device-tree blob.

Sans plus attendre, téléchargeons les sources du noyau Linux sur le même référentiel que précédemment, configurons notre construction pour notre plateforme, puis lançons la construction :

$ git clone https://github.com/linux4sam/linux-at91

$ cd linux-at91/

$ git checkout origin/linux-4.9-at91 -b linux-4.9-at91

$ make ARCH=arm sama5_defconfig

$ make ARCH=arm menuconfig (pour effectuer des modifications)

$ make ARCH=arm

3.2.3 FIT image !

Nous voilà fin prêts à construire notre première FIT image à destination de notre cible sama5d27. Tout d’abord, commençons par rapatrier l’image noyau et le device-tree associé à la racine des sources de U-Boot :

$ ln -s <chemin vers sources du noyau linux>/arch/arm/boot/zImage

$ ln -s <chemin vers sources du noyau linux>/arch/arm/boot/dts/at91-sama5d27_som1_ek.dtb

Puis, éditons notre image en nous basant sur la syntaxe du device-tree comme évoquée un peu plus tôt. Appelons cette image fitImage.its :

/dts-v1/;


/ {

description = "Simple image with single Linux kernel and FDT blob for GLMF";

#address-cells = <1>;


images {

kernel {

description = "zImage";

data = /incbin/("./zImage");

type = "kernel";

arch = "arm";

os = "linux";

compression = "none";

load = <0x22000000>;

entry = <0x22000000>;

hash-1 {

algo = "crc32";

};

hash-2 {

algo = "sha256";

};

};

fdt-1 {

description = "Flattened Device Tree blob";

data = /incbin/("./at91-sama5d27_som1_ek.dtb");

type = "flat_dt";

arch = "arm";

compression = "none";

hash-1 {

algo = "crc32";

};

hash-2 {

algo = "sha256";

};

};

};


configurations {

default = "conf-1";

conf-1 {

description = "configuration 1 (Kernel & dtb)";

kernel = "kernel";

fdt = "fdt-1";

};

};

};

Ceci mérite quelques explications :

  • le champ description se suffit à lui-même, il est tout de même important de mettre un descriptif clair ;
  • #address-cells = <1>, définit le nombre de cellules <u32> utilisées pour encoder le champ d’adresse dans la propriété load ou entry au sein du nœud enfant ;
  • images est le nœud principal (node) qui contient un ensemble de nœuds enfants (child node), qui chacun représente une sous-image, dans notre cas :
    • Le nœud enfant kernel, permet de décrire la configuration du composant sous-image, on retrouvera :
      • un champ description permettant de définir brièvement le composant ;
      • un champ data, qui permet de renseigner le chemin vers le fichier externe à prendre en compte pour le composant en question. Dans notre cas, c’est notre zImage ;
      • un champ type, qui permet d’expliciter le type du composant de la sous-image. Ici, nous faisons référence au kernel. On retrouvera aussi des types comme firmware, ramdisk, script, etc. ;
      • le champ arch pour le nom de l’architecture visée, qui est arm dans notre exemple ;
      • le champ os quant à lui permet de spécifier le nom du système d’exploitation, qui fait logiquement référence à Linux dans notre cas ;
      • un champ compression afin de renseigner si un algorithme doit être appliqué sur les données, ici aucune compression, donc propriété à none. Il est possible de spécifier ici : lzo, bzip2, gzip ;
      • le champ load, spécifie l’adresse en RAM où l’image noyau doit être copiée ;
      • le champ entry, spécifie l’emplacement de l’image noyau en mémoire afin de permettre à U-Boot de démarrer celle-ci ;
      • un nœud enfant hash, permettant de spécifier le type de fonction de hachage ou checksum par le biais de la propriété algo. Dans notre exemple, nous avons positionné 2 nœuds enfants pour avoir une gestion par checksum (crc32) et l’autre pour une gestion par hachage (sha256).
    • Le nœud enfant fdt-1, est quasiment identique au précédent, mais cette fois-ci, la propriété datacontiendra le fichier device-tree et la propriété type fera référence au device-tree (flat_dt) ;
    • Le nœud configuration, qui dans notre cas permettra de définir la ou les configurations. Ici une seule configuration est de rigueur. Il faudra donc définir dans le sous-nœud de la configuration, les éléments qui la composent (kernel et fdt-1).

Ensuite, il ne nous restera qu’à générer notre FIT image à partir du fichier source, le tout grâce à la commandemkimage :

$ tools/mkimage -f fitImage.its fitImage

Une fois généré, il nous est possible de vérifier le contenu de l’image en listant les informations par le biais de cette même commande :

$ tools/mkimage -l fitImage

FIT description: Simple image with single Linux kernel and FDT blob for GLMF

Created: Wed Aug 8 22:03:54 2018

Image 0 (kernel)

Description: zImage

Created: Wed Aug 8 22:03:54 2018

Type: Kernel Image

Compression: uncompressed

Data Size: 3652128 Bytes = 3566.53 KiB = 3.48 MiB

Architecture: ARM

OS: Linux

Load Address: 0x22000000

Entry Point: 0x22000000

Hash algo: crc32

Hash value: ecff0c84

Hash algo: sha256

Hash value: b6b3ff64c52370cd1f80af887058ef8d5e1bdf16a81beb640abf1b3631d6b593

Image 1 (fdt-1)

Description: Flattened Device Tree blob

Created: Wed Aug 8 22:03:54 2018

Type: Flat Device Tree

Compression: uncompressed

Data Size: 32255 Bytes = 31.50 KiB = 0.03 MiB

Architecture: ARM

Hash algo: crc32

Hash value: 61c42b20

Hash algo: sha256

Hash value: 76917d3e1697d11fa52cf5e0a5efa03061c669c3ba0e1d2b2cf10eda30420334

Default Configuration: 'conf-1'

Configuration 0 (conf-1)

Description: dummy description

Kernel: kernel

FDT: fdt-1

Le listing de l’image nous permet de constater que celle-ci contient bien une sous-image « Kernel » (kernel) et une sous-image « device-tree » (fdt-1) et que la configuration utilisée par défaut contient bien ces deux sous-images (Default Configuration: 'conf-1'). Il aurait été ici possible de mettre un deuxième fichier device-tree et ainsi créer une deuxième configuration. Mais ça, vous êtes capable de le faire !

Maintenant que l’ensemble nous donne entière satisfaction, il est temps pour nous d’essayer notre premier démarrage avec ce format d’image. Pour ce faire, il faudra mettre à jour l’image U-Boot (u-boot.bin) et positionner le fichier fitImage sur la première partition (juste à titre d’exemple ici, certains préfèreront positionner celui-ci sous/boot, l’auteur le premier). Une fois l’opération terminée, il suffira de stopper l’autoboot au niveau de notre chargeur d’amorçage afin d’avoir la main sur le shell mis à disposition par ce dernier.

Il nous faudra en premier lieu charger notre image (via la commande load qui est plus générique que fatload ou ext4load pour la gestion des accès au File-System) en spécifiant l’interface, le périphérique, la partition, l’adresse et le fichier à récupérer, ce qui nous donne :

=> load mmc 0:1 0x21000000 fitImage

reading fitImage

3686232 bytes read in 237 ms (14.8 MiB/s)

Une fois le fichier chargé, il nous est possible de démarrer nos images à l’adresse mémoire préalablement spécifiée. Ceci s’effectue par le biais de la commandebootm :

=> bootm 0x21000000

## Loading kernel from FIT Image at 21000000 ...

Using 'conf-1' configuration

Trying 'kernel' kernel subimage

Description: zImage

Type: Kernel Image

Compression: uncompressed

Data Start: 0x210000e0

Data Size: 3652128 Bytes = 3.5 MiB

Architecture: ARM

OS: Linux

Load Address: 0x22000000

Entry Point: 0x22000000

Hash algo: crc32

Hash value: ecff0c84

Hash algo: sha256

Hash value: b6b3ff64c52370cd1f80af887058ef8d5e1bdf16a81beb640abf1b3631d6b593

Verifying Hash Integrity ... crc32+ sha256+ OK

## Loading fdt from FIT Image at 21000000 ...

Using 'conf-1' configuration

Trying 'fdt-1' fdt subimage

Description: Flattened Device Tree blob

Type: Flat Device Tree

Compression: uncompressed

Data Start: 0x2137bc34

Data Size: 32255 Bytes = 31.5 KiB

Architecture: ARM

Hash algo: crc32

Hash value: 61c42b20

Hash algo: sha256

Hash value: 76917d3e1697d11fa52cf5e0a5efa03061c669c3ba0e1d2b2cf10eda30420334

Verifying Hash Integrity ... crc32+ sha256+ OK

Booting using the fdt blob at 0x2137bc34

Loading Kernel Image ... OK

Loading Device Tree to 27b55000, end 27b5fdfe ... OK

Comme nous le constatons sur les traces du shell précédent, le Bootloader charge bien les éléments contenus dans la FIT image (voir « Loading Kernel from FIT … ») tout en vérifiant l’intégrité de ceux-ci, qui dans notre cas n’ont pas été altérés (crc32+ sha256+ OK, le + signifiant la réussite lors de la vérification, à l’inverse, le signe serait apparu).

Sans paramètre, la commande bootm prendra la configuration par défaut (voir using  conf-1 configuration). Mais dans le cas où l’utilisateur souhaiterait par exemple spécifier les sous-images à charger, la commande deviendrait la suivante :

=> bootm 0x21000000:kernel – 0x21000000:fdt-1

La forme employée est bootm [<addr>]:<subimg1> - [<addr>]:<subimg2>.

Dans le cas d’une FIT image avec plusieurs configurations, l’exécution d’une configuration spécifique s’effectuerait avec la commande ci-après :

=> bootm 0x21000000#conf-2

Autre aspect intéressant, il est possible d’extraire une sous-partie de l’image via la commandeimxtract, chose très utile pour les images de typefirmware. Par exemple, sur un i.MX7, le chargement et le démarrage du cortex M4 se ferait comme suit :

=> imxtract $loadaddr firmware-1 $m4_addr

## Copying 'firmware-1' subimage from FIT image at 80800000 ...

crc32+ sha1+ Loading part 15 ... OK

=> bootaux $m4_addr

## Starting auxiliary core at 0x007F8000 …

Nous voilà maintenant prêts pour attaquer la partie fatidique de l’article, l’aspect vérification. Pour les plus curieux d’entre vous, la documentation relative au format d’image FIT se trouve sousdoc/uImage.FIT/dans les sources du projet U-Boot.

3.3 Verified (U-)Boot

Dans cette dernière partie de l’article, nous allons mettre en pratique les différentes techniques abordées tout au long de notre étude afin de réaliser notre premier démarrage vérifié. Soyez prêt !

3.3.1 Concept

L’étape de signature et vérification de la FIT image avec U-Boot repose sur les mêmes concepts qu’évoqués en début d’article. Dans notre contexte, les étapes seront :

  • Pour la génération de la signature :
    • génération du « hash » ou « empreinte » des images par le biais d’une fonction de hachage, sha1 ou sha256 ;
    • chiffrement du « hash » avec la clé privée afin de générer la signature numérique ;
    • stockage de la signature dans la FIT image (FIT image signée).
  • Pour la vérification lors du démarrage :
    • lecture de la FIT image ;
    • récupération de la clé publique ;
    • extraction de la signature depuis la FIT image ;
    • calcul du « hash » de la configuration contenue dans la FIT image ;
    • déchiffrement de la signature par le biais de la clé publique afin d’extraire le « hash » ;
    • si les deux « hashs » sont identiques, alors U-Boot pourra charger les images et ainsi garantir l’authenticité de celles-ci.

3.3.2 Implémentation

Sans plus attendre, mettons à jour notre configuration afin d’y ajouter le support pour la vérification de signature d’une FIT image. Il faudra ainsi dans Boot images > Enable signature verification of FIT uImages, cocher l’option comme le montre la figure 10.

FIT_signature

Fig. 10 : Ajout du support pour la vérification de la signature d’une FIT image, CONFIG_FIT_SIGNATURE.

Ce n’est pas tout, il estaussi primordial de rajouter le support de la partie cryptographique RSA nécessaire pour signer et vérifier. Il faut donc activer l’option Library routines > Use RSA Library.

Il est maintenant temps de construire notre FIT image avec la prise en compte de notre nouvelle configuration. Partons d’un nouveau fichier que l’on nommera fitImage_sign.its :

/dts-v1/;


/ {

description = "Simple signed image with single Linux kernel and FDT blob for GLMF";

#address-cells = <1>;


images {

kernel {

description = "zImage";

data = /incbin/("./zImage");

type = "kernel";

arch = "arm";

os = "linux";

compression = "none";

load = <0x22000000>;

entry = <0x22000000>;

hash-1 {

algo = "sha256";

};

};

fdt-1 {

description = "Flattened Device Tree blob";

data = /incbin/("./at91-sama5d27_som1_ek.dtb");

type = "flat_dt";

arch = "arm";

compression = "none";

hash-1 {

algo = "sha256";

};

};

};


configurations {

default = "conf-1";

conf-1 {

description = "signed configuration 1 (Kernel & dtb)";

kernel = "kernel";

fdt = "fdt-1";

signature {

algo = "sha256,rsa4096";

key-name-hint = "glmf";

sign-images = "fdt", "kernel";

};

};

};

};

Explications :

  • Le nœud images restera quasiment identique à notre première image. Il n’est en effet pas forcément nécessaire de garder plusieurs « hash » ici ;
  • Le nœud configurationquant à lui se verra être mis à jour avec :
    • le nœud enfant signature qui contient quelques propriétés essentielles à définir afin de garantir la signature de notre configuration :
    • algo, qui permet de renseigner le type d’algorithme utilisé, icisha256etrsa4096 ;
    • key-name-hint, pour spécifier le nom de la clé utilisée pour signer l’image. C’est le nom qu’il faudra donner à notre clé privée ainsi qu’au certificat lors de la génération avec openssl ;
    • sign-images, la liste des images à signer. Dans notre cas, nous spécifions le fichier device-tree ainsi que l’image noyau. Mais nous aurions pu ajouter ici une image de type firmware par exemple.

Signature d’image VS signature configuration

Dans notre exemple, nous avons pris le parti de signer la configuration. Ceci a pour avantage de signer l’ensemble des « hashs » contenus dans la configuration. Ceci permet d’éviter les attaques de type mix-and-match lors d’une signature simple sur les images et non sur la configuration. En effet, il est simple dans le cas d’une configuration non signée de garder les images signées, mais d’établir une configuration différente.

Maintenant que nous avons notre fichier source prêt à être utilisé. Il va nous falloir disposer de tout le nécessaire afin de réaliser les différentes étapes de signature.

Mettons en place dans un premier temps un endroit afin de stocker le matériel relatif à la cryptographie :

$ mkdir keys

Puis générons notre clé privée de type RSA 4096 bits (ou 3072 selon votre humeur !) nécessaire pour les opérations cryptographiques, clé que nous appellerons iciglmfafin d’être cohérent avec notre fichierits :

$ openssl genpkey -algorithm RSA -out keys/glmf.key -pkeyopt rsa_keygen_bits:4096

Et notre certificat à clé publique de type x.509, ici glmf.crt :

$ openssl req -new -x509 -key keys/glmf.key -out keys/glmf.crt

Pour les curieux, l’extraction de la clé publique se fera de la manière suivante :

$ openssl rsa -pubout -in key.key -out pubkey.pem

3.3.3 Gestion de la clé publique

Comme déjà abordé lors du second chapitre, afin de vérifier une image signée par une clé publique lors de la phase de démarrage, il faut que celle-ci (la clé publique) soit dite de confiance.

Pour ce faire, U-Boot a fait le choix d’intégrer la clé publique dans le fichier device-tree de la plateforme (dtb au sens U-Boot et non Linux), qui en temps normal est utilisé pour du « run-time configuration » (versus CONFIG_* dans les fichiers de définition de la carte cible) tout comme le fait le noyau Linux par exemple. L’implémentation du support du device-tree dans U-Boot porte le nom de « Control FDT » (Flattened Device Tree), qu’il faudra sélectionner avec l’option CONFIG_OF_CONTROL. De plus, à l’inverse du noyau Linux où le fichier dtb est un fichier binaire à part entière dans l’étape de chargement (que nous avons ici encapsulé dans notre FIT image), il en est une tout autre histoire pour dtb utilisé par le Bootloader. En effet, lors de l’étape de compilation, celui-ci sera construit séparément, mais ajouté au binaire U-Boot par un simple appel à la commande cat, exemple :

$ cat u-boot.bin u-boot.dtb > image.bin

Avec ce type de processus, et de par le fait que notre Bootloader est lui-même vérifié par le logiciel précédent, nous pouvons ainsi faire confiance à notre clé publique.

À nous de jouer ! Nous allons dans un premier temps récupérer le fichier dtb de notre plateforme, fichier que nous avons compilé durant la première étape. Ainsi, nous le renommerons pubkey.dtb afin de le différencier durant notre étude, car c’est celui-ci qui contiendra notre clé publique pour la vérification de notre FIT image :

$ cp arch/arm/dts/at91-sama5d27_som1_ek.dtb pubkey.dtb

Puis, ayant rajouté le support pour la gestion de la signature, il nous faut maintenant compiler à nouveau les différents outils afin de pouvoir travailler avec notre nouvelle configuration :

$ make tools

Nous avons maintenant la commandemkimageà jour pour réaliser notre étape de signature. Respirez un grand coup et lancez la commande suivante :

$ tools/mkimage -f fitImage_sign.its -K pubkey.dtb -k <chemin vers le dossier contenant les clés> -r fitImage

Facile, non ?

Si on s’attarde un peu sur les différentes options et leurs paramètres :

  • -f, pour spécifier le fichier source en entrée ;
  • -K, permet de spécifier le fichier device-tree compilé afin d’y insérer notre clé publique. Nécessaire pour l’étape de vérification sur cible ;
  • -k, pour renseigner le chemin contenant la clé privée (glmf.key) et le certificat contenant la clé publique (glmf.crt) pour la vérification ;
  • -r, pour spécifier la propriété « required ». Ainsi, il sera donc impossible de démarrer tout logiciel n’ayant pas été vérifié avec cette clé. Dans notre cas, cela signifie que nous forçons la vérification de notre configuration avec notre clé glmf.

Pour de plus amples informations sur les différentes options de la commandemkimage, il sera conseillé de se référer à l’aide de celle-ci :

Usage: tools/mkimage -l image

-l ==> list image header information

tools/mkimage [-x] -A arch -O os -T type -C comp -a addr -e ep -n name -d data_file[:data_file...] image

-A ==> set architecture to 'arch'

-O ==> set operating system to 'os'

-T ==> set image type to 'type'

-C ==> set compression type 'comp'

-a ==> set load address to 'addr' (hex)

-e ==> set entry point to 'ep' (hex)

-n ==> set image name to 'name'

-d ==> use image data from 'datafile'

-x ==> set XIP (execute in place)

tools/mkimage [-D dtc_options] [-f fit-image.its|-f auto|-F] [-b <dtb> [-b <dtb>]] [-i <ramdisk.cpio.gz>] fit-image

<dtb> file is used with -f auto, it may occur multiple times.

-D => set all options for device tree compiler

-f => input filename for FIT source

-i => input filename for ramdisk file

Signing / verified boot options: [-E] [-k keydir] [-K dtb] [ -c <comment>] [-p addr] [-r] [-N engine]

-E => place data outside of the FIT structure

-k => set directory containing private keys

-K => write public keys to this .dtb file

-c => add comment in signature node

-F => re-sign existing FIT image

-p => place external data at a static position

-r => mark keys used as 'required' in dtb

-N => engine to use for signing (pkcs11)

tools/mkimage -V ==> print version information and exit

Use -T to see a list of available image types

Pour les (plus) curieux qui se demandent à quel endroit est stockée la signature au sein de note FIT image, mkimage a placé celle-ci au sein du nœud enfant signature dans la propriété value, contenu qu’il est facile de récupérer par la commande suivante :

$ fdtget -tx fitImage /configurations/conf-1/signature value

8694ec11 e8386c70 c57f43fd 1964dddd bd34e7c0 c91cf6f6 a03575e6 224e4125 2ff06e1e 99d4c92a 47d42fbf f63f39e5 9f5de96a 49c3bcc9 998a8f98 659f2299 417fe78d 2673b7d2 622201a2 8e66b91c 11238fa4 5552d67c 13ea54fc 7097d2a 4051e812 c7ab02d3 6c30bf9c 557377ff 99354e81 8fd724c4 aa79e01 1e97095c 67a8d550 ee9e32d1 c11c3ae2 a93e4953 803955f5 900d6481 3cef2d73 ...

Il est aussi possible de convertir notre fichier dtb en dts afin de porter attention au nœudsignaturecontenant notre clé publique :

$ dtc -I dtb -O dts -o pubkey.dts pubkey.dtb

En voici le résultat :

/dts-v1/;


/ {

#address-cells = <0x1>;

#size-cells = <0x1>;

model = "Atmel SAMA5D27 SOM1 EK";

compatible = "atmel,sama5d27-som1-ek", "atmel,sama5d2", "atmel,sama5";


signature {


key-glmf {

required = "conf";

algo = [00];

rsa,r-squared = <0xd979a356 ...>;

rsa,modulus = <0xed12cc1d ...;

rsa,exponent = <0x0 0x10001>;

rsa,n0-inverse = <0xdeec77db>;

rsa,num-bits = <0x1000>;

key-name-hint = "glmf";

};

};

...

Nous voyons en effet qu’il existe maintenant l’ensemble de la définition relative à notre clé publique (child node key-glmf). On retrouvera par exemple le champ required précédemment configuré ou encore le champ rsa,num-bits qui spécifie le nombre de bits utilisés pour la clé RSA, ici 0x1000 pour 4096 en décimal. Pour de plus amples informations sur les différentes propriétés, la documentation est disponible en [4].

Afin de valider notre précédente opération, nul besoin pour l’instant de mettre à jour FIT image et binaire U-Boot. En effet, il est d’ores et déjà possible de vérifier notre image par le biais de l’utilitaire fit_check_sign que nous venons de compiler. Pour ce faire, nous allons passer en argument notre FIT image accompagnée du fichier dtb contenant la clé publique (pubkey.dtb) :

$ tools/fit_check_sign -f fitImage -k pubkey.dtb

Verifying Hash Integrity ... sha256,rsa4096:glmf+

## Loading kernel from FIT Image at 7fbd1975c000 ...

Using 'conf-1' configuration

Verifying Hash Integrity ...

sha256,rsa4096:glmf+

OK


Trying 'kernel' kernel subimage

Description: zImage

Created: Wed Aug 8 21:27:58 2018

Type: Kernel Image

Compression: uncompressed

Data Size: 3652128 Bytes = 3566.53 KiB = 3.48 MiB

Architecture: ARM

OS: Linux

Load Address: 0x22000000

Entry Point: 0x22000000

Hash algo: sha256

Hash value: b6b3ff64c52370cd1f80af887058ef8d5e1bdf16a81beb640abf1b3631d6b593

Verifying Hash Integrity ...

sha256+

OK


Loading Kernel Image ... OK


## Loading fdt from FIT Image at 7fbd1975c000 ...

Using 'conf-1' configuration

Trying 'fdt-1' fdt subimage

Description: Flattened Device Tree blob

Created: Wed Aug 8 21:27:58 2018

Type: Flat Device Tree

Compression: uncompressed

Data Size: 32255 Bytes = 31.50 KiB = 0.03 MiB

Architecture: ARM

Hash algo: sha256

Hash value: 76917d3e1697d11fa52cf5e0a5efa03061c669c3ba0e1d2b2cf10eda30420334

Verifying Hash Integrity ...

sha256+

OK


Loading Flat Device Tree ... OK


## Loading ramdisk from FIT Image at 7fbd1975c000 ...

Using 'conf-1' configuration

Could not find subimage node


Signature check OK

Comme vous pouvez le constater, la commande nous sort le résultat suivant : sha256,rsa4096:glmf+. Ceci signifie que pour la vérification, une clé de type RSA 4096 bits de nom glmf avec une fonction de hachage de type sha256 a été utilisée. Le + nous informe de la bonne réussite de l’opération (comme vu plus haut dans l’article). À l’inverse, le signe aurait été présent. Nous pouvons faire le test en modifiant la signature de notre FIT image :

$ fdtput -tx fitImage /configurations/conf-1/signature value 8694ec12 e8386c70 c57f43fd 1964dddd bd34e7c0 c91cf6f6 a03575e6 224e4125 2ff06e1e 99d4c92a 47d42fbf f63f39e5 9f5de96a 49c3bcc9 998a8f98 659f2299 417fe78d 2673b7d2 622201a2 8e66b91c 11238fa4 5552d67c 13ea54fc 7097d2a 4051e812 c7ab02d3 6c30bf9c 557377ff 99354e81 8fd724c4 aa79e01 1e97095c 67a8d550 ee9e32d1 c11c3ae2 a93e4953 803955f5 900d6481 3cef2d73 ...

Et relançons l’étape de vérification :

$ tools/fit_check_sign -f fitImage -k pubkey.dtb

Verifying Hash Integrity ... sha256,rsa4096:glmf-

Failed to verify required signature 'key-glmf

Signature check Bad (error 1)

Nous observons bien un problème lors de la vérification de la signature de notre configuration (sha256,rsa4096:glmf-). Jusqu’ici tout va bien ...

L’ensemble de la chaîne étant maintenant validé et fonctionnel (signature + vérification), nous pouvons générer à nouveau notre binair eu-boot.bin. Notez tout de même qu’il faudra être vigilant et utiliser un argument à la commande make afin de spécifier le fichier dtb spécifique à utiliser (notre fameux fichierpubkey.dtb) et non celui généré en premier lieu par U-Boot. Sinon, la clé publique ne serait donc pas contenue dans notre binaire final. Afin de réaliser cette opération, l’argument EXT_DTB devra être utilisé lors de l’exécution de la commande :

$ make V=1 EXT_DTB=pubkey.dtb

...

cat pubkey.dtb > dts/dt.dtb

cat u-boot-nodtb.bin dts/dt.dtb > u-boot-dtb.bin

cp u-boot-dtb.bin u-boot.bin

./tools/mkimage -A arm -T firmware -C none -O u-boot -a 0x23f00000 -e 0 -n "U-Boot 2017.03-linux4sam_5.8 for sama5d27_som1_ek board" -d u-boot.bin u-boot.img

Image Name: U-Boot 2017.03-linux4sam_5.8 for

Created: Wed Aug 8 21:47:36 2018

Image Type: ARM U-Boot Firmware (uncompressed)

Data Size: 400515 Bytes = 391.13 KiB = 0.38 MiB

Load Address: 23f00000

Entry Point: 00000000

Grâce à l’option permettant d’activer le mode verbeux, il nous est possible de constater les différentes étapes relatives à l’ajout de notre fichier dtb contenant la clé publique au sein du binair eu-boot.bin. Ainsi, nous disposons de tout le nécessaire afin de prétendre réaliser un démarrage vérifié via notre bootloader !

3.3.4 Test sur cible

Vous l’aurez compris, il est temps de mettre à jour notre fichier u-boot.bin ainsi que notre fichier fitImage sur la première partition de notre SD Card (l’utilisation du support SD n’est utile que pour les phases de tests soulignons-le, car il n’y a rien d’industriel à utiliser un tel support !).

Comme nous l’avons fait lors du premier test, il nous faudra ici aussi charger le fichier fitImage afin de commencer nos différentes étapes de vérification :

=> load mmc 0:1 0x21000000 fitImage

Puis comme lors de notre premier démarrage, lançons l’exécution :

=> bootm 0x21000000

## Loading kernel from FIT Image at 21000000 ...

Using 'conf-1' configuration

Verifying Hash Integrity ... sha256,rsa4096:glmf+ OK

Trying 'kernel' kernel subimage

Description: zImage

Type: Kernel Image

Compression: uncompressed

Data Start: 0x210000e0

Data Size: 3652128 Bytes = 3.5 MiB

Architecture: ARM

OS: Linux

Load Address: 0x22000000

Entry Point: 0x22000000

Hash algo: sha256

Hash value: b6b3ff64c52370cd1f80af887058ef8d5e1bdf16a81beb640abf1b3631d6b593

Verifying Hash Integrity ... sha256+ OK

## Loading fdt from FIT Image at 21000000 ...

Using 'conf-1' configuration

Trying 'fdt-1' fdt subimage

Description: Flattened Device Tree blob

Type: Flat Device Tree

Compression: uncompressed

Data Start: 0x2137bc00

Data Size: 32255 Bytes = 31.5 KiB

Architecture: ARM

Hash algo: sha256

Hash value: 76917d3e1697d11fa52cf5e0a5efa03061c669c3ba0e1d2b2cf10eda30420334

Verifying Hash Integrity ... sha256+ OK

Booting using the fdt blob at 0x2137bc00

Loading Kernel Image ... OK

Loading Device Tree to 27b53000, end 27b5ddfe ... OK


Starting kernel ...


Booting Linux on physical CPU 0x0

Sans surprise, nous constatons que l’étape de vérification pour notre configuration (Kernel et device-tree) s’est effectuée sans problème pour notre nouveau test sur cible. Il serait maintenant judicieux d’améliorer notre process en intégrant à notre FIT une sous-image RAMDisk.

À titre d’exemple, voici la même séquence de démarrage sur la WaRP7 avec RAMDisk :

=> tftp 0x85000000 fitImage_ramdisk

...

TFTP from server 192.168.7.1; our IP address is 192.168.7.2

Filename 'fitImage_ramdisk'.

Load address: 0x85000000

Loading: #################################################################

#################################################################

#################################################################

#################################################################

#################################################################

#################################################################

#################################################################

#################################################################

#################################################################

#################################################################

##########################################################

629.9 KiB/s

done

Bytes transferred = 10392830 (9e94fe hex)

=> bootm 0x85000000

## Loading kernel from FIT Image at 85000000 ...

Using 'conf-1' configuration

Verifying Hash Integrity ... sha256,rsa4096:ynov+ OK

Trying 'kernel' kernel subimage

Description: zImage

Type: Kernel Image

Compression: uncompressed

Data Start: 0x850000b8

Data Size: 8581208 Bytes = 8.2 MiB

Architecture: ARM

OS: Linux

Load Address: 0x80800000

Entry Point: 0x80800000

Hash algo: sha256

Hash value: 2acbc1f07c1e576c7a73fd5ef4bd5fe44c87c32846c39ba4d1828e10ffa915eb

Verifying Hash Integrity ... sha256+ OK

## Loading ramdisk from FIT Image at 85000000 ...

Using 'conf-1' configuration

Trying 'ramdisk' ramdisk subimage

Description: initramfs

Type: RAMDisk Image

Compression: gzip compressed

Data Start: 0x858358a0

Data Size: 1753020 Bytes = 1.7 MiB

Architecture: ARM

OS: Linux

Load Address: 0x86000000

Entry Point: 0x86000000

Hash algo: sha256

Hash value: 8a46721ddd678c8e4bc1f440e97e5aacbff21794be7fd059b16dec8ae38330f8

Verifying Hash Integrity ... sha256+ OK

Loading ramdisk from 0x858358a0 to 0x86000000

## Loading fdt from FIT Image at 85000000 ...

Using 'conf-1' configuration

Trying 'fdt-1' fdt subimage

Description: Flattened Device Tree blob

Type: Flat Device Tree

Compression: uncompressed

Data Start: 0x8582f210

Data Size: 26067 Bytes = 25.5 KiB

Architecture: ARM

Hash algo: sha256

Hash value: 86f0938b586a01d06ecbfe6b49e3e68ac8e8b07e5e9bcb73648526b51394ee6d

Verifying Hash Integrity ... sha256+ OK

Booting using the fdt blob at 0x8582f210

Loading Kernel Image ... OK

Using Device Tree in place at 8582f210, end 858387e2


Starting kernel ...


[ 0.000000] Booting Linux on physical CPU 0x0

[ 0.000000] Linux version 4.17.4-fslc+g65375933 (oe-user@oe-host) (gcc version 7.3.0 (GCC)) #1 SMP Mon Sep 10 16:35:53 UTC 2018

...

[ 3.055701] #0: imx7-sgtl5000

[ 3.076914] Freeing unused kernel memory: 1024K

/ # ls /

bin dev etc init lib proc root run sbin sys usr var

/ # ...

/ # exec switch_root /newroot /sbin/init

...

[ 181.924093] systemd[1]: Detected architecture arm.

Welcome to Poky (Yocto Project Reference Distro) 2.5.1 (sumo)!

3.3.5 Pour résumer

Afin de récapituler un peu l’ensemble des éléments liés à la mise en place du démarrage vérifié avec U-Boot, voici en figure 11 un diagramme permettant de rappeler les différentes étapes.

fitImage

Fig. 11 : U-Boot et démarrage vérifié.

Autre point : lors de la création de nos différentes FIT images, nous avons sur chaque partie de l’étude, élaboré celles-ci de façon manuelle ce qui, dans certains contextes (industriel pour n’en citer qu’un), peut s’avérer comme une tâche assez lourde et difficilement maintenable suivant les configurations. Mais c’est sans compter sur l’aide du projet Yocto ! En effet, il est possible via celui-ci de générer automatiquement durant la phase de construction, une FIT image, moyennant une configuration assez simple (fichiers*.conf) :

KERNEL_CLASSES ?= " kernel-fitimage "

KERNEL_IMAGETYPE ?= "fitImage"

Il est également possible de gérer les aspects signature de la FIT image depuis le build system :

UBOOT_SIGN_KEYDIR = "/keys/directory"

UBOOT_SIGN_KEYNAME = "glmf" # keys name in keydir (eg. "dev.crt", "dev.key")

UBOOT_SIGN_ENABLE = "1"

Conclusion

Dans cet article, nous avons pu naviguer dans les différentes étapes d’un démarrage vérifié, en partant du ROM code jusqu’au chargement de l’image noyau par le chargeur d’amorçage, mais sans aller jusqu’à l’étape de vérification du système de fichiers racine via des mécanismes comme dm-verity (pourtant un sujet tout aussi intéressant à explorer).

Tout ceci nous a permis de mettre en place sur une plateforme de type industrielle, un bout du processus du démarrage vérifié avec U-Boot [5]. Mécanisme qui est de plus en plus utilisé sur les périphériques grands publics (e.g smartphones, Chromebook, etc.).

Enfin, comme déjà évoqué un peu plus tôt, il serait à présent intéressant de continuer notre étude sur la partie TrustZone en explorant l’implémentation open source OP-TEE. La suite au prochain épisode…

Références

[1] P. FICHEUX, « Buildroot Vs Yocto Vs le reste du monde », Open Silicium n°20, octobre 2016 : https://connect.ed-diamond.com/Open-Silicium/OS-020/Buildroot-vs-Yocto-vs-le-reste-du-monde

[2] Secure Boot sur Sama5 : http://ww1.microchip.com/downloads/en/AppNotes/SAMA5D2-Linux-Secure-Boot-Application-Note-00002748A.pdf

[3] Secure Boot sur Artik : https://developer.artik.io/documentation/advanced-concepts/secure-os/secure-boot.html

[4] Documentation signature U-Boot : http://git.denx.de/?p=u-boot.git;a=blob;f=doc/uImage.FIT/signature.txt

[5] Documentation « verified boot » U-Boot : http://git.denx.de/?p=u-boot.git;a=blob;f=doc/uImage.FIT/verified-boot.txt


Sur le même sujet

Reprenez le contrôle ! Faites tourner Linux nativement sur vos téléphones et tablettes

Magazine
Marque
GNU/Linux Magazine
Numéro
237
|
Mois de parution
mai 2020
|
Domaines
Résumé

J’ai toujours été admiratif des personnes qui arrivent à faire tourner le noyau Linux sur de nouvelles plateformes. J’ai passé un nombre de soirées incalculables à étudier différents portages de Linux (Nintendo Switch, Nintendo 3DS, PlayStation...) et par la suite, j’ai moi-même passé beaucoup de temps à essayer de porter Linux sur tout ce qui me tombait sous la main. C’est une passion pour moi ! Et je vous propose de la partager. Dans cet article, je vais vous expliquer comment faire tourner vos distributions favorites (Debian, Arch, CentOS, Fedora, Gentoo…) nativement sur vos téléphones et tablettes Android.

Maîtriser la sécurité de son accès Internet avec OpenWRT

Magazine
Marque
Linux Pratique
Numéro
117
|
Mois de parution
janvier 2020
|
Domaines
Résumé

Nous allons voir dans cet article comment installer le système GNU/Linux embarqué de type OpenWRT. OpenWRT est un système GNU/Linux pour les matériels embarqués et pour les matériels de types routeurs et box. Il constitue donc la part essentielle pour se libérer des GAFA. C’est un système léger, rapide et performant pour administrer et contrôler son accès Internet. Ce système est optimisé pour la gestion des ressources et supporte de nombreux routeurs.

La liberté jusqu’au cœur du processeur avec RISC-V

Magazine
Marque
Hackable
Numéro
31
|
Mois de parution
octobre 2019
|
Domaines
Résumé
RISC-V est un jeu d’instructions 32 bits libre, développé initialement par l’université de Berkeley. Ce jeu d’instructions (ISA pour Instruction Set Architecture) est maintenant soutenu par une fondation regroupant quasiment tous les grands noms de l’industrie informatique. Dans cet article, nous allons décrire succinctement le concept de RISC vs CISC, puis nous expliquerons les bases du jeu d’instructions avec un peu de code assembleur, enfin nous terminerons par une description de quelques émulateurs et processeurs RISC-V disponibles aujourd’hui sur le marché.

Démarrez avec MicroPython

Magazine
Marque
GNU/Linux Magazine
Numéro
228
|
Mois de parution
juillet 2019
|
Domaines
Résumé
Pour mettre sur pied une preuve de concept ou un prototype électronique, il faut habituellement choisir une carte de développement et ensuite acquérir les outils de développement logiciel du fabricant. Ces logiciels s’appuient généralement sur les langages bas niveau comme le C, dont la maîtrise n’est pas accessible à tout le monde. MicroPython est a été mis sur pied pour permettre l’utilisation d’un langage de programmation haut niveau (Python) sur microcontrôleur, pour créer de magnifiques applications d’IoT.

Créez simplement votre périphérique matériel avec le langage C

Magazine
Marque
Hackable
Numéro
29
|
Mois de parution
avril 2019
|
Domaines
Résumé
Cet article présente la création et la mise en œuvre d’un périphérique matériel libre sous Linux embarqué pour la carte ZedBoard, utilisant un circuit FPGA Zynq. La synthèse de haut niveau HLS, à l’aide du langage C, sera utilisée ici pour la création du périphérique matériel.

Par le même auteur

Yocto/OE et Qt MQTT : le duo gagnant

Magazine
Marque
GNU/Linux Magazine
Numéro
223
|
Mois de parution
février 2019
|
Domaines
Résumé
Déjà évoqué dans diverses publications [1], le protocole Machine-2-Machine MQTT, se veut être le standard de communication pour les objets connectés (comprendre IoT ici !). En effet, de par sa légèreté et son efficacité, il en fait un protocole très prisé pour la gestion de la télémétrie en environnement embarqué.

U-Boot : à la découverte du « démarrage vérifié »

Magazine
Marque
GNU/Linux Magazine
Numéro
221
|
Mois de parution
décembre 2018
|
Domaines
Résumé
Sécuriser le processus de démarrage est la première étape afin de garantir qu’un système Linux embarqué est fiable. Cette technique, appelée Secure Boot, permet de s’assurer que seuls les logiciels authentifiés de manière cryptographique (bootloader, image noyau, etc.) pourront s’exécuter sur la cible, ceci afin de certifier par exemple qu’une mise à jour logicielle est sûre, qu’aucune faille de sécurité ne subsiste ni même qu’il existe une quelconque altération provenant d’une attaque externe malveillante.

Mise à jour d’un système Linux embarqué « Over The Air » : comment intégrer et utiliser « Mender » pour vos déploiements

Magazine
Marque
GNU/Linux Magazine
Numéro
219
|
Mois de parution
octobre 2018
|
Résumé
Afin de mieux comprendre les enjeux liés à la mise à jour d’un système embarqué connecté (nous parlons bien d’(I)IoT !), nous mettrons en œuvre dans cet article, Mender, une solution OTA permettant la gestion des déploiements sur des systèmes Linux embarqués.

À l’assaut du sous-système noyau « Industrial I/O » ! (et du QML … !)

Magazine
Marque
GNU/Linux Magazine
Numéro
215
|
Mois de parution
mai 2018
|
Domaines
Résumé
Dans cet article, nous allons développer un driver de périphérique en utilisant à la fois le bus i2c et le sous-système Industrial I/O. Le but final sera la mise en place d'une petite application Qt/QML permettant d'afficher les informations d'un capteur sur écran LCD.

i.MX7 : « Communication interprocesseur, donnons vie au Cortex M4 »

Magazine
Marque
GNU/Linux Magazine
Numéro
211
|
Mois de parution
janvier 2018
|
Domaines
Résumé
Nous allons découvrir dans cet article comment appréhender le développement sur plateforme i.MX7. Nous développerons un démonstrateur IoT en associant acquisition des données via Cortex M4, communication interprocesseur et consommation des données côté Cortex A7.

Mise en œuvre du protocole Modbus (RTU) sur WaRP7 via Qt5

Magazine
Marque
GNU/Linux Magazine
Numéro
208
|
Mois de parution
octobre 2017
|
Domaines
Résumé

Dans le milieu industriel, l'intégration de périphériques séries (RS232) est monnaie courante : moteur pas à pas, système de refroidissement, matériel scientifique... Malheureusement, le constat est à chaque fois accablant. En effet, de façon très récurrente on s’aperçoit que les protocoles de communication diffèrent en fonction du fabricant : protocole ASCII sans CRC pour l'un, protocole avec CRC16 pour l'autre et même CRC32 pour d'autres ! Donc aucune homogénéité. C'est pour cette raison que nous proposons d'étudier le protocole Modbus série dans sa forme la plus simpliste (RTU).