La base de còmput de confiança: per què importa la mida
Les discussions de seguretat sovint se centren en errors individuals:
- un desbordament de buffer
- un problema de diputat confús
- un parser sense comprovacions suficients
- una ruta d’escalada de privilegis
Aquests errors importen, però són símptomes d’una pregunta més profunda:
Quant codi ha de ser correcte perquè el sistema continuï sent segur?
Aquest codi és la base de còmput de confiança, normalment abreujada com a TCB.
La mida i la forma de la TCB determinen quant codi cal confiar, auditar, provar i raonar. Un sistema pot tenir abstraccions fortes sobre el paper, però si aquestes abstraccions depenen que una gran quantitat de codi privilegiat es comporti perfectament, l’argument de seguretat es torna molt més feble.
Aquesta entrada explica què és la TCB, per què la seva mida afecta la superfície d’atac i com EriX intenta mantenir el codi de confiança petit i explícit.
Què és la TCB?⌗
La base de còmput de confiança és el conjunt de components la correcció dels quals és necessària perquè les propietats de seguretat del sistema es mantinguin.
Si un component de la TCB queda compromès, les garanties de seguretat del sistema poden deixar de ser certes.
En un sistema operatiu, la TCB sovint inclou:
- el bootloader
- el kernel
- serveis privilegiats del sistema
- lògica d’autenticació i autorització
- parsers de dades d’arrencada confiables
- codi de verificació criptogràfica
- codi que distribueix o transfereix autoritat
La TCB exacta depèn del disseny del sistema.
En un kernel monolític tradicional, bona part del kernel normalment forma part de la TCB perquè molts subsistemes s’executen amb privilegi complet de kernel. Un bug en un controlador, sistema de fitxers o stack de xarxa pot convertir-se en un compromís del kernel.
En un sistema microkernel, la TCB del kernel pot ser més petita, però el sistema confiable total encara inclou els components que distribueixen autoritat i apliquen política.
Aquesta distinció importa.
Els microkernels redueixen la quantitat de codi que s’executa amb autoritat completa sobre la màquina. No fan que tots els serveis d’espai d’usuari siguin no confiables per defecte.
Confiable no vol dir segur⌗
La paraula “confiable” és fàcil de malinterpretar.
Un component confiable no és un component que sabem correcte.
És un component del qual el sistema depèn perquè sigui correcte.
Aquesta definició és menys còmoda, però és la útil.
Si un servei és confiable per distribuir capacitats, un bug en aquest servei pot concedir autoritat incorrectament. Si un parser és confiable per validar un executable abans de l’arrencada, un bug del parser pot soscavar la cadena d’arrencada. Si un manejador de syscall del kernel és confiable per validar drets d’endpoints, una comprovació absent pot trencar l’aïllament.
La confiança no és elogi.
La confiança és risc.
Per això l’objectiu no és etiquetar tant codi com sigui possible com a confiable. L’objectiu és fer que el conjunt confiable sigui tan petit, estret i auditable com sigui possible.
Per què importa la mida⌗
La mida de la TCB importa per diverses raons.
1. Més codi vol dir més bugs⌗
Tot programari té bugs.
A mesura que creix la quantitat de codi confiable, també creix la probabilitat de bugs rellevants per a la seguretat. Això és especialment cert per al codi que:
- parseja entrada no confiable
- gestiona memòria
- tracta concurrència
- interpreta permisos
- tradueix un model d’autoritat a un altre
Els sistemes operatius contenen tots aquests patrons.
Reduir la mida de la TCB no elimina els bugs, però redueix la quantitat de codi on un bug pot comprometre tot el sistema.
2. Més interfícies volen dir més superfície d’atac⌗
La superfície d’atac no és només línies de codi.
També són punts d’entrada.
Cada interfície cap a codi confiable és un lloc on un atacant pot aportar entrada:
- arguments de syscall
- missatges IPC
- metadades d’imatge d’arrencada
- capçaleres ELF
- estructures de sistemes de fitxers
- descriptors de dispositius
- esdeveniments d’interrupció
- taules proporcionades pel firmware
Cada interfície necessita validació.
Un component confiable petit amb una interfície mal dissenyada pot continuar sent perillós. Però quan creix el nombre d’interfícies confiables, també creix la càrrega de validació.
3. Més estat vol dir raonament més difícil⌗
Els fallos de seguretat sovint no passen perquè falti una comprovació aïllada, sinó perquè l’estat canvia en un ordre inesperat.
Per exemple:
- una capacitat es copia abans de reduir-ne els drets
- un servei s’inicia abans que el seu paquet d’inici estigui validat del tot
- un slot local obsolet es tracta com a prova d’autoritat
- un dispositiu es considera present abans que el descobriment hagi acabat
- un procés conserva autoritat després d’una ruta de llançament fallida
Com més estat mutable confiable té un sistema, més difícil és demostrar que cada transició preserva les invariants previstes.
Per això EriX emfatitza l’arrencada determinista, els registres explícits de transferència i el comportament fail-closed.
4. Més privilegi vol dir més radi d’impacte⌗
El mateix bug té conseqüències diferents segons on passa.
Un bug de parsing en una eina no privilegiada pot fer caure aquella eina.
Un bug de parsing al bootloader pot comprometre tot el sistema abans que el kernel comenci.
Un bug en un controlador d’espai d’usuari amb accés només a un rang concret d’E/S és seriós, però és diferent d’un bug en un controlador que s’executa dins del kernel amb autoritat completa sobre la màquina.
Reduir la TCB també significa reduir el radi d’impacte dels bugs individuals.
El kernel és només part de la TCB⌗
És temptador dir:
La TCB és el kernel.
Normalment això és massa simple.
El kernel és central, però un sistema segur també depèn del codi que prepara el kernel, inicia la primera tasca d’espai d’usuari, defineix formats d’autoritat i distribueix capacitats.
En EriX, el kernel és explícitament dins de la TCB.
Posseeix les taules de capacitats de l’espai del kernel, l’estat de planificació i els recursos de la màquina. Valida el handoff del bootloader al kernel, crea la tasca arrel, gestiona objectes bàsics del kernel i exposa punts d’entrada de trap, syscall i interrupció específics de l’arquitectura.
Si el kernel falla en aplicar comprovacions de capacitats, drets d’endpoints, límits d’espais d’adreces o cicles de vida d’objectes, el model d’aïllament del sistema falla.
Però el kernel no és tota la història.
El codi d’arrencada també és confiable⌗
El bootloader s’executa abans del kernel.
Això el fa crític per a la seguretat.
En EriX, el bootloader és responsable de carregar i verificar un boot.img
signat, parsejar imatges del kernel i serveis, construir una estructura de
handoff determinista i transferir el control al kernel.
Això posa el bootloader dins de la TCB.
Durant l’arrencada, té autoritat proporcionada pel firmware i controla el salt final al kernel. Ha de tractar el mitjà d’arrencada com a no confiable, validar l’estructura i la integritat criptogràfica de la imatge, rebutjar binaris ELF malformats i fallar tancat davant l’ambigüitat.
Si el bootloader accepta una imatge manipulada o construeix un handoff inconsistent, el kernel pot començar des d’una base compromesa.
Per això el codi d’arrencada ha de ser petit, estricte i avorrit.
Els parsers poden ser fronteres de TCB⌗
Els parsers sovint se subestimen en seguretat de sistemes.
Són exactament al punt on bytes no confiables es converteixen en estructura confiable.
EriX tracta diversos crates de parsing i ABI com a components dins o adjacents a la TCB:
lib-bootimgparseja i verifica l’estructura, els hashes i les signatures deboot.imglib-elfvalida binaris ELF64 abans que el bootloader confiï en segments de càrregalib-handoffvalida estructures de handoff versionades entre etapes d’arrencadalib-ipcdefineix i valida layouts de missatges IPClib-capabidefineix tipus de capacitats, drets, constants de slots i descriptors de transferència
Aquestes biblioteques poden no tenir capacitats de runtime per si mateixes.
Això no les fa irrellevants per a la TCB.
Si lib-bootimg accepta una imatge d’arrencada modificada, el bootloader pot
confiar en codi que hauria de rebutjar. Si lib-elf accepta un executable
malformat, la cadena d’arrencada pot carregar bytes incorrectes o confiar en
rangs de segments invàlids. Si lib-ipc decodifica malament un missatge, una
operació es pot interpretar incorrectament. Si lib-capabi defineix una
política de rol massa ampla, un servei pot rebre autoritat que no hauria de
tenir mai.
El codi pur també pot ser codi confiable.
La propietat important és que aquestes biblioteques són estretes. No fan E/S, no posseeixen política del sistema i eviten autoritat ambiental. La seva feina és parsejar, validar i rebutjar.
Els serveis d’espai d’usuari poden ser components confiables⌗
Moure la política fora del kernel no fa que desaparegui.
La mou a serveis d’espai d’usuari, on es pot aïllar, restringir i auditar per separat.
En EriX, rootd és la primera autoritat d’espai d’usuari que porta política.
Valida el handoff del kernel a root, parseja la configuració d’arrencada,
executa el DAG d’inici i transfereix capacitats de mínim privilegi als serveis
requerits.
rootd té privilegis alts.
Però no és el kernel.
Aquesta distinció és important. rootd és confiable per a la política primerenca
i la distribució de capacitats, però no implementa objectes del kernel ni
posseeix autoritat directa sobre la màquina. La seva feina és distribuir
autoritat segons contractes explícits d’inici.
Altres serveis també se situen dins de fronteres confiables específiques:
procdés confiable per a l’orquestració del cicle de vida de processosdevicedés confiable per a la política de controladors i el lliurament de capacitats de controladorsvfsdés confiable com a frontera de l’espai de noms públic del sistema de fitxers- els proveïdors privats de sistemes de fitxers són confiables només per al seu rol de proveïdor
Això no fa que aquests serveis siguin poc importants.
Fa que la seva autoritat sigui més estreta que l’autoritat completa del kernel.
Superfície d’atac en un disseny monolític⌗
En un kernel monolític, molts subsistemes comparteixen un únic espai d’adreces privilegiat.
Això pot simplificar les rutes ràpides, però també crea una superfície d’atac àmplia.
Una vulnerabilitat en qualsevol subsistema dins del kernel pot convertir-se en una vulnerabilitat del kernel:
- metadades de sistema de fitxers malformades
- tractament defectuós de paquets de xarxa
- codi insegur de controladors
- descriptors de maquinari inesperats
- condicions de carrera en estat compartit del kernel
L’atacant només necessita una ruta cap a codi privilegiat.
Això no vol dir que els kernels monolítics no puguin ser segurs. Es poden enginyar, endurir, fuzzear, sandboxar i auditar extensament.
Però l’arquitectura comença amb una superfície privilegiada gran.
L’argument de seguretat ha d’explicar com es controla aquesta gran superfície.
Superfície d’atac en un disseny microkernel⌗
Un microkernel canvia la forma de la superfície d’atac.
El kernel encara exposa interfícies crítiques:
- syscalls
- lliurament IPC
- planificació
- operacions d’espai d’adreces
- operacions de capacitats
- gestió d’interrupcions
Aquestes interfícies han de ser correctes.
Però els serveis de nivell superior no s’executen automàticament amb privilegi complet de kernel. Si un proveïdor de sistema de fitxers tracta mitjans malformats, l’objectiu és que el bug quedi dins de l’autoritat d’aquell proveïdor. Si un controlador falla, l’objectiu és que falli només amb l’autoritat de dispositiu que va rebre explícitament.
Això converteix una gran superfície privilegiada en diverses superfícies d’autoritat més petites.
Això no és automàticament més simple.
Només funciona si les fronteres són estrictes i l’autoritat que les travessa és estreta.
Com EriX minimitza la TCB⌗
EriX redueix la mida de la TCB i la superfície d’atac amb diverses decisions de disseny.
1. Un kernel mínim en política⌗
El kernel d’EriX és responsable de mecanismes, no de política del sistema.
Gestiona:
- objectes bàsics del kernel
- semàntica de capacitats
- planificació i execució de tasques
- primitives d’espais d’adreces
- IPC i despatx d’endpoints
- punts d’entrada d’interrupcions i excepcions
No posseeix:
- política d’inici de serveis
- política d’orquestració de processos
- política de l’espai de noms de sistemes de fitxers
- política d’activació de controladors
- política d’assignació de memòria d’alt nivell
Això manté el kernel centrat a aplicar aïllament en lloc de decidir la forma de tot el sistema.
2. Capacitats explícites en lloc d’autoritat ambiental⌗
EriX modela l’autoritat mitjançant capacitats.
Un component només pot actuar si té una capacitat amb els drets requerits. El kernel valida referències de capacitats en cada ús, aplica drets d’endpoints al despatx de syscalls i tracta les transferències com a esdeveniments explícits.
Això evita dependre de noms globals o números de slot convencionals com a permís.
Conèixer un número de slot no és autoritat.
Tenir la capacitat correcta al CSpace local sí que és autoritat.
Aquesta distinció és central per reduir la TCB: el codi confiable no necessita inferir permisos a partir d’estat global quan l’autoritat es porta explícitament.
3. Endpoints estrets de control del kernel⌗
Els dissenys antics de sistemes operatius sovint concentren el control darrere d’interfícies privilegiades àmplies.
EriX va en la direcció oposada.
El runtime actual usa famílies estretes d’endpoints de control del kernel per a tasques específiques:
- temps
- interrupcions
- hotplug
- configuració PCI
- consola i framebuffer
- E/S COM1
- E/S i8042
- retyping de memòria
- mapatge de VSpace
- resolució de fallades del pager
- control de processos
- lectures ACPI
L’arrencada normal de runtime no dona als serveis un endpoint root ampli per a totes les operacions del kernel.
En canvi, cada servei rep la família d’endpoint específica que necessita.
timed rep control de temps. irqd rep control d’interrupcions. drv-serial
rep E/S específica de COM1. drv-i8042 rep E/S específica d’i8042. drv-acpi
rep autoritat de lectura ACPI.
Això estreny tant la interfície confiable com el dany causat per un mal ús.
4. Contractes exactes d’inici⌗
EriX tracta la transferència de capacitats d’inici com un contracte, no com un suggeriment.
Els sobres d’inici descriuen quines capacitats ha de rebre un servei, on han d’aparèixer i quins drets han de portar.
Els serveis validen els slots locals reals que han rebut abans de declarar-se preparats. Les transferències d’endpoints es comproven per slot d’origen, slot de destinació, drets i tipus esperat d’endpoint. Les transferències desconegudes, incorrectes o extra es rebutgen.
Això evita un bug comú d’autoritat:
“Un slot està ocupat, per tant deu ser el correcte.”
En EriX, l’ocupació d’un slot per si sola no prova autoritat.
La capacitat ha de coincidir amb la forma d’autoritat declarada.
5. Creació de processos per etapes⌗
La creació de processos és una operació d’alt risc perquè combina execució, espais d’adreces, endpoints i autoritat inicial.
EriX encamina la creació de processos en runtime per creació de fills per etapes en lloc d’una operació directa heretada.
El flux per etapes és explícit:
- crear un fill en etapa
- rebre un grant d’instal·lació i un àlies d’endpoint del fill
- instal·lar el paquet declarat de capacitats d’inici
- iniciar el procés només quan la població sigui completa
El kernel denega l’inici del procés mentre hi hagi grants d’instal·lació vius dirigits a aquella etapa de fill.
Això evita que processos parcialment poblats esdevinguin executables amb un estat d’autoritat ambigu.
6. Autoritat de controladors específica per rol⌗
Els controladors són una font important de risc en sistemes operatius.
EriX no tracta tot control de maquinari com un únic permís ampli.
L’autoritat de controladors és específica per rol:
drv-serialrep autoritat d’E/S només per a COM1drv-i8042rep autoritat d’E/S només per a i8042drv-acpirep autoritat de lectura ACPIprobedrep autoritat de lectura de configuració PCIdrv-virtio-blockrep un frame de dispositiu validat en lloc d’autoritat general de memòria
deviced gestiona la política de controladors, però no dona simplement a cada
controlador una capacitat genèrica de control de dispositius. Usa instal·lació
explícita de capacitats d’inici i superfícies d’autoritat específiques per rol.
Això limita l’impacte de bugs de controladors sobre la TCB.
7. La memòria de dispositiu és un tipus separat de capacitat⌗
La memòria de dispositiu és perillosa perquè pot afectar directament l’estat del maquinari.
EriX distingeix la memòria de dispositiu de la RAM ordinària amb
CAP_TYPE_DEVICE_FRAME.
La ruta d’emmagatzematge pot derivar un frame MMIO recolzat per BAR per a
deviced, i deviced pot instal·lar només aquest frame de dispositiu derivat
al paquet d’inici d’un controlador.
Aquest frame de dispositiu no es tracta com a memòria assignable ordinària.
Això importa perquè confondre memòria de dispositiu amb frames normals eixamplaria el model d’autoritat i faria més difícil raonar sobre seguretat de memòria.
8. Dependències de sala neta⌗
Les dependències poden ampliar la TCB silenciosament.
Si el codi confiable depèn d’una biblioteca externa de propòsit general, el sistema pot heretar:
- rutes de codi que no necessita
- supòsits que no encaixen amb l’OS
- comportament de parsing massa permissiu
- risc d’actualitzacions i cadena de subministrament
EriX evita crates de tercers i implementa les seves biblioteques crítiques dins del projecte.
Això augmenta el treball d’implementació, però manté visible la frontera confiable. Quan un parser, crate d’ABI o helper criptogràfic forma part de la ruta d’arrencada o autoritat, forma part de la superfície de revisió del sistema.
9. Comportament fail-closed⌗
Una TCB petita no és suficient si els errors són ambigus.
EriX intenta fer que els fallos sensibles per a la seguretat siguin explícits i terminals:
- un handoff d’arrencada malformat atura l’arrencada
- les versions no suportades es rebutgen
- l’autoritat d’inici invàlida impedeix la preparació
- els tipus incorrectes d’endpoint fallen la validació
- el fallo d’un servei requerit atura la progressió
- els fallos posteriors a l’inici activen teardown fail-closed
Fallar tancat és important perquè les heurístiques de recuperació sovint es converteixen en política oculta.
La política oculta amplia el comportament confiable del sistema.
Més petit no vol dir trivial⌗
Reduir la TCB no fa fàcil el disseny del sistema.
Sovint el fa més explícit.
En lloc de posar tota la lògica en un únic espai d’adreces privilegiat, el sistema ha de definir:
- quin component posseeix cada decisió
- quina autoritat rep cada component
- com es transfereix l’autoritat
- com s’informen els fallos
- com es neteja un inici parcial
- com s’invaliden o eliminen capacitats obsoletes
Això és més feina al principi.
Però produeix un sistema on l’argument de seguretat és més fàcil d’inspeccionar.
La pregunta esdevé:
Quin component ha de ser confiable per a aquesta propietat específica?
Aquesta és una pregunta millor que:
Tot el kernel és correcte?
Una vista pràctica de la TCB d’EriX⌗
Una vista pràctica de la TCB d’EriX és per capes.
A la base hi ha la cadena d’arrencada:
- bootloader
- verificació d’imatge d’arrencada
- ruta de lectura/verificació de
lib-bootimg - parsing ELF
- validació de handoff
Després el kernel:
- objectes de capacitats
- aplicació de CSpace i VSpace
- IPC i drets d’endpoints
- planificació i gestió de traps
- lliurament d’interrupcions
Després l’espai d’usuari confiable primerenc:
rootdper a política d’inici i distribució de capacitatsprocdper al cicle de vida de processosdevicedper a política de controladors- biblioteques seleccionades d’ABI i validació compartides entre serveis
Després serveis confiables més estrets:
- mediació de l’espai de noms de sistemes de fitxers a
vfsd - proveïdors backend privats
- serveis d’entrada, consola, logging, blocs, temps i interrupcions
No tots aquests components tenen el mateix privilegi.
Aquest és el punt.
EriX intenta evitar un únic món confiable pla. En canvi, cada component hauria de ser confiable només per al seu rol documentat i només amb les capacitats que ha rebut explícitament.
Mirant endavant⌗
La discussió sobre la TCB condueix naturalment al llenguatge d’implementació.
Si el codi confiable ha de ser petit, explícit i auditable, aleshores la
seguretat de memòria importa. També importen els límits unsafe, la correcció
dels parsers, el layout de dades i la disciplina necessària quan es treballa a
prop del maquinari.
La propera entrada examinarà per què EriX està escrit principalment en Rust, què resol i què no resol Rust per al desenvolupament de kernels, i com es compara amb l’enfocament tradicional en C per a la programació de sistemes.