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

ADR-156 — air-sandbox cible l’ABI Landlock v7 : on expose tout ce que le noyau offre

Statut : Accepté (2026-08-13, BDFL). Prolonge ADR-112, qui a créé air-sandbox, et sert le seuil d’exécution d’ADR-046 D6.

Catégorie : Sécurité + contrat d’API. Décide jusqu’où la cage d’Air suit le noyau, et selon quelle règle.

Contexte

air-sandbox expose aujourd’hui une fraction de ce que Landlock sait faire. L’inventaire, constaté le 2026-08-13 :

ABINoyauCe qu’elle apporteExposé par air-sandbox ?
v15.13droits de base par chemin
v25.19REFER — déplacement inter-répertoires
v36.2TRUNCATE✅ (via read_write_file)
v46.7ports TCP bind/connect
v56.10IOCTL_DEVioctl borné par chemin✅ depuis le 2026-08-13
v66.12scoping : sockets UNIX abstraits, signaux✅ depuis le 2026-08-13
v7~6.15journalisation/audit (à confirmer)
v8(constatée)LANDLOCK_RESTRICT_SELF_TSYNC — la restriction s’applique à tous les fils

Le plancher d’Air est 6.12, c’est-à-dire v6 : tout ce qui précède est donc acquis, et le manque n’est plus une prudence — c’est un retard.

Mesuré le 2026-08-13, pas recopié. Un noyau 7.0 de développement annonce une ABI v8, et non v7 — la sonde et l’en-tête noyau installé le disent tous deux. D1 prévoyait ce cas : la cible se déplace avec le noyau, sans nouvel ADR. Le tableau est donc daté par construction, et c’est la sonde qui fait foi, jamais lui.

Un piège relevé au passage : /usr/include/linux/landlock.h (celui de la libc) était périmé — deux champs, seize octets — là où l’en-tête noyau en porte trois, vingt-quatre. Prendre la disposition dans le mauvais fichier aurait fait écrire un scoped que le noyau n’aurait jamais lu.

v8 est un enjeu réel pour Air, et pas une curiosité : TSYNC applique la restriction à tous les fils du processus. Sans lui, une cage posée sur un seul fil est une cage qu’on contourne en changeant de fil. À instruire dans son propre incrément.

Ce que le retard coûte, mesuré

deny_all() ne gère que les droits v1. Or un droit non géré est un droit non restreint — c’est le mécanisme exact de l’écart EXECUTE trouvé le 2026-08-12, où handled_access_fs était l’union des droits des règles présentes et où l’exécution n’était donc bornée nulle part.

Conséquence : une cage qui se présente comme une fermeture totale ne restreint aujourd’hui ni TRUNCATE, ni REFER, ni IOCTL_DEV. Le commentaire qui justifiait ce gel disait que « ce qui reste hors couverture … n’ouvre aucune porte à un worker qui, précisément, ne peut plus rien ouvrir ». L’argument vaut pour ce qu’un worker ouvrirait ; il ne vaut pas pour les descripteurs qu’il a hérités — un worker en tient (canal de médiation, cibles de redirection, maître de PTY).

Ce n’est pas une faille démontrée : pour les profils en service, seccomp borne le verbe et referme la question en pratique. C’est un écart entre ce que la cage annonce et ce qu’elle gère, et cet écart est exactement ce qu’ADR-112 existait pour supprimer.

La règle qui décide

On n’expose pas ce dont on a besoin ; on expose ce que le noyau offre. Air fournit une plateforme : un développeur qui écrit pour Air doit trouver toute la capacité de confinement d’un noyau 6.x ou 7.x, pas la part qu’Air a consommée jusqu’ici. C’est la doctrine d’API du projetl’API est une anticipation, jamais une réaction.

Décision

D1 — La cible est l’ABI v7

air-sandbox expose tous les droits et mécanismes de Landlock jusqu’à v7 inclus : REFER (v2), le scoping des sockets UNIX abstraits et des signaux (v6), et la journalisation d’audit (v7).

La cible se déplace avec le noyau : une ABI v8 ouvrira un incrément de plus, sans nouvel ADR — la règle est posée ici une fois pour toutes.

D2 — Ce qui est au plancher est exigé ; ce qui est au-dessus est sondé

Le plancher d’Air est 6.12, donc v6. Jusqu’à v6, l’absence d’un droit est une erreur de plateforme : Air refuse, il ne dégrade pas (ADR-004 — aucun repli vers les noyaux antérieurs).

