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_LSMdes Pi ne nomme paslockdown. C’était faux, et la mesure ci-dessus dit pourquoi :lockdownest 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 annonceeBPF program_type lsm is availableaussi 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 sansbpfactif, 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 quedmesglit 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 :
Machine bpfactifdébut du tampon dmesgdmesgjournalctl -k -bkrypton oui [0.000000]1 1 speedy oui [0.106211]0 ← faux négatif 1 neon oui [10899.426334]0 ← faux négatif 1 helium oui [9443.670247]0 ← faux négatif 1 carbon non [212872.436640]0 0 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 runningest tout aussi absente dudmesget du journal de carbon — orlandlockest 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 -bne 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 quebpfest actif :journalctl -k -brend toujours 0 pourLSM support for eBPF. La cause est à la ligne près — carbon est un Mac, et le piloteapple-propertiesvide 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/lsmest 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 :
/etc/default/grubn’est pas la vérité.update-grublit ensuite/etc/default/grub.d/*.cfgen ordre de glob, et sur une image cloud50-cloudimg-settings.cfgréassigneGRUB_CMDLINE_LINUX_DEFAULT. Sur krypton, le 2026-10-12, une édition de/etc/default/gruba été écrasée en silence : la chaîne n’est jamais arrivée dansgrub.cfg, et le diff du fichier généré était vide. Le fragment est nommézz-parce quekdump-tools.cfgtrie après n’importe quel préfixe numérique.GRUB_CMDLINE_LINUX_DEFAULTet nonGRUB_CMDLINE_LINUX: l’entréerecoveryreste ainsi sanslsm=, et sert de repli si quelque chose se passait mal. À contrôler : le nombre d’entréesrecoveryportantlsm=doit valoir zéro.- Relire le fichier généré est la seule vérification possible avant le redémarrage. GRUB
n’offre pas l’équivalent du
trybootdes 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/unknown | valide — c’est l’état visé |
0/good/good/unknown | invalid or unexpected state |
1/good/good/good | invalid 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
| Machine | Rôle | Mécanisme | État |
|---|---|---|---|
| helium | machine de test (aarch64) | piboot-try | basculée |
| neon | exécuteur air-aarch64 | piboot-try | basculée |
| krypton | exécuteur x86 + référence de couverture | fragment GRUB | basculée |
| speedy | poste de travail et exécuteur air-docs | fragment GRUB | basculée |
| carbon | exécuteur x86 (Mac Intel) | fragment GRUB | basculé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.