Sei sulla pagina 1di 52

Karrie Moore

Sicurezza in Android
Anno 2015
3

I NDICE

1 U N MODELLO SEMPLIFICATO DI A NDROID PAGINA 1


1.1 Architettura generale 1
1.2 Kernel 2
1.3 Virtual machine e Runtime 2
1.3.1 Dalvik . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.3.2 Il garbage collector . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.3.3 ART . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.4 Demoni e librerie native 8
1.5 Servizi e applicazioni 10
1.6 IPC e Binder 10
1.6.1 Forme di IPC a livello applicativo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
1.6.2 Funzionamento del Binder . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12

2 D ETTAGLI RELATIVI ALLA SICUREZZA PAGINA 17


2.1 Sandboxing e permessi 17
2.1.1 Permessi a basso livello . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
2.1.2 Android Paranoid Network . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
2.1.3 Permessi a livello applicativo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
2.1.4 Tipologie di permessi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
2.2 SELinux (SEAndroid) 20
2.2.1 SELinux in Android . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
2.2.2 Middleware Mandatory Access Control . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
2.2.3 SEAndroid e releases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
2.3 JCA 31
2.3.1 Engine . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
2.3.2 Provider . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
2.4 JSSE 33

3 V ULNERABILITÀ E POSSIBILI ATTACCHI PAGINA 35


3.1 L’insicurezza del Binder 35
3.1.1 Comprendere gli oggetti di tipo Parcel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
3.1.2 Attacchi al binder . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
3.2 SELinux e vulnerabilità 38
3.2.1 Android volume daemon (vold) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
3.2.2 Zygote . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
3.2.3 Anonymous Shared Memory (ashmem) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
3.2.4 /proc pseudo filesystem. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
1

1 Un modello semplificato di Android

1.1 Architettura generale

L’architettura di Android si può considerare come uno stack comprendente applicazioni, un sistema opera-
tivo, un run-time environment, del middleware, servizi e librerie. Questa architettura è visibile all’interno
dello schema nella successiva immagine, ogni layer dello stack corrisponde ad un certo insieme di elementi
che uniti insieme eseguono un determinato compito specifico per quel layer.

Figura 1.1: Figura Architettura generale di Android


1.2 Kernel
2

1.2 Kernel
Posizionato nella parte più bassa dello stack, si trova il kernel Linux che permette l’accesso all’hardware,
tramite gli appositi driver ai livelli che gli stanno sopra. Il kernel Android è partito avendo come base
un Linux versione 2.6, tale kernel fornisce multitasking preemptive, servizi di sistema di basso livello che
forniscono accesso a sistemi hardware quali la memoria, i processi e la gestione dell’alimentazione, oltre a
fornire un network stack e driver per le periferiche come il display del dispositivo, Wi-Fi e audio.
Il kernel Linux originale è stato sviluppato nel 1991 da Linus Torvalds ed è stato combinato con una serie
di strumenti, utility e compilatori sviluppati da Richard Stallman alla Free Software Foundation per creare
un sistema operativo completo denominato GNU / Linux. Diverse distribuzioni Linux hanno poi esteso la
versione base, Debian e Red Hat Enterprise Linux.
Il kernel Linux è stato originariamente sviluppato per l’uso su desktop ma ormai si trova su moltissimi
server, anche negli ambienti mission critical enterprise, a prova del fatto che le sue performance possono
essere incredibili.

1.3 Virtual machine e Runtime


Nello schema sopra al kernel è possibile vedere Android Runtime, come detto precedentemente il multita-
sking del kernel Linux permette l’esecuzione di più processi concorrentemente.
Si può facilmente assumere che ogni applicazione faccia girare un processo direttamente nel kernel di Linux;
infatti ogni applicazione che gira su un device Android lo fa tramite una sua istanza della Virtual Machine.
Avviare le applicazioni dentro una macchina virtuale permette un certo numero di vantaggi, ad esempio
le applicazioni sono essenzialmente sandboxed, cioè non possono in alcun modo comunicare direttamente
con il sistema operativo o con le altre applicazioni e non possono nemmeno accedere direttamente all’hard-
ware. Un altro vantaggio in questa astrazione consiste nel fatto che il codice delle applicazioni non dipende
dall’hardware, quindi le librerie di base hanno lo stesso funzionamento su qualsiasi device.
La Dalvik virtual machine è stata sviluppata da Google e si affida su funzionalità base del sistema Linux.
In termini di uso di memoria è molto più efficiente delle virtual machine Java comunemente utilizzate,
inoltre è fatta in modo che le istanze multiple possano avviarsi efficientemente senza rendere troppo lento
un dispositivo mobile.
Per eseguire una Dalvik VM, il codice dell’applicazione deve essere trasformato da una classe Java ad
un eseguibile dalvik (.dex), la maggior parte delle classi Java possono essere convertite in formato Dex,
utilizzando un tool incluso nelle SDK Android (ad esempio $ANDROID_HOME/build-tools/21.1.2/dx).

1.3.1 Dalvik
La maggior parte delle macchine virtuali utilizzano un’architettura stack-based, impilando tutte le istruzioni
all’interno di uno stack. In questo modo è molto più semplice implementare la virtual machine e anche il
compilatore, ma tutto questo ha un costo estremamente grande in termini di performance. Un’altra alternati-
va interessante consiste nell’architettura basata su registri che ricorda un po’ un assembly. Spesso l’esempio
per illustrare le due modalità di funzionamento consiste nel guardare il bytecode risultante delle due virtual
machine, dal seguente snippet di codice Java:
public static int add(int i, int j) {
return i+ j;
}
La compilazione produce infatti il seguente bytecode per la Java virtual machine
1.3 Virtual machine e Runtime
3

public static int add(int, int) ;


Code:
0: iload_0
1: iload_1
2: iadd
3: ireturn

Come si può vedere la virtual machine Java usa due istruzioni per caricare i parametri nello stack e poi ne
esegue la somma e restituisce il risultato.
Il seguente bytecode è quello che gira sulla virtual machine Dalvik:

.method public static add(II)I

add-int v0, p0, p1

return v0
.end method

Come si può vedere è presente una singola istruzione per sommare i parametri nei registri p0 e p1, il risultato
finisce nel registro v0. Dalvik utilizza un numero inferiore di istruzioni, ma il codice risultante ha dimensioni
superiori rispetto al codice di una virtual machine basata su stack.
Nella maggior parte delle architetture il caricamento del codice risulta meno oneroso rispetto al dispatching
delle istruzioni, quindi le VM basate sui registri possono essere interpretate in maniera più efficiente.
Nella maggior parte dei dispositivi le librerie e le applicazioni di sistema non contengono direttamente il
codice DEX indipendente dal dispositivo, ma un codice ottimizzato dipendente dalla piattaforma chiamato
.ODEX, tale processo di ottimizzazione viene eseguito in fase di installazione della applicazione. Tale file
rappresenta la parte ottimizzata di un’applicazione che permette di eseguire un avvio veloce.
Tutto ciò è possibile grazie al caricamento dell’applicazione nella Dalvik Cache, uno spazio temporaneo che
viene utilizzato dalla DVM per memorizzare indirizzi di memoria, struttura delle directory delle applicazioni
installate etc.
Tramite il .odex e alla Dalvik Cache il sistema operativo è in grado di conoscere in anticipo quali applicazioni
avviare in modo da ridurre il tempo di avvio del sistema.
Un file ODEX viene creato grazie ad un software chiamato dex2opt, questo software esegue l’ottimizzazione
tramite una serie di operazioni di snellimento del file .dex: i metodi virtuali vengono sostituiti con opportune
versioni ottimizzate contenenti l’indice della tabella virtuale del metodo chiamato, in modo da evitare la
ricerca nella Virtual Table in fase di esecuzione. Inoltre vengono tolti i metodi vuoti, come ad esempio
quei metodi come Object<init> che vengono utilizzati in fase di allocazione di un oggetto, ecc. Il software
dex2opt ricorre anche all’indicizzazione permettendo di ignorare la risoluzione simbolica iniziale e una
rapida esecuzione

Un’altra ottimizzazione è stata fatta anche dentro il tool dx, infatti durante la conversione di un class a dex
viene anche effettuata una compressione prima di ottenere il file dex. I file contenuti all’interno del file jar
vengono riorganizzati, in generale nel Jar sono presenti un file .class per ogni classe in cui è suddiviso il
codice, questo porta ad una certa ridondanza, poiché il codice di determinati oggetti viene ripetuto.
In un file .DEX, ad esempio Ljava/lang/String è memorizzata una sola volta poiché si permette alle classi
di condividere la Constant Pool (tabella dei simboli per la rappresentazione del class file per ogni classe ed
1.3 Virtual machine e Runtime
4

interfaccia) riducendo al minimo l’utilizzo delle costanti. Questa ripetizione minima comporta l’utilizzo di
numerosi puntatori logici e/o riferimenti.
Un file .DEX è una struttura basata su 8 campi, chiamata Big Header a causa dell’elevato numero di
informazioni in esso contenute, riassunte qui di seguito:

1. header contiene l’header del file .DEX;

2. string ids comprende una lista che contiene gli identificatori di stringhe. Gli identificatori vengono
utilizzati dal file .DEX sia per la denominazione interna (ad esempio descrittori) sia per le costanti.
La lista deve, essere ordinata sulla base del contenuto e non deve contenere identificatori duplicati;

3. type ids: comprende una lista contenente gli identificatori di tutti i tipi di dato (ad esempio array,
classi, tipi primitivi).Come per string ids la lista deve essere ordinata sulla base degli identificatori del
campo string id e non deve contenere voci duplicate.

4. proto ids: lista contenente gli identificatori per tutti i prototipi delle funzioni presenti nel file .DEX.
L’elenco contenuto in questo campo deve essere ordinato sulla base del tipo di ritorno più importante
(indicetype ids). L’elenco non deve contenere voci duplicate.

5. field ids: lista contenente gli identificatori di tutti i campi a cui fa riferimento il .DEX. La lista deve
essere ordinata in modo tale da specificare prima il tipo del campo e poi il nome.

6. method ids: consiste di una lista contenente tutti i metodi a cui fa riferimento il file .DEX e deve
essere ordinato in modo che ci sia prima il tipo del metodo, poi il nome ed infine il prototipo.

7. class defs: lista contenente le definizioni delle classi. In particolare, essa deve essere ordinata in
modo tale che la super classe di una determinata classe e i suoi metodi vengono visualizzati prima
della classe di riferimento;

8. data: l’area dati contiene tutte quelle informazioni di supporto per le liste descritte nei precedenti
punti. È importante sottolineare che a questo livello di categoria i vari oggetti hanno esigenze diffe-
renti di allineamento: per questo motivo vengono talvolta utilizzati dei byte di riempimento in modo
tale da garantire un corretto allineamento.

Dalla versione 2.2 di Android (Froyo) il compilatore è stato modificato in modo da diventare un JIT compi-
lation (Just In Time), una tipologia di compilazione dinamica. In un ambiente JIT la prima fase è costituita
dalla compilazione del bytecode, in cui si trasforma il codice sorgente in bytecode. Successivamente il
codice bytecode viene installato sul sistema di destinazione.
Quando il bytecode viene eseguito, il compilatore dell’ambiente di esecuzione lo traduce in codice macchina
nativo. La traduzione in codice macchina può avvenire per file o per funzione: le funzioni possono essere
compilate solo quando stanno per essere eseguite, da qui il nome just-in-time, ovvero «appena in tempo».
Tutte le operazioni pesanti vengono effettuate in fase di building, da cui un maggior tempo di compilazione:
nella fase successiva alla compilazione del bytecode (durante la trasformazione in codice nativo) tutte quelle
operazioni che richiedono molto tempo per essere eseguite, come l’analisi sintattica e semantica del codice
sorgente e una prima fase di ottimizzazione vengono eseguite e quindi i tempi di esecuzione successivi sono
minori.
In seguito il codice nativo che è stato generato, viene salvato per le successive esecuzioni e quindi con gli
avvii successivi le prestazioni aumentano.
1.3 Virtual machine e Runtime
5

Tra le varie ottimizzazioni messe in atto per migliorare le prestazioni delle macchine virtuali dalvik, una di
queste è detta Zygote. Zygote consiste in un processo demone inizializzato dal sistema operativo in fase di
avvio come system service, la cui funzione è quella di creare istanze di Dalvik. Tale servizio apre anche una
socket /dev/socket/zigote in modo tale da mettersi in ascolto per eventuali richieste di avvio da parte delle
varie applicazioni.
Ogni volta che arriva una richiesta sulla socket di Zygote, questo effettua una fork() per creare una nuova
istanza di Dalvik con il set minimo di risorse necessarie ad una applicazione. Per ottimizzare tale avvio viene
usata una strategia detta Copy On Write (COW): quando lo Zygote effettua una fork, non viene veramente
effettuata una copia della memoria, ma viene condivisa e marcata come copy-on-write.
In questo modo, quando un processo di un’applicazione cerca di accedere e modificare quell’area di memo-
ria, il kernel intercetta la chiamata e ne crea una copia che potrà esserere opportunamente modificata.

1.3.2 Il garbage collector


L’ultima questione di cui vale la pena parlare è la modalità di garbage collection di Dalvik, un garbage
collector è uno strumento automatico per la gestione delle risorse non utilizzate, come i blocchi di memoria
che sono stati allocati e utilizzati ma che non vengono più referenziati da processi attivi. Il garbage collector
permette al programmatore di non doversi occupare della allocazione e deallocazione delle risorse.

All’interno di Dalvik il processo della gestione della memoria da parte del Garbage collector comprende
ben due pause in cui l’esecuzione della applicazione viene sospesa. Nella prima pausa vengono enumerati i
blocchi di memoria che possono essere liberati, nella seconda vengono marcati.

