Convertir un e-reader en tu sistema: ingeniería inversa del Kobo Libra 2

Un lector de libros de 512 MB con un SoC i.MX6SLL, sin secure boot y con una partición de usuario montable por USB. Le hicimos ingeniería inversa al arranque, escribimos un init propio que verifica el sistema por sha256 en cada boot y convertimos Plato en el sistema operativo entero. Esto es el proceso, los resultados y lo que se rompió por el camino.

21 min de lecturaRead in English

// Sec · QUANTARMY research

Convertir un e-reader en tu sistema

Ingeniería inversa del Kobo Libra 2 y PlatoX

Es un lector de libros. Pantalla e-ink, Wi-Fi, 512 MB de RAM, un SoC que NXP dejó de considerar nuevo hace casi una década. La clase de aparato que nadie mira con ojos de atacante porque "no tiene nada dentro". Y sin embargo tiene todo lo que hace interesante a un objetivo de hardware: una cadena de arranque sin secure boot, un mecanismo de actualización OTA que aplica paquetes con confianza implícita, una partición de usuario que se monta por USB como una memoria cualquiera, y un contador en el MBR que decide si el aparato se borra solo.

Este artículo es el registro de lo que le hicimos a un Kobo Libra 2: ingeniería inversa de su arranque y su mecanismo de actualización, un selector de arranque propio en C que verifica el sistema por sha256 en cada boot, un kernel propio, y la conversión de Plato —un lector de documentos open-source— en el sistema operativo entero del dispositivo. El resultado público se llama PlatoX y es software libre (AGPL-3.0); la versión personal, con más cosas enganchadas, se llamó KoboX. Vamos a separar las dos con cuidado, porque la seguridad está en los detalles y mezclar ediciones es mentir con precisión.

No es un tutorial de jailbreak. Es un caso de estudio de seguridad del hardware: qué se puede aprender de un dispositivo cerrado leyendo su propio firmware, dónde están los límites reales de confianza, y por qué "integridad" y "autenticidad" no son la misma palabra.

El ecosistema que había que entender antes de tocar nada

La primera regla de cualquier intervención sobre hardware ajeno: reconstruir el sistema de serie antes de cambiar un solo byte. No se puede defender —ni romper con control— lo que no se entiende.

El aparato es un Kobo Libra 2, nombre interno io, placa E70K10, confirmado leyendo la tabla de PCB de kobo_config.sh del propio firmware. Dentro: un SoC NXP i.MX6SLL (ARM Cortex-A9), PMIC Rohm BD71828, Wi-Fi Realtek RTL8723DS por SDIO, pantalla e-ink de 1264×1680 y 512 MB de RAM. El stack de software de serie, reconstruido pieza a pieza desde el firmware extraído, es un museo de Linux embebido de 2015:

Cadena de arranque de serie del Kobo Libra 2: hardware i.MX6SLL, U-Boot sin secure boot, kernel Linux 4.1.15, busybox init y Nickel como lector; aparte, los servicios de serie pickel, udev y wpa_supplicantKobo Libra 2 (io / E70K10) SoC NXP i.MX6SLL · Wi-Fi RTL8723DS e-ink 1264×1680 · 512 MB RAM → U-Boot NXP 2016.03-00128 sin secure boot ni firma; U-Boot NXP 2016.03-00128 sin secure boot ni firma → Kernel Linux 4.1.15 fuentes GPL de Kobo; Kernel Linux 4.1.15 fuentes GPL de Kobo → busybox init 1.31.1 /etc/init.d/rcS · glibc 2.19; busybox init 1.31.1 /etc/init.d/rcS · glibc 2.19 → Nickel lector Qt · tienda · cuenta (arranque normal); busybox init 1.31.1 /etc/init.d/rcS · glibc 2.19 → pickel factory-reset · upgrade (lanza servicios)arranque normallanza serviciosServicios de serieKobo Libra 2 (io / E70K10)SoC NXP i.MX6SLL · Wi-Fi RTL8723DSe-ink 1264×1680 · 512 MB RAMU-Boot NXP 2016.03-00128sin secure boot ni firmaKernel Linux 4.1.15fuentes GPL de Kobobusybox init 1.31.1/etc/init.d/rcS · glibc 2.19Nickellector Qt · tienda · cuentapickelfactory-reset · upgradeudev 142wpa_supplicant 2.9OpenSSL 1.0.1u
El stack de serie: de U-Boot a Nickel, sin un solo eslabón firmado
  • Arranque: U-Boot de NXP (2016.03-00128 en la rama 4.38), sin verificación de firma. Quien controle lo que el kernel ejecuta como init, controla el aparato.
  • Kernel: Linux 4.1.15, build exacto de Kobo -00868-g58a2758be07, compilado con GCC 5.3.0.
  • Userland: busybox 1.31.1 como PID 1, glibc 2.19, libstdc++ de GCC 4.9. udev 142, wpa_supplicant 2.9 sobre OpenSSL 1.0.1u.
  • El lector: Nickel, la aplicación Qt de Kobo, con su tienda y su cuenta.
  • pickel: un wrapper de shell que ejecuta pickel-imx (un ELF). Guárdalo en la cabeza: pickel es quien decide si el aparato hace factory-reset o acepta una actualización. Es, sin saberlo, una pieza de seguridad.

El almacenamiento es un eMMC /dev/mmcblk0 con tres particiones y varias zonas crudas fuera de ellas:

ZonaQué esTamaño / posición
mmcblk0p1rootfs (donde corre Nickel)256 MB ext4
mmcblk0p2recoveryfs384 MB ext4
mmcblk0p3KOBOeReader (el "onboard", visible por USB)29,1 GB vfat
zImagekernel crudosector 2048
hwconfigconfig de hardwaresector 1024
waveformtabla e-inksector 14336

Ese 256 MB de la rootfs y ese vfat de la partición de usuario no son detalles: los dos nos mordieron más adelante. El firmware de referencia quedó fijado a la versión 4.38.23828 (los hashes de kernel que el sistema acepta están anclados a ella).

Ingeniería inversa: proceso y resultados

Con el mapa en la mano, la pregunta operativa es una sola: ¿cómo se mete código propio sin abrir el aparato ni tocar U-Boot? La respuesta estaba en el propio mecanismo de actualización de Kobo.

El vector: KoboRoot.tgz y la confianza implícita

Kobo actualiza así: si el init de serie encuentra un fichero .kobo/KoboRoot.tgz en el almacenamiento, lo desempaqueta sobre / como root y reinicia. En el rcS de serie eso ocurre alrededor de las líneas 251–269, con pickel can-upgrade como guardián. No hay firma. No hay verificación de origen. Es un tar/gzip de ficheros propiedad de root que se extrae sobre el sistema, y los binarios tienen que ir en modo 755.

Dicho en lenguaje de amenazas: el actualizador es el vector. Cualquiera que pueda escribir en la partición de usuario —que se monta por USB sin credenciales— puede colocar un KoboRoot.tgz que el sistema aplicará como root en el siguiente arranque. Toda la cadena de confianza del dispositivo se apoya en que nadie meta un fichero en una carpeta. Ese es el eslabón que convierte un lector cerrado en un sistema abierto, y el que un diseño con secure boot cerraría.

El contador de arranque en el MBR

El hallazgo más bonito de toda la ingeniería inversa no estaba en un binario, sino en cinco bytes del MBR. En /dev/mmcblk0, offsets 0x133–0x137, Kobo guarda un contador de arranque. En cada boot, pickel should-factory-reset lo incrementa; cuando el índice llega a 4, el aparato muestra la pantalla de factory-reset y se borra. Lo único que resetea ese contador es Nickel, que escribe la secuencia 8d ef 02 d2 00 cuando arranca bien.

