I sistemi operativi moderni impongono confini di sicurezza tra processi, file, dispositivi e utenti. Tuttavia, il modo in cui questi confini vengono implementati varia in modo significativo tra diversi progetti di sistema.

La maggior parte dei sistemi operativi tradizionali si basa sul controllo di accesso basato sull’identità e sugli spazi dei nomi globali. I sistemi operativi basati su capacità adottano un approccio fondamentalmente diverso: rappresentano l’autorità in modo esplicito e la rendono un concetto di primo livello.

Questo articolo introduce i sistemi basati su capacità, spiega in cosa differiscono dai modelli tradizionali e descrive perché sono centrali per il design di EriX.


Il problema: l’autorità ambientale

Nei sistemi tradizionali, i processi hanno spesso accesso alle risorse tramite l’autorità ambientale.

Autorità ambientale significa che un programma può accedere a una risorsa semplicemente perché essa esiste in uno spazio dei nomi condiviso e il sistema determina che il programma ha il permesso di usarla.

Per esempio:

  • Un processo può aprire /etc/passwd se ha permessi sufficienti.
  • Un programma può collegarsi a un socket di rete se il sistema operativo lo consente.
  • Un servizio può accedere ai file in base all’identità dell’utente o all’appartenenza a un gruppo.

In tutti questi casi, l’autorità per accedere alla risorsa è implicita.

Il processo non possiede un riferimento diretto ed esplicito alla risorsa. Invece, si basa su:

  • nomi globali (percorsi di file, porte, identificatori di dispositivo)
  • controlli di accesso (ID utente, permessi, ACL)

Questo modello crea diversi problemi:

1. Problema del delegato confuso

Un programma può usare accidentalmente la propria autorità in modo improprio per conto di un altro programma.

Per esempio, un servizio privilegiato potrebbe leggere un file richiesto da un client non affidabile, anche se quel client non dovrebbe avere accesso al file.

2. Autorità eccessivamente ampia

I programmi spesso vengono eseguiti con più permessi di quanti ne abbiano realmente bisogno. Questo aumenta l’impatto di bug o compromissioni.

3. Difficile da analizzare

È difficile determinare cosa un processo sia autorizzato a fare senza analizzare lo stato globale, le identità utente e le policy di controllo degli accessi.


L’idea centrale: le capacità

Una capacità è un token non falsificabile che concede accesso a uno specifico oggetto con specifici diritti.

Invece di chiedere:

“Questo processo ha il permesso di accedere a questa risorsa?”

un sistema basato su capacità chiede:

“Questo processo possiede una capacità che autorizza questa operazione?”

Se la risposta è no, l’operazione non può procedere.


Proprietà delle capacità

Le capacità hanno diverse proprietà definitorie:

1. Non falsificabilità

Un processo non può creare una capacità valida dal nulla.

Le capacità vengono create e gestite dal kernel, il che garantisce che non possano essere falsificate né indovinate.


2. Autorità esplicita

Tutta l’autorità è rappresentata esplicitamente tramite capacità.

Se un processo può eseguire un’operazione, deve possedere una capacità che la consenta. Non esiste accesso implicito tramite spazi dei nomi globali.


3. Diritti granulari

Le capacità possono codificare permessi specifici, come:

  • accesso in sola lettura
  • accesso in scrittura
  • permessi di esecuzione
  • sottoinsiemi limitati di operazioni

Questo consente un controllo preciso su ciò che ogni processo può fare.


4. Trasferibilità

Le capacità possono essere trasferite tra processi, tipicamente tramite comunicazione inter-processo (IPC).

Questo consente una delega controllata dell’autorità.


Oggetti e capacità

In un sistema basato su capacità, tutto viene modellato come un oggetto:

  • regioni di memoria
  • file
  • dispositivi
  • endpoint IPC
  • processi

Una capacità è un riferimento a un oggetto combinato con un insieme di diritti.

Un processo interagisce con il sistema invocando operazioni sugli oggetti attraverso le proprie capacità.

Non c’è bisogno di nomi globali come percorsi di file o identificatori di dispositivo all’interno del kernel. Ogni accesso è mediato da capacità.


Come funzionano i sistemi basati su capacità

