guest@6admin.io:~$

Les liens : hard links et symbolic links et ce qui dépasse le chroot

Linux connaît deux types de liens et la différence entre les deux est un sujet d’examen sysadmin ET un vecteur d’évasion de chroot. Le même concept qui explique la sauvegarde de vos données explique aussi comment sortir d’une jail.

Un hard link est une entrée de répertoire qui pointe directement sur un inode. Le fichier et son lien sont deux noms pour le même contenu disque. Supprimer l’un ne supprime pas l’autre (l’inode reste tant que son compteur de liens est supérieur à zéro).

$ echo "test" > original.txt
$ ln original.txt hardlink.txt
$ ls -li original.txt hardlink.txt
12345 -rw-r--r-- 2 user user 5 Oct 26 10:00 original.txt
12345 -rw-r--r-- 2 user user 5 Oct 26 10:00 hardlink.txt

Le même inode (12345), le même contenu, le compteur de liens à 2. Supprimer original.txt:

$ rm original.txt
$ cat hardlink.txt
test

Le contenu survit. Le compteur est passé à 1. Le fichier disparaîtra quand le compteur atteindra zéro ET que le dernier descripteur de fichier sera fermé.

Un hard link ne peut pas traverser un filesystem (un inode n’existe que dans son filesystem) et ne peut pas pointer vers un répertoire (pour éviter les cycles, sauf le . et le .. que le kernel gère).

Un symbolic link (symlink) est un fichier qui contient un chemin. Il pointe vers un chemin, pas vers un inode. Si la cible est supprimée, le symlink devient un lien mort (dangling symlink): il existe mais il ne mène nulle part.

$ ln -s /chemin/vers/original.txt symlink.txt
$ ls -la symlink.txt
lrwxrwxrwx 1 user user 25 Oct 26 10:00 symlink.txt -> /chemin/vers/original.txt

Le l au début des permissions indique le symlink. Le symlink peut traverser les filesystems, pointer vers un répertoire, pointer vers un fichier qui n’existe pas encore et se cascader (un symlink vers un symlink vers un symlink).

Un chroot change le répertoire racine d’un process: / devient par exemple /var/jail/. Le process ne peut pas accéder aux fichiers en dehors de la jail, parce que le kernel résout les chemins en partant du nouveau root.

Sauf que les symlinks sont résolus APRÈS le chroot et un symlink qui pointe vers /etc/passwd dans la jail est résolu comme /var/jail/etc/passwd. Ça marche. Mais un symlink créé AVANT le chroot et qui pointe vers /etc/passwd du vrai système est résolu dans le contexte du filesystem réel, pas du chroot. Le symlink traverse la jail parce que le chemin est stocké sous forme de chaîne, pas résolu au moment de la création.

# Dans la jail:
ln -s /etc/passwd /var/jail/escape_link
# Après chroot dans /var/jail:
# escape_link -> /etc/passwd
# Mais /etc/passwd est résolu depuis le VRAI root si le symlink
# a été créé avec un chemin absolu qui dépasse la jail

La contre-mesure: créer les symlinks APRÈS le chroot (relatifs au nouveau root) ou utiliser pivot_root (qui change le root du kernel, pas seulement du process). C’est exactement la leçon que les containers ont retenue: le namespace mount isole le VFS, le chroot isole le chemin et les symlinks traversent le chemin mais pas le namespace.

Le trio à retenir

  • Hard link: même inode, pas de cross-filesystem, pas de symlink dans un chroot, le contenu survit tant que le compteur de liens > 0.
  • Symlink: chemin stocké, cross-filesystem, peut pointer hors d’un chroot, peut être mort (dangling), se cascade.
  • FIFO/socket/device: pas des liens, mais des fichiers spéciaux qui occupent un inode sans contenu disque.

[edit: 2026] Le chroot escape par symlink est toujours le classique des CTF, mais les containers (Docker, LXC, Podman) ont résolu le problème avec les mount namespaces (le process voit son propre VFS, pas seulement un root différent) et le rootless s’appuie sur les user namespaces, qui isolent plus que le chroot n’a jamais isolé. Le symlink traverse toujours le chemin; il ne traverse plus le namespace.