Säkerhetsdiskussioner fokuserar ofta på enskilda buggar:

  • ett buffertspill
  • ett confused-deputy-fel
  • en okontrollerad parser
  • en väg för privilegieeskalering

De buggarna spelar roll, men de är symptom på en djupare fråga:

Hur mycket kod måste vara korrekt för att systemet ska förbli säkert?

Den koden är den betrodda beräkningsbasen, vanligtvis förkortad TCB.

TCB:ns storlek och form avgör hur mycket kod som måste betros, granskas, testas och förstås. Ett system kan ha starka abstraktioner på papperet, men om de abstraktionerna beror på att en stor mängd privilegierad kod beter sig perfekt blir säkerhetsargumentet mycket svagare.

Det här inlägget förklarar vad TCB är, varför dess storlek påverkar attackytan och hur EriX försöker hålla betrodd kod liten och explicit.


Vad är TCB?

Den betrodda beräkningsbasen är mängden komponenter vars korrekthet krävs för att systemets säkerhetsegenskaper ska hålla.

Om en komponent i TCB komprometteras kan systemets säkerhetsgarantier sluta gälla.

I ett operativsystem omfattar TCB ofta:

  • bootloadern
  • kärnan
  • privilegierade systemtjänster
  • autentiserings- och auktoriseringslogik
  • parsers för betrodd bootdata
  • kryptografisk verifieringskod
  • kod som distribuerar eller överför auktoritet

Den exakta TCB:n beror på systemets design.

I en traditionell monolitisk kärna ingår vanligtvis stora delar av kärnan i TCB, eftersom många delsystem körs med fulla kärnprivilegier. En bugg i en drivrutin, ett filsystem eller en nätverksstack kan bli en kärnkompromettering.

I ett mikrokärnesystem kan kärnans TCB vara mindre, men det totala betrodda systemet omfattar fortfarande komponenterna som distribuerar auktoritet och genomdriver policy.

Den skillnaden spelar roll.

Mikrokärnor minskar mängden kod som körs med full maskinauktoritet. De gör inte alla tjänster i användarrymden obetrodda automatiskt.


Betrodd betyder inte säker

Ordet “betrodd” är lätt att missförstå.

En betrodd komponent är inte en komponent som vi vet är korrekt.

Det är en komponent som systemet är beroende av att den är korrekt.

Det är en mindre bekväm definition, men den användbara.

Om en tjänst är betrodd för att distribuera kapabiliteter kan en bugg i tjänsten ge auktoritet felaktigt. Om en parser är betrodd för att validera en körbar fil före boot kan en parserbugg underminera bootkedjan. Om en syscall-hanterare i kärnan är betrodd för att validera endpoint-rättigheter kan en saknad kontroll bryta isoleringen.

Förtroende är inte beröm.

Förtroende är risk.

Målet är därför inte att märka så mycket kod som möjligt som betrodd. Målet är att göra den betrodda mängden så liten, smal och granskningsbar som möjligt.


Varför storlek spelar roll

TCB:ns storlek spelar roll av flera skäl.

1. Mer kod betyder fler buggar

All programvara har buggar.

När mängden betrodd kod växer växer också sannolikheten för säkerhetsrelevanta buggar. Det gäller särskilt kod som:

  • parsar obetrodd indata
  • hanterar minne
  • hanterar samtidighet
  • tolkar behörigheter
  • översätter en auktoritetsmodell till en annan

Operativsystem innehåller alla dessa mönster.

Att minska TCB:ns storlek tar inte bort buggar, men minskar mängden kod där en bugg kan kompromettera hela systemet.


2. Fler gränssnitt betyder större attackyta

Attackyta handlar inte bara om kodrader.

Det handlar också om ingångspunkter.

Varje gränssnitt in i betrodd kod är en plats där en angripare kan lämna indata:

  • syscall-argument
  • IPC-meddelanden
  • metadata i bootavbildningar
  • ELF-huvuden
  • filsystemsstrukturer
  • enhetsdeskriptorer
  • avbrottshändelser
  • tabeller från firmware

Varje gränssnitt behöver validering.

En liten betrodd komponent med ett dåligt designat gränssnitt kan fortfarande vara farlig. Men när antalet betrodda gränssnitt växer växer valideringsbördan med det.


