Integrità del flusso di controllo

A partire dal 2016, circa l'86% di tutte le vulnerabilità su Android riguarda la sicurezza della memoria. La maggior parte delle vulnerabilità viene sfruttata dagli utenti malintenzionati modificando il normale flusso di controllo di un'app per eseguire attività dannose arbitrarie con tutti i privilegi dell'app 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 eseguire questi attacchi.

In Android 8.1 abbiamo attivato l'implementazione di CFI di LLVM nello stack multimediale. In Android 9 abbiamo attivato CFI in più componenti e anche nel kernel. Il controllo CFI di sistema è attivo per impostazione predefinita, ma devi attivare il controllo CFI del kernel.

Il CFI di LLVM richiede la compilazione con Ottimizzazione in fase di linking (LTO). LTO conserva la rappresentazione in bitcode di LLVM dei file oggetto fino al tempo di collegamento, il che consente al compilatore di ragionare meglio sulle ottimizzazioni che possono essere eseguite. L'attivazione 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 overhead trascurabile per le dimensioni e le prestazioni del codice; in alcuni casi, entrambi sono migliorati.

Per ulteriori dettagli tecnici sul controllo di CFI e su come vengono gestiti altri controlli di controllo in avanti, consulta la documentazione sul design di LLVM.

Esempi e origine

CFI viene fornito dal compilatore e aggiunge la misurazione al file binario durante il tempo di compilazione. Supportiamo CFI nella toolchain Clang e nel sistema di compilazione 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. Inoltre, è attivato direttamente in un insieme di file makefile/blueprint dei componenti multimediali, come /platform/frameworks/av/media/libmedia/Android.bp e /platform/frameworks/av/cmds/stagefright/Android.mk.

Implementazione del CFI di sistema

CFI è abilitato per impostazione predefinita se utilizzi Clang e il sistema di compilazione Android. Poiché CFI contribuisce a proteggere gli utenti Android, non dovresti disattivarlo.

Ti consigliamo vivamente di attivare il CFI per componenti aggiuntivi. I candidati ideali sono il codice nativo con privilegi o il codice nativo che elabora input utente non attendibili. Se utilizzi clang e il sistema di compilazione Android, puoi attivare il CFI nei nuovi componenti aggiungendo alcune righe ai file makefile o blueprint.

Supporto di CFI nei file make

Per attivare il CFI in un file make, ad esempio /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 strumento di sanificazione durante la compilazione.
  • LOCAL_SANITIZE_DIAG attiva la modalità di diagnostica per CFI. La modalità di 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à di diagnostica dalle build di produzione.
  • LOCAL_SANITIZE_BLACKLIST consente ai componenti di disattivare in modo selettivo la misurazione CFI per singole funzioni o file di origine. Puoi utilizzare una lista nera come ultima risorsa per risolvere eventuali problemi rivolti agli utenti che altrimenti potrebbero verificarsi. Per maggiori dettagli, consulta Disattivare CFI.

Supporto di CFI nei file blueprint

Per attivare il CFI in un file di blueprint, ad esempio /platform/frameworks/av/media/libmedia/Android.bp, aggiungi:

   sanitize: {
        cfi: true,
        diag: {
            cfi: true,
        },
        blacklist: "cfi_blacklist.txt",
    },

Risoluzione dei problemi

Se attivi il CFI nei nuovi componenti, potresti riscontrare alcuni problemi con gli errori di mancata corrispondenza del tipo di funzione e gli errori di mancata corrispondenza del tipo di codice assembly.

Gli errori di mancata corrispondenza del tipo di funzione si verificano perché il CFI limita le chiamate indirette solo per saltare 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 in modo che saltino solo agli oggetti che sono una classe derivata del tipo statico dell'oggetto utilizzato per effettuare la chiamata. Ciò significa che, se il codice viola una di queste ipotesi, la misurazione aggiunta da CFI verrà interrotta. Ad esempio, la traccia dello stack mostra un SIGABRT e logcat contiene una riga relativa all'integrità del flusso di controllo che ha rilevato una mancata corrispondenza.

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

Un altro possibile problema è provare ad attivare il CFI nel codice che contiene chiamate indirette all'assembly. Poiché il codice assembly non è tipizzato, si verifica una mancata corrispondenza di tipo.

Per risolvere il problema, crea wrapper di codice nativo per ogni chiamata all'assembly e assegna ai wrapper la stessa firma della funzione del puntatore di chiamata. 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), il problema verrà risolto.

Se le funzioni di assembly sono troppe e non possono essere tutte corrette, puoi anche inserire nella lista nera tutte le funzioni che contengono chiamate indirette all'assembly. Questa operazione non è consigliata perché disattiva i controlli CFI su queste funzioni, aprendo così la superficie di attacco.

Disattivazione del CFI

Non abbiamo rilevato alcun sovraccarico del rendimento, pertanto non dovresti dover disattivare la funzionalità CFI. Tuttavia, se l'impatto è rivolto agli utenti, puoi disattivare in modo selettivo il controllo dei flussi di controllo per singole funzioni o file di origine specificando un file della lista nera del programma di sanificazione in fase di compilazione. La lista nera indica al compilatore di disattivare l'instrumentazione CFI in posizioni specifiche.

Il sistema di compilazione di Android fornisce il supporto per le liste nere per componente (consentendo di scegliere i file di origine o le singole funzioni che non riceveranno l'instrumentazione CFI) sia per Make che per Soong. Per ulteriori dettagli sul formato di un file della lista nera, consulta la documentazione di Clang.

Convalida

Al momento non sono disponibili test CTS specifici per i CFI. Assicurati invece che i test CTS superino con o senza l'attivazione di CFI per verificare che CFI non influisca sul dispositivo.