Instruction — avec quelle chaîne bâtir le kernel d’Air ?
PROMUE EN ADR-155 le 2026-08-13. C’est l’ADR qui fait autorité. Ce document reste la trace de l’instruction — le raisonnement, les alternatives pesées, et l’ordre dans lequel les quatre décisions sont tombées. On ne le réécrit pas.
Date : 2026-08-13. Statut : instruction ouverte, décision de chaîne non prise — mais le Rust dans le kernel est tranché (BDFL, 2026-08-13 : oui), ce qui contraint fortement le reste. Deviendra un ADR dès que la décision sera arrêtée — elle est structurante (elle engage la reproductibilité, le durcissement, et le nombre de chaînes à figer).
Pourquoi la question se pose maintenant
Air devra produire une image installable, donc un kernel. Ce kernel est le premier artefact du projet qui ne soit ni du Rust ni de la libc d’Air : il est écrit en C, et il faut choisir avec quoi le compiler. Le choix n’est pas neutre — il décide de fonctions de sécurité disponibles ou non, et du nombre de chaînes que ADR-025 devra figer.
Ce qui n’est PAS en jeu
Une idée reçue mérite d’être écartée d’entrée : bâtir le kernel avec Clang n’a rien
d’expérimental. Le support est en amont (make LLVM=1), x86_64 et arm64 sont des cibles de
premier rang du projet ClangBuiltLinux, et c’est ce qui tourne sur la quasi-totalité des
appareils Android ainsi que sur la flotte de production de Google.
La question n’est donc pas « est-ce que ça marche », mais « qu’est-ce que chacune apporte que l’autre n’a pas ».
Les critères qui décident, pour Air
Ils ne sont pas les mêmes que pour une distribution généraliste, et c’est ce qui rend la réponse différente de la sienne :
- Périmètre matériel — Linux tier-1, x86_64 et ARM64, rien d’autre (ADR-004, ADR-014).
- Reproductibilité — un même tag Git doit produire des artefacts bit-pour-bit identiques (ADR-025). Chaque chaîne supplémentaire est une chaîne de plus à pinner, et une source de non-déterminisme de plus à surveiller.
- Posture de durcissement — le projet borne quel programme démarre (ADR-046 D6, ADR-153). Ce qui borne ce qu’un flot de contrôle détourné peut faire s’y ajoute au lieu de s’y substituer.
- Nombre de pièces mouvantes — le reste du stack est déjà entièrement LLVM :
rustcl’est par construction, les cibles*-unknown-linux-airaussi, et un audit de chaîne clang/LLVM existe déjà.
Ce que chaque chaîne apporte seule
| Clang / LLVM | GCC | |
|---|---|---|
| kCFI (Control Flow Integrity kernel) | ✅ | ❌ aucun équivalent |
| ShadowCallStack (arm64) | ✅ | ❌ |
| LTO / ThinLTO sur le kernel | ✅ | ❌ non supporté en amont |
| KMSAN (mémoire non initialisée) | ✅ | ❌ |
| Une seule chaîne pour toutes les arches | ✅ LLVM=1 ARCH=… | ❌ une cross-chaîne par arche |
| Architectures exotiques (m68k, alpha, parisc…) | ❌ | ✅ — sans objet pour Air |
Plugin latent_entropy | ❌ | ✅ |
Plugin stackleak | ⚠️ à vérifier | ✅ |
randstruct | ✅ (natif, Clang 15+) | ✅ (plugin) |
| Exposition de terrain sur distributions | moindre | ✅ majoritaire |
Disponibles des deux côtés, donc hors débat : KASAN, KCSAN, -ftrivial-auto-var-init
(INIT_STACK_ALL_ZERO), les protections de pile classiques.
Recommandation provisoire : Clang
Deux raisons, et la seconde pèse plus lourd que la première.
Les fonctions exclusives sont dans l’axe du projet. kCFI et ShadowCallStack bornent ce qu’un détournement de flot de contrôle peut accomplir — exactement la couche qui manque au-dessus du seuil d’exécution. GCC n’a rien à leur opposer.
Une chaîne de moins à figer. Le stack est déjà tout LLVM. Choisir GCC pour le seul kernel ajouterait une seconde chaîne à pinner, à reproduire et à faire vieillir, dans un projet dont ADR-025 exige le bit-pour-bit. Ce n’est pas un argument de confort : c’est une classe entière de non-déterminisme évitée.
Ce qu’il faut vérifier avant de trancher
Trois points, tous factuels, aucun tranché ici :
- Les versions minimales réelles, sur
Documentation/process/changes.rstde la version ciblée — c’est la seule source qui fasse foi, et elle bouge. De mémoire, les 6.x récents demandent Clang/LLVM 13.0.1 et GCC autour de 5.1 (relevé à 8.1 sur les plus récents) ; certaines options exigent nettement plus haut (asm goto with outputs: Clang 15+). À relire, pas à recopier d’ici. - L’état de
stackleakcôté Clang sur cette même version. C’est le seul durcissement où GCC pourrait manquer. S’il n’existe pas, il faut décider si on s’en passe, et le dire. Voulons-nous du Rust dans le kernel ?TRANCHÉ le 2026-08-13 (BDFL) : oui. Voir la section suivante, qui en tire les conséquences — elles sont plus lourdes que prévu.
Ce que le Rust dans le kernel change (décision BDFL du 2026-08-13)
Le cadre bascule. Tant qu’il ne s’agissait que de C, la question était « quelle chaîne choisir ». Avec du Rust dans le kernel, elle devient « une chaîne, ou deux » — et c’est une question différente.
LLVM est dans le lot, quel que soit le choix
Deux raisons indépendantes, et il suffit de l’une :
rustcest LLVM par construction. Il n’existe pas de version d’rustcqui ne le soit pas (gccrs etrustc_codegen_gccexistent, mais ne sont pas ce sur quoi Rust-for-Linux s’appuie) ;bindgenutiliselibclangpour lire les en-têtes C du kernel et en dériver les liaisons Rust. Il est obligatoire pour bâtir le Rust du kernel.
Conséquence : choisir GCC ne retire pas LLVM de l’ensemble à figer — cela y ajoute GCC. L’arbitrage n’est donc pas « LLVM contre GCC » mais « LLVM seul contre LLVM + GCC », dans un projet dont ADR-025 exige le bit-pour-bit sur chaque chaîne pinnée.
Ce qu’un kernel bâti avec GCC ferait perdre au Rust
Rust-for-Linux fonctionne sur un kernel bâti avec GCC : ce n’est pas un obstacle fonctionnel, et il faut le dire pour ne pas caricaturer. Ce qui se perd est ailleurs, et c’est précisément ce qui motivait le choix :
- kCFI ne couvre pas le Rust sans parité LLVM entre
clangetrustc. Un kernel où le C est protégé et le Rust ne l’est pas offre au premier venu la moitié non protégée — le contraire de ce qu’on cherche en écrivant du Rust ; - le LTO à l’échelle du kernel (C et Rust) exige la même LLVM des deux côtés.
Autrement dit : avec GCC, on entretient deux chaînes et on renonce aux deux fonctions qui justifiaient l’exercice.
Ce que cela ajoute à vérifier
- La version d’
rustcque la version ciblée du kernel accepte — Rust-for-Linux est exigeant et documente une version précise, pas seulement un plancher.make LLVM=1 rustavailabledit ce qui manque, et c’est l’outil à croire. - La version minimale de
bindgen, documentée au même endroit. - La parité de version LLVM entre le
clangretenu et la LLVM embarquée parrustc— la condition de kCFI et du LTO ci-dessus. - Les architectures que Rust-for-Linux supporte : x86_64 et arm64 en font partie, donc le périmètre tier-1 d’Air est couvert. À reconfirmer sur la version ciblée.
Effet sur la recommandation
Elle passe de raisonnable à quasi contrainte. Les trois décisions prises le 2026-08-13
— Rust dans le kernel, un seul kernel (le nôtre), une seule rustc — réduisent l’espace
restant à presque rien : LLVM est obligatoire, la parité de version l’est aussi pour que kCFI
couvre le Rust, et il ne reste à décider que si l’on ajoute GCC par-dessus pour deux
plugins. Le seul motif restant de préférer GCC
serait un durcissement qu’il détiendrait seul et qu’on jugerait indispensable — en pratique
latent_entropy, et stackleak si son support Clang manque toujours. Il faudrait alors
accepter, en échange : deux chaînes à figer, kCFI qui ne couvre pas le Rust, et pas de LTO.
C’est un prix élevé pour deux plugins.
Deux planchers, et ils n’ont rien à voir (décision BDFL du 2026-08-13)
La question « Rust-in-kernel est-il possible sur 6.12 ? » naît d’une confusion qu’il faut dissiper une fois pour toutes, parce qu’elle reviendra.
| Ce que c’est | Ce qu’il contraint | |
|---|---|---|
| Plancher d’exécution — Linux 6.12 LTS | La version minimale sur laquelle le userland d’Air sait tourner (io_uring mature, Landlock, cgroups v2, eBPF complet — macro-architecture-fr.md) | Les syscalls et ABI que la couche 0 peut supposer |
| Le kernel qu’Air produit pour son image | Ce qu’on compile et livre | Rust-in-kernel, kCFI, LTO, le triplet de chaîne |
Rust-in-kernel est une propriété du kernel qu’on BÂTIT, pas de celui sur lequel on s’exécute. Les deux planchers ne se rencontrent que dans un cas : vouloir qu’un module kernel Air écrit en Rust se charge sur un noyau qu’on n’a pas bâti.
Tranché : non — Air ne livre que son propre kernel. Trois conséquences, toutes libératrices :
- La maturité de Rust sur 6.12 est sans objet. Elle ne serait un sujet que pour du code destiné à un noyau étranger. Le plancher 6.12 reste ce qu’il est — un plancher d’exécution — et n’a jamais rien dit du kernel livré ;
- Aucun contrat de compatibilité de modules. Les abstractions Rust du kernel changent d’une version à l’autre ; cela ne devient un coût qu’au moment où nous décidons de monter de version, jamais imposé par un tiers. On peut donc utiliser les abstractions les plus récentes sans réserve ;
- On choisit et on fige le triplet kernel +
rustc+bindgen, avecclangen parité LLVM — exactement ce qu’ADR-025 demande. La confiance ne vient pas d’un numéro de version du noyau, elle vient de ce qu’on le bâtit soi-même.
Le point de vigilance qui subsiste, et il est contre-intuitif
Ce n’est pas « rustc trop ancienne ». C’est l’inverse : le Rust du kernel s’appuie sur
des fonctionnalités instables du langage, et une rustc nettement plus récente que le
noyau casse régulièrement la construction. Le kernel accepte un minimum depuis qu’il a
abandonné l’exigence d’une version exacte, mais « accepté » n’est pas « testé ».
Air épingle aujourd’hui rustc 1.96.0 (rust-toolchain.toml). L’appariement naturel est
donc un noyau contemporain de cette version — pas une LTS de deux ans plus tôt.
Tranché le 2026-08-13 (BDFL) : une SEULE rustc pour le userland et le kernel. Même
arbitrage qu’au-dessus — une chaîne, pas deux — appliqué un étage plus bas, et cohérent avec
lui. rust-toolchain.toml reste la source unique.
Il faut en écrire le prix, parce qu’il est réel et qu’il se paiera :
- Le choix de version du noyau devient contraint par le pin
rustc, et réciproquement. On ne peut pas viser un noyau dont le Rust refuse larustcépinglée, ni monter larustcsans vérifier que le noyau suit ; - Monter de version devient un geste couplé : userland et kernel bougent ensemble. Un besoin userland — une fonctionnalité de langage, une correction — ne peut plus se satisfaire seul ; il attend le noyau, ou l’entraîne.
Ce que la décision précédente rend ce prix payable : Air ne livrant que son propre kernel et n’ayant aucun contrat de compatibilité de modules, faire monter les deux de concert est un geste coordonné, pas une rupture. C’est exactement pourquoi les deux décisions se tiennent : prise isolément, celle-ci serait pénible ; adossée à la précédente, elle ne coûte qu’un ordre de manœuvre.
Conséquence outillable : le jour où un kernel entre dans la construction, un contrôle
doit vérifier que la rustc de rust-toolchain.toml est bien celle que ce noyau accepte
(make LLVM=1 rustavailable). Sans ce contrôle, la contrainte n’existe que dans ce document —
et un document ne fait pas échouer une construction.
Alternatives rejetées
- Bâtir avec les deux (GCC en production, Clang en vérification, ou l’inverse). Rejeté : double le coût de pinning et de reproductibilité pour un bénéfice — la détection de bugs propres à une chaîne — qui relève de l’amont, pas de nous. Air n’a pas les moyens d’être le laboratoire de deux chaînes.
- Suivre le choix de la distribution hôte. Rejeté : Air produit sa propre image ; se caler sur une distribution reviendrait à hériter d’un choix fait pour un périmètre matériel qui n’est pas le nôtre — c’est précisément le raisonnement qu’ADR-004 écarte.
- Reporter le choix jusqu’à l’image. Rejeté : les fonctions exclusives (kCFI, SCS) se
décident à la configuration du kernel, et une image bâtie sans elles ne les gagne pas
après coup sans tout reconstruire. Autant le savoir avant d’écrire le premier
defconfig.
Ce qui déclencherait un ADR
La décision elle-même. Elle engage la reproductibilité (ADR-025), la posture de durcissement, et le nombre de chaînes que le projet entretient — les trois critères qui font qu’une décision n’est pas un détail d’implémentation.