Linux Embedded

Le blog des technologies libres et embarquées

Une réflexion sur les rootfs en read-only

Introduction

Dans le monde de l'embarqué, certaines pratiques perdurent. Des habitudes héritées du passé, développées pour des raisons tout à fait valides, se retrouvent ancrées dans des pratiques bien au-delà de leur justification initiale.

Parmi celles-ci, l'habitude de toujours monter systématiquement le système de fichiers racine en mode lecture-seule, puis de le remonter en mode écriture, perdure. 

Dans cet article nous allons remonter aux origines des pratiques de montage des root-filesystems et comprendre s'il est pertinent de continuer d'utiliser ce mode de fonctionnement ou s'il introduit de la complexité inutile dans nos systèmes actuels.

Techniquement : comment le noyau trouve son système de fichiers

Lorsqu'un processeur est mis sous tension, le premier code exécuté est celui d'un ou plusieurs firmwares chargés de préparer le terrain pour le noyau Linux. Le rôle de ces firmwares couvre la préparation du hardware essentiel, l'authentification si nécessaire du noyau Linux et la mise en place de l'environnement que le noyau attend. 

Pour schématiser, nos firmwares:

  • Initialisent le hardware essentiel (Horloges, RAM, Pinmux etc.)
  • Trouvent, vérifient et chargent en mémoire le noyau linux
  • Chargent en mémoire des informations secondaires dont le noyau peut avoir besoin (initramfs, devicetree,  ligne de commande noyau)
  • Positionnent certains registres pour pointer vers des informations essentielles au noyau (adresses de l'initramfs, du devicetree et de la ligne de commande en RAM)
  • Passent la main au noyau linux.

Par conséquent, lorsque le noyau Linux prend la main, celui-ci n’a donc aucune connaissance directe de l’emplacement du root-filesystem. Le processeur fonctionne, la RAM est disponible mais les périphériques de stockage ne sont pas forcément initialisés.

Le noyau doit alors commencer par initialiser tout ce qui est nécessaire pour accéder à son root-filesystem. Une fois ce dernier accessible, le noyau lance le programme init  - fourni par le rootfs - qui prend ensuite la main et démarre le reste du système. 

Nécessairement, pour pouvoir monter son rootfs, le noyau doit savoir où chercher. 
Quel disque? Quelle partition sur le disque? 

De plus, 

  1. Si le montage est complexe, il doit disposer de toutes les instructions nécessaires (RAID, Chiffrement)
  2. S'il s'agit d'un système de fichier réseau, il doit pouvoir gérer toute la complexité correspondante (configuration IP, SSL). 

Traditionnellement, cette configuration est fournie au noyau via la ligne de commande  (le paramètre root=<config> fournit cette information).

Si cette approche reste suffisante pour des cas simples, elle s'avère inutilisable pour les cas complexes.

Au delà de cette complexité, les premiers systèmes Unix sont rapidement tombés sur une difficulté supplémentaire : la vérification du système de fichier racine.

Pourquoi le montage read-only : Le problème historique du fsck (filesystem checker)

Pour les partitions autres que la partition racine, la bonne pratique consiste à vérifier la cohérence des données avant de monter les partitions. Un appel à fsck est fait par le script de montage avant le montage lui-même.

Mais pour la partition racine cette vérification est problématique. En effet le programme fsck lui-même se trouve sur la partition racine. On ne peut donc pas l'utiliser avant d'accéder à cette partition.

Afin de minimiser ce problème, les systèmes UNIX traditionnels montent la partition racine en mode lecture seule avant de basculer dessus. Ensuite, ce sont les différents scripts d'initialisation qui sont chargés d'utiliser fsck pour vérifier la cohérence de la partition racine puis de remonter cette partition racine en mode écriture.

La vérification de cette cohérence est donc faite alors que le système de fichier est déjà monté en mode lecture seule. C'est plus prudent que de l'avoir monté en mode écriture, mais cela reste complexe et fragile.

La bonne pratique actuelle : initramfs

De nos jours, un nouvel outil beaucoup plus puissant est utilisé pour contourner ces difficultés : l'initramfs.

Un initramfs est un système de fichiers minimal chargé en RAM par le firmware avant le démarrage du noyau et dont l'adresse est passée au noyau via un registre dédié. Comme l'initramfs est en RAM et que la RAM est configurée par le firmware, il est très facile pour le noyau de trouver et charger l'initramfs comme rootfs initial.

Une fois l'initramfs chargé en mémoire, le programme init qu'il contient est exécuté et prend la main pour organiser la suite des événements.

L'initramfs ne constitue généralement pas le rootfs final mais un rootfs temporaire chargé de trouver le rootfs définitif. L'init de l'initramfs exécute alors toutes les opérations nécessaires pour localiser, vérifier et monter le véritable root-filesystem.

Il peut notamment :

  • Lire des fichiers de configuration complexes
  • Demander un mot de passe pour déchiffrer (donc interagir avec l'utilisateur)
  • Accéder à une enclave de sécurité pour vérifier l'intégrité du rootfs (TPM, Trusted Zone...)
  • Charger des modules noyaux disponibles dans l'initramfs
  • Monter plusieurs disques pour gérer des partitions complexes (RAID, LVM)
  • Gérer certaines actions liées à la mise à jour et la redondance (bascule A/B)
  • Trouver un serveur et se connecter pour monter une partition réseau
    • Savoir gérer des connexions sécurisées (TLS/SSL) avec toute la complexité de gestion des certificats
    • Savoir configurer le réseau (adresse IP)
    • Contenir une éventuelle configuration de proxy, firewall etc.
  • Une fois le rootfs trouvé, l'initramfs peut avoir son propre fsck et donc vérifier le rootfs avant que celui-ci ne soit montée (ce qui est plus robuste)

En dernière action, l'initramfs utilise l'appel système switch-root pour basculer sur le root-filesystem définitif. 

Le véritable programme init prend alors la main et le processus de démarrage peut continuer normalement

Comment avoir le beurre et l'argent du beurre

Il semble clair qu'un initramfs devient rapidement indispensable dans un système embarqué récent, ne serait-ce que pour implémenter des mécanismes de boot sécurisé.

Il est également clair que basculer sur un rootfs en lecture seule qui basculera en mode écriture dans la séquence de boot n'a aucun sens dès lors qu'on dispose d'un initramfs.

Néanmoins avoir un initramfs qui bascule sur un rootfs en lecture seule peut se justifier dans certains cas :

  • Certains systèmes n'ont simplement pas besoin d'écrire sur le disque dans leur utilisation normale.
  • Lorsqu'une vérification d'intégrité est mise en place, elle impose souvent une partition racine en lecture seule
  • Les méthodes les plus fiables pour implémenter un retour aux paramètres usine se font en utilisant une partition de base en lecture seule et en stockant sur une partition séparée les éléments modifiables. Ces derniers sont simplement effacés lors du retour aux paramètres usine.

A ce jour, il n’existe pas de méthode universelle pour concevoir un système "globalement en lecture seule" tout en conservant des éléments modifiables. 

Toutefois, Linux fournit un certain nombre d'outils permettant d'obtenir ce résultat.

L'approche "Lecture seule avec exception"

C'est l'approche la plus courante dans l'embarqué : la partition racine est en lecture seule, mais une partition "overlay" est utilisée pour stocker des modifications apportées à cette base. Selon les besoins, l'overlay peut ne concerner que certains fichiers ou répertoires précis (configuration réseau par exemple), l'ensemble du répertoir /etc ou l'ensemble du rootfs.

Il est généralement nécessaire de mettre en place les montages d'overlay dans l'initramfs afin que tous les montages complexes soient déjà en place lors de la bascule sur le vrai root-filesystem définitif.

Cette approche offre une excellente robustesse : le système de base reste immuable, ce qui limite fortement les risques de corruption, tout en permettant de disposer d’un mécanisme de réinitialisation simple et fiable.

Yocto fournit un moyen de faire un overlay complet dans l'initramfs ainsi que la possibilité de faire un overlay sur un répertoire particulier.

L'approche "Ecriture avec exception"

Sur les systèmes les plus complexes, inventorier l'ensemble des fichiers pouvant être modifiés peut s'avérer complexe. Il est alors intéressant de partir sur une approche inverse de la précédente : monter le rootfs en écriture tout en restreignant finement les capacités d'écriture de certaines applications jugées peu sûres.

Cette stratégie est pertinente pour les applications métiers (dont on maîtrise mieux les actions) ainsi que les applications particulièrement exposées à la malfaisance (applications web).

Dans ce contexte, la méthode la plus simple consiste à ne pas agir au niveau des montages mais plutôt au niveau des services systemd. Sans entrer dans les détails ici, nous vous recommandons de consulter cette page de man de systemd.exec.

Un mot de (cyber)sécurité

Il peut être tentant d'utiliser un filesystem monté en lecture seule comme élément de sécurité. 

À première vue, l’impossibilité de modifier des fichiers semble constituer un moyen efficace de limiter les actions d’un acteur hostile.

En réalité, cette protection reste illusoire. En effet, monter un filesystem en lecture seule n'interdit pas à un malfaisant qui aurait pris le contrôle du système de remonter le filesystem en mode écriture. 

L’utilisation d'un filesystem intrinsèquement en lecture seule (EROFS, UBIFS) n'est que marginalement mieux car un malfaisant suffisamment privilégié peut écrire directement dans le périphérique bloc, sans passer par le driver de filesystem

Enfin, si le malfaisant a un accès physique, il est toujours possible de modifier le périphérique de stockage sans démarrer le système.

Par conséquent, si l'utilisation d'un rootfs en lecture seule constitue un élément important de robustesse et peut avoir un rôle à jouer dans l'implémentation d'un système de mise à jour, il ne doit pas être considéré comme une mesure de sécurité à part entière.

La garantie de sécurité repose avant tout sur l’intégrité de la chaîne de démarrage (boot sécurisé). 

Il est donc essentiel de ne pas confondre ces deux problématiques.

Conclusion

La gestion du montage du rootfs est un sujet complexe, dont les enjeux et les différentes approches possibles restent parfois méconnus.

Nous avons néanmoins vu que l'utilisation d'un initramfs est aujourd’hui quasiment incontournable. 

Il constitue la base de toute gestion complexe du rootfs, en particulier dans les cas de montages complexes ou de mise en œuvre d’un boot sécurisé. 

Au-delà de cet aspect, l'utilisation d'un rootfs monté en lecture seule est un outil important, parmi d'autres, pour améliorer la robustesse du système. Cette approche est par ailleurs relativement facile à mettre en œuvre avec les build-system récents.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée.