Seu celular pode detectar corrupção de memória em produção. Não com a instrumentação completa que você executa em CI, e não em cada alocação. Mas o hardware no seu bolso já vem enviando as primitivas necessárias há alguns anos, e um número crescente de apps em produção as está ativando silenciosamente.

A versão curta: AddressSanitizer é caro demais para produção. ARM Memory Tagging Extension não é. E se você ainda não pode confiar em MTE, o GWP-ASan oferece cobertura probabilística com quase nenhum overhead. Juntos, respondem a uma pergunta que costumava ter uma resposta deprimente.

Corrupção de Memória é o Bug que Você Envia

Use-after-free, buffer overflow e heap corruption são os bugs que passam por toda a test suite. Eles vivem em código nativo, se reproduzem mal e frequentemente travam em algum lugar longe do erro real. Quando um usuário relata um crash, a corrupção original já aconteceu, e sua única evidência é um stack trace deturpado ou um SIGSEGV em um endereço que parece aleatório.

Ferramentas tradicionais pegam esses problemas cedo. Valgrind é preciso mas lento. ASan é mais rápido mas ainda adiciona aproximadamente 2-3x de overhead de memória e 2x de overhead de CPU. Isso é aceitável para fuzzing ou testes unitários. Não é aceitável para um dispositivo alimentado por bateria rodando Instagram.

A produção historicamente tem sido um ponto cego. Ou você reproduzia o bug localmente ou adivinhava.

Como o ARM MTE Marca Memória em Hardware

ARM Memory Tagging Extension, introduzida no ARMv8.5-A, dá à CPU uma forma barata de verificar a validade de ponteiros em cada acesso de memória. Funciona atribuindo uma tag de 4 bits a cada grânulo de 16 bytes de memória e armazenando uma tag correspondente no byte superior do ponteiro.

Em cada load ou store, a CPU compara a tag do ponteiro contra a tag da memória. Se diferirem, o hardware levanta uma falta. Isso não é uma verificação de software. Acontece no subsistema de memória, e o overhead é tipicamente menor que 5%.

O byte superior de um ponteiro de 64 bits já era ignorado pelo hardware. MTE reutiliza esses bits. Um ponteiro como 0xb7f0_0000_1234_5678 carrega a tag 0xb7. A memória nesse endereço deve carregar a mesma tag, ou o acesso falha.

MTE está disponível em dispositivos com ARMv8.5-A ou posterior. Isso inclui o Google Pixel 8 e posterior, o iPhone 15 Pro e posterior, e uma parcela crescente de dispositivos Android de médio porte. Não é universal, mas não é mais exótico.

O kernel expõe MTE através de flags prctl. Uma aplicação pode solicitar verificação síncrona ou assíncrona. O modo síncrono falha imediatamente em mismatch de tag. O modo assíncrono enfileira a falta e a entrega depois, o que é mais barato mas atrasa ligeiramente a detecção.

GWP-ASan: Quando Você Não Pode Marcar Tudo

MTE é ótimo, mas requer hardware compatível e alocações marcadas em toda a sua codebase. Se você envia para dispositivos mais antigos, ou se quer apenas proteção direcionada, o GWP-ASan é a alternativa pragmática.

GWP-ASan significa Guarded Write Protection AddressSanitizer. É um alocador de amostragem que coloca uma pequena fração de alocações de heap em páginas protegidas cercadas por redzones envenenadas. Se um use-after-free ou buffer overflow tocar essas redzones, a MMU de hardware dispara uma falta imediatamente.

A ideia-chave é probabilidade. O GWP-ASan pode proteger 1 em cada 10.000 alocações. Isso soa inútil até você perceber que um app com bugs corromperá a memória milhares de vezes antes de travar. Dadas sessões de usuário suficientes, até uma taxa de amostragem de 0,01% pega bugs reais em produção.

A Google executa o GWP-ASan no Chrome e nos serviços do sistema Android há anos. Ele encontrou centenas de bugs de use-after-free que escaparam tanto dos testes quanto do fuzzing. O overhead de CPU é insignificante porque apenas uma fração minúscula de alocações passa pelo caminho protegido. O overhead de memória é limitado porque o pool protegido é pequeno e as alocações são eventualmente recicladas.

Os Compromissos que Ninguém Quer Falar

MTE e GWP-ASan não são de graça. Eles são apenas baratos o suficiente para valer a pena.

