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, na sigla em inglês) é um mecanismo de segurança que impede mudanças no gráfico de fluxo de controle original de um binário compilado, tornando-o significativamente mais resistente a esses ataques.

No Android 8.1, ativamos a implementação de 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 está ativada por padrão, mas você precisa ativar a CFI do kernel.

A CFI do LLVM exige a compilação com otimização no momento de vinculação (LTO). O LTO preserva a representação de bitcode LLVM de arquivos de objeto até o momento da vinculação, o que permite que o compilador pense melhor sobre quais otimizações podem ser realizadas. Ativar o 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, houve melhorias em ambos.

Para mais detalhes técnicos sobre o CFI e como outras verificações de controle de encaminhamento são processadas, consulte a documentação de design do LLVM.

Exemplos e origem

O CFI é fornecido pelo compilador e adiciona instrumentação ao binário durante o tempo de compilação. Oferecemos suporte ao CFI na cadeia de ferramentas Clang e no sistema de build do Android no AOSP.

A CFI é ativada por padrão para dispositivos Arm64 para o conjunto de componentes em /platform/build/target/product/cfi-common.mk. Ele também é ativado diretamente em um conjunto de arquivos de modelo/makefiles de componentes de mídia, como /platform/frameworks/av/media/libmedia/Android.bp e /platform/frameworks/av/cmds/stagefright/Android.mk.

Como implementar o CFI do sistema

O CFI é ativado 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 desative essa configuração.

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 entradas de usuários não confiáveis. Se você estiver usando o clang e o sistema de build do Android, é possível ativar a CFI em novos componentes adicionando algumas linhas aos seus arquivos de make ou de modelo.

Suporte a CFI em arquivos de make

Para ativar o CFI em um arquivo de 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 limpador durante o build.
  • LOCAL_SANITIZE_DIAG ativa o modo de diagnóstico para CFI. O modo de diagnóstico mostra outras informações de depuração no logcat durante falhas, o que é útil durante o desenvolvimento e teste dos seus builds. No entanto, remova o modo de diagnóstico nos builds de produção.
  • LOCAL_SANITIZE_BLACKLIST permite que os componentes desativem seletivamente o 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 do usuário que possam existir. Para mais detalhes, consulte Como desativar o CFI.

Suporte a CFI em arquivos de blueprint

Para ativar o CFI em um arquivo de modelo, 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ê ativar 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 montagem.

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 as chamadas de função de 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 suposições, a instrumentação adicionada pelo CFI é interrompida. Por exemplo, o stack trace mostra um SIGABRT, e o logcat contém uma linha sobre a integridade do fluxo de controle encontrando uma incompatibilidade.

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

Outro problema possível é tentar ativar o CFI em um código que contém chamadas indiretas para o assembly. Como o código de montagem não é digitado, isso resulta em um tipo de incompatibilidade.

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 filiais diretas não são instrumentadas pelo CFI (elas não podem ser redirecionadas no momento da execução e, portanto, não representam um risco de segurança), isso vai corrigir o problema.

Se houver muitas funções de montagem e elas não puderem ser corrigidas, você também pode colocar na lista de bloqueio todas as funções que contêm chamadas indiretas para a montagem. Isso não é recomendado, porque desativa as verificações de CFI nessas funções, abrindo a superfície de ataque.

Como desativar o CFI

Não observamos nenhuma sobrecarga de desempenho, então não é necessário desativar o CFI. No entanto, se houver um impacto para o usuário, você poderá desativar seletivamente o CFI para funções ou arquivos de origem individuais fornecendo um arquivo de lista negra de desinfetantes no momento da compilação. A lista negra instrui o compilador a desativar a instrumentação 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 a instrumentação CFI para Make e Soong. Para mais detalhes sobre o formato de um arquivo de lista negra, consulte as documentações upstream do Clang.

Validação

No momento, não há um teste CTS específico para CFI. Em vez disso, verifique se os testes do CTS são aprovados com ou sem o CFI ativado para verificar se o CFI não está afetando o dispositivo.