Integrità del flusso di controllo

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 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 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 le dimensioni 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 di controllo avanzato, vedere la documentazione di progettazione LLVM .

Esempi e fonte

CFI è fornito dal compilatore e aggiunge la strumentazione al file binario durante la fase di compilazione. Supportiamo CFI nella toolchain Clang e il sistema di build Android in AOSP.

CFI è abilitato per impostazione predefinita per i dispositivi Arm64 per l'insieme di componenti in /platform/build/target/product/cfi-common.mk . È anche abilitato direttamente in un set di makefile/file 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 stai usando clang e il sistema di build Android, puoi abilitare CFI in nuovi componenti aggiungendo alcune righe ai tuoi makefile o blueprint.

Supportare 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. Assicurati di rimuovere la modalità diagnostica sulle build di produzione, però.
  • 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 maggiori dettagli, vedere Disabilitazione CFI .

Supporto CFI nei file blueprint

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 in 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 di funzione membro virtuali e non virtuali a 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, 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, assicurati che la funzione chiamata abbia lo stesso tipo che è stato dichiarato staticamente. Ecco due esempi di CL:

Un altro possibile problema sta tentando di abilitare CFI nel codice che contiene chiamate indirette all'assembly. Poiché il codice assembly non è 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 pointer chiamante. Il wrapper può quindi chiamare direttamente il codice assembly. Poiché le filiali dirette non sono strumentate da CFI (non possono essere reindirizzate 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 possono essere risolte tutte, 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.

Disabilitazione CFI

Non abbiamo osservato alcun sovraccarico delle prestazioni, quindi non dovrebbe essere necessario disabilitare CFI. Tuttavia, se si verifica un impatto sull'utente, è possibile disabilitare in modo selettivo CFI per singole funzioni o file di origine fornendo un file di blacklist disinfettante in fase di compilazione. La lista nera indica al compilatore di disabilitare la strumentazione CFI in posizioni specificate.

Il sistema di build Android fornisce supporto per blacklist per componente (consentendo di scegliere file di origine o singole funzioni che non riceveranno la strumentazione CFI) sia per Make che per Soong. Per maggiori dettagli sul formato di un file della lista nera, vedere i documenti Clang a monte .

Convalida

Attualmente, non ci sono 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.