Arm Memory Tagging Extension

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

De modo geral, a 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 ser associado a ponteiros que referenciam esse local. Em tempo de execução, a CPU verifica se o ponteiro e as tags de metadados correspondem em cada carga e armazenamento.

No Android 12, o alocador de memória de 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 a liberação e estouro de buffer, 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 da MTE

A MTE tem três modos de operação:

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

Modo síncrono (SYNC)

Esse modo é otimizado para a correção da detecção de bugs em vez de desempenho e pode ser usado como uma ferramenta precisa de detecção de bugs, quando uma sobrecarga maior for aceitável. Quando ativada, a MTE SYNC atua como uma mitigação de segurança. Em casos de incompatibilidade de tag, o processador aborta a execução imediatamente e encerra 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 esse modo durante os testes como uma alternativa ao HWASan/KASAN ou em produção quando o processo de destino representa uma superfície de ataque vulnerável. Além disso, quando o modo ASYNC indica a presença de um bug, é possível gerar um relatório preciso do bug usando as APIs 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 usa 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 os rastreamentos de pilha 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)

Esse modo é otimizado para desempenho acima da precisão de relatórios de bugs e pode ser usado como detecção de baixa sobrecarga de bugs de segurança de memória.
Em casos de incompatibilidade de tag, o processador continua a execução até a entrada do kernel mais próxima (por exemplo, uma chamada de sistema ou interrupção de timer), em que encerra o processo com SIGSEGV (código SEGV_MTEAERR) sem registrar o endereço ou acesso à memória com falha.
Recomendamos usar esse modo em produção em bases de código bem testadas em que a densidade de bugs de segurança de memória é conhecida por ser baixa, o que é possível usando o modo SYNC durante o teste.

Modo assimétrico (ASYMM)

Um recurso adicional no Arm v8.7-A, o modo MTE assimétrico, oferece verificação síncrona em 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, esse modo é uma melhoria em relação ao ASYNC, e recomendamos usá-lo em vez do ASYNC sempre que estiver disponível.

Por isso, nenhuma das APIs descritas abaixo menciona o modo assimétrico. Em vez disso, o SO pode ser configurado para sempre usar o modo assíncrono quando o modo assíncrono for solicitado. Consulte a seção "Como configurar o nível de MTE preferido específico da CPU" para mais informações.

MTE no espaço do usuário

As seções a seguir descrevem como o MTE pode ser ativado para processos e apps do sistema. A MTE fica desativada por padrão, a menos que uma das opções abaixo seja definida para um processo específico. Consulte os componentes em que a MTE está ativada abaixo.

Ativar a MTE usando o sistema de build

Como uma propriedade de todo o processo, o MTE é controlado pela configuração de tempo de build do executável principal. As opções a seguir permitem mudar 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 um teste.

1. Para ativar a MTE no Android.bp (exemplo) em um projeto específico:

Modo MTE Configuração
MTE assíncrono
  sanitize: {
  memtag_heap: true,
  }
MTE síncrono
  sanitize: {
  memtag_heap: true,
  diag: {
  memtag_heap: true,
  },
  }

ou em Android.mk:

Modo MTE Configuração
Asynchronous MTE LOCAL_SANITIZE := memtag_heap
Synchronous MTE LOCAL_SANITIZE := memtag_heap
LOCAL_SANITIZE_DIAG := memtag_heap

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

Modo MTE Lista de inclusão Lista de exclusão
assíncrono PRODUCT_MEMTAG_HEAP_ASYNC_INCLUDE_PATHS MEMTAG_HEAP_ASYNC_INCLUDE_PATHS PRODUCT_MEMTAG_HEAP_EXCLUDE_PATHS MEMTAG_HEAP_EXCLUDE_PATHS
sincronização PRODUCT_MEMTAG_HEAP_SYNC_INCLUDE_PATHS MEMTAG_HEAP_SYNC_INCLUDE_PATHS

ou

Modo MTE Configuração
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 Configuração
MTE assíncrono PRODUCT_MEMTAG_HEAP_EXCLUDE_PATHS MEMTAG_HEAP_EXCLUDE_PATHS
MTE síncrono

Por exemplo, (uso semelhante a PRODUCT_CFI_INCLUDE_PATHS)

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

Ativar a MTE usando propriedades do sistema

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

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

Em que basename representa o nome base do executável.

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

Ativar a MTE usando uma variável de ambiente

Outra maneira de substituir a configuração de build para processos nativos (não apps) é 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.

Ativar a MTE para apps

Se não for especificado, o MTE será desativado por padrão, mas os apps que quiserem usar o MTE poderão fazer isso 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 app e pode ser substituído por processos individuais definindo a tag <process>.

Para experimentação, as mudanças de compatibilidade podem ser usadas para definir o valor padrão do atributo memtagMode de um app que não especifica nenhum valor no manifesto (ou especifica default).
Essas mudanças podem ser encontradas em System > Advanced > Developer options > App Compatibility Changes no menu de configurações globais. Definir NATIVE_MEMTAG_ASYNC ou NATIVE_MEMTAG_SYNC ativa a MTE para um app específico.
Como alternativa, isso pode ser definido usando o comando am desta forma:

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

Criar uma imagem do sistema MTE

Recomendamos 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 no início e oferece cobertura realista do usuário, se ativado em builds de teste.

