jchroot : chroot avec isolation améliorée
Vincent Bernat
Deux nouvelles fonctionnalités très intéressantes ont vu le jour lors de la sortie de Linux 2.6.24 il y a plus de trois ans : les espaces de noms pour les PID et le réseau. Cela permet de créer des processus qui ne partagent pas l’espace de noms correspondant avec les autres processus.
PID & points de montage#
Un espace de noms pour les PID est une vue des processus qui tournent
actuellement sur le système. Chaque espace de noms dispose de sa
propre numérotation des processsus qu’il contient (y compris les
processus qui se trouvent dans l’espace de noms des fils). Les autres
processus sont cachés et il n’est alors pas possible d’utiliser une
opération nécessitant leur PID, comme par exemple
kill(2)
. De plus, le premier processus de l’espace de noms
obtient le PID 1. S’il est tué, tous les processus du même espace sont
tués également. systemd, un gestionnaire de services
remplaçant init, utilise pourrait utiliser cette
fonctionnalité pour lancer chaque service dans un espace différent et
ainsi pouvoir les arrêter très facilement sans obtenir aucune
coopération de leur part (notamment, aucun besoin d’un fichier
contenant le PID du service) : quand il a besoin d’arrêter un service,
il lui suffit de tuer le PID 1 de l’espace de noms correspondant au
service (ce processus a un autre PID dans l’espace de noms global) et
le noyau s’occupera du reste. La page de manuel de clone(2)
(section CLONE_NEWPID
) contient des explications plus détaillées sur
le fonctionnement de cet espace de noms.
Mise à jour (08.2011)
En fait, systemd n’utilise pas cette fonctionnalité. Il utilise les cgroups qui permettent d’affecter un processus (et tous ses descendants) à un groupe, ce qui permettra de le retrouver facilement et d’y mettre fin.
Par exemple, voici le résultat de pstree -p
à l’intérieur d’un
espace de noms pour les PID :
# pstree -p bash(1)─┬─pstree(8) └─sleep(7)
Le PID de sleep
est 7. Dans l’espace de noms global, il s’agit du PID 31762. Dans les deux cas, il s’agit bien du même processus !
# ps -eo pid,command | grep sleep 31762 sleep 1000
Linux supporte depuis longtemps les espaces de noms pour les points de montage : chaque processus peut disposer de ses propres points de montage qui ne seront visibles que par les processus du même espace de noms ou par les processus des fils. Quand l’espace de noms est détruit (quand il ne contient plus aucun processus), le noyau fera le nettoyage lui-même sans qu’il soit nécessaire de démonter manuellement chacun des points.
chroot#
Une façon courante de tester des logiciels ou de travailler sur de
l’empaquetage est de construire une chroot : debootstrap
permet de
construire un système complet dans un répertoire donné et chroot
permet « d’entrer » dans ce nouveau système. Des outils comme
schroot
facilitent grandement la gestion de ce genre de choses.
Malheureusement, chroot
ne permet d’obtenir qu’une isolation au
niveau du système de fichiers. Si vous lancez un démon ou montez des
partitions à l’intérieur de la chroot, il faut noter toutes les
actions effectuées pour pouvoir les défaire une fois terminé.
Étendre un outil existant, comme schroot
pour utiliser les espaces
de noms pour les PID et les points de montage semble être la meilleure
solution pour contourner ces problèmes. J’ai créé le
bug #637870 à cet effet. Toutefois, schroot
permet de
lancer des processus dans une session existante. Quand cette session
est matérialisée uniquement par une chroot, ce n’est pas très
compliqué. Il suffit de se chrooter au même endroit et de lancer le
processus. Avec les autres types d’espaces de noms, il faut utiliser
l’appel système setns()
, nouvellement introduit, pour s’accrocher à
un espace de noms existant. Cependant, non seulement la Glibc ne
contient pas encore le wrapper pour cet appel système mais le noyau ne
contient pas encore le code nécessaire pour se rattacher à un espace
de noms pour PID.
D’autres outils existent, notamment lxc (Linux
Containers). Toutefois, il est surtout conçu pour lancer des systèmes
entiers (et non une commande ou un shell). J’ai créé un rapport de bug
pour indiquer que
lxc-execute
ne peut être utilisé pour lancer un shell
mais je n’ai pas obtenu de réponse satisfaisante pour le moment.
Mise à jour (08.2011)
systemd contient systemd-nspawn
, un utilitaire
qui correspond exactement à ce besoin. Toutefois, l’utilisation de
systemd est obligatoire pour l’exploiter. En dehors de cet aspect,
il s’agit d’un utilitaire plus avancé que celui que je décris par la
suite (mise en place des points de montage essentiels au bon
fonctionnement d’un système complet, configuration adéquate de la
console, transmission des signaux, …). En cas d’utilisation de
systemd, c’est sans doute l’outil à mettre en œuvre !
jchroot#
Programmer un clone de chroot
qui exploite les espaces de noms n’est
pas très difficile. Voici à quoi ressemble un tel programme :
int main() { pid_t pid; int ret; long stack_size = sysconf(_SC_PAGESIZE); void *stack = alloca(stack_size) + stack_size; /* New process with its own PID/IPC/NS namespace */ pid = clone(step2, stack, SIGCHLD | CLONE_NEWPID | CLONE_NEWIPC | CLONE_NEWNS | CLONE_FILES, NULL); /* Wait for the child to terminate */ while (waitpid(pid, &ret, 0) < 0 && errno == EINTR) continue; return WIFEXITED(ret)?WEXITSTATUS(ret):EXIT_FAILURE; } static int step2(void *a) { /* Mount /proc as an example */ mount("proc", "/proc", "proc", 0, ""); /* Chroot */ chroot("/srv/debian"); chdir("/"); /* Drop privileges */ setgid(1000); setgroups(0, NULL); setuid(1000); /* Exec shell */ execl("/bin/bash", "/bin/bash", NULL); }
Si on ajoute les options en ligne de commande, la gestion des erreurs et la capacité de lire un fichier au format fstab pour la définition des points de montage, on obtient jchroot. Voici un exemple d’utilisation :
$ cat fstab proc /proc proc defaults 0 0 sys /sys sysfs defaults 0 0 /home /home none bind,rw 0 0 /dev/pts /dev/pts none bind,rw 0 0 /var/run /var/run tmpfs rw,nosuid,noexec,mode=755 0 0 /etc/resolv.conf /etc/resolv.conf none bind,ro 0 0 $ sudo jchroot -f fstab -n test /srv/debian/squeeze /bin/bash # hostname test # ps auxww USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND root 1 0.0 0.0 19480 1888 pts/1 S 15:18 0:00 /bin/bash root 3 0.0 0.0 16536 1184 pts/1 R+ 15:18 0:00 ps auxww # /etc/init.d/postgresql start Starting PostgreSQL 9.0 database server: main. # exit $ ps auxwwc | grep postgres $
jchroot est un outil temporaire dont les fonctionnalités seront sans
doute intégrées à terme dans les outils pour créer des
chroot. D’ailleurs, je pensais à proposer un patch pour ajouter le
support des espaces de noms dans chroot(8)
. A-t’il une chance d’être
accepté ?