Memoria de solo ejecución (XOM) para objetos binarios AArch64

De forma predeterminada, las secciones de código ejecutable de los objetos binarios del sistema AArch64 están marcadas como de solo ejecución (no legibles) para endurecer la mitigación contra ataques de reutilización de código just-in-time. El código que combina los datos y el código, y el código que inspecciona deliberadamente esas secciones (sin reasignar primero los segmentos de la memoria como legibles) ya no funciona. Las apps con un SDK de destino de 10 (nivel de API 29 o versiones posteriores) se ven afectadas si la app intenta leer secciones de código de bibliotecas del sistema habilitadas con memoria de solo ejecución (XOM) en la memoria sin primero marcar la sección como legible.

Para aprovechar al máximo esta mitigación, se requiere compatibilidad con el hardware y el kernel. Sin esta compatibilidad, es posible que la mitigación solo se aplique de forma parcial. El núcleo común de Android 4.9 contiene los parches adecuados para brindar compatibilidad total con esto en dispositivos ARMv8.2.

Implementación

Los objetos binarios de AArch64 que genera el compilador suponen que el código y los datos no están mezclados. Habilitar esta función no afecta negativamente el rendimiento del dispositivo.

En el caso del código que debe realizar una introspección de memoria intencional en sus segmentos ejecutables, se recomienda llamar a mprotect en los segmentos de código que requieren inspección para permitir que sean legibles y, luego, quitar la legibilidad cuando se complete la inspección.
Esta implementación hace que las lecturas en los segmentos de memoria marcados como solo de ejecución generen una falla de segmentación (SEGFAULT). Esto puede ocurrir como resultado de un error, una vulnerabilidad, datos mezclados con código (agrupación literal) o una introspección intencional de la memoria.

Compatibilidad y impacto de los dispositivos

Es posible que los dispositivos con hardware o kernels anteriores (inferiores a 4.9) sin los parches necesarios no admitan por completo esta función ni se beneficien de ella. Es posible que los dispositivos sin compatibilidad con el kernel no apliquen los accesos de los usuarios a la memoria de solo ejecución. Sin embargo, el código del kernel que comprueba de forma explícita si una página es legible puede aplicar esta propiedad, como process_vm_readv().

La marca del kernel CONFIG_ARM64_UAO se debe establecer en el kernel para garantizar que este respete las páginas de espacio de usuario marcadas como de solo ejecución. Es posible que los dispositivos ARMv8 anteriores o los dispositivos ARMv8.2 con la anulación de acceso de usuario (UAO) inhabilitada no se beneficien por completo de esto y aún puedan leer páginas de solo ejecución con llamadas al sistema.

Refactoriza el código existente

El código que se portó de AArch32 puede contener datos y código mezclados, lo que puede generar problemas. En muchos casos, solucionar estos problemas es tan simple como mover las constantes a una sección .data en el archivo de ensamblado.

Es posible que se deba refactorizar el ensamblado escrito a mano para separar las constantes agrupadas de forma local.

Ejemplos:

Los objetos binarios que genera el compilador Clang no deberían tener problemas con los datos que se mezclan en el código. Si se incluye código generado por la colección de compiladores GNU (GCC) (desde una biblioteca estática), inspecciona el objeto binario de salida para asegurarte de que las constantes no se hayan agrupado en secciones de código.

Si la introspección de código es necesaria en secciones de código ejecutable, primero llama a mprotect para marcar el código como legible. Luego, una vez que se complete la operación, vuelve a llamar a mprotect para marcarla como no legible.

Habilita XOM

La ejecución solo está habilitada de forma predeterminada para todos los objetos binarios de 64 bits en el sistema de compilación.

Inhabilita XOM

Puedes inhabilitar la ejecución solo a nivel de un módulo, por un árbol de subdirectorios completo o de forma global para una compilación completa.

XOM se puede inhabilitar para módulos individuales que no se pueden refactorizar o que necesitan leer su código ejecutable. Para ello, configura las variables LOCAL_XOM y xom en false.

// Android.mk
LOCAL_XOM := false

// Android.bp
cc_binary { // or other module types
   ...
   xom: false,
}

Si la memoria de solo ejecución está inhabilitada en una biblioteca estática, el sistema de compilación la aplica a todos los módulos dependientes de esa biblioteca. Puedes anular esto con xom: true,.

Para inhabilitar la memoria de solo ejecución en un subdirectorio en particular (por ejemplo, foo/bar/), pasa el valor a XOM_EXCLUDE_PATHS.

make -j XOM_EXCLUDE_PATHS=foo/bar

Como alternativa, puedes establecer la variable PRODUCT_XOM_EXCLUDE_PATHS en la configuración de tu producto.

Para inhabilitar los objetos binarios de solo ejecución de forma global, pasa ENABLE_XOM=false a tu comando make.

make -j ENABLE_XOM=false

Validación

No hay CTS ni pruebas de verificación disponibles para la memoria de solo ejecución. Puedes verificar los objetos binarios de forma manual con readelf y verificar las marcas de segmento.