Todo sistema operativo comienza antes de ser realmente él mismo. La CPU arranca en un entorno definido por la plataforma, el firmware inicializa suficiente hardware para cargar el primer ejecutable, y ese ejecutable prepara la máquina para el kernel. Solo después de que esta cadena haya hecho su trabajo puede el sistema operativo empezar a imponer sus propias reglas.

Es fácil tratar ese camino inicial como simple fontanería, pero el proceso de arranque forma parte del modelo de seguridad. Para EriX, el boot es el primer lugar donde bytes no confiables se convierten en ejecución confiable, y también es el primer lugar donde la autoridad de la máquina se traduce en estructura explícita: imágenes verificadas, mapas de memoria, descriptores de módulos, direcciones de entrada, metadatos de framebuffer, punteros ACPI y, con el tiempo, objetos del kernel.

Si este camino es descuidado, el sistema de capacidades empieza desde una mentira. Este post recorre el camino de arranque de EriX desde el firmware hasta el kernel: qué proporciona UEFI, qué debe hacer el bootloader, qué contiene el handoff y por qué se entra al kernel como un ejecutable de la mitad alta.


El boot comienza con autoridad de firmware

En los objetivos actuales de EriX, el camino de arranque empieza con UEFI en x86_64. UEFI es firmware, y se ejecuta antes del sistema operativo. Proporciona los servicios de boot que permiten a una aplicación EFI leer archivos, asignar memoria, inspeccionar el mapa de memoria de la plataforma, descubrir tablas de configuración como ACPI, consultar salida gráfica mediante GOP y salir de los servicios de boot antes de que el kernel tome el control.

El bootloader de EriX está construido como una aplicación UEFI, así que el firmware carga primero el bootloader y llama a su punto de entrada UEFI. En la implementación, ese punto de entrada es efi_main, usando la convención de llamada UEFI para x86_64. En este momento EriX aún no controla la máquina; se está ejecutando dentro de un entorno proporcionado por el firmware.

El bootloader puede pedirle a UEFI que lea un archivo, asigne memoria, inspeccione tablas del firmware y prepare tablas de páginas, pero toda esa autoridad es temporal. Los servicios de boot de UEFI no son el sistema operativo. Son andamiaje que el bootloader debe usar con cuidado, resumir en hechos explícitos y luego dejar atrás.


El bootloader es código de confianza

El bootloader se ejecuta antes de que el kernel pueda imponer nada, lo que lo coloca dentro de la base de computación confiable. Si el bootloader carga los bytes equivocados, salta a la dirección equivocada, acepta una imagen manipulada, etiqueta mal la memoria o inventa autoridad que no proviene de entrada verificada, el kernel empieza desde una base comprometida. Para mantener ese riesgo auditable, EriX mantiene el trabajo del bootloader estrecho y explícito:

  • localizar y cargar boot.img
  • validar la imagen antes de confiar en ella
  • validar el dynamic boot store
  • reubicar el kernel dinámico y sus objetos tempranos requeridos
  • construir una estructura de handoff determinista
  • preparar tablas de páginas y una pila de bootstrap
  • salir de los servicios de boot de UEFI
  • saltar al kernel una sola vez

Esto deliberadamente no es un entorno de boot de propósito general. No hay política de menú de arranque en el alcance actual, no hay ruta de recuperación remota y no hay intento de continuar después de una entrada malformada crítica para el boot. La regla es verificar antes de ejecutar: si la imagen de boot no se puede parsear, verificar, cargar, mapear o describir de forma coherente, el intento de arranque falla antes de que el kernel reciba el control.


Cargar boot.img

El camino UEFI actual busca este archivo en el volumen de boot. La ruta es fija para el perfil actual, lo que mantiene estrecho el manejo temprano del medio y evita convertir el boot en un problema de descubrimiento de política:

\ERIX\BOOT.IMG

El bootloader abre el volumen de boot mediante protocolos de archivo UEFI, lee el archivo en memoria asignada por UEFI y hace una primera comprobación barata del magic de contenedor ERIXBOOT antes del parseo más profundo. Después, lib-bootimg toma el relevo: el bootloader parsea la imagen como un BootImage, verifica su firma y sus hashes, comprueba la arquitectura del manifiesto y solo entonces empieza a extraer payloads.

Esta separación importa porque el medio de boot no es confiable. El archivo puede faltar, estar truncado, estar malformado o haber sido manipulado, y el bootloader no puede tratar un descriptor de sección como verdadero solo porque vino del disco. En EriX, el parser y verificador de boot.img forman parte del camino de boot confiable, y deben rechazar mala estructura, malos límites, malos hashes, versiones no soportadas y datos de arquitectura incompatibles antes de que cualquier byte cargado se vuelva ejecutable.