Recomendamos 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 qualquer variável no sistema de build, SANITIZE_TARGET pode ser usada como uma variável de ambiente ou uma configuração de make (por exemplo, em um arquivo product.mk).
Isso ativa a MTE para todos os processos nativos, mas não para apps (que são ramificados de zygote64) em que a MTE pode ser ativada seguindo as instruções acima.

Configurar o nível preferido de MTE específico da CPU

Em algumas CPUs, a performance da MTE nos modos ASYMM ou até mesmo SYNC pode ser semelhante à do ASYNC. Por isso, vale a pena ativar verificações mais rigorosas nessas CPUs quando um modo de verificação menos rigoroso é solicitado, para aproveitar os benefícios da detecção de erros das verificações mais rigorosas sem as desvantagens de desempenho.
Por padrão, os processos configurados para serem executados no modo ASYNC serão executados nesse modo em todas as CPUs. Para configurar o kernel para executar esses processos no modo SYNC em CPUs específicas, a sincronização de valor precisa ser gravada 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 a 1 para executar processos do modo ASYNC no modo SYNC e as CPUs 2 a 3 para usar o modo ASYMM, o seguinte pode ser adicionado à cláusula init de um script init 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

Os marcadores de exclusão dos processos do modo ASYNC executados no modo SYNC vão conter um rastreamento de pilha preciso do local do erro de memória. No entanto, eles não incluem 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)

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

int mallopt(M_MEMTAG_TUNING, level)

em que 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 estouro e subfluxo de buffer linear atribuindo valores de tag distintos a alocações adjacentes. Esse modo tem uma chance um pouco menor de detectar bugs de uso após liberação porque apenas metade dos valores de tag possíveis está disponível para cada local de memória. O MTE não detecta estouros no mesmo granulado de tag (pedaço alinhado de 16 bytes) e pode perder estouros pequenos mesmo nesse modo. Esse estouro não pode ser a causa da corrupção de memória, porque a memória em um único grânulo nunca é usada para várias alocações.
  • M_MEMTAG_TUNING_UAF: ativa tags aleatórias independentes para uma probabilidade uniforme de ~93% de detectar bugs espaciais (transbordamento de dados) e temporais (use-after-free).

Além das APIs descritas acima, os usuários experientes podem querer saber o seguinte:

  • Definir o registro de hardwarePSTATE.TCO pode suprimir temporariamente a verificação de tags (exemplo). Por exemplo, ao copiar um intervalo de memória com conteúdo de tag desconhecido ou ao resolver um gargalo de desempenho em um loop ativo.
  • Ao usar o M_HEAP_TAGGING_LEVEL_SYNC, o manipulador de falhas do sistema fornece mais informações, como rastreamentos de pilha de alocação e desalocação. Essa funcionalidade requer acesso aos bits de tag e é ativada transmitindo a flag SA_EXPOSE_TAGBITS ao definir o manipulador de sinal. Recomendamos que qualquer programa que defina seu próprio manipulador de sinal e delegue falhas desconhecidas ao sistema faça o mesmo.

MTE no kernel

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

  • kasan=[on|off]: ativa ou desativa o 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 é necessário coletar rastreamentos de pilha (padrão: on)
    • A coleta de rastreamento de pilha também exige stack_depot_disable=off.
  • kasan.fault=[report|panic]: se é para apenas imprimir o relatório ou também gerar um panic no kernel (padrão: report). Independente dessa opção, a verificação de tags é desativada após o primeiro erro informado.

Recomendamos usar o modo SYNC durante a inicialização, o desenvolvimento e os testes. Essa opção precisa ser ativada globalmente para todos os processos usando a variável de ambiente ou com o sistema de build. Nesse 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 mais tarde na produção é evitado.

Recomendamos usar o 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, além de uma defesa em profundidade. Depois que um bug é detectado, o desenvolvedor pode aproveitar as APIs de execução para mudar para o modo SYNC e receber um rastreamento de pilha preciso de um conjunto amostrado de usuários.

Recomendamos configurar o nível de MTE preferido específico da CPU para o SoC. O modo assíncrono geralmente tem as mesmas características de desempenho que ASYNC e é quase sempre preferível a ele. Núcleos pequenos em ordem geralmente mostram desempenho semelhante nos três modos e podem ser configurados para preferir SYNC.

Os desenvolvedores precisam verificar a presença de falhas conferindo /data/tombstones, logcat ou monitorando o pipeline do fornecedor DropboxManager para bugs do usuário final. Para mais informações sobre a depuração de código nativo do Android, consulte este link.

Componentes da plataforma ativados para MTE

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

  • Daemons e utilitários de rede (exceto netd)
  • Bluetooth, SecureElement, HALs do NFC e apps do sistema
  • daemon statsd
  • system_server
  • zygote64 (para permitir que os apps ativem o uso da MTE)

Esses destinos foram selecionados 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)
  • Redução aceitável de desempenho (a redução não cria latência visível ao usuário)

Incentivamos os fornecedores a ativar o MTE em produção para mais componentes, seguindo os critérios mencionados acima. Durante o desenvolvimento, recomendamos testar esses componentes usando o modo SYNC para detectar bugs fáceis de corrigir e avaliar o impacto do ASYNC na performance deles.
No futuro, o Android planeja expandir a lista de componentes do sistema em que o MTE está ativado, com base nas características de desempenho dos próximos designs de hardware.