La consecuencia de seguridad es profunda: un contador en disco es estado de seguridad, y si tu sistema sustituye a Nickel, hereda su obligación. Un sistema propio que arranque en lugar de Nickel y no resetee ese contador se autodestruye en cuatro reinicios. Tuvimos que replicar el gesto exacto de Nickel —escribir 8d ef 02 d2 00 en el offset 307, releer y registrar— solo cuando Plato llevaba vivo más de un umbral de tiempo. Un test de mutación (escribir en el offset 308) debía fallar, y fallaba. Eso llegó en la versión 1.0.3, después de verlo borrarse en el banco de pruebas.

Los bricks, contados como lo que son: resultados

La parte honesta de cualquier trabajo sobre hardware es la lista de veces que lo dejaste inservible. Cada brick fue un hallazgo sobre el sistema:

  • Permisos (EACCES): empaquetamos rcS en modo 644. busybox init lo invoca con execv, que exige el bit de ejecución → EACCES → no arranca nada. Se corrigió enviándolo en 755 con un check_exec de guarda. (fix 1.0.1)
  • Espacio (ENOSPC): metimos 44,5 MB en una rootfs de 256 MB que ya iba al 93%. El etc/ se extendió a medias y el aparato se quedó en el splash sin llegar a Plato. La solución fue mover Plato al onboard (vfat) y dejar la rootfs con un delta de 29 KB, con un límite duro de ≤ 256 KB en la partición de sistema. (fix 1.0.2)
  • El factory-reset autoinfligido: una fase de debloat temprana metió todo lo de /usr/local/Kobo/* en la lista de borrado… incluido pickel-imx. Siguiente arranque: pickel should-factory-reset devuelve 127, el sistema interpreta eso como "hay que resetear", entra en recovery, reflashea el firmware 4.33.19747 y borra el onboard. De ahí nació la regla más importante del proyecto: una protect-list que nunca deja tocar pickel*, udev, init.d, las imágenes de U-Boot ni ld.so.preload. El debloat, además, pasó a hacerse con placeholders de 0 bytes dentro del KoboRoot.tgz, nunca con rm sobre el sistema vivo.

Kernel y módulos: confianza reconstruida desde el binario

Querer cargar módulos propios (zram, tun) en el kernel de Kobo obliga a reconstruir la confianza que Kobo no publica. El kernel verifica vermagic y los CRC de cada símbolo; un módulo compilado contra cabeceras que no cuadran al bit se rechaza con Invalid argument.

El proceso fue arqueología de binarios:

  • Extraer del aparato su propio config.gz (vía IKCONFIG_PROC) y su tabla kallsyms: 75.337 símbolos.
  • Reconstruir el Module.symvers del kernel de serie directamente desde el zImage oficial, con un descompresor LZO1X propio para abrirlo y localizando __ksymtab/__kcrctab con kallsyms: 6.847 símbolos, todos contrastados contra kallsyms.
  • Verificar cada módulo con check-module.py (identidad de vermagic) y verify-crcs.sh, recomputando los CRC desde fuentes propias: 186 iguales, 0 distintos.

El árbol de kernel de la Libra 2 (2021) no traía los drivers de esta placa, pero el de la Clara 2E (2023) encajaba con la config del aparato salvo una opción. Las compilaciones salieron reproducibles: tun.ko y zsmalloc.ko idénticos byte a byte entre builds. Esa es la diferencia entre "parece que funciona" y "sé exactamente qué estoy cargando en ring 0".

Qué es Plato, y qué es PlatoX

Plato es un lector de documentos open-source para e-readers, escrito en Rust por Bastien Dejean, pensado como alternativa a Nickel: rápido, sin tienda, sin cuenta. Es el lector; no es el sistema.

PlatoX es la idea que da sentido a todo lo anterior: coger Plato y convertirlo en el sistema operativo entero del Kobo. En vez de parchear Nickel, lo sustituimos del todo. El aparato arranca un pequeño sistema Buildroot desde RAM, verificado por sha256, y cae directamente en Plato: sin tienda, sin cuenta, sin servicio de sincronización. Técnicamente, PlatoX es Plato 0.9.45 más una serie de nueve parches (plato-patches/0001–0009) y un puñado de crates de Rust propios. Nickel se queda instalado e intacto: si algo falla, el Kobo arranca Nickel como siempre.

Conviene fijar los dos nombres antes de seguir, porque de aquí en adelante importa:

  • KoboX (y su evolución KoboX 2 / "0.2 Master Edition") es el firmware personal completo, con cosas que dependen de infraestructura propia: Tailscale, una terminal, mercados, tiempo, noticias, Home Assistant, sincronización de biblioteca.
  • PlatoX 0.1 es la destilación pública (AGPL-3.0): Plato como sistema, las optimizaciones, el asistente de IA y la calculadora. Todo lo personal se quitó a propósito, y con ello buena parte de la superficie de ataque.
Arquitectura en capas de PlatoX: platox-boot verifica sha256 y arranca Buildroot en RAM, cae en Plato 0.9.45 con parches, que llama a los crates Rust platox-core, platox-ai, platox-calc y platox-sys; el crate de IA habla por HTTPS con el proveedor LLM; Nickel queda como fallbackplatox-boot (C) verifica sha256 Buildroot en RAM → Plato 0.9.45 + 9 parches biblioteca · lector · launcher · UI; platox-boot (C) verifica sha256 Buildroot en RAM → Nickel intacto, como fallback (si algo falla); Plato 0.9.45 + 9 parches biblioteca · lector · launcher · UI → platox-core Wi-Fi · ajustes · sysinfo · caché; Plato 0.9.45 + 9 parches biblioteca · lector · launcher · UI → platox-ai prompts · streaming · caché offline; Plato 0.9.45 + 9 parches biblioteca · lector · launcher · UI → platox-calc decimal exacto · teclado; platox-boot (C) verifica sha256 Buildroot en RAM → platox-sys binario platox: hw · splash · rtc; platox-ai prompts · streaming · caché offline → Proveedor LLM (tu clave) OpenAI · Claude · DeepSeek o servidor en tu red (HTTPS · sólo bajo petición)si algo fallaHTTPS · sólo bajo peticiónCrates Rustplatox-boot (C)verifica sha256Buildroot en RAMNickelintacto, como fallbackPlato 0.9.45 + 9 parchesbiblioteca · lector · launcher · UIplatox-coreWi-Fi · ajustes · sysinfo · cachéplatox-aiprompts · streaming · caché offlineplatox-calcdecimal exacto · tecladoplatox-sysbinario platox: hw · splash · rtcProveedor LLM (tu clave)OpenAI · Claude · DeepSeeko servidor en tu red
Tres capas: arranque verificado, Plato como UI, y los crates de Rust detrás

Las funcionalidades nuevas de Plato

Sobre Plato se añadieron funciones que lo convierten en un entorno completo. Las clasifico por edición, porque un lector de seguridad querrá saber exactamente qué código corre en la versión pública.

En PlatoX público (open source)

  • Plato como launcher (parche 0001): rutas desde el arrancador, entradas por nombre. Es lo que permite que Plato sea el sistema y no una app más.
  • Panel de red y selector de Wi-Fi (parche 0002): un view/network_panel.rs nuevo (+281 líneas) que abre, desde cualquier vista, un panel con el estado del Wi-Fi en frases legibles (Apagado / Buscando una red conocida… / Conectado a X / No hay ninguna red conocida cerca), IP y señal. El icono de la barra tiene cuatro estados (wifi-off, wifi-connecting, wifi, wifi-secure) y un hilo poller lo actualiza cada 2 s, nunca desde el hilo de la UI.
  • Asistente de IA (parches 0004 y 0009): sobre una selección de texto, Resumir, Traducir o Explicar; sobre un libro, insights —reseña corta, reseña extendida, resumen o un mapa de conceptos navegable (view/concept_map.rs, +801 líneas)—. Funciona con tu propia clave: OpenAI, Claude (vía la Messages API nativa con streaming por SSE, con claude-opus-5-5 preseleccionado si tu clave lo lista), DeepSeek o cualquier servidor compatible en tu red. Las respuestas se cachean offline y el Wi-Fi solo se enciende cuando una petición lo necesita, apagándose tras cinco minutos de inactividad.
  • Calculadora estilo teléfono (parche 0006): reemplaza la vieja calculadora de línea de comandos de Plato por una de teclas. Detrás va el crate platox-calc, con un decimal exacto (0,1 + 0,2 = 0,3, no 0,30000000000000004), porcentaje y descuento (120 − 15 % = 102, con la línea "Descuento: 18"), raíz cuadrada y división por cero controlada.
  • Menú limpio, About y System Info (parche 0005): el menú cabe en una página, Reiniciar y Apagar quedan siempre visibles, y la gestión de Wi-Fi se va entera al panel de red. De paso, System Info arregla un bug del propio Plato, que mostraba la carga del sistema multiplicada por 100 con un signo de "%", y la memoria libre en vez de la disponible.
  • Herramientas de desarrollo (parche 0007): Test All y Benchmark, detrás de un marcador de desarrollador, con redacción de secretos en los diagnósticos (nada de api-keys, tokens o PSK en los logs).

En KoboX (firmware personal, fuera de la versión pública)

Esto existe y funciona, pero depende de infraestructura propia y se quitó del público para reducir superficie: Tailscale (primero en modo usuario, luego con TUN y módulo propio), una terminal (shell de busybox sobre un pty, con fastfetch y ssh), Markets (datos de Yahoo), Weather (Open-Meteo), News (un generador propio con trafilatura en un contenedor endurecido contra SSRF), Home Assistant (luz y clima por un túnel CONNECT) y sincronización de PDF contra un Calibre-Web propio.

La optimización final: más rápido, más robusto, más responsive, más actualizado

Un lector que tarda dos segundos en cambiar de capítulo es inusable, por muy elegante que sea su arquitectura. La última fase fue una campaña de rendimiento con una regla estricta: medir en el aparato, no en el host. Estas son las medianas, iteración a iteración, de la versión 1.4:

Métrica (mediana)Antes (beta7)Después (beta12)
Pasar página (EPUB)231 ms31 ms
Cambio de capítulo2967 ms46 ms
Abrir / reabrir libro1265 ms74 ms
Primer paint tras encender9,4 s6,9 s
Wi-Fi conectado12–13 s5,0 s
Gráfico de barras del cambio de capítulo en milisegundos de beta7 a beta12: 2967, 2816, 834, 260, 113 y 46 ms, con la caída grande al introducir la caché de layout en discobeta7: 2.967; beta8: 2.816; beta9: 834; beta10: 260; beta11: 113; beta12: 4601.0002.0003.000Cambio de capítulo (ms, mediana)2967beta72816beta8834beta9260beta10113beta1146beta12Iteración (medido en el Kobo)caché de layout en disco + worker de siguiente capítulo: 2967 → 46 ms
El cambio de capítulo, de 2967 a 46 ms: dónde cayó y por qué

Más rápido

El trabajo (parche 0003) ataca cada capa del coste:

  • Pasar página: una copia directa de gray8 a RGB565 con una tabla de 256 entradas (framebuffer/fast16.rs) y el prerender de la página vecina en un hilo aparte (nice 5). El page turn de beta8 cayó de 230,7 a 42,8 ms.
  • Texto y layout: caché de shaping por palabra, buffers de HarfBuzz reutilizados, patrones de guionado cargados en diferido por idioma, CSS parseado una sola vez por documento.
  • Capítulos sin esperas: layout del siguiente capítulo en un worker y caché de layout en disco (prefetch.rs, layoutcache.rs, dlcodec.rs), con verificación por dev/inode/tamaño/mtime. Esa es la barra que se desploma en el gráfico.
  • Compilación: MuPDF, HarfBuzz, FreeType y DjVuLibre compilados desde fuente con LTO y NEON (el layout bajó de 13,4 a 9,8 ms/KB), y el perfil de release con lto fat y codegen-units = 1 y target-cpu=cortex-a9. El binario encogió de 10.494.280 a 8.851.200 bytes (−16 %).
  • E-ink: refresco parcial entre páginas y refresco completo cada 20 para controlar el ghosting, más zram para estirar los 512 MB.

Más responsive

La responsividad no es solo la UI. El Wi-Fi tardaba 10 segundos en escanear por un timeout fijo del driver wext; cambiándolo a nl80211,wext, el escaneo bajó de 10,5 a 1,4 s y la autoconexión a 5,0 s. El apagado y reinicio se midió en 0,18–0,23 s entre la petición y el desmontaje limpio del onboard.

Más robusto

La robustez es, en este proyecto, una propiedad de seguridad. El corazón es platox-boot, un programa en C estático instalado como PID 1 en la partición de sistema de Kobo. En cada arranque verifica el kernel, un boot.manifest y el sha256 de la imagen del sistema; y si algo falla —o si se encadenan tres arranques que no llegan a Plato— cede el control al init de serie, Nickel. El primer paint de Plato resetea ese contador.

Diagrama de decisión de platox-boot: comprueba kernel conocido, manifiesto y sha256 válidos, y menos de tres intentos fallidos; si todo pasa desempaqueta el sistema en RAM y arranca PlatoX, y cualquier fallo cae a Nickel/sbin/init = platox-boot C estático, en la partición de Kobo → ¿Kernel conocido y .platox/enabled presente?; ¿Kernel conocido y .platox/enabled presente? → boot.manifest: ¿tamaño y sha256 cuadran? (sí); boot.manifest: ¿tamaño y sha256 cuadran? → ¿menos de 3 arranques que no llegaron a Plato? (sí); ¿menos de 3 arranques que no llegaron a Plato? → Sistema a RAM tmpfs + pivot_root (sí); Sistema a RAM tmpfs + pivot_root → PlatoX Plato 0.9.45 + parches; ¿Kernel conocido y .platox/enabled presente? → init de serie: Nickel la red de seguridad (no); boot.manifest: ¿tamaño y sha256 cuadran? → init de serie: Nickel la red de seguridad (no); ¿menos de 3 arranques que no llegaron a Plato? → init de serie: Nickel la red de seguridad (no: 3 fallos)sísísínonono: 3 fallos/sbin/init = platox-bootC estático, en la partición de Kobo¿Kernel conocido y.platox/enabled presente?boot.manifest:¿tamaño y sha256 cuadran?¿menos de 3 arranquesque no llegaron a Plato?Sistema a RAMtmpfs + pivot_rootPlatoXPlato 0.9.45 + parchesinit de serie: Nickella red de seguridad
El selector de arranque: tres comprobaciones y una red de seguridad

El escritor de la ranura del kernel es igual de cuidadoso: escribe con dd conv=notrunc,fsync, vuelve a leer y comprueba el sha256, y si no cuadra restaura la copia verificada previa sin reiniciar, todo bajo timeout 300 y solo con la batería al 30 % o cargando. El init propio de KoboX 1.4, además, rechaza paquetes de actualización ajenos: solo aplica los suyos, con manifiesto (sha256, tamaño, rutas, espacio, batería), y aparta cualquier KoboRoot.tgz extraño sin ejecutarlo. La versión estable (Master Edition) remató esto con escrituras atómicas de configuración y clippy -D warnings limpio en los ocho crates.

Más actualizado

Donde el sistema de serie era un museo de 2015, el userland propio es de hoy: Buildroot 2025.02 LTS, glibc 2.41, busybox 1.37, wpa_supplicant 2.12 con mbedTLS y soporte de WPA3/SAE/OWE — y sin OpenSSL, sin udev y sin systemd. Hay incluso un kernel propio 4.1.15 recompilado desde las fuentes GPL de Kobo con suspensión fiable: el aparato se colgaba al suspender, y por bisección de configuración encontramos que la causa era, paradójicamente, quitar CONFIG_PM_DEBUG; esos mensajes de depuración ocultaban una race en la suspensión de la placa, así que la solución fue dejarlo como en stock.

Nota de método y modelo de amenazas

Lo que esto es y lo que no es, dicho sin adornos:

  • Hay integridad, no autenticidad. platox-boot verifica el sha256 del sistema en cada arranque, pero no hay firmas. Un atacante local que pueda escribir en la partición onboard puede sustituir a la vez el cpio y su manifiesto. El modelo de amenazas de este diseño es anti-brick y anti-corrupción, no anti-atacante con acceso físico y permiso de escritura. Cerrar eso exigiría lo único que no tocamos: U-Boot y una raíz de confianza en el SoC.
  • La clave de API va en claro, en .platox/config/ai.toml, en el almacenamiento del propio Kobo. Está documentado así y la recomendación es explícita: usa una clave revocable y con límite de gasto. La UI la enmascara a sus últimos cuatro caracteres y nunca llega a los logs; el diagnóstico redacta secretos antes de escribir nada.
  • No llama a casa. No hay telemetría, ni cuenta, ni comprobación de actualizaciones. El aparato habla con el proveedor de IA que tú configures, solo cuando se lo pides, y con pool.ntp.org para la hora. El Wi-Fi está apagado salvo que una acción lo necesite.
  • El debloat no borra, oculta. Se hace con placeholders de 0 bytes dentro del paquete de actualización y una protect-list que jamás toca las piezas críticas. Es reversible y no puede, por construcción, repetir el brick que lo inspiró.
  • Cadena de suministro: fuentes fijadas por versión y sha256, kernel y paquetes reproducibles, y un check-public.sh que rechaza datos personales, infraestructura privada y ficheros propietarios de Kobo antes de cada publicación.

Lo que nos llevamos

  • El actualizador es el vector. Un mecanismo OTA que aplica paquetes con confianza implícita —sin firma, sobre una partición que se monta por USB— es la puerta de entrada, no una pieza secundaria. Si diseñas hardware, ahí es donde va la firma.
  • El estado en disco es estado de seguridad. Cinco bytes en el MBR decidían si el aparato se borraba. Quien sustituye al sistema, hereda sus invariantes; descubrirlas tarde cuesta un dispositivo.
  • Integridad no es autenticidad. Un sha256 por boot te protege de la corrupción y del brick, no de un atacante que controle el bit. Saber cuál de los dos problemas estás resolviendo es la mitad del trabajo.
  • Los bricks son datos. Cada aparato que dejamos inservible —permisos, espacio, un pickel-imx de más en una lista de borrado— enseñó una invariante del sistema que ninguna documentación contaba.

Un lector de 512 MB resultó ser un objetivo de hardware security completo: cadena de arranque sin firmar, actualizador de confianza implícita, estado de seguridad en disco y un modo de recuperación con los dientes afilados. Convertirlo en un sistema propio, verificado y rápido fue la excusa; el mapa de confianza que salió del proceso es lo que nos llevamos.

Recursos

  • Plato, de Bastien Dejean — el lector open-source en el corazón de todo: github.com/baskerville/plato.
  • PlatoX — la edición pública (AGPL-3.0) de este trabajo; sin repositorio público por ahora.
  • Buildroot — el sistema embebido que arranca en RAM: buildroot.org.