Las discusiones de seguridad suelen centrarse en errores individuales:

  • un desbordamiento de búfer
  • un fallo de diputado confundido
  • un parser sin comprobaciones suficientes
  • una ruta de escalada de privilegios

Esos errores importan, pero son síntomas de una pregunta más profunda:

¿Cuánto código debe ser correcto para que el sistema siga siendo seguro?

Ese código es la base de cómputo confiable, normalmente abreviada como TCB.

El tamaño y la forma de la TCB determinan cuánto código debe ser confiado, auditado, probado y razonado. Un sistema puede tener abstracciones fuertes sobre el papel, pero si esas abstracciones dependen de que una gran cantidad de código privilegiado se comporte perfectamente, el argumento de seguridad se debilita.

Esta publicación explica qué es la TCB, por qué su tamaño afecta a la superficie de ataque y cómo EriX intenta mantener el código confiable pequeño y explícito.


Qué es la TCB

La base de cómputo confiable es el conjunto de componentes cuya corrección es necesaria para que las propiedades de seguridad del sistema se mantengan.

Si un componente de la TCB queda comprometido, las garantías de seguridad del sistema pueden dejar de ser ciertas.

En un sistema operativo, la TCB suele incluir:

  • el bootloader
  • el kernel
  • servicios privilegiados del sistema
  • lógica de autenticación y autorización
  • parsers de datos confiables de arranque
  • código de verificación criptográfica
  • código que distribuye o transfiere autoridad

La TCB exacta depende del diseño del sistema.

En un kernel monolítico tradicional, gran parte del kernel suele formar parte de la TCB porque muchos subsistemas se ejecutan con privilegio completo de kernel. Un bug en un controlador, sistema de archivos o stack de red puede convertirse en un compromiso del kernel.

En un sistema microkernel, la TCB del kernel puede ser menor, pero el sistema confiable total sigue incluyendo los componentes que distribuyen autoridad y aplican política.

Esa distinción importa.

Los microkernels reducen la cantidad de código que se ejecuta con autoridad total sobre la máquina. No convierten automáticamente todos los servicios de espacio de usuario en componentes no confiables.


Confiable no significa seguro

La palabra “confiable” es fácil de malinterpretar.

Un componente confiable no es un componente que sepamos correcto.

Es un componente del que el sistema depende para ser correcto.

Esa definición es menos cómoda, pero es la útil.

Si un servicio es confiable para distribuir capacidades, un bug en ese servicio puede conceder autoridad incorrectamente. Si un parser es confiable para validar un ejecutable antes del arranque, un bug del parser puede socavar la cadena de arranque. Si un manejador de syscall del kernel es confiable para validar derechos de endpoints, una comprobación ausente puede romper el aislamiento.

La confianza no es elogio.

La confianza es riesgo.

Por eso el objetivo no es etiquetar tanto código como confiable sea posible. El objetivo es hacer que el conjunto confiable sea tan pequeño, estrecho y auditable como sea posible.


Por qué importa el tamaño

El tamaño de la TCB importa por varias razones.

1. Más código significa más bugs

Todo software tiene bugs.

A medida que crece la cantidad de código confiable, también crece la probabilidad de bugs relevantes para la seguridad. Esto es especialmente cierto para código que:

  • parsea entrada no confiable
  • gestiona memoria
  • maneja concurrencia
  • interpreta permisos
  • traduce un modelo de autoridad en otro

Los sistemas operativos contienen todos estos patrones.

Reducir el tamaño de la TCB no elimina los bugs, pero reduce la cantidad de código donde un bug puede comprometer todo el sistema.


2. Más interfaces significan más superficie de ataque

La superficie de ataque no se mide solo en líneas de código.

También se mide en puntos de entrada.

Cada interfaz hacia código confiable es un lugar donde un atacante puede proporcionar entrada:

  • argumentos de syscall
  • mensajes IPC
  • metadatos de imagen de arranque
  • cabeceras ELF
  • estructuras de sistemas de archivos
  • descriptores de dispositivos
  • eventos de interrupción
  • tablas proporcionadas por firmware

Cada interfaz necesita validación.

Un componente confiable pequeño con una interfaz mal diseñada puede seguir siendo peligroso. Pero cuando crece el número de interfaces confiables, también crece la carga de validación.


