basato su

"What is a Capability, Anyway?"

di Jonathan Shapiro
For terms of redistribution, see the GNU General Public License

prefazione

"What is a Capability, Anyway?" è uno dei saggi introduttivi a EROS (Extremely Reliable Operating System1), che descrive brevemente cosa sono le capabilities, cosa permettono di fare, e perché possono fornire una maggiore sicurezza rispetto agli attuali sistemi operativi. Lo stile informale di questa traduzione ricalca quello ancora più informale dell'originale di Shapiro.

introduzione

Le capabilities in sé sono un concetto molto semplice, ma rigirano sottosopra tutto quello che uno conosce a proposito della sicurezza; la prima impressione di molte persone è che siano troppo semplici per poter funzionare;

quella che segue è una semplice introduzione alle capabilities e ai sistemi su di esse basati; vedremo cosa si intende per controllo dell'accesso, cos'è una capability e alcuni esempi di come può essere usata per fornire un flessibile accesso agli oggetti senza rinunciare alla sicurezza; saranno descritti alcuni problemi che i comuni meccanismi di controllo dell'accesso non possono affrontare, e considereremo in particolare il problema della revoca dell'accesso. Infine vedremo perché le capabilities sono state rifiutate dalla maggioranza degli informatici.

Utilizzeremo UNIX come sistema di riferimento, ma gli stessi problemi esistono in VMS, Windows, ecc.;

Il problema: controllo dell'accesso

Il problema fondamentale della sicurezza in un sistema informatico è controllare a quali oggetti un programma può accedere, e in quali modi. Per oggetti intendiamo: files, schede audio, altri programmi, la rete, il modem, ecc., e per accesso quali tipi di operazioni possono essere eseguite su quegli oggetti; ad es.: leggere un file, scrivere su un file, creare o eliminare degli oggetti, comunicare con altri programmi, ecc.;

quando parliamo di "controllo dell'accesso", stiamo parlando in realtà di quattro cose diverse:

Impedire l'accesso: vogliamo essere sicuri che un utente non possa danneggiarne un altro, o accedere alle sue informazioni private; questo è ciò che molti intendono quando si parla di sicurezza. Limitare l'accesso: vogliamo essere sicuri che un programma o un utente non facciano più di quello che vogliamo: ad es., eseguire un programma scaricato da internet senza che possa leggere o scrivere alcun file, per esser sicuri che non installi un virus o mandi in giro sulla rete gli indirizzi di posta elettronica dal nostro elenco. Soltanto da quando si è diffuso Java, si sta cominciando a prestare attenzione a questo problema. Consentire l'accesso: potremmo voler permettere a due persone di lavorare assieme a un documento, o fornire l'accesso a un file particolare a qualcun altro, per potergli delegare un lavoro; ma probabilmente vorremo farlo in maniera selettiva: il fatto che si lavori assieme ad un progetto non vuol dire che uno deve poter leggere le informazioni sanitarie dell'altro. Da un punto di vista pratico, questo punto è importante come i primi due, ma i teorici della sicurezza non ci badano molto. Revocare l'accesso: dopo aver permesso l'accesso a un oggetto potremmo aver bisogno di revocare quest'accesso quando la persona non è più autorizzata ad accedervi. Quando si parla di controllo dell'accesso ci si riferisce ai programmi, non alle persone; questa distinzione è importante per diversi motivi: Vediamo ora cosa sono le capabilities e come permettono di ottenere il controllo dell'accesso.

Cos'è una capability

Il termine capability fu introdotto da Dennis e Van Horn nel 1966 in un saggio intitolato Programming Semantics for Multiprogrammed Computations; l'idea fondamentale è questa: supponiamo di progettare un sistema informatico in modo che per accedere a un oggetto, un programma debba avere un token particolare: questo token specifica un oggetto e allo stesso tempo fornisce al programma l'autorità per eseguire un determinato insieme di operazioni (come lettura o scrittura) su quell'oggetto. Un tale token si può definire una capability.

Una capability somiglia molto ad una chiave di un comune mazzo di chiavi; consideriamo ad esempio le chiavi di un'automobile: esse funzionano su una macchina particolare (identificano uno specifico oggetto) e chiunque possegga quelle chiavi può eseguire determinate operazioni (aprirla, chiuderla, avviarla, aprire il cassetto del cruscotto); ovviamente alla macchina non è necessario "sapere" se sono io che la sto avviando: è sufficiente che io possegga la chiave: allo stesso modo non importa chi sta usando una capability.