3. Mer tillstånd gör resonemang svårare

Säkerhetsfel uppstår ofta inte för att en enskild kontroll saknas, utan för att tillstånd ändras i oväntad ordning.

Till exempel:

  • en kapabilitet kopieras innan rättigheter reduceras
  • en tjänst startar innan dess startup-bundle är helt validerad
  • ett gammalt lokalt slot behandlas som bevis på auktoritet
  • en enhet anses närvarande innan upptäckten är klar
  • en process behåller auktoritet efter en misslyckad startväg

Ju mer betrott muterbart tillstånd ett system har, desto svårare blir det att bevisa att varje övergång bevarar avsedda invariants.

Det är därför EriX betonar deterministisk startup, explicita överföringsposter och fail-closed-beteende.


4. Mer privilegium betyder större skadezon

Samma bugg får olika konsekvenser beroende på var den sker.

En parserbugg i ett oprivilegierat verktyg kan krascha verktyget.

En parserbugg i bootloadern kan kompromettera hela systemet innan kärnan startar.

En bugg i en användarrymdsdriver som bara har åtkomst till ett specifikt I/O- intervall är allvarlig, men den är annorlunda än en bugg i en drivrutin som körs inne i kärnan med full maskinauktoritet.

Att minska TCB handlar delvis om att minska skadezonen för enskilda buggar.


Kärnan är bara en del av TCB

Det är frestande att säga:

TCB är kärnan.

Det är oftast för enkelt.

Kärnan är central, men ett säkert system beror också på koden som förbereder kärnan, startar den första användarrymdsuppgiften, definierar auktoritetsformat och distribuerar kapabiliteter.

I EriX är kärnan uttryckligen i TCB.

Den äger kapabilitetstabeller i kärnrymden, schemaläggningstillstånd och maskinresurser. Den validerar handoff från bootloader till kernel, skapar root-uppgiften, hanterar grundläggande kärnobjekt och exponerar arkitekturspecifika ingångspunkter för trap, syscall och avbrott.

Om kärnan inte genomdriver kapabilitetskontroller, endpoint-rättigheter, adressrymdsgränser eller objektlivstider fallerar systemets isoleringsmodell.

Men kärnan är inte hela berättelsen.


Bootkod är också betrodd

Bootloadern körs före kärnan.

Det gör den säkerhetskritisk.

I EriX ansvarar bootloadern för att ladda och verifiera en signerad boot.img, parsa kärn- och tjänsteavbildningar, bygga en deterministisk handoff-struktur och överföra kontrollen till kärnan.

Det placerar bootloadern i TCB.

Den har firmwaretilldelad auktoritet under boot och kontrollerar det sista hoppet in i kärnan. Den måste behandla bootmediet som obetrott, validera avbildningens struktur och kryptografiska integritet, avvisa felformade ELF-binärer och misslyckas stängt vid oklarhet.

Om bootloadern accepterar en manipulerad avbildning eller bygger en inkonsekvent handoff kan kärnan börja från en komprometterad grund.

Därför måste bootkod vara liten, strikt och tråkig.


Parsers kan vara TCB-gränser

Parsers underskattas ofta i systemsäkerhet.

De sitter precis där obetrodda bytes blir betrodd struktur.

EriX behandlar flera parser- och ABI-crates som TCB- eller TCB-nära komponenter:

  • lib-bootimg parsar och verifierar boot.img-struktur, hashar och signaturer
  • lib-elf validerar ELF64-binärer innan bootloadern litar på laddsegment
  • lib-handoff validerar versionerade handoff-strukturer mellan bootsteg
  • lib-ipc definierar och validerar IPC-meddelandelayouter
  • lib-capabi definierar kapabilitetstyper, rättigheter, slotkonstanter och överföringsdeskriptorer

Dessa bibliotek behöver inte själva ha runtime-kapabiliteter.

Det gör dem inte irrelevanta för TCB.

Om lib-bootimg accepterar en ändrad bootavbildning kan bootloadern lita på kod som borde avvisas. Om lib-elf accepterar en felformad körbar fil kan bootkedjan ladda fel bytes eller lita på ogiltiga segmentintervall. Om lib-ipc avkodar ett meddelande fel kan en operation tolkas fel. Om lib-capabi definierar en för bred rollpolicy kan en tjänst få auktoritet den aldrig borde ha.

