Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Bascule d’une machine sous lsm=…,bpf

Ce guide décrit un geste d’administration, pas une procédure automatisée. Il n’existe aucun script du dépôt qui l’exécute, et c’est délibéré (ADR-121) : changer la ligne de commande du noyau d’une machine et la redémarrer est un acte qu’on relit avant de le faire. Les commandes ci-dessous sont à taper, machine par machine.

Pourquoi

Le seuil d’exécution d’ADR-188 — « Air n’exécute que des binaires Air » — repose sur un programme eBPF attaché comme module de sécurité (LSM). Le noyau refuse de poser le seuil tant que le module bpf n’est pas actif. CONFIG_BPF_LSM=y ne suffit pas : c’est la liste lsm= de la ligne de commande, ou à défaut CONFIG_LSM, qui décide. Aucune machine du parc ne l’activait avant le 2026-10-12.

L’effet dépasse Air : avec le module actif, systemd attache son propre programme LSM (bpf-restrict-fs), celui qui met en œuvre RestrictFileSystems= dans les unités. Cette directive était donc inerte sur tout le parc, sans que rien ne le signale.

La chaîne, et pourquoi une seule suffit partout

lsm=lockdown,capability,landlock,yama,apparmor,ima,evm,bpf

Elle vaut pour les noyaux generic et raspi, bien que leurs CONFIG_LSM diffèrent (celui des Pi ne nomme pas lockdown). La raison se lit dans les traces que le noyau produit avec le paramètre lsm.debug, mesurées sous QEMU le 2026-10-12 :

LSM: available LSMs: lockdown(E),capability,selinux,smack,tomoyo,apparmor,yama,safesetid,bpf,landlock,ipe,ima,evm
LSM: built-in LSM config: landlock,lockdown,yama,integrity,apparmor
LSM: enabled  LSM early:lockdown
LSM: enabling LSM first:capability
LSM: enabling LSM cmdline:landlock
LSM: enabling LSM cmdline:yama
LSM: enabling LSM cmdline:apparmor
LSM: enabling LSM cmdline:bpf
LSM: enabling LSM last:ima
LSM: enabling LSM last:evm
LSM: skip disabled LSM builtin:bpf        ← sans lsm=, c'est ce que le noyau fait de bpf

Quatre modules échappent à la chaîne : lockdown est marqué (E) et s’active early:, capability s’active first:, ima et evm last: — quoi qu’on écrive. Les nommer quand même ne nuit pas et rend la chaîne lisible ; les omettre ne les retirerait pas. Ce qui se décide réellement, c’est landlock, yama, apparmor et bpf.

Correction d’une affirmation de ce dépôt. Le suivi a porté, du 2026-10-11 au 2026-10-12, que « la valeur se relève par machine, elle ne se recopie pas », au motif que le CONFIG_LSM des Pi ne nomme pas lockdown. C’était faux, et la mesure ci-dessus dit pourquoi : lockdown est actif sur les Pi sans y figurer, parce qu’il ne passe pas par la liste. Une seule chaîne a rendu le même ensemble correct sur les quatre noyaux éprouvés.

Ce qu’il faut vérifier après, et avec quoi

La liste active doit être un surensemble strict de celle d’avant : bpf en plus, rien en moins. Une chaîne fautive ne vous empêchera pas de démarrer — elle vous retirera silencieusement apparmor ou landlock, et la cage d’air-sshd dépend de landlock.

cat /sys/kernel/security/lsm
# attendu : lockdown,capability,landlock,yama,apparmor,bpf,ima,evm
sudo journalctl -k -b | grep 'LSM support for eBPF'   # confirme ; son ABSENCE ne prouve RIEN
sudo journalctl -b 0 | grep 'bpf-restrict-fs'     # systemd attache son programme
sudo aa-status | head -3                           # les profils AppArmor sont toujours chargés
systemctl --failed                                 # relever AVANT, pour pouvoir comparer

Instrument à ne pas employer : bpftool feature probe. Il annonce eBPF program_type lsm is available aussi bien sur une machine basculée que sur une machine qui ne l’est pas — il rapporte que le type de programme est compilé, pas que le module est actif. Mesuré le 2026-10-12 des deux côtés. Le noyau charge et attache un programme LSM sans bpf actif, mais ne l’exécute jamais ; cette sonde ne distingue donc pas les deux situations.