Esistono diverse variazioni delle chiavi dell'auto: ad esempio la chiave per il parcheggio (apre, chiude e avvia l'auto, ma non il cassetto del cruscotto), o quella che permette di aprirla, chiuderla, ma non avviarla; esattamente allo stesso modo, due capabilities possono identificare lo stesso oggetto (come l'auto), ma autorizzare diversi insiemi di azioni. Un programma potrebbe possedere una capability di sola lettura per un file, mentre un altro una di lettura/scrittura (per quello stesso file).

Come nel caso delle chiavi, si può fornire una capability per l'accesso a un "contenitore" pieno di altre capabilities.

Le capabilities possono essere delegate: se dai a qualcuno le chiavi della tua macchina fai affidamento sul fatto che egli non le dia a qualcun altro; se non ti fidi di lui, semplicemente non dovresti prestargli le chiavi...

Le capabilities possono essere duplicate: se mi dai le chiavi della tua macchina, niente mi impedisce di andare dal mio concessionario e farmi fare una copia; nella vita pratica questo non è un problema perché non si danno le chiavi a chi non è affidabile; comunque, in casi estremi, puoi sempre cambiare le serrature dell'auto e rendere inutili tutte le chiavi; lo stesso può essere fatto con le capability, e viene definito severing su un oggetto; l'effetto è quello di rescindere tutte le capabilities: una volta rescissa, una capability non fornisce più alcuna autorità.

In effetti esistono poche differenza tra le capabilities e le normali chiavi; le più importanti sono:

in conclusione: le capabilities sono un concetto semplice e familiare, che usiamo quotidianamente e raramente ci sorprende; se pensiamo alle normali chiavi e ai tipi di controllo dell'accesso che forniscono non ci sbaglieremo di molto.

Per essere realmente utili le capabilities devono essere non duplicabili: se si potesse far apparire dal nulla una chiave per aprire una macchina qualsiasi, esse non garantirebbero molta protezione. La contraffazione di una capability può essere impedita a livello hardware o software; quest'ultimo è certamente più conveniente perché può funzionare su un normale PC; EROS, che è attualmente il più veloce capability system esistente, utilizza questo metodo, e i dati raccolti suggeriscono che non ci sarebbe nessun reale beneficio nel fare diversamente.

Capability-Based Computer Systems

In un sistema operativo basato su capabilities, l'accesso a qualsiasi oggetto viene effettuato attraverso di esse, ed esse diventano l'unico mezzo per accedere agli oggetti. In un tale sistema, ogni programma possiede un determinato insieme di capabilities. Se, ad es., il programma A ha la capability di comunicare con il programma B, i due programmi possono (inizialmente) fornirsi l'un l'altro altre capabilities.
In molti capability systems, un programma può possedere potenzialmente infinite capabilities: per questo motivo tali sistemi sono spesso stati lenti; una soluzione migliore consiste nel permettere un numero limitato (e piccolo, come 16, o 32) di capabilities; e fornire poi un metodo alternativo per memorizzarne altre se necessario.

Utilizzare un gran numero di capabilities è generalmente inutile: l'obbiettivo è fare in modo che i programmi possiedano un insieme di capabilities il più piccolo e specifico possibile, cosicché non possano abusare di autorità che non hanno (questo è conosciuto come "principio del minor privilegio possibile", principle of least privilege)

In un sistema di questo tipo, un programma che vuole eseguire un'operazione su un oggetto deve possedere una capability per quell'oggetto; per eseguire una certa azione invoca quindi la capability (specificando l'azione da eseguire); sotto UNIX, ad es., la chiamata di sistema read(fd, buf, sz) può essere considerata come l'esecuzione dell'operazione read sul file individuato da fd (la capability), passando i parametri buf e sz; tralasciando le informazioni sulla posizione corrente all'interno del file, un file descriptor UNIX è essenzialmente una capability.

la memorizzazione delle capabilities

Il problema principale delle capabilities è trovare un modo per salvarle su disco; questa è anche una delle ragioni per cui esistono così pochi sistemi basati su capabilities e per cui molti di essi hanno problemi quando si giunge al livello del filesystem.

Supponiamo che un programma abbia una capability che gli permette di creare un file e scriverci; supponiamo ancora che lo faccia, e che infine siano state scritte tutte le informazioni di cui avevamo bisogno; a questo punto, fortuitamente, manca la corrente. Quando facciamo riapartire il sistema ci ritroviamo in ua situazione del tipo "..l'uovo o la gallina..?":

Questi problemi hanno messo in difficoltà gli sviluppatori di capability systems fino ai primi anni '70.

soluzione tradizionale: ACL (Access Control List)

La soluzione più comune è stata di far conferire al filesystem (o a un sottosistema dedicato) il diritto di accesso allo stesso filesystem e di usare un sistema basato sull'identità dell'utente per determinare quali file possono essere usati da un certo programma.
Ad es., un programma che gira per conto [on behalf] di un certo utente può aprire tutti i file che quell'utente ha creato; un sistema di questo tipo è chiamato ACL (access control list):
ad ogni oggetto è collegata una lista di utenti e una lista di azioni che quell'utente può eseguire sull'oggetto. Se l'utente è presente sull'ACL, i suoi programmi possono ottenere una capability per l'oggetto e quindi accedervi; la chiamata di sistema open sotto UNIX ha all'incirca questo scopo.

Riconsideriamo un attimo i quattro requisiti che un meccanismo di controllo dell'accesso dovrebbe avere: i sistemi ACL possono impedire o revocare l'accesso, ma non permettono né di limitarlo né di fornirlo: tutti i programmi di un determinato utente hanno diritto indistintamente a tutti i suoi oggetti; negli attuali ACL non c'è nessun modo di creare dei sottoinsiemi a questi diritti. Inoltre non c'è un modo, per un utente, di delegare ad altri una parte della sua autorità (ad es. l'accesso a un singolo file di cui non è owner), a meno che non sia il proprietario dell'oggetto in questione; solo in questo caso può modificare l'ACL.

una soluzione migliore: universal persistence

Una soluzione migliore è quella di non avere un normale filesystem a cui fornire l'accesso per default; i filesystems sono molto utili, ma la maggior parte dei programmi non ne ha bisogno: un programma per il controllo ortografico che gira come componente di un word-processor, ad es., necessita dell'accesso al particolare file su cui si sta lavorando, ma non ad altri (e quindi non ha bisogno di accedere al filesystem).

Se non è possibile per un programma salvare il suo stato su un file, bisogna trovare un altro modo per farlo: una soluzione è quella di salvare tutto ciò su cui sta lavorando il computer, ad es., ogni 5 minuti; se manca la corrente, il sistema ritorna semplicemente all'ultima copia salvata; poiché questa include tutti i programmi che stavano girando, quando il sistema riparte non è necessario specificare a chi appartengono e quali autorità hanno.

Si potrebbe pensare che questo sia un approccio poco efficiente, ma in pratica esso è più veloce dei comuni filesystem e richiede meno codice.

Ovviamente l'utente potrebbe essersi allontanato fisicamente dalla macchina, e al restart è necessario quindi un modo per riportarlo al suo ambiente di lavoro; la soluzione è quella di assegnare ad ogni utente un programma che costituisca sempre la sua interfaccia (ad es. un sistama a finestre, come XWindow); il compito dell'agente di login è soltanto quello di riconnettere l'utente alla sua interfaccia, dove tutti i suoi programmi staranno ancora girando.

Il metodo dell'universal persistence è usata sia da KeyKOS che EROS, e funziona molto bene..

Le limitazioni delle ACL

I sistemi tradizionali di controllo dell'accesso hanno difficoltà ad affrontare diversi problemi importanti; nel seguito considereremo l'impatto di alcuni di essi sui sistemi operativi odierni e vedremo invece perché non esistono nel caso dei capability systems.

programmi privilegiati

Consideriamo il programma che gestisce le password di accesso al sistema: esso ha bisogno dell'accesso in lettura e scrittura al file (o database) delle password, ma non deve fornirlo all'utente che lo utilizza: in un sistema ACL l'unica soluzione è quella di limitare l'accesso al file ad un utente speciale (root o sysadmin) e specificare in qualche modo che il programma sta girando "come"2quell'utente. Ciò ha condotto a soluzioni come il "setuid bit" (che permette ad un programma di girare sia come l'owner [solitamente root] che come uno specifico utente) o la tabella dei privilegi di sistema (in VMS: elenca i programmi con speciali privilegi in una unica tabella di sistema).

Entrambi questi meccanismi sono insicuri: dare al programma che gestisce le password la stessa autorità della root è evidentemente eccessivo. Nel caso del sistema VMS possono essere attaccati (e ad es. sostituiti) proprio i particolari programmi elencati nella tabella, creando falle nella sicurezza difficili da individuare: quando uno di quei programmi verrà lanciato nuovamente, potrà fare qualsiasi cosa.

Inoltre i programmi di gestione della sicurezza sono generalmente difficili da modificare e la loro manutenzione, quando non se ne comprendono a fondo i vincoli, crea spesso nuovi bug.

La soluzione corretta è quella di dare esplicitamente al programma l'accesso al file delle password e non lasciarlo in giro nel filesystem dove può essere sovrascritto: in un capability system basta dare al programma una capability per l'accesso al database delle password e lasciarlo girare per sempre. Alle applicazioni si fornisce quindi una capability che permette di lanciare il programma delle password ma non di leggerlo o modificarlo.

restrizione dell'accesso

Supponiamo di avere un programma che gestisce i nostri dati finanziari: ovviamente non vogliamo che esso li invii al ministero del tesoro senza il nostro permesso, ma come possiamo esserne sicuri se il computer è collegato ad internet?
In un sistema ACL, il programma eredita la possibilità di accesso alla rete dall'utente stesso; in un sistema basato su capability è invece sufficiente non fornire al programma il diritto di accesso alla rete al momento della sua installazione, impedendogli in questo modo di inviare informazioni riservate.

Java risolve questo problema con restrizioni ad-hoc: in questo modo però si ottengono solo nuovi e differenti problemi di sicurezza: i meccanismi ad-hoc non sempre funzionano: è meglio affrontare la questione dal principio; le capabilities poggiano su un modello matematico formale che può essere (ed è stato) usato per dimostrarne la sicurezza.

collaborazione

La parte più difficile del problema è comunque quella che riguarda la collaborazione: supponiamo che io abbia un software segreto di grande valore economico, e che non voglia venderne l'eseguibile per evitare qualsiasi possibilità di reverse-engineering; qualcun altro invece ha dei dati segreti riguardanti, ad es., i risultati dei test di un nuovo farmaco, ed ha bisogno del mio programma, ma non vuole assolutamente mostrarmi i dati.

In un capability system si può creare un ambiente in cui è possibile eseguire il programma senza poterne vedere il codice e fare in modo, allo stesso tempo, che sia l'utente a controllare l'autorità data al programma: in questo modo egli può essere sicuro che il mio programma non rivelerà i suoi segreti, ma non avendo accesso (in lettura) al codice dell'eseguibile, non potrà rubare il programma. La maggior parte degli esperti di sicurezza crede che ciò sia impossibile... e in un sistema ACL lo è veramente!

Revoca selettiva dell'accesso

Un problema caratteristico delle capability è che non è possibile eliminare in un solo colpo tutte le possibilità di accesso di un utente ad un certo oggetto [tranne che effettuando il severing sull'oggetto, ndt3]; se ad es. il mio cane4 decide di lasciarmi per un altro padrone, come posso togliergli tutte le capabilities per la sua ciotola del cibo? oppure, più realisticamente, se un impiegato si licenzia, come si può essere sicuri che non abbia più accesso agli archivi? e se viene semplicemente spostato ad un altro dipartimento, come si può evitare che abbia accesso a documenti sensibili di quello in cui si trovava prima?

Sembrano esserci tre possibilità:

  1. Rimuovere completamente l'accesso per quell'utente

  2. Revocare l'accesso dell'utente senza eliminarlo, mantenendo possibilmente l'accesso ad alcuni dei suoi file

  3. Revocare l'accesso dell'utente ad un oggetto particolare

All'effetto pratico, gli ultimi due casi coincidono.

Il primo problema, sia nei sistemi ACL che in quelli basati su capability, è risolto cancellando l'utente; per entrambi i sistemi, il secondo problema può essere risolto creando una nuova login per quello stesso utente, e copiando nel nuovo account solo gli oggetti ai quali deve conservare l'accesso: in genere questa è la soluzione migliore; nei capability systems esiste però un'alternativa: costruire intorno agli oggetti uno speciale "compartimento" software che impedisce di copiarli al di fuori di esso, e permette di accedere solo a quelli al suo interno. Questa non è la stessa cosa del creare uno user group: in questo caso, se l'utente ha fatto delle copie dei documenti, la sua cancellazione dallo user group potrà soltanto impedire ulteriori accessi agli originali, ma non potrà nulla contro il loro furto quando l'utente vi ha legittimo accesso.

La revoca a un oggetto particolare presenta gli stessi problemi del compartimento software: entrambi funzionano solo se vengono messi in atto fin dall'inizio; in caso contrario è impossibile impedire all'utente di scappar via con i dati...

Allora perché la cattiva reputazione?

Se le capabilities sono tanto migliori delle ACL, perché non sono state usate? perché tante società vendono sistemi operativi insicuri se sappiamo come risolvere il problema?

Il problema è in parte di carattere storico: i primi capability systems erano implementati a livello hardware, quando ancora le conoscenze sull'architettura di un calcolatore erano limitate, e utilizzavano le capabilities per l'accesso alla memoria fisica; questo li rendeva terribilmente lenti e complessi. La loro reputazione non è ancora migliorata, pur essendo in realtà molto meno complessi degli attuali sistemi operativi.

Nel 1970 comparve un sistema operativo chiamato MULTICS e la maggior parte del mondo seguì la scia di UNIX;
UNIX è un sistema straordinario, progettato però in un'epoca in cui pochi computer erano collegati al mondo esterno: in un mondo in cui si conoscono tutti gli utenti, la collaborazione è più importante della sicurezza; e infatti il meccanismo di sicurezza di UNIX era adatto ad un ambiente collaborativo.
Fino all'avvento di internet e della sua connettività diffusa, solo poche macchine, dedicate a particolari applicazioni finanziarie, avevano la necessità di considerare seriamente i problemi relativi alla sicurezza, e in questi casi essa era da implementare comunque a livello di applicazione.

Negli anni '80 è stato fatto molto lavoro decisamente mediocre sui microkernel (che sono simili ai moderni capability systems), e un'analisi ugualmente superficiale dei risultati concluse che il problema riguardava i microkernel in generale, piuttosto che le mancanze delle particolari implementazioni.
Esistono attualmente esempi di diversi microkernel, e almeno un capability system, che sono significativamente più veloci dei sistemi operativi tradizionali.

La questione di fondo è che durante gli ultimi 25 anni sono stati effettuati grossi investimenti in sistemi insicuri, e fin quando non ci sarà una ragione impellente e evidente per sostituirli, chi li sostiene farà finta di niente...
E infatti i produttori di software pongono invariabilmente delle clausole che escludono ogni propria responsabilità per errori nel software, persino quando sono reali, dimostrabili e incontrovertibili.
Fin quando ciò non cambierà, non ci sarà nessun motivo di progettare sistemi sicuri.

Uno degli argomenti citati a sfavore dei capability systems è che essi possano essere resi formalmente equivalenti agli ACL (a patto, ovviamente di fare sufficienti modifiche a questi ultimi): ciò in realtà può trarre in inganno, facendo pensare che i due siano la stessa cosa. Ma due cose possono essere uguali in teoria senza esserlo in pratica: si possono immaginare diversi modi per migliorare i sistemi ACL in modo da gestire una collaborazione tra processi mutualmente sospettosi (mutually untrusted), ma le soluzioni sono allo stesso tempo draconiane e troppo lente per essere effettivamente utilizzabili.

Le cose stanno lentamente cambiando: Java è un piccolo passo nella direzione giusta, e i suoi utenti stanno cominciando a rendersi conto di quanto siano esposti. Col passare del tempo ci si potrà aspettare che alcuni dei principi di Java siano incorporati dai comuni sistemi operativi.

Al momento sono già stati creati emulatori UNIX che girano come applicazioni sicure su capability systems e che permettono quindi di riutilizzare i programmi esistenti (è da notare che non è invece possibile costruire un emulatore di capability system per un sistema ACL).

Conclusioni

A questo punto dovrebbe essere chiaro cosa sono le capabilities e si dovrebbe poter partecipare alle discussioni sulle diverse
mailing list su EROS ad esse dedicate.

note

  1. KeyKOS, il sistema operativo da cui EROS deriva, è stato classificato come B2 dal Department of Defense Trusted Computer System Evaluation Criteria, DOD 5200.28 STD
  2. cioè: come se fosse stato lanciato da quell'utente.
  3. in uno scambio di mail Shapiro ha successivamente confermato la mia nota:
      > 1) on paragraph 5, "Revoking Access Selectively", you say:
      >
      > One problem with capabilities is that there is no way to say "Remove all
      of Fred's access to this object"
      >
      > you mean: apart from severing that object, right?
    
      Yes. The text should read "*selectively* remove. It's easy to get rid of
      everybody's access. The problem is to get rid of Fred's access without
      killing all other access.
      
  4. l'originale di Shapiro è ricco di esempi che si riferiscono ai suoi due cani (Sheena e Natasha) che ho reso generalmente in uno stile più "impersonale";