Environnement : Arch Linux.
Commandes de base
date
uname -a
lsns --version || lsns -V
unshare --version
ip -V
id -u
cat /proc/$$/status | grep NoNewPrivs1. Rappels : lsns et comptage des namespaces
Objectif
Lister les namespaces en cours d’utilisation et compter les types distincts.
Commandes
lsns --output=NS,TYPE,NPROCS,PID,USER,COMMAND > lsns.before.txtObservation
Sur ma session utilisateur, j’observe 8 types de namespaces :
mnt,pid,net,ipc,uts,user,cgroup,time.
TIP
Le nombre observé dépend du kernel et de la visibilité (root vs user). Un utilisateur non-root voit déjà une grande partie des namespaces.
Évolution des namespaces
On fige l’état des namespaces, puis on démarre une instance d’alpine au sein d’un conteneur Docker :
sudo lsns --output=NS,TYPE,NPROCS,PID,USER,COMMAND > lsns.no_container.txt
docker run --rm -it alpine shDans une autre instance du shell, on capture l’état courant des namespaces :
sudo lsns --output=NS,TYPE,NPROCS,PID,USER,COMMAND > lsns.with_container.txtSubséquemment, on constate les différences suivantes entre les résultats de lsns :
--- lsns.sudo.no_container.txt 2026-01-31 19:46:56.908995754 +0100
+++ lsns.sudo.with_container.txt 2026-01-31 19:47:21.587995660 +0100
@@ -1,16 +1,16 @@
NS TYPE NPROCS PID USER COMMAND
-4026531832 mnt 278 1 root /usr/lib/systemd/systemd --switched-root --system --deserialize=50
-4026531833 net 277 1 root /usr/lib/systemd/systemd --switched-root --system --deserialize=50
-4026531834 time 298 1 root /usr/lib/systemd/systemd --switched-root --system --deserialize=50
-4026531835 cgroup 292 1 root /usr/lib/systemd/systemd --switched-root --system --deserialize=50
-4026531836 pid 297 1 root /usr/lib/systemd/systemd --switched-root --system --deserialize=50
-4026531837 user 282 1 root /usr/lib/systemd/systemd --switched-root --system --deserialize=50
-4026531838 uts 284 1 root /usr/lib/systemd/systemd --switched-root --system --deserialize=50
-4026531839 ipc 279 1 root /usr/lib/systemd/systemd --switched-root --system --deserialize=50
+4026531832 mnt 281 1 root /usr/lib/systemd/systemd --switched-root --system --deserialize=50
+4026531833 net 281 1 root /usr/lib/systemd/systemd --switched-root --system --deserialize=50
+4026531834 time 303 1 root /usr/lib/systemd/systemd --switched-root --system --deserialize=50
+4026531835 cgroup 296 1 root /usr/lib/systemd/systemd --switched-root --system --deserialize=50
+4026531836 pid 301 1 root /usr/lib/systemd/systemd --switched-root --system --deserialize=50
+4026531837 user 287 1 root /usr/lib/systemd/systemd --switched-root --system --deserialize=50
+4026531838 uts 287 1 root /usr/lib/systemd/systemd --switched-root --system --deserialize=50
+4026531839 ipc 283 1 root /usr/lib/systemd/systemd --switched-root --system --deserialize=50
4026532209 mnt 4 478 root ├─/usr/lib/systemd/systemd-userdbd
4026532210 uts 4 478 root ├─/usr/lib/systemd/systemd-userdbd
-4026532374 mnt 1 498 root ├─/usr/lib/systemd/systemd-udevd
-4026532375 uts 1 498 root ├─/usr/lib/systemd/systemd-udevd
+4026532374 mnt 2 498 root ├─/usr/lib/systemd/systemd-udevd
+4026532375 uts 2 498 root ├─/usr/lib/systemd/systemd-udevd
4026532558 mnt 2 631 dbus ├─/usr/bin/dbus-broker-launch --scope system --audit
4026532559 uts 1 636 root ├─/usr/lib/systemd/systemd-logind
4026532561 mnt 1 633 root ├─/usr/bin/NetworkManager --no-daemon
@@ -62,6 +62,7 @@
4026533319 user 1 2605 ryan /opt/zen/zen -contentproc -isForBrowser ...
4026533320 ipc 1 2605 ryan /opt/zen/zen -contentproc -isForBrowser ...
4026533321 net 1 2605 ryan /opt/zen/zen -contentproc -isForBrowser ...
+4026533407 mnt 1 94808 root sh
4026533411 mnt 2 94263 ryan bwrap --unshare-all ...
4026533434 user 1 2615 ryan /opt/zen/zen -contentproc ...
4026533435 net 1 2615 ryan /opt/zen/zen -contentproc ...
@@ -86,3 +87,8 @@
4026533808 cgroup 2 94263 ryan bwrap --unshare-all ...
4026533809 net 2 94263 ryan bwrap --unshare-all ...
4026533870 user 2 94263 ryan bwrap --unshare-all ...
+4026533871 uts 1 94808 root sh
+4026533872 ipc 1 94808 root sh
+4026533873 pid 1 94808 root sh
+4026533874 cgroup 1 94808 root sh
+4026533875 net 1 94808 root shLa comparaison montre plusieurs points importants :
- aucun nouveau type de namespace n’apparaît : les types restent identiques à ceux décrits dans
man 7 namespaces(mnt,pid,net,ipc,uts,user,cgroup,time) ; - le nombre de processus (
NPROCS) attachés aux namespaces globaux de l’hôte augmente, ce qui reflète la création de nouveaux processus par le conteneur ; - de nouveaux namespaces apparaissent, associés à un processus
shlancé dans le conteneur. Ces namespaces (mnt,pid,net,ipc,uts,cgroup) ontNPROCS = 1, indiquant qu’ils sont privés au conteneur.
On observe ainsi que le conteneur repose sur la création de nouveaux namespaces dédiés à son processus « init », tout en coexistant avec les namespaces globaux de l’hôte. Cela illustre que les conteneurs ne créent pas de nouveaux mécanismes d’isolation, mais exploitent de manière coordonnée les namespaces existants.
2. Interprétation d’une sortie lsns type Firefox contentproc
Analyse
-
Chaque processus
firefox -contentprocpossède souvent :- son propre user namespace,
- son propre ipc namespace,
- parfois son propre net namespace.
-
NPROCS=1indique un namespace privé à un seul processus.
Conclusion
Firefox utilise massivement les namespaces pour sandboxer ses processus de contenu. Il ne s’agit pas de conteneurs, mais d’une isolation fine par processus visant à réduire la surface d’attaque.
3. Comparaison avec man 7 namespaces
Les namespaces existants sont : mnt, pid, net, ipc, uts, user, cgroup, time. Les 8 types observés via lsns correspondent exactement à ceux décrits dans la documentation officielle.
4. Découverte : user namespaces
Première approche
lab/linux_hardening/tp-namespaces ❯ unshare -U
(eval):unsetopt:1: PRIVILEGED: can't drop privileges; failed to change group ID: argument invalide
(eval):1: can't change option: privileged
lab/linux_hardening/tp-namespaces ❯ whoami
nobody
lab/linux_hardening/tp-namespaces ❯ cat /proc/$$/uid_map
lab/linux_hardening/tp-namespaces ❯ exit
lab/linux_hardening/tp-namespaces ❯ whoami
ryanunshare -U crée un nouveau user namespace sans mapping UID/GID par défaut. Le processus se retrouve avec un identifiant interne non mappé, ce qui explique que whoami retourne nobody et que /proc/$$/uid_map soit vide. Sans mapping explicite, le user namespace est fortement restreint et ne permet pas d’obtenir des privilèges effectifs.
INFO
Lors de l’exécution de
unshare -U, le shellzshpeut afficher des messages tels que :(eval):unsetopt: PRIVILEGED: can't drop privileges; failed to change group ID.Ce comportement est lié à la gestion interne du mode
PRIVILEGEDdezshlors d’un changement de contexte UID/GID atypique, comme l’entrée dans un user namespace sans mapping explicite.Des problématiques similaires ont fait l’objet de la vulnérabilité CVE-2019-20044, où
zshne signalait pas correctement l’échec de l’abandon de privilèges. Depuis les versions corrigées (≥ 5.8), ces erreurs sont explicitement reportées afin d’éviter toute ambiguïté ou récupération involontaire de privilèges.Dans ce TP, ces messages sont informatifs et n’indiquent pas une vulnérabilité active : le user namespace est correctement créé et reste fortement restreint en l’absence de mapping UID/GID.
Deuxième approche
lab/linux_hardening/tp-namespaces ❯ unshare -Ur
(eval):unsetopt:1: can't drop privileges; failed to set supplementary group list: opération non permise
(eval):1: can't change option: privileged
lab/linux_hardening/tp-namespaces ❯ whoami
root
lab/linux_hardening/tp-namespaces ❯ id
uid=0(root) gid=0(root) groupes=0(root),65534(nobody)
lab/linux_hardening/tp-namespaces ❯ cat /proc/$$/uid_map
0 1000 1
lab/linux_hardening/tp-namespaces ❯ exitAprès l’exécution de unshare -Ur, le processus se retrouve avec l’UID 0 à l’intérieur du user namespace, ce qui explique que whoami retourne root. La commande id confirme que l’utilisateur et le groupe principaux sont root, tandis que certains groupes supplémentaires restent non mappés.
Le fichier /proc/$$/uid_map montre le mapping automatique : 0 1000 1, indiquant que l’UID 0 du namespace correspond à l’UID utilisateur sur l’hôte. Cela permet d’obtenir des privilèges complets dans le namespace sans être root sur le système hôte.
5. Mount namespace et mini rootfs Alpine
Dans un premier temps, on récupère l’image :
mkdir -p alpine-root
cd alpine-root
wget https://dl-cdn.alpinelinux.org/alpine/v3.13/releases/x86_64/alpine-minirootfs-3.13.1-x86_64.tar.gz
tar -xzf alpine-minirootfs-3.13.1-x86_64.tar.gzPuis on expérimente le montage suivant :
linux_hardening/tp-namespaces/alpine-root ❯ unshare -Ur --mount -R "$PWD" /bin/sh
/ # pwd
/
/ # ps
/bin/sh: ps: not found
/ # ls
/bin/sh: ls: not found
/ # whoami
root
/ # id
uid=0(root) gid=0(root) groups=65534(nobody),65534(nobody),0(root)
/ # exitL’exécution de unshare -Ur --mount -R "$PWD" /bin/sh crée un nouveau mount namespace et définit $PWD comme nouvelle racine du système de fichiers.
La commande pwd retourne /, ce qui confirme que l’arborescence visible correspond désormais au contenu du répertoire Alpine extrait, et non plus au système de fichiers de l’hôte.
L’identité utilisateur reste root à l’intérieur du user namespace, montrant que les namespaces user et mount se combinent sans interférer.
6. PID namespace
linux_hardening/tp-namespaces/alpine-root ❯ unshare -Ur --pid
compinit:563: fork failed: ne peut allouer de la mémoire
Error: Could not source /home/ryan/.config/zsh/conf.d/hyde/terminal.zsh
/etc/profile.d/debuginfod.sh:17: fork failed: ne peut allouer de la mémoire
/etc/profile.d/flatpak.sh:30: fork failed: ne peut allouer de la mémoire
/etc/profile.d/gpm.sh:4: fork failed: ne peut allouer de la mémoire
/home/ryan/.config/zsh/.zshrc:55: fork failed: ne peut allouer de la mémoire
RyanHP# ls
zsh: fork failed: ne peut allouer de la mémoire
RyanHP#En lançant unshare -Ur --pid sans --fork, le shell se retrouve dans un PID namespace mal initialisé pour un usage interactif. zsh tente de forker (compinit, scripts de profil), mais les appels à fork() échouent et remontent un ENOMEM (« ne peut allouer de la mémoire »), qui est ici un symptôme de refus de création de processus dans ce contexte, pas nécessairement un manque de RAM.
Avec --fork, unshare crée un processus enfant qui devient le PID 1 du namespace, ce qui correspond au modèle attendu (un « init » dans le namespace). Le shell peut alors forker normalement et l’environnement redevient stable.
linux_hardening/tp-namespaces/alpine-root ❯ unshare -Ur --pid --fork
erreur: ID de processus hors limites
Usage:
ps [options]
Essayez 'ps --aide <simple|liste|sortie|threads|divers|tous>'
ou 'ps --aide <s|l|o|h|d|t>'
pour plus d'aide.
Pour plus de détails, consultez ps(1).
zsh compinit: insecure directories and files, run compaudit for list.
Ignore insecure directories and files and continue [y] or abort compinit [n]? ycompinit:480: compdump: function definition file not found
Error: Could not source /home/ryan/.config/zsh/conf.d/hyde/terminal.zsh
RyanHP# ./bin/ps
zsh: aucun fichier ou dossier de ce nom: ./bin/ps
RyanHP# ps
PID TTY TIME CMD
866 ? 00:00:18 systemd
...
48714 ? 00:01:21 nvimEn lançant un PID namespace sans --mount-proc, la commande ps (ici via BusyBox) affiche de nombreux processus de l’hôte (systemd, waybar, pipewire). Cela s’explique par le fait que ps lit les informations depuis /proc, et que dans ce cas /proc n’a pas été remonté pour correspondre au PID namespace créé : la vue des PID est donc incohérente et reflète l’hôte.
L’option --mount-proc remonte un /proc propre au namespace et rend la vue des processus isolée (on ne voit alors plus les processus externes).
WARNING
Lancer
unshare -Ur --pid --fork --mount-procsans préciser de commande relance le shell par défaut (ici zsh), qui exécute automatiquement la configuration utilisateur et peut invoquerpsdès le démarrage. Dans ce contexte,ps(procps-ng) peut afficher « ID de processus hors limites » avant même toute commande manuelle.
linux_hardening/tp-namespaces/alpine-root ❯ unshare -Ur --pid --fork --mount-proc /bin/shUne fois le shell opérationnel :
sh-5.3# echo "PID=$$"
cat /proc/self/status | grep NSpid
ps
exit
PID=1
NSpid: 2
PID TTY TIME CMD
1 pts/2 00:00:00 sh
4 pts/2 00:00:00 ps
exitAvec unshare -Ur --pid --fork --mount-proc, le shell lancé devient le processus PID 1 du namespace. Le fichier /proc est isolé, ce qui fait que ps ne montre plus que les processus appartenant au PID namespace courant. L’isolation des PID est donc effective et complète.
7. Combinaison des namespaces
linux_hardening/tp-namespaces/alpine-root ❯ unshare -Ur --mount -R "$PWD" --pid --fork --mount-proc --uts sh
unshare: failed to execute sh: No such file or directory
linux_hardening/tp-namespaces/alpine-root ❯ unshare -Ur --mount -R "$PWD" --pid --fork --mount-proc --uts /bin/sh
/ # ls
/bin/sh: ls: not found
/ # pwd
/
/ # ps
/bin/sh: ps: not found
/ # whoami
root
/ # id
uid=0(root) gid=0(root) groups=65534(nobody),65534(nobody),0(root)NOTE
Après l’isolement du mount namespace avec
-R, les commandes usuelles (ls,ps) peuvent sembler absentes. En réalité, elles existent bien (via BusyBox), mais la variablePATHn’est pas initialisée dans l’environnement minimal. Il est nécessaire de définir explicitement :
export PATH=/bin:/sbin:/usr/bin:/usr/sbin
linux_hardening/tp-namespaces/alpine-root ❯ unshare -Ur --mount -R "$PWD" --pid --fork --mount-proc --uts \
/bin/sh -c 'export PATH=/bin:/sbin:/usr/bin:/usr/sbin; exec /bin/sh'
/ # ls
alpine-minirootfs-3.13.1-x86_64.tar.gz
bin
dev
etc
...8. Namespaces réseau
TIP
Par défaut, un network namespace démarre avec l’interface loopback
loà l’état DOWN. J’ai dû activerlodansclientetserver(ip link set lo up) avant de valider la connectivité. Ensuite, en liant explicitement le serveur HTTP sur l’IPv4 du veth (--bind 10.10.10.20), lecurldepuis le namespaceclientfonctionne.
lab/linux_hardening/tp-namespaces ❯ sudo ip netns exec server /usr/bin/python3 -m http.server 8000 &
sudo ip netns exec client curl 10.10.10.20:8000
[2] 173124
::ffff:10.10.10.10 - - [31/Jan/2026 21:02:53] "GET / HTTP/1.1" 200 -
<!DOCTYPE HTML>
<html lang="en">
<head>
<meta charset="utf-8">
<style type="text/css">
:root {
color-scheme: light dark;
}
</style>
<title>Directory listing for /</title>
</head>
<body>
<h1>Directory listing for /</h1>
<hr>
<ul>
<li><a href="alpine-root/">alpine-root/</a></li>Nettoyage
. vars
sudo ip netns del $namespace1
sudo ip netns del $namespace2