3. Más estado significa razonamiento más difícil

Los fallos de seguridad a menudo no ocurren porque falte una comprobación aislada, sino porque el estado cambia en un orden inesperado.

Por ejemplo:

  • una capacidad se copia antes de reducir sus derechos
  • un servicio se inicia antes de validar completamente su paquete de inicio
  • un slot local obsoleto se trata como prueba de autoridad
  • un dispositivo se considera presente antes de completar el descubrimiento
  • un proceso conserva autoridad después de una ruta de lanzamiento fallida

Cuanto más estado mutable confiable tiene un sistema, más difícil resulta probar que cada transición preserva las invariantes previstas.

Por eso EriX enfatiza el inicio determinista, los registros explícitos de transferencia y el comportamiento fail-closed.


4. Más privilegio significa mayor radio de impacto

El mismo bug tiene consecuencias distintas según dónde ocurra.

Un bug de parsing en una herramienta no privilegiada puede hacer caer esa herramienta.

Un bug de parsing en el bootloader puede comprometer todo el sistema antes de que arranque el kernel.

Un bug en un controlador de espacio de usuario con acceso solo a un rango específico de E/S es serio, pero no es lo mismo que un bug en un controlador que se ejecuta dentro del kernel con autoridad total sobre la máquina.

Reducir la TCB consiste en parte en reducir el radio de impacto de los bugs individuales.


El kernel es solo parte de la TCB

Es tentador decir:

La TCB es el kernel.

Normalmente eso es demasiado simple.

El kernel es central, pero un sistema seguro también depende del código que prepara el kernel, inicia la primera tarea de espacio de usuario, define formatos de autoridad y distribuye capacidades.

En EriX, el kernel está explícitamente en la TCB.

Posee las tablas de capacidades del espacio del kernel, el estado de planificación y los recursos de la máquina. Valida la entrega del bootloader al kernel, crea la tarea raíz, gestiona objetos básicos del kernel y expone puntos de entrada de trap, syscall e interrupción específicos de la arquitectura.

Si el kernel falla al aplicar comprobaciones de capacidades, derechos de endpoints, límites de espacios de direcciones o ciclos de vida de objetos, el modelo de aislamiento del sistema falla.

Pero el kernel no es toda la historia.


El código de arranque también es confiable

El bootloader se ejecuta antes del kernel.

Eso lo hace crítico para la seguridad.

En EriX, el bootloader es responsable de cargar y verificar un boot.img firmado, parsear imágenes del kernel y servicios, construir una estructura de handoff determinista y transferir el control al kernel.

Esto coloca al bootloader en la TCB.

Durante el arranque posee autoridad proporcionada por el firmware y controla el salto final al kernel. Debe tratar el medio de arranque como no confiable, validar estructura e integridad criptográfica de la imagen, rechazar binarios ELF malformados y fallar cerrado ante ambigüedad.

Si el bootloader acepta una imagen manipulada o construye un handoff inconsistente, el kernel puede empezar desde una base comprometida.

Por eso el código de arranque debe ser pequeño, estricto y aburrido.


Los parsers pueden ser fronteras de TCB

Los parsers suelen subestimarse en seguridad de sistemas.

Están exactamente en el punto donde bytes no confiables se convierten en estructura confiable.

EriX trata varios crates de parsing y ABI como componentes de la TCB o adyacentes a ella:

  • lib-bootimg parsea y verifica estructura, hashes y firmas de boot.img
  • lib-elf valida binarios ELF64 antes de que el bootloader confíe en segmentos de carga
  • lib-handoff valida estructuras de handoff versionadas entre etapas de arranque
  • lib-ipc define y valida layouts de mensajes IPC
  • lib-capabi define tipos de capacidades, derechos, constantes de slots y descriptores de transferencia

Estas bibliotecas pueden no poseer capacidades de runtime por sí mismas.

Eso no las vuelve irrelevantes para la TCB.

Si lib-bootimg acepta una imagen de arranque modificada, el bootloader puede confiar en código que debería rechazar. Si lib-elf acepta un ejecutable malformado, la cadena de arranque puede cargar bytes incorrectos o confiar en rangos de segmentos inválidos. Si lib-ipc decodifica mal un mensaje, una operación puede interpretarse incorrectamente. Si lib-capabi define una política de rol demasiado amplia, un servicio puede recibir autoridad que nunca debería poseer.

