Copyright © 2007 Silas Brown
Copyright © 2007 Romain Dufils
Copyright © 2007 Joëlle Cornavin
Article paru dans le n°139 de la Gazette Linux de juin 2007.
Article publié sous Open Publication License. La Linux Gazette n'est ni produite, ni sponsorisée, ni avalisée par notre hébergeur principal, SSC, Inc.
Table des matières
Aussi ennuyeux que soient les processus « impossibles à arrêter » (unkillable), il y a des circonstances où on pourrait légitimement être amené à en créer un. Par exemple, si je lance un outil d'audit ou si je veux écrire un programme qui veille à ce que j'arrête de travailler même quand je suis vraiment très occupé, je pourrais être amené à ce que l'utilisateur root lui-même ne puisse l'arrêter.
Une première approche serait de supprimer tout simplement tout accès root au système ou, du moins, de les supprimer à des moments critiques, mais l'opération pourrait s'avérer très complexe si l'on doit encore être en mesure d'administrer le système et/ou qu'on ne peut pas indiquer avec certitude les moments qui ne seront pas critiques. Donc j'ai voulu une approche qui ne dépend pas du tout de la suppression des accès root.
Du fait que l'utilisateur root peut tout faire (y compris écrire des lots de commandes qui, une fois lancées, devanceront probablement tout contrôle d'intégrité périodique) et que, même modifier tous les outils de l'utilisateur root n'exclut pas d'en installer de nouveaux, la seule façon de réellement rendre un processus « impossible à arrêter »e est de modifier le noyau. Cependant il me fallait si possible une solution qui ne m'obligeait pas à « fouiller » dans le noyau, pour des raisons de portabilité et pour le garder simple.
Donc j'ai dû me résigner à l'objectif plus modeste de « créer un processus qui, s'il est arrêté, redémarre immédiatement ». Ce n'est pas trop difficile : modifier /etc/inittab
et faire en sorte que init(8) redémarre le processus à chaque fois qu'il meurt. Cependant, l'utilisateur root peut changer /etc/inittab
et peut également changer le fichier exécutable sur le disque, ce qui pourrait empêcher le processus de redémarrer correctement après qu'il a été arrêté.
Afin d'empêcher de tels changements, l'exécutable ainsi que /etc/inittab
devront aller sur un système de fichiers en lecture seule. Cependant, il ne suffit pas de prendre simplement un système de fichiers quelconque et de le monter en lecture seule, car l'utilisateur root peut tout simplement le remonter en lecture/écriture. On peut néanmoins créer une image ISO et la monter comme périphérique en boucle (loop device) ; ce montage peut, le cas échéant, ne pas être remonté en lecture/écriture et le changement du fichier ISO sous-jacent ne devra pas affecter le système de fichiers monté. Cependant, il sera toujours nécessaire d'empêcher root de le démonter et de monter quelque chose d'autre à sa place (ou de le démonter/remonter après avoir changé le fichier ISO).
On peut empêcher qu'un système de fichiers soit démonté en s'assurant qu'il est toujours occupé, c'est-à-dire qu'il y ait des processus dont le répertoire actuel se trouve dans ce système de fichiers. Néanmoins, cela n'arrête pas l'utilisation de umount -l
(démontage paresseux, lazy unmount) qui détache le système de fichiers de la hiérarchie et reporte le démontage proprement dit jusqu'à ce qu'il ne soit plus occupé ; root peut faire un démontage différé et monter quelque chose d'autre, tous les nouveaux processus verront alors la nouvelle version.
En fait ce n'est pas tout à fait vrai : si root utilise effectivement umount -l
, tous les processus tournant en ce moment dont le répertoire de travail actuel est situé dans l'ancien montage peuvent continuer à voir les fichiers de l'ancien montage et donc leurs processus fils, à condition que la liaison avec eux ait toujours lieu à partir du répertoire de travail actuel et non par un chemin absolu. S'ils utilisent un chemin absolu, alors ils verront le nouveau montage.
Donc si nous pouvons faire en sorte que init(8) fonctionne avec le fichier ISO monté comme son répertoire de travail actuel et qu'il exécute notre programme à partir du répertoire actuel à la place d'un chemin absolu, alors il ne devra pas être possible de changer le contenu de ce fichier ISO pour ce qui est de init(8), en tout cas pas sans redémarrer la machine ou sans « craquer » le noyau.
Pour ce faire, on change /sbin/init
en /sbin/init.orig
et on crée un nouveau /sbin/init
, un script shell :
#!/bin/bash mount /sbin/init.iso /init-mnt -o loop cd /init-mnt exec /sbin/init.orig $@
Il faudra également s'assurer que les futures mises à niveau de paquetages n'écrasent pas le script /sbin/init
avec le binaire d'origine.
Faites ensuite un chmod +x
sur ce script, créez le répertoire /init-mnt
et utilisez mkisofs pour que le fichier /sbin/init.iso
contienne tous les binaires que vous souhaitez exécuter. Vous pouvez lancer les scripts, mais assurez-vous que les fichiers binaires de l'interpréteur sont dans l'ISO et que /etc/inittab
les appelle à partir du répertoire actuel, par exemple :
AA:23:respawn:./python myscript.py
(Dans le cas de Python, on vérifiera qu'il lit ses bibliothèques standard à partir de l'ISO plutôt que d'un quelconque autre emplacement, sinon il pourrait y avoir une porte dérobée (back door) de cette façon.)
Bien qu'il doive être maintenant impossible pour root de changer votre script sans réamorcer, il lui est toujours possible de modifier /etc/inittab
et de demander à init de le relire. Sur la plupart des systèmes, init est codé « en dur » afin de charger /etc/inittab
par un chemin d'accès absolu, ce qui signifie qu'on ne peut pas s'en sortir sans correctif init, soit pour qu'il charge inittab
depuis le répertoire de travail actuel, soit pour l'empêcher une bonne fois pour toutes de relire inittab
pendant son exécution.
Vous pourriez utiliser un éditeur hexadécimal pour le binaire d'init et modifier la chaîne de caractères, mais il est probable que le système qui en résulte ne redémarrera pas. Il est plus judicieux de télécharger le paquetage des sources de votre distribution correspondant à sysvinit (ou tout autre nom que votre distribution lui donne), d'en faire son répertoire src
, de modifier paths.h
et de changer etc/inittab
en inittab
. Saisissez ensuite make et déplacez le binaire d'init qui en résulte pour le mettre à l'endroit de votre choix. N'oubliez pas de placer un fichier inittab
dans l'image ISO : c'est le fichier inittab
qui sera utilisé (et non /etc/inittab
), et la seule manière de le modifier est de changer le fichier ISO sous-jacent et de redémarrer.
Il y a encore un autre problème, cependant. Si votre processus est arrêté trop souvent, init refusera de le redémarrer pendant un moment. Vous pourriez le rendre plus agressif à chaque fois qu'il redémarre (par exemple, en mettant fin à tous les shells root et en désactivant le compte root momentanément, afin d'éviter qu'il soit arrêté à nouveau trop tôt), mais si root lance un script qui analyse périodiquement les processus en cours et arrête le vôtre et que la boucle du script est petite et rapide, alors votre processus ne sera très probablement plus en mesure d'obtenir jusqu'à l'arrêter.
Peut-être le moyen le plus simple de contourner ce problème consiste-t-il à traiter la condition respawning too fast
(relance répétitive trop rapide) plus sérieusement. Par exemple, on cherche dans le source d'init la partie qui affiche le message respawning too fast
(dans la version 2.86, elle se trouve dans init.c
) et d'ajouter exit(1);
après le point-virgule de fin d'instruction. Cela signifie que, si un processus se relance trop vite (par exemple parce que root fait tourner un script agressif pour arrêter l'exécution de votre processus), init s'arrêtera, ce qui aura pour conséquence une erreur système kernel panic
(panique du noyau) et un système inutilisable. Notez cependant que cela signifie aussi que le système « plantera » si une tâche inittab se relance trop vite à cause d'une faute de frappe, soyez donc prudent.
Dans cet article, nous avons constitué un moyen d'empêcher même l'utilisateur root de se débarrasser d'un certain processus sans redémarrer. Néanmoins, il subsiste encore la question du redémarrage lui-même. Comme on ne peut pas vraiment empêcher root de changer /sbin/init
ou /sbin/init.iso
et de redémarrer le système, notamment si on le fait rapidement, sans éteindre le système correctement, il vaut mieux que les redémarrages se voient bien. Si l'on veut compliquer les choses, cependant, on peut demander au programme de vérifier régulièrement les changements de stat()
du fichier /sbin.init*
, en prenant soin de le faire à partir du fil d'exécution (thread) principal (rappelez-vous que si votre programme devient « à fils multiples » (multi-thread), alors il sera peut-être possible d'arrêter certains des fils d'exécution (threads) tout en en préservant d'autres). Étant donné qu'il serait encore possible d'agir en démarrant depuis un support de secours cependant, voire peut-être sans démarrer depuis un support de secours dans certaines circonstances, cette approche n'est pas totalement sans défaut.
[L'élagage des privilèges root est un travail complexe. Comme le montre l'article, cette tâche est liée à la couche du système de fichiers. Des projets tels que Linux capabilities ou Security-Enhanced Linux abordent également le système de fichiers et méritent un coup d'œil.] | ||
-- René |
Silas Brown est un informaticien mal voyant, de Cambridge (Grande-Bretagne). Il utilise des versions très personnalisées de Debian Linux depuis 1999.
L'adaptation française de ce document a été réalisée dans le cadre du Projet de traduction de la Gazette Linux.
Vous pourrez lire d'autres articles traduits et en apprendre plus sur ce projet en visitant notre site : http://wiki.traduc.org/Gazette_Linux.
Si vous souhaitez apporter votre contribution, n'hésitez pas à nous rejoindre, nous serons heureux de vous accueillir.