Il garbage collector di Dalvik si basa sull’algoritmo mark-and-sweep, in questo algoritmo viene mantenuta
una lista contenente tutti quei blocchi della memoria nello heap contrassegnati come liberi.
Quando un sistema comincia a scarseggiare di memoria viene chiamato il GC, questo in una prima fase,
detta di enumerazione, enumera tutte le radici, dove per radice si intende il punto di entrata in un grafo di
oggetti referenziati uno all’altro. In seguito avviene la fase di marcatura, in questa fase quando un oggetto è
raggiungibile da qualche radice viene marcato con un flag che indica che tale oggetto non è garbage.
In seguito alla fase di marcatura viene eseguita la fase di sweep in cui tutti gli oggetti in memoria che non
sono marcati vengono indicati al sistema come eliminabili.
Per altri dettagli relativi al mark and sweep si veda [Mark and sweep] e [Garbage collection].
1.3 Virtual machine e Runtime
6

1.3.3 ART
Come visto nella sezione precedente, Dalvik utilizza tecnologie molto semplici e adatte ai sistemi mono
processore, questo è dovuto al fatto che i primi device sui quali veniva usato Android erano molto limitati.
Con ART, Android RunTime, si assiste ad un incremento di performance su tutti i dispositivi. Le differenze
tra ART e Dalvik sono riassunte nel seguente schema:

Figura 1.2: Dalvik versus ART


1.3 Virtual machine e Runtime
7

Le maggiori features implementate da ART sono: La Ahead-of-time (AOT) compilation che consiste nella
compilazione e ottimizzazione del codice durante l’installazione. Durante l’installazione, ART compila le
apps usando un tool chiamato dex2oat. Questa utilitz accetta i files DEX come input e genera una app com-
pilata eseguibile sul device di destinazione. Questa utility può compilare tutti i DEX validi senza difficoltà.
Tuttavia alcuni tools di post/processing, possono produrre dei DEX non del tutto validi, che benché siano
tollerati da Dalvik, non sono compilabili per ART. Un nuovo garbage collector che migliora le performance

applicando diverse modifiche:

• l’enumerazione e la collezione sono effettuate in un unico momento e quindi si ha una sola pausa
invece di due

• durante la pausa del GC viene parallelizzata l’elaborazione

• esistono nuovi tipi di oggetti con vita breve che vengono cancellati facilmente dal GC

• vengono lanciati più garbage collectors in modo concorrente e più spesso, il che rende gli eventi
GC_FOR_ALLOC estremamente rari.

• il GC è stato compattato in modo da usare meno spazio nella memoria e ridurre la frammentazione.

Come anticipato, dex2oat è un tool che viene utilizzato in ART per evitare che un’applicazione ottimizzata
per Dalvik venga nuovamente riscritta: questo tool, infatti, effettua una conversione del bytecode Dalvik
(file .dex) in un file con formato eseguibile .elf (Executable and Linkable Format)
Tutte le informazioni riguardante la struttura interna del file sono contenute nello header, questo contiene
due campi:

1. Program Header Table contiene le informazione relative ai vari segmenti del file .ELF. È opzionale
in fase di linkaggio, ma non in fase di compilazione perché deve permettere a quei programmi che si
occupano della creazione dell’immagine del processo di caricare il file .ELF nel modo corretto

2. Section Header Table contiene le informazioni che descrivono le sezioni del file.

Android RunTime utilizza i file ELF in maniera diversa rispetto alle versioni precedenti non ricorre ai
simboli per trovare ogni singolo entry point.
Infatti un programma Java usa un numero elevato di metodi e assegnare un simbolo ad ognuno di essi è
un’operazione molto onerosa. ART, invece, utilizza una struttura dati che permette di caricare velocemente
i metodi di una determinata classe.
Come accennato in precedenza, la compilazione ahead-of-time sposta la fase di compilazione al momento
della installazione: il codice Java viene compilato in codice binario nativo per la piattaforma hardware
attraverso il tool DEX2OAT.
Questo nuovo tipo di compilazione però occupa spazio ( sui nuovi dispositivi con maggiori dimensioni di
archiviazione dei dispositivi mobili non da problemi) e allunga i tempi di installazione delle applicazioni.
Una caratteristica peculiare del compilatore ahead-of-time utilizzato da ART è la parallelizzazione delle
operazioni di compilazione. In particolare, la fase introduttiva coincide con l’estrazione dei file .dex da
processare.
La prima operazione è chiamata class resolution: durante questa fase il compilatore apprende le informa-
zioni relative al layout delle classi che successivamente vengono caricate insieme ai loro metodi e ai loro
campi.
1.4 Demoni e librerie native
8

La seconda operazione effettuata viene chiamata class verification e va ad effettuare una verifica delle classi
marcandole se essa non ha successo. Per questo compito viene usato un class verifier che ha il compito di
garantire la robustezza di un’applicazione, assicurando che tutte le classi caricate abbiano una struttura
appropriata in modo da evitare crash runtime della Virtual Machine.
La verifica a sua volta si articola in due fasi: internal checks durante questa fase il class verifier verifica
che il class file sia buildato correttamente, consistente, non sia troncato e rispetti i vincoli del linguaggio
Java. In caso contrario quel class file non verrà mai utilizzato in fase di esecuzione; Durante una seconda
fase si ha la verifica dei riferimenti simbolici: viene controllata la correttezza dei riferimenti ai simboli e se
la risoluzione avviene correttamente dynamic linking)

ART ricorre all’uso di un nuovo memory allocator chiamato Rosalloc (Runs of Slots Allocator). La mag-
gior parte dei moderni sistemi operativi utilizza allocator basati su un singolo memory lock globale (un
esempio ne è il «Doug Lea’s Malloc» in glibc) che mette in attesa tutti quei thread che per poter procedere
all’esecuzione devono entrare in possesso di uno specifico oggetto.
Se un thread non puo acquisire immediatamente il lock si mette in attesa che questo diventi libero oppure
abbandona la sezione protetta; in un ambiente mulithread e object-oriented questo lock interferisce con il
garbage collector e le varie operazioni in memoria.
In Rosalloc gli oggetti condivisi vengono allocati senza memory locking mentre quelli di grandi dimensioni
hanno una propria heap e un lock indipendente dagli altri che riduce i conflitti e migliora le operazioni di
parallelismo. In questo modo quando un’applicazione tenta di allocare memoria per un nuovo oggetto non
deve aspettare che il garbage collector liberi una regione di memoria.
Per altri dettagli relativi a Rosalloc [Rosalloc]

1.4 Demoni e librerie native


Le Core Libraries si dividono in tre categorie: Java Interoperability Libraries, derivate dalle librerie standard,
le Android Libreries specifiche dell’ambiente di sviluppo Android e le librerie C o C++.
La maggior parte delle applicazioni sono sviluppate usando il linguaggio di programmazione Java, il Java
development environment standard include una vasta collezione di classi che sono contenute nella libre-
ria core di Java. Queste librerie permettono la gestione semplificata di stringhe, numeri, date ma anche
funzionalità di gestione della rete e manipolazione di file, in pratica una JDK.
In realtà le Java Interoperability Libraries sono una implementazione open souce basata sul progetto Apache
Harmony Project che consiste in un sottoinsieme della libreria core di Java, in seguito poi Harmony è stato
ampiamente modificato.
Una seconda categoria è data dalle librerie Android, queste librerie sono specifiche dello sviluppo su An-
droid. Alcuni esempi comprendono le librerie per il framework delle applicazioni, le librerie per la gestione
delle componenti grafiche o l’accesso alla rete. Le principali, alle quali se ne affiancano altrettante, sono le
seguenti:

android.app Contiene la definizione di applicazione ma anche tutte le classi che ne hanno a che fare come
le Activity o i Fragment.

android.content Facilita l’accesso, la pubblicazione di notifiche e la messaggistica tra diverse componenti


dell’applicazione, contiene ad esempio gli Intent, i content providers e la definizione di async task.

android.database È usata per accedere ai dati pubblicati dai content providers ed include le classi SQLite
per il database management.
1.4 Demoni e librerie native
9

android.graphics È una libreria di grafica 2D a basso livello include colori, punti, filtri, rettangoli e canvas.

android.hardware Presenta una API che permette l’accesso all’hardware tipo l’accelerometro o i sensori
di luminosità.

android.opengl Un’interfaccia Java alle API OpesnGL ES 3D per il rendering grafico.

android.os Permette alle applicazioni di accedere ai servizi standard del sistema operativo inclusi i messa-
ges, system services ed i sistemi di inter-process communication.

android.media Permette alle classi di abilitare il playback per gli audio ed i video.

android.net Consiste in un set di API che permettono l’accesso allo stack network. Includono ad esempio
android.net.wifi, che permette l’accesso allo stack dei device wireless.

android.provider Un insieme di classi di convenienza che permette l’accesso intelligente ai database del
content provider come i dati del calendario o la lista dei contatti.

android.text Usato per renderizzare e manipolare i dati in formato testo sul display

android.util Un insieme di classi di utilità per effettuare operazioni come la conversione tra stringhe e
numeri, gestire XML e manipolare date e ore (da notare che in Java, fino alle JDK per una gestione
comoda delle date bisognava includere joda-time).

android.view Comprende tutte le componenti visuali di base delle interfacce grafiche android, in pratica
le View di base (i vari menù) e tutte le funzionalità offerte da essa, come il layout inflater o i vari
listener di azioni effettuate su essa.

android.widget Consiste di una collezione di interfacce utente più complesse delle view come bottoni,
liste, layout managers, radio buttons etc.

android.webkit Contiene dei tools per le operazioni relative al web-browsing (WebView, WebChrome
client).

Le librerie di Android sono spesso scritte in Java e permettono agli sviluppatori di estenderle ed usarle come
preferiscono all’interno delle loro applicazioni. È importante notare che le librerie core Java in realtà non
contengono molto codice Java, infatti essenzialmente sono dei wrapper di librerie scritte in C++.
Quando viene effettuata una chiamata per esempio alla android.opengl per disegnare una certa componente
in grafica 3d sul display, la libreria effettua una chiamata alla libreria C++ che a sua volta descrive le
operazioni da effettuare al kernel Linux per compiere il lavoro di disegnare effettivamente la linea.
Le librerie C e C++ sono incluse per effettuare tutto un insieme di operazioni anche di grafica 3D o per
chiamare il layer di sicurezza SSL nelle comunicazioni o effettuare operazioni con il database SQLite o
visualizzare un video o ascoltare un file audio.
In pratica, il tipico sviluppatore di applicazioni Android accede a queste funzionalità solo attraverso le API
della libreria di base di Android scritta in Java.
Nel caso in cui sia necessario un accesso diretto a queste librerie lo sviluppatore può utilizzare il Native
Development Kit NDK, il cui scopo è quello di chiamare i metodi nativi dei linguaggi di programmazione
non Java come C e C++ usando la Java Native Interface JNI.
1.5 Servizi e applicazioni
10

1.5 Servizi e applicazioni


Il framework applicativo è un insieme di servizi che insieme formano l’ambiente in cui le applicazioni
Android possono girare ed essere gestite. Questo framework implementa il concetto che le applicazioni
Android sono costruite come riutilizzabili, le componenti sono intercambiabili e rimpiazzabili. Questo
concetto implica che un’applicazione debba esplicare le sue funzioni insieme a tutti i suoi dati, in modo da
poter essere trovata e riutilizzata da altre applicazioni. Il framework applicativo include i seguenti servizi
chiave:
Activity Manager Controlla gli aspetti del lifecycle delle applicazioni e lo stack delle activity.

Content Providers Permette alle applicazioni di pubblicare e condividere dati con le altre applicazioni.

Resource Manager Permette l’accesso alle risorse incluse nell’applicazione come stringhe colori e layouts
(directory /res).

Notifications Manager Permette alle applicazioni di mostrare alerts e notifiche all’utente.

View System Un insieme di views estendibile usato per creare le interfacce utente.

Package Manager Il sistema con cui le applicazioni possono trovare le informazioni relative ad altre
applicazioni installate sul device.

Telephony Manager Fornisce informazioni ad un’applicazione relativamente ai servizi di telefonia dispo-


nibili sul device come lo stato ed il fornitore.

Location Manager Permette l’accesso ai servizi di locazione permettendo all’applicazione di ricevere gli
updates relativi ai cambiamenti di locazione che avvengono.
Le applicazioni si localizzano nella parte superiore dello stack, si dividono in applicazioni di sistema e
applicazioni installate dall’utente. Le applicazioni di sistema sono incluse nello stesso sistema, in un file
system di sola lettura montato sotto /system e come tali non possono essere modificate dagli utenti e possono
ricevere privilegi a livello di sistema operativo. Le applicazioni di sistema possono essere aggiornate dagli
utenti e alcune possono essere sostituite da app installate dall’utente.
Le applicazioni installate dall’utente vengono configurate in una partizione del sistema montata sotto /data
che ospita tutti i dati utente, tali applicazioni possono essere disinstallate e modificate a piacere. Ogni
applicazione risiede in una sandbox dedicata, ad esempio i dati di una particolare applicazione potrebbero
trovarsi in /data/data/nome.del.package/. Le applicazioni possono accedere unicamente alle risorse per cui
hanno una autorizzazione definita nel manifesto dell’applicazione stessa.

1.6 IPC e Binder


Il Binder è una componente per la comunicazione tra processi (IPC) di Android, per sviluppare servizi di
sistema orientati agli oggetti. Crea quindi un ambiente di sistema in cui è possibile usare degli oggetti, il
tutto funzionante su un kernel come quello di Linux.
Inizialmente Binder si chiamava OpenBinder e faceva parte di un sistema chiamato BeOS, dell’omonima
società Be Inc. BeOS era basata su un microkernel proprietario che usava il Binder per poter gestire degli
oggetti a livello di sistema.
Benché nelle prime versioni l’OpenBinder usato fosse proprio quello di BeOS (adattato a girare su Linux),
con gli anni l’intera componente è stata riscritta. Il Binder è orientato alla scalabilità, stabilità, flessibilità e
bassa latenza, il tutto non complicando troppo le cose per i programmatori.
1.6 IPC e Binder
11