MTE requer o byte superior de cada ponteiro, o que significa que sua codebase deve ser compilada com -fsanitize=memtag ou -march=armv8.5-a+memtag. Se você tem código que mascara ponteiros, os hashea ou os passa através de JNI sem marcação adequada, você terá falsos positivos. O Android NDK e implementações modernas de libc lidam com isso corretamente, mas custom allocators ou esquemas de empacotamento de ponteiros quebrarão.

MTE também só pega bugs dentro das regiões de heap e stack que você marca explicitamente. Um ponteiro selvagem que salta para uma região mmap não marcada não será pego. Isso é melhor que nada, mas não é cobertura total.

GWP-ASan tem o problema oposto. Ele só pega as alocações que acontecem de cair no pool protegido. Um bug que corrompe uma alocação não protegida passará despercebido. Você também paga um pequeno custo de latência na alocação e desalocação para o conjunto amostrado, e precisa lidar com os crashes elegantemente em seu pipeline de telemetry.

Nenhuma das ferramentas substitui o ASan em seu pipeline de CI. Elas o complementam. O ASan oferece detecção determinística de alta cobertura durante os testes. MTE e GWP-ASan oferecem uma rede de segurança no campo.

Habilitando MTE em um Dispositivo Android Moderno

Se você tem um Pixel 8 ou mais novo, pode testar o MTE hoje. O Android NDK o suporta com uma única flag do compiler.

Primeiro, verifique se seu dispositivo suporta MTE:

#include <sys/prctl.h>
#include <linux/prctl.h>
#include <sys/auxv.h>
#include <asm/hwcap.h>

bool mte_supported() {
    unsigned long hwcap2 = getauxval(AT_HWCAP2);
    return (hwcap2 & HWCAP2_MTE) != 0;
}

Então compile sua biblioteca nativa com MTE habilitado no seu CMakeLists.txt:

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=memtag")

Se você quer faulting síncrono, que é o que tipicamente quer em produção, solicite-o na inicialização:

#include <sys/prctl.h>

void enable_mte() {
    if (mte_supported()) {
        prctl(PR_SET_TAGGED_ADDR_CTRL,
              PR_TAGGED_ADDR_ENABLE | PR_MTE_TCF_SYNC | (0xfffe << PR_MTE_TAG_SHIFT),
              0, 0, 0);
    }
}

A flag PR_MTE_TCF_SYNC diz ao kernel para falhar imediatamente em mismatch de tag. A máscara de tag 0xfffe exclui a tag zero, que ainda é usada por algum código do sistema.

Configurando o GWP-ASan para Produção

O GWP-ASan é mais fácil de habilitar porque não requer hardware especial. No Android, você pode linkar contra o wrapper do alocador GWP-ASan ou habilitá-lo através do alocador do sistema em níveis de API mais novos.

Para uma integração mínima, envolva seus pontos de entrada do alocador:

extern "C" void* malloc(size_t size) {
    if (__gwp_asan_sample()) {
        return __gwp_asan_guarded_malloc(size);
    }
    return __libc_malloc(size);
}

Na prática, você usará o runtime do Android GWP-ASan ou a implementação do LLVM compiler-rt. O principal botão de configuração é a taxa de amostragem. Comece de forma conservadora:

// Uma alocação protegida a cada 10.000
__gwp_asan_set_sample_rate(10000);

Colete os crashes resultantes através da sua telemetry existente. Um crash do GWP-ASan parece um SIGSEGV padrão, mas o endereço faltante cairá em uma página protegida. Seu crash reporter pode detectar isso verificando se o endereço faltante está dentro do pool do GWP-ASan.

O Que Você Deveria Realmente Fazer

Se você envia código nativo em mobile, já deveria estar executando o ASan em CI e fuzzing. A pergunta é o que acontece depois que você envia.

Se seu dispositivo mínimo suportado inclui chips ARMv8.5-A, habilite o MTE no modo síncrono. O overhead é baixo o suficiente para que os usuários não percebam, e os crashes que você obtiver terão informações precisas de mismatch de tag em vez de corrupção aleatória de heap.

Se você suporta dispositivos mais antigos, habilite o GWP-ASan com uma taxa de amostragem conservadora. Ele não pegará todos os bugs, mas pegará bugs que nada mais pega. Ao longo de milhões de sessões, isso não é teórico. Foi assim que o Chrome encontrou um use-after-free em seu decodificador de imagens no ano passado.

O hardware do seu celular já é capaz de pegar os bugs de segurança de memória que você mais teme. A única pergunta é se você o ligou.