Saltar al contenido
Proyecto 03: Sistema operativo

Un gestor de pantalla con separación de privilegios.

El inicio de sesión gráfico de un sistema Linux, repartido entre un demonio root y una interfaz sin privilegios que se comunican mediante un protocolo documentado. Escrito en Rust, para un sistema runit y elogind, como la capa de inicio de sesión del trabajo sobre el sistema operativo.

RustPAMX11runitelogindD-Bus
01: La clase de problema

El programa que tiene root y es dueño de la pantalla.

Un gestor de pantalla está en una intersección poco habitual. Autentica, así que maneja credenciales. Inicia sesiones, así que tiene root. Dibuja una interfaz, así que analiza la entrada de quien esté frente al teclado, incluido quien no debería estarlo. La mayoría de los diseños de esta categoría meten las tres cosas en un solo proceso, lo que hace que un error de interfaz y un compromiso de root sean el mismo suceso.

Es también la capa bajo la que no hay nada a lo que echar la culpa. Cuando un gestor de pantalla falla, normalmente falla teniendo la pantalla, así que la superficie de diagnóstico en la que se apoyaría un programa normal es justo lo que acaba de romperse. El usuario se queda ante una pantalla negra, en su único equipo, sin un camino visible de vuelta.

Eso invierte las prioridades habituales. La frontera de privilegios y el camino de recuperación no son un endurecimiento que se añade cuando ya funciona: son el diseño, y todo lo demás se organiza a su alrededor.

02: Lo que exigió

Los requisitos que salieron de ahí.

Todos tratan de limitar hasta dónde puede llegar un fallo, porque en esta capa un fallo llega a toda la máquina.

  • Una frontera de privilegios que un error de la sesión no pueda atravesar hacia atrás
  • Credenciales que no sobrevivan al momento en que se usan
  • Ningún camino por el que una interfaz comprometida pueda iniciar una sesión
  • Recuperación tras un fallo que no dependa de que la pantalla siga funcionando
03: Las decisiones

Cómo se repartió la autoridad.

Cuatro decisiones que definen el modelo de seguridad. Cada una tiene un contrario más simple, y ese contrario más simple es lo que hace la mayor parte de la categoría.

Presentadas como decisiones y no como funcionalidades, porque cada una costó algo real. Esto es hacia qué lado se inclinó esta y lo que permitió.

  1. Tres procesos, no uno

    Un demonio root inicia un proceso de trabajo por cada inicio de sesión, que es dueño de todo el ciclo de vida de la autenticación, y ese proceso crea con fork al hijo de sesión, que renuncia de forma irreversible a los privilegios antes de ejecutar nada del usuario. El proceso que tiene el descriptor de autenticación nunca se convierte en el usuario, y el proceso que se convierte en el usuario nunca lo tiene. Un diseño de un solo proceso es mucho más simple y hace que cualquier error en él sea un error de root.

  2. La interfaz no tiene privilegios y es reemplazable

    La interfaz de bienvenida se ejecuta con su propia cuenta sin privilegios y habla con el demonio mediante un protocolo versionado sobre un socket unix, con una trama prefijada por su longitud, un tamaño máximo y una negociación de versión. No puede iniciar una sesión sin autenticar ni pasar un comando libre a través de la frontera: solo puede nombrar una sesión que el propio sistema haya descubierto. Publicar ese protocolo con una licencia permisiva es lo que permite a otra persona escribir una interfaz distinta sin heredar la licencia del demonio.

  3. Un solo hilo y bloqueante, a propósito

    Sin entorno de ejecución asíncrono, sin grupo de hilos. Un proceso que hace fork no debe tener otros hilos activos cuando lo hace, y todo el modelo depende de hacer el fork correctamente en el momento exacto en que cambian los privilegios. Renunciar aquí a la concurrencia compra la única garantía de la que el modelo de seguridad no puede prescindir.

  4. Una pantalla negra se trata como un defecto, no como un accidente

    El estado del terminal se restaura desde manejadores de señales, usando solo llamadas que es seguro hacer desde uno, en lugar de desde el código de limpieza habitual que un fallo brusco nunca alcanza, con un gancho de servicio y una comprobación en el siguiente arranque como respaldo. Las consolas de texto siguen disponibles como camino de vuelta. Es la vía de recuperación para el caso en que lo que ha fallado es justo lo que habrías usado para diagnosticarlo.

04: La contrapartida

La decisión que importó.

Una decisión por proyecto, tomada con una alternativa real sobre la mesa. Aquí se nombra la alternativa, y también lo que costó decidir en su contra.

Tres procesos donde uno habría bastado.

La autenticación, los privilegios y la interfaz se reparten entre procesos separados que se comunican mediante un protocolo definido, en lugar de convivir en un único programa como hace la mayor parte de esta categoría.

Es la decisión de la que se derivan todas las demás de la página. Una vez que la interfaz no puede llegar a la autoridad, las preguntas que quedan son sobre cómo se comunican las piezas, que es ingeniería. Si se deja en un solo proceso, ningún cuidado en el resto recupera la propiedad.

La alternativa
Un solo proceso, ejecutándose como root, que hace las tres cosas. Es la forma de la herramienta a la que este sustituye y es realmente más simple: ningún protocolo que definir, ninguna negociación de versión, ninguna serialización, ningún fallo parcial entre componentes y un ciclo de vida de sesión que se puede leer de arriba abajo en un solo archivo.
Lo que costó
Una gran cantidad de complejidad, de forma permanente, para un programa cuyo único trabajo es mostrar un cuadro de contraseña. Un formato de transmisión con negociación de versión, un socket cuyo interlocutor hay que verificar, forks en los momentos exactos y un conjunto de modos de fallo que solo existen porque las piezas están separadas. Todo ello es código que puede estar mal, en un programa en el que equivocarse sale caro.
Por qué la contrapartida se sostiene
En un solo proceso, un error en la parte que analiza el teclado es un error en la parte que tiene root, porque son el mismo espacio de direcciones. No es un perfil de riesgo hipotético, es una identidad. La separación convierte el peor caso de un compromiso total de la máquina en una interfaz caída que de todos modos no habría podido iniciar una sesión. La complejidad es un precio que se puede pagar una vez y revisar; el diseño de un solo proceso cobra su precio una sola vez y nunca lo devuelve.
Sin medición publicada

Ninguna cifra de arranque, ninguna de memoria, ningún resultado de auditoría. El argumento de seguridad anterior es estructural, trata de hasta dónde puede llegar un fallo, y nada de esto ha sido revisado de forma independiente ni verificado formalmente. Esa ausencia se dice en voz alta en lugar de disimularse: es software anterior a la v1 que presenta un argumento de diseño, no un programa con un historial de seguridad.

03: Siguiente paso

Pregúntanos por cualquiera de ellos.

Los detalles no están en esta página. Pídelos directamente, o envíanos el problema que de verdad necesitas resolver.