Extensão de marcação de memória do braço

Arm v9 apresenta Arm Memory Tagging Extension (MTE), uma implementação de hardware de memória marcada.

Em um nível alto, o MTE marca cada alocação/desalocação de memória com metadados adicionais. Ele atribui uma tag a um local de memória, que pode então ser associado a ponteiros que fazem referência a esse local de memória. Em tempo de execução, a CPU verifica se o ponteiro e as tags de metadados correspondem em cada carregamento e armazenamento.

No Android 12, o alocador de memória heap do kernel e do espaço do usuário pode aumentar cada alocação com metadados. Isso ajuda a detectar bugs de uso após liberação e buffer overflow, que são a fonte mais comum de bugs de segurança de memória em nossas bases de código.

Modos de operação MTE

O MTE possui três modos de operação:

  • Modo síncrono (SYNC)
  • Modo assíncrono (ASYNC)
  • Modo assimétrico (ASYMM)

Modo síncrono (SYNC)

Este modo é otimizado para a correção da detecção de bugs em relação ao desempenho e pode ser usado como uma ferramenta precisa de detecção de bugs, quando uma sobrecarga de desempenho mais alta é aceitável. Quando ativado, o MTE SYNC atua como uma mitigação de segurança. Em caso de incompatibilidade de tag, o processador aborta a execução imediatamente e finaliza o processo com SIGSEGV (código SEGV_MTESERR ) e informações completas sobre o acesso à memória e o endereço com falha.

Recomendamos usar este modo durante os testes como uma alternativa ao HWASan/KASAN ou na produção quando o processo alvo representa uma superfície de ataque vulnerável. Além disso, quando o modo ASYNC indica a presença de um bug, um relatório de bug preciso pode ser obtido usando as APIs de tempo de execução para alternar a execução para o modo SYNC.

Ao executar no modo SYNC, o alocador do Android registra rastreamentos de pilha para todas as alocações e desalocações e os utiliza para fornecer relatórios de erros melhores que incluem uma explicação de um erro de memória, como uso após liberação ou estouro de buffer, e a pilha vestígios dos eventos de memória relevantes. Esses relatórios fornecem mais informações contextuais e facilitam o rastreamento e a correção de bugs.

Modo assíncrono (ASYNC)

Este modo é otimizado para desempenho em relação à precisão de relatórios de bugs e pode ser usado como detecção de baixa sobrecarga para bugs de segurança de memória.
Em uma incompatibilidade de tag, o processador continua a execução até a entrada do kernel mais próxima (por exemplo, um syscall ou interrupção de timer), onde finaliza o processo com SIGSEGV (código SEGV_MTEAERR ) sem registrar o endereço com falha ou acesso à memória.
Recomendamos usar este modo em produção em bases de código bem testadas, onde a densidade de bugs de segurança de memória é baixa, o que é conseguido usando o modo SYNC durante o teste.

Modo assimétrico (ASYMM)

Um recurso adicional no Arm v8.7-A, o modo MTE assimétrico fornece verificação síncrona de leituras de memória e verificação assíncrona de gravações de memória, com desempenho semelhante ao do modo ASYNC. Na maioria das situações, este modo é uma melhoria em relação ao modo ASYNC e recomendamos usá-lo em vez de ASYNC sempre que estiver disponível.

Por esse motivo, nenhuma das APIs descritas abaixo menciona o modo Assimétrico. Em vez disso, o sistema operacional pode ser configurado para sempre usar o modo Assíncrono quando o Assíncrono for solicitado. Consulte a seção "Configurando o nível MTE preferencial específico da CPU" para obter mais informações.

MTE no espaço do usuário

As seções a seguir descrevem como o MTE pode ser habilitado para processos e aplicativos do sistema. O MTE está desabilitado por padrão, a menos que uma das opções abaixo esteja definida para um processo específico (veja abaixo para quais componentes o MTE está habilitado).

Ativando o MTE usando o sistema de compilação

Como uma propriedade de todo o processo, o MTE é controlado pela configuração de tempo de construção do executável principal. As opções a seguir permitem alterar essa configuração para executáveis ​​individuais ou para subdiretórios inteiros na árvore de origem. A configuração é ignorada em bibliotecas ou em qualquer destino que não seja executável nem teste.