Instrument à n’employer que dans un sens : dmesg | grep 'LSM support for eBPF'. Sa présence confirme ; son absence ne prouve rien, parce que dmesg lit le tampon circulaire du noyau, qui déborde. L’init des LSM a lieu dans les premières millisecondes du démarrage : c’est la toute première chose évincée. Mesuré le 2026-10-12, à liste active identique et constante :

Machinebpf actifdébut du tampon dmesgdmesgjournalctl -k -b
kryptonoui[0.000000]11
speedyoui[0.106211]0 ← faux négatif1
neonoui[10899.426334]0 ← faux négatif1
heliumoui[9443.670247]0 ← faux négatif1
carbonnon[212872.436640]00

Ces débuts de tampon dérivent : sur speedy, trois relevés à quelques minutes d’intervalle ont donné [0.101428], [0.106211], [0.107696]. C’est le phénomène lui-même — le tampon continue d’évincer. Ce qui compte n’est pas la valeur mais le fait qu’elle ne soit pas [0.000000] : alors le début du journal d’amorçage est déjà perdu.

Le témoin qui tranche, et qui ne laisse aucune échappatoire : la ligne landlock: Up and running est tout aussi absente du dmesg et du journal de carbon — or landlock est actif sur carbon, il figure dans /sys/kernel/security/lsm. Le 0 de carbon ne mesurait donc pas l’absence du module : il ne mesurait rien.

Et journalctl -k -b ne suffit pas non plus. journald lit le tampon circulaire au moment où il démarre lui-même : il hérite donc de tout ce que le tampon avait déjà perdu. Mesuré sur carbon le 2026-10-12, après sa bascule et alors que bpf est actif : journalctl -k -b rend toujours 0 pour LSM support for eBPF. La cause est à la ligne près — carbon est un Mac, et le pilote apple-properties vide des blocs hexadécimaux dans le journal noyau : 2002 des 2670 lignes capturées. journald a démarré 12 s après l’amorçage, et la première ligne encore présente était [0.359373]. L’init des LSM, qui est avant, était déjà perdue — pour le tampon comme pour le journal.

Conclusion, et elle est courte : /sys/kernel/security/lsm est la seule sonde valable dans les deux sens. C’est une interface vivante du noyau : elle ne déborde pas, elle ne dépend pas de l’instant du relevé, ni de ce qu’un pilote bavard a écrit avant journald.

Coût relevé sous QEMU, avec lsm.debug sur la ligne de commande : LSM: blob(inode) size passe de 96 à 104 octets — huit octets de plus par inode en mémoire noyau. Sans lsm.debug, le noyau n’imprime aucune ligne LSM: : ne pas chercher à reproduire ce chiffre sur une machine du parc, il n’y est pas.

x86 sous GRUB — par un fragment, jamais par /etc/default/grub seul

sudo tee /etc/default/grub.d/zz-air-lsm-bpf.cfg >/dev/null <<'EOF'
# Seuil d'exécution d'Air (ADR-188) : le LSM BPF doit être ACTIF, pas seulement compilé.
GRUB_CMDLINE_LINUX_DEFAULT="$GRUB_CMDLINE_LINUX_DEFAULT lsm=lockdown,capability,landlock,yama,apparmor,ima,evm,bpf"
EOF
sudo update-grub
sudo grep -E '^\s+linux\s' /boot/grub/grub.cfg        # LA vérité : relire ici avant de redémarrer

