Tu teléfono puede detectar corrupción de memoria en producción. No con la instrumentation completa que ejecutas en CI, y no en cada asignación. Pero el hardware en tu bolsillo ha estado incluyendo las primitivas necesarias desde hace unos años, y un número creciente de aplicaciones de producción las están activando en silencio.

La versión corta: AddressSanitizer es demasiado costoso para producción. ARM Memory Tagging Extension no lo es. Y si aún no puedes confiar en MTE, GWP-ASan te brinda cobertura probabilística con casi ninguna sobrecarga. Juntas, responden una pregunta que solía tener una respuesta deprimente.

La corrupción de memoria es el bug que envías

Use-after-free, buffer overflow y heap corruption son los bugs que pasan cada suite de pruebas. Viven en código nativo, se reproducen mal y a menudo se bloquean en algún lugar lejos del error real. Para cuando un usuario reporta un bloqueo, la corrupción original ya ha ocurrido, y tu única evidencia es un stack trace destrozado o un SIGSEGV en una dirección que parece aleatoria.

Las herramientas tradicionales detectan estos problemas temprano. Valgrind es preciso pero lento. ASan es más rápido pero aún agrega aproximadamente 2-3x de sobrecarga de memoria y 2x de sobrecarga de CPU. Eso está bien para fuzzing o unit tests. No está bien para un dispositivo alimentado por batería ejecutando Instagram.

La producción históricamente ha sido un punto ciego. O reproducías el bug localmente o adivinabas.

Cómo ARM MTE tag memoria en hardware

ARM Memory Tagging Extension, introducida en ARMv8.5-A, le da a la CPU una forma económica de verificar la validez de punteros en cada acceso de memoria. Funciona asignando una tag de 4 bits a cada gránulo de 16 bytes de memoria y almacenando una tag coincidente en el byte superior del puntero.

En cada load o store, la CPU compara la tag del puntero contra la tag de memoria. Si difieren, el hardware genera un fallo. Esto no es una verificación de software. Ocurre en el subsistema de memoria, y la sobrecarga es típicamente menor al 5%.

El byte superior de un puntero de 64 bits ya era ignorado por el hardware. MTE reutiliza esos bits. Un puntero como 0xb7f0_0000_1234_5678 lleva la tag 0xb7. La memoria en esa dirección debe llevar la misma tag, o el acceso falla.

MTE está disponible en dispositivos con ARMv8.5-A o posterior. Eso incluye el Google Pixel 8 y posterior, el iPhone 15 Pro y posterior, y una porción creciente de dispositivos Android de gama media. No es universal, pero ya no es exótico.

El kernel expone MTE a través de banderas prctl. Una aplicación puede solicitar verificación síncrona o asíncrona. El modo síncrono falla inmediatamente en discrepancia de tag. El modo asíncrono encola el fallo y lo entrega después, lo cual es más barato pero retrasa ligeramente la detección.

GWP-ASan: Cuando no puedes etiquetar todo

MTE es genial, pero requiere hardware compatible y asignaciones etiquetadas en toda tu codebase. Si envías a dispositivos más antiguos, o si solo quieres protección dirigida, GWP-ASan es la alternativa pragmática.

GWP-ASan significa Guarded Write Protection AddressSanitizer. Es un asignador de muestreo que coloca una pequeña fracción de asignaciones de heap en páginas protegidas rodeadas por redzones envenenadas. Si un use-after-free o buffer overflow toca esas redzones, la MMU de hardware activa un fallo inmediatamente.

La idea clave es probabilidad. GWP-ASan podría proteger 1 de cada 10,000 asignaciones. Eso suena inútil hasta que te das cuenta de que una aplicación con bugs corromperá la memoria miles de veces antes de bloquearse. Dadas suficientes sesiones de usuario, incluso una tasa de muestreo del 0.01% detecta bugs reales en producción.

Google ha ejecutado GWP-ASan en Chrome y servicios del sistema Android durante años. Ha encontrado cientos de bugs de use-after-free que escaparon a las pruebas y al fuzzing. La sobrecarga de CPU es insignificante porque solo una fracción mínima de asignaciones pasa por el camino protegido. La sobrecarga de memoria está acotada porque el pool protegido es pequeño y las asignaciones eventualmente se reciclan.

