Fonctionnement de l'image¶
Comment l'image de la borne est faite et protégée. Pour la construire, la tester, l'installer et la maintenir, voir Construction de l'image, Test avec QEMU, Installation initiale et Mises à jour du système.
L'image est construite avec mkosi à partir des paquets Debian 13 (trixie).
Partitions¶
L'image disque a deux emplacements système (mises à jour A/B) et une partition de données :
| Partition | Système de fichiers | Contenu |
|---|---|---|
| ESP | vfat, 512 Mo | systemd-boot et les UKI (pandora-box_<version>.efi), tous signés |
root, emplacement A (pandora-box_<version>) |
EROFS, lecture seule, 1 Go | tout le système sauf /var (environ 400 Mo utilisés) |
| root-verity, emplacement A | 64 Mo | arbre de hachage dm-verity du système de fichiers racine |
| root-verity-sig, emplacement A | 16 Ko | signature du hash racine |
root, root-verity, root-verity-sig, emplacement B (_empty) |
mêmes tailles | la version suivante (mises à jour A/B) |
données (pandora-box-data) |
ext4, 1 Go dans l'image | /var : rapports d'analyse, quarantaine, journaux, jeux de signatures signés (clamd y lit ses bases) |
La version de l'image est la date et l'heure de sa construction, en UTC,
comme le numéro de série d'un jeu de signatures : 20261005143700
(image/build.sh la fixe, --image-version=N la remplace). Elle figure dans
/etc/os-release (IMAGE_VERSION), dans les étiquettes de ses partitions
(pandora-box_<version>, _verity, _veritysig : 36 caractères au
maximum) et dans le nom de son UKI.
Le système de fichiers racine est en lecture seule par conception (EROFS ne
peut pas du tout être écrit) : le système et sa configuration ne changent
qu'avec une nouvelle image. Tout ce que la borne écrit est dans /var. La
partition de données est la dernière : à chaque démarrage, systemd-repart
l'agrandit pour remplir le disque (/usr/lib/repart.d/20-var.conf), puis
systemd-growfs agrandit son système de fichiers (x-systemd.growfs dans
/etc/fstab), si bien que l'image peut être écrite sur un disque de
n'importe quelle taille. Comme /etc est en lecture seule, l'identifiant de
machine est régénéré à chaque démarrage (un nouveau dossier de journal par
démarrage, dans les limites de taille de journald).
L'image contient :
- le noyau, systemd et udev, Python 3 et clamd (
clamav-daemon), sans service réseau :systemd-networkd,systemd-resolved, freshclam et l'analyse à l'accès de ClamAV sont désactivés (10-pandora-box.preset) ; - pandora-box dans
/usr/lib/pandora-box(/usr/bin/pandora-box), sa configuration dans/etc/pandora-box/pandora-box.tomlet son service, qui dessine l'interface graphique tactile sur la première console (framebuffer, voir Écran) ; - ni programme
loginni mot de passe root : aucune session interactive n'est possible.
Signature, Secure Boot et dm-verity¶
La chaîne de démarrage est vérifiée du firmware jusqu'à chaque bloc du système :
- avec Secure Boot, le firmware ne démarre systemd-boot que s'il est signé
avec une clé de sa base
db, et systemd-boot ne démarre qu'une UKI signée ; - l'UKI contient le noyau, l'initrd et la ligne de commande du noyau, qui
contient le hash racine dm-verity (
roothash=) : le modifier casse la signature ; - le système de fichiers racine est ouvert avec dm-verity : chaque bloc lu est vérifié par rapport à l'arbre de hachage, dont la racine est ce hash. Un bloc modifié ne peut pas être lu (erreur d'entrée-sortie, « data block ... is corrupted » dans le journal du noyau) ;
- avec Secure Boot, le noyau Debian active son mode lockdown
(
integrity) : même root ne peut pas modifier le noyau en cours d'exécution.
/var n'est pas protégé par dm-verity : il contient les données de la
borne, jamais de programmes ni de configuration.
L'image est signée avec image/mkosi.key et image/mkosi.crt (systemd-boot,
UKI, hash racine). Quand ils n'existent pas, image/build.sh génère une
paire de clés de développement, valable 10 ans, qui ne quitte jamais
l'ordinateur du développeur (ignorée par git). Une borne ne doit faire
confiance qu'aux clés de publication du projet, qui ne sont pas encore
définies : elles seront gardées hors du dépôt, et mkosi peut signer avec une
clé dans un jeton matériel (--secure-boot-key-source=provider:pkcs11).
L'image fait aussi confiance aux jeux de signatures signés avec
image/update.key : sa clé publique image/update.pem est installée dans
/usr/share/pandora-box/keys/ (sous dm-verity). image/build.sh génère une
paire de développement quand elles n'existent pas ; le profil de test signe
son jeu de signatures avec elle. Voir Signatures.
Sur une borne, le certificat est enrôlé dans le firmware à la place des clés
Microsoft (PK, KEK et db), pour qu'elle ne démarre rien d'autre. L'ESP
contient les clés au format attendu par le firmware (loader/keys/auto/) :
avec le firmware en mode configuration de Secure Boot (clés effacées dans la
configuration du firmware), systemd-boot les enrôle au premier démarrage
(secure-boot-enroll force). N'enrôler que le certificat du projet refuse
aussi les option ROM signées par Microsoft : un ordinateur dont l'affichage
ou le contrôleur de disque en a besoin (une carte graphique, par exemple) ne
la démarrerait pas. Les machines virtuelles enrôlent plutôt le certificat
avec virt-fw-vars (image/vm/machine.py).
systemd-boot n'a pas de menu (/efi/loader/loader.conf, timeout
menu-disabled) : il démarre l'entrée par défaut, la version la plus récente
qui n'est pas marquée mauvaise, et ne lit pas le clavier. Un clavier branché
sur la borne ne peut ni choisir une autre version, ni modifier la ligne de
commande du noyau (refusé avec Secure Boot de toute façon), ni éteindre la
machine, ni redémarrer dans la configuration du firmware. L'ESP ne fait pas
partie des mises à jour de l'image : ce fichier reste tel qu'installé.
Écran¶
La borne dessine son interface graphique dans le framebuffer du pilote
graphique (/dev/fb0), sans serveur d'affichage ni navigateur, et met la
première console (tty1) en mode graphique pour que le noyau n'écrive pas
de texte par-dessus : voir Interface. L'écran du matériel de
référence est un Waveshare 7 pouces HDMI LCD (1024x600). Le firmware du
ThinkCentre démarre en 1024x768, et le pilote Intel garde ce mode : le bas
de l'interface serait hors de l'écran. La ligne de commande du noyau règle
donc le mode de l'écran, video=1024x600M@60 (timings CVT, comme les
documente Waveshare) ; avec un autre écran, modifiez-le dans
image/mkosi.conf. Le pilote Intel garde tout de même le framebuffer du
firmware, assez grand pour ce mode : la borne ne dessine que sa partie
visible, de la taille de video=, et suit un changement de taille du
framebuffer. Elle journalise la taille et le format de pixel pour lesquels
elle dessine (display_started).
L'interface curses (kiosk.interface = "curses", mode administrateur)
écrit plutôt du texte sur la console : elle réduit de la même façon la
console à l'écran de video= (police 8x16 : 37 lignes, 128 colonnes), et
journalise la taille pour laquelle elle dessine (display_started,
display_resized).
Périphériques USB¶
Une borne ne doit utiliser que des périphériques de stockage USB : une clé qui est aussi un clavier (BadUSB) pourrait taper des commandes, un adaptateur réseau pourrait ouvrir un réseau.
- USBGuard (
/etc/usbguard/rules.conf) autorise les concentrateurs et les périphériques dont toutes les interfaces sont du stockage de masse ; il bloque tous les autres, y compris une clé qui a une interface de stockage et une autre. Les périphériques bloqués sont listés dans/var/log/usbguard/usbguard-audit.log. - Aucun périphérique n'est autorisé avant le démarrage d'USBGuard : la
ligne de commande du noyau fixe
usbcore.authorized_default=0. Si USBGuard ne démarre pas, aucun périphérique USB n'est utilisable. - Pilotes retirés de l'image (
KernelModulesExclude=dansimage/mkosi.conf) : adaptateurs réseau USB, sans-fil, Bluetooth, adaptateurs série et modems USB, ainsi que les pilotes staging et batman-adv, qui ramèneraient les modules sans-fil comme dépendances. De tels périphériques n'ont pas de pilote même s'ils étaient autorisés. -
L'USB HID est gardé pour l'écran tactile du matériel de référence, un Waveshare 7 pouces HDMI LCD (C), autorisé par sa propre règle :
allow id 0eef:0005 serial "220211" name "WS170120" with-interface 03:00:00 via-port "1-8"son contrôleur tactile USB (eGalax) avec son unique interface HID générique (un clavier a
03:01:01), sur le port interne du boîtier, cet exemplaire uniquement. Un périphérique qui copie ses identifiants sur un autre port, ou qui ajoute une interface clavier, est bloqué ; tout clavier et toute souris aussi. Pour un autre exemplaire ou un autre port : adaptez la règle d'après la ligne que montre le journal d'audit quand il est bloqué. Le noyau (mousedev) transforme un appui en clic gauche sur/dev/input/mice, que la borne lit pour confirmer un nettoyage.L'image de test autorise la tablette tactile de la machine virtuelle (QEMU
usb-tablet, même interface) par sa propre règle (/etc/usbguard/rules.d/50-vm-test.conf) : le test confirme un nettoyage par un appui. Le clavier des machines virtuelles n'est pas un périphérique USB.
Durcissement du système¶
D'après les recommandations de configuration d'un système GNU/Linux de l'ANSSI :
- Réglages du noyau (
/usr/lib/sysctl.d/90-pandora-box.conf) : journaux et adresses du noyau masqués, pas de ptrace, pas de kexec, pas d'eBPF, d'espaces de noms utilisateur ni d'userfaultfd sans privilèges, io_uring désactivé, pas de SysRq, pas de core dumps, liens et FIFO protégés, ni routage IP ni redirections. - Ligne de commande du noyau : mémoire initialisée à l'allocation et à
la libération, pas de fusion des slabs, allocateur de pages et pile du
noyau aléatoires, pas de vsyscall historique, pas de debugfs, lockdown en
mode
confidentiality(Secure Boot n'active queintegrity: même root ne peut pas lire la mémoire du noyau), IOMMU en mode strict et pas de DMA avant le démarrage du noyau (attaques DMA par les ports). - Pare-feu (
/etc/nftables.conf) : chaque paquet est rejeté, en entrée comme en sortie, sauf sur l'interface de bouclage. - Aucune session : pas de console de connexion sur les écrans (
getty@etgetty-staticdésactivés), pas de programmelogin, pas de mot de passe root, Ctrl-Alt-Suppr masqué. - Bac à sable de clamd (
clamav-daemon.service.d/50-pandora-box.conf) : pas de réseau, système en lecture seule, pas de périphérique, filtre d'appels système,MemoryDenyWriteExecute(le clamd de Debian interprète les signatures bytecode, sans JIT) ; il tourne sous l'utilisateurclamav. - Service de la borne (
packaging/systemd/pandora-box.service) : système en lecture seule, filtre d'appels système, capacités limitées. Il gardeAF_NETLINK(événements udev), les capacités nécessaires pour lancer le bac à sable des processus d'analyse (espaces de noms, changement d'utilisateur, interface de bouclage des processus) et ne peut pas avoirMemoryDenyWriteExecuteniRestrictSUIDSGID: le premier s'appliquerait aussi aux processus YARA-X, qui compilent les règles en code natif, le second refuse un appel système dont bubblewrap a besoin. - Modules du noyau retirés : protocoles réseau que la borne n'utilise
jamais (DCCP, SCTP, RDS, TIPC) et FireWire, un autre moyen d'atteindre la
mémoire de la borne, en plus des pilotes réseau USB, Bluetooth et Wi-Fi.
Les protocoles et FireWire sont aussi listés dans
/etc/modprobe.d/pandora-box.conf, où les outils d'audit les cherchent. - Journal d'audit (
/etc/audit/audit.rules, auditd) : chargement de modules du noyau, montages (sauf ceux de systemd et de bubblewrap), chaque programme lancé, ptrace, changements de l'horloge, des jeux de signatures et des mises à jour de l'image en attente. Les règles sont verrouillées (-e 2) : les modifier demande un redémarrage. Elles sont chargées parauditctl -R(augenrulesécrit dans/etc, en lecture seule). Le journal est dans/var/log/audit/audit.log, par exempleausearch -k signatures -i.
Le profil de test inclut lynis, lancé par le test automatisé : indice de
durcissement 73, aucun avertissement. Ses suggestions restantes ne
s'appliquent pas à une borne sans session (politique de mots de passe,
bannières, outils de paquets, fail2ban) ou viendront plus tard
(journalisation distante, phase 5).
Bac à sable des processus d'analyse¶
Les processus d'analyse examinent des fichiers hostiles avec plusieurs
moteurs (libmagic, YARA-X, le client clamd...) : ils sont supposés
compromettables (scan.sandbox, src/pandora_box/sandbox.py).
- Un moteur par processus : un moteur compromis (un fichier piégé qui
exploite YARA-X ou libmagic) ne peut pas falsifier le résultat d'un autre
moteur. La borne calcule elle-même les empreintes de chaque fichier,
interroge le processus de chaque moteur d'empreintes (MalwareBazaar,
Hashlookup) avec les empreintes seulement, puis passe le fichier à un
processus de chaque moteur de contenu (ClamAV, YARA-X), et combine
elle-même leurs résultats ; un processus ne rapporte que le résultat de
son propre moteur. Les moteurs de contenu ont chacun
scan.workersprocessus, les moteurs d'empreintes un seul.scan.workers = "auto"(par défaut) analyse un fichier par processeur en même temps, autant que la mémoire le permet (1,5 Go pour clamd et le système, puis 256 Mo chacun, surtout pour les règles YARA) : 6 sur le ThinkCentre M720q (6 cœurs, 8 Go), 1 ou 2 sur un ordinateur avec 2 Go. - Pas d'accès à la clé : la borne ouvre chaque fichier de façon sûre (aucun lien suivi, le fichier de l'inventaire) et passe le descripteur ouvert aux processus des moteurs de contenu, qui n'ouvrent jamais un fichier de la clé ; les processus des moteurs d'empreintes ne reçoivent jamais le fichier.
- bubblewrap : nouveaux espaces de noms PID, IPC, UTS, cgroup et réseau
(aucune interface réseau à part le bouclage) ; un système de fichiers qui
ne contient que
/usr, quelques fichiers de/etc, les dossiers de signatures des moteurs activés (en lecture seule), le cache de YARA-X et le dossier du socket de clamd : ni point de montage de la clé, ni rapports, quarantaine, journaux ou configuration. - Utilisateur dédié
pandora-box-scan(packaging/sysusers/), adopté parsetpriv: aucune capacité, aucun groupe supplémentaire,no_new_privs. - Filtre d'appels système (
src/pandora_box/seccomp.py), installé une fois les moteurs chargés : ni exécution de programme, ni débogage, ni montage, ni espace de noms, ni eBPF, ni module du noyau, ni io_uring, ni socket réseau... - Réponses non fiables : les processus répondent en JSON (jamais en
pickle), avec une taille limitée et validée
(
src/pandora_box/protocol.py) ; le chemin et la taille du fichier viennent toujours de la borne. Une réponse malformée tue le processus et le fichier est déclaré en erreur.
bubblewrap tourne en root (le service de la borne), si bien que les espaces de noms utilisateur sans privilèges restent désactivés.
Le test en machine virtuelle vérifie ces réglages et affiche l'exposition
des services mesurée par systemd-analyze security (de 0 à 10, plus c'est
bas, mieux c'est) : environ 1,6 pour clamd, 3,8 pour la borne (qui lance le
bac à sable des processus d'analyse), 2,8 pour USBGuard.
Image noyau unifiée¶
L'UKI (environ 40 Mo) contient le noyau, l'initrd de l'image
(image/mkosi.images/initrd/, sans la base matérielle d'udev, les
conversions de jeux de caractères, Perl, la documentation et networkd) et un
initrd de modules du noyau. Celui-ci ne contient que ce qui monte le
système de fichiers racine (KernelModulesInitrdInclude= dans
image/mkosi.conf) : contrôleurs de disque (NVMe, SATA, Intel VMD, eMMC,
virtio), dm-verity, EROFS et CRC32C, qu'EROFS charge par son nom. Les autres
modules sont chargés depuis le système de fichiers racine. Un ordinateur
dont le contrôleur de disque n'est pas dans cette liste ne trouve pas son
système de fichiers racine : ajoutez-y son module.