Trois raisons à ce détour, chacune payée :

  1. /etc/default/grub n’est pas la vérité. update-grub lit ensuite /etc/default/grub.d/*.cfg en ordre de glob, et sur une image cloud 50-cloudimg-settings.cfg réassigne GRUB_CMDLINE_LINUX_DEFAULT. Sur krypton, le 2026-10-12, une édition de /etc/default/grub a été écrasée en silence : la chaîne n’est jamais arrivée dans grub.cfg, et le diff du fichier généré était vide. Le fragment est nommé zz- parce que kdump-tools.cfg trie après n’importe quel préfixe numérique.
  2. GRUB_CMDLINE_LINUX_DEFAULT et non GRUB_CMDLINE_LINUX : l’entrée recovery reste ainsi sans lsm=, et sert de repli si quelque chose se passait mal. À contrôler : le nombre d’entrées recovery portant lsm= doit valoir zéro.
  3. Relire le fichier généré est la seule vérification possible avant le redémarrage. GRUB n’offre pas l’équivalent du tryboot des Pi.

Retrait : supprimer le fragment, sudo update-grub, redémarrer.

Raspberry Pi — par piboot-try(8), qui se révoque tout seul

Les Pi sous Ubuntu n’utilisent pas GRUB et ne lisent pas le cmdline.txt de la racine de la partition : config.txt porte os_prefix=current/, donc le fichier lu est /boot/firmware/current/cmdline.txt. Le paquet piboot-try fournit un mécanisme A/B outillé : les nouveaux fichiers vont dans new/, un démarrage d’essai les éprouve, et /etc/flash-kernel/piboot-validate décide — succès ⇒ new/ est promu dans current/ et l’ancien current/ devient old/ ; échec ou non-retour ⇒ la machine repart de current/, intacte.

F=/boot/firmware
CHAINE="lsm=lockdown,capability,landlock,yama,apparmor,ima,evm,bpf"
sudo piboot-try --status                       # doit être silencieux (état stable)
sudo cp -a $F/current/cmdline.txt $F/current/cmdline.txt.avant-lsm-bpf
sudo rm -rf $F/old                             # ⚠ AVANT de créer new/ — voir ci-dessous
sudo cp -a $F/current $F/new
printf '%s %s\n' "$(cat $F/current/cmdline.txt)" "$CHAINE" | sudo tee $F/new/cmdline.txt
sudo piboot-try --reset-new                    # marque new/ « unknown »
sudo piboot-try --status                       # doit décrire « untested boot assets present »
sudo piboot-try --reboot                       # démarrage d'essai immédiat

L’ordre compte, et c’est le piège. La clé d’état est tryboot/old/current/new, et la transition « non testé → à l’essai » n’est admise que si old/ est absent :

CléVerdict de piboot-try
[01]/-/good/unknownvalide — c’est l’état visé
0/good/good/unknowninvalid or unexpected state
1/good/good/goodinvalid or unexpected state

Créer new/ sans retirer old/ met la machine à états dans un état qu’elle refuse, et fait échouer les deux services piboot-try-reboot et piboot-try-validate — constaté sur helium le 2026-10-12. Un cp -a current new recopie aussi state, qui vaut alors good au lieu de unknown : d’où le --reset-new, qui est la porte prévue.

Retrait après promotion : sudo piboot-try --restore-old puis redémarrer. old/ conserve la ligne d’avant la bascule, et la sauvegarde cmdline.txt.avant-lsm-bpf reste dans current/.

Place nécessaire : current/ pèse ~101 Mio sur une partition d’amorçage de 505 Mio ; retirer old/ d’abord libère de quoi écrire new/.

État du parc au 2026-10-12

MachineRôleMécanismeÉtat
heliummachine de test (aarch64)piboot-trybasculée
neonexécuteur air-aarch64piboot-trybasculée
kryptonexécuteur x86 + référence de couverturefragment GRUBbasculée
speedyposte de travail et exécuteur air-docsfragment GRUBbasculée
carbonexécuteur x86 (Mac Intel)fragment GRUBbasculée — redémarrage autorisé par le BDFL le 2026-10-12, ce qui lève pour cet objet l’interdiction du 2026-09-28

Les cinq machines du périmètre sont basculées. Sur carbon, air-sshd a repris le :22 sans EADDRINUSE (l’incident du 2026-09-25 ne s’est pas reproduit), et l’accès d’administration passe de toute façon par OpenSSH sur 52222 — vérifié avant de redémarrer, parce que c’est la condition qui rend le geste réversible à distance.

Les cinq machines de production — nitrogen, argon, quartz, topaze, onyx — sont hors de ce périmètre, et c’est mesuré, non supposé : air-launchd est absent des cinq (relevé le 2026-10-12). Elles portent toutes la même liste sans bpf ; deux d’entre elles (quartz, topaze) tournent encore sous Ubuntu 25.04 / noyau 6.14, pour lesquelles la chaîne ci-dessus n’a pas été éprouvée.