A partire dal 2016, circa l'86% di tutte le vulnerabilità su Android sono legate alla sicurezza della memoria. La maggior parte delle vulnerabilità viene sfruttata dagli aggressori che modificano il normale flusso di controllo di un'applicazione per eseguire attività dannose arbitrarie con tutti i privilegi dell'applicazione sfruttata. L'integrità del flusso di controllo (CFI) è un meccanismo di sicurezza che non consente modifiche al grafico del flusso di controllo originale di un file binario compilato, rendendo notevolmente più difficile l'esecuzione di tali attacchi.
In Android 8.1, abbiamo abilitato l'implementazione di CFI da parte di LLVM nello stack multimediale. In Android 9, abbiamo abilitato CFI in più componenti e anche nel kernel. System CFI è attivo per impostazione predefinita, ma è necessario abilitare il kernel CFI.
Il CFI di LLVM richiede la compilazione con Link-Time Optimization (LTO) . LTO conserva la rappresentazione del codice bit LLVM dei file oggetto fino al momento del collegamento, il che consente al compilatore di ragionare meglio su quali ottimizzazioni possono essere eseguite. L'abilitazione di LTO riduce la dimensione del file binario finale e migliora le prestazioni, ma aumenta il tempo di compilazione. Nei test su Android, la combinazione di LTO e CFI comporta un sovraccarico trascurabile per le dimensioni e le prestazioni del codice; in alcuni casi entrambi sono migliorati.
Per ulteriori dettagli tecnici su CFI e su come vengono gestiti altri controlli forward-control, consultare la documentazione di progettazione LLVM .
Esempi e fonte
CFI è fornito dal compilatore e aggiunge la strumentazione nel binario durante la compilazione. Supportiamo CFI nella toolchain Clang e il sistema di compilazione Android in AOSP.
CFI è abilitato per impostazione predefinita per i dispositivi Arm64 per il set di componenti in /platform/build/target/product/cfi-common.mk
. È anche abilitato direttamente in un set di makefile/file blueprint dei componenti multimediali, ad esempio /platform/frameworks/av/media/libmedia/Android.bp
e /platform/frameworks/av/cmds/stagefright/Android.mk
.
Implementazione del sistema CFI
CFI è abilitato per impostazione predefinita se utilizzi Clang e il sistema di compilazione Android. Poiché CFI aiuta a proteggere gli utenti Android, non dovresti disabilitarlo.
In effetti, ti consigliamo vivamente di abilitare CFI per componenti aggiuntivi. I candidati ideali sono codice nativo privilegiato o codice nativo che elabora l'input utente non attendibile. Se stai usando clang e il sistema di compilazione Android, puoi abilitare CFI nei nuovi componenti aggiungendo alcune righe ai tuoi makefile o file blueprint.
Supporto CFI nei makefile
Per abilitare CFI in un file make, come /platform/frameworks/av/cmds/stagefright/Android.mk
, aggiungi:
LOCAL_SANITIZE := cfi # Optional features LOCAL_SANITIZE_DIAG := cfi LOCAL_SANITIZE_BLACKLIST := cfi_blacklist.txt
-
LOCAL_SANITIZE
specifica CFI come disinfettante durante la compilazione. -
LOCAL_SANITIZE_DIAG
attiva la modalità diagnostica per CFI. La modalità diagnostica stampa ulteriori informazioni di debug in logcat durante gli arresti anomali, il che è utile durante lo sviluppo e il test delle build. Tuttavia, assicurati di rimuovere la modalità diagnostica sulle build di produzione. -
LOCAL_SANITIZE_BLACKLIST
consente ai componenti di disabilitare in modo selettivo la strumentazione CFI per singole funzioni o file di origine. Puoi utilizzare una lista nera come ultima risorsa per risolvere eventuali problemi riscontrati dall'utente che potrebbero altrimenti esistere. Per ulteriori dettagli, vedere Disattivazione di CFI .
Supportare CFI nei file di progetto
Per abilitare CFI in un file blueprint, come /platform/frameworks/av/media/libmedia/Android.bp
, aggiungi:
sanitize: { cfi: true, diag: { cfi: true, }, blacklist: "cfi_blacklist.txt", },
Risoluzione dei problemi
Se stai abilitando CFI nei nuovi componenti, potresti riscontrare alcuni problemi con errori di mancata corrispondenza del tipo di funzione ed errori di mancata corrispondenza del tipo di codice assembly .
Gli errori di mancata corrispondenza del tipo di funzione si verificano perché CFI limita le chiamate indirette per passare solo alle funzioni che hanno lo stesso tipo dinamico del tipo statico utilizzato nella chiamata. CFI limita le chiamate di funzioni membro virtuali e non virtuali per passare solo agli oggetti che sono una classe derivata del tipo statico dell'oggetto utilizzato per effettuare la chiamata. Ciò significa che, quando si dispone di codice che viola uno di questi presupposti, la strumentazione aggiunta da CFI si interromperà. Ad esempio, la traccia dello stack mostra un SIGABRT e logcat contiene una riga sull'integrità del flusso di controllo che trova una mancata corrispondenza.
Per risolvere questo problema, assicurarsi che la funzione chiamata abbia lo stesso tipo dichiarato staticamente. Ecco due CL di esempio:
- Bluetooth : /c/platform/system/bt/+/532377
- NFC : /c/piattaforma/sistema/nfc/+/527858
Un altro possibile problema è il tentativo di abilitare CFI nel codice che contiene chiamate indirette all'assembly. Poiché il codice assembly non è tipizzato, ciò comporta una mancata corrispondenza del tipo.
Per risolvere questo problema, crea wrapper di codice nativo per ogni chiamata di assembly e assegna ai wrapper la stessa firma di funzione del puntatore chiamante. Il wrapper può quindi chiamare direttamente il codice assembly. Poiché i rami diretti non sono instrumentati da CFI (non possono essere reindirizzati in fase di esecuzione e quindi non rappresentano un rischio per la sicurezza), questo risolverà il problema.
Se ci sono troppe funzioni di assembly e non possono essere tutte corrette, puoi anche inserire nella blacklist tutte le funzioni che contengono chiamate indirette all'assembly. Questo non è raccomandato in quanto disabilita i controlli CFI su queste funzioni, aprendo così la superficie di attacco.
Disattivazione CFI
Non abbiamo osservato alcun sovraccarico di prestazioni, quindi non dovresti aver bisogno di disabilitare CFI. Tuttavia, se c'è un impatto rivolto all'utente, è possibile disabilitare selettivamente CFI per singole funzioni o file di origine fornendo un file della lista nera del disinfettante in fase di compilazione. La lista nera indica al compilatore di disabilitare la strumentazione CFI in posizioni specificate.
Il sistema di compilazione di Android fornisce il supporto per le blacklist per componente (consentendo di scegliere i file di origine o le singole funzioni che non riceveranno la strumentazione CFI) sia per Make che per Soong. Per ulteriori dettagli sul formato di un file di lista nera, vedere i documenti di Clang upstream .
Convalida
Attualmente non esistono test CTS specifici per CFI. Assicurati invece che i test CTS vengano superati con o senza CFI abilitato per verificare che CFI non abbia alcun impatto sul dispositivo.