ADR-146 — Forwarding serveur en privsep par médiation du monitor
Statut : Accepté (2026-07-31, décision BDFL). Le quoi (« le serveur en privsep doit faire du forwarding ») et le comment (« par médiation du monitor », option B) ont été tranchés par le BDFL. Cet ADR fixe la conception avant tout code, sur le modèle des mini-ADR de conception ADR-124/ADR-129/ADR-139.
S’appuie sur ADR-122 (topologie privsep), ADR-124 (canal RPC pré-auth
monitor↔enfant), ADR-129 (transition post-auth), ADR-131 (worker post-auth confiné),
ADR-132 (io_uring DONTFORK), ADR-089 (autorité SecurityManager), ADR-117
(redirections et multiplexing), ADR-118 (politique de forwarding côté serveur),
ADR-097 (§C.5 multiplexing), ADR-128 (options et permissions).
Catégorie : conception d’architecture couche 2 (air-sshd). Aucun code ici :
topologie du canal, découpage en incréments, invariants de sécurité. Aucun
descellement, aucun re-sceau.
Contexte
En privsep, air-sshd sert la session post-auth via un worker confiné : bascule
setuid vers l’utilisateur cible, sandbox en mode drop-only ou cage SFTP. Ce worker
est mono-fil, sans réacteur — il travaille sur un ppoll bloquant
(blocking.rs) — et il refuse aujourd’hui tout forwarding
(connection/mod.rs:350, connection/mod.rs:585, garde is_deferred()).
Deux murs expliquent ce refus.
- Pas de concurrence. Le forwarding exige de suivre en même temps le transport SSH et le socket cible. Le worker ne sait faire qu’une chose à la fois.
- La sandbox tue le réseau. Les syscalls
socket,connect,bindetlistensont hors des profilspre_authetsftp_worker; seulrecvmsgest permis au worker.
À cela s’ajoute une contrainte doctrinale qui ferme la sortie de secours évidente :
aucun anneau io_uring en étage confiné (ADR-131 §153, ADR-132). Un anneau
contourne seccomp et échappe à Landlock — il est l’évasion, pas un moyen de l’éviter.
Conséquence pratique : le serveur Air réellement déployé (celui en privsep) ne sait pas faire de forwarding, alors que le chemin monolithique, lui, le sait.
Décision
Médiation par le monitor (option B). Deux volets.
1. Les syscalls réseau sont délégués au monitor
Le worker ne fait aucun socket, connect, bind ni listen. Il demande au
monitor (le seul étage privilégié) via un canal RPC dédié — un socketpair
établi avant la transition et survivant à la purge de descripteurs de celle-ci
(transition.rs:309).
Le monitor, et lui seul :
- applique la politique —
AllowTcpForwarding,PermitOpen,PermitListen— sous l’autorité duSecurityManager(ADR-089, ADR-118) ; - fait le syscall réseau ;
- rend le descripteur au worker par
SCM_RIGHTS.
Le worker reçoit donc un fd déjà ouvert, déjà autorisé, sans jamais gagner le droit de l’ouvrir lui-même.
2. La concurrence vient d’une pompe bloquante multi-fd
Dans le worker, une pompe bloquante multi-fd — extension directe du motif ppoll
déjà en place pour le PTY interactif (worker.rs:336) — multiplexe les canaux SSH et
route transport↔fd. Sans io_uring, ce qui respecte la doctrine anti-anneau.
Corollaire assumé : les moteurs monolithiques existants (forward_pump, tcpip_forward),
écrits sur air-async/io_uring, ne sont pas réutilisés dans le worker. On écrit la
version bloquante.
Découpage
| Incrément | Contenu | Résultat visé |
|---|---|---|
| V2.6-1 | Canal RPC worker↔monitor, opération ForwardConnect (-L, direct-tcpip), pompe bloquante multi-fd pour le cas -N (forward-only) | air-ssh -N -L fonctionne contre un serveur Air en privsep |
| V2.6-2 | -R : ForwardListen, boucle d’accept médiée par le monitor, forwarded-tcpip | forwarding distant |
| V2.6-3 | Streamlocal Unix médié, puis coexistence session + forwards (le §C.5 d’ADR-097, portée (ii)) | parité fonctionnelle |
La portée (i) — forwarding isolé — se traite d’abord ; la coexistence avec une session active vient après.
Invariants de sécurité
- Le monitor est l’autorité de politique. Il vérifie lui-même, ne se fie jamais au worker, recheck systématiquement, et échoue fermé (ADR-122).
- La RPC worker↔monitor est avare : deux à trois opérations, dans l’esprit de
start_session. Pas de surface généraliste. - La sandbox du worker reste minimale : on n’ouvre pas
socket/connect/bindau worker.recvmsgetSCM_RIGHTSsont déjà permis — rien à élargir. - Aucun anneau io_uring en étage confiné (ADR-131, ADR-132).
- Un échec d’ouverture se solde par le refus du canal, jamais par la mort de la session.
port_forwardingreste la porte de politique, issue de la contrainte (ADR-128).
Alternatives rejetées
(A) Mini-réacteur bloquant dans le worker, faisant lui-même les syscalls réseau.
Exigerait d’élargir la sandbox du worker à socket/connect/bind et de découpler
les moteurs d’air-async. Rejeté : élargir la sandbox de l’étage confiné est
strictement moins sûr que médier par l’étage privilégié.
(C) Réacteur io_uring dans le worker shell/exec. Rejeté : rompt la doctrine anti-anneau (ADR-131, ADR-132) et n’aide en rien SFTP.
Conséquences
- Le serveur
air-sshddéployé (privsep) fera enfin du forwarding, sans affaiblir le confinement du worker. - Nouvelle surface RPC privilégiée : à concevoir avec le même soin que le reste du monitor — codec sans-IO, fuzzé, avare en opérations.
- La politique d’ADR-118 (
AllowTcpForwarding,PermitOpen,PermitListen) s’appliquera côté monitor, à l’endroit où elle a l’autorité pour être opposable.