Los próximos posts entrarán más a fondo en el formato boot.img y la verificación de imágenes. Para este post, el punto importante es el orden:

  1. leer bytes
  2. parsear estructura
  3. verificar integridad
  4. comprobar compatibilidad con el objetivo
  5. validar y reubicar artefactos dinámicos de boot
  6. construir metadatos de handoff

La ejecución viene después de la validación, no antes. Ese orden es la diferencia entre tratar la imagen de boot como una entrada y tratarla como autoridad.


Cargar el kernel

Una vez aceptado boot.img, el bootloader necesita un kernel, y en el camino de boot de EriX el kernel se carga desde el dynamic boot store firmado. El bootloader valida el kernel dinámico como un objeto ELF64 ET_DYN, comprueba los metadatos de dynamic-link de EriX, verifica nombres de dependencias y hashes de objetos contra metadatos firmados, carga objetos compartidos requeridos, aplica reubicaciones aprobadas, impone restricciones W^X y prepara un catálogo de boot dinámico para el kernel.

Eso suena a mucho porque es mucho, pero la forma de seguridad sigue siendo directa y revisable:

  • los datos provienen de una imagen de boot verificada
  • el parseo del formato ejecutable es explícito
  • los rangos de segmentos se comprueban
  • los efectos secundarios de reubicación están restringidos
  • los metadatos dinámicos ausentes o inconsistentes fallan antes de la entrada al kernel

El bootloader existe para validar y reubicar el kernel dinámico, y luego describir al kernel el grafo de objetos tempranos verificado. No se convierte en un enlazador dinámico general para el sistema en ejecución; ese papel pertenece más tarde, después de que existan el kernel y el modelo de servicios en espacio de usuario.


Preservar metadatos de boot

El kernel dinámico no es lo único dentro de la imagen de boot. El bootloader también preserva metadatos de boot no ejecutables requeridos por el kernel y el sistema temprano en espacio de usuario, mientras que los servicios ejecutables pertenecen al grafo de objetos dinámico. Esos artefactos ejecutables son descritos por el catálogo dinámico como objetos, segmentos y aristas de dependencia derivadas del store y del manifiesto firmados.

Otras secciones requeridas son blobs no ejecutables. Los ejemplos incluyen configuración de boot, stores de metadatos de dynamic-link y datos de fuente de consola. Se copian en memoria mapeada y se describen como módulos de solo lectura sin punto de entrada, porque un blob no es ejecutable solo porque aparece en la imagen de boot.

Esa distinción es importante para el diseño de capacidades. Un payload de configuración de boot debería ser legible por el sistema temprano, pero no debería tratarse como código. Un blob de fuente puede ser necesario para la continuidad del framebuffer, pero no necesita autoridad de ejecución. EriX preserva esta distinción en el handoff:

  • descriptores de objetos dinámicos para artefactos ejecutables
  • descriptores de segmentos dinámicos para rangos ejecutables y de datos mapeados
  • descriptores de dependencias dinámicas para relaciones de objetos tempranos
  • SECTION_TYPE_BOOT_CONFIG para datos de política de boot
  • descriptores de módulos para blobs de boot no ejecutables

El handoff lleva esta diferencia hacia adelante para que el kernel y rootd no tengan que adivinar si una pieza de datos de boot es autoridad ejecutable, configuración de solo lectura o metadatos ordinarios.


El handoff es el contrato

El bootloader no llama a una API del kernel, porque todavía no hay ningún kernel en ejecución. En su lugar, el bootloader construye un blob de handoff: un contrato binario versionado definido por lib-handoff. En el perfil actual de bootloader a kernel, empieza con el magic ERIXHK01, campos de versión mayor y menor, tamaño total, identificadores de arquitectura y plataforma, y luego offsets y conteos para las tablas que siguen.

El handoff puede incluir varias clases de datos que el kernel necesita antes de poder construir su propia vista runtime de la máquina:

  • entradas normalizadas del mapa de memoria
  • descriptores de módulos cargados
  • puntero ACPI RSDP
  • build ID y hash de imagen verificados
  • metadatos de continuidad de framebuffer
  • descriptores de objetos dinámicos
  • descriptores de segmentos dinámicos
  • aristas de dependencia dinámicas

Esta es la primera transferencia estructurada de autoridad. El firmware le dio al bootloader hechos crudos y servicios temporales, y la imagen de boot verificada le dio al bootloader metadatos de payload firmados. El bootloader combina todo eso en una descripción determinista de qué cargó, dónde lo colocó y en qué puede confiar el kernel.

El handoff no es una pista ni metadatos “best effort”. Es la entrada a partir de la cual el kernel empieza a construir su propia visión de la máquina, y por eso lleva conteos, tamaños de entrada, offsets, hashes, tipos, rangos y campos de versión. Debe ser posible que el kernel lo rechace.


