Controlar la integridad del flujo

En 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 aprovechadas 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 dichos 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 es necesario habilitar el CFI del kernel.

El CFI de LLVM requiere compilación con Link-Time Optimization (LTO) . LTO conserva la representación del código de bits LLVM de los archivos objeto hasta el momento del enlace, lo que permite al compilador razonar mejor 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 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 CFI y cómo se manejan otras comprobaciones 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 Clang y el sistema de compilación de Android en AOSP.

CFI está habilitado de forma predeterminada para 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 makefiles/blueprint de componentes multimedia, como /platform/frameworks/av/media/libmedia/Android.bp y /platform/frameworks/av/cmds/stagefright/Android.mk .

Sistema de implementación 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 debes desactivarlo.

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 entradas de usuarios que no son de confianza. Si está utilizando clang y el sistema de compilación de Android, puede habilitar CFI en componentes nuevos agregando algunas líneas a sus archivos MAKE o blueprint.

Soporte a CFI en makefiles

Para habilitar CFI en un archivo make, 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 fallas, lo cual 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 utilizar una lista negra como último recurso para solucionar cualquier problema relacionado con el usuario que de otro modo podría existir. Para obtener más detalles, consulte Desactivación de CFI .

Soporte a CFI en archivos de planos

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

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

Solución de problemas

Si habilita CFI en componentes nuevos, puede encontrarse con algunos problemas con errores de discrepancia de tipos de funciones y errores de discrepancia de tipos de códigos ensambladores .

Los errores de discrepancia de tipos de funciones se producen porque CFI restringe las llamadas indirectas para saltar únicamente a funciones que tienen el mismo tipo dinámico que el tipo estático utilizado en la llamada. CFI restringe las llamadas a funciones miembro virtuales y no virtuales para saltar únicamente a objetos que son una clase derivada del tipo estático del objeto utilizado para realizar la llamada. Esto significa que, cuando tiene un código que viola cualquiera de estos supuestos, la instrumentación que agrega CFI abortará. 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 este problema, asegúrese de que la función llamada tenga el mismo tipo que se declaró estáticamente. A continuación se muestran dos CL de ejemplo:

Otro posible problema es intentar habilitar CFI en código que contiene llamadas indirectas al ensamblador. Debido a que el código ensamblador no está escrito, esto da como resultado una discrepancia de tipos.

Para solucionar este problema, cree contenedores de código nativo para cada llamada de ensamblado y asigne a los contenedores la misma firma de función que el puntero de llamada. Luego, el contenedor puede llamar directamente al código ensamblador. Debido a que CFI no instrumenta las bifurcaciones directas (no se pueden redirigir en tiempo de ejecución y, por lo tanto, no representan un riesgo de seguridad), esto solucionará el problema.

Si hay demasiadas funciones de ensamblador y no se pueden arreglar todas, también puede incluir en la lista negra todas las funciones que contengan llamadas indirectas al ensamblador. Esto no se recomienda ya que deshabilita las comprobaciones de CFI en estas funciones, abriendo así una superficie de ataque.

Deshabilitar CFI

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

El sistema de compilación de Android brinda soporte para listas negras por componente (lo que le permite elegir archivos fuente 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 anteriores de Clang .

Validación

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