ADR-112 — air-sandbox (AirSandboxManager) : confinement seccomp/Landlock sûr, compilateur BPF maison, profils fail-closed, fin de la fuite couche 0
Statut : Accepté (2026-07-26, décision BDFL). RFC de direction
(ADR-015).
S’appuie sur ADR-077 (managers de domaine
couche 1), ADR-089 (SecurityManager =
politique), ADR-088 (sans libc C),
ADR-111 (air-log — journalisation de
l’opt-out), ADR-107
(pipeline CI — tests privilégiés + arches natives), ADR-029
(nommage), ADR-019 (modèle d’erreurs).
Catégorie : Architecture couche 1 (nouveau manager de domaine) + correction de frontière couche 0/1. Vague 1 #5 (dernier) du triage des écarts OpenSSH.
Contexte
OpenSSH confine son worker pré-auth (le code le plus exposé) par un bac à sable
seccomp. Air dispose déjà des briques couche 0 (seccomp-bpf, Landlock, eBPF —
ebpf mergé), mais aucune surface haut niveau sûre ne les expose : air-process
laisse aujourd’hui fuiter des types couche 0 (with_seccomp_filter(SockFprog),
with_landlock(LandlockRuleset)) jusqu’aux appelants couche 2 — un démon (air-sshd)
devrait construire un SockFprog brut pour se confiner, ce qui viole le layering et est
dangereux à écrire à la main.
Il manque le mécanisme : un manager qui compile une intention haut niveau (« ce worker n’a droit qu’à ces syscalls / ces chemins ») en filtre seccomp-BPF + ruleset Landlock, sans exposer la couche 0.
Directive BDFL (2026-07-26). Toutes les recommandations retenues ; et sur le compilateur seccomp : « pas besoin d’évaluation. On fait le nôtre. » (contraste assumé avec la doctrine crypto : seccomp n’est pas de la crypto, et un compilateur allow-list→BPF est du ressort d’un DIY maîtrisable).
Décision
1. air-sandbox est un manager de domaine couche 1 = mécanisme
Une crate air-sandbox exposant un AirSandboxManager ([ADR-077]) : le mécanisme
de confinement. Il applique-à-soi (apply-to-self : un worker pré-auth se met en cage)
et applique-au-spawn (apply-to-spawn). no_std + alloc, erreurs AirError
([ADR-019]). Il est distinct du futur SecurityManager ([ADR-089]) qui porte la
politique (quel profil pour quel contexte) : air-sandbox pose la cage, le
SecurityManager décide laquelle.
2. Builders d’allow-list haut niveau — zéro fuite couche 0
L’API est une liste d’autorisations haut niveau (syscalls permis, chemins accessibles
en lecture/écriture) qui compile en interne vers un ruleset Landlock et un filtre
seccomp-BPF. Les types couche 0 ne fuient jamais dans la surface publique. Des
profils prédéfinis sont fournis, consommés directement par air-sshd : pre-auth,
sftp-worker, etc.
3. Compilateur seccomp allow-list → BPF, écrit par nous (pas de libseccomp C)
Le compilateur qui traduit une allow-list en programme seccomp-BPF est maison
(décision BDFL : pas d’évaluation de crate, pas de libseccomp C — cohérent [ADR-088]) :
- Arch-aware (x86_64 et aarch64 : numéros de syscalls, vérification de l’architecture dans le filtre pour rejeter les appels 32 bits/mauvaise arch).
no_new_privsposé avant le filtre (invariant seccomp).- Fuzzé (cargo-fuzz) — surface : allow-list → programme BPF bien formé ; plus des tests d’efficacité (le filtre bloque effectivement ce qu’il doit, autorise ce qu’il doit — harnais d’exécution réelle, cf. tests privilégiés [ADR-107] §7).
4. Fail-closed par défaut
Si le noyau ne fournit pas le socle attendu (Landlock trop ancien, seccomp
indisponible), le manager refuse de tourner (fail-closed, esprit « sur-sécuriser »
Principe 5). L’opt-out (tourner sans cage) est explicite et journalisé via
air-log ([ADR-111]) — jamais un dégradé silencieux.
5. Fin de la fuite couche 0 dans air-process
air_process::with_seccomp_filter(SockFprog) / with_landlock(LandlockRuleset) sont
retravaillés pour prendre les types haut niveau d’air-sandbox (profil / allow-list)
au lieu des types couche 0 bruts. Après quoi un appelant couche 2 (air-sshd) ne manipule
plus jamais un SockFprog : il choisit un profil. La compilation vers la couche 0
reste interne à air-sandbox / air-process (couche 1 → couche 0 = direction autorisée).
Conséquences
Positives.
- Confinement pré-auth enfin disponible et sûr à utiliser : profil, pas de BPF à la main.
- Layering réparé : plus de type couche 0 dans la surface consommée par la couche 2.
- Sans C : compilateur BPF maison, pas de
libseccomp; tient sur*-linux-air. - Fail-closed + opt-out journalisé : la sécurité ne se dégrade pas en silence.
- Séparation mécanisme/politique propre ([ADR-089]) :
air-sandboxpose,SecurityManagerdécide.
Négatives / coûts assumés.
- Compilateur seccomp = code critique à écrire nous-mêmes : un filtre trop laxiste = faux sentiment de sécurité, trop strict = worker qui meurt sur un syscall légitime. ⇒ fuzz + tests d’efficacité on-target (privilégiés, [ADR-107] §7), coverage 100 %.
- Arch-aware = double maintenance (tables de syscalls x86_64/aarch64) : suivre l’ABI kernel ; erreur d’arch = trou. Tests des deux arches (exécution native aarch64, [ADR-107]).
- Migration
air-process(retrait des types couche 0 publics) : incrément avec non-régression des consommateurs existants.
Mise en œuvre (référence, hors décision). Incréments : (a) crate air-sandbox
(allow-list + compilateur BPF maison + rulesets Landlock + profils) ; (b) fuzz du
compilateur + tests d’efficacité on-target ; (c) fail-closed + opt-out journalisé via
air-log ; (d) refonte air-process (types haut niveau) + migration air-sshd (worker
pré-auth/sftp en cage). Non engagés par cet ADR.
Alternatives rejetées
Aucune alternative n’a été consignée lors de l’instruction.
Amendement — le nom livré est AirSandbox, pas AirSandboxManager (2026-08-03)
Constat d’audit du 2026-08-03, dans le cadre de la remise en conformité des ADR nommant des Managers (cf. l’amendement d’ADR-077, qui porte l’état faisant autorité).
L’écart
Le titre du présent ADR et son §1 annoncent un AirSandboxManager. Le code livre :
| Livré | Rôle effectif |
|---|---|
AirSandbox | Poseur de profil : apply(). C’est l’objet réel du confinement |
AirSandboxProfile, AirLandlockPolicy, AirPathRule, AirPathAccess | Les types du domaine |
compile_seccomp_filter, compile_allowlist | Deux fonctions libres de compilation |
Ce n’est pas un manquement fonctionnel : le mécanisme décrit par cet ADR est en place,
éprouvé, et consommé en production par air-sshd (cages sftp-worker et session — ADR-105,
ADR-147). C’est un écart de surface : AirSandbox pose la cage sur commande, mais ne joue pas
le rôle de médiateur de domaine qu’ADR-077 attend d’un Manager — un point d’entrée unique que
les toits consomment.
Le statut fait de cet ADR reste juste : il porte sur le mécanisme, qui est livré. Il ne
vaut pas conformité à ADR-077.
Ce qui reste, et pourquoi ce n’est pas encore le moment
Poser AirSandboxManager exige de trancher ce que la surface médiatise, et ces questions sont
ouvertes : la largeur des enveloppes par défaut, la forme de l’arbitre de lancement,
l’articulation manifeste/octroi, le sort des binaires de provenance étrangère.
L’amendement D6 d’ADR-046 (2026-08-03) a gravé les invariants —
plus aucune exécution non confinée, refus par défaut, octroi explicite — mais renvoie
explicitement le mécanisme à un ADR sécurité dédié. Tant que celui-ci n’existe pas, écrire la
surface de AirSandboxManager reviendrait à trancher ces questions par défaut, c’est-à-dire
sans les poser.
AirSandboxManagerest partiellement débloqué depuis ADR-150 (2026-08-03), qui fixe le manifeste, l’octroi et la procédure d’arbitrage à l’exec. Reste bloquante la largeur des enveloppes par défaut — elle détermine ce que le Manager doit savoir poser. Dépendance de décision, pas de code.
Rappel de la répartition posée au §1, que l’amendement D6 confirme : air-sandbox pose la
cage, le futur SecurityManager (ADR-089)
décide laquelle.