Los mapas de memoria necesitan normalización

Los mapas de memoria del firmware no están automáticamente modelados para la política del kernel. UEFI informa regiones con tipos de memoria de firmware, mientras que el bootloader también conoce la memoria que asignó para el kernel dinámico, mapeos de objetos tempranos, blobs de boot requeridos, pila de bootstrap, páginas de handoff, mapeos de framebuffer y la imagen de boot original. Esas vistas deben fusionarse antes de que el kernel pueda razonar sobre la memoria disponible.

En EriX, el bootloader captura el mapa de memoria UEFI, añade regiones explícitamente poseídas por el boot y normaliza el resultado en rangos no superpuestos con tipos de memoria de EriX como:

  • RAM usable
  • memoria reservada
  • memoria ACPI reclaimable
  • memoria ACPI NVS
  • memoria MMIO/dispositivo
  • memoria poseída por el bootloader
  • memoria poseída por la imagen de boot

Las regiones explícitamente poseídas por el boot ganan sobre clasificaciones genéricas del firmware. Eso importa porque el kernel no debe tratar accidentalmente la memoria que contiene la imagen de boot, el blob de handoff, los mapeos de objetos dinámicos, los blobs de boot requeridos o la pila de bootstrap como RAM libre ordinaria. La memoria es autoridad en EriX, así que incluso tan temprano el sistema cuida quién puede reutilizar qué bytes.


Dejar atrás UEFI

Los servicios de boot de UEFI son útiles, pero temporales. Antes de saltar al kernel, el bootloader llama a ExitBootServices, que en la práctica es una transición de una sola dirección. Después de una salida exitosa, el bootloader debe tratar los punteros a servicios de boot como inválidos; no puede seguir pidiéndole al firmware que asigne memoria o lea archivos después de que el sistema operativo toma el control.

Esta transición es delicada porque UEFI requiere que el bootloader salga usando una clave actual del mapa de memoria. Si el mapa cambia entre la captura y la salida, la llamada puede fallar y el loader debe reintentar con un mapa fresco. El adaptador UEFI de EriX maneja ese bucle de reintento en la capa de plataforma.

El punto de diseño importante es que se entra al kernel después de que el bootloader ha terminado de usar servicios de firmware. El kernel no debería heredar una dependencia de firmware medio abierta; debería heredar datos explícitos.


Construir las primeras tablas de páginas

Se entra al kernel con la paginación ya activa. Para el perfil UEFI x86_64 actual, el bootloader construye tablas de páginas mínimas con dos tipos de mapeos: una región de memoria baja mapeada por identidad para el bring-up temprano y mapeos específicos de la mitad alta para los objetos que el kernel usará inmediatamente.

La implementación actual mapea por identidad el primer 1 GiB usando páginas de 2 MiB y también mapea por identidad las ventanas MMIO de APIC. Luego mapea rangos virtuales de la mitad alta para el kernel, objetos dinámicos cargados, blobs de boot requeridos, blob de handoff, framebuffer y pila de bootstrap. Eso es suficiente para que el kernel empiece en el espacio de direcciones que espera.

Las tablas de páginas no son el sistema final de memoria virtual. Son un puente que permite al kernel ejecutarse, validar el handoff, instalar estado temprano de CPU y empezar a construir el entorno real de kernel y espacio de usuario. El puente todavía debe ser correcto: si la página de entrada del kernel no está mapeada, la máquina falla inmediatamente; si el blob de handoff está mapeado en la dirección virtual equivocada, el kernel lee basura; si falta la pila, la entrada falla antes de que el código Rust pueda hacer mucho.


Por qué un kernel de mitad alta

EriX entra al kernel en la mitad alta del espacio de direcciones virtual. Eso significa que el kernel se ejecuta en direcciones virtuales altas en vez de estar enlazado y ejecutado solo en el rango bajo mapeado por identidad. Es un diseño común de kernel porque le da al kernel una región de direcciones virtuales estable independiente de dónde se haya asignado la memoria física.

El layout de mitad alta también separa el espacio virtual del kernel de los rangos ordinarios de espacio de usuario y permite que el kernel mantenga presentes sus propios mapeos a través de cambios de espacio de direcciones más tarde, mientras que los mapeos de espacio de usuario pueden variar por tarea. El layout de direcciones no impone por sí solo todo el modelo de seguridad, pero apoya el límite al hacer que la memoria del kernel sea distinta de la memoria normal de proceso.

En EriX, el bootloader es responsable de hacer posible la entrada inicial en la mitad alta. Carga el kernel según direcciones virtuales ELF, mapea esas direcciones virtuales a páginas físicas asignadas, crea una pila de bootstrap en la mitad alta, mapea el blob de handoff en una dirección conocida de la mitad alta, carga cr3 y salta al punto de entrada del kernel.