L’Inter/Process Communication (IPC) è un framework per lo scambio di dati tra processi multipli, è
utilizzato per lo scambio di messaggi, la sincronizzazione, la condivisione di memoria e per le chiamate
a procedure remote (RPC). L’IPC permette la condivisione di informazioni, una velocità computazionale
maggiore, modularità, convenienza, separazione dei privilegi, isolamento dei dati e stabilità. Infatti ogni
processo ha un suo spazio di memoria legato ad un ID di sistema univoco.
IPC prevede moltissime opzioni di comunicazione: tramite files, segnali, sockets, pipes, semafori, me-
moria condivisa, scambio di messaggi (come code e message bus), intenti, contentProviders e sistemi di
messaggistica e Binder.
Le applicazioni Android ed i servizi di sistema girano su processi separati per ragioni di sicurezza: infatti
ogni processo è dotato di una sandbox ed ha una identità all’interno del sistema, stabilità, perché se un
processo ha un malfunzionamento e crasha, non si vuole che questo abbia effetto sugli altri processi e
gestione della memoria, in caso di poca memoria i processi meno usati vengono terminati per liberare
risorse ad altri processi.

Tuttavia permane la necessità dei vari processi di comunicare e condividere dati, il tutto evitando l’ove-
rhead dell’IPC tradizionale. Anche perché quando si parla di Android si parla di un Linux particolare che
non supporta tutte le funzionalità di quello tradizionale, nella documentazione relativa alla libc di Android
[SysV-IPC] viene spiegato che Android non supporta i System V IPCs classici come i semafori, i segmenti
di memoria condivisi o le code di messaggi.
Secondo gli sviluppatori la maggior parte dei classici sistemi IPC comportano una perdita di risorse consi-
stente a livello kernel, ad esempio non c’è un modo per rilasciare un semaforo SysV allocato nel kernel da
un processo se questo è buggato o malizioso ed esce improvvisamente o se crasha o è killato. Sul kernel di
Android si vuole avere la possibilità di rilasciare ogni risorsa prima che il sistema venga riavviato.

Il Binder, però, è diverso perché prevede un contatore di riferimenti ad oggetti ed un meccanismo di notifica
di terminazione di un riferimento, questo lo rende adatto ad un ambiente in cui i processi possono essere
terminati in caso di poca memoria disponibile.
Ma esistono altre caratteristiche del Binder che lo rendono interessante, a livello di gestione di thread è
presente la gestione automatica di pools di theads, si possono chiamare i metodi sugli oggetti remoti come
se fossero locali, prevede inoltre un modello di invocazione sincrono e asincrono.
È possibile identificare il mittente ad un ricevitore, tramite l’UID o il PID, un riferimento a un oggetto
remoto può essere passato a un altro processo e può essere utilizzato come un token di identificazione,
Possibilità di inviare file descriptor oltre i limiti imposti dal processo.

È presente anche un linguaggio di definizione dell’interfaccia detto Android Interface definition langua-
ge (AIDL) ed un supporto per effettuare marshalling/unmarshalling dei più comuni tipo di oggetto. Le
invocazioni a transazioni verso il modello sono semplificate grazie a dei proxy autogenerati lato Java.
La ricorsione attraverso i progetti ha lo stesso comportamento della ricorsione semantica quando si chiama
un metodo su un oggetto locale. Se il client ed il servizio sono all’interno dello stesso processo, allora si ha
un’esecuzione locale, senza IPC o data marshalling.

A livello applicativo si può dire che la maggior parte delle callback di uso comune come onResume(),
onDestroy, onCreate() delle Activity sono invocate dall’ActivityManagerService che passa per il Binder.
1.6 IPC e Binder
12

1.6.1 Forme di IPC a livello applicativo


Android supporta una semplice forma di IPC tramite gli intenti ed i content providers.
L’intento è un oggetto per la messaggistica che serve per le comunicazioni asincrone tramite le componenti
di Android. Queste componenti possono girare sulla stessa app o su app diverse (quindi diversi processi)
[Intent]. Ad alto livello si possono immaginare come un collegamento point-to-point o un’architettura di
tipo publish-subscribe.
Un intento rappresenta a sua volta un messaggio contenente la descrizione dell’operazione da fare, i dati da
passare ed infine i due end-point.
Gli intenti possono essere di tipo implicito o esplicito, gli intenti espliciti comprendono il nome (contenente
tutto il package) della componente che gli ha avviati, un certo insieme di dati (i cosiddetti Extra, che poi
saranno presi tramite il Bundle) e il nome della componente di destinazione.
Gli intenti impliciti, invece, dichiarano un’azione generale da effettuare, dato un insieme di dati si chiederà
ad un tipo di intento di eseguire un’operazione (tipo condivisione di un file con un insieme di contatti o invio
di un messaggio). Questo tipo di intento permette quindi di suddividere le applicazioni in parti distinte ma
porta anche ad errori che sono rilevabili solo a runtime, perché il tipo di dato passato dalla prima componente
non è quello che si aspetta una seconda componente.

I ContentResolvers sono delle componenti che comunicano in modalità sincrona con i ContentProviders,
generalmente i ContentResolvers girano su applicazioni distinte ed usano un sistema di interrogazione/scrit-
tura di tipo CRUD (Create Read Update Delete), tale comuncazione non è orientata agli oggetti Ogni
componente Android può agire come mittente o come desinatario ma tutte le comunicazioni passano per il
main thread.
Sia gli intenti che i ContentProvider sono delle astrazioni ad alto livello del Binder.

Un Messenger di Android rappresenta un riferimento ad un Handler che può essere inviato ad un processo
remoto tramite un intento. Tale riferimento viene passato tramite il meccanismo IPC. Un messaggio è quindi
in un certo senso come un intento, poiché contiene informazioni su cosa fare e i dati sui quali agire, questi
però vengono usati direttamente come message.what e message.getData().

1.6.2 Funzionamento del Binder


Visto che un processo non può invocare direttamente delle operazioni su altri processi, per via dell’isola-
mento tra processi previsto dal sistema, devono entrambi passare per il kernel e lo fanno tramite un Binder
driver, questo driver è mappato su /dev/binder ed ha una API piuttosto semplice che prevede: open, release,
poll,mmap, flush e ioctl (usato per la maggior parte delle comunicazioni)
Esistono numerosi comandi ioctl: BINDER_WRITE_READ, BINDER_SET_MAX_THREADS,
BINDER_SET_CONTEXT_MGR, BINDER_THREAD_EXIT, BINDER_VERSION.
La chiamata ioctl è spesso usata nella forma ioctl(binderFd, BINDER_WRITE_READ, &bwd), dove bwd
è un binder_write_read, definito in drivers/staging/android/uapi/binder.h come segue:
struct binder_write_read {
size_t write_size; /* bytes to write */
size_t write_consumed; /* bytes consumed by driver */
unsigned long write_buffer;
size_t read_size; /* bytes to read */
size_t read_consumed; /* bytes consumed by driver */
unsigned long read_buffer;
1.6 IPC e Binder
13

};

Il write_buffer, a sua volta, contiene una serie di comandi che devono essere eseguiti dal driver come
incrementare o decrementare il contatore di riferimenti al binder (reference counting), richieste di notifica
di terminazione (death notification). In particolare contiene una enum con il tipo di transazione seguito da
un binder_transaction_data.

struct binder_transaction_data {
/* The first two are only used for bcTRANSACTION and brTRANSACTION,
* identifying the target and contents of the transaction.
*/
union {
size_t handle; /* target descriptor of command transaction */
void *ptr; /* target descriptor of return transaction */
} target;
void *cookie; /* target object cookie */
unsigned int code; /* transaction command */

/* General information about the transaction. */


unsigned int flags;
pid_t sender_pid;
uid_t sender_euid;
size_t data_size; /* number of bytes of data */
size_t offsets_size; /* number of bytes of offsets */

/* If this transaction is inline, the data immediately


* follows here; otherwise, it ends with a pointer to
* the data buffer.
*/
union {
struct {
/* transaction data */
const void *buffer;
/* offsets from buffer to flat_binder_object structs */
const void *offsets;
} ptr;
uint8_t buf[8];
} data;
};

Al ritorno dal comando eseguito si può avere all’interno del read_buffer una serie di risposte relative al
thread in esecuzione.
I clients possono comunicare con i servizi tramite transazioni, che contengono un binder-token, il codice del
metodo da eseguire, un buffer con i dati grezzi e il PID/UID del richiedente.
Molte operazioni a basso livello e strutture dati (come i Parcel) hanno bisogno di usare il Binder, tuttavia
non passano per il protocollo o per la libbinder ma usano invece Proxy e Stubs.
1.6 IPC e Binder
14

Questi sono delle implementazioni dell’interfaccia AIDL, in particolare i Proxy sono un’implementazione
completa di AIDL che permettono un marshall e unmarshall dei dati (trasformando quindi gli oggetti in
strutture piatte chiamati Parcels) e mappano le chiamate ai metodi su transazioni che vengono poi inclusi in
un oggetto Binder (che implementa l’interfaccia IBinder).
Gli stubs sono simili ai proxy solo che prevedono un’implementazione parziale dell’interfaccia AIDL che
fa marshalling e unmarshalling dei dati e mappa le transazioni tramite chiamate verso il binder service (che
comunica direttamente con il Binder driver).
Il manager facilita il discovery dei servizi con i quali si vuole parlare e nasconde l’interazione che viene
effettuata con il binder.
1.6 IPC e Binder
15

Quando ad esempio avviene un’interazione con un location manager, in realtà si sta parlando con un oggetto
locale che nasconde le interazioni più a basso livello, traduce cioè i vari metodi a chiamate verso un proxy,
ma si occupa anche di tutte quelle operazioni scomode da gestire come le eccezioni, il meccanismo di lookup
e gestione della sincronizzazione.
Quando il client interpella un manager, questo deve comunque trovare il servizio giusto tra tanti servizi
disponibili, per fare questo si ha a disposizione un servicemanager.
Per prima cosa il Context Manager si registra con il Binder Driver come gestore di servizi unico, visto che
il Binder Driver permette un solo context manager, questo avviene all’avvio del sistema. Il compito del
context manager consiste nello stare in ascolto di altri servizi che devono registrarsi con un certo nome, che
sarà quello che userà il client quando avrà bisogno di un particolare servizio.

Service Binder Context Service


Client Manager Service
Manager Proxy Driver Manager Manager Proxy

register CM
await reqs
await reqs
await reqs
await reqs
register
get CM service
register svc tx

init manager registered


get service service
get CM
get svc tx

got service
init'd mgr
Process A Kernel servicemanager Process B

Figura 1.3: Figura Utilizzo del Service manager

Tramite un AIDL (Android Interface Definition Language) è possibile definire l’interfaccia di un servizio
che vuole registrarsi sul binder. Per fare questo è sufficiente definire in un file .aidl l’interfaccia del servi-
zio che si vuole usare e tramite l’aidl build tool si riesce a generare una classe che permette di effettuare
transazioni verso il binder [AIDL].
Se si vogliono effettuare transazioni che comprendono oggetti particolari, è necessario che tali classi imple-
mentino «Parcelable».
public class MyParcelable implements Parcelable {
private int mData;

public int describeContents() {


return 0;
}
public void writeToParcel(Parcel out, int flags) {
out.writeInt(mData);
}
public static final Parcelable.Creator<MyParcelable> CREATOR
= new Parcelable.Creator<MyParcelable>() {
public MyParcelable createFromParcel(Parcel in) {
return new MyParcelable(in);
}
1.6 IPC e Binder
16

public MyParcelable[] newArray(int size) {


return new MyParcelable[size];
}
};
private MyParcelable(Parcel in) {
mData = in.readInt();
}
}
Un riferimento ad un oggetto binder può essere gestito in maniera diversa, se il processo che richiede in-
formazioni (client) e quello che le fornisce (service) risiedono all’interno dello stesso processo allora è
sufficiente un indirizzo di memoria virtuale, praticamente un puntatore nella memoria locale del processo.

Se i due processi sono distinti l’operazione è più complessa: ad ogni transazione il binder driver mappa un
indirizzo locale su dei binder handles remoti e viceversa. Per fare questo il driver mantiene dei mapping tra
gli indirizzi locali e handles remoti tra i vari processi, sotto forma di alberi binari, in modo da velocizzare il
processo. I riferimenti ai dati inclusi nelle transazioni sono recuperati basandosi sugli offsets segnalati dal
client all’interno della transazione e riscritti durante la transazione stessa.

Il binder driver non conosce nulla a proposito degli oggetti che vengono condivisi tra i vari processi.
Quando viene trovato un nuovo riferimento ad un oggetto di binder questo viene aggiunto alla struttura dati e
ricordato, ogni volta che un riferimento viene condiviso con un altro processo, un «contatore di riferimento»
viene incrementato. Tale contatore è decrementato esplicitamento o automaticamente (se il processo muore).
Quando un riferimento non è più necessario il processo che lo possiede deve notificare che può essere
rilasciato, a quel punto il binder rimuove il mapping.

È possibile usare il binder in modalità asincrona, usando la speciale keyword «oneway» all’interno della
definizione dell’interfaccia AIDL.
In questo modo il client può passare un riferimento a se stesso, come listener di una callback, in questo
modo i ruoli si invertono e quando il servizio può comunicare un risultato chiamerà il listener che il client
gli ha fornito.
Per altri dettagli relativi al funzionamento del binder: [Gargenta].
17

2.1
2
Sandboxing e permessi
Dettagli relativi alla sicurezza

Il sandboxing di Android si verifica in due momenti distinti: la prima modalità di sandboxing si ha duran-
te l’installazione di un’applicazione, poiché in questo momento viene assegnato ad essa un UID univoco
chiamato anche app ID, questo ID è usato anche successivamente come identificativo del processo.
La seconda modalità invece non è a livello di processo ma a livello di file, ogni applicazione ha una cartella
dedicata ai suoi dati del tipo data/data/esempio.package.

2.1.1 Permessi a basso livello