Ren kod kan fortfarande vara betrodd kod.

Den viktiga egenskapen är att dessa bibliotek är smala. De gör ingen I/O, äger ingen systempolicy och undviker ambient auktoritet. Deras jobb är att parsa, validera och avvisa.


Tjänster i användarrymden kan vara betrodda komponenter

Att flytta policy ut ur kärnan gör inte att policyn försvinner.

Den flyttas till tjänster i användarrymden där den kan isoleras, begränsas och granskas separat.

I EriX är rootd den första policybärande auktoriteten i användarrymden. Den validerar handoff från kernel till root, parsar bootkonfiguration, kör startup- DAG:en och överför kapabiliteter med minsta privilegium till nödvändiga tjänster.

rootd är högprivilegierad.

Men den är inte kärnan.

Den skillnaden är viktig. rootd är betrodd för tidig policy och kapabilitetsdistribution, men implementerar inte kärnobjekt och äger inte maskinauktoritet direkt. Dess jobb är att distribuera auktoritet enligt explicita startup-kontrakt.

Andra tjänster finns också inom specifika betrodda gränser:

  • procd är betrodd för processlivscykelorkestrering
  • deviced är betrodd för drivrutinspolicy och leverans av drivrutinskapabilitet
  • vfsd är betrodd som gräns för det publika filsystemsnamnrymden
  • privata filsystemsproviders är betrodda endast för sin providerroll

Det gör inte dessa tjänster oviktiga.

Det gör deras auktoritet smalare än full kärnauktoritet.


Attackyta i en monolitisk design

I en monolitisk kärna delar många delsystem ett enda privilegierat adressutrymme.

Det kan göra snabba vägar enkla, men skapar också en bred attackyta.

En sårbarhet i vilket delsystem som helst i kärnan kan bli en kärnsårbarhet:

  • felformad filsystemsmetadata
  • buggig nätpakethantering
  • osäker drivrutinskod
  • oväntade hårdvarudeskriptorer
  • race conditions i delat kärntillstånd

Angriparen behöver bara en väg in i privilegierad kod.

Det betyder inte att monolitiska kärnor inte kan vara säkra. De kan konstrueras, härdas, fuzzas, sandboxas och granskas omfattande.

Men arkitekturen börjar med en stor privilegierad yta.

Säkerhetsargumentet måste sedan förklara hur den stora ytan kontrolleras.


Attackyta i en mikrokärnedesign

En mikrokärna ändrar attackytans form.

Kärnan exponerar fortfarande kritiska gränssnitt:

  • syscalls
  • IPC-leverans
  • schemaläggning
  • adressrymdsoperationer
  • kapabilitetsoperationer
  • avbrottshantering

Dessa gränssnitt måste vara korrekta.

Men högre nivåers tjänster körs inte automatiskt med fulla kärnprivilegier. Om en filesystemprovider hanterar felformade medier är målet att buggen stannar inom providerauktoriteten. Om en drivrutin misslyckas är målet att den misslyckas endast med den enhetsauktoritet den explicit fick.

Detta gör en stor privilegierad yta till flera mindre auktoritetsytor.

Det är inte automatiskt enklare.

Det fungerar bara om gränserna är strikta och auktoriteten som passerar dem är smal.


Hur EriX minimerar TCB

EriX minskar TCB-storlek och attackyta genom flera designval.

1. En policyminimal kärna

EriX-kärnan ansvarar för mekanismer, inte systempolicy.

Den hanterar:

  • grundläggande kärnobjekt
  • kapabilitetssemantik
  • schemaläggning och task-exekvering
  • adressrymdsprimitiver
  • IPC och endpoint-dispatch
  • ingångspunkter för avbrott och undantag

Den äger inte:

  • policy för tjänstestart
  • processorkestreringspolicy
  • filsystemsnamnrymdspolicy
  • drivrutinsaktiveringspolicy
  • högnivåpolicy för minnesallokering

Det håller kärnan fokuserad på isolering i stället för att bestämma hela systemets form.


