guest@6admin.io:~$

Limites de ressources : empêcher une machine d'être convaincue de mourir

Le fork bomb résume tout l’argument en une ligne:

:(){ :|:& };:

Une fonction qui s’appelle elle-même deux fois et met les deux copies en arrière-plan, lancée une fois et le nombre de processus double toutes les fractions de seconde jusqu’à ce que la table des processus soit pleine, qu’aucun nouveau processus ne puisse démarrer et que la machine soit une brique réclamant un reboot brutal. Ce n’est pas un exploit, ça n’exige aucun privilège et n’importe quel utilisateur local peut le taper par accident. La même forme anime le reste de la famille: une boucle qui alloue de la mémoire jusqu’à ce que l’OOM killer parte, une qui ouvre des descripteurs de fichiers jusqu’à ce que fork échoue sur “too many open files”, une qui remplit /tmp ou /var/log jusqu’à ce que le système de fichiers racine se coince. Aucune n’est maligne. Toutes sont arrêtées par la même chose, un plafond que l’utilisateur ne peut dépasser et le seul vrai travail est de poser ce plafond au bon endroit.

Le plafond de session de login : pam_limits

pam_limits plafonne les ressources par session de login et pour le fork bomb en particulier le bouton pertinent est nproc. Une base saine refuse à un compte unique assez de processus pour épuiser la table, relève le plafond pour les comptes qui en ont vraiment besoin et désactive les core dumps pour qu’une boucle qui plante ne remplisse pas aussi le disque:

# /etc/security/limits.conf
*        soft  nproc    4096
*        hard  nproc    8192
*        soft  nofile   1024
*        hard  nofile   65536
*        hard  core     0
@admins  hard  nproc    16384

Le piège de pam_limits est le mot “login”. Il s’applique à ce qui passe par PAM, c’est-à-dire les connexions interactives, ssh et su et il ne s’applique pas aux processus lancés par cron, par un démon ou par un gestionnaire de services. Une limite posée ici et supposée tout couvrir est une limite trouée précisément là où vivent les services de longue durée. Pour ceux-là, le plafond doit venir d’un endroit que PAM ne touche jamais.

Le plafond de service : systemd

Cet endroit est systemd, le contrôle moderne de premier plan parce que chaque service est déjà un cgroup et que les limites sont des propriétés déclaratives plutôt qu’un fichier que le service lira ou non:

# /etc/systemd/system/app.service  (drop-in)
[Service]
TasksMax=200          # le plafond anti-fork-bomb, par service
MemoryMax=1G          # plafond mémoire dur; l'OOM du service, pas celui de la machine
MemoryAccounting=yes
CPUQuota=50%
LimitNOFILE=16384

TasksMax est la défense anti-fork-bomb d’un démon comme nproc l’est d’un login: un emballement dans le service touche le plafond propre du service et le reste de la machine ne s’en aperçoit jamais. MemoryMax transforme une fuite mémoire en problème confiné à une unité, où le cgroup tue le service fautif au lieu que l’OOM killer global du noyau choisisse une victime au hasard. Le même mécanisme de drop-in, appliqué à user@.service, pose un plafond sous toute une tranche d’utilisateur interactif:

# /etc/systemd/system/user-.slice.d/limits.conf
[Slice]
TasksMax=4096
MemoryMax=8G

systemd s’appuie sur cgroup v2 en dessous et descendre à l’interface brute est rarement nécessaire aujourd’hui, mais c’est le même mécanisme: un pids.max, un memory.max, un cpu.max sur un cgroup, c’est-à-dire exactement ce que les propriétés d’unité ci-dessus écrivent pour vous.

Quelques planchers et plafonds à l’échelle système

Certaines limites appartiennent à la machine entière, posées une fois dans sysctl:

# /etc/sysctl.d/limits.conf
kernel.pid_max = 65536
fs.file-max = 2097152
fs.suid_dumpable = 0          # pas de core dump des binaires setuid
kernel.yama.ptrace_scope = 1  # un processus ne peut ptrace que ses propres enfants

Deux de ces réglages dépassent l’épuisement. suid_dumpable = 0 empêche le plantage d’un programme setuid de laisser un core dump plein de secrets et ptrace_scope = 1 empêche un processus non privilégié de s’attacher à un autre, ce qui ferme un chemin courant de vol d’identifiants. Ils figurent dans cette note parce qu’ils voyagent avec les limites, pas parce qu’ils arrêtent un fork bomb.

Les boutons d’overcommit mémoire (vm.overcommit_memory, vm.overcommit_ratio) tentent vers la comptabilité stricte et ils peuvent aider, mais ils changent le comportement d’allocation pour toutes les charges de la machine et cassent régulièrement les applications qui allouent paresseusement. Je n’en ferais pas un défaut de durcissement général; le MemoryMax par service donne le confinement sans la surprise globale.

La surveillance et à quoi elle sert

La vue honnête de l’usage des ressources est par cgroup, donc par service et systemd l’agrège déjà:

systemd-cgtop                               # en direct, par tranche et par service
systemctl show app.service -p MemoryCurrent -p TasksCurrent

Ce que je ne construirais pas, c’est le réflexe que ce genre de document recommande d’habitude, un script qui greppe le motif du fork bomb et tue les processus correspondants ou pire, de la “détection d’anomalie par ML”. Le temps qu’un fork bomb soit visible, la table est déjà pleine et le surveillant ne peut pas démarrer un nouveau processus pour y faire quoi que ce soit. C’est précisément pourquoi la défense est un plafond statique posé à l’avance, pas un détecteur réagissant après coup: la limite refuse le millième processus que quelqu’un regarde ou non et le surveillant existe pour le post-mortem, pas pour le sauvetage.

Où atterrir

Posez nproc, nofile et core dans limits.conf pour les sessions interactives et ssh, en vous rappelant que cela s’arrête à la frontière PAM. Mettez les vrais plafonds sur les services avec systemd, TasksMax et MemoryMax par unité, ce qui est la moitié qui couvre les démons que PAM ne voit jamais. Ajoutez la poignée de planchers sysctl, gardez suid_dumpable et ptrace_scope verrouillés tant que vous y êtes et laissez les boutons d’overcommit globaux tranquilles sauf si une charge précise les réclame. Le fork bomb est trivial et c’est là le point: la défense l’est tout autant et le seul échec est de ne pas l’avoir posée avant que l’utilisateur ne le tape.