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

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 sessionsPourquoi
exécution sur /usr, /lib, /lib64, /bince que l’utilisateur lance depuis son shell
{home} en lecture-écriturele 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 lectureADR-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 :

  1. 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.
  2. 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 root est 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-sshd compromis se réduit à ce qu’air-sshd fait ;
  • l’exemption transitoire change de titulaire : elle n’est plus sur un service, mais sur les sessions, là où elle sert, et air-bundle exemptions le 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 -tt rendant « 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-launchd lui-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 lanceur fork avant 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 C n’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 un pidfd au monitor, ne suffit pas : waitid sur un pidfd exige d’être le parent.
  • Q4 — Les comptes admis. Refuser root et les comptes système par défaut au lanceur, ou laisser cette politique au seul monitor (configuration PermitRootLogin) ? Recommandé : le lanceur refuse root en 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

  1. Mesurer ce dont le démon a besoin sans ses sessions : strace et le filtre en mode journal sur carbon et neon, pour fixer la cage cible du démon avec des chiffres.
  2. Écrire l’ADR qui tranche Q1 à Q5 et marque le non-objectif d’ADR-147 comme dépassé.
  3. Le protocole : SpawnSession, SessionSpawned, SessionRefused, SessionExited dans air-launch-proto, fuzzés comme le reste du protocole.
  4. 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é.
  5. Le monitor : il demande au lieu de lancer. Bascule unique.
  6. Le manifeste d’air-sshd : retirer transitionalGlibc et les droits du §1, puis vérifier sur carbon et neon que les sessions ouvrent, que bash, apt, git et vim fonctionnent, et que air-bundle exemptions montre 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 alize ni 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-sshd sans séparation de privilèges), qui n’a pas de monitor.