Integridad del flujo de control

A partir de 2016, alrededor del 86 % de todas las vulnerabilidades en Android están relacionadas con la seguridad de la memoria. La mayoría de las vulnerabilidades son explotadas por atacantes que cambian el flujo de control normal de una aplicación para realizar actividades maliciosas arbitrarias con todos los privilegios de la aplicación explotada. La integridad del flujo de control (CFI) es un mecanismo de seguridad que no permite cambios en el gráfico de flujo de control original de un binario compilado, lo que dificulta significativamente la realización de este tipo de ataques.

En Android 8.1, habilitamos la implementación de CFI de LLVM en la pila de medios. En Android 9, habilitamos CFI en más componentes y también en el kernel. El CFI del sistema está activado de forma predeterminada, pero debe habilitar el CFI del kernel.

El CFI de LLVM requiere compilación con Link-Time Optimization (LTO) . LTO conserva la representación de código de bits LLVM de los archivos de objetos hasta el momento del enlace, lo que permite al compilador razonar mejor sobre qué optimizaciones se pueden realizar. Habilitar LTO reduce el tamaño del 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 da como resultado una sobrecarga insignificante para el tamaño y el rendimiento del código; en algunos casos ambos mejoraron.

Para obtener más detalles técnicos sobre CFI y cómo se manejan otras verificaciones de control directo, consulte la documentación de diseño de LLVM .

Ejemplos y fuente

El compilador proporciona CFI y agrega instrumentación al 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.

CFI está habilitado de forma predeterminada para los dispositivos Arm64 para el conjunto de componentes en /platform/build/target/product/cfi-common.mk . También está habilitado directamente en un conjunto de archivos makefile/blueprint de componentes multimedia, como /platform/frameworks/av/media/libmedia/Android.bp y /platform/frameworks/av/cmds/stagefright/Android.mk .

Implementación del sistema CFI

CFI está habilitado de forma predeterminada si usa Clang y el sistema de compilación de Android. Debido a que CFI ayuda a mantener seguros a los usuarios de Android, no debe deshabilitarlo.

De hecho, le recomendamos encarecidamente que habilite CFI para componentes adicionales. Los candidatos ideales son el código nativo privilegiado o el código nativo que procesa la entrada del usuario que no es de confianza. Si usa clang y el sistema de compilación de Android, puede habilitar CFI en nuevos componentes agregando algunas líneas a sus archivos MAKE o blueprint.

Compatibilidad con CFI en archivos MAKE

Para habilitar CFI en un archivo de creación, como /platform/frameworks/av/cmds/stagefright/Android.mk , agregue:

LOCAL_SANITIZE := cfi
# Optional features
LOCAL_SANITIZE_DIAG := cfi
LOCAL_SANITIZE_BLACKLIST := cfi_blacklist.txt
  • LOCAL_SANITIZE especifica CFI como desinfectante 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 bloqueos, lo que es útil al desarrollar y probar sus compilaciones. Sin embargo, asegúrese de eliminar el modo de diagnóstico en las compilaciones de producción.
  • LOCAL_SANITIZE_BLACKLIST permite que los componentes deshabiliten selectivamente la instrumentación CFI para funciones individuales o archivos fuente. Puede usar una lista negra como último recurso para solucionar cualquier problema que pueda tener el usuario. Para obtener más detalles, consulte Deshabilitar CFI .

Compatibilidad con CFI en archivos blueprint

Para habilitar CFI en un archivo blueprint, como /platform/frameworks/av/media/libmedia/Android.bp , agregue:

   sanitize: {
        cfi: true,
        diag: {
            cfi: true,
        },
        blacklist: "cfi_blacklist.txt",
    },

Solución de problemas

Si está habilitando CFI en componentes nuevos, es posible que tenga algunos problemas con errores de discrepancia de tipo de función y errores de discrepancia de tipo de código ensamblador .

Los errores de discrepancia de tipo de función se producen porque CFI restringe las llamadas indirectas para que solo salten a funciones que tienen el mismo tipo dinámico que el tipo estático utilizado en la llamada. CFI restringe las llamadas a funciones de miembros virtuales y no virtuales para saltar solo a objetos que son una clase derivada del tipo estático del objeto utilizado para realizar la llamada. Esto significa que, cuando tenga un código que viole cualquiera de estas suposiciones, la instrumentación que agrega CFI se cancelará. Por ejemplo, el seguimiento de la pila muestra un SIGABRT y logcat contiene una línea sobre la integridad del flujo de control que encuentra una discrepancia.

Para solucionar esto, asegúrese de que la función llamada tenga el mismo tipo que se declaró estáticamente. Aquí hay dos CL de ejemplo:

Otro posible problema es intentar habilitar CFI en código que contiene llamadas indirectas a ensamblador. Debido a que el código ensamblador no se escribe, esto da como resultado una falta de coincidencia de tipos.

Para solucionar esto, cree contenedores de código nativo para cada llamada de ensamblaje y asigne a los contenedores la misma firma de función que el puntero de llamada. El contenedor puede entonces llamar directamente al código ensamblador. Debido a que las ramas directas no están instrumentadas por CFI (no se pueden volver a señalar en 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 arreglar todas, también puede incluir en la lista negra todas las funciones que contienen llamadas indirectas a ensamblar. Esto no se recomienda ya que deshabilita las comprobaciones de CFI en estas funciones, lo que abre la superficie de ataque.

Deshabilitar CFI

No observamos ninguna sobrecarga de rendimiento, por lo que no debería necesitar desactivar CFI. Sin embargo, si hay un impacto de cara al usuario, puede deshabilitar selectivamente CFI para funciones individuales o archivos de origen proporcionando un archivo de lista negra de desinfectante en el momento de la compilación. La lista negra le indica al compilador que deshabilite la instrumentación CFI en ubicaciones específicas.

El sistema de compilación de Android admite listas negras por componente (lo que le permite elegir archivos de origen o funciones individuales que no recibirán instrumentación CFI) tanto para Make como para Soong. Para obtener más detalles sobre el formato de un archivo de lista negra, consulte los documentos de Clang anteriores .

Validación

Actualmente, no hay una prueba CTS específica para CFI. En su lugar, asegúrese de que las pruebas de CTS pasen con o sin CFI habilitado para verificar que CFI no esté afectando el dispositivo.