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

O Arm v9 apresenta o 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 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 use-after-free 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 (SINC)
  • Modo assíncrono (ASYNC)
  • Modo assimétrico (ASYMM)

Modo síncrono (SINC)

Este modo é otimizado para correção da detecção de bugs sobre o desempenho e pode ser usado como uma ferramenta de detecção de bugs precisa, quando uma sobrecarga de desempenho mais alta é aceitável. Quando ativado, o MTE SYNC atua como uma mitigação de segurança. Em uma incompatibilidade de tag, o processador aborta a execução imediatamente e termina 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 o teste como uma alternativa ao HWASan/KASAN ou em produção quando o processo de destino representar 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 ser executado 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 use-after-free ou buffer overflow, 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 sobre 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 tags, o processador continua a execução até a entrada do kernel mais próxima (por exemplo, uma syscall ou interrupção de timer), onde termina o processo com SIGSEGV (código SEGV_MTEAERR ) sem registrar o endereço com falha ou acesso à memória.
Recomendamos usar esse 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 é alcançado 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, esse 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 SO pode ser configurado para sempre usar o modo Assíncrono quando o Assíncrono for solicitado. Consulte a seção "Configurando o nível de 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 compilaçã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 o MTE no 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. Habilitando 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 ao PRODUCT_CFI_INCLUDE_PATHS )

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

Habilitando 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 significa 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 .

Habilitando 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.

Habilitando o MTE para aplicativos

Se não for especificado, o MTE é desabilitado 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 habilita 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

Como criar uma imagem do sistema MTE

É altamente recomendável ativar o MTE em todos os binários nativos durante o desenvolvimento e a ativação. Isso ajuda a detectar erros de segurança de memória antecipadamente e fornece cobertura realista ao usuário, se ativado em compilações de teste.

É altamente recomendável 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 de 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 de MTE preferido específico da CPU

Em algumas CPUs, o desempenho do MTE nos modos ASYMM ou mesmo SYNC pode ser semelhante ao do ASYNC. Isso faz com que valha a pena habilitar 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 serem executados 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 no 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

Tombstones de processos de modo ASYNC executados em 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.
Desabilita a inicialização de memória em malloc e evita a alteração de 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 overflow e underflow de buffer linear atribuindo valores de tag distintos a alocações adjacentes. Este modo tem uma chance ligeiramente reduzida de detectar erros de uso após a liberação porque apenas metade dos valores de tag possíveis estão disponíveis para cada local de memória. Lembre-se de que o MTE não pode detectar estouro dentro do mesmo grânulo de tag (bloco alinhado de 16 bytes) e pode perder pequenos estouros 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 várias alocações.
  • M_MEMTAG_TUNING_UAF - permite tags aleatórias de forma independente para uma probabilidade uniforme de ~93% de detectar erros espaciais (estouro de buffer) e temporais (usar após a liberação).

Além das APIs descritas acima, 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 tags ( exemplo ). Por exemplo, ao copiar um intervalo de memória com conteúdo de tag desconhecido ou endereçar 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. Essa funcionalidade requer acesso aos bits do tag e é habilitada passando o sinalizador SA_EXPOSE_TAGBITS ao definir o manipulador de sinal. Recomenda-se 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 habilitar 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 o Android 12-5.10 .
Isso pode ser controlado no momento da 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 apenas imprimir o relatório, ou também colocar o kernel em pânico (padrão: report ). Independentemente dessa opção, a verificação de tags é desativada após o primeiro erro relatado.

É altamente recomendável usar o modo SYNC durante a ativação, desenvolvimento e teste. Esta opção deve ser habilitada globalmente para todos os processos usando a variável de ambiente ou com o sistema de compilação . 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.

É altamente recomendável 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 profunda. Depois que um bug é detectado, o desenvolvedor pode aproveitar as APIs de tempo de execução para alternar para o modo SYNC e obter um rastreamento de pilha preciso de um conjunto de amostra de usuários.

É altamente recomendável configurar o nível de MTE preferencial 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. Pequenos núcleos em ordem geralmente mostram desempenho semelhante em todos os três modos e podem ser configurados para preferir o SYNC.

Os desenvolvedores devem verificar a presença de falhas verificando /data/tombstones , logcat ou monitorando o pipeline do fornecedor DropboxManager 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 de 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 em profundidade. Esses componentes são:

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

Esses alvos 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 )
  • Desaceleração de desempenho aceitável (desaceleração não cria latência visível ao usuário)

Incentivamos os fornecedores a habilitar 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 erros facilmente corrigidos e avaliar o impacto ASYNC em seu desempenho.
No futuro, o Android planeja expandir a lista de componentes do sistema em que o MTE está habilitado, guiado pelas características de desempenho dos próximos designs de hardware.