Moderna operativsystem upprätthåller säkerhetsgränser mellan processer, filer, enheter och användare. Hur dessa gränser implementeras varierar dock avsevärt mellan olika systemdesigner.

De flesta vanliga operativsystem förlitar sig på identitetsbaserad åtkomstkontroll och globala namnrymder. Kapabilitetsbaserade operativsystem använder ett fundamentalt annorlunda angreppssätt: de representerar auktoritet explicit och gör den till ett förstaklassigt begrepp.

Detta inlägg introducerar kapabilitetssystem, förklarar hur de skiljer sig från traditionella modeller och beskriver varför de är centrala för EriX design.


Problemet: omgivande auktoritet

I traditionella system har processer ofta åtkomst till resurser genom omgivande auktoritet.

Omgivande auktoritet innebär att ett program kan komma åt en resurs enbart för att den existerar i en delad namnrymd och systemet avgör att programmet har tillstånd att använda den.

Till exempel:

  • En process kan öppna /etc/passwd om den har tillräckliga rättigheter.
  • Ett program kan ansluta till en nätverkssocket om operativsystemet tillåter det.
  • En tjänst kan komma åt filer baserat på användaridentitet eller gruppmedlemskap.

I alla dessa fall är auktoriteten att komma åt resursen implicit.

Processen har ingen direkt, explicit referens till resursen. I stället förlitar den sig på:

  • globala namn (filsökvägar, portar, enhetsidentifierare)
  • åtkomstkontroller (användar-ID:n, rättigheter, ACL:er)

Denna modell skapar flera problem:

1. Confused Deputy-problemet

Ett program kan av misstag missbruka sin auktoritet på uppdrag av ett annat program.

Till exempel kan en privilegierad tjänst läsa en fil på begäran av en opålitlig klient, även om klienten själv inte borde ha åtkomst till den.

2. Alltför bred auktoritet

Program kör ofta med fler rättigheter än de faktiskt behöver. Det ökar konsekvenserna av buggar eller komprometteringar.

3. Svårt att resonera om

Det är svårt att avgöra vad en process får göra utan att analysera globalt tillstånd, användaridentiteter och åtkomstkontrollpolicyer.


Kärnidén: kapabiliteter

En kapabilitet är en oförfalskbar token som ger åtkomst till ett specifikt objekt med specifika rättigheter.

I stället för att fråga:

“Har den här processen tillstånd att komma åt denna resurs?”

frågar ett kapabilitetsbaserat system:

“Innehar den här processen en kapabilitet som auktoriserar denna operation?”

Om svaret är nej kan operationen inte fortsätta.


Egenskaper hos kapabiliteter

Kapabiliteter har flera definierande egenskaper:

1. Oförfalskbarhet

En process kan inte skapa en giltig kapabilitet ur tomma luften.

Kapabiliteter skapas och hanteras av kärnan, vilket säkerställer att de inte kan förfalskas eller gissas fram.


2. Explicit auktoritet

All auktoritet representeras explicit genom kapabiliteter.

Om en process kan utföra en operation måste den ha en kapabilitet som tillåter det. Det finns ingen implicit åtkomst via globala namnrymder.


3. Finkorniga rättigheter

Kapabiliteter kan koda specifika rättigheter, till exempel:

  • skrivskyddad åtkomst
  • skrivrättigheter
  • exekveringsrättigheter
  • begränsade delmängder av operationer

Detta möjliggör exakt kontroll över vad varje process får göra.


4. Överförbarhet

Kapabiliteter kan överföras mellan processer, vanligtvis via interprocesskommunikation (IPC).

Detta möjliggör kontrollerad delegering av auktoritet.


Objekt och kapabiliteter

I ett kapabilitetsbaserat system modelleras allt som ett objekt:

  • minnesregioner
  • filer
  • enheter
  • IPC-ändpunkter
  • processer

En kapabilitet är en referens till ett objekt kombinerad med en uppsättning rättigheter.

En process interagerar med systemet genom att anropa operationer på objekt via sina kapabiliteter.

Det finns inget behov av globala namn som filsökvägar eller enhetsidentifierare inne i kärnan. All åtkomst medieras genom kapabiliteter.


Hur kapabilitetssystem fungerar