El código puro también puede ser código confiable.

La propiedad importante es que estas bibliotecas son estrechas. No realizan E/S, no poseen política del sistema y evitan autoridad ambiental. Su tarea es parsear, validar y rechazar.


Los servicios de espacio de usuario pueden ser componentes confiables

Mover la política fuera del kernel no hace que desaparezca.

La mueve a servicios de espacio de usuario donde puede aislarse, restringirse y auditarse por separado.

En EriX, rootd es la primera autoridad de espacio de usuario que porta política. Valida el handoff del kernel a root, parsea la configuración de arranque, ejecuta el DAG de inicio y transfiere capacidades de mínimo privilegio a los servicios requeridos.

rootd es de alto privilegio.

Pero no es el kernel.

Esa distinción es importante. rootd es confiable para la política temprana y la distribución de capacidades, pero no implementa objetos de kernel ni posee autoridad directa sobre la máquina. Su tarea es distribuir autoridad según contratos explícitos de inicio.

Otros servicios también se encuentran dentro de fronteras confiables específicas:

  • procd es confiable para la orquestación del ciclo de vida de procesos
  • deviced es confiable para la política de controladores y la entrega de capacidades de controladores
  • vfsd es confiable como frontera del espacio de nombres público del sistema de archivos
  • los proveedores privados de sistemas de archivos son confiables solo para su rol de proveedor

Esto no hace que esos servicios sean poco importantes.

Hace que su autoridad sea más estrecha que la autoridad completa del kernel.


Superficie de ataque en un diseño monolítico

En un kernel monolítico, muchos subsistemas comparten un único espacio de direcciones privilegiado.

Esto puede simplificar las rutas rápidas, pero también crea una superficie de ataque amplia.

Una vulnerabilidad en cualquier subsistema dentro del kernel puede convertirse en una vulnerabilidad del kernel:

  • metadatos de sistema de archivos malformados
  • manejo defectuoso de paquetes de red
  • código inseguro de controladores
  • descriptores de hardware inesperados
  • condiciones de carrera en estado compartido del kernel

El atacante solo necesita una ruta hacia código privilegiado.

Esto no significa que los kernels monolíticos no puedan ser seguros. Pueden ingenierizarse, endurecerse, fuzzearse, aislarse y auditarse extensamente.

Pero la arquitectura empieza con una superficie privilegiada grande.

El argumento de seguridad debe explicar entonces cómo se controla esa superficie grande.


Superficie de ataque en un diseño microkernel

Un microkernel cambia la forma de la superficie de ataque.

El kernel sigue exponiendo interfaces críticas:

  • syscalls
  • entrega IPC
  • planificación
  • operaciones de espacio de direcciones
  • operaciones de capacidades
  • manejo de interrupciones

Esas interfaces deben ser correctas.

Pero los servicios de nivel superior no se ejecutan automáticamente con privilegio completo de kernel. Si un proveedor de sistema de archivos maneja medios malformados, el objetivo es que el bug permanezca dentro de la autoridad de ese proveedor. Si un controlador falla, el objetivo es que falle solo con la autoridad de dispositivo que recibió explícitamente.

Esto convierte una gran superficie privilegiada en varias superficies de autoridad más pequeñas.

Eso no es automáticamente más simple.

Solo funciona si las fronteras son estrictas y la autoridad que pasa por ellas es estrecha.


Cómo EriX minimiza la TCB

EriX reduce el tamaño de la TCB y la superficie de ataque mediante varias decisiones de diseño.

1. Un kernel mínimo en política

El kernel de EriX es responsable de mecanismos, no de política del sistema.

Maneja:

  • objetos básicos del kernel
  • semántica de capacidades
  • planificación y ejecución de tareas
  • primitivas de espacios de direcciones
  • IPC y despacho de endpoints
  • puntos de entrada de interrupciones y excepciones

No posee:

  • política de inicio de servicios
  • política de orquestación de procesos
  • política del espacio de nombres de sistemas de archivos
  • política de activación de controladores
  • política de asignación de memoria de alto nivel

Esto mantiene al kernel enfocado en aplicar aislamiento en lugar de decidir la forma de todo el sistema.


2. Capacidades explícitas en lugar de autoridad ambiental