Tale modalità di isolamento si ha sia sulle applicazioni native, sia quelle presenti sulla virtual machine, infatti
gli UID dei servizi di sistema sono costanti, infatti il sistema operativo non prevede di un file /etc/password,
ma all’interno di un header del sistema stesso (android_filesystem_config.h) In tale header si può vedere
come gli uid tra 1000 e 10000 siano quelli dedicati al sistema con user system, da 10000 in poi possono
venire allocati gli uid per le applicazioni.
All’interno di Android esiste un utente per ogni applicazione, il nome di tale utente è generato a partire
dallo UID, infatti considerando il prefisso «app_» ed aggiungendo poi l’offeset tra il valore dell’AID_APP
(presente nello header) e l’UID si ottiene il nome utente. Ad esempio se lo UID di un’applicazione è 10055,
allora l’utente sarà app_55.
Nelle ultime versioni di Android multiutente il prefisso diventa una u accompagnata dal numero che identi-
fica l’utente, quindi per tornare all’esempio di sopra, in un sistema monoutente, tale app girerà con lo user
u0_a55.
Gli UID delle applicazioni sono contenuti nei metadati del package nel file /data/system/packages.xml, qui
si trova una lista in cui ogni entry comprende tra le altre cose, il package, l’UID ed il flag che segnala se
l’app è debuggable, il path in cui vengono salvati i dati della applicazione, che rispetta il nome standard/
data/data/esempio.nome.package.
In seguito è presente una lista di GID con cui può essere avviata l’app, ogni GID ha una serie di permessi
diversi all’interno dell’applicazione.
Le applicazioni possono essere installate utilizzando uno stesso UID e quindi condividere file ed essere
eseguite nello stesso processo, ovviamente le due applicazioni devono essere firmate con la stessa chiave.
I componenti di basso livello come i servizi nativi fanno affidamento su UID e GID assegnati ad un determi-
nato processo per determinare quali privilegi possono essere assegnanti. Un esempio di risorse che vengono
rilasciate sono il filesystem,i socket locali e quelli di rete.
2.1 Sandboxing e permessi
18

