Vai al contenuto
Sistemi Operativi

Virtualizzazione, contenitori e sicurezza

Protezione hardware, teorema di Popek e Goldberg, ipervisori di tipo 1 e 2, costo delle tabelle annidate dimostrato, namespace e cgroup, principi di sicurezza.

Un centro di calcolo moderno non esegue un sistema operativo per macchina: ne esegue decine, ciascuno in una macchina virtuale, oppure migliaia di applicazioni isolate in contenitori. La virtualizzazione ha reso possibile il cloud, e si fonda sugli stessi meccanismi di protezione che separano i processi. In questa lezione vediamo la protezione hardware, il teorema di Popek e Goldberg sulla virtualizzabilità con la sua giustificazione, l’ipervisore e il supporto hardware con il calcolo del costo della doppia traduzione degli indirizzi, i contenitori e alcuni principi di sicurezza.

La protezione

L’isolamento tra i processi poggia su tre meccanismi hardware, controllati dal kernel:

  • la modalità privilegiata (lezione sui processi): solo il kernel esegue le istruzioni che configurano il sistema;
  • la traduzione degli indirizzi: ogni processo vede solo le pagine che la sua tabella mappa, con permessi di lettura, scrittura ed esecuzione per pagina (lezione sulla memoria);
  • le interruzioni del timer: il kernel riprende il controllo periodicamente, anche da un processo che non chiama mai il sistema.

Quando una macchina si può virtualizzare

Un ipervisore (o monitor di macchine virtuali) esegue più sistemi operativi ospiti, facendo credere a ciascuno di avere la macchina per sé. Il metodo naturale è intercettare ed emulare (trap-and-emulate): il kernel ospite gira in modalità utente; quando esegue un’istruzione privilegiata, il processore genera un’eccezione, l’ipervisore la intercetta ed emula l’effetto sulla macchina virtuale.

Popek e Goldberg (1974) hanno distinto le istruzioni sensibili, che leggono o modificano lo stato di configurazione della macchina (modalità, tabelle delle pagine, interruzioni), e quelle privilegiate, che in modalità utente provocano un’eccezione.

Teorema (Popek e Goldberg). Si può costruire un ipervisore efficiente con il metodo intercetta ed emula se l’insieme delle istruzioni sensibili è contenuto in quello delle istruzioni privilegiate.

Giustificazione. Se ogni istruzione sensibile è privilegiata, ogni tentativo dell’ospite di toccare la configurazione provoca un’eccezione e arriva all’ipervisore, che lo emula sullo stato virtuale: l’ipervisore mantiene il controllo. Tutte le altre istruzioni non toccano la configurazione e si possono eseguire direttamente sul processore, alla velocità piena. Se invece un’istruzione sensibile non fosse privilegiata, l’ospite in modalità utente la eseguirebbe senza eccezione, con un effetto diverso da quello atteso in modalità kernel, e l’ipervisore non se ne accorgerebbe. ∎

L’architettura x86 originale violava la condizione: alcune istruzioni, come quella che ripristina i flag del processore, si comportavano in modo diverso in modalità utente senza generare eccezioni. Le prime soluzioni furono la traduzione binaria (VMware riscriveva al volo il codice del kernel ospite) e la paravirtualizzazione (Xen modificava il kernel ospite perché chiamasse l’ipervisore). Dal 2005-2006 i processori hanno estensioni apposite (Intel VT-x, AMD-V) con una nuova modalità per l’ipervisore, che rendono la macchina virtualizzabile secondo il teorema.

Ipervisori e memoria

Un ipervisore di tipo 1 gira direttamente sull’hardware (ESXi, Xen, Hyper-V, KVM integrato nel kernel Linux); uno di tipo 2 gira come applicazione su un sistema operativo ospitante (VirtualBox).

Macchine virtuali e contenitori
A sinistra: sull’hardware HW gira l’ipervisore HV, che ospita tre macchine virtuali, ciascuna con il proprio sistema operativo OS e le proprie applicazioni A. A destra: sull’hardware gira un solo sistema operativo, con un motore di contenitori CE che isola tre applicazioni, le quali condividono lo stesso kernel.

La memoria richiede una doppia traduzione: l’ospite traduce gli indirizzi virtuali in indirizzi “fisici dell’ospite”, e l’ipervisore traduce questi negli indirizzi fisici veri. I processori moderni fanno entrambe in hardware con le tabelle delle pagine annidate (EPT, NPT).

Teorema. Con tabelle a n livelli nell’ospite e m livelli nell’ipervisore, un errore del TLB può richiedere fino a (n + 1)(m + 1) - 1 accessi alla memoria.

Dimostrazione. La traduzione dell’ospite legge n voci di tabella, e poi c’è l’indirizzo finale del dato: in tutto n + 1 indirizzi fisici dell’ospite, ciascuno da tradurre con la tabella dell’ipervisore, che richiede m letture. Si aggiungono le n letture delle voci dell’ospite stesse (l’accesso finale al dato non si conta, perché c’è anche senza virtualizzazione). Totale m(n + 1) + n = (n + 1)(m + 1) - 1. ∎

Esempio. Con quattro livelli in entrambi, 5\cdot 5 - 1 = 24 accessi invece di 4: per questo il TLB e le cache delle voci di tabella sono ancora più importanti nelle macchine virtuali, e conviene usare pagine grandi.

I contenitori

Un contenitore non virtualizza l’hardware ma il sistema operativo: le applicazioni condividono un solo kernel, che dà a ciascun gruppo di processi una vista separata delle risorse con gli spazi dei nomi (namespace: processi, rete, file system, utenti) e ne limita il consumo con i gruppi di controllo (cgroup: CPU, memoria, ingresso e uscita). Un contenitore parte in frazioni di secondo e occupa solo la memoria dell’applicazione; un’immagine (Docker, Podman) impacchetta l’applicazione con tutte le sue librerie e gira uguale ovunque. Il prezzo è un isolamento più debole: un difetto nel kernel condiviso espone tutti i contenitori, mentre una macchina virtuale ha il proprio kernel. Per questo nei servizi cloud pubblici i contenitori di clienti diversi girano spesso dentro macchine virtuali leggere.

Principi di sicurezza

  • Privilegio minimo: ogni programma ha solo i permessi che gli servono; un servizio di rete non gira come amministratore.
  • Difesa in profondità: più barriere indipendenti, perché ognuna può avere difetti.
  • Protezioni contro lo sfruttamento degli errori di memoria: pagine di dati non eseguibili (bit NX), disposizione casuale dello spazio di indirizzamento (ASLR), che rende imprevedibili gli indirizzi a chi cerca di dirottare il programma, valori sentinella sullo stack.
  • Aggiornamenti: la maggior parte degli attacchi riusciti sfrutta vulnerabilità già note e corrette, ma non installate.
Subscribe
Notify of
guest
0 Comments
Oldest
Newest Most Voted
0
Would love your thoughts, please comment.x
()
x