EriX modela la autoridad mediante capacidades.

Un componente solo puede actuar si posee una capacidad con los derechos requeridos. El kernel valida referencias de capacidades en cada uso, aplica derechos de endpoints durante el despacho de syscalls y trata las transferencias como eventos explícitos.

Esto evita depender de nombres globales o números de slot convencionales como permiso.

Conocer un número de slot no es autoridad.

Poseer la capacidad correcta en el CSpace local sí es autoridad.

Esa distinción es central para reducir la TCB: el código confiable no necesita inferir permisos desde estado global cuando la autoridad viaja explícitamente.


3. Endpoints estrechos de control del kernel

Los diseños de sistemas operativos anteriores a menudo concentran el control detrás de interfaces privilegiadas amplias.

EriX va en la dirección opuesta.

El runtime actual usa familias estrechas de endpoints de control del kernel para tareas específicas:

  • tiempo
  • interrupciones
  • hotplug
  • configuración PCI
  • consola y framebuffer
  • E/S COM1
  • E/S i8042
  • retyping de memoria
  • mapeo de VSpace
  • resolución de fallos del pager
  • control de procesos
  • lecturas ACPI

El arranque normal de runtime no entrega a los servicios un endpoint root amplio para todas las operaciones de kernel.

En su lugar, cada servicio recibe la familia de endpoint específica que necesita. timed recibe control de tiempo. irqd recibe control de interrupciones. drv-serial recibe E/S específica de COM1. drv-i8042 recibe E/S específica de i8042. drv-acpi recibe autoridad de lectura ACPI.

Esto estrecha tanto la interfaz confiable como el daño causado por uso indebido.


4. Contratos exactos de inicio

EriX trata la transferencia de capacidades de inicio como un contrato, no como una sugerencia.

Los sobres de inicio describen qué capacidades debe recibir un servicio, dónde deben aparecer y qué derechos deben portar.

Los servicios validan los slots locales reales que recibieron antes de declarar preparación. Las transferencias de endpoints se comprueban por slot de origen, slot de destino, derechos y tipo esperado de endpoint. Transferencias desconocidas, incorrectas o extra se rechazan.

Esto evita un bug común de autoridad:

“Un slot está ocupado, así que debe ser lo correcto.”

En EriX, la ocupación de un slot por sí sola no prueba autoridad.

La capacidad debe coincidir con la forma de autoridad declarada.


5. Creación de procesos por etapas

La creación de procesos es una operación de alto riesgo porque combina ejecución, espacios de direcciones, endpoints y autoridad inicial.

EriX enruta la creación de procesos en runtime mediante creación de hijos por etapas en lugar de una operación directa heredada.

El flujo por etapas es explícito:

  1. crear un hijo en etapa
  2. recibir un grant de instalación y un alias de endpoint del hijo
  3. instalar el paquete declarado de capacidades de inicio
  4. iniciar el proceso solo cuando la población esté completa

El kernel deniega el inicio del proceso mientras existan grants de instalación vivos dirigidos a esa etapa de hijo.

Esto impide que procesos parcialmente poblados se vuelvan ejecutables con un estado de autoridad ambiguo.


6. Autoridad de controladores específica por rol

Los controladores son una gran fuente de riesgo en sistemas operativos.

EriX no trata todo control de hardware como un único permiso amplio.

La autoridad de controladores es específica por rol:

  • drv-serial recibe autoridad de E/S solo para COM1
  • drv-i8042 recibe autoridad de E/S solo para i8042
  • drv-acpi recibe autoridad de lectura ACPI
  • probed recibe autoridad de lectura de configuración PCI
  • drv-virtio-block recibe un frame de dispositivo validado en lugar de autoridad general de memoria

deviced gestiona la política de controladores, pero no entrega simplemente a cada controlador una capacidad genérica de control de dispositivos. Usa instalación explícita de capacidades de inicio y superficies de autoridad específicas por rol.

Esto limita el impacto de bugs de controladores sobre la TCB.


7. La memoria de dispositivo es un tipo separado de capacidad

La memoria de dispositivo es peligrosa porque puede afectar directamente el estado del hardware.

EriX distingue la memoria de dispositivo de la RAM ordinaria mediante CAP_TYPE_DEVICE_FRAME.

