Arm Memory Tagging Extension

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

Em um nível alto, a MTE marca cada alocação/desalocação de memória com metadados adicionais. A MTE atribui uma tag a um local de memória, que pode ser associado a ponteiros que fazem referência a esse local. No 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 kernel e o alocador de memória de heap do espaço do usuário podem aumentar cada alocação com metadados. Isso ajuda a detectar bugs use-after-free e de 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 relação ao 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 interrompe 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 na 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 stack traces 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 use-after-free ou de estouro de buffer, e os stack traces 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 para 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 na produção em bases de código bem testadas em que é sabido que a densidade de bugs de segurança de memória é 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, fornece 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, este modo é uma melhoria em relação ao modo ASYNC, e recomendamos usá-lo em vez do ASYNC sempre que estiver disponível.

Por esse motivo, nenhuma das APIs descritas abaixo menciona o modo assimétrico. Em vez disso, o SO pode ser configurado para sempre usar o modo assimétrico quando o assíncrono for solicitado. Para mais informações, consulte Configurar o nível de MTE preferido específico da CPU.

MTE no espaço do usuário

As seções a seguir descrevem como a MTE pode ser ativada 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 abaixo para quais componentes a MTE está ativada abaixo).

Ativar a MTE usando o sistema de build

Como uma propriedade de todo o processo, a MTE é controlada pela configuração de tempo de build de o 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 qualquer destino que não seja executável nem um teste.

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

Modo MTE Configuração
MTE assíncrona
  sanitize: {
  memtag_heap: true,
  }
MTE síncrona
  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. Como 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
async PRODUCT_MEMTAG_HEAP_ASYNC_INCLUDE_PATHS MEMTAG_HEAP_ASYNC_INCLUDE_PATHS PRODUCT_MEMTAG_HEAP_EXCLUDE_PATHS MEMTAG_HEAP_EXCLUDE_PATHS
sync PRODUCT_MEMTAG_HEAP_SYNC_INCLUDE_PATHS MEMTAG_HEAP_SYNC_INCLUDE_PATHS

ou

Modo MTE Configuração
MTE assíncrona MEMTAG_HEAP_ASYNC_INCLUDE_PATHS
MTE síncrona MEMTAG_HEAP_SYNC_INCLUDE_PATHS

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

Modo MTE Configuração
MTE assíncrona PRODUCT_MEMTAG_HEAP_EXCLUDE_PATHS MEMTAG_HEAP_EXCLUDE_PATHS
MTE síncrona

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 a MTE assíncrona, 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, a MTE será desativada por padrão, mas os apps que quiserem usar a 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 para processos individuais definindo a tag <process>.

Para testes, 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).

Eles estão em Sistema > Avançado > Opções do desenvolvedor > Mudanças de compatibilidade de apps 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 am comando da seguinte maneira:

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

Criar uma imagem do sistema MTE

Recomendamos ativar a 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 com antecedência e fornece cobertura realista do usuário, se ativado em builds de teste.

Recomendamos ativar a 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 usado como uma variável de ambiente ou uma configuração 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 bifurcados de zygote64) para os quais a MTE pode ser ativada seguindo as instruções em Ativar a MTE para apps.

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

Em algumas CPUs, o desempenho da MTE nos modos ASYMM ou até mesmo SYNC pode ser semelhante a o do ASYNC. Isso faz com que valha a pena ativar verificações mais rigorosas nessas CPUs quando um modo de verificação menos rigoroso for solicitado, para aproveitar 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 sã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 precisa ser gravado na sysfs entrada /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 de 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 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

Os tombstone de processos de modo ASYNC em execução no modo SYNC contêm um stack trace preciso do local do erro de memória. No entanto, eles não incluem um stack trace de alocação ou desalocação. Esses stack traces só estão disponíveis se o processo estiver configurado para execução no modo SYNC.

int mallopt(M_THREAD_DISABLE_MEM_INIT, level)

em que level é 0 ou 1.

Desativa a inicialização da memória no malloc e evita a mudança de 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 bugs de estouro de buffer linear e de subfluxo, atribuindo valores de tag distintos a alocações adjacentes. Esse modo tem uma chance ligeiramente reduzida de detectar bugs use-after-free porque apenas metade dos valores de tag possíveis estão disponíveis para cada local de memória. A MTE não consegue detectar estouro no mesmo grânulo de tag (bloco alinhado de 16 bytes) e pode perder pequenos estouros mesmo nesse modo. Esse estouro não pode ser a causa da corrupção de memória, porque a memória em um 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 (estouro de buffer) e temporais (use-after-free).

Além das APIs descritas acima, os usuários experientes podem querer estar cientes do 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 M_HEAP_TAGGING_LEVEL_SYNC, o gerenciador de falhas do sistema fornece informações extras, como stack traces de alocação e desalocação. Essa funcionalidade exige acesso aos bits de tag e é ativada transmitindo a SA_EXPOSE_TAGBITS flag ao definir o gerenciador de sinais. Recomendamos que qualquer programa que defina o próprio gerenciador de sinais e delegue falhas desconhecidas ao sistema faça 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 ativadas por padrão nos kernels GKI, começando com Android 12-5.10.

É possível controlar isso no momento da 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 os stack traces devem ser coletados (padrão: on)
    • A coleta de stack trace também exige stack_depot_disable=off.
  • kasan.fault=[report|panic] - se apenas o relatório deve ser impresso, ou se o kernel também deve entrar em pânico (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 detectar 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, bem como uma defesa mais aprofundada. Depois que um bug é detectado, o desenvolvedor pode aproveitar as APIs de execução para mudar para o modo SYNC e receber um stack trace preciso de um conjunto de usuários amostrados.

Recomendamos configurar o nível de 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. Os núcleos pequenos em ordem geralmente mostram desempenho semelhante em todos os três modos e podem ser configurados para preferir o SYNC.

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

Componentes da plataforma ativados pela MTE

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

  • Daemons e utilitários de rede (com exceção de netd)
  • Bluetooth, SecureElement, NFC HALs e apps do sistema
  • Daemon statsd
  • system_server
  • zygote64 (para permitir que os apps usem a 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 entrada não confiável (Regra de dois)
  • Desaceleração de desempenho aceitável (a desaceleração não cria latência visível ao usuário )

Incentivamos os fornecedores a ativar a MTE na 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 facilmente corrigidos e avaliar o impacto do ASYNC no desempenho deles.

Planejamos expandir a lista de componentes do sistema em que a MTE está ativada, orientada pelas características de desempenho dos próximos designs de hardware.