Au-delà (v7 et suivantes), la composition est au mieux-disant, décidée par la sonde landlock_supported_abi() — qui existe déjà en couche 0 et qu’air-sandbox utilise déjà pour le ruleset réseau. Ce qui manque est absent, pas silencieusement ignoré : l’appelant peut le constater.

D3 — deny_all() gère tout ce que l’ABI constatée permet de gérer

Une fermeture totale doit fermer tout ce que le noyau sait fermer. Le jeu figé en v1 disparaît au profit d’un jeu calculé à partir de la version sondée.

C’est la correction directe de l’écart mesuré ci-dessus, et l’application de la règle qui a manqué à EXECUTE : ce qui n’est pas géré n’est pas restreint.

D4 — Le scoping (v6) est exposé comme un mécanisme à part

LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET et LANDLOCK_SCOPE_SIGNAL ne sont pas des droits par chemin : ils bornent ce qu’un domaine peut joindre hors du système de fichiers. Ils reçoivent donc leur propre surface, et non un AirPathAccess de plus.

Leur intérêt pour Air est direct : un socket UNIX abstrait n’a pas de chemin, donc aucune règle Landlock de fichier ne le borne — c’était jusqu’ici une voie de communication latérale entre services confinés que la cage ne voyait pas.

D5 — L’audit (v7) est opt-in, jamais un défaut

Les drapeaux de journalisation de landlock_restrict_self disent au noyau ce qu’il doit tracer d’un refus. Utiles au diagnostic, ils ne changent aucune décision d’accès : ils sont exposés, et leur activation appartient à l’appelant.

Conséquences

La surface d’air-sandbox s’élargit : nouveaux constructeurs d’accès, surface de scoping, drapeaux d’audit. C’est une évolution additive de la couche 1 — donc conforme à la directive de re-sceau unique, dont elle rejoint l’inventaire.

deny_all() devient strictement plus fermant sur tout noyau ≥ 6.12. Aucun appelant ne perd de droit : ce qui était permis parce que non géré cesse de l’être, ce qui est le sens même d’une fermeture totale. Un service qui en dépendait sans le savoir le découvrira — et c’est le but.

Un test doit exister par ABI, et il doit mordre : un droit annoncé géré et non posé est précisément le défaut qu’ADR-112 et cet ADR-ci existent pour empêcher. Le motif est connu : neutraliser la règle, vérifier que le test tombe.

Le coût est nommé : les ABI ≥ v7 ne sont pas garanties par le plancher. Le code doit donc porter deux chemins — exigé jusqu’à v6, sondé au-delà — et cette asymétrie est un endroit où deux lecteurs peuvent diverger. Elle est le prix de la règle « tout ce que le noyau offre » sur un plancher qui, lui, ne bouge pas.

Alternatives rejetées

  • N’exposer que ce qu’Air consomme. Rejeté par la règle qui décide : Air est une plateforme, et un développeur doit y trouver la capacité du noyau, pas le sous-ensemble qu’Air a utilisé. C’est la doctrine d’API du projet.
  • Attendre un besoin. Rejeté pour la même raison — et parce que l’écart de deny_all est déjà là, mesuré, indépendamment de tout besoin futur.
  • Relever le plancher noyau à 6.15 pour garantir v7. Rejeté : le plancher sert le userland d’Air et se décide sur d’autres critères (io_uring, cgroups v2, eBPF). Le tirer pour une commodité de cage inverserait la dépendance.
  • Dégrader en silence quand un droit manque. Rejeté : c’est ainsi qu’on obtient une cage qui semble poser une politique. Ce qui manque doit être constatable par l’appelant.

À vérifier à la mise en œuvre

Points factuels, qui conditionnent le code sans remettre la décision en cause :

  1. Le contenu exact de l’ABI v7 et le noyau qui l’apporte, dans Documentation/userspace-api/landlock.rst de la version ciblée. Le tableau ci-dessus donne v7 ≈ 6.15 pour la journalisation d’audit : à confirmer, pas à recopier d’ici ;
  2. Le comportement de landlock_create_ruleset quand on lui demande de gérer un droit inconnu — EINVAL attendu, à re-constater plutôt qu’à supposer, puisque toute la composition au mieux-disant repose dessus ;
  3. Les droits que le scoping (v6) exige de déclarer au create_ruleset, sa granularité, et son interaction avec AirCom — dont les anneaux voyagent par descripteur et non par socket abstrait.

Licence du document : MPL 2.0