La ruta de almacenamiento puede derivar un frame MMIO respaldado por BAR para deviced, y deviced puede instalar solo ese frame de dispositivo derivado en el paquete de inicio de un controlador.

Ese frame de dispositivo no se trata como memoria asignable ordinaria.

Esto importa porque confundir memoria de dispositivo con frames normales ensancharía el modelo de autoridad y haría más difícil razonar sobre seguridad de memoria.


8. Dependencias de sala limpia

Las dependencias pueden ampliar la TCB silenciosamente.

Si el código confiable depende de una biblioteca externa de propósito general, el sistema puede heredar:

  • rutas de código que no necesita
  • supuestos que no coinciden con el OS
  • comportamiento de parsing demasiado permisivo
  • riesgo de actualizaciones y cadena de suministro

EriX evita crates de terceros e implementa sus bibliotecas críticas dentro del proyecto.

Esto aumenta el trabajo de implementación, pero mantiene visible la frontera confiable. Cuando un parser, crate de ABI o helper criptográfico forma parte de la ruta de arranque o autoridad, forma parte de la superficie de revisión del sistema.


9. Comportamiento fail-closed

Una TCB pequeña no basta si los fallos son ambiguos.

EriX intenta hacer que los fallos sensibles para la seguridad sean explícitos y terminales:

  • un handoff de arranque malformado detiene el arranque
  • las versiones no soportadas se rechazan
  • la autoridad de inicio inválida impide la preparación
  • los tipos incorrectos de endpoint fallan la validación
  • el fallo de un servicio requerido detiene la progresión
  • los fallos posteriores al inicio activan teardown fail-closed

Fallar cerrado es importante porque las heurísticas de recuperación suelen convertirse en política oculta.

La política oculta amplía el comportamiento confiable del sistema.


Más pequeño no significa trivial

Reducir la TCB no hace fácil el diseño del sistema.

A menudo lo hace más explícito.

En lugar de colocar toda la lógica en un único espacio de direcciones privilegiado, el sistema debe definir:

  • qué componente posee cada decisión
  • qué autoridad recibe cada componente
  • cómo se transfiere la autoridad
  • cómo se informan los fallos
  • cómo se limpia un inicio parcial
  • cómo se invalidan o eliminan capacidades obsoletas

Esto es más trabajo al principio.

Pero produce un sistema donde el argumento de seguridad es más fácil de inspeccionar.

La pregunta se vuelve:

¿Qué componente debe ser confiable para esta propiedad específica?

Esa es una pregunta mejor que:

¿Es correcto todo el kernel?


Una vista práctica de la TCB de EriX

Una vista práctica de la TCB de EriX se ve por capas.

En la base está la cadena de arranque:

  • bootloader
  • verificación de imagen de arranque
  • ruta de lectura/verificación de lib-bootimg
  • parsing ELF
  • validación de handoff

Luego el kernel:

  • objetos de capacidades
  • aplicación de CSpace y VSpace
  • IPC y derechos de endpoints
  • planificación y manejo de traps
  • entrega de interrupciones

Luego el espacio de usuario confiable temprano:

  • rootd para política de inicio y distribución de capacidades
  • procd para ciclo de vida de procesos
  • deviced para política de controladores
  • bibliotecas seleccionadas de ABI y validación compartidas entre servicios

Luego servicios confiables más estrechos:

  • mediación del espacio de nombres de sistemas de archivos en vfsd
  • proveedores backend privados
  • servicios de entrada, consola, logging, bloques, tiempo e interrupciones

No todos estos componentes tienen el mismo privilegio.

Ese es el punto.

EriX intenta evitar un único mundo confiable plano. En su lugar, cada componente debería ser confiable solo para su rol documentado y solo con las capacidades que recibió explícitamente.


Mirando hacia delante

La discusión sobre la TCB conduce naturalmente al lenguaje de implementación.

Si el código confiable debe ser pequeño, explícito y auditable, entonces la seguridad de memoria importa. También importan los límites unsafe, la corrección de parsers, el layout de datos y la disciplina necesaria al trabajar cerca del hardware.

La próxima publicación examinará por qué EriX está escrito principalmente en Rust, qué resuelve y qué no resuelve Rust para el desarrollo de kernels, y cómo se compara con el enfoque tradicional en C para la programación de sistemas.