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 realizar 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 CFI em mais componentes e também no kernel. A CFI do sistema está ativada por padrão, mas você precisa habilitar a CFI do kernel.

O CFI do LLVM requer compilação com Link-Time Optimization (LTO) . O LTO preserva a representação de bitcode LLVM de arquivos de objeto até o momento do link, o que permite que o compilador raciocine melhor sobre quais otimizações podem ser executadas. A ativação do 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 uma sobrecarga insignificante no tamanho e no desempenho do código; em alguns casos, ambos melhoraram.

Para obter mais detalhes técnicos sobre CFI e como outras verificações de controle avançado 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. Oferecemos suporte a CFI na cadeia de ferramentas Clang e no sistema de compilação do Android no AOSP.

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

Implementando o 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 que você habilite o CFI para componentes adicionais. Os candidatos ideais são o código nativo privilegiado ou o código nativo que processa a entrada do 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 de blueprint.

Suportando CFI em makefiles

Para habilitar 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 desinfetante durante a compilaçã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. 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 CFI .

Suportando CFI em arquivos de blueprint

Para habilitar o 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 habilitando 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 .

Erros de incompatibilidade de tipo de função ocorrem porque o CFI restringe chamadas indiretas para saltar 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 saltar 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 qualquer uma dessas suposições, a instrumentação que o CFI adiciona será abortada. 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 dê aos wrappers a mesma assinatura de função que o ponteiro de chamada. O wrapper pode então chamar diretamente o código do assembly. Como as ramificações diretas não são instrumentadas pelo CFI (elas não podem ser redirecionadas 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 todas elas não puderem ser corrigidas, você também poderá 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.

Desativando CFI

Não observamos nenhuma sobrecarga de desempenho, portanto, você não precisa desabilitar o CFI. No entanto, se houver um impacto voltado para o usuário, você pode desativar seletivamente o CFI para funções individuais ou arquivos de origem fornecendo um arquivo de lista negra de limpeza em 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 oferece 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 Clang upstream .

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 habilitado para verificar se o CFI não está afetando o dispositivo.