A livello generale, un sistema basato su capacità funziona così:

  1. Il kernel crea oggetti e capacità.
  2. Ogni processo ha uno spazio di capacità (spesso chiamato CSpace), che memorizza le sue capacità.
  3. Un processo può operare solo sugli oggetti per cui possiede capacità.
  4. Le capacità possono essere trasferite tra processi tramite IPC.
  5. Il kernel applica tutti i controlli di capacità.

Il risultato è un sistema in cui l’autorità è:

  • esplicita
  • localizzata
  • trasferibile
  • facile da analizzare

Esempio: aprire un file

Modello tradizionale

In un sistema tradizionale:

  1. Un processo chiama open("/etc/config")
  2. Il kernel controlla i permessi:
    • ID utente
    • appartenenza a un gruppo
    • bit di modalità del file
  3. Se consentito, viene restituito un descrittore di file

L’autorità proviene da stato globale e identità.


Modello basato su capacità

In un sistema basato su capacità:

  1. Un processo deve già possedere una capacità di file
  2. Usa questa capacità per eseguire operazioni di lettura o scrittura

Se il processo non possiede la capacità, non può accedere al file, a prescindere dal nome che usa.

Non esiste un passaggio di ricerca globale all’interno del kernel.


Delega e privilegio minimo

Uno degli aspetti più potenti dei sistemi basati su capacità è la delega.

Un processo può dare a un altro processo un sottoinsieme della propria autorità trasferendogli una capacità.

Per esempio:

  • Un file server concede a un client accesso in sola lettura a un file
  • Un gestore della memoria concede accesso a una regione di memoria specifica
  • Un processo concede accesso a un endpoint IPC

Questo abilita il principio del privilegio minimo:

Ogni componente riceve solo l’autorità di cui ha realmente bisogno.


Eliminare l’autorità ambientale

I sistemi basati su capacità eliminano completamente l’autorità ambientale.

Non ci sono:

  • spazi dei nomi globali all’interno del kernel
  • accesso implicito basato sull’identità
  • privilegi nascosti

Ogni autorità deve essere passata esplicitamente.

Questo rende molto più facile analizzare:

  • che cosa un processo può fare
  • come l’autorità fluisce attraverso il sistema
  • dove possono sorgere potenziali problemi di sicurezza

Revoca (un problema difficile)

Una delle sfide dei sistemi basati su capacità è la revoca.

Una volta che una capacità è stata data a un processo, come può essere tolta?

Sistemi diversi implementano la revoca in modi diversi:

  • livelli di indirezione
  • tracciamento dei riferimenti
  • alberi di capacità
  • meccanismi di versioning

La revoca è un’importante area di ricerca e sarà esplorata nelle fasi successive di EriX.


Sistemi basati su capacità nella pratica

Le idee basate su capacità non sono nuove. Diversi sistemi le hanno implementate:

  • KeyKOS / EROS
  • seL4 (un micro-nucleo verificato formalmente)
  • CHERI (capacità assistite dall’hardware)
  • Capsicum (estensioni di capacità per FreeBSD)

Questi sistemi dimostrano che i design basati su capacità sono sia pratici sia potenti.


Come EriX usa le capacità

EriX è progettato fin dall’inizio come un sistema basato su capacità.

In EriX:

  • tutta l’autorità è rappresentata tramite capacità
  • le capacità sono fortemente tipizzate
  • le capacità sono immutabili una volta create
  • le capacità sono trasferite tramite IPC
  • il kernel applica non falsificabilità e invarianti di sicurezza

Non ci sono spazi dei nomi globali all’interno del kernel. Ogni accesso alle risorse è mediato da capacità.

Questo si allinea con l’obiettivo più ampio di rendere l’autorità:

  • esplicita
  • minima
  • facile da analizzare

Perché questo conta

I sistemi basati su capacità forniscono una base per costruire:

  • sistemi più sicuri
  • architetture più modulari
  • sistemi più facili da analizzare e verificare

Eliminando l’autorità implicita e rendendo esplicito ogni accesso, eliminano intere classi di vulnerabilità comuni nei sistemi tradizionali.


Guardando avanti

Le capacità sono un concetto fondamentale in EriX. Nei prossimi articoli esploreremo come vengono implementate nella pratica, inclusi:

  • spazi di capacità (CSpace)
  • comunicazione inter-processo (IPC)
  • gestione della memoria usando capacità non tipizzate
  • delega e grafi di autorità

Comprendere le capacità è il primo passo per comprendere il resto del sistema.