Integridade do fluxo de controle

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) é um mecanismo de segurança que impede mudanças no gráfico de fluxo de controle original de um binário compilado, tornando significativamente mais difícil realizar esses ataques.

No Android 8.1, ativamos a implementação da CFI pelo LLVM na pilha de mídia. No Android 9, habilitamos o CFI em mais componentes e também no kernel. O CFI do sistema fica ativado por padrão, mas você precisa ativar o CFI do kernel.

A CFI do LLVM exige compilação com otimização no tempo de vinculação (LTO, na sigla em inglês). A LTO preserva a representação de bitcode 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. Ativar a LTO reduz o tamanho do binário final e melhora o desempenho, mas aumenta o tempo de compilação. Nos testes no Android, a combinação de LTO e CFI resulta em uma sobrecarga insignificante para o tamanho e o desempenho do código. Em alguns casos, ambos foram melhorados.

Para mais detalhes técnicos sobre CFI e como outras verificações de controle de encaminhamento 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 no AOSP.

A CFI é ativada por padrão para dispositivos Arm64 no conjunto de componentes em /platform/build/target/product/cfi-common.mk. Ele também é ativado diretamente em um conjunto de arquivos makefile/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ê usa o Clang e o sistema de build do Android. Como a CFI ajuda a proteger os usuários do Android, não desative essa função.

Na verdade, recomendamos ativar a CFI para outros componentes. Os candidatos ideais são código nativo privilegiado ou código nativo que processa entradas de usuários não confiáveis. 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 makefiles ou blueprint.

Compatibilidade com CFI em makefiles

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_SANITIZE especifica o CFI como o higienizador durante o build.
  • LOCAL_SANITIZE_DIAG ativa o modo de diagnóstico para CFI. O modo de diagnóstico imprime informações de depuração adicionais no logcat durante falhas, o que é útil ao desenvolver e testar seus builds. Não se esqueça de remover o modo de diagnóstico nas versões de produção.
  • O LOCAL_SANITIZE_BLACKLIST permite que os componentes desativem seletivamente a instrumentação de CFI para funções ou arquivos de origem individuais. Você pode usar uma lista de bloqueio como último recurso para corrigir problemas que possam existir. Para mais detalhes, consulte Como desativar a CFI.

Suporte a CFI em arquivos de blueprint

Para ativar a CFI em um arquivo de 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 as chamadas indiretas 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 membro virtuais e não virtuais 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 premissas, a instrumentação adicionada pela CFI é interrompida. Por exemplo, o rastreamento de pilha mostra um SIGABRT, e o logcat contém uma linha sobre o fluxo de controle integridade encontrando uma incompatibilidade.

Para corrigir isso, verifique se a função chamada tem o mesmo tipo que foi declarado estaticamente. Confira dois exemplos de CLs:

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 dê aos wrappers a mesma assinatura de função do ponteiro de chamada. Em seguida, o wrapper pode chamar diretamente o código de assembly. Como os ramais diretos não são instrumentados pelo CFI (não podem ser redirecionados 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 não for possível corrigir todas, 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.

Como desativar a CFI

Não observamos nenhuma sobrecarga de desempenho, 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 uma lista negra de sanitização no momento da compilação. A lista de bloqueio instrui o compilador a desativar a instrumentação de CFI em locais especificados.

O sistema de build do Android oferece suporte a listas negras por componente (permitindo escolher arquivos de origem ou funções individuais que não vão receber instrumentação CFI) para Make e Soong. Para mais detalhes sobre o formato de um arquivo de lista de bloqueio, consulte a documentação do Clang upstream (em inglês).

Validação

No momento, não há testes do CTS especificamente para CFI. Em vez disso, verifique se os testes do CTS são aprovados com ou sem a CFI ativada para confirmar que ela não está afetando o dispositivo.