En el salto, el contrato x86_64 actual es intencionalmente pequeño y explícito. El bootloader suministra solo el estado que el kernel necesita para empezar a validar el handoff e instalar su propio estado temprano de CPU:

  • ABI SysV
  • rdi contiene el puntero de handoff
  • rsp apunta a la pila de bootstrap con la alineación esperada
  • la paginación está activa
  • el control no retorna

Esa ABI es pequeña por diseño. Cuanto menos estado implícito dependa de la entrada del kernel, más fácil es auditar el límite.


Entrar al kernel

Del lado del kernel, la entrada comienza antes de que exista el runtime completo del kernel. El kernel dinámico expone erix_dynlink_entry, que entra en el camino temprano del kernel con el puntero de handoff, y el primer trabajo es defensivo más que cargado de política.

El kernel desactiva interrupciones, inicializa salida serial temprana, comprueba que el puntero de handoff no sea nulo, lee el tamaño del handoff desde la cabecera, impone un tamaño máximo de handoff y luego valida todo el blob mediante lib-handoff. Después de la validación estructural, el kernel aplica sus propias comprobaciones de política:

  • la arquitectura debe ser x86_64
  • la plataforma debe ser UEFI
  • el build ID no debe estar vacío
  • las tablas del catálogo dinámico deben ser internamente coherentes
  • nombres de objetos dinámicos, hashes, segmentos, dependencias y rangos del store deben validarse antes de usarse

El bootloader ya construyó el handoff, pero el kernel todavía lo valida. Los componentes de confianza no pueden saltarse contratos solo porque otro componente de confianza produjo los datos. El sentido de un handoff versionado es que ambos lados puedan ponerse de acuerdo exactamente sobre qué se transfirió.

Solo después de eso el kernel avanza más dentro de la inicialización temprana: configuración de GDT, configuración de IDT, configuración del camino de syscall, inicialización opcional de consola temprana y finalmente orquestación de bootstrap para la primera tarea root. El kernel se convierte gradualmente en el kernel, y lo primero que hace es comprobar el suelo bajo sus pies.


El boot es traducción de autoridad

Es tentador describir el boot como “cargar el kernel y saltar”. Eso es técnicamente cierto, pero pierde el punto de diseño del sistema operativo. Para EriX, el boot es traducción de autoridad: la autoridad de firmware se convierte en hechos explícitos de boot, los bytes de la imagen de boot se convierten en secciones verificadas, los archivos ELF se convierten en rangos ejecutables mapeados, los blobs se convierten en descriptores de módulos no ejecutables, los mapas de memoria del firmware se convierten en regiones de memoria normalizadas, los metadatos de dynamic-link se convierten en un grafo de objetos acotado y el estado de framebuffer se convierte en metadatos de continuidad.

Todo eso se convierte en un blob de handoff, y el kernel recibe ese blob y decide si es aceptable. Solo entonces puede empezar a convertir recursos de la máquina en objetos del kernel, capacidades, espacios de direcciones, endpoints y la primera tarea de espacio de usuario. Por eso el proceso de boot pertenece a una discusión sobre sistemas operativos de capacidades: el modelo de capacidades no empieza después del boot como un añadido; depende de que el boot no introduzca autoridad ambiente.

El bootloader no debería decir simplemente que cargó algunas cosas. Debería decir exactamente qué cargó, dónde está, qué es, cómo fue verificado y qué hechos de plataforma observó. Esa es la diferencia entre un salto y un handoff.


Qué deja EriX fuera del bootloader

El bootloader es poderoso porque se ejecuta temprano, y precisamente por eso debe permanecer pequeño. EriX no quiere que el bootloader decida política runtime: no debería decidir qué servicio posee la política de memoria, cómo se supervisan los procesos, cómo se gestionan los drivers o cómo se componen los sistemas de archivos.

Esas decisiones pertenecen al kernel y a los servicios de sistema en espacio de usuario. El trabajo del bootloader es más estrecho: validar el artefacto de boot, preparar el entorno mínimo de ejecución, describir qué hizo y transferir el control.

Esa línea importa para el tamaño de la TCB. Un bootloader con más funcionalidad no es automáticamente mejor, porque cada funcionalidad en el boot temprano es código que se ejecuta antes de que el kernel pueda aislarlo. Cada parser, ruta de fallback, modo interactivo y excepción de política aumenta la cantidad de comportamiento de confianza que debe ser correcto antes de que el sistema arranque.


Mirando hacia adelante

Este post trató boot.img principalmente como un contenedor verificado. El siguiente paso es abrir ese contenedor y mirar directamente su diseño.

El próximo post explicará el formato boot.img de EriX: por qué el sistema usa una imagen unificada, cómo se disponen las secciones, qué metadatos se llevan y cómo el formato soporta artefactos de boot reproducibles y deterministas.