- Définit l’initrd comme une unité de programme que le noyau interprète et exécute directement, et réinterprète Linux comme une sorte d’interpréteur
- Construit une distribution Linux récursive qui redémarre sur elle-même à l’aide de
kexec, base64 et cpio, l’initrd se réexécutant lui-même
- Si le script
/init est configuré pour afficher sa propre image cpio, on obtient un initrd auto-répliquant de type Quine
- Explique, via la structure d’exécution ELF,
ld.so et binfmt_misc, une hiérarchie d’interpréteurs qui se prolonge jusqu’au noyau
- En utilisant
kexec ou QEMU, il devient possible d’exécuter récursivement un autre Linux au-dessus de Linux de manière tail-récursive, étendant expérimentalement les frontières entre noyau, virtualisation et interpréteur
Rétro-ingénierie de rkx.gz et structure d’un initrd auto-récursif
- La commande
curl https://astrid.tech/rkx.gz | gunzip | sudo sh télécharge et exécute un script shell de 20 Mo encodé en base64
- Le script vérifie les privilèges root et la présence de
kexec, base64 et cpio
- Il décode les données base64 pour créer une archive cpio nommée
r, puis en extrait une image de noyau nommée k
- Il utilise
kexec pour charger et exécuter k comme noyau et r comme ramdisk
- À l’intérieur de
r.cpio se trouvent /bin, /init et le fichier k, où k est une image du noyau Linux 6.18.18 et /init un script shell
/init monte /proc, regroupe ensuite le système de fichiers courant en cpio dans /r, puis relance /k et /r via kexec
- On obtient ainsi une distribution Linux récursive qui redémarre continuellement sur elle-même
Le noyau Linux vu comme un interpréteur
- L’initrd n’est pas qu’un simple ramdisk de démarrage, mais peut être vu comme un programme que le noyau Linux interprète et exécute
- Comme
curl | sh ou python3 script.py, l’initrd est lui aussi une forme de programme d’entrée exécuté par le noyau
- Le noyau Linux fonctionne donc comme un interpréteur de l’initrd
- Cette structure ressemble à une optimisation de récursion terminale (tail-call optimization)
kexec ne remplace pas le noyau précédent en l’écrasant, mais charge et exécute le nouveau dans un nouvel espace mémoire
- Chaque noyau ne conserve pas l’état précédent et est remplacé par une nouvelle « stack frame »
Quine et auto-réplication de l’initrd
- Un Quine désigne un programme qui affiche sa propre source
- Si le script
/init exécute cat /r à la fin, il affiche un cpio identique à lui-même
- Dans ce cas, on obtient un Quine de l’interpréteur initrd de Linux
- Tous les fichiers existent en RAM sur
tmpfs, donc aucune E/S disque réelle n’a lieu
ELF, ld.so et la hiérarchie des interpréteurs
- Un exécutable ELF inclut dans son en-tête le chemin de l’interpréteur (
ld-linux-x86-64.so.2)
- Lors de l’exécution, le noyau lance d’abord
ld.so, puis ld.so charge les bibliothèques dynamiques de l’ELF avant d’exécuter le programme
- On peut donc aussi voir ELF comme une sorte de langage interprété
/bin/sh est interprété par ld.so, et ld.so est lui-même interprété directement par le noyau
ld.so étant un ELF lié statiquement, le noyau peut l’exécuter directement
- Cela forme ainsi le cas de base de la hiérarchie des interpréteurs
Exécution de CPIO via binfmt_misc
- Avec
binfmt_misc, il est possible d’exécuter via un interpréteur désigné des fichiers possédant des octets magiques spécifiques
- On peut enregistrer comme interpréteur un script qui exécute un CPIO comme initrd via QEMU
- QEMU démarre une machine virtuelle à l’aide du noyau et de l’initrd spécifiés
- En conséquence, l’interpréteur du fichier CPIO devient le noyau Linux lancé par QEMU
Interpréteurs récursifs et « la boucle la plus étrange »
- Un interpréteur fondé sur QEMU crée une nouvelle stack frame d’environnement Linux
- Dans cette structure, Linux en exécute un autre, et l’imbrication peut se poursuivre jusqu’aux limites de la mémoire
- En le remplaçant par un interpréteur fondé sur
kexec, on peut obtenir une exécution récursive de Linux avec optimisation d’appel terminal
- Si l’on enregistre
binfmt_misc dans /init puis qu’on configure l’exécution de /r,
on obtient un initrd qui s’exécute lui-même
/r est le prochain processus init au format CPIO et, lors de son exécution, il se réinterprète à nouveau
Conclusion
- L’initrd n’est pas un simple outil de démarrage, mais une unité de programme interprétée par le noyau Linux
- Avec
kexec et binfmt_misc, il devient possible d’exécuter Linux lui-même récursivement comme un interpréteur
- Cette structure est un concept expérimental qui brouille les frontières entre noyau, virtualisation, interpréteur et programme auto-répliquant
- Le code source associé est publié dans le dépôt GitHub ifd3f/rekexec
2 commentaires
L’ignorance donne du courage, dit-on… J’aimerais qu’on évite ce genre d’articles.
Réactions sur Hacker News
J’ai souffert en lisant ce billet à cause du trop grand nombre de malentendus
Une archive cpio n’est pas un système de fichiers. L’auteur utilise initramfs, qui repose sur tmpfs. Linux peut extraire un cpio vers tmpfs. Une archive de fichiers et de répertoires n’est pas, en soi, un programme
Ce n’est pas parce que deux choses se ressemblent qu’elles sont identiques. Un programme binaire s’exécute sur le CPU et, s’il y a un interpréteur, il est caché dans l’environnement matériel. Cela sort du périmètre du noyau
Pour exécuter un script shell, il faut un shell qui l’interprète. L’auteur passe ce point sous silence et confond le noyau avec le programme shell
Linux peut être compilé sans initramfs ni ramdisk, et peut quand même exécuter un userland situé sur un système de fichiers
L’expression « Linux initrd interpreter » est une description vraiment erronée
ld.socharge un ELF en mémoire puis exécute son point d’entrée, et le fait que le noyau décompresse un initramfs puis exécute son point d’entrée, relèvent d’une idée assez similaireTous les OS ne jouent-ils pas le rôle d’interpréteurs de code machine avec les privilèges du noyau ?
Ce billet fonctionne si on prend « Linux est un interpréteur » comme un modèle mental, mais c’est faux si on le prend au pied de la lettre
Il est plus juste d’y voir le rôle du noyau comme une orchestration des formats exécutables comme ELF, les scripts shebang ou initramfs, plutôt qu’une interprétation au niveau des instructions CPU. La confusion semble venir du mélange entre deux sens du mot « interpréteur »
L’essentiel n’est pas de savoir si la métaphore est correcte, mais de montrer à quel point la notion d’« exécution » dépend de son environnement
« Tout est interpréteur ? »
Le Theta Combinator de Turing
Dans un précédent billet de la série, l’auteur disait avoir fabriqué lui-même une image VPS parce qu’il ne voulait pas utiliser le stockage objet de Contabo
Je pense qu’il existe un juste milieu entre passer 50 heures pour économiser 1,50 $ par mois et dépenser 250 000 $ en tokens.
Si l’on n’arrive pas à assumer les coûts d’infrastructure, le problème relève peut-être davantage de facteurs sociaux que de compétences techniques. S’acharner à faire tourner Doom avec
curlne me semble pas productifDans
man ld.so, il est explicitement indiqué que le linker dynamique stocké dans la section.interpd’un ELF est exécuté. Le nom même de la section est intéressantLinux est très utile comme interface programmable. Windows le permet aussi, mais Linux me semble plus adapté
Je pense que l’interface graphique de Windows est meilleure, mais GNOME ou KDE me mettent aussi mal à l’aise. J’utilise donc fluxbox, icewm, et parfois xfce ou mate-desktop. Ces jours-ci, je préfère les environnements simples et rapides. Je fais l’essentiel de mon travail en ligne de commande et en éditant du code