Controllare l'integrità del flusso

Nel 2016, circa l'86% di tutte le vulnerabilità su Android sono legate alla sicurezza della memoria. La maggior parte delle vulnerabilità vengono sfruttate 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 binario compilato, rendendo molto più difficile eseguire 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. Il CFI di sistema è attivo per impostazione predefinita ma è necessario abilitare il CFI del kernel.

Il CFI di LLVM richiede la compilazione con Link-Time Optimization (LTO) . LTO preserva la rappresentazione del codice bit LLVM dei file oggetto fino al momento del collegamento, consentendo 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 in termini di dimensioni e prestazioni del codice; in alcuni casi entrambi sono migliorati.

Per ulteriori dettagli tecnici sul CFI e su come vengono gestiti gli altri controlli avanzati, consultare la documentazione di progettazione LLVM .

Esempi e fonte

CFI viene fornito dal compilatore e aggiunge la strumentazione al file binario durante la fase di compilazione. Supportiamo CFI nella toolchain Clang e nel sistema di build 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 una serie di file makefile/blueprint dei componenti multimediali, come /platform/frameworks/av/media/libmedia/Android.bp e /platform/frameworks/av/cmds/stagefright/Android.mk .

Sistema di attuazione CFI

CFI è abilitato per impostazione predefinita se utilizzi Clang e il sistema di build 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 il codice nativo privilegiato o il codice nativo che elabora l'input dell'utente non attendibile. Se utilizzi clang e il sistema di compilazione Android, puoi abilitare CFI nei nuovi componenti aggiungendo alcune righe ai tuoi makefile o file blueprint.

Supporto di CFI nei makefile

Per abilitare CFI in un file make, ad esempio /platform/frameworks/av/cmds/stagefright/Android.mk , aggiungere:

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. Assicurati però di rimuovere la modalità diagnostica sulle build di produzione.
  • LOCAL_SANITIZE_BLACKLIST consente ai componenti di disabilitare selettivamente la strumentazione CFI per singole funzioni o file di origine. Puoi utilizzare una lista nera come ultima risorsa per risolvere eventuali problemi riscontrati dagli utenti che potrebbero altrimenti esistere. Per ulteriori dettagli, vedere Disabilitazione di CFI .

Supporto di CFI nei file di progetto

Per abilitare CFI in un file blueprint, come /platform/frameworks/av/media/libmedia/Android.bp , aggiungere:

   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 a passare solo alle funzioni che hanno lo stesso tipo dinamico del tipo statico utilizzato nella chiamata. CFI limita le chiamate alle 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 verrà interrotta. Ad esempio, l'analisi dello stack mostra un SIGABRT e logcat contiene una riga sull'integrità del flusso di controllo che rileva una mancata corrispondenza.

Per risolvere questo problema, assicurati che la funzione chiamata abbia lo stesso tipo dichiarato staticamente. Ecco due CL di esempio:

Un altro possibile problema è provare ad abilitare CFI nel codice che contiene chiamate indirette all'assembly. Poiché il codice assembly non viene digitato, 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 dotati di strumenti da CFI (non possono essere reindirizzati in fase di esecuzione e quindi non rappresentano un rischio per la sicurezza), questo risolverà il problema.

Se sono presenti troppe funzioni di assembly e non è possibile correggerle tutte, è anche possibile inserire nella lista nera tutte le funzioni che contengono chiamate indirette all'assembly. Questa operazione non è consigliata poiché disabilita i controlli CFI su queste funzioni, aprendo così una superficie di attacco.

Disabilitazione del CFI

Non abbiamo osservato alcun sovraccarico delle prestazioni, quindi non dovrebbe essere necessario disabilitare CFI. Tuttavia, se si verifica un impatto a carico dell'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 nelle posizioni specificate.

Il sistema di build Android fornisce supporto per liste nere per componente (che consentono di scegliere file sorgente o singole funzioni che non riceveranno la strumentazione CFI) sia per Make che per Soong. Per maggiori dettagli sul formato di un file di lista nera, vedere la documentazione Clang upstream .

Validazione

Attualmente non esistono test CTS specifici per CFI. Assicurati invece che i test CTS superino con o senza CFI abilitato per verificare che CFI non influisca sul dispositivo.