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 sinalizadorSA_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
.
- a coleta de rastreamento de pilha também requer
-
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.
Uso recomendado
É 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.