Note de conception — une cage propre à chaque session SSH
Statut : proposition, à trancher par le BDFL (§7). Rien n’est mis en œuvre.
Met en œuvre, si elle est retenue : ADR-150 D12 (« le lanceur médie ») et D13 (l’enveloppe
de session), pour les sessions d’air-sshd.
Rédigée le 2026-09-27, après la vérification qui a interdit de retirer transitionalGlibc
du manifeste d’air-sshd.
1. Le constat, mesuré
Le 2026-09-27, sur carbon, un processus de session (sleep, lancé par l’utilisateur thierry,
enfant d’air-sshd) portait un seul filtre seccomp, exactement comme le démon :
air-sshd Seccomp: 2 Seccomp_filters: 1
sleep (en session) Seccomp: 2 Seccomp_filters: 1 NoNewPrivs: 1
La session n’a pas de cage à elle : elle tourne sous celle du démon, que le lanceur pose
avant l’execve d’air-sshd. Il en va de même du domaine Landlock (ADR-187 §1 le constatait
déjà : « une session hérite de la cage d’air-sshd »).
Or seccomp, Landlock et no_new_privs sont monotones : un descendant ne peut que se
restreindre davantage, jamais s’élargir. La cage du démon doit donc contenir tout ce dont une
session a besoin, et le démon porte en permanence des droits qui ne lui servent pas. Sur carbon,
l’inventaire d’ADR-188 D4 (air-bundle exemptions) en montre une partie :
| Porté par le démon, pour ses sessions | Pourquoi |
|---|---|
exécution sur /usr, /lib, /lib64, /bin | ce que l’utilisateur lance depuis son shell |
{home} en lecture-écriture | le répertoire de travail de l’utilisateur (ADR-185) |
/etc/profile, /etc/bash.bashrc, /etc/inputrc, /etc/terminfo, /etc/vim… | les fichiers que lit un shell interactif |
les bases apt et dpkg en lecture | ADR-187 D2 |
| le réseau sortant non géré | ADR-187 D1 |
le bloc d’appels système TransitionalGlibc (64 entrées) | le chargeur de la glibc (access, que seul ce bloc contient) et les outils en session (ADR-150 D13, annexe des 136 appels) |
Le binaire air-sshd n’a besoin d’aucune de ces lignes pour lui-même : c’est un static-PIE
linux-air. Un monitor compromis, ou un worker qui s’échapperait vers lui, hérite pourtant de
toutes.
2. La contrainte qui décide de tout
Une cage de session plus large que celle du démon, ou simplement différente, ne peut être posée que par un processus qui n’est pas dans la cage du démon. Ce n’est pas une politique : c’est la monotonie du noyau.
Aujourd’hui, le lancement est fait par le monitor (ADR-147 inc.1,
crates/air-sshd/src/monitor/session_spawn.rs) : il alloue le terminal, fork (via le serveur
de fork d’ADR-186 D6), largue vers l’identité du compte et execve le programme de session.
Le monitor est dans la cage du démon : il ne peut pas faire mieux.
Un seul processus d’Air est, par construction, hors de toute cage et déjà parent du démon :
air-launchd, qui pose la cage d’air-sshd. C’est exactement ce qu’ADR-150 D12 a
décidé le 2026-08-22 : « le lancement d’un programme est médié par le lanceur, hors de la
cage de la session ». Cette note l’applique aux sessions d’air-sshd.
Une tension à lever au passage. ADR-147 rangeait « confiner le shell utilisateur » parmi ses non-objectifs. ADR-150 D12 et D13, plus récents, ont décidé l’inverse : la session a une enveloppe (
C). Cette note suit D12/D13 ; le non-objectif d’ADR-147 est à marquer comme dépassé dans l’ADR qui mettra cette note en œuvre.
3. La proposition : le monitor demande, le lanceur lance
worker (compte cible, confiné)
│ SpawnSession{service, commande, terminal?}
▼
monitor (root borné, DANS la cage du démon)
│ garde ce qu'il décide déjà : l'identité authentifiée, la commande forcée
│ (ADR-128 §2), une session par connexion, l'allocation du PTY sous l'identité du compte
│
│ ToLauncher::SpawnSession{compte, programme, arguments, environnement, terminal?}
│ + SCM_RIGHTS : la voie d'I/O (PTY ou socketpair)
▼
air-launchd (root, HORS cage)
│ 1. vérifie (§4)
│ 2. fork ; setsid ; terminal de contrôle ; cgroup de la session (bornes)
│ 3. largue vers le compte (uid, gid, groupes résolus par le lanceur)
│ 4. pose la cage de SESSION : enveloppe C + exemptions transitoires nommées
│ 5. execve du programme de session
│
├── ToService::SessionSpawned{jeton} (ou SessionRefused{raison})
└── plus tard : ToService::SessionExited{jeton, statut} ─► monitor ─► worker
Ce qui change pour le monitor : il ne fork plus le programme de session et ne le récolte plus.
Le protocole de lancement (air-launch-proto) n’a aujourd’hui que deux messages vers le lanceur,
Stopping et Status : il gagne une demande et deux ou trois réponses.
4. Ce que le lanceur vérifie — les deux conditions de D12
ADR-150 D12 pose deux conditions, « des décisions et non des recommandations ». Pour une session, elles deviennent :
- Le programme est accordé à ce service. Le lanceur lit l’octroi vérifié du service qui
demande (il l’a déjà : il l’a arbitré pour le lancer) et refuse tout programme absent de sa
famille
session. Le monitor ne peut rien obtenir que l’administrateur n’ait accordé àair-sshd. - Cet utilisateur a le droit d’invoquer ce programme. C’est le point délicat : l’identité
est établie par l’authentification SSH, dans le monitor. Le lanceur ne peut pas la
refaire ; il doit croire le monitor sur « qui ». Cette confiance n’est pas nouvelle : le
monitor, root, peut déjà prendre n’importe quelle identité aujourd’hui. La note propose que le
lanceur la borne au lieu de l’étendre :
- la demande nomme le compte, jamais un uid ; le lanceur le résout lui-même dans la base Air (comme pour l’identité de confinement, ADR-182 D3) ;
- un compte système (uid < 1000) ou
rootest refusé, sauf décision contraire (§7, Q4) ; - une seule demande en vol par connexion, et un plafond de sessions par service.
Ainsi, un monitor compromis n’obtient pas plus qu’aujourd’hui, et même moins : il ne choisit plus ni l’uid numérique, ni la cage, ni le programme hors de l’octroi.
5. Les deux cages, après
La cage du démon perd tout ce qui ne servait qu’aux sessions (§1), et d’abord
transitionalGlibc. Elle garde ce que le démon fait lui-même : écouter, lire sa configuration, sa
base de comptes et les authorized_keys ({home}/.config/air/sshd/authorized_keys), allouer les
PTY (/dev/ptmx, /dev/pts), la séparation de privilèges. La liste exacte se mesure : c’est
le premier incrément (§8).
La cage de session est l’enveloppe C d’ADR-150 D13 : 66 appels, $HOME en
lecture-écriture avec REFER, /usr et /lib en lecture et exécution. Tant qu’Air repose sur
Ubuntu, elle porte en plus, nommées et inventoriées par air-bundle exemptions (ADR-188 D4) :
le bloc TransitionalGlibc, /lib64 et /bin, les lectures d’/etc d’ADR-187, et le
réseau sortant si Q2 le garde. En v1, ces exemptions tombent d’un bloc, sans toucher au démon.
6. Ce que cela apporte, et ce que cela coûte
Apporte :
- le démon cesse de porter les droits de ses utilisateurs : la surface d’un
air-sshdcompromis se réduit à ce qu’air-sshdfait ; - l’exemption transitoire change de titulaire : elle n’est plus sur un service, mais sur les
sessions, là où elle sert, et
air-bundle exemptionsle montrera ; - chaque session a son cgroup, donc des bornes (
pids.max, mémoire), ce que la directive « aucune version publique sans bornes » exige de toute façon ; - c’est le chemin qu’emprunteront les applications (ADR-150 D12) : on le construit une fois.
Coûte :
- un verbe de plus dans le protocole de lancement, et la récolte déplacée du monitor au lanceur ;
- le lanceur devient joignable par une demande qui produit un processus sous une autre identité. C’est la surface la plus sensible de la proposition, et elle appelle une revue de sécurité dédiée (§4) ;
- le passage des descripteurs (PTY) du monitor au lanceur, puis le terminal de contrôle dans
l’enfant : des étapes où une erreur produit une session sans terminal. Des sessions sans
terminal ont déjà été observées sur carbon (
ssh -ttrendant « not a tty ») : ce passage se teste en vraie session, pas seulement en unitaire.
7. Les questions qui reviennent au BDFL
- Q1 — Qui lance ? (a)
air-launchdlui-même, recommandé : il est déjà hors cage, parent du démon, résout les identités (ADR-182) et développe{home}(ADR-180). Ou (b) un courtier de sessions dédié, que le lanceurforkavant de cager le démon : un processus root de plus, à maintenir, pour la même confiance. - Q2 — Le réseau sortant des sessions. L’enveloppe
Cn’a pas de réseau ; ADR-187 D1 l’a gardé ouvert aux sessions « jusqu’àalize». Recommandé : le garder dans la cage de session comme exemption transitoire nommée, et le retirer du démon. Le démon n’a besoin que d’écouter. - Q3 — La récolte. Le lanceur devient parent : il transmet le statut de sortie au monitor
(
SessionExited). Recommandé. L’alternative, rendre unpidfdau monitor, ne suffit pas :waitidsur unpidfdexige d’être le parent. - Q4 — Les comptes admis. Refuser
rootet les comptes système par défaut au lanceur, ou laisser cette politique au seul monitor (configurationPermitRootLogin) ? Recommandé : le lanceur refuserooten dur, comme défense en profondeur ; un besoin contraire se déclarerait dans l’octroi. - Q5 — La bascule. Une coexistence des deux chemins pendant la mise en œuvre serait un second
chemin (ADR-149 D10) : recommandé, une bascule unique, par service, déclarée dans le
manifeste d’
air-sshd, et l’ancien chemin retiré dans la même campagne.
8. Incréments proposés, si la note est retenue
- Mesurer ce dont le démon a besoin sans ses sessions :
straceet le filtre en mode journal sur carbon et neon, pour fixer la cage cible du démon avec des chiffres. - Écrire l’ADR qui tranche Q1 à Q5 et marque le non-objectif d’ADR-147 comme dépassé.
- Le protocole :
SpawnSession,SessionSpawned,SessionRefused,SessionExiteddansair-launch-proto, fuzzés comme le reste du protocole. - Le lanceur : les vérifications du §4, le
fork, le terminal, le cgroup, la cage de session, la récolte. C’est l’incrément qui demande la revue de sécurité. - Le monitor : il demande au lieu de lancer. Bascule unique.
- Le manifeste d’
air-sshd: retirertransitionalGlibcet les droits du §1, puis vérifier sur carbon et neon que les sessions ouvrent, quebash,apt,gitetvimfonctionnent, et queair-bundle exemptionsmontre les exemptions du côté des sessions.
9. Ce que cette note ne décide pas
- Rien sur les applications empaquetées : elles suivront le même chemin de lancement, mais avec leur propre octroi (ADR-150 D12). C’est un chantier à part.
- Rien sur
alizeni sur le portage des outils : ils font tomber les exemptions de la cage de session, pas la cage elle-même. - Rien sur le chemin monolithique (
air-sshdsans séparation de privilèges), qui n’a pas de monitor.