Por que usar a MTE?
Os bugs de segurança de memória, que são erros no processamento de memória em linguagens de programação nativas, são problemas comuns de código. Eles causam vulnerabilidades de segurança, bem como problemas de estabilidade.
A Armv9 introduziu a Arm Memory Tagging Extension (MTE), uma extensão de hardware que permite capturar bugs use-after-free e de estouro de buffer no código nativo.
Procurar suporte
No Android 13 e versões mais recentes, alguns dispositivos têm suporte à MTE. Para conferir se o seu dispositivo está sendo executado com a MTE ativada, use o seguinte comando:
adb shell grep mte /proc/cpuinfo
Se o resultado for Features : [...] mte
, isso significa que o dispositivo está sendo executado com a MTE
ativada.
Alguns dispositivos não ativam a MTE por padrão, mas permitem que os desenvolvedores reinicializem o dispositivo com a MTE ativada. Essa é uma configuração experimental que não é recomendada para uso normal, porque pode reduzir o desempenho ou a estabilidade do dispositivo, mas pode ser útil para o desenvolvimento de apps. Para acessar esse modo, navegue até Opções do desenvolvedor > Memory Tagging Extension no seu app Configurações. Se essa opção não estiver presente, isso significa que o dispositivo não oferece suporte para ativar a MTE dessa forma.
Modos de operação da MTE
A MTE oferece suporte a dois modos: SYNC e ASYNC. O modo SYNC fornece informações de diagnóstico melhores e, portanto, é mais adequado para fins de desenvolvimento, enquanto o modo ASYNC tem alto desempenho que permite que ele seja ativado para apps lançados.
Modo síncrono (SYNC)
Esse modo é otimizado para melhor capacidade de depuração 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 também atua como uma mitigação de segurança.
Em casos de incompatibilidade de tag, o processador encerra o processo na carga incompatível ou na instrução de armazenamento com SIGSEGV (com si_code SEGV_MTESERR) e informações completas sobre o acesso à memória e o endereço com falha.
Esse modo é útil durante os testes como uma alternativa mais rápida ao HWASan que não exige que você recompile o código ou em produção, quando o app representa uma superfície de ataque vulnerável. Além disso, quando o modo ASYNC (descrito abaixo) encontra 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.
Além disso, ao executar no modo SYNC, o alocador do Android registra o stack trace de cada alocação e desalocação e os usa para fornecer relatórios de erros melhores que incluem a 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. Consulte Noções básicas sobre relatórios de MTE para mais detalhes. Esses relatórios fornecem mais informações contextuais e facilitam o rastreamento e a correção de bugs no modo ASYNC.
Modo assíncrono (ASYNC)
Esse modo é otimizado para desempenho acima da precisão de relatórios de bugs e pode ser usado para 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 (como 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.
Esse modo é útil para atenuar vulnerabilidades de segurança de memória 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.
Ativar a MTE
Para um único dispositivo
Para experimentação, as mudanças de compatibilidade de app podem ser usadas para definir o valor
padrão do atributo memtagMode
de um aplicativo que não especifique
nenhum valor no manifesto (ou que especifique "default"
).
Elas podem ser encontradas em "Sistema" > "Avançado" > "Opções do desenvolvedor" > "Mudanças na
compatibilidade do app" no menu de configurações globais. Definir NATIVE_MEMTAG_ASYNC
ou NATIVE_MEMTAG_SYNC
ativa a MTE de um aplicativo específico.
Ou você pode usar o comando am
desta forma:
- Para o modo SYNC:
$ adb shell am compat enable NATIVE_MEMTAG_SYNC my.app.name
- Para o modo ASYNC:
$ adb shell am compat enable NATIVE_MEMTAG_ASYNC my.app.name
No Gradle
Você pode ativar a MTE para todos os builds de depuração do seu projeto do Gradle colocando
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application android:memtagMode="sync" tools:replace="android:memtagMode"/>
</manifest>
em app/src/debug/AndroidManifest.xml
. Isso vai substituir o memtagMode
do manifesto pela sincronização para builds de depuração.
Como alternativa, você pode ativar a MTE para todos os builds de um buildType personalizado. Para
fazer isso, crie seu próprio buildType e coloque o
XML em app/src/<name of buildType>/AndroidManifest.xml
.
Para um APK em qualquer dispositivo compatível
A MTE fica desativada por padrão. Para usá-la, os apps podem
definir 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>
.
Criar com instrumentação
Ativar a MTE, conforme explicado anteriormente, ajuda a detectar bugs de corrupção de memória no heap nativo. Detectar a corrupção de memória na pilha, além de ativar MTE para o app, o código precisa ser recriado com instrumentação. A o app resultante só será executado em dispositivos compatíveis com MTE.
Para criar o código nativo (JNI) do seu app com a MTE, faça o seguinte:
ndk-build
No seu arquivo Application.mk
:
APP_CFLAGS := -fsanitize=memtag -fno-omit-frame-pointer -march=armv8-a+memtag
APP_LDFLAGS := -fsanitize=memtag -fsanitize-memtag-mode=sync -march=armv8-a+memtag
CMake
Para cada destino no CMakeLists.txt:
target_compile_options(${TARGET} PUBLIC -fsanitize=memtag -fno-omit-frame-pointer -march=armv8-a+memtag)
target_link_options(${TARGET} PUBLIC -fsanitize=memtag -fsanitize-memtag-mode=sync -march=armv8-a+memtag)
Executar o app
Depois de ativar a MTE, use e teste o app normalmente. Se um problema de segurança de memória
for detectado, o app vai falhar com um Tombstone semelhante a este (observe
o SIGSEGV
com SEGV_MTESERR
para SYNC ou SEGV_MTEAERR
para ASYNC):
pid: 13935, tid: 13935, name: sanitizer-statu >>> sanitizer-status <<<
uid: 0
tagged_addr_ctrl: 000000000007fff3
signal 11 (SIGSEGV), code 9 (SEGV_MTESERR), fault addr 0x800007ae92853a0
Cause: [MTE]: Use After Free, 0 bytes into a 32-byte allocation at 0x7ae92853a0
x0 0000007cd94227cc x1 0000007cd94227cc x2 ffffffffffffffd0 x3 0000007fe81919c0
x4 0000007fe8191a10 x5 0000000000000004 x6 0000005400000051 x7 0000008700000021
x8 0800007ae92853a0 x9 0000000000000000 x10 0000007ae9285000 x11 0000000000000030
x12 000000000000000d x13 0000007cd941c858 x14 0000000000000054 x15 0000000000000000
x16 0000007cd940c0c8 x17 0000007cd93a1030 x18 0000007cdcac6000 x19 0000007fe8191c78
x20 0000005800eee5c4 x21 0000007fe8191c90 x22 0000000000000002 x23 0000000000000000
x24 0000000000000000 x25 0000000000000000 x26 0000000000000000 x27 0000000000000000
x28 0000000000000000 x29 0000007fe8191b70
lr 0000005800eee0bc sp 0000007fe8191b60 pc 0000005800eee0c0 pst 0000000060001000
backtrace:
#00 pc 00000000000010c0 /system/bin/sanitizer-status (test_crash_malloc_uaf()+40) (BuildId: 953fc93301472d0b72709b2b9a9f6f30)
#01 pc 00000000000014a4 /system/bin/sanitizer-status (test(void (*)())+132) (BuildId: 953fc93301472d0b72709b2b9a9f6f30)
#02 pc 00000000000019cc /system/bin/sanitizer-status (main+1032) (BuildId: 953fc93301472d0b72709b2b9a9f6f30)
#03 pc 00000000000487d8 /apex/com.android.runtime/lib64/bionic/libc.so (__libc_init+96) (BuildId: 6ab39e35a2fae7efbe9a04e9bbb14331)
deallocated by thread 13935:
#00 pc 000000000004643c /apex/com.android.runtime/lib64/bionic/libc.so (scudo::Allocator<scudo::AndroidConfig, &(scudo_malloc_postinit)>::quarantineOrDeallocateChunk(scudo::Options, void*, scudo::Chunk::UnpackedHeader*, unsigned long)+688) (BuildId: 6ab39e35a2fae7efbe9a04e9bbb14331)
#01 pc 00000000000421e4 /apex/com.android.runtime/lib64/bionic/libc.so (scudo::Allocator<scudo::AndroidConfig, &(scudo_malloc_postinit)>::deallocate(void*, scudo::Chunk::Origin, unsigned long, unsigned long)+212) (BuildId: 6ab39e35a2fae7efbe9a04e9bbb14331)
#02 pc 00000000000010b8 /system/bin/sanitizer-status (test_crash_malloc_uaf()+32) (BuildId: 953fc93301472d0b72709b2b9a9f6f30)
#03 pc 00000000000014a4 /system/bin/sanitizer-status (test(void (*)())+132) (BuildId: 953fc93301472d0b72709b2b9a9f6f30)
allocated by thread 13935:
#00 pc 0000000000042020 /apex/com.android.runtime/lib64/bionic/libc.so (scudo::Allocator<scudo::AndroidConfig, &(scudo_malloc_postinit)>::allocate(unsigned long, scudo::Chunk::Origin, unsigned long, bool)+1300) (BuildId: 6ab39e35a2fae7efbe9a04e9bbb14331)
#01 pc 0000000000042394 /apex/com.android.runtime/lib64/bionic/libc.so (scudo_malloc+36) (BuildId: 6ab39e35a2fae7efbe9a04e9bbb14331)
#02 pc 000000000003cc9c /apex/com.android.runtime/lib64/bionic/libc.so (malloc+36) (BuildId: 6ab39e35a2fae7efbe9a04e9bbb14331)
#03 pc 00000000000010ac /system/bin/sanitizer-status (test_crash_malloc_uaf()+20) (BuildId: 953fc93301472d0b72709b2b9a9f6f30)
#04 pc 00000000000014a4 /system/bin/sanitizer-status (test(void (*)())+132) (BuildId: 953fc93301472d0b72709b2b9a9f6f30)
Learn more about MTE reports: https://source.android.com/docs/security/test/memory-safety/mte-report
Consulte Noções básicas sobre relatórios da MTE na documentação do AOSP para saber mais. Também é possível depurar o app com o Android Studio. O depurador será interrompido na linha que está causando o acesso inválido à memória.
Usuários avançados: como usar a MTE no seu próprio alocador
Para usar a MTE na memória não alocada com os alocadores de sistema normais, modifique o alocador para adicionar uma tag na memória e nos ponteiros.
As páginas do alocador precisam ser alocadas usando PROT_MTE
na
flag prot
de mmap
(ou mprotect
).
Todas as alocações com tags precisam estar alinhadas a 16 bytes, já que as tags só podem ser atribuídas a blocos de 16 bytes, também conhecidos como grânulos.
Antes de retornar um ponteiro, use a instrução IRG
para
gerar e armazenar uma tag aleatória dentro dele.
Use as seguintes instruções para adicionar uma tag na memória:
STG
: adicionar uma tag em um único grânulo de 16 bytesST2G
: adicionar uma tag em dois grânulos de 16 bytesDC GVA
: adicionar a mesma tag à linha de cache
Como alternativa, as seguintes instruções também inicializam a memória do zero:
STZG
: adicionar uma tag e inicializar um único grânulo de 16 bytes do zeroSTZ2G
: adicionar uma tag e inicializar dois grânulos de 16 bytes do zeroDC GZVA
: adicionar a mesma tag e inicializar a linha de cache do zero
CPUs mais antigas não têm suporte a essas instruções. Portanto, elas precisam ser executadas condicionalmente quando a MTE está ativada. Confira se a MTE está ativada:
#include <sys/prctl.h>
bool runningWithMte() {
int mode = prctl(PR_GET_TAGGED_ADDR_CTRL, 0, 0, 0, 0);
return mode != -1 && mode & PR_MTE_TCF_MASK;
}
A implementação do scudo pode ser útil como referência.
Saiba mais
Consulte o Guia do usuário da MTE para o SO Android (link em inglês) escrito pela Arm para saber mais.