På en övergripande nivå fungerar ett kapabilitetsbaserat system så här:

  1. Kärnan skapar objekt och kapabiliteter.
  2. Varje process har ett kapabilitetsutrymme (ofta kallat CSpace), där dess kapabiliteter lagras.
  3. En process kan bara operera på objekt som den har kapabiliteter för.
  4. Kapabiliteter kan överföras mellan processer via IPC.
  5. Kärnan upprätthåller alla kapabilitetskontroller.

Resultatet är ett system där auktoritet är:

  • explicit
  • lokaliserad
  • överförbar
  • lätt att resonera om

Exempel: att öppna en fil

Traditionell modell

I ett traditionellt system:

  1. En process anropar open("/etc/config")
  2. Kärnan kontrollerar rättigheter:
    • användar-ID
    • gruppmedlemskap
    • filens lägesbitar
  3. Om åtkomst tillåts returneras en filbeskrivare

Auktoriteten kommer från globalt tillstånd och identitet.


Kapabilitetsbaserad modell

I ett kapabilitetssystem:

  1. En process måste redan inneha en filkapabilitet
  2. Den använder denna kapabilitet för att utföra läs- eller skrivoperationer

Om processen inte har kapabiliteten kan den inte komma åt filen, oavsett vilket namn den använder.

Det finns inget globalt uppslagssteg inne i kärnan.


Delegering och minsta privilegium

En av de mest kraftfulla aspekterna av kapabilitetssystem är delegering.

En process kan ge en annan process en delmängd av sin auktoritet genom att överföra en kapabilitet.

Till exempel:

  • En filserver ger en klient skrivskyddad åtkomst till en fil
  • En minneshanterare ger åtkomst till en specifik minnesregion
  • En process ger åtkomst till en IPC-ändpunkt

Detta möjliggör principen om minsta privilegium:

Varje komponent får bara den auktoritet den faktiskt behöver.


Att eliminera omgivande auktoritet

Kapabilitetssystem eliminerar omgivande auktoritet helt.

Det finns:

  • inga globala namnrymder inne i kärnan
  • ingen implicit åtkomst baserad på identitet
  • inga dolda privilegier

All auktoritet måste överföras explicit.

Det gör det mycket enklare att analysera:

  • vad en process kan göra
  • hur auktoritet flödar genom systemet
  • var potentiella säkerhetsproblem kan uppstå

Återkallelse (ett svårt problem)

En utmaning i kapabilitetssystem är återkallelse.

När en kapabilitet väl har getts till en process, hur kan den tas tillbaka?

Olika system implementerar återkallelse på olika sätt:

  • indirektionslager
  • referensspårning
  • kapabilitetsträd
  • versionsmekanismer

Återkallelse är ett viktigt forskningsområde och kommer att utforskas i senare skeden av EriX.


Kapabilitetssystem i praktiken

Kapabilitetsbaserade idéer är inte nya. Flera system har implementerat dem:

  • KeyKOS / EROS
  • seL4 (en formellt verifierad mikrokärna)
  • CHERI (maskinvarustödda kapabiliteter)
  • Capsicum (kapabilitetsutökningar för FreeBSD)

Dessa system visar att kapabilitetsbaserade designer är både praktiska och kraftfulla.


Hur EriX använder kapabiliteter

EriX är från grunden utformat som ett kapabilitetsbaserat system.

I EriX:

  • representeras all auktoritet genom kapabiliteter
  • är kapabiliteter starkt typade
  • är kapabiliteter immutabla när de väl har skapats
  • överförs kapabiliteter via IPC
  • upprätthåller kärnan oförfalskbarhet och säkerhetsinvarianter

Det finns inga globala namnrymder inne i kärnan. All resursåtkomst medieras genom kapabiliteter.

Detta ligger i linje med det bredare målet att göra auktoritet:

  • explicit
  • minimal
  • lätt att resonera om

Varför detta spelar roll

Kapabilitetsbaserade system ger en grund för att bygga:

  • säkrare system
  • mer modulära arkitekturer
  • system som är lättare att analysera och verifiera

Genom att ta bort implicit auktoritet och göra all åtkomst explicit eliminerar de hela klasser av sårbarheter som är vanliga i traditionella system.


Framåt

Kapabiliteter är ett grundläggande begrepp i EriX. I framtida inlägg kommer vi att utforska hur de implementeras i praktiken, inklusive:

  • kapabilitetsutrymmen (CSpace)
  • interprocesskommunikation (IPC)
  • minneshantering med hjälp av otypade kapabiliteter
  • delegering och auktoritetsgrafer

Att förstå kapabiliteter är det första steget mot att förstå resten av systemet.