1. Habilitando MTE em Android.bp ( exemplo ), para um projeto específico:

Modo MTE Contexto
MTE assíncrono
  sanitize: {
  memtag_heap: true,
  }
MTE síncrono
  sanitize: {
  memtag_heap: true,
  diag: {
  memtag_heap: true,
  },
  }

ou em Android.mk:

Modo MTE Contexto
Asynchronous MTE LOCAL_SANITIZE := memtag_heap
Synchronous MTE LOCAL_SANITIZE := memtag_heap
LOCAL_SANITIZE_DIAG := memtag_heap

2. Ativando o MTE em um subdiretório na árvore de origem usando uma variável de produto:

Modo MTE Incluir lista Excluir lista
assíncrono PRODUCT_MEMTAG_HEAP_ASYNC_INCLUDE_PATHS MEMTAG_HEAP_ASYNC_INCLUDE_PATHS PRODUCT_MEMTAG_HEAP_EXCLUDE_PATHS MEMTAG_HEAP_EXCLUDE_PATHS
sincronizar PRODUCT_MEMTAG_HEAP_SYNC_INCLUDE_PATHS MEMTAG_HEAP_SYNC_INCLUDE_PATHS

ou

Modo MTE Contexto
MTE assíncrono MEMTAG_HEAP_ASYNC_INCLUDE_PATHS
MTE síncrono MEMTAG_HEAP_SYNC_INCLUDE_PATHS

ou especificando o caminho de exclusão de um executável:

Modo MTE Contexto
MTE assíncrono PRODUCT_MEMTAG_HEAP_EXCLUDE_PATHS MEMTAG_HEAP_EXCLUDE_PATHS
MTE síncrono

Exemplo, (uso semelhante a PRODUCT_CFI_INCLUDE_PATHS )

  RODUCT_MEMTAG_HEAP_SYNC_INCLUDE_PATHS=vendor/$(vendor)
  PRODUCT_MEMTAG_HEAP_EXCLUDE_PATHS=vendor/$(vendor)/projectA \
                                    vendor/$(vendor)/projectB

Ativando o MTE usando propriedades do sistema

As configurações de compilação acima podem ser substituídas em tempo de execução, definindo a seguinte propriedade do sistema:

arm64.memtag.process.<basename> = (off|sync|async)

Onde basename representa o nome base do executável.

Por exemplo, para definir /system/bin/ping ou /data/local/tmp/ping para usar MTE assíncrono, use adb shell setprop arm64.memtag.process.ping async .

Ativando o MTE usando uma variável de ambiente

Mais uma maneira de substituir a configuração de compilação é definindo a variável de ambiente: MEMTAG_OPTIONS=(off|sync|async) Se a variável de ambiente e a propriedade do sistema forem definidas, a variável terá precedência.

Ativando MTE para aplicativos

Se não for especificado, o MTE é desativado por padrão, mas os aplicativos que desejam usar o MTE podem fazê-lo definindo android:memtagMode na tag <application> ou <process> no AndroidManifest.xml .

android:memtagMode=(off|default|sync|async)

Quando definido na tag <application> , o atributo afeta todos os processos usados ​​pelo aplicativo e pode ser substituído por processos individuais definindo a tag <process> .

Para experimentação, as alterações de compatibilidade podem ser usadas para definir o valor padrão do atributo memtagMode para um aplicativo que não especifica nenhum valor no manifesto (ou especifica default ).
Eles podem ser encontrados em System > Advanced > Developer options > App Compatibility Changes no menu de configuração global. Definir NATIVE_MEMTAG_ASYNC ou NATIVE_MEMTAG_SYNC ativa o MTE para um aplicativo específico.
Alternativamente, isso pode ser definido usando o comando am da seguinte forma:

$ adb shell am compat enable NATIVE_MEMTAG_[A]SYNC my.app.name

Construindo uma imagem do sistema MTE

Recomendamos fortemente ativar o MTE em todos os binários nativos durante o desenvolvimento e a inicialização. Isso ajuda a detectar bugs de segurança de memória antecipadamente e fornece cobertura realista ao usuário, se habilitado em compilações de teste.

