A partir de 2016, alrededor del 86% de todas las vulnerabilidades de Android están relacionadas con la seguridad de la memoria. Los atacantes aprovechan la mayoría de las vulnerabilidades cambiando el flujo de control normal de una app para realizar actividades maliciosas arbitrarias con todos los privilegios de la app explotada. La integridad del flujo de control (CFI) es un mecanismo de seguridad que no permite cambios en el gráfico del flujo de control original de un objeto binario compilado, lo que dificulta considerablemente que se produzcan estos ataques.
En Android 8.1, habilitamos la implementación de CFI de LLVM en la pila de medios. En Android 9, habilitamos la CFI en más componentes y también en el kernel. La CFI del sistema está activada de forma predeterminada, pero debes habilitar la CFI del kernel.
La CFI de LLVM requiere la compilación con optimización del tiempo de vinculación (LTO). El LTO preserva la representación de código de bits de LLVM de los archivos objeto hasta el tiempo de vinculación, lo que permite que el compilador razone mejor sobre qué optimizaciones se pueden realizar. Habilitar LTO reduce el tamaño del objeto binario final y mejora el rendimiento, pero aumenta el tiempo de compilación. En las pruebas en Android, la combinación de LTO y CFI genera una sobrecarga insignificante en el tamaño y el rendimiento del código. En algunos casos, ambos mejoraron.
Para obtener más detalles técnicos sobre la CFI y cómo se manejan otras verificaciones de control de transferencia, consulta la documentación de diseño de LLVM.
Ejemplos y fuente
El compilador proporciona la CFI y agrega instrumentación al objeto binario durante el tiempo de compilación. Admitimos CFI en la cadena de herramientas de Clang y el sistema de compilación de Android en AOSP.
La CFI está habilitada de forma predeterminada para los dispositivos Arm64 para el conjunto de componentes en /platform/build/target/product/cfi-common.mk
.
También se habilita directamente en un conjunto de archivos de makefile o de plantilla de componentes multimedia, como /platform/frameworks/av/media/libmedia/Android.bp
y /platform/frameworks/av/cmds/stagefright/Android.mk
.
Implementa la CFI del sistema
La CFI está habilitada de forma predeterminada si usas Clang y el sistema de compilación de Android. Debido a que la CFI ayuda a proteger a los usuarios de Android, no debes inhabilitarla.
De hecho, te recomendamos que habilites la CFI para componentes adicionales. Los candidatos ideales son el código nativo con privilegios o el código nativo que procesa entradas de usuario no confiables. Si usas clang y el sistema de compilación de Android, puedes habilitar la CFI en componentes nuevos agregando algunas líneas a tus archivos de make o de plantillas.
Compatibilidad con CFI en archivos makefile
Para habilitar CFI en un archivo make, como /platform/frameworks/av/cmds/stagefright/Android.mk
, agrega lo siguiente:
LOCAL_SANITIZE := cfi # Optional features LOCAL_SANITIZE_DIAG := cfi LOCAL_SANITIZE_BLACKLIST := cfi_blacklist.txt
LOCAL_SANITIZE
especifica CFI como el validador durante la compilación.LOCAL_SANITIZE_DIAG
activa el modo de diagnóstico para CFI. El modo de diagnóstico imprime información de depuración adicional en logcat durante los fallas, lo que es útil durante el desarrollo y las pruebas de tus compilaciones. Sin embargo, asegúrate de quitar el modo de diagnóstico en las compilaciones de producción.LOCAL_SANITIZE_BLACKLIST
permite que los componentes inhabiliten de forma selectiva la instrumentación de CFI para funciones o archivos de origen individuales. Puedes usar una lista de entidades bloqueadas como último recurso para solucionar cualquier problema que pueda existir para los usuarios. Para obtener más detalles, consulta Cómo inhabilitar CFI.
Compatibilidad con CFI en archivos de esquemas
Para habilitar CFI en un archivo de modelo, como /platform/frameworks/av/media/libmedia/Android.bp
, agrega lo siguiente:
sanitize: { cfi: true, diag: { cfi: true, }, blacklist: "cfi_blacklist.txt", },
Solución de problemas
Si habilitas la CFI en componentes nuevos, es posible que encuentres algunos problemas con los errores de discrepancia de tipo de función y los errores de discrepancia de tipo de código de ensamblado.
Los errores de discrepancia de tipo de función se producen porque la CFI restringe las llamadas indirectas para que solo salten a funciones que tengan el mismo tipo dinámico que el tipo estático que se usa en la llamada. La CFI restringe las llamadas a funciones miembro virtuales y no virtuales para que solo salten a objetos que sean una clase derivada del tipo estático del objeto que se usa para realizar la llamada. Esto significa que, cuando tengas código que incumpla cualquiera de estas suposiciones, se abortará la instrumentación que agrega CFI. Por ejemplo, el seguimiento de pila muestra un SIGABRT y logcat contiene una línea sobre la integridad del flujo de control que encuentra una discrepancia.
Para solucionar este problema, asegúrate de que la función llamada tenga el mismo tipo que se declaró de forma estática. Estos son dos ejemplos de CL:
- Bluetooth: /c/platform/system/bt/+/532377
- NFC: /c/platform/system/nfc/+/527858
Otro problema posible es intentar habilitar la CFI en un código que contiene llamadas indirectas al ensamblado. Debido a que el código de ensamblado no está escrito, se produce una discrepancia de tipo.
Para solucionar este problema, crea wrappers de código nativo para cada llamada de ensamblado y asígnales la misma firma de función que el puntero de llamada. Luego, el wrapper puede llamar directamente al código de ensamblado. Debido a que CFI no instrumenta las ramas directas (no se pueden volver a apuntar durante el tiempo de ejecución y, por lo tanto, no representan un riesgo de seguridad), esto solucionará el problema.
Si hay demasiadas funciones de ensamblado y no se pueden corregir todas, también puedes incluir en la lista de entidades bloqueadas todas las funciones que contengan llamadas indirectas al ensamblado. No se recomienda, ya que inhabilita las verificaciones de CFI en estas funciones, lo que abre la superficie de ataque.
Inhabilita CFI
No observamos ninguna sobrecarga de rendimiento, por lo que no deberías necesitar inhabilitar CFI. Sin embargo, si hay un impacto para el usuario, puedes inhabilitar de forma selectiva la CFI para funciones o archivos de origen individuales proporcionando un archivo de lista de entidades bloqueadas del validador en el momento de la compilación. La lista negra le indica al compilador que inhabilite la instrumentación de CFI en ubicaciones específicas.
El sistema de compilación de Android admite listas negras por componente (lo que te permite elegir archivos de origen o funciones individuales que no recibirán instrumentación de CFI) para Make y Soong. Para obtener más detalles sobre el formato de un archivo de lista de entidades bloqueadas, consulta la documentación de Clang upstream.
Validación
Actualmente, no hay pruebas de CTS específicas para CFI. En su lugar, asegúrate de que las pruebas de CTS aprueben con o sin CFI habilitado para verificar que CFI no afecte al dispositivo.