Em 2016, cerca de 86% de todas as vulnerabilidades no Android estavam relacionadas à segurança de memória. A maioria das vulnerabilidades é explorada por invasores que mudam o fluxo de controle normal de um app para realizar atividades maliciosas arbitrárias com todos os privilégios do app explorado. A Integridade do fluxo de controle (CFI, na sigla em inglês) é um mecanismo de segurança que impede alterações no gráfico de fluxo de controle original de um binário compilado, tornando-o significativamente mais resistente a tais ataques.
No Android 8.1, ativamos a implementação da CFI do LLVM na pilha de mídia. No Android 9, ativamos a CFI em mais componentes e também no kernel. A CFI do sistema é ativada por padrão, mas é necessário ativar a CFI do kernel.
A CFI do LLVM exige a compilação com a otimização de tempo de vinculação (LTO). A LTO preserva a representação de bitcode do LLVM de arquivos de objeto até o tempo de vinculação, o que permite que o compilador raciocine melhor sobre quais otimizações podem ser realizadas. A ativação da LTO reduz o tamanho do binário final e melhora a performance, mas aumenta o tempo de compilação. Nos testes no Android, a combinação de LTO e CFI resulta em uma sobrecarga insignificante no tamanho e na performance do código. Em alguns casos, ambos melhoraram.
Para mais detalhes técnicos sobre a CFI e como outras verificações de controle direto são processadas, consulte a documentação de design do LLVM.
Exemplos e origem
A CFI é fornecida pelo compilador e adiciona instrumentação ao binário durante o tempo de compilação. Oferecemos suporte à CFI na cadeia de ferramentas Clang e no sistema de build do Android na AOSP.
A CFI é ativada por padrão para dispositivos Arm64 para o conjunto de componentes em
/platform/build/target/product/cfi-common.mk.
Ela também é ativada diretamente em um conjunto de arquivos make/blueprint
de componentes de mídia, como /platform/frameworks/av/media/libmedia/Android.bp
e /platform/frameworks/av/cmds/stagefright/Android.mk.
Implementar a CFI do sistema
A CFI é ativada por padrão se você usar o Clang e o sistema de build do Android. Como a CFI ajuda a manter os usuários do Android seguros, não a desative.
Na verdade, recomendamos que você ative a CFI para outros componentes. Os candidatos ideais são códigos nativos privilegiados ou códigos nativos que processam a entrada do usuário não confiável. Se você estiver usando o clang e o sistema de build do Android, poderá ativar a CFI em novos componentes adicionando algumas linhas aos arquivos make ou blueprint.
Oferecer suporte à CFI em arquivos make
Para ativar a CFI em um arquivo make, como /platform/frameworks/av/cmds/stagefright/Android.mk,
adicione:
LOCAL_SANITIZE := cfi # Optional features LOCAL_SANITIZE_DIAG := cfi LOCAL_SANITIZE_BLACKLIST := cfi_blacklist.txt
LOCAL_SANITIZEespecifica a CFI como o higienizador durante o build.LOCAL_SANITIZE_DIAGativa o modo de diagnóstico para a CFI. O modo de diagnóstico imprime informações de depuração adicionais no logcat durante falhas, o que é útil ao desenvolver e testar builds. No entanto, remova o modo de diagnóstico nos builds de produção.LOCAL_SANITIZE_BLACKLISTpermite que os componentes desativem seletivamente a instrumentação da CFI para funções ou arquivos de origem individuais. Você pode usar uma lista de bloqueio como último recurso para corrigir problemas do usuário que possam existir. Para mais detalhes, consulte Desativar a CFI.
Oferecer suporte à CFI em arquivos blueprint
Para ativar a CFI em um arquivo blueprint, como /platform/frameworks/av/media/libmedia/Android.bp,
adicione:
sanitize: {
cfi: true,
diag: {
cfi: true,
},
blacklist: "cfi_blacklist.txt",
},Solução de problemas
Se você estiver ativando a CFI em novos componentes, poderá encontrar alguns problemas com erros de incompatibilidade de tipo de função e erros de incompatibilidade de tipo de código de assembly.
Os erros de incompatibilidade de tipo de função ocorrem porque a CFI restringe chamadas indiretas para pular apenas para funções que têm o mesmo tipo dinâmico que o tipo estático usado na chamada. A CFI restringe chamadas de função de membro virtual e não virtual para pular apenas para objetos que são uma classe derivada do tipo estático do objeto usado para fazer a chamada. Isso significa que, quando você tem um código que viola uma dessas suposições, a instrumentação adicionada pela CFI será interrompida. Por exemplo, o rastreamento de pilha mostra um SIGABRT e o logcat contém uma linha sobre a integridade do fluxo de controle que encontra uma incompatibilidade.
Para corrigir isso, verifique se a função chamada tem o mesmo tipo que foi declarado estaticamente. Confira dois exemplos de CLs:
- Bluetooth: /c/platform/system/bt/+/532377
- NFC: /c/platform/system/nfc/+/527858
Outro problema possível é tentar ativar a CFI em um código que contém chamadas indiretas para assembly. Como o código de assembly não é digitado, isso resulta em uma incompatibilidade de tipo.
Para corrigir isso, crie wrappers de código nativo para cada chamada de assembly e atribua aos wrappers a mesma assinatura de função do ponteiro de chamada. O wrapper pode chamar diretamente o código de assembly. Como as ramificações diretas não são instrumentadas pela CFI (elas não podem ser repontuadas no tempo de execução e, portanto, não representam um risco de segurança), isso vai corrigir o problema.
Se houver muitas funções de assembly e elas não puderem ser corrigidas, você também poderá colocar na lista de bloqueio todas as funções que contêm chamadas indiretas para assembly. Isso não é recomendado, porque desativa as verificações de CFI nessas funções, abrindo assim a superfície de ataque.
Desativar a CFI
Não observamos nenhuma sobrecarga de performance, então não é necessário desativar a CFI. No entanto, se houver um impacto no usuário, você poderá desativar seletivamente a CFI para funções ou arquivos de origem individuais fornecendo um arquivo de lista de bloqueio do higienizador no tempo de compilação. A lista de bloqueio instrui o compilador a desativar a instrumentação da CFI em locais especificados.
O sistema de build do Android oferece suporte a listas de bloqueio por componente (permitindo que você escolha arquivos de origem ou funções individuais que não receberão instrumentação de CFI) para Make e Soong. Para mais detalhes sobre o formato de um arquivo de lista de bloqueio, consulte os documentos do Clang upstream.
Validação
No momento, não há testes do CTS especificamente para a CFI. Em vez disso, verifique se os testes do CTS são aprovados com ou sem a CFI ativada para verificar se a CFI não está afetando o dispositivo.