Aller au contenu
Réalisation 03 : Système d'exploitation

Un gestionnaire d'affichage à privilèges séparés.

Le programme de connexion graphique d'un système Linux, réparti entre un démon root et une interface sans privilèges qui dialoguent par un protocole documenté. Écrit en Rust, pour un système runit et elogind, comme couche de connexion du travail sur le système d'exploitation.

RustPAMX11runitelogindD-Bus
01 : La classe de problème

Le programme qui détient root et possède l'écran.

Un gestionnaire d'affichage se trouve à une intersection inhabituelle. Il authentifie, donc il manipule des identifiants. Il démarre des sessions, donc il détient root. Il dessine une interface, donc il analyse les entrées de quiconque se trouve devant le clavier, y compris de qui ne devrait pas s'y trouver. La plupart des conceptions de cette catégorie réunissent les trois dans un seul processus, ce qui fait d'un bogue d'interface et d'une compromission de root un seul et même événement.

C'est aussi la couche sous laquelle il n'y a rien sur quoi rejeter la faute. Quand un gestionnaire d'affichage tombe en panne, il le fait généralement en tenant l'affichage : la surface de diagnostic sur laquelle un programme ordinaire s'appuierait est précisément ce qui vient de casser. L'utilisateur se retrouve devant un écran noir, sur son unique ordinateur, sans chemin visible pour revenir.

Cela inverse les priorités habituelles. La frontière de privilèges et le chemin de reprise ne sont pas un durcissement à ajouter une fois que ça marche : ils sont la conception, et tout le reste s'organise autour d'eux.

02 : Ce qu'il a exigé

Les exigences qui en découlent.

Chacune consiste à limiter ce qu'une défaillance peut atteindre, car à cette couche, une défaillance atteint toute la machine.

  • Une frontière de privilèges qu'un bogue de session ne peut pas franchir en sens inverse
  • Des identifiants qui ne survivent pas à l'instant où ils sont utilisés
  • Aucun chemin par lequel une interface compromise puisse démarrer une session
  • Une reprise après plantage qui ne dépend pas du bon fonctionnement de l'écran
03 : Les décisions

Comment l'autorité a été répartie.

Quatre choix qui définissent le modèle de sécurité. Chacun a un contraire plus simple, et c'est ce contraire plus simple que retiennent la plupart des logiciels de la catégorie.

Présentés comme des décisions plutôt que comme des fonctionnalités, parce que chacune a coûté quelque chose de réel. Voici dans quel sens celle-ci a tranché et ce que cela a permis.

  1. Trois processus, pas un seul

    Un démon root démarre un processus de travail par connexion, qui possède tout le cycle de vie de l'authentification, et ce processus crée par fork l'enfant de session, qui abandonne définitivement ses privilèges avant d'exécuter quoi que ce soit de l'utilisateur. Le processus qui détient le descripteur d'authentification ne devient jamais l'utilisateur, et le processus qui devient l'utilisateur ne le détient jamais. Une conception à processus unique est bien plus simple et fait de tout bogue qu'elle contient un bogue root.

  2. L'interface est sans privilèges et remplaçable

    L'interface d'accueil s'exécute sous son propre compte sans privilèges et dialogue avec le démon par un protocole versionné sur socket unix, avec une trame préfixée par sa longueur, une taille maximale et une négociation de version. Elle ne peut pas démarrer de session non authentifiée et ne peut pas faire passer une commande libre à travers la frontière : elle peut seulement désigner une session que le système a lui-même découverte. Publier ce protocole sous une licence permissive est ce qui permet à quelqu'un d'écrire une autre interface sans hériter de la licence du démon.

  3. Monothread et bloquant, délibérément

    Pas d'environnement d'exécution asynchrone, pas de pool de threads. Un processus qui fait un fork ne doit pas avoir d'autres threads actifs à ce moment-là, et tout le modèle repose sur un fork correct à l'instant exact où les privilèges changent. Renoncer à la concurrence ici procure la seule garantie dont le modèle de sécurité ne peut pas se passer.

  4. Un écran noir est traité comme un défaut, pas comme un accident

    L'état du terminal est restauré depuis des gestionnaires de signaux, en n'utilisant que des appels sûrs dans ce contexte, plutôt que depuis le code de nettoyage ordinaire qu'un plantage brutal n'atteint jamais, avec un crochet de service et une vérification au démarrage suivant en renfort. Les consoles texte restent disponibles comme chemin de retour. C'est la voie de reprise pour le cas où ce qui a échoué est précisément ce qui aurait servi à diagnostiquer.

04 : Le compromis

La décision qui a compté.

Un choix par réalisation, fait avec une vraie alternative sur la table. L'alternative est nommée ici, et le coût d'avoir choisi contre elle aussi.

Trois processus là où un seul aurait suffi.

L'authentification, les privilèges et l'interface sont répartis entre des processus distincts qui dialoguent par un protocole défini, au lieu de cohabiter dans un seul programme comme le font la plupart des outils de cette catégorie.

C'est la décision dont découle tout le reste de la page. Dès lors que l'interface ne peut pas atteindre l'autorité, les questions restantes portent sur la manière dont les éléments communiquent, ce qui relève de l'ingénierie. Si tout reste dans un seul processus, aucun soin apporté au reste ne rétablit la propriété.

L'alternative
Un seul processus, exécuté en root, qui fait les trois. C'est la forme de l'outil que celui-ci remplace et il est réellement plus simple : aucun protocole à définir, aucune négociation de version, aucune sérialisation, aucune défaillance partielle entre composants, et un cycle de vie de session qu'on peut lire de haut en bas dans un seul fichier.
Ce que cela a coûté
Une grande quantité de complexité, définitivement, pour un programme dont tout le travail est d'afficher une boîte de mot de passe. Un format d'échange avec négociation de version, un socket dont l'interlocuteur doit être vérifié, des appels à fork aux moments exacts, et un ensemble de modes de défaillance qui n'existent que parce que les éléments sont séparés. Tout cela est du code qui peut être faux, dans un programme où se tromper coûte cher.
Pourquoi le compromis tient
Dans un processus unique, un bogue dans la partie qui analyse les touches du clavier est un bogue dans la partie qui détient root, parce que c'est le même espace d'adressage. Ce n'est pas un profil de risque hypothétique, c'est une identité. La séparation fait passer le pire cas d'une compromission complète de la machine à une interface plantée qui n'aurait de toute façon pas pu démarrer de session. La complexité est un prix qui peut être payé une fois et relu ; la conception à processus unique ne facture son prix qu'une fois, et ne le rembourse jamais.
Aucune mesure publiée

Aucun chiffre de démarrage, aucun chiffre de mémoire, aucun résultat d'audit. L'argument de sécurité ci-dessus est structurel, il porte sur ce qu'une défaillance peut atteindre, et rien ici n'a été relu de façon indépendante ni vérifié formellement. Cette absence est énoncée plutôt que masquée : c'est un logiciel antérieur à la v1 qui avance un argument de conception, pas un programme doté d'un historique de sécurité.

03 : Prochaine étape

Interrogez-nous sur l'une ou l'autre de ces réalisations.

Les détails ne figurent pas sur cette page. Demandez-les directement, ou envoyez-nous le problème que vous avez réellement besoin de résoudre.