Se nel file /data/system/packages.xml era possibile trovare la lista di applicazioni associate a UID e GID, un
altro file di fondamentale importanza è /etc/permission/platform.xml, in cui sono elencate tutte le associa-
zioni tra permessi UID e GID.
In una prima parte di questo file è possibile trovare il mapping tra tipo di permesso e gruppo, questo significa
che ogni processo applicativo che possiede quel permesso può girare anche con il GID attaccato a quel
processo, quindi può eseguire tutte le operazioni di filesystem (read write and execute) permesse per quel
gruppo. Un esempio potrebbe essere il seguente:
<permission name="android.permission.NET_TUNNELING" >
<group gid="vpn" />
</permission>
<permission name="android.permission.INTERNET" >
<group gid="inet" />
</permission>
<permission name="android.permission.READ_LOGS" >
<group gid="log" />
</permission>
In una seconda parte si trovano i permessi di alto livello specifici per gli UID, questi sono usati per permettere
a degli system user che hanno bisogno di effettuare certe operazioni ad alto livello di accedere a determinate
risorse.
<assign-permission name="android.permission.ACCESS_SURFACE_FLINGER"
uid="media" />
<assign-permission name="android.permission.WAKE_LOCK"
uid="media" />
...
<assign-permission name="android.permission.ACCESS_SURFACE_FLINGER"
uid="graphics" />
Per quanto riguarda il mapping tra nomi di gruppo e GID, che serve per risolvere i permessi, questo si trova
nel file nominato prima: header android_filesystem_config.h in una particolare struct:
static const struct android_id_info android_ids[] = {
{ "root", AID_ROOT, },
{ "system", AID_SYSTEM, },
{ "radio", AID_RADIO, },
Come anticipato precedentemente Android all’avvio crea un processo zygote che contiene una parziale
inizializzazione di un’applicazione generica, in seguito quando è necessario avviare una nuova applicazione
zygote si biforca, in caso l’applicazione sia un processo nativo viene chiamata una exec, altrimenti vengono
eseguiti una serie di controlli sulle capabilities ed i gruppi (dalvik_system_zygote.cpp).
Inizialmente vengono configurati i GIT supplementari, in seguito sono impostate le limitazioni alle risorse,
infine gli UID e GID effettivi. Dopo l’impostazione di UID e GID il processo configura le sue capabilities,
le sue policies ed il contesto SELinux. Infine vengono abilitate le funzionalità di debug (se necessarie) e il
processo viene lanciato con lo UID effettivo e con il processo padre settato come zygote.

2.1.2 Android Paranoid Network


Se l’accesso alla maggior parte delle risorse è regolamentato come tutti gli altri sistemi linux, Android
prevede dei controlli particolari per i processi che vogliono creare socket di rete come si può vedere dal
2.1 Sandboxing e permessi
19

af_inet.c ( ed il corrispettivo af_inet6), questo include android_aid.h e prima di aprire una socket viene
chiamato il seguente codice:
static inline int current_has_network(void)
{
return in_egroup_p(AID_INET) || capable(CAP_NET_RAW);
}
Da questo codice si desume che se i processi chiamati non appartengono al gruppo AID_INET e/o non
possiedono la capability CAP_NET_RAW non possono creare socket. Infatti all’interno di /etc/permissio-
n/platform.xml si era visto che la permission INTERNET era associata al GID inet. È proprio per questo
motivo che le applicazioni che vogliono accedere ad internet devono specificare nel proprio manifest la
INTERNET permission.
Per quanto riguarda i socket locali che servono ai demoni di sistema per effettuare comunicazioni (il binder
viene usato solo ad alto livello), tali socket vengono creati all’avvio di sistema da init e poi usati dai processi
che ne hanno la necessità. A queste socket particolari sono associate delle credenziali che vengono verificate
dal kernel, il demone poi ha un ulteriore controllo sulle operazioni consentite ad un client specifico che
necessita di lanciare qualche comando.

2.1.3 Permessi a livello applicativo


Per ogni UID di un’applicazione è associato un package ed è compito del package manager service di tener
traccia dei permessi concessi ad ogni package. Il metodo utilizzato all’interno del package manager è il
checkUIdPermission che prende come parametri il nome del permesso e l’UID.
Se un’applicazione ignora il risultato PackageManager.PERMISSION_DENIED restituito dalla checkPer-
mission e tenta di accedere ad una risorsa per cui non ha i permessi, viene lanciata una SecurityException.
Il check dei permessi viene chiamato molto spesso in android, infatti

nelle activity i permessi vengono verificati quando il contesto chiamante effettua lo start sulla activity

nei servizi la verifica delle permissions viene effettuata sulle chiamate di start e stop e bind.

nei content providers statici esiste un manifest con un insieme di permessi separati per lettura e scrittura,
che possono anche prevedere delle path permission espresse tramite uri per proteggere dei sottoin-
siemi di dati. Per effettuare una query ad un content provider è necessaria la readPermission, per le
operazioni di scrittura la writePermission.

nei content providers dinamici si definisce nel manifest l’authority con la grantUriPermissions settata,
in questo modo è possibile concedere l’accesso temporaneo ad un determinato uri. Questo tipo di
accesso può anche essere reso persistente o revocato.

per i broadcast sender quando vengono inviati degli intenti ai receiver, questi intenti non possono far
parte di quelli denominati come protetti se l’UID non è uno di quelli di sistema o root.

per i broadcast receivers quando vengono inviati degli intenti ai receiver registrati, se questi non hanno i
permessi specificati dal sender, allora l’intento non viene ricevuto.

i pending intents incapsulano l’identità dell’applicazione che li ha creati, e contengono un intent e un’a-
zione da eseguire; in questo modo l’applicazione può eseguire azioni per conto delle applicazioni
originali con la stessa identità e gli stessi permessi.
2.2 SELinux (SEAndroid)
20

2.1.4 Tipologie di permessi


In Android un permesso consiste in una stringa che denota la capacita di eseguire una specifica operazione.
Un’applicazione senza permessi di base non può fare molto, infatti la scrittura su disco, e lo scaricamento
di dati da internet, come visto in precedenza hanno bisogno di particolari permessi.
Un permesso è definito nel seguente modo:
<permission android:description="string resource"
android:icon="drawable resource"
android:label="string resource"
android:name="string"
android:permissionGroup="string"
android:protectionLevel=["normal" | "dangerous" |
"signature" | "signatureOrSystem"] />
Il campo che desta maggiore interesse è il protectionLevel, questo campo consiste in una stima del rischio
che comporta dare un certo permesso.

normal è il valore di default consiste in un basso rischio, tale permesso viene concetto senza chiedere
conferma all’utente.

dangerous un permesso con questo livello viene dato solo se l’utente conferma di voler autorizzare l’appli-
cazione. Di solito si tratta di permessi che chiedono l’accesso ai dati dell’utente o a dispositivi delicati
come microfono e fotocamera.

signature è il livello di permesso concesso solo alle applicazioni firmate con la stessa chiave dell’applica-
zione che ha dichiarato il permesso.

signatureOrSystem concesso solo alle applicazioni di sistema o che sono firmate con la stessa chiave della
applicazione che ha dichiarato il permesso.

2.2 SELinux (SEAndroid)


Nei moderni sistemi operativi le decisioni d’accesso sono basate sul Discretionary Access Control (DAC),
il DAC è un meccanismo software che permette al proprietario di un file o di una directory di concedere o
negare l’accesso agli altri utenti.
In un sistema con DAC i permessi dei files possono essere cambiati da un processo in esecuzione con lo
stesso user ID del proprietario del file o con user ID 0 cioè root. Nel tempo il DAC si è mostrato inadeguato
poiché le decisioni di accesso sono basate solo sull’identità dell’utente e su ciò che gli appartiene, inoltre
l’amministratore di sistema ha il pieno controllo del sistema stesso.
Il Mandatory Access Control o MAC è uno dei mezzi usati per limitare l’accesso agli oggetti, l’acces-
so in questo caso è basato sulla sensibilità delle informazioni contenute dentro gli oggetti stessi e sulle
autorizzazioni dei soggetti per accedere a tali informazioni sensibili.
In un sistema che usa MAC ad ogni file viene assegnata un’etichetta di sicurezza con le informazioni di
sicurezza legate al file stesso, il MAC non si basa sul proprietario del file o dell’ID del processo che ha
effettuato una richiesta.
Un processo che ha una sicurezza di livello inferiore a quello del file non può leggere un particolare file, se
un processo ha una sicurezza di tipo diverso non può scrivere su tale file. Il MAC offre decisioni di accesso
basate su livelli e tipi di sicurezza. All’amministratore viene a meno il concetto di controllo assoluto.
2.2 SELinux (SEAndroid)
21

Basandosi su questi concetti, la National Security agency ha definito il MAC flessibile creando l’architettura
FLASK (Flux Advanced Security Kernel), l’architettura FLASK separa in maniera netta la definizione logica
della politica dal meccanismo di protezione. SELinux (Security-Enhanced Linux) è un’implementazione di
tale architettura.

Figura 2.1: Figura Architettura FLASK

La politica di sicurezza logica è incapsulata in una componente separata del sistema operativo, dotata di
un’interfaccia ben definita per effettuare le decisioni della politica di sicurezza, questa componente separata
è detta Security Server (SS) anche se nelle versioni di uso comune tale componente non si trova su un
server ma all’interno del sistema stesso.

Le componenti nel sistema che realizzano le politiche di sicurezza sono dette, nell’architettura FLASK,
Object Managers, come dice il nome stesso gli object manager richiedono al security server quali sono le
politiche di sicurezza legate ad un certo oggetto e le fanno rispettare.
L’architettura Flask fornisce anche una componente AVC (access vector cache) che memorizza le computa-
zioni relative alle decisioni di accesso fornite dal security server per un successivo uso da parte degli object
managers, tale componente supporta anche la revoca dei permessi. Nell’implementazione di SELinux gli
altri sottosistemi del kernel, come gestione dei processi, filesystem, socket IPC, System V IPC, sono object
managers.

L’architettura Flask definisce due tipi di dati per le etichette di sicurezza: il contesto di sicurezza (Security
Context) e l’identificatore di sicurezza (SID). Il contesto di sicurezza consiste in una etichetta di sicurezza,
lo SS memorizza il contesto di sicurezza come una struttura usando tipi di dati privati. L’identificatore di
sicurezza è un intero che è mappato dal SS in un contesto di sicurezza.

Gli object managers dell’architettura Flask sono responsabili dell’associazione tra SID ed oggetti attivi del
kernel, in particolare l’object manager del filesystem deve mantenere, anche, un legame persistente tra file e
contesti di sicurezza. Poiché gli object managers gestiscono i SID e i contesti di sicurezza senza conoscerne
il contenuto un cambiamento nel formato o nel contenuto delle etichette di sicurezza non richiedono un
cambiamento agli object managers. In SELinux il security server definisce una politica di sicurezza che è
la combinazione di Type Enforcement (TE), controllo di accesso basato sul ruolo (RBAC) e un’opzionale
sicurezza multilivello (MLS). I file e i processi hanno un security context (contesto di sicurezza).

Per vedere il security context di un file, si usa il flag -Z del comando ls:
[root@alpha ~]\# ls -Z /sbin/ifconfig
-rwxr-xr-x. root root system_u:object_r:ifconfig_exec_t:s0 /sbin/ifconfig
2.2 SELinux (SEAndroid)
22

Per vedere il security context di un processo, si usa il flag -Z del comando ps:
[root@andy ~]\# ps -Z
LABEL USER PID PPID NAME
u:r:init:s0 root 1 0 /init
u:r:kernel:s0 root 2 0 kthreadd
u:r:kernel:s0 root 3 2 ksoftirqd/0
...
u:r:zygote:s0 root 39 1 zygote
u:r:drmserver:s0 drm 40 1 /system/bin/drmserver
u:r:mediaserver:s0 media 41 1 /system/bin/mediaserver
u:r:dbusd:s0 bluetooth 42 1 /system/bin/dbus-daemon
Il security context è formato da 3/4 campi: user:role:type[:range]

user rappresenta l’identità dell’utente o della classe di utenti, in questo caso termina con un _u, su Android
vale ’u’

role associato ad uno o più utenti che possono avere un determinato accesso, su Android vale ’r’

type invece ha una funzione diversa rispetto all’oggetto al quale è associato: se è associato ad un proces-
so definisce a quale processo o dominio (gruppo di processi) lo user SELinux (detto soggetto) può
accedere. Se è associato ad un oggetto, definisce che permessi ha l’utente selinux su tale oggetto.

range conosciuto anche come level, è presente solo se la policy supporta MCS o MLS (non usato in An-
droid); consiste in due livelli di sicurezza (uno basso ed uno alto) separati da un trattino, ma Android
usa solo il valore s0.

SELinux prevede tre modalità di funzionamento: disabled, permissive ed enforcing. La prima modalità
prevede che non venga effettuato alcun controllo delle policy, la seconda modalità prevede che vengano ef-
fettuati controlli ma le negazioni siano solo loggate, nella terza invece le negazioni sono applicate e loggate.
Tramite i comandi getenforce e setenforce è possibile conoscere e configurare la modalità preferita.

In Android il security context è salvato all’interno dell’attributo security.selinux nei metadati di un file. Il
security context viene impostato alla creazione del file, il suo valore di default è impostato dal valore del
padre, quindi se si tratta di un file in una directory, il valore del file sarà impostato al valore che possiede
la directory; lo stesso discorso vale anche per i processi che effettuano una fork. In seguito il valore del
security context di un oggetto puà cambiare, in questo caso si parla di type transition, se invece si tratta di
un processo, si parla di domain transition.

Una policy di sicurezza consiste di un insieme di regole che definisce i permessi di accesso (detti anche
access vectors) per tutti i soggetti e gli oggetti. All’interno di una policy sono definite le azioni che i soggetti
possono eseguire sugli oggetti, i ruoli assegnati ad un utente e le transazioni consentite tra domini.
Le policy sono scritte in un linguaggio strutturato che comprende le istruzioni e le regole, la policy è usata
dal sistema in una forma binaria generata compilando un certo numero di sorgenti di policy tramite il tool
GNU m4.

Le istruzioni comprendono:
attribute serve a dichiarare un attributo;
2.2 SELinux (SEAndroid)
23

type serve a dichiarare un tipo ed associarlo ad uno o più attributi;


user dichiara un utente SELinux e lo associa ad un ruolo, può anche associarlo ad un livello di sicurezza di
default;
role dichiara un ruolo per degli utenti;
permissive consente ad un dominio di essere usato in modalità permissive, cioè viene loggata la violazione
ma non viene bloccata nessuna azione;
class definisce staticamente una classe di oggetti e permessi;
common si tratta di una classe che prevede una lista di permessi definiti staticamente;
inherits viene usato insieme a class, serve ad ereditare da un common un insieme di permessi.

Tali istruzioni sono accompagnate da regole, il formato standard di una access vector rule è:
rule_name source_type target_type : class perm_set;
In cui la rule_name può consistere nelle keyword allow, dontaudit, auditallow e neverallow. source_type e
target_type sono dei set di identificatori racchiusi da parentesi graffe di tipo type o attribute. il source_type
è l’identificatore di un soggetto (processo) e target_type è l’identificatore dell’oggetto a cui il processo tenta
di accedere.
Infine class è il nome del target al quale si vuole fare riferimento, e perm_set consiste in una lista di permessi
che il processo di origine ha sull’oggetto target.

Degli esempi di regole possono essere:


allow initrc_t acct_exec_t:file { getattr read execute };
Che permette ad initrc_t di avere accesso ai file di tipo acct_exec_t che hanno i permessi di getattr, read ed
execute.
dontaudit traceroute_t { port_type -port_t }:tcp_socket name_bind;
Evita che venga loggato l’evento che si presume che accada e non si vuole registrare. In pratica la regola
dice che quando al processo traceroute_t viene negato l’accesso al permesso name_bind su una tcp_socket
per tutti i tipi associati all’attributo port_type, tale evento non deve essere registrato.
neverallow ~can_read_shadow_passwords shadow_t:file read;
Usando la regola neverallow, non si permette mai a nessun file l’accesso in lettura al tipo shadow_t, fatta
eccezione per i soggetti associati all’attributo can_read_shadow_passwords. Come si può vedere in questa
regola viene usata la tilde per indicare l’insieme complementare di soggetti.

Per ulteriori esempi consultare [Selinux: Access Vector Rules].


2.2 SELinux (SEAndroid)
24

Figura 2.2: Figura Architettura SELinux in dettaglio


2.2 SELinux (SEAndroid)
25

2.2.1 SELinux in Android

Quando è stato progettato il modello di sicurezza di Android, esso non prevedeva SELinux, infatti la sicurez-
za a livello applicativo doveva basarsi sui permessi mentre a livello kernel a proteggere il sistema sarebbero
dovuti bastare il meccanismo di isolamento ed il sandboxing.
Tuttavia il modello di Android è basato sul DAC (Discretionary Access Control) e per quanto si possa stare
attenti, una volta che un utente ha ottenuto determinati privilegi può a sua discrezione trasferire il privilegio
ad un altro utente.
Da questo è comparsa la necessità di un modello di tipo MAC (Mandatory Access Control) che si affian-
casse al DAC, in modo che l’accesso a file e risorse di ogni user rispettasse sempre e comunque un insieme
di policy definite a livello di sistema. Ma il semplice SELinux non è sufficiente a coprire tutta una serie di
funzionalità del kernel di Android (o androidismi), come il sistema di IPC Binder, il meccanismo di condi-
visione anonima di memoria (Anonymous Shared Memory o ashmem), il logging ed i meccanismi di wake
lock.
Le differenze sono anche presenti a livello di sistema, infatti il programma init è differente, così come la
maggior parte dei demoni di sistema e della libreria C. Infine a livello applicativo, come accennato prece-
dentemente, il processo zygote contiene una Dalvik vm preinizializzata con un certo insieme funzionalità
precaricate, e questo non permette a SELinux di effettuare le transazioni di sicurezza prima dell’avvio di un
programma.
Per usare SELinux, Android ha dovuto subire numerose modifiche, per cominciare a bassissimo livello il
filesystem utilizzato da Android, yaffs2 non supportava originariamente gli attributi estesi e le etichette di
sicurezza, funzionalità che è stata poi aggiunta permettendo ora di chiamare la funzione getxattr su un file e
prevedendo che su ogni nuovo file venga associata un’etichetta di sicurezza.
I device Android più recenti, inoltre, prevedono come filesystem ext4, in uso da molti anni su Linux e quindi
perfettamente compatibile con SELinux. In fase di costruzione delle immagini del sistema, vengono usati
due tools mkyaffs2image e make_ext4fs, questi sono stati estesi per assegnare ad ogni file che viene scritto
una etichetta di sicurezza, come risultato le immagini generate con tali tool per il sistema e le partizioni dei
dati hanno una struttura compatibile con SELinux.

Come spiegato nella sezione relativa al Binder, esso permette l’invocazione trasparente di oggetti che pos-
sono trovarsi sullo stesso processo o su processi diversi. Al livello del kernel il driver del binder è map-
pato come /dev/binder, questa interfaccia può essere acceduta da qualsiasi applicazione per effettuare le
transazioni IPC tramite i comandi ioctl che mette a disposizione il driver.
Il programma servicemanager allo startup del sistema si registra come context manager sul /dev/binder e
gestisce le richieste delle applicazioni che vogliono accedere ai vari servizi.
In quanto driver del kernel, il binder ha la possibilità di effettuare tutto un insieme di operazioni a livello
kernel, ma la sua implementazione non prevedeva inizialmente degli hooks di sicurezza LSM e quindi in
fase di esecuzione il suo comportamento non poteva essere controllato, così sono stati aggiunti degli hooks.
Anche SELinux è stato modificato in modo da supportare gli oggetti di tipo binder con i suoi permessi
all’interno del classmap header.
L’Anonymous Shared Memory (ashmem) è un sottosistema Android, una regione ashmem è rappresentata
un descrittore di file aperto accompagnato da un oggetto Linux standard chiamato Shared Memory (shmem).
L’oggetto shmem è già supportato da SELinux, quindi per un oggetto ashmem, tale supporto pare sufficiente
per controllare gli accessi in lettura e scrittura in tali regioni di memoria. Ma al momento non è ancora stato
testato benissimo, quindi questa parte rimane solo parzialmente securizzata.
2.2 SELinux (SEAndroid)
26

All’interno dello userspace sono state necessarie delle modifiche ad alcuni componenti, ad esempio l’imple-
mentazione della libreria C, bionic, è stata modificata in modo da prevedere dei wrapper per le chiamate di
sistema che si occupano delle etichette di sicurezza. È stato anche modificato il linker dinamico in modo
che potesse riconoscere ed utilizzare il flag auxv (AT_SECURE auxiliary vector) che serve a stabilire se
è necessario abilitare la modalita sicura. Questo flag viene utilizzato dal kernel per notificare a livello di

userspace se è avvenuta o meno una transazione sicura. Prima di questo cambiamento, il linker Android
controllava che lo UID/GID ed EUID/EGID fossero corretti, ma questo significava da un lato effettuare
delle chiamate di sistema per stabilire l’uid e il gid e dall’altro non venivano controllate le capabilities del
file.
Poiché la libreria C usata da Android è diversa, è stato necessario anche creare un port della libreria lib-
selinux che non prevedesse dipendenze dalle estensioni di libreria GNU. Le altre librerie usate in Linux
per gestire SELinux (libsepol e libsemanage) che servivano a creare e manipolare policy sul sistema, sotto
Android non sono necessarie e quindi non sono state aggiunte nello stesso sistema.
libsepol ed il policy compiler di SELinux sono stati adattati in modo da poter generare le policy Android ed
inserirla dentro il sistema operativo in fase di build.

A differenza dell’init dei sistemi linux, che lanciano un interprete per la shell, Android interpreta diretta-
mente i file per la configurazione iniziale (init.rc), per questo motivo al linguaggio in cui sono definite le
init, sono state aggiunti una serie di comandi come seclabel, restorecon, setcon, setenforce e setsebool.

on early-init
...
# Set the security context for the init process.
# This should occur before anything else (e.g. ueventd) is started.
setcon u:r:init:s0
....
# Set the security context of /adb_keys if present.
restorecon /adb_keys
...
on post-fs-data
# We chown/chmod /data again so because mount is run as root + defaults
chown system system /data
chmod 0771 /data
# We restorecon /data in case the userdata partition has been reset.
restorecon /data
...
## Daemon processes to be run by init.
service ueventd /sbin/ueventd
class core
critical
seclabel u:r:ueventd:s0
service logd /system/bin/logd
class core
socket logd stream 0666 logd logd
socket logdr seqpacket 0666 logd logd
2.2 SELinux (SEAndroid)
27

socket logdw dgram 0222 logd logd


seclabel u:r:logd:s0
...
# adbd is controlled via property triggers in init.<platform>.usb.rc
service adbd /sbin/adbd --root_seclabel=u:r:su:s0
class core
socket adbd stream 660 system system
disabled
seclabel u:r:adbd:s0
...

Nota: Il resto del codice il sorgente si trova all’interno della release lollipop [init.rc di lollipop] build.

Come si può vedere nel listato, all’avvio del sistema, nel file di init viene specificato che tra le prime
operazioni da effettuare venga impostato il contesto di protezione dello stesso init tramite il comando setcon.
In seguito viene lanciato ueventd che è stato esteso per etichettare i nodi del device rispetto alla policy di cui
necessitano.
In seguito a questa fase iniziale di setup, detta early-init, i processi ed i file creati sono perlopiù etichettati
correttamente: il loro contesto di sicurezza viene settato dal kernel appena vengono eseguiti.
In seguito i servizi come ueventd, logd e adbd, vengono etichettati esplicitamente tramite una seclabel perché
sono due eseguibili del filesystem di root, che non è altro che una versione spacchettata dell’immagine
iniziale di sistema ramfs e quindi non ha le etichette a livello di file.

L’ init gestisce la creazione ed il binding delle socket locali per molti servizi di sistema e queste socket
deveno essere etichettate in modo da riflettere gli attributi di sicurezza del servizio al quale fanno riferimento
e non del processo init, per adempiere a questo compito vengono settate, tramite seclabel, le etichette di
sicurezza per tutte le socket ed i loro files, in modo che le socket dei servizi siano sotto il controllo della
policy SELinux. Alla fine dello script viene ricaricata la policy e vengono riavviati tutti i demoni.
Il reset della policy è anche stato usato nella recovery console e nel programma di update, questi sono
stati infatti estesi in modo da assicurare uno stato di etichettatura sicura a seguito delle varie operazioni che
possono compromettere il sistema. Per fare ciò all’interno dell’immagine di recovery è presente un file di
configurazione chiamato file_contexts.
I comandi SELinux sono stati aggiunti anche all’estensione toolbox che ha permesso di poter lanciare
comandi relativi al contesto SELinux (come ls -Z e ps -Z visti prima).

Tutte le applicazioni di Android sono avviate dal processo Zygote, tipicamente facendo delle richieste dirette
all’Activity Manager Service (AMS), che a sua volta invia un messaggio nella socket di zygote e chiama
Zygote.forkAndSpecialize(), a sua volta zygote effettua una chiamata JNI alla sua implementazione nativa.
La VM Dalvik a questo punto effettua una fork e nel processo figlio setta le credenziali DAC (UID, GID e
gruppi supplementari) in modo che tali credenziali rispettino le credenziali dell’applicazione.
Anche il system_server di Android con UID system che consiste di un framework che permette alle app
di accedere ai servizi, viene creato usando una fork, in questo caso una variante particolare chiamata
forkSystemServer che internamente a Dalvik utilizza la stessa funzione: forkAndSpecializeCommon.
Per mettere in sicurezza il system_server e distinguerlo da tutti gli altri processi avviati da zygote è stato
aggiunto uno hook dentro la Dalvik VM in modo che chiamasse una funzione libselinux durante il setup del
processo figlio.
2.2 SELinux (SEAndroid)
28

La configurazione del security context per il processo figlio deve avvenire dopo la configurazione delle cre-
denziali DAC, poiché al nuovo contesto di sicurezza potrebbe non essere permesso modificare le credenziali
DAC e questo deve essere fatto prima che qualsiasi altro oggetto o thread sia creato da tale processo figlio
in modo da assicurare un’etichettatura corretta per questi oggetti e thread.

In Android sono state estese tutte le interfacce che si occupano di creazione dei processi in modo da prendere
come argomento addizionale la stringa seinfo, che può essere passata attraverso i vari livelli da AMS,
a zygote e alla Dalvik VM. In questo modo il system_server può essere distinto dalle altre applicazioni
tramite la stringa seinfo.
Oltre a controllare la socket di comunicazione di zygote per creare un processo figlio si è reso necessario
anche controllare lo stesso zygote, per fare in modo che le API di SELinux fossero accessibili da Java, è
stato creato un Java jni binding di un subset della libreria nativa libselinux; in seguito la classe Zygote-
Connection è stata estesa per usare queste API ed ottenere il security context del processo client e applicare
i check dei permessi SELinux per ogni operazione che richieda privilegi.
2.2 SELinux (SEAndroid)
29

2.2.2 Middleware Mandatory Access Control


La modifica più complessa, per completare la gestione sicura dell’avvio dei processi, è quella che permette
a zygote di comunicare con l’AMS o più in generale di gestire SELinux all’interno del middleware, poiché
a questo livello non sono usate le socket ma il Binder. Le transazioni con il Binder spesso necessitano di
chiamate che passano a più livelli, richiedono salvataggi e recupero dell’identità del chiamante per effettuare
il controllo dei permessi.
Ad esempio l’accesso ai content providers passa attraverso AMS, che si salva l’identità del chiamante in
uno storage locale, prima di chiamare il content provider, in modo da poter comunicare al content provider
se il chiamante possiede i permessi adeguati per effettuare una determinata query.
Conoscendo l’identità del chiamante, è possibile chiamare il metodo checkPermission che prende come
parametri PID e UID, ed è in questo modo che agisce ad esempio AMS. checkPermission è stato esteso in
modo da prevedere dei controlli basati sul contesto di sicurezza SELinux.

Per questo caso d’uso e tanti altri è stato aggiunto un layer middleware del MAC chiamato MMAC. Il
MMAC layer deve interagire con il MAC a livello kernel per determinare la strina SELinux usata per l’eti-
chettatura di sicurezza. In tutti gli altri casi il MMAC ha funzionalità indipendenti dal kernel e usa solo la
libselinux. Questa scelta di design rimuove la necessità di passare il security context del sender tramite il
Binder.

Figura 2.3: Figura Architettura SEAndroid

MMAC ha numerosi casi d’uso, uno di questi consiste nell’install-time MAC che è stato integrato come una
parte del core di SEAndorid poiché provvede a determinare la stringa seinfo delle applicazioni. Come dice il
nome stesso questo meccanismo viene applicato durante il check dei permessi che viene effettuato durante
l’installazione, tale approccio di autorizzazione di un’installazione è policy-driven e preserva il contratto
tutto-o-niente attualmente presente su Android. L’install-time MAC si basa su quello che richiede l’appli-
cazione invece che sull’utente che la vuole installare. La policy è descritta nel file mac_permissions.xml e
viene applicata grazie ad un’estensione del PackageManagerService (PMS) di Android.

Benché la SEAndroid policy sia stata integrata all’interno del immagine del sistema nel ramdisk, in modo
da poter essere avviata dall’init nei primi momenti del boot, questo approccio assicura che i conseguenti
2.2 SELinux (SEAndroid)
30

processi e files siano etichettati correttamente quando sono creati e la policy sia forzata il prima possibile.
Tuttavia questo approccio non considera le modifiche a runtime che possono avvenire senza che l’immagine
del sistema venga modificata.
Per supportare questo caso d’uso sono stati prima creati dei bindings per supportare all’interno del sy-
stem_server o delle app con UID di sistema, le configurazioni di SELinux in modalità enforcing ed il set
dei booleani della policy è stata creata la applicazione SEAndroidManager che permette all’utente di mo-
dificare queste configurazioni.Tuttavia questo meccanismo non supporta le modifiche della policy attraverso
i booleani.
La prima funzionalità aggiunta al SEAndroidManager permette di ricaricare i files di policy dalla directory
standard /data/system che è già stata usata per varie configurazioni di sistema a runtime. Questa cartella è
scrivibile solo dall’utente con UID system e quindi i files di configurazione possono essere scritti solo da
system_Server o system,. Una volta fatto l’update dei files di policy in /data/system, viene scatenato un
nuovo evento di reload delle policy, settando la proprietà selinux.reload_policy.

Per quanto riguarda le policy del MMAC, queste utilizzano lo stesso formato xml delle policy a livello kernel
e sono raccolte all’interno del file mac_permissions.xml. Questa policy è scalabile visto che deve occuparsi
di controllare l’accesso alle risorse per le moltissime applicazioni che l’utente potrebbe voler installare.

Per effettuare questi controlli senza avere una specifica policy per ogni app, è stato aggiunto il supporto
ai certificati X.509 della policy principale. Visto che Android richiede già che le applicazioni siano firma-
te con un certificato, dentro l’applicazione c’era già l’attributo che si occupava di identificare i gruppi di
applicazioni a partire dal loro certificato.

Quindi dentro mac_permissions.xml vengono utilizzate le chiavi di firma AOSP per organizzare le appli-
cazioni in classi di equivalenza, usate poi per decidere se approvare o negare un insieme di permessi. Ogni
elemento della configurazione contiene anche un tag seinfo che corrisponde alla seinfo string usata dalle
etichette di sicurezza.
Ogni applicazione può specificare il suo package name oltre al suo certificato per poter essere distinta dalle
altre.

2.2.3 SEAndroid e releases


Anche se Android 4.2 conteneva già una gran parte del codice SELinux, SELinux è stato disattivato nella
build da rilasciare. Nella versione successiva, Android 4.3, SELinux era abilitato ma la sua modalità pre-
definita era Permissive e tutti i domini erano impostati singolarmente sulla modalità Permissive ed erano
basati sul dominio unconfined, che concedeva loro accesso completo, quindi c’era solo DAC a gestire le
permission , tuttavia la modalità SELinux globale era di tipo enforcing. Infine Android 4.4 è stata la prima
versione fornita con SELinux in modalità enforcing con i domini di enforcing per i demoni di sistema.
Anche se SELinux è in modalità globale enforcing, solo i domini assegnati ad alcuni demoni sono attual-
mente in modalità enforcing: installd (responsabile della creazione delle directory dati delle applicazioni),
netd (responsabile della gestione di connessioni di rete e route), vold (responsabile del mounting della
memoria esterna e dei contenuti sicuri) e zygote.
Questi demoni vengono eseguiti come root o ricevono capabilities speciali in quanto devono eseguire
operazioni di amministrazione, come cambiare la proprietà di una directory (installd), manipolare le regole
di routine e filtraggio dei pacchetti (netd), montare i filesystems (vold) e cambiare le credenziali del processo
(zygote), per conto di altri processi.
2.3 JCA
31

Per ulteriori informazioni su come SELinux e’ stato migrato su Android, si veda [Portare SELinux su Android]

2.3 JCA
I servizi crittografici di Android si basano sulla Java Cryptography Architecture (JCA). JCA è un fra-
mework disponibile in Java sin dalla versione 1.1 ed è parte del package java.security, JCA fornisce dei
servizi crittografici di base come block cyphers, message digests, digital signatures, etc.
JCA definisce due entità principali: l’engine ed il provider. L’engine definisce una API che permette ad
un applicazione o ad un servizio di usare un cypher o un message digest. Un provider fornisce delle
implementazioni concrete degli engine.

2.3.1 Engine
Una classe engine fornisce l’interfaccia per un particolare servizio di crittografia. Tali servizi rientrano in
una delle seguenti categorie:

1. Operazioni di crittografia come ad esempio codifica/ decodifica, firma/ verifica ed hash

2. Generazione o conversione di materiale di crittografia ad esempio chiavi e parametri degli algoritmi.

3. Gestione e mantenimento degli oggetti di crittografia come chiavi e certificati.

Un engine è inoltre definito in modo astratto ed indipendente da una specifica piattaforma hardware o
software.
Ad esempio l’interfaccia Key rappresenta delle chiavi opache che possono essere utilizzate nelle operazioni
crittografiche ma che non forniscono l’accesso alla key material («raw key bytes»). Questo permette alle
stesse classi JCA di operare sia con implementazioni di algoritmi software, che memorizzano la chiave in
memoria, sia con algoritmi hardware che lavorano con chiavi memorizzate su una smart card.
Ogni classe Engine presenta un certo numero di factory methods statici chiamati getInstance():

static EngineClassName getInstance(String name)


static EngineClassName getInstance(String name, String provider)
static EngineClassName getInstance(String name, String provider)

La prima variante è quella da preferire nella maggior parte dei casi, in quanto si lascia JCA decidere
il «miglior» provider da utilizzare. La seconda e terza variante consentono di richiedere un’implemen-
tazione da uno specifico provider. Tutte le varianti infine lanciano una checked exception (omessa) se
l’implementazione non è disponibile.
Ad esempio è possibile richiedere il digester SHA-256 tramite l’engine MessageDigest nel seguente modo:

MessageDigest sha256 = MessageDigest.getInstance("SHA-256");

Come si vede nell’esempio, l’argomento «name» equivale al nome dell’algoritmo che si vuole utilizzare,
tale crea un mapping con una trasformazione o un algoritmo di crittografia particolare, oppure specifica una
strategia di implementazione. Nell’esempio sopra si vede che il mapping è diretto, ma è possibile anche
specificare una serie di algoritmi da usare in combinazione, ad esempio «SHA1WithRSA» che significa che
SHA1 sarà usato per l’hashing e RSA per la firma.
Lo standard di composizione è descritto nella documentazione di java [Standard Names]
2.3 JCA
32

2.3.2 Provider
Ogni provider implementa un certo numero di Engine e JCA mantiene internamente un registro dinamico di
tutte le implementazioni di Engine disponibili. Prima di utilizzare un Engine questo deve essere registrato
tramite un provider. Inoltre il registry mantiene, per ogni provider, un intero che rappresenta la precedenza:
quindi se un certo engine è implementato da più provider, verrà selezionato quello con precedenza maggiore.
Esistono due modi principali per registrare un provider: statico e dinamico.
Nella registrazione statica si fa uso del file security.properties con una riga formattata come segue:
security.provider.n=FullyQualifiedClassName
dove n rappresenta la precedenza. Come si può vedere la registrazione statica richiede la modifica del file
delle proprietà di sicurezza del sistema con l’aggiunta di una voce per il provider.
Questo file delle proprietà è chiamato security.properties ed è presente all’interno del jar di sistema co-
re non può quindi essere modificato pertanto la registrazione statica dei provider al momento non viene
supportata da Android.
Nella registrazione dinamica invece si fa uso della classe Security, che mette a disposizione un metodo
statico chiamato insertProviderAt:
static {
int orderOfPreference = 42;
Security.insertProviderAt(new MyProvider(), orderOfPreference);
}
Esiste inoltre il metodo iaddProvider per aggiungere nella prima posizione disponibile il provider.

Providers
Tramite la classe Security è possibile accedere ai Provider registrati:
Provider[] providers = Security.getProviders();
Android presenta tre provider di base: Harmony, Bouncy Castle, AndroidOpenSSL.

Harmony
Il primo si chiama Harmony e deriva da Apache Harmony, una implementazione del JRE ormai dismessa,
tale provider fornisce un’implementazione limitata di JCA (denominata Crypto) che si limita a fornire le
implementazioni di servizi crittografici come hashing, random generation e digital signatures. Questo pro-
vider è ancora incluso in Android, al fine di preservare la retro compatibilità, ma ha la priorità più bassa tra
tutti i provider JCA.

Bouncy Castle
Bouncy Castle è stato l’unico provider JCA completo disponibile prima di Android 4.0, fa parte del progetto
[Bouncy Castle] . La versione presente in Android è, tuttavia, una versione modificata del provider Bouncy
Castle:

• alcuni algoritmi, modi e parametri sono stati rimossi in quanto non supportati dalla reference imple-
mentation di Java

• alcuni algoritmi insicuri, come MD2 e RC2 sono stati rimossi

• MD5 e SHA1 sono forniti come implementazione nativa (JNI) per migliorare le performances
2.4 JSSE
33

Questa versione ridotta di Bouncy Castle ha causato alcuni problemi di upgrade, una possibile alternativa
è rappresentata dal progetto [Spongy Castle]. Questo progetto offre una versione stock di Bouncy Castle,
il cui cambia il namespace (org.bouncycastle.* a org.spongycastle.*) per evitare conflitti di classpath con
quello di Android. Infine per assicurarsi che venga usato questo provider occorre caricarlo con priorità
massima (1):
static {
int priority = 1;
Security.insertProviderAt(
new org.spongycastle.jce.provider.BouncyCastleProvider(),
priority
);
}

AndroidOpenSSL
Questo provider JCA continua la strategia di portare in codice nativo gli Engine con problemi di performan-
ce, come già visto per Bouncy Castle.
AndroidOpenSSL quindi usa la libreria nativa OpenSSL, tramite JNI, per fornire implementazioni di tutti
gli engine. Questo provider a partire da Android 4.4 è stato marcato come default, a priorità 1.

2.4 JSSE
JSSE, Java Secure Socket Extension, è un framework Java per la comunicazione sicura su Internet. Intro-
dotto sino a partire dalla versione 1.2 di Java come package opzionale, è stato in seguito reso ufficiale dalla
versione 1.4. Molti elementi di design delle classi sono stati ripresi da JCA, come ad esempio i provider e
gli engine.
Implementa sia Secure Socker Layer (SSL) che Transport Layer Security (TLS). Questo implica che un’ap-
plicazione o un servizio Android può instaurare una connessione sicura point-to-point con un host remoto,
usando una combinazione di crittografia simmetrica e asimmetrica. Le API sono disponibili nei packages
javax.net e javax.net.ssl e forniscono:
• HttpsURLConnection, HTTP over SSL streams, come definiti dall’RFC 2818
• SSLContext
• SSLEngine
• SSLServerSocketFactory
• SSLServerSocket
• SSLSocketFactory
• SSLSocket
• peer authentication
• hostname verification
Per quanto riguarda la gestione e la validazione dei certificati, Android non segue l’implementazione Java
SE nella quale esiste un solo keystore file (cacerts). Piuttosto implementa uno schema basato su più file che
permette, tra le altre cose, la revoca, il blacklisting ed il pinning dei certificati.
2.4 JSSE
34

Providers
In Android esistono due provider di JSSE: HarmonyJSSE e AndroidOpenSSL.
HarmonyJSSE fornisce un’implementazione di JSSE basata sui socket Java (java.net.x) e JCA. AndroidO-
penSSL invece si basa sulla libreria nativa OpenSSL (con qualche patch di sicurezza) e JNI.
Recentemente AndroidOpenSSL è stato marcato come il provider JCA e JSSE di default, mentre Harmony-
JSSE è stato marcato come deprecato.
35

3.1
3
L’insicurezza del Binder
Vulnerabilità e possibili attacchi

Dal momento che i servizi di sistema non sono implementati nello spazio di indirizzi del client, il cliente ha
bisogno di passare per IPC per richiedere ad un servizio di sistema quello di cui ha bisogno.
Il binder mette a disposizione tutto quello che serve alle semplici applicazioni, usando una libreria chiamata
libbinder.so che viene caricata nella maggior parte dei processi di Android.
La libreria tra le altre cose gestisce la maggior parte di lavoro di wrapping e unwrapping di oggetti complessi
in oggetti semplici chiamati Parcels. Questi oggetti corrispondono al blocco unitario che viene invitato tra
un processo e l’altro.

Il binder driver invece svolge le funzioni a livello kernel, usando IPC, ad esempio copia i dati da un
processo all’altro tenendo traccia di quale handle corrisponde a quale oggetto all’interno di uno specifico
processo.
Dopo aver gestito tutti i compiti come lo smistamento di oggetti in Parcels, il Binder chiama una syscall
ioctl e passa come parametro il descrittore /dev/binder in questo modo trasferisce i dati rilevanti al kernel.
Il binder driver, poi cerca il servizio richiesto e copia i dati nello spazio di indirizzi del sistema, in seguito
sveglia un thread in attesa all’interno del processo server per gestire la richiesta.
Dopo l’unmarshalling degli oggetti di tipo Parcel e la verifica che il processo client disponga delle autoriz-
zazioni pertinenti per svolgere il compito richiesto (come accedere alla telecamera o creare una connessione
di rete), il server esegue il servizio richiesto con tutte le varie chiamate al kernel per interagire con l’hardware
richiesto e per leggere le risposte alle varie richieste
In seguito la copia di libbinder che viene caricata all’interno dei proprio spazi di indirizzamento effettua un
marshall dei dati di risposta e li invia indietro al driver che poi li gestisce correttamente e li rimanda indietro
al processo chiamante.

Come visto prima, il codice di un servizio Android tipicamente è diviso in due parti: il servizio stesso e la
sua interfaccia, l’interfaccia però ha una doppia implementazione: un proxy client side ed uno stub server
side.
Questo permette allo sviluppatore di creare un app che chiama dei servizi trasparentemente senza accorgersi
che sta utilizzando IPC.
Un oggetto binder può essere utilizzato per inviare oggetti piuttosto complessi tramite un descrittore. Il
driver ha il compito di mantenere una tabella di traduzione tra puntatori agli oggetti reali nel processo
3.1 L’insicurezza del Binder
36

originale e gli handle assegnati a tali oggetti, in modo che i processi remoti possano accedere ad essi.
Se un’applicazione in esecuzione su Andoid non interagisse con il binder sarebbe estremamente limitata.
Ogni applicazione in media richiede interazioni con una decina di servizi di sistema e può usare il binder
migliaia di volte al minuto. Infatti, come visto, anche all’interno di un’applicazione stessa, per trasferire i
dati da un’activity ad un’altra vengono utilizzati gli intenti, che passano attraverso il binder.

3.1.1 Comprendere gli oggetti di tipo Parcel


Un esempio concreto di come funziona il wrapping e unwrapping degli oggetti si può vedere aprendo i
sorgenti di Android. Un caso interessante è quello del MediaPlayer, localizzato nel package android.media,
contiene al suo interno una doppia natura, infatti oltre ai context ed ai listener Java, contiene anche mNati-
veContext e mListenerContext. Anche i metodi sono duplicati, prendiamo ad esempio setVolume:

public void setVolume(float leftVolume, float rightVolume) {


if (isRestricted()) {
return;
}
_setVolume(leftVolume, rightVolume);
}

private native void _setVolume(float leftVolume, float rightVolume);

Il metodo Java fa riferimento alla implementazione nativa, ed è proprio in IMediaPlayer.cpp che si può
vedere l’interazione del player con il sistema, tramite il binder:

status_t setVolume(float leftVolume, float rightVolume)


{
Parcel data, reply;
data.writeInterfaceToken(IMediaPlayer::getInterfaceDescriptor());
data.writeFloat(leftVolume);
data.writeFloat(rightVolume);
remote()->transact(SET_VOLUME, data, &reply);
return reply.readInt32();
}

I dati vengono inseriti all’interno dela pila di dati «data» ed in seguito viene effettuata una transazione di
tipo SET _VOLUME, dove SET _VOLUME è una costante numerica presente all’inizio del sorgente (vale
16). remote() fa riferimento al BnMediaPlayer, una classe che implementa l’interfaccia stub nel processo
server side. Qui viene fatto un dispatching rispetto al codice ricevuto:

case SET_VOLUME: {
CHECK_INTERFACE(IMediaPlayer, data, reply);
float leftVolume = data.readFloat();
float rightVolume = data.readFloat();
reply->writeInt32(setVolume(leftVolume, rightVolume));
return NO_ERROR;
} break;
3.1 L’insicurezza del Binder
37

3.1.2 Attacchi al binder


Di recente sono stati presentati un paio di attacchi possibili grazie all’architettura del Binder, il primo di
questi prevede la costruzione di un Keylogger sfruttando gli strumenti che Android mette a disposizione.
L’attacco è stato chiamato Man in The Binder, in analogia con il Man in The Middle, per ricevere i dati
della tastiera, in questo attacco un’applicazione si registra come server IME (Input Method Editor), un IME
consiste in un’implementazione di tastiera di Android, un utente può modificare il tipo di IME (questo
accade spesso, quando si usa una versione customizzata di android, come quella fornita da samsung o da htc
sense) è quindi possibile anche installare nuovi tipi di IME ma solo un IME alla volta può essere abilitato.
Su una versione «vanilla» di Android, l’IME abilitato di default è com.android.inputmethod.latin.

Quando un’applicazione si registra con un server, in questo caso l’IME, per ricevere i dati da esso, il modello
client-server viene invertito: l’applicazione diventa il server ed il server diventa il client. Quando nuovi
dati sono disponibili per l’applicazione, il servizio raggiunge l’obiettivo desiderato, usando il binder per
effettuare una chiamata in un metodo di callback inizializzato dall’applicazione, in questo caso l’argomento
della callback sarà l’input della tastiera.
Per prendere i dati quindi non è necessario intercettare i dati che devono andare dall’applicazione al binder,
ma basta intercettare la risposta lanciata dal Binder per l’applicazione.

Prima si era visto che nella struttura del binder_write_read era presente anche un buffer di lettura, all’in-
terno di questo buffer i primi 4 bytes sono un codice che descrivono di fronte a che tipo di buffer ci si trova
( la definizione è presente all’interno della enum binder_driver_return_protocol), quelle che servono in
questo particolare tipo di attacco sono la BR_REPLY e la BR_TRANSACTION. La prima contiene i dati re-
stituiti dalla funzione che è stata chiamata, la seconda detiene le informazioni inviate da un servizio quando
è stato registrato un metodo di callback per gestire i dati in arrivo.

Il tipo di transazione che si ha quando si ricevono dei dati della tastiera dall’IME, contiene quindi un
read_buffer formato da una enum di 4 bytes, in seguito si trova un binder_transaction_data contentente
tra le varie informazioni il codice della funzione, che si ricava dall’interfaccia AIDL del tipo di interfaccia
interna, passata come parametro sempre nella stessa struttura.

Se vogliamo leggere i dati della tastiera ci aspettiamo un com.android.internal.view.IInputContext . Que-


sta interfaccia invia i dati attraverso la classe InputContext, che gestisce i dati ricevuti dalla tastiera verso
il processo client. In questa transazione però, il processo client agisce come un server e riceve le chiamate
dall’IME, qualsiasi tasto venga premuto viene registrato ed inviato dentro un nuovo buffer al Binder.
La funzione che si vuole intercettare sarà invece la 6, come si può vedere dall’interfaccia AIDL:
oneway interface IInputContext {
...
/* 6 */ void setComposingText(CharSequence text,
int newCursorPosition);
}

Un altro attacco potrebbe consistere nel visualizzare i dati che passano all’interno di un’applicazione,
poiché un’applicazione è formata da un certo numero di activity ed i dati tra un’activity e l’altra vengono pas-
sati tramite il binder. Come nel caso di prima è possibile leggere all’interno di una binder_transaction_data,
in questo caso l’interfaccia di interesse è quella dell’activity manager o IActivityManager che è implemen-
tata dalla classe ActivityManagerNative che estende il binder:
3.2 SELinux e vulnerabilità
38

@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
throws RemoteException {
switch (code) {
case START_ACTIVITY_TRANSACTION:
{
data.enforceInterface(IActivityManager.descriptor);
IBinder b = data.readStrongBinder();
IApplicationThread app = ApplicationThreadNative.asInterface(b);
String callingPackage = data.readString();
Intent intent = Intent.CREATOR.createFromParcel(data);
String resolvedType = data.readString();
IBinder resultTo = data.readStrongBinder();
String resultWho = data.readString();
int requestCode = data.readInt();
int startFlags = data.readInt();
ProfilerInfo profilerInfo = data.readInt() != 0
? ProfilerInfo.CREATOR.createFromParcel(data) : null;
Bundle options = data.readInt() != 0
? Bundle.CREATOR.createFromParcel(data) : null;
int result = startActivity(app, callingPackage, intent, resolvedType,
resultTo, resultWho, requestCode, startFlags, profilerInfo, options);
reply.writeNoException();
reply.writeInt(result);
return true;
}
...

In questa classe, viene gestito l’avvio della transazione, l’Intent che viene passato è generato a partire dalla
Parcel. L’intento contiene il nome della attività in fase di avvio, nel buffer si può facilmente vedere che l’og-
getto Bundle ha un determinato offset, ed è identificato dalla sequenza BNDL, preceduta dalla dimensione
dell’oggetto. al suo interno si trovano le coppie chiave valore che vengono passate da un’activity all’altra. E
poiché tutti i dati passano per il binder è possibile che se tali dati non vengono cifrati, sia facile intercettarli.
Altri attacchi simili a questo sono presenti in [Nitay Artenstein, Idan Revivo].

3.2 SELinux e vulnerabilità


A livello kernel SEAndroid fornisce un mezzo efficace per prevenire i privilege escalation da parte di ap-
plicazioni e l’accesso ai dati non autorizzati da parte di applicazioni, tramite le interfacce di livello kernel.
Fornisce inoltre una base per garantire le funzionalità di sicurezza ad un livello superiore. A tale livello
SEAndroid non è bypassabile e prevede una protezione contro le possibili manomissioni da parte di ap-
plicazioni. SEAndroid può garantire che i dispositivi hardware siano accessibili solo da parte dei servizi
autorizzati e non direttamente dalle applicazioni, in questo modo i servizi di sistema possono applicare un
livello adeguato per le autorizzazioni.
SEAndroid garantisce anche l’integrità delle applicazioni stesse inclusi i loro dati, e queste protezioni sono
state largamente testate nel tempo sotto Linux.
3.2 SELinux e vulnerabilità
39

Allo stesso modo il meccanismo del MMAC ad install-time di SEAndroid assicura che le applicazioni
possano essere installate solo se i permessi e le autorizzazioni rispettano la policy di sicurezza. Questo
meccanismo protegge l’utente dall’installare applicazioni non sicure con set di permessi pericolosi che pos-
sono essere anche usati per modificare applicazioni installate in precedenza. Tuttavia questo meccanismo
non può proteggere l’utente dagli attacchi di privilege escalation o di condivisione di dati non autorizzate
tramite applicazioni che hanno ottenuto il permesso di essere installate. Per fare questo si rende necessario
aggiungere ulteriori meccanismi MMAC che vanno al di là del periodo di installazione.
Ci sono una serie di parti di un sistema Android che SELinux non può controllare, se un determinato com-
portamento è permesso dalla policy, allora SELinux considera tale comportamento corretto anche se in realtà
non lo è. Per questo è importante sviluppare delle buone policy per avere un sistema sicuro. In secondo luo-
go SEAndroid non può proteggere il sistema da se stesso, non riesce a correggere le vulnerabilità del kernel,
in alcuni casi di vulnerabilità conosciute, SEAndroid può rendere non sfruttabile una vulnerabilità del kernel
oppure rendere l’impatto di tale vulnerabilità non sfruttabile. Ma questo non vale per tutte le vulnerabilità
Per questo altri meccanismi di protezione che si integrino con il kernel e si combinino con SEAndroid sono
sempre desiderabili.
Infine SEAndroid non può proteggere il sistema dalle componenti sconosciute che hanno diretto accesso
alle risorse del sistema come potrebbe essere un processore compromesso o una scheda di rete modificata.
Esistono tutta una serie di componenti del sistema che hanno vulnerabilità, molte di queste vulnerabilità
sono state sfruttate dalle community che desiderano ottenere accessi particolari ai sistemi Android per poterli
customizzare e ottimizzare a loro piacimento. Ma il lato negativo di queste vulnerabilità è che permettono
anche l’accesso di dati sensibili ad applicazioni maliziose.

3.2.1 Android volume daemon (vold)


vold è il demone di gestione dei volumi di Android, permette di effettuare operazioni privilegiate quali mon-
tare e smontare volumi. Questo demone viene eseguito come root e permette al MountService di effettuare
i suoi compiti visto che questo servizio è eseguito con utente system e non possiede i privilegi necessari per
montare e smontare i volumi del disco. Per approfondire l’architettura di vold [Funzionamento di vold].
3.2 SELinux e vulnerabilità
40

Figura 3.1: Figura Architettura di vold

vold dispone di un’interfaccia locale esposta via socket di dominio Unix /dev/socket/vold, accessibile uni-
camente a root e ai membri del gruppo mount. Visto che l’elenco di GID supplementari del processo
system_server (che ospita MountService) include mount, MountService può accedere al socket dei comandi
di vold. Oltre a montare e smontare i volumi, vold può creare e formattare i file system e gestire i contenitori
sicuri.
Per le comunicazioni con il kernel vold resta in ascolto sulla socket netlink in attesa di eventi relativi al
cambiamento di stato dei volumi. Questo sistema però ha una vulnerabilità pericolosa, poiché vold non
verifica il mittente del messaggio ricevuto sul netlink, quindi non è detto che il messaggio sia stato inviato
dal kernel.

Una seconda vulnerabilità è data dal fatto che all’interno del messaggio ricevuto è presente un indice di
array, di tale indice è effettuatuato un controllo per verificare che non sia troppo grande, ma non viene
verificato che tale intero non sia negativo.
Questa vulnerabilità è stata sfruttata da un exploit denominato GingerBreak, per compiere l’incremento
dei privilegi inizialmente vengono collezionate le informazioni necessarie all’exploit, tra cui l’identità del
processo vold e gli indirizzi ed i dati di interesse. Per fare questo si cercano i riferimenti all’interno del
sistema stesso.

1. all’interno di /proc/net/netlink gli utenti della netlink socket

2. in /proc/pic/cmdline si trova il vold PID.

3. all’interno di /system/bin/vold si ottiene un range di indirizzi,

4. in /system/lib/libc.so si recupera l’indirizzo di sistema

5. in /etc/vold.fstab un nome di device valido.


3.2 SELinux e vulnerabilità
41

6. logcat viene utilizzato per monitorare l’attacco ed ottenere l’indirizzo in errore.

In seguito viene costruito un messaggio fittizio ed inviato nella netlink socket verso vold, tale messaggio ha
il compito di scatenare un evento che lancierà l’ esecuzione di un exploit binario con il compito di creare
una shell con i privilegi di root.

Tramite SEAndroid è possibile bloccare questo attacco nelle primissime fasi di raccolta delle informazio-
ni, infatti la policy può essere impostata in modo da impedire la lettura delle informazioni del processo vold
da /proc. Anche il tentativo di lettura del binario vold per scoprire l’indirizzo target può essere immedia-
tamente bloccato. La policy di sicurezza è anche in grado di impedire al GingerBreak di creare una netlink
socket, poiché tale utente non è legittimato a creare questo genere di socket.

Supponendo che la costruzione del messaggio sia comunque riuscita e questo sia stato ricevuto da vold,
SEAndroid impedirebbe a vold di eseguire un binario non di sistema e che risiede in una partizione dati.
Infine se anche tutto l’attacco avesse successo, nel momento in cui il codice dell’exploit tentasse di creare
una shell con setuid-root, SEAndroid bloccherebbe tale tentativo di settare proprietario e permessi da parte
di tale codice e quindi la shell sarebbe limitata ai permessi dell’utente e non quelli di superuser.
Questo dimostra che SEAndroid se viene usato può aiutare a compensare alcune fragilità di sistema.

Usando la stessa vulnerabilità sulle netlink socket, è stato effettuato anche un altro attacco, denominato
Exploid, che aveva come target il demone degli eventi ueventd, la policy di SELinux che protegge dalla
prima vulnerabilità protegge da tutta una classe di vulnerabilità dei messaggi nelle socket netlink.

3.2.2 Zygote
Come discusso in precedenza, Zygote è un servizio di sistema che gira come root e deve far partire tutte le ap-
plicazioni Android. Zygote riceve queste richieste tramite un socket locale. Appena è ricevuta una richiesta
viene effettuata una fork e viene settato l’uid non privilegiato prima di eseguire il codice dell’applicazione.
In particolare il codice implementa la stessa logica presente nella Dalvik VM.
Tuttavia la Dalvik VM non effettua un check del fallimento della chiamata setuid(), perché solitamente tale
chiamata non fallisce per i processi a livello di root, e quindi il processo non viene abortito in caso la setuid
fallisca.

Zimperlich è un exploit che sfrutta questa vulnerabilità per guadagnare l’accesso di root da una applicazione
Android, per fare questo induce la setuid() a fallire sfruttando i limiti delle risorse. Per prima cosa il codice
forka se stesso ripetutamente in modo da raggiungere il massimo numero di processi permessi per UID
(definito in RLIMIT_NPROC).
In seguito invia una richiesta a Zygote tramite la socket locale chiedendo di generare un nuovo processo,
ma avendo raggiunto il limite, quando zygote effettua la fork e poi prova a settare l’uid, fallisce perché ha
raggiunto il limite delle risorse gestibili. Visto che Dalvik non abortisce l’operazione, l’esecuzione procede
e il codice malizioso è avviato con lo stesso UID di zygote, ovvero root. In seguito Zimperlich procede
avviando una applicazione maliziosa che rimonta la partizione di sistema in modalità read-write e crea una
shell con privilegi di root all’interno della partizione di sistema per utilizzi successivi.

Utilizzando SEAndroid anche se l’applicazione maliziosa viene avviata con l’uid di root, security context
di SELinux continua ad essere correttamente settato dalla Dalvik VM, poiché è basato sulle credenziali
dell’applicazione, l’applicazione viene avviata in un contesto di sicurezza senza particolari privilegi da su-
3.2 SELinux e vulnerabilità
42

peruser e come tale non è in grado di rimontare la partizione di sistema o di eseguire operazioni privilegiate.

Una vulnerabilità simile è stata scoperta dentro l’Android Debug Bridge Daemon o adbd, tale vulnerabilità
è sfruttata dall’exploit RageAgainstTheCage.
Abilitando SEAndroid, si vede che viene creata una shell da adbd che gira sotto l’UID di root ma può
effettuare solo transizioni non privilegiate, perché il suo contesto è automaticamente basato sulla policy
SELinux.

3.2.3 Anonymous Shared Memory (ashmem)


ashmem è un sottosistema del kernel di Android usato per ottenere una zona di memoria condivisa tra diverse
applicazioni che vogliono condividere dati [Funzionamento di ashmem].
Originariamente ashmem era usato per implementare lo spazio di memoria in cui erano racchiuse le pro-
prietà di sistema globali gestite dall’Android init. Un mapping in sola lettura di queste proprietà globali
è mappato all’interno di ogni processo del sistema ed è usato per leggere i valori delle proprietà quando ce
n’è la necessità.
Sono state identificate molte vulnerabilità relative alla protezione dello spazio di memoria dedicato alle
proprietà di sistema.
3.2 SELinux e vulnerabilità
43

Figura 3.2: Il sistema ashmem

Queste vulnerabilità sono state utilizzate all’interno degli exploit KillingInTheNameOf e psneuter.
L’exploit KillingInTheNameOf invoca la system call mprotect() per aggiungere un accesso in scrittura
al mapping delle proprietà di sistema nello spazio condiviso in modo da poter essere in seguito libero di
modificare qualsiasi proprietà direttamente tramite una operazione di scrittura.
In seguito l’exploit usa le abilità appena acquisite per modificare la proprietà ro.secure ed ottenere l’accesso
ad una shell di root tramite adbd al successivo riavvio.
Sotto SEAndroid a tale exploit viene subito negata la possibilità di aggiungere l’accesso in scrittura sul
mapping, perché la policy non permette un accesso in scrittura al mapping di proprietà del processo di init.
L’exploit psneuter usa una diversa vulnerabilità dell’implementazione di ashmem: usa uno specifico co-
mando ioctl ASHMEM_ SET_ PROT_MASK per settare una maschera di protezione sulle proprietà di
sistema.
Al successivo riavvio di adbd, la maschera viene applicata e qualsiasi tentativo di mappare valori nello spazio
di memoria con le proprietà del sistema fallisce. In questo modo anche la proprietà ro.secure è trattata come
zero in quanto non leggibile, questo permette di avere una shell con privilegi di root via adb.
Di seguito il codice della parte centrale del psneuter (sorgente completo su [?, psneuter]:
3.2 SELinux e vulnerabilità
44

workspace = getenv("ANDROID_PROPERTY_WORKSPACE");
if(!workspace){
fprintf(stderr, "Couldn’t get workspace.\n");
exit(1);
}
fdStr = workspace;proprietà
if(strstr(workspace, ",")) {
*(strstr(workspace, ",")) = 0;
} else {
fprintf(stderr,
"Incorrect format of ANDROID_PROPERTY_WORKSPACE environment variable?\n");
exit(1);
}
szStr = fdStr + strlen(fdStr) + 1;
fd = atoi(fdStr);
sz = atol(szStr);
if((ppage = mmap(0, sz, PROT_READ, MAP_SHARED, fd, 0)) == MAP_FAILED) {
fprintf(stderr, "mmap() failed. %s\n", strerror(errno));
exit(1);
}
if(ioctl(fd, ASHMEM_SET_PROT_MASK, 0)) {
fprintf(stderr, "Failed to set prot mask (%s)\n", strerror(errno));
exit(1);
}
printf("property service neutered.\n");
printf("killing adbd. (should restart in a second or two)\n");

L’uso di SEAndroid non permette all’exploit di modificare la maschera di protezione e quindi l’attacco non
ha successo.

3.2.4 /proc pseudo filesystem

Il /proc pseudo filesystem mette a disposizione un’interfaccia per accedere a vari dati globali relativi allo
stato di sistema e di accedere alle informazioni dei processi. In particolare il file /proc/pid/mem rende
disponibile un’interfaccia a livello kernel per accedere alla memoria del processo con uno specifico PID.
In passato questa interfaccia è stata ristretta ad una modalità di accesso read-only se il processo che vuole
accedere a tali dati non è il processo legato a quello stesso PID, anche se si tratta di un processo con le stesse
credenziali DAC.
In seguito è stato aggiunto un supporto per avere accesso in scrittura ai files /proc/pid/mem, perché si riteneva
che non portasse a particolari conseguenze in termini di sicurezza. Ma è stata identificata una vulnerabilità
nel controllo dei permessi per /proc/pid/mem che può essere usata per indurre il programma setuid-root a
scrivere nella sua stessa memoria.

L’exploit mempodroid fa uso del programma run-as di Android avendo come target il programma setuid-
root. Poi invoca il programma setuid-root con un descrittore di file aperto su /proc/pid/mem come standard
error stream e passa lo shellcode come argomento. A questo punto run-as procede a sovrascrivere il suo
3.2 SELinux e vulnerabilità
45

eseguibile con il codice malizioso che setta UID/GID a 0 ed eseguie una shell o una stringa di comandi con
privilegi di root.

Usando SEAndroid la prima parte dell’attacco non viene rilevata ed ha successo: run-as non viene bloccato
e la sua area di memoria viene sovrascritta con il codice malizioso con l’UID di root. Ma l’attacco finisce
qui, poiché il contesto di sicurezza del programma run-as rimane quello del processo chiamante, senza
particolari privilegi.
Però il file run-as è stato sovrascritto e non può adempiere alle sue funzionalità come abilitare le modalità
di debug utilizzate, ad esempio, dagli sviluppatori in fase di sviluppo di applicazioni.
Quindi è necessario modificare la policy del programma run-as ed il programma run-as stesso in modo che
venga effettuato un passaggio al contesto di sicurezza corretto prima di eseguire un qualsiasi programma.
Con questa modifica l’exploit fallisce a sovrascrivere la memoria in cui è contenuto il programma run-as a
causa del check delle permission su file imposto da SELinux all’accesso sul file /proc/pid/mem.
La modifica alla policy per il programma run-as consiste nel garantire al programma solo le funzionalità da
super-user di cui necessita e cioè CAP_DAC_READ_SEARCH, CAP_SETUID e CAP_SETGID.
L’exploit non ha la possibilità di effettuare operazioni privilegiate o richiedere una qualsiasi funzionalità
come mandare dati ai devices, caricare moduli del kernel o rimontare partizioni.

SEAndroid risulta quindi una scelta vincente anche per mitigare le vulnerabilità di sistema conosciute e in
molti casi anche dai rischi derivanti da exploit di vulnerabilità non conosciute.
Per altri dettagli [Portare SELinux su Android].
47

Bibliografia

[SysV-IPC] http://www.kandroid.org/ndk/docs/system/libc/SYSV-IPC.html

[Intent] http://developer.android.com/guide/components/intents-filters.html

[AIDL] http://developer.android.com/guide/components/aidl.html

[Gargenta] https://thenewcircle.com/s/post/1392/Deep_Dive_Into_Binder_Presentation.
htm

[Nitay Artenstein, Idan Revivo] https://www.blackhat.com/eu-14/briefings.html#


man-in-the-binder-he-who-controls-ipc-controls-the-droid

[Shi, Gregg, Beatty, Ertl, Stack vs Register] https://www.usenix.org/legacy/events/vee05/full_


papers/p153-yunhe.pdf

[Dalvik Internals] http://sites.google.com/site/io/dalvik-vm-internals

[ART] http://www.anandtech.com/show/8231/a-closer-look-at-android-runtime-art-in-android-l/
2

[Garbage collection] http://en.wikipedia.org/wiki/Tracing_garbage_collection#Na.C3.


AFve_mark-and-sweep

[Mark and sweep] http://blogs.msdn.com/b/abhinaba/archive/2009/01/30/


back-to-basics-mark-and-sweep-garbage-collection.aspx

[Rosalloc] https://blog.newrelic.com/2014/07/07/android-art-vs-dalvik/

[Configuring ART] https://source.android.com/devices/tech/dalvik/configure.html

[Standard Names] http://docs.oracle.com/javase/7/docs/technotes/guides/security/


StandardNames.html

[Bouncy Castle] https://www.bouncycastle.org/java.html

[Spongy Castle] https://rtyley.github.io/spongycastle/

[Permessi su Android] http://developer.android.com/guide/topics/manifest/


permission-element.html

[init.rc di lollipop] https://android.googlesource.com/platform/system/core/+/


lollipop-release/rootdir/init.rc
BIBLIOGRAFIA
48

[Selinux: Access Vector Rules] http://selinuxproject.org/page/AVCRules

[Funzionamento di ashmem] http://yannik520.github.io/ashmem.html

[Funzionamento di vold] http://www.slideshare.net/wiliwe/android-storage-vold

[Portare SELinux su Android] http://www.internetsociety.org/sites/default/files/02_4.pdf

[SEAndroid e sicurezza] http://hsc.com/Blog/SEAndroid-SELinux-Making-Devices-More-Secure-A-Technology

[Sorgente di psneuter] https://sites.google.com/site/root4android/rooting/psneuter-c

Potrebbero piacerti anche