Integridade do fluxo de controle

A partir de 2016, cerca de 86% de todas as vulnerabilidades no Android estão relacionadas à segurança da memória. A maioria das vulnerabilidades é explorada por invasores que alteram o fluxo de controle normal de um aplicativo para executar atividades maliciosas arbitrárias com todos os privilégios do aplicativo explorado. A integridade do fluxo de controle (CFI) é um mecanismo de segurança que não permite alterações no gráfico de fluxo de controle original de um binário compilado, dificultando significativamente a execução de tais ataques.

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

O CFI do LLVM requer compilação com Link-Time Optimization (LTO) . O LTO preserva a representação de código de bits LLVM dos arquivos de objeto até o tempo de vinculação, o que permite ao compilador raciocinar melhor sobre quais otimizações podem ser executadas. Habilitar o LTO reduz o tamanho do binário final e melhora o desempenho, mas aumenta o tempo de compilação. Em testes no Android, a combinação de LTO e CFI resulta em sobrecarga insignificante para tamanho e desempenho do código; em alguns casos, ambos melhoraram.

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

Exemplos e fonte

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

CFI é ativado 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 makefiles/blueprint dos componentes de mídia, como /platform/frameworks/av/media/libmedia/Android.bp e /platform/frameworks/av/cmds/stagefright/Android.mk .

Implementação do sistema CFI

O CFI é ativado por padrão se você usar o Clang e o sistema de compilação do Android. Como o CFI ajuda a manter os usuários do Android seguros, você não deve desativá-lo.

Na verdade, recomendamos fortemente que você habilite o CFI para componentes adicionais. Os candidatos ideais são código nativo privilegiado ou código nativo que processa entrada de usuário não confiável. Se você estiver usando o clang e o sistema de compilação do Android, poderá habilitar o CFI em novos componentes adicionando algumas linhas aos seus makefiles ou arquivos blueprint.

Suportando CFI em makefiles

Para ativar o 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 CFI como o higienizador durante a construção.
  • 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 suas compilações. No entanto, certifique-se de remover o modo de diagnóstico nas compilações de produção.
  • LOCAL_SANITIZE_BLACKLIST permite que os componentes desativem seletivamente a instrumentação CFI para funções individuais ou arquivos de origem. Você pode usar uma lista negra como último recurso para corrigir quaisquer problemas enfrentados pelo usuário que possam existir. Para obter mais detalhes, consulte Desativando o CFI .

Compatível com CFI em arquivos de projeto

Para habilitar o 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 habilitando CFI em novos componentes, poderá ter alguns problemas com erros de incompatibilidade de tipo de função e erros de incompatibilidade de tipo de código de montagem .

Ocorrem erros de incompatibilidade de tipo de função porque o CFI restringe chamadas indiretas para pular apenas para funções que tenham 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ê tiver um código que viole qualquer uma dessas suposições, a instrumentação adicionada pelo 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 encontrando uma incompatibilidade.

Para corrigir isso, certifique-se de que a função chamada tenha o mesmo tipo que foi declarado estaticamente. Aqui estão dois exemplos de CLs:

Outro possível problema é tentar habilitar o CFI no código que contém chamadas indiretas para assembly. Como o código 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 forneça aos wrappers a mesma assinatura de função que o ponteiro de chamada. O wrapper pode então chamar diretamente o código assembly. Como as ramificações diretas não são instrumentadas por CFI (elas não podem ser realocadas em tempo de execução e, portanto, não representam um risco de segurança), isso corrigirá o problema.

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

Desabilitando CFI

Não observamos nenhuma sobrecarga de desempenho, portanto, você não precisa desativar o CFI. No entanto, se houver um impacto voltado para o usuário, você poderá desabilitar seletivamente o CFI para funções individuais ou arquivos de origem, fornecendo um arquivo de lista negra do higienizador no tempo de compilação. A lista negra instrui o compilador a desabilitar a instrumentação CFI em locais especificados.

O sistema de compilação do Android fornece suporte para listas negras por componente (permitindo que você escolha arquivos de origem ou funções individuais que não receberão instrumentação CFI) para Make e Soong. Para obter mais detalhes sobre o formato de um arquivo de lista negra, consulte os documentos upstream do Clang .

Validação

Atualmente, não há teste CTS especificamente para CFI. Em vez disso, certifique-se de que os testes CTS sejam aprovados com ou sem CFI ativado para verificar se o CFI não está afetando o dispositivo.