2. Explicita kapabiliteter i stället för ambient auktoritet

EriX modellerar auktoritet genom kapabiliteter.

En komponent kan bara agera om den har en kapabilitet med rätt rättigheter. Kärnan validerar kapabilitetsreferenser vid användning, genomdriver endpoint-rättigheter vid syscall-dispatch och behandlar överföringar som explicita händelser.

Detta undviker globala namn eller konventionella slotnummer som behörighet.

Att känna till ett slotnummer är inte auktoritet.

Att ha rätt kapabilitet i lokal CSpace är auktoritet.

Den skillnaden är central för att minska TCB: betrodd kod behöver inte härleda behörighet ur globalt tillstånd när auktoriteten bärs explicit.


3. Smala kernel-control-endpoints

Äldre operativsystemdesigner koncentrerar ofta kontroll bakom breda privilegierade gränssnitt.

EriX går åt andra hållet.

Nuvarande runtime använder smala familjer av kernel-control-endpoints för specifika jobb:

  • tid
  • avbrott
  • hotplug
  • PCI-konfiguration
  • konsol och framebuffer
  • COM1-I/O
  • i8042-I/O
  • memory retyping
  • VSpace-mappning
  • pager-felupplösning
  • processkontroll
  • ACPI-läsningar

Normal runtime-boot ger inte tjänster ett brett root-endpoint för alla kärnoperationer.

I stället får en tjänst den endpoint-familj den behöver. timed får tidskontroll. irqd får avbrottskontroll. drv-serial får COM1-specifik I/O. drv-i8042 får i8042-specifik I/O. drv-acpi får ACPI-läsauktoritet.

Det smalnar både det betrodda gränssnittet och skadan från missbruk.


4. Exakta startup-kontrakt

EriX behandlar startup-överföring av kapabiliteter som ett kontrakt, inte ett förslag.

Startup-envelopes beskriver vilka kapabiliteter en tjänst ska få, var de ska synas och vilka rättigheter de ska bära.

Tjänster validerar de faktiska lokala slot de fick innan de deklarerar sig redo. Endpoint-överföringar kontrolleras efter källslot, destinationsslot, rättigheter och förväntad endpoint-typ. Okända, felaktiga eller extra överföringar avvisas.

Detta förhindrar en vanlig auktoritetsbugg:

“Ett slot är upptaget, alltså måste det vara rätt sak.”

I EriX är slotockupation i sig inget bevis på auktoritet.

Kapabiliteten måste matcha den deklarerade auktoritetsformen.


5. Stegvis processkapande

Processkapande är en riskfylld operation eftersom den kombinerar exekvering, adressrymder, endpoints och initial auktoritet.

EriX styr runtime-processkapande genom stegvis child creation i stället för en äldre direkt skapa-operation.

Flödet är explicit:

  1. skapa ett staged child
  2. ta emot install grant och child endpoint-alias
  3. installera deklarerad startup-kapabilitetsbundle
  4. starta processen först när populationen är klar

Kärnan nekar processstart medan levande install grants fortfarande pekar på det child-staget.

Det hindrar delvis populerade processer från att bli körbara med tvetydigt auktoritetstillstånd.


6. Rollspecifik drivrutinsauktoritet

Drivrutiner är en stor källa till operativsystemsrisk.

EriX behandlar inte all hårdvarukontroll som en bred behörighet.

Drivrutinsauktoritet är rollspecifik:

  • drv-serial får endast COM1-I/O-auktoritet
  • drv-i8042 får endast i8042-I/O-auktoritet
  • drv-acpi får ACPI-läsauktoritet
  • probed får PCI-konfigurationsläsauktoritet
  • drv-virtio-block får en validerad device frame i stället för generell minnesauktoritet

deviced hanterar drivrutinspolicy, men ger inte varje drivrutin en generisk device-control-kapabilitet. Den använder explicit installation av startup-kapabiliteter och rollspecifika auktoritetsytor.

Det begränsar TCB-effekten av drivrutinsbuggar.


7. Enhetsminne är en separat kapabilitetstyp

Enhetsminne är farligt eftersom det kan påverka hårdvarutillstånd direkt.

