Elaborato Cane` Stephanie N46000454

Transcript

Elaborato Cane` Stephanie N46000454
Scuola Politecnica e delle Scienze di Base
Corso di Laurea in Ingegneria Informatica
Elaborato finale in Sistemi Operativi
Meccanismi di access control in SELinux
Anno Accademico 2012/2013
Candidato:
Stephanie Cané
matr. N46000454
A tutti coloro che lottano per
qualcosa in cui credono.
Indice
Indice .................................................................................................................................................. III
Introduzione ......................................................................................................................................... 4
Capitolo 1: Introduzione a SELinux .................................................................................................... 6
1.1 Cenni sui meccanismi di access control ..................................................................................... 6
1.2 La storia di SELinux ................................................................................................................... 8
1.3 Architettura di SELinux ............................................................................................................. 9
1.3.1 Cenni sull’architettura del kernel ........................................................................................ 9
1.3.2 Cenni sul SELinux Policy Language ................................................................................. 13
Capitolo 2: Type Enforcement ........................................................................................................... 15
2.1 Tipi, Attributi e Alias ............................................................................................................... 15
2.2 Access Vector Rules ................................................................................................................. 18
2.3 Type Rules ................................................................................................................................ 22
Capitolo 3: Role-Based access control e Multilevel Security ............................................................ 25
3.1 Role-Based access control ........................................................................................................ 25
3.2 Multilevel security .................................................................................................................... 26
Conclusioni ........................................................................................................................................ 29
Bibliografia ........................................................................................................................................ 30
Introduzione
“Hello netlanders,
Due to a project I'm working on (in minix), I'm interested in the posix standard definition.
Could somebody please point me to a (preferably) machine-readable format of the latest
posix rules? Ftp-sites would be nice.”
Tutto ebbe inizio da questo messaggio che Linus Benedict Torvalds inserì su usenet il 3
luglio 1991: il progetto a cui si riferisce Torvalds è Linux, noto sistema operativo UNIXlike open-source. Il più famoso progetto GNU eredita il proprio modello di sicurezza da
UNIX, sistema operativo multiutente per il quale la sicurezza assume un ruolo cruciale.
Un sistema contiene molti oggetti che necessitano di protezione, sia hardware (CPU,
segmenti di memoria) che software (processi, file). L’access control, come definito da
Sandhu [2], permette di prevenire violazioni di sicurezza sul sistema, limitando ciò che un
soggetto può fare sugli oggetti del sistema.
È importante distinguere, come specificato da Sandhu [2], le politiche dai meccanismi di
access control: le politiche rappresentano delle linee guida di alto livello che specificano
cosa deve essere protetto e da chi, mentre i meccanismi sono funzioni hardware di basso
livello che implementano le politiche. In generale non esiste una politica migliore delle
altre, ma esistono politiche che assicurano una maggior protezione del sistema; ciò
nonostante non è possibile fare un confronto da questo punto di vista in quanto ogni
4
politica deve essere contestualizzata in base all’uso per cui il sistema è stato adibito.
Esistono infatti sistemi che richiedono flessibilità e in tal caso una politica di access
control troppo rigida risulterebbe inappropriata.
Esistono tre categorie di politiche di access control: discretionary access control,
mandatory access control e role-based access control. La discretionary access control
policy è la principale politica di access control, la quale permette ad un soggetto di poter
dire chi può o non può accedere a determinate risorse. Questo tipo di politica è flessibile,
tuttavia non impone alcun vincolo all’utente abilitato di concedere gli stessi permessi a
terzi. Bisogna infatti sempre tener presente che non è l’utente in sé e per sé ad accedere ad
un oggetto, ma sono i programmi che vi accedono a nome dell’utente, e non tutti i
programmi sono benevoli. La sicurezza standard in Linux è di tipo discretionary. Nella
politica mandatory access control, invece, la decisione su chi può o non può accedere ad
una determinata risorsa non viene più presa da un soggetto in quanto l’obiettivo principale
in questo tipo di politica è la riservatezza delle informazioni. Esistono diversi meccanismi
di mandatory access control tra i quali si citano TOMOYO Linux, lanciata nel marzo 2003
e sponsorizzata dalla NTT DATA Corporation nipponica, AppArmor e SELinux. Nel
corso del tempo a queste due politiche sono state proposte diverse alternative, le quali
hanno cercato di coniugare i punti di forza di entrambe. In particolare si parla di una
politica role-based access control basata sul ruolo che gli utenti hanno in un sistema.
Dunque le autorizzazioni per l’accesso ad un determinato oggetto non vengono specificate
per ogni utente ma per ogni ruolo, che può essere visto come un insieme di azioni e di
responsabilità, come definito da Sandhu [2].
Lo scopo del presente elaborato è quello di trattare uno dei meccanismi di access control
citati in precedenza: SELinux. Nel primo capitolo verrà introdotto SELinux fornendo
cenni sulle sue implementazioni e sul contesto storico e infine analizzandone architettura e
linguaggio. Nel secondo capitolo verrà analizzata
in dettaglio la principale
implementazione di SELinux, ovvero type enforcement (di tipo mandatory) mentre nel
terzo capitolo si tratterà di ulteriori implementazioni (role-based e multilevel security) che
basandosi sul type enforcement lo vincolano maggiormente.
5
Capitolo 1: Introduzione a SELinux
SELinux (Security Enhanced Linux) è una tecnologia per la sicurezza del sistema
operativo che rappresenta un’implementazione di mandatory access control nel kernel
Linux. L’obiettivo dei ricercatori di SELinux è stato quello di creare un complemento al
sistema operativo che ne garantisse la sicurezza sia per quanto concerne riduzione delle
vulnerabilità presenti in esso, sia per la realizzazione di quegli obiettivi di sicurezza quali
la riservatezza, l’integrità e la robustezza dei dati.
1.1 Cenni sui meccanismi di access control
Il meccanismo MAC più comune è il Multilevel Security (MLS), nel quale ad ogni
soggetto e ad ogni oggetto del sistema viene assegnato un livello di sicurezza (ad esempio
pubblico e segreto) che ne indica la sensibilità delle informazioni, ovvero i potenziali
danni che scaturirebbero da una divulgazione non autorizzata delle stesse. In figura tratta
da [1] è illustrato il modello di sicurezza MLS, per il quale valgono le seguenti: un
soggetto può scrivere e leggere su e da un oggetto dello stesso livello; un soggetto con
livello più alto di un oggetto può leggere su quell’oggetto ma non può scrivere su di esso;
un soggetto con livello più basso di un oggetto può scrivere su quell’oggetto ma non può
leggere da esso.
6
A titolo esemplificativo, si supponga di avere due server Apache come riportato da Walsh
[4], di cui il primo ha un livello di sicurezza di tipo secret mentre il secondo è di tipo top
secret. Se il processo Apache di tipo secret viene violato, l’hacker è in grado di leggere il
contenuto del server di livello secret, ma non può leggere quello di livello top secret
proprio grazie a MLS. Se invece viene attaccato il processo Apache di tipo top secret,
l’hacker può leggere sia il contenuto del server di tipo top secret che quello di tipo secret.
Concludendo, MLS è stato implementato con l’obiettivo di evitare la disseminazione di
informazioni dai livelli alti ai livelli più bassi, salvaguardando in tal modo la riservatezza
dei dati. Tuttavia, proprio per questo motivo, presenta una rigidità non necessaria in tutti i
sistemi.
SELinux adotta un meccanismo MAC più flessibile che prende il nome di Type
Enforcement (TE), il quale permette di soddisfare anche altri obiettivi di sicurezza oltre la
riservatezza, permettendo alla società che ne fa uso di poter definire una politica di
sicurezza adeguata ai propri bisogni. In particolare ad ogni oggetto ed ad ogni soggetto è
assegnato un security context costituito da tre elementi: user, role e type identifier.
Affinché ad un soggetto venga concesso l’accesso ad un oggetto, è necessario che il type
identifier del soggetto sia autorizzato ad accedere al type identifier dell’oggetto,
indipendentemente dall’identità del soggetto. Quello che essenzialmente differenzia MLS
dall’approccio adottato da SELinux, è che mentre il primo è predefinito, il secondo di
default
non
concede
l’accesso
a
nessuno:
saranno
le
politiche
specificate
dall’organizzazione a decretare l’accesso o meno a determinate risorse, permettendo in tal
modo una modellazione delle politiche di sicurezza perfettamente adeguata alle esigenze
del sistema. L’insieme di regole che gestiscono gli accessi si trovano in un file speciale
7
detto SELinux policy, il quale verrà caricato nel kernel durante il processo di boot.
SELinux implementa anche una politica di tipo Role-Based basata su TE: in tal caso i ruoli
sono usati per raggruppare i tipi identificativi dei soggetti. Si vuole precisare però che la
politica RBAC non garantisce l’accesso, ma di fatto vincola maggiormente la politica TE.
1.2 La storia di SELinux
SELinux trae le sue origini da ricerche effettuate negli anni ’80 sulla sicurezza dei sistemi
operativi e sui microkernel. Agli inizi degli anni ’90 i ricercatori dell’NSA (National
Security Agency) e della SCC (Secure Computing Corporation) lavorarono insieme al
sistema operativo DTMach (Distribute Trusted Mach), il quale combinava i risultati
raggiunti dal progetto LOCK (Logical Co-processing Kernel) e dal progetto Trusted
Mach, come scritto da Ivashko [5]. Il progetto LOCK nacque per sviluppare un sistema di
sicurezza affidabile fornendo una sicurezza multilivello, mentre il progetto Trusted Mach
incorporò i controlli di sicurezza nei sistemi operativi a microkernel [1]. Successivamente
DTMach divenne parte del progetto Distributed Trusted Operating System (DTOS) la cui
architettura, grazie agli sforzi congiunti dell’NSA, SCC e dell’università dello Utah, fu
integrata nel sistema operativo Fluke dando vita ad un nuovo progetto chiamato Flux, il
quale a sua volta ha portato allo sviluppo dell’architettura Flask [5]. Nell’estate del 1999
l’NSA cominciò ad implementare l’architettura di sicurezza Flask nel kernel di Linux e nel
dicembre del 2000 rilasciò pubblicamente il suo lavoro sotto il nome di SELinux,
acronimo di Security Enhanced Linux, il quale venne originariamente fornito come una
serie di patch per il kernel 2.2.x. Durante il Linux Kernel Summit del 2001 ad Ottawa, in
Canada, i rappresentanti dell’NSA proposero di integrare SELinux nel kernel 2.5. Tuttavia
la proposta venne respinta da Linus Torvalds e da altri sviluppatori i quali non ritennero
SELinux la migliore soluzione possibile [5]. Dunque fu iniziato il progetto Linux Security
Modules (LSM), il quale rappresentò un sottosistema del kernel Linux atto
all’integrazione di moduli di sicurezza in quest’ultimo. Dopo circa 3 anni il sottosistema
LSM è stato incluso dal kernel Linux versione 2.6 in poi. I moduli di sicurezza attualmente
8
supportati sono TOMOYO Linux, AppArmor, SMACK e SELinux. Tuttavia solo grazie
all’interesse di Red Hat, SELinux è diventato una parte della distribuzione convenzionale
di Fedora Core Linux: in questo modo se ne è provata l’efficacia anche in ambito
aziendale. Agli inizi del 2005, Red Hat ha rilasciato l’Enterprise Linux version 4 avente
SELinux abilitato di default [1].
1.3 Architettura di SELinux
Come visto nel paragrafo precedente, SELinux è integrato nel kernel grazie al modulo
LSM. In questo paragrafo si analizzerà l’architettura del kernel e il policy language di
SELinux.
1.3.1 Cenni sull’architettura del Kernel
SELinux rappresenta un’aggiunta al sistema di controllo degli accessi standard di Linux,
di conseguenza è consultato solo se è stato validato l’accesso ad una determinata risorsa
dalla politica DAC. Si veda a tal proposito la seguente figura tratta da [1].
9
Il modulo LSM, nel quale è implementato SELinux, è collegato al kernel grazie ad un
insieme di hooks i quali agiscono solo se è stata superata la verifica di accesso da parte
della politica discretionary: in altre parole la politica di controllo di SELinux non
sostituisce quella DAC, ma ne rappresenta un complemento al fine di ottenere un controllo
più efficiente. Un aspetto da sottolineare è quello riguardante la fase di auditing di
SELinux: questa fase rappresenta un’analisi a posteriori delle richieste di accesso e
fornisce il supporto necessario per analizzare il comportamento di un utente nel sistema.
Tuttavia se un soggetto non supera la verifica DAC, SELinux non agirà perché non sarà
necessario, e di conseguenza l’accesso negato non verrà riportato in quando l’auditing non
sarà proprio intervenuto.
Ci si soffermi ora sul modulo LSM in figura tratta da [1]. Le tre componenti fondamentali
di quest’ultimo sono: Security Server, Kernel Object Managers e Access Vector Cache
(AVC).
La politica di access control è implementata come una serie di regole nel blocco Security
Server, il quale viene caricato dall’utente grazie alla Policy Management Interface. In
questo modo è possibile cambiare la logica di controllo senza modificare il resto
10
dell’architettura. Gli object managers, ovvero gli hooks dell’LSM, sono responsabili di far
rispettare le decisioni prese dalla politica specificata nel Security Server, concedendo o
negando l’accesso alle risorse del kernel. Infine si ha l’Access Vector Cache il quale
immagazzina le decisioni precedentemente valutate dal Security Server in modo da
velocizzare analoghe richieste future. Inoltre l’AVC rappresenta un’interfaccia tra il
Security Server e i kernel object managers.
Una delle caratteristiche peculiari di SELinux è che la sua architettura può essere applicata
sia alle risorse nello userspace che a quelle nel kernel space. È importante sottolineare
questo aspetto a causa delle origini di SELinux dalle ricerche sui microkernel, nei quali la
gestione delle risorse era governata da userspace server. Esistono due modi differenti per
fornire un supporto agli userspace object managers: l’utilizzo del kernel security server e
l’istituzione di un policy server. Per quanto concerne il primo metodo si faccia riferimento
alla seguente figura tratta da [1].
11
In questo caso lo userspace object manager si comporta similmente al kernel object
managers, richiedendo l’accesso ad una determinata risorsa direttamente al security
server. Esistono però due differenze fondamentali tra l’object manager dello userspace e
quello del kernel space: la prima è che ogni userspace object manager deve avere un
proprio AVC, le cui funzionalità sono implementate nella libreria libselinux; la
seconda è che lo userspace object manager non possiede gli hooks, che sono un concetto
strettamente legato al kernel.
Il secondo metodo per supportare lo userspace object manager è quello di aggiungere un
policy server nello userspace [1]. Esso è costituito da due server: userspace security server
(USSS) e policy management server (PMS). Il PMS crea gli object classes che
rappresentano le risorse e controlla che la politica adottata sia rispettata su quest'ultime.
L’USSS è utilizzato dallo userspace object manager per richiedere l’accesso ad una
determinata risorsa.
12
1.3.2 Cenni sul SELinux Policy Language
Come si è visto nel sottoparagrafo precedente la politica di access control non è statica ma
è definita da un utente (o più utenti) il quale, attraverso la Policy Management Interface,
carica le specifiche nel Security Server del modulo LSM. Per creare un policy file è
necessario compilare, tramite un programma detto checkpolicy, un policy source file
(tipicamente chiamato policy.conf). Checkpolicy analizzerà sintassi e semantica del file
sorgente e se tutto è scritto correttamente compilerà il file dando luogo ad un binary policy
file leggibile dal loader del kernel. In figura tratta da [1] è presente la schematizzazione di
un tipico policy source file.
La prima sezione riguarda la definizione delle classi del Security Server e dei permessi
specificati su ogni classe. La seconda sezione è quella in generale più ampia e contiene
tutte le dichiarazioni di tipo, le regole TE, i ruoli e gli utenti usati nella politica. La
successiva sezione contiene invece i vincoli, i quali limitano ulteriormente la politica TE.
Si noti che, ad esempio, il MLS è definito come una serie di vincoli. L’ultima sezione
contiene invece le specifiche per la catalogazione di tutti gli oggetti perché, come è stato
13
già detto in precedenza, tutti gli oggetti devono avere un proprio security context.
Tipicamente SELinux è implementato monoliticamente, ovvero checkpolicy dà luogo ad
un unico file binario che sarà caricato nel kernel. Per rendere la politica modulare, è
possibile utilizzare due metodologie differenti: sources modules e loadable modules. Nel
primo caso si creano file sorgenti modulari i quali vengono collegati tra di loro tramite
shell scripts, macro e makefile, come mostrato nella seguente figura complessiva tratta da
[1].
Nel secondo caso invece, utilizzando una recente estensione di checkpolicy e un
compilatore di moduli detto checkmodule, si costruiscono moduli caricabili nel kernel
l’uno indipendentemente dall’altro. In altre parole si crea un modulo base monolitico e
successivamente si creano dei loadable modules che andranno ad ampliare il modulo base
secondo le esigenze.
14
Capitolo 2: Type Enforcement
Type Enforcement è il meccanismo MAC adottato da SELinux. Esso è costituito da un
insieme di regole che determinano quali soggetti possono accedere a determinate risorse.
Esistono due tipologie di regole: Access Vector (AV), che garantisce l’accesso ad una
risorsa o l’auditing, e le type rules che invece riguardano il labeling. Il Type Enforcement
opera sui type identifier associati a ciascun soggetto e a ciascun oggetto del sistema. Si
vuole sottolineare che gli eventuali accessi vengono concessi ai programmi e non agli
utenti. Gli esempi e le figure che seguiranno in questo capitolo sono tratti da [1].
2.1 Tipi, Attributi e Alias
I tipi rappresentano la chiave di volta di tutto il meccanismo type enforcement: grazie ad
essi è possibile specificare le regole che permettono l’accesso alle risorse. Per semplificare
l’uso di questi elementi fondamentali del sistema, si fa ricorso ad attributi e alias: gli
attributi permettono di riferirsi a più tipi grazie ad un unico nome (l’attributo appunto),
mentre un alias ne è semplicemente un sinonimo.
Prima di utilizzare un tipo è necessario dichiararlo. La dichiarazione può essere fatta
anteponendo al nome del tipo la parola type. La sintassi per la dichiarazione di un tipo è
la seguente:
type type_name [ alias alias_set ] [, attribute_set];
nella quale type_name può avere lunghezza arbitraria e contenere caratteri ASCII,
numeri, un trattino basso o un punto. Gli alias_set devono soddisfare le stesse regole
15
sintattiche richieste per il nome del tipo, tuttavia è possibile specificare più di un alias
utilizzando le parentesi graffe in questo modo: alias {aliasa_t aliasb_t}. Anche
gli attribute_set, come gli alias_set, possono essere molteplici: in tal caso per
indicarli nella dichiarazione del tipo basta elencarli separandoli con una virgola.
L’uso degli attributi rappresenta un notevole aiuto nella gestione dei tipi: infatti non
bisogna dimenticare che ogni soggetto e ogni oggetto presente nel sistema ha un proprio
tipo identificativo e dunque potremmo avere centinaia di migliaia di tipi. A scopo
esemplificativo si supponga di voler concedere ad un programma di tipo backup_t, la
possibilità di effettuare appunto il backup dei file di un sistema. Secondo la politica TE per
ogni singolo file (ognuno con un proprio type identifier) si dovrebbe specificare la regola
che ne permette l’accesso al programma di tipo backup_t. Data la quantità di file in un
sistema ciò si tradurrebbe in centinaia di regole allow. Per sopperire a questo problema si
ricorre all’uso degli attributi, i quali permettono di raggruppare diversi tipi. In questo
modo infatti si potrebbe semplicemente definire un attributo file_type e scrivere una
sola regola che garantisca l’accesso del programma di tipo backup_t a tutti gli oggetti del
sistema aventi attributo file_type. La sintassi per la dichiarazione di un attributo è la
seguente:
attribute attribute_name;
dove attribute_name gode delle stesse specifiche sintattiche del type_name visto in
precedenza. Si noti però che gli attributi e i tipi si trovano nello stesso namespace: di
conseguenza non possono avere lo stesso nome. Inoltre è consuetudine scrivere alla fine
del nome del tipo “_t” mentre non si usa posporre nulla al nome dell’attributo proprio per
permettere di distinguerli a prima vista.
Per associare un attributo ad un tipo si usa la seguente sintassi:
type type_name, attribute_name;
Bisogna tuttavia prestare attenzione nell’associare degli attributi ad un tipo, onde evitare
accessi non desiderati. A questo proposito si consideri il seguente esempio:
type httpd_t;
type httpd_user_content_t;
16
type backup_t;
type shadow_t;
attribute file_type;
attribute httpdcontent;
type httpd_user_content_t, file_type, httpdcontent;
type shadow_t, file_type;
allow backup_t file_type : file read;
allow httpd_t httpdcontent : file read;
Le prime quattro righe rappresentano dichiarazioni di tipo, nello specifico con httpd_t si
vuole indicare il tipo identificativo di un web server, con httpd_user_content_t il
tipo identificativo di dati utente presenti sul web server, con backup_t il tipo di un
programma che permette di effettuare il backup dei dati di un sistema e con shadow_t il
tipo del file shadow di Linux (particolare file contenente tutte le password del sistema). La
quinta e la sesta riga sono dichiarazioni di attributi. La settima e l’ottava riga
rappresentano l’associazione degli attributi ai tipi, in particolare si associa al tipo
identificativo dell’utente che può accedere al web server gli attributi file_type e
httpdcontent, mentre si associa al tipo shadow_t l’attributo file_type. Le ultime
due righe rappresentano infine le vere e proprie regole di accesso che permettono
rispettivamente la lettura di oggetti aventi attributo file_type da parte del tipo
backup_t, e la lettura di oggetti aventi attributo httpdcontent da parte del tipo
httpd_t.
In questo esempio l’utilizzo di molteplici attributi si rivela di fondamentale importanza: se
così non fosse e si avesse il solo attributo file_type associato sia al tipo shadow_t che
al tipo httpd_user_content_t, quando si specifica la regola allow httpd_t
file_type : file read non si permetterebbe solo la lettura al tipo httpd_t (che si
ricorda rappresenta il web server nel presente esempio) di tutti i file aventi attributo
file_type (e dunque ai dati utente presenti sul web server), ma si consentirebbe in
questo modo anche l’accesso al file shadow, cosa ovviamente indesiderata.
È possibile associare degli attributi ad un tipo in maniera non contestuale alla
17
dichiarazione del tipo, utilizzando la seguente sintassi:
typeattribute type_name attrib_names;
In tal modo, in riferimento all’esempio visto in precedenza, le seguenti righe:
type httpd_user_content_t;
typeattribute httpd_user_content_t file_type, httpdcontent;
sono equivalenti alla formula:
type httpd_user_content_t, file_type, httpdcontent;
Ciò permette di migliorare la flessibilità e la modularità del linguaggio.
Gli aliases invece, a differenza degli attributi, rappresentano dei sinonimi per i tipi. La
sintassi per definire un alias associato ad un tipo è la seguente:
typealias type_name alias alias_name;
dove type_name è il nome del tipo a cui si vuole associare un alias_name, il quale
deve soddisfare le stesse regole sintattiche di cui gode type_name (le quali sono state
specificate precedentemente). È possibile associare ad un tipo più di un alias_name: in
tal caso i vari alias verranno elencati all’interno di parentesi graffe.
2.2 Access Vector Rules
Le regole Access Vector consentono di effettuare quattro tipi di operazioni che riguardano
sia la concessione (o la negazione) vera e propria di permessi, sia la possibilità o meno di
effettuare auditing. In particolare la regola allow permette di definire quale soggetto può
accedere ad un determinato oggetto con i permessi specificati nella regola (come si vedrà
in seguito). Si ricorda che in SELinux non è concesso alcun accesso di default, di
conseguenza ogni accesso può avvenire solo se esiste una regola allow che lo permette.
Come si è visto in precedenza, l’auditing è molto utile all’amministratore di sistema per
consentirgli di revisionare quali sono stati gli accessi negati ed eventualmente constatare
possibili bug o tentativi di intrusione nel sistema. Di default vengono registrati tutti gli
accessi negati, tuttavia può capitare che per alcuni di essi non sia necessaria l’operazione
di auditing. La regola dontaudit è quella che appunto specifica quali accessi negati non
18
devono essere registrati. Si ricordi però che SELinux opera dopo che è stato superato il
controllo di sicurezza standard di Linux, dunque se quest’ultimo nega l’accesso SELinux
non interverrà proprio e di conseguenza l’accesso negato non verrà registrato. La regola
auditallow invece specifica quali accessi concessi devono essere registrati: ciò può
essere utile per monitorare accessi a risorse di particolare importanza, come ad esempio la
richiesta di scrittura sul file shadow. Infine vi è la regola neverallow che specifica quale
accesso tra due entità non deve essere mai accordato: essenzialmente quest’ultima regola
rappresenta un aiuto in più per colui che scrive la politica al fine di negare permessi
indesiderati. Queste regole hanno tutte la stessa sintassi:
rule source_type(s) target_type(s) : object_class(es) permission(s)
Ad esempio, la seguente regola:
allow user_t bin_t : file execute;
garantisce che al soggetto di tipo user_t venga accordata l’esecuzione del file di tipo
bin_t. Source type, target type e object class costituiscono una key. Quando un processo
vuole accedere ad una risorsa, come si è visto nella sezione relativa all’architettura di
SELinux, ne fa richiesta al modulo LSM sulla base di questa key. Nel caso in cui due
regole AV hanno la stessa key, l’effetto delle due è cumulativo. Ad esempio, si
considerino le seguenti due regole allow:
allow user_t bin_t : file execute;
allow user_t bin_t : file read;
in tal caso verrà concessa sia l’esecuzione che la lettura. Si noti che nonostante esista una
regola che conceda l’accesso, non esiste alcuna regola che rimuova l’accesso garantito da
una regola precedente.
È possibile utilizzare gli attributi nelle regole AV per evitare di scrivere troppe righe di
permessi; si consideri il seguente esempio:
allow user_t bin_t : file execute;
allow user_t local_bin_t : file execute;
allow user_t sbin_t : file execute;
allow staff_t bin_t : file execute;
19
allow staff_t local_bin_t : file execute;
allow staff_t sbin_t : file execute;
se si definisse l’attributo domain per i tipi user_t e staff_t e l’attributo exec_type
per i tipi bin_t, local_bin_t e sbin_t, potremmo scrivere la seguente regola al posto
delle sei viste nell’esempio sopracitato:
allow domain exec_type : file execute;
È possibile inoltre specificare più attributi e più tipi sia per il source type che per il target
type, oltre a poter effettuare una loro combinazione, ad esempio:
allow {user_t domain} {bin_t file_type sbin_t} : file execute;
Esistono due tipi speciali: il tipo self e il tipo negazione. Il tipo self si usa nel campo target
per riferirsi allo stesso tipo specificato nel campo source, ad esempio le seguenti regole
sono equivalenti tra loro:
allow user_t user_t : process signal;
allow user_t self : process signal;
Allo stesso modo le seguenti due regole:
allow user_t user_t : process signal;
allow staff_t staff_t : process signal;
sono equivalenti alla regola:
allow {user_t staff_t} self : process signal;
L’operatore di negazione è utile invece quando si ha a che fare con una regola con attributi
e si vuole che questa effettui una determinata azione di tipo AV su tutti gli attributi tranne
che ad un tipo specificato. Ad esempio:
allow domain { exec_type –sbin_t} : file execute;
in questo caso si vuole che tutti i tipi con attributo domain eseguano tutti i tipi con
attributo exec_type tranne sbin_t (che pure ha attributo exec_type). Per specificare
la negazione basta anteporre un trattino alto al nome del tipo che non si vuole includere
nella lista. L’ordine di attributo e tipo negato è ininfluente, ovvero è possibile scrivere
anche:
{ -sbin_t exec_type }
20
Nella sintassi delle regole AV è possibile specificare anche più object classes e
permissions. Si considerino a questo proposito le seguenti regole:
allow user_t bin_t : file { read getattr };
allow user_t bin_t : dir { read getattr };
Queste sono equivalenti alla seguente:
allow user_t bin_t : { file dir } { read getattr };
Tuttavia bisogna prestare attenzione in questo caso alla validità dei permessi su
determinate classi di oggetti; ad esempio, si consideri:
allow user_t bin_t : { file dir } { read getattr search };
in questo caso la regola è illegale in quanto il permesso search non può essere applicato
ad un oggetto di tipo file, ma solo ad oggetti di tipo dir (è infatti possibile cercare in una
directory ma non in un file). In questo caso l’unico espediente che è possibile utilizzare è
quello di scrivere le due regole separatamente in questo modo:
allow user_t bin_t : file { read getattr };
allow user_t bin_t : dir { read getattr search };
Infine esistono due operatori speciali comuni alle regole AV: l’operatore asterisco (*) e la
tilde (~). L’asterisco si utilizza nel campo dei permessi per specificare che alle classi di
oggetti definite nella regola devono essere applicati tutti i permessi. Un esempio di utilizzo
è il seguente:
allow user_t bin_t : { file dir } *;
L’operatore tilde invece si utilizza per specificare che bisogna applicare tutti i permessi a
quella classe di oggetti specificata nella regola tranne quelli indicati dopo la tilde tra
parentesi graffe. Di seguito un esempio:
allow user_t bin_t : file ~{ write setattr ioctl };
21
2.3 Type Rules
Le type rules hanno una sintassi simile alle regole AV, tuttavia differiscono
semanticamente da queste ultime: infatti mentre le regole AV riguardano permessi e
auditing, le type rules operano nel campo del labeling. Esistono due tipi di type rules: type
transition rules (default domain transition e default object transition) e type change rules.
La sintassi per entrambe è la seguente:
rule source_type(s) target_type(s) object class(es) default_type
Le type transition rules nascono dall’esigenza di risolvere il problema della transizione di
dominio. Per spiegare questo problema si farà riferimento al programma passwd di Linux,
ovvero il programma di gestione delle password che deve operare sul file /etc/shadow.
Nella sicurezza standard di Linux è necessario che il programma passwd abbia i privilegi
di root per poter effettuare queste operazioni.
Tuttavia molti programmi possono avere i permessi di root e dunque si rende necessaria
una maggior protezione, ovviata da SELinux. Come si può vedere dalla figura precedente
il programma passwd può operare sul file shadow solo se gode dei privilegi di root (come
in Linux standard) e contemporaneamente esiste una regola allow che lo permette. Sono
dunque necessarie entrambe le condizioni. Il problema della transizione di dominio lo si
può rappresentare con la seguente figura:
22
Ci si chiede come un utente possa cambiare la propria password dato che l’unico
programma che può effettuare operazioni sul file shadow è passwd. Ovviamente non si
vuole che un qualsiasi programma di tipo user_t possa accedere a questo file così
importante, per questo si effettua questo tipo di restrizione. Si consideri ora la seguente
figura:
Quando un utente effettua il login, viene creata una shell (il programma bash in figura) la
quale opera effettuando una fork(), ovvero creando una copia di se stessa con gli stessi
attributi di sicurezza. Successivamente grazie alla regola:
allow user_t passwd_exec_t : file { getattr execute };
si permette alla copia della shell (avente tipo user_t) di poter eseguire il programma
passwd (avente tipo passwd_exec_t). La successiva regola, ovvero:
allow passwd_t passwd_exec_t : file entrypoint;
23
concede al tipo passwd_t di poter avere un entrypoint sul file eseguibile di tipo
passwd_exec_t. Infine l’ultima regola:
allow user_t passwd_t : process transition;
permette l’effettiva transizione dal dominio user_t a quello passwd_t. Si noti che,
affinché sia possibile la transizione di dominio, è necessaria la presenza di tutte e tre le
regole sopraelencate. Il problema sta nel fatto che l’utente solitamente non richiede in
maniera esplicita una transizione di dominio, ma si aspetta che, una volta eseguito il
programma passwd, gli sia possibile cambiare password. La regola type transition è fatta in
modo tale che quando un processo di tipo user_t esegue un file di tipo passwd_exec_t,
allora il tipo del processo dovrà essere cambiato in passwd_t:
type_transition user_t passwd_exec_t : process passwd_t
Si noti che questa regola non permette la transizione di dominio ma definisce
esclusivamente un nuovo tipo di default. Questo tipo di type transition di cui si è appena
trattato è un esempio di default domain transition. I default object transition invece sono
del tipo:
type_transition passwd_t tmp_t : file passwd_tmp_t
cioè quando un processo di tipo passwd_t crea un file in una cartella di tipo tmp_t, il tipo
che deve essere associato al nuovo file, previa buona riuscita delle tre regole che
permettono la transizione di dominio di cui sopra, deve avere il tipo passwd_tmp_t.
Un esempio di regola type change è invece la seguente:
type_change sysadm_t tty_device_t : chr_file sysadm_tty_device_t;
con questa si vuole indicare che quando un processo di tipo sysadm_t vuole rinominare un
file del tipo tty_device_t, il file rinominato dovrà avere tipo sysadm_tty_device_t.
24
Capitolo 3: Role-Based access control e Multilevel Security
Type enforcement è il meccanismo di access control principale in SELinux tuttavia, al fine
di vincolarlo ulteriormente, si utilizzano il meccanismo role-based e il multilevel security,
entrambi costruiti su type enforcement. In questo capitolo si vuole dare una breve
panoramica delle funzionalità di questi ulteriori meccanismi.
3.1 Role-Based access control
Come si è visto nel primo capitolo, ad ogni soggetto e ad ogni oggetto è assegnato un
security contest, costituito da user, role e type identifier. Nella trattazione sul type
enforcement, la pietra miliare nella gestione degli accessi è stata il type identifier: infatti in
SELinux gli accessi non sono concessi direttamente a ruoli e utenti. Questi ultimi due
rappresentano gli elementi di base dell’approccio role-based. Si vuole ulteriomente
sottolineare che questo approccio non concede gli accessi ma rende il type enforcement
ancora più restrittivo. Si consideri nuovamente l’esempio tratto da [1] sul problema del
domain transition: tramite una user declaration statement del tipo user joe roles {
user_r } si associa un ruolo user_r all’utente joe; tramite due role declaration
statements si associa il tipo user_t al ruolo user_r (role user_r types user_t) e
il tipo passwd_t al ruolo user_r (role user_r types passwd_t). Senza queste
dichiarazioni l’accesso, anche nel caso in cui le condizioni siano verificate per consentirlo,
sarà negato. Di seguito la figura tratta da [1] che mostra l’esempio sopracitato:
25
In SELinux non esistono ruoli preesistenti nel sistema, tranne per il ruolo object_r che è
il ruolo per tutti gli oggetti. La sintassi per il role declaration statement è la seguente:
role role_name [types type_set];
dove role_name è il nome del ruolo che può essere di lunghezza arbitraria, contenere
caratteri ASCII, numeri, punti e trattini bassi; type_set può contenere un insieme di tipi
o attributi che verranno associati al ruolo che si sta dichiarando. Si noti che la
dichiarazione di un ruolo può avvenire anche senza associazione ad alcun tipo.
La sintassi per lo user declaration statement è invece la seguente:
user user_name roles role_set;
dove user_name gode delle stesse regole sintattiche che valgono per role_name, mentre
role_set identifica l’insieme di ruoli da poter associare a quell’utente che si sta
dichiarando.
3.2 Multilevel security
Multilevel security (MLS) è una forma di mandatory access control implementata su type
enforcement. Essa consente di restringere ulteriormente TE implementando un tipo di
access control adatto a contesti in cui è richiesto il massimo grado di sicurezza. MLS è
un’estensione opzionale in SELinux; quando questo è abilitato, il security context viene
26
ampliato includendo altri due campi: un security level basso e un security level alto.
Questo security context non è necessariamente fisso, nel senso che è obbligatoria la
presenza di almeno un security level, ma possono essercene anche due. Un security level è
costituito da due campi: sensitivity e un insieme di categories. La sensitivity è un insieme
gerarchico costituito da “gradi” come Top Secret, Secret e Unclassified, mentre le
categories non sono ordinate. La relazione tra i security level non è gerarchica ma di tipo
dominance: la differenza tra queste due relazioni sta nel fatto che mentre nella prima un
livello può essere più basso, più alto o uguale ad un altro, nella relazione di tipo
dominance esiste anche lo stato noncomparable. Il motivo per cui viene utilizzata questa
relazione di tipo dominance al posto di quella gerarchica è dovuta alla presenza delle
categories che non godono di una relazione ordinata. Dunque il confronto tra security
level avviene grazie ai seguenti operatori: dom (dominates), domby (dominated by), eq
(equal) e incomp (noncomparable). Ad esempio si supponga che un soggetto con security
level SL1 voglia leggere un oggetto con security level SL2: affinché ciò avvenga è
necessario che si abbia SL1 dom SL2, ovvero che SL1 domini SL2. Se questo soggetto
vuole scrivere sull’oggetto, si dovrà avere invece SL1 domby SL2, ovvero che SL1 sia
dominato da SL2. La modalità di lettura e scrittura concomitante si ha invece solo se SL1
eq SL2. Per quanto riguarda la sintassi si ha che la dichiarazione sensitivity è la
seguente:
sensitivity identifier [ alias alias_id [alias_id(s)] ];
come si può vedere è possibile definire anche degli alias: questo può tornare utile
soprattutto perché per convenzione si usa indicare in maniera generica le varie
sensitivities. Ad esempio:
sensitivity s0 alias low;
Un’altra dichiarazione che è possibile fare è la dominance, la quale serve a stabilire un
ordine gerarchico tra le varie sensitivities. La sintassi per questa dichiarazione è la
seguente:
dominance { identifier indentifier … identifier }
Un esempio applicativo è il seguente:
27
dominance { s0 s1 s2 s3}
dove al primo identifier specificato sarà assegnato nella gerarchia il livello più basso
mentre al secondo un livello superiore rispetto al precedente e così via.
La dichiarazione category invece ha la seguente sintassi:
category identifier [alias alias_id [alias_id(s)]];
dato che le categories non sono ordinate, non sarà necessario stabilire alcun tipo di
relazione tra di loro. Un esempio è il seguente:
category c0 alias blue;
Infine si ha la dichiarazione level che consente di definire il security level che, come si è
detto precedentemente, è costituito da un solo campo sensitivity e un insieme di
categories. La sintassi è la seguente:
level sensitivity [:category_set];
28
Conclusioni
SELinux offre una serie di vantaggi che lo rendono adatto sia a sistemi che richiedono un
alto livello di sicurezza (implementando ad esempio anche un’approccio di tipo MLS per
soddisfare richieste di sicurezza più stringenti) sia a sistemi che non hanno particolari
esigenze. Come specificato nella Fedora Documentation [3], SELinux non è un software
antivirus, né può essere inteso come un firewall o come un sistema di sicurezza completo:
esso infatti può contribuire solo a migliorare soluzioni di sicurezza in un sistema operativo
ma non ne rappresenta un’alternativa. Pertanto è importante continuare a seguire un
comportamento adeguato anche in presenza di un sistema su cui opera SELinux,
aggiornando il software, utilizzando password difficili da identificare e adottando
componenti di difesa come firewall e quant’altro. Come affermava il Maestro di karate
Gichin Funakoshi “La disattenzione è causa di disgrazia”, dunque soprattutto in un
contesto di sicurezza non bisogna mai sottovalutare il pericolo e incorrere nella pigrizia,
ma bisogna sempre essere preparati a possibili bug o intrusioni nel sistema.
29
Bibliografia
[1]
Mayer, F., MacMillan, K., Caplan, D. (2006). SELinux by Example: Using Security
Enhanced Linux. Prentice Hall.
[2]
Sandhu, R., Samarati, P. (1994). Access Control: Principles and Practice. IEEE
Communications Magazine.
[3]
Fedora
Documentation,
in
http://docs.fedoraproject.org/en-
US/Fedora/13/html/Security-Enhanced_Linux/chap-Security-Enhanced_LinuxIntroduction.html#sect-Security-Enhanced_Linux-IntroductionBenefits_of_running_SELinux (consultato il 19 febbraio 2014)
[4]
Daniel J. Walsh, Your visual how-to guide for SELinux policy enforcement, in
http://opensource.com/business/13/11/selinux-policy-guide
(Consultato
il
20
gennaio 2014)
[5]
Evgeny Ivashko, Secure Linux: Part 1. SELinux – history of its development,
architecture
and
operating
principles,
in
http://www.ibm.com/developerworks/linux/library/l-secure-linux-ru/l-secure-linuxru-pdf.pdf (Consultato il 27 gennaio 2014)
30