Intégrité du flux de contrôle

En 2016, environ 86 % de toutes les vulnérabilités sur Android étaient liées à la sécurité de la mémoire. La plupart des vulnérabilités sont exploitées par des attaquants modifiant le flux de contrôle normal d'une application pour effectuer des activités malveillantes arbitraires avec tous les privilèges de l'application exploitée. L'intégrité du flux de contrôle (CFI) est un mécanisme de sécurité qui interdit les modifications du graphique de flux de contrôle d'origine d'un binaire compilé, ce qui rend beaucoup plus difficile la réalisation de telles attaques.

Dans Android 8.1, nous avons activé l'implémentation de CFI par LLVM dans la pile multimédia. Dans Android 9, nous avons activé CFI dans davantage de composants ainsi que dans le noyau. Le système CFI est activé par défaut mais vous devez activer le noyau CFI.

Le CFI de LLVM nécessite une compilation avec Link-Time Optimization (LTO) . LTO préserve la représentation bitcode LLVM des fichiers objets jusqu'au moment de la liaison, ce qui permet au compilateur de mieux réfléchir aux optimisations qui peuvent être effectuées. L'activation de LTO réduit la taille du binaire final et améliore les performances, mais augmente le temps de compilation. Lors des tests sur Android, la combinaison de LTO et CFI entraîne une surcharge négligeable en termes de taille et de performances du code ; dans quelques cas, les deux se sont améliorés.

Pour plus de détails techniques sur CFI et sur la façon dont les autres contrôles de contrôle avant sont traités, consultez la documentation de conception LLVM .

Exemples et source

CFI est fourni par le compilateur et ajoute une instrumentation dans le binaire pendant la compilation. Nous prenons en charge CFI dans la chaîne d'outils Clang et le système de build Android dans AOSP.

CFI est activé par défaut pour les appareils Arm64 pour l'ensemble des composants dans /platform/build/target/product/cfi-common.mk . Il est également directement activé dans un ensemble de fichiers makefile/blueprint de composants multimédias, tels que /platform/frameworks/av/media/libmedia/Android.bp et /platform/frameworks/av/cmds/stagefright/Android.mk .

Mise en œuvre du système CFI

CFI est activé par défaut si vous utilisez Clang et le système de build Android. Étant donné que Finance contribue à assurer la sécurité des utilisateurs d'Android, vous ne devez pas le désactiver.

En fait, nous vous encourageons fortement à activer CFI pour des composants supplémentaires. Les candidats idéaux sont le code natif privilégié ou le code natif qui traite les entrées utilisateur non fiables. Si vous utilisez Clang et le système de build Android, vous pouvez activer CFI dans de nouveaux composants en ajoutant quelques lignes à vos makefiles ou fichiers blueprint.

Prise en charge de Finance dans les makefiles

Pour activer CFI dans un fichier make, tel que /platform/frameworks/av/cmds/stagefright/Android.mk , ajoutez :

LOCAL_SANITIZE := cfi
# Optional features
LOCAL_SANITIZE_DIAG := cfi
LOCAL_SANITIZE_BLACKLIST := cfi_blacklist.txt
  • LOCAL_SANITIZE spécifie CFI comme désinfectant pendant la construction.
  • LOCAL_SANITIZE_DIAG active le mode de diagnostic pour CFI. Le mode diagnostic imprime des informations de débogage supplémentaires dans logcat lors de pannes, ce qui est utile lors du développement et du test de vos builds. Assurez-vous cependant de supprimer le mode de diagnostic sur les versions de production.
  • LOCAL_SANITIZE_BLACKLIST permet aux composants de désactiver sélectivement l'instrumentation CFI pour des fonctions individuelles ou des fichiers source. Vous pouvez utiliser une liste noire en dernier recours pour résoudre tout problème rencontré par l'utilisateur qui pourrait autrement exister. Pour plus de détails, voir Désactivation de CFI .

Prise en charge de Finance dans les fichiers de modèles

Pour activer CFI dans un fichier Blueprint, tel que /platform/frameworks/av/media/libmedia/Android.bp , ajoutez :

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

Dépannage

Si vous activez CFI dans de nouveaux composants, vous risquez de rencontrer quelques problèmes liés aux erreurs de non-concordance des types de fonctions et aux erreurs de non-concordance des types de code assembleur .

Des erreurs de non-concordance de type de fonction se produisent car CFI restreint les appels indirects à accéder uniquement aux fonctions qui ont le même type dynamique que le type statique utilisé dans l'appel. CFI restreint les appels de fonctions membres virtuelles et non virtuelles pour accéder uniquement aux objets qui sont une classe dérivée du type statique de l'objet utilisé pour effectuer l'appel. Cela signifie que lorsque vous avez du code qui viole l'une de ces hypothèses, l'instrumentation ajoutée par Finance s'abandonnera. Par exemple, la trace de la pile affiche un SIGABRT et logcat contient une ligne sur l'intégrité du flux de contrôle trouvant une incompatibilité.

Pour résoudre ce problème, assurez-vous que la fonction appelée a le même type que celui déclaré statiquement. Voici deux exemples de CL :

Un autre problème possible est de tenter d'activer CFI dans un code contenant des appels indirects à l'assembly. Étant donné que le code assembleur n’est pas typé, cela entraîne une incompatibilité de type.

Pour résoudre ce problème, créez des wrappers de code natifs pour chaque appel d’assembly et attribuez aux wrappers la même signature de fonction que le pointeur appelant. Le wrapper peut alors appeler directement le code assembleur. Étant donné que les branches directes ne sont pas instrumentées par CFI (elles ne peuvent pas être redirigées au moment de l'exécution et ne posent donc pas de risque de sécurité), cela résoudra le problème.

S'il y a trop de fonctions d'assembly et qu'elles ne peuvent pas toutes être corrigées, vous pouvez également mettre sur liste noire toutes les fonctions contenant des appels indirects à assembly. Ceci n'est pas recommandé car cela désactive les contrôles CFI sur ces fonctions, ouvrant ainsi la surface d'attaque.

Désactiver CFI

Nous n'avons observé aucune surcharge de performances, vous ne devriez donc pas avoir besoin de désactiver CFI. Cependant, s'il y a un impact sur l'utilisateur, vous pouvez désactiver sélectivement CFI pour des fonctions individuelles ou des fichiers source en fournissant un fichier de liste noire de désinfectant au moment de la compilation. La liste noire demande au compilateur de désactiver l'instrumentation CFI dans les emplacements spécifiés.

Le système de construction Android prend en charge les listes noires par composant (vous permettant de choisir des fichiers sources ou des fonctions individuelles qui ne recevront pas d'instrumentation CFI) pour Make et Soong. Pour plus de détails sur le format d'un fichier de liste noire, consultez la documentation Clang en amont .

Validation

Actuellement, il n'existe pas de test CTS spécifiquement pour CFI. Au lieu de cela, assurez-vous que les tests CTS réussissent avec ou sans CFI activé pour vérifier que CFI n'a pas d'impact sur l'appareil.