EriX skiljer enhetsminne från vanligt RAM med CAP_TYPE_DEVICE_FRAME.

Lagringsvägen kan härleda en BAR-stödd MMIO-frame för deviced, och deviced kan installera endast den härledda enhetsframen i en drivrutins startup-bundle.

Den enhetsframen behandlas inte som vanligt allokerbart minne.

Det spelar roll eftersom sammanblandning av enhetsminne och normala frames skulle bredda auktoritetsmodellen och göra resonemang om minnessäkerhet svårare.


8. Clean-room-beroenden

Beroenden kan förstora TCB tyst.

Om betrodd kod beror på ett generellt externt bibliotek kan systemet ärva:

  • kodvägar det inte behöver
  • antaganden som inte matchar operativsystemet
  • parserbeteende som är för tillåtande
  • uppdaterings- och leveranskedjerisk

EriX undviker tredjepartscrates och implementerar sina kritiska bibliotek inom projektet.

Det ökar implementationsarbetet, men håller den betrodda gränsen synlig. När en parser, ABI-crate eller kryptografisk hjälpare är del av boot- eller auktoritetsvägen är den del av systemets granskningsyta.


9. Fail-closed-beteende

En liten TCB räcker inte om fel är tvetydiga.

EriX försöker göra säkerhetskänsliga fel explicita och terminala:

  • felformad boot-handoff stoppar boot
  • versioner som inte stöds avvisas
  • ogiltig startup-auktoritet hindrar readiness
  • fel endpoint-typer misslyckas vid validering
  • fel i en nödvändig tjänst stoppar progression
  • fel efter start utlöser fail-closed-teardown

Att misslyckas stängt är viktigt eftersom återhämtningsheuristik ofta blir dold policy.

Dold policy utvidgar systemets betrodda beteende.


Mindre betyder inte trivialt

Att minska TCB gör inte systemdesign lätt.

Det gör den ofta mer explicit.

I stället för att placera all logik i ett privilegierat adressutrymme måste systemet definiera:

  • vilken komponent som äger varje beslut
  • vilken auktoritet varje komponent får
  • hur auktoritet överförs
  • hur fel rapporteras
  • hur delvis startup städas upp
  • hur gamla kapabiliteter ogiltigförklaras eller släpps

Det är mer arbete i början.

Men det producerar ett system där säkerhetsargumentet är lättare att granska.

Frågan blir:

Vilken komponent måste vara betrodd för denna specifika egenskap?

Det är en bättre fråga än:

Är hela kärnan korrekt?


En praktisk TCB-bild av EriX

En praktisk bild av EriX TCB är lagerindelad.

Längst ned finns bootkedjan:

  • bootloader
  • verifiering av bootavbildning
  • read/verify-vägen i lib-bootimg
  • ELF-parsning
  • handoff-validering

Sedan kärnan:

  • kapabilitetsobjekt
  • CSpace- och VSpace-genomdrivning
  • IPC och endpoint-rättigheter
  • schemaläggning och trap-hantering
  • avbrottsleverans

Sedan tidigt betrott användarrymme:

  • rootd för startpolicy och kapabilitetsdistribution
  • procd för processlivscykel
  • deviced för drivrutinspolicy
  • utvalda ABI- och valideringsbibliotek delade mellan tjänster

Sedan smalare betrodda tjänster:

  • filsystemsnamnrymdsmediering i vfsd
  • privata backend-providers
  • tjänster för input, konsol, loggning, block, tid och avbrott

Alla dessa komponenter är inte lika privilegierade.

Det är poängen.

EriX försöker undvika en platt betrodd värld. Varje komponent bör vara betrodd endast för sin dokumenterade roll och endast med de kapabiliteter den uttryckligen fick.


Framåt

TCB-diskussionen leder naturligt till implementationsspråket.

Om betrodd kod måste vara liten, explicit och granskningsbar spelar minnessäkerhet roll. Det gör även unsafe-gränser, parserkorrekthet, datalayout och disciplinen som krävs nära hårdvaran.

Nästa inlägg undersöker varför EriX främst är skrivet i Rust, vad Rust löser och inte löser för kärnutveckling, och hur det jämförs med den traditionella C-metoden för systemprogrammering.