Recomendamos fortemente ativar o MTE no modo síncrono em todos os binários nativos durante o desenvolvimento

SANITIZE_TARGET=memtag_heap SANITIZE_TARGET_DIAG=memtag_heap m

Como acontece com qualquer variável no sistema de compilação, SANITIZE_TARGET pode ser usado como uma variável de ambiente ou uma configuração make (por exemplo, em um arquivo product.mk ).
Observe que isso habilita o MTE para todos os processos nativos, mas não para aplicativos (que são bifurcados de zygote64 ) para os quais o MTE pode ser habilitado seguindo as instruções acima .

Configurando o nível MTE preferencial específico da CPU

Em algumas CPUs, o desempenho do MTE nos modos ASYMM ou mesmo SYNC pode ser semelhante ao do ASYNC. Isto faz com que valha a pena permitir verificações mais rigorosas nessas CPUs quando um modo de verificação menos rigoroso é solicitado, a fim de obter os benefícios de detecção de erros das verificações mais rigorosas sem as desvantagens de desempenho.
Por padrão, os processos configurados para execução no modo ASYNC serão executados no modo ASYNC em todas as CPUs. Para configurar o kernel para executar esses processos no modo SYNC em CPUs específicas, o valor sync deve ser gravado na entrada sysfs /sys/devices/system/cpu/cpu<N>/mte_tcf_preferred no momento da inicialização. Isso pode ser feito com um script de inicialização. Por exemplo, para configurar as CPUs 0-1 para executar processos no modo ASYNC no modo SYNC e as CPUs 2-3 para usar a execução no modo ASYMM, o seguinte pode ser adicionado à cláusula init de um script de inicialização do fornecedor:

  write /sys/devices/system/cpu/cpu0/mte_tcf_preferred sync
  write /sys/devices/system/cpu/cpu1/mte_tcf_preferred sync
  write /sys/devices/system/cpu/cpu2/mte_tcf_preferred asymm
  write /sys/devices/system/cpu/cpu3/mte_tcf_preferred asymm

As lápides dos processos do modo ASYNC em execução no modo SYNC conterão um rastreamento de pilha preciso do local do erro de memória. No entanto, eles não incluirão um rastreamento de pilha de alocação ou desalocação. Esses rastreamentos de pilha só estarão disponíveis se o processo estiver configurado para ser executado no modo SYNC.

int mallopt(M_THREAD_DISABLE_MEM_INIT, level)

onde level é 0 ou 1.
Desativa a inicialização da memória no malloc e evita alterar tags de memória, a menos que seja necessário para correção.

int mallopt(M_MEMTAG_TUNING, level)

onde level é:

  • M_MEMTAG_TUNING_BUFFER_OVERFLOW
  • M_MEMTAG_TUNING_UAF

Seleciona a estratégia de alocação de tags.

  • A configuração padrão é M_MEMTAG_TUNING_BUFFER_OVERFLOW .
  • M_MEMTAG_TUNING_BUFFER_OVERFLOW - permite a detecção determinística de erros de buffer overflow e underflow lineares, atribuindo valores de tag distintos a alocações adjacentes. Este modo tem uma chance ligeiramente reduzida de detectar bugs de uso após liberação porque apenas metade dos valores possíveis de tags estão disponíveis para cada local de memória. Tenha em mente que o MTE não consegue detectar overflow dentro do mesmo grânulo de tag (bloco alinhado de 16 bytes) e pode perder pequenos overflows mesmo neste modo. Esse estouro não pode ser a causa da corrupção da memória, porque a memória dentro de um grânulo nunca é usada para alocações múltiplas.
  • M_MEMTAG_TUNING_UAF - permite tags aleatórias independentemente para probabilidade uniforme de ~93% de detecção de bugs espaciais (estouro de buffer) e temporais (uso após liberação).