Los compromisos de los que nadie quiere hablar

MTE y GWP-ASan no son gratuitos. Solo son lo suficientemente baratos para valer la pena.

MTE requiere el byte superior de cada puntero, lo que significa que tu codebase debe compilarse con -fsanitize=memtag o -march=armv8.5-a+memtag. Si tienes código que enmascara punteros, los hashea o los pasa a través de JNI sin etiquetado adecuado, obtendrás falsos positivos. El Android NDK y las implementaciones modernas de libc manejan esto correctamente, pero los custom allocators o los esquemas de empaquetado de punteros fallarán.

MTE también solo detecta bugs dentro de las regiones de heap y stack que tags explícitamente. Un puntero salvaje que salta a una región mmap no etiquetada no será detectado. Esto es mejor que nada, pero no es cobertura total.

GWP-ASan tiene el problema opuesto. Solo detecta las asignaciones que suceden caer en el pool protegido. Un bug que corrompe una asignación no protegida pasará desapercibido. También pagas un pequeño costo de latencia en asignación y desasignación para el conjunto muestreado, y necesitas manejar los bloqueos elegantemente en tu pipeline de telemetry.

Ninguna herramienta reemplaza a ASan en tu pipeline de CI. Lo complementan. ASan te da detección determinística de alta cobertura durante las pruebas. MTE y GWP-ASan te dan una red de seguridad en el campo.

Habilitando MTE en un dispositivo Android moderno

Si tienes un Pixel 8 o más nuevo, puedes probar MTE hoy. El Android NDK lo admite con una sola bandera del compiler.

Primero, verifica si tu dispositivo admite 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;
}

Luego compila tu biblioteca nativa con MTE habilitado en tu CMakeLists.txt:

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

Si quieres fallos síncronos, que es lo que típicamente quieres en producción, solicítalo al inicio:

#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);
    }
}

La bandera PR_MTE_TCF_SYNC le dice al kernel que falle inmediatamente en discrepancia de tag. La máscara de tag 0xfffe excluye la tag cero, que todavía usa algo de código del sistema.

Configurando GWP-ASan para producción

GWP-ASan es más fácil de habilitar porque no requiere hardware especial. En Android, puedes vincular contra el wrapper del asignador GWP-ASan o habilitarlo a través del asignador del sistema en niveles de API más nuevos.

Para una integración mínima, envuelve tus puntos de entrada del asignador:

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

En la práctica, usarás el runtime de Android GWP-ASan o la implementación de LLVM compiler-rt. La perilla de configuración clave es la tasa de muestreo. Empieza conservador:

// Una asignación protegida por cada 10,000
__gwp_asan_set_sample_rate(10000);

Recopila los bloqueos resultantes a través de tu telemetry existente. Un bloqueo de GWP-ASan parece un SIGSEGV estándar, pero la dirección que causa el fallo caerá en una página protegida. Tu reportero de bloqueos puede detectar esto verificando si la dirección que causa el fallo cae dentro del pool de GWP-ASan.

Lo que deberías hacer realmente

Si envías código nativo en móvil, ya deberías estar ejecutando ASan en CI y fuzzing. La pregunta es qué pasa después de que envías.

Si tu dispositivo mínimo soportado incluye chips ARMv8.5-A, habilita MTE en modo síncrono. La sobrecarga es lo suficientemente baja como para que los usuarios no la noten, y los bloqueos que obtengas tendrán información precisa de discrepancia de tag en lugar de corrupción de heap aleatoria.

Si soportas dispositivos más antiguos, habilita GWP-ASan con una tasa de muestreo conservadora. No atrapará cada bug, pero atrapará bugs que nada más atrapa. Durante millones de sesiones, eso no es teórico. Así es como Chrome encontró un use-after-free en su decoder de imágenes el año pasado.

El hardware en tu teléfono ya es capaz de atrapar los bugs de seguridad de memoria que más temes. La única pregunta es si lo has encendido.