Além das APIs descritas acima, os usuários experientes podem querer estar cientes do seguinte:

  • A configuração do registro de hardware PSTATE.TCO pode suprimir temporariamente a verificação de tag ( exemplo ). Por exemplo, ao copiar um intervalo de memória com conteúdo de tag desconhecido ou resolver um gargalo de desempenho em um hot loop.
  • Ao usar M_HEAP_TAGGING_LEVEL_SYNC , o manipulador de falhas do sistema fornece informações extras, como rastreamentos de pilha de alocação e desalocação. Esta funcionalidade requer acesso aos bits da tag e é habilitada passando o sinalizador SA_EXPOSE_TAGBITS ao configurar o manipulador de sinal. Qualquer programa que defina seu próprio manipulador de sinal e delegue falhas desconhecidas ao sistema é recomendado para fazer o mesmo.

MTE no kernel

Para ativar o KASAN acelerado por MTE para o kernel, configure o kernel com CONFIG_KASAN=y , CONFIG_KASAN_HW_TAGS=y . Essas configurações são habilitadas por padrão nos kernels GKI, começando com Android 12-5.10 .
Isso pode ser controlado durante a inicialização usando os seguintes argumentos de linha de comando:

  • kasan=[on|off] - habilita ou desabilita KASAN (padrão: on )
  • kasan.mode=[sync |async ] - escolha entre o modo síncrono e assíncrono (padrão: sync )
  • kasan.stacktrace=[on|off] - se deve coletar rastreamentos de pilha (padrão: on )
    • a coleta de rastreamento de pilha também requer stack_depot_disable=off .
  • kasan.fault=[report|panic] - se deve apenas imprimir o relatório ou também entrar em pânico no kernel (padrão: report ). Independentemente desta opção, a verificação de tags é desativada após o primeiro erro relatado.

Recomendamos fortemente o uso do modo SYNC durante a inicialização, desenvolvimento e teste. Esta opção deve ser habilitada globalmente para todos os processos que usam a variável de ambiente ou com o sistema de compilação . Neste modo, os bugs são detectados no início do processo de desenvolvimento, a base de código é estabilizada mais rapidamente e o custo de detecção de bugs posteriormente na produção é evitado.

Recomendamos fortemente o uso do modo ASYNC na produção. Isso fornece uma ferramenta de baixa sobrecarga para detectar a presença de bugs de segurança de memória em um processo, bem como uma defesa mais profunda. Depois que um bug é detectado, o desenvolvedor pode aproveitar as APIs de tempo de execução para mudar para o modo SYNC e obter um rastreamento de pilha preciso de um conjunto de amostras de usuários.

Recomendamos fortemente a configuração do nível MTE preferido específico da CPU para o SoC. O modo Asymm normalmente tem as mesmas características de desempenho do ASYNC e é quase sempre preferível a ele. Núcleos pequenos em ordem geralmente mostram desempenho semelhante em todos os três modos e podem ser configurados para preferir SYNC.

Os desenvolvedores devem verificar a presença de falhas verificando /data/tombstones , logcat ou monitorando o pipeline DropboxManager do fornecedor em busca de bugs do usuário final. Para obter mais informações sobre como depurar o código nativo do Android, consulte as informações aqui .

Componentes da plataforma habilitados para MTE

No Android 12, vários componentes críticos de segurança do sistema usam o MTE ASYNC para detectar falhas do usuário final e atuar como uma camada adicional de defesa profunda. Esses componentes são:

  • Daemons e utilitários de rede (com exceção de netd )
  • Bluetooth, SecureElement, NFC HALs e aplicativos de sistema
  • daemon statsd
  • system_server
  • zygote64 (para permitir que os aplicativos optem pelo uso do MTE)

Essas metas foram selecionadas com base nos seguintes critérios:

  • Um processo privilegiado (definido como um processo que tem acesso a algo que o domínio SELinux unprivileged_app não tem)
  • Processa entradas não confiáveis ​​( regra de dois )
  • Lentidão de desempenho aceitável (a desaceleração não cria latência visível para o usuário)

Incentivamos os fornecedores a ativar o MTE na produção de mais componentes, seguindo os critérios mencionados acima. Durante o desenvolvimento, recomendamos testar esses componentes usando o modo SYNC, para detectar bugs facilmente corrigidos e avaliar o impacto do ASYNC em seu desempenho.
No futuro, o Android planeja expandir a lista de componentes do sistema nos quais o MTE está habilitado, guiado pelas características de desempenho dos próximos designs de hardware.