Intégrité du flux de contrôle

En 2016, environ 86 % de toutes les vulnérabilités sur Android sont 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 graphe de flux de contrôle d'origine d'un binaire compilé, ce qui rend beaucoup plus difficile l'exécution 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 plus 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 du code binaire LLVM des fichiers objets jusqu'au moment de la liaison, ce qui permet au compilateur de mieux raisonner sur les optimisations pouvant ê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 pour la taille et les performances du code ; dans quelques cas, les deux se sont améliorés.

Pour plus de détails techniques sur CFI et sur la manière dont les autres vérifications de contrôle avancé sont gérées, 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 construction Android dans AOSP.

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

Système de mise en œuvre CFI

CFI est activé par défaut si vous utilisez Clang et le système de construction Android. Étant donné que CFI aide à protéger les 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 construction Android, vous pouvez activer CFI dans de nouveaux composants en ajoutant quelques lignes à vos fichiers makefile ou blueprint.

Prise en charge de CFI 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 des plantages, 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 de manière sélective 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 CFI dans les fichiers de plans

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 pouvez rencontrer quelques problèmes avec des erreurs de non- concordance de type de fonction et des erreurs de non- concordance de type de code d'assemblage .

Des erreurs d'incompatibilité de type de fonction se produisent parce que CFI limite les appels indirects pour accéder uniquement aux fonctions qui ont le même type dynamique que le type statique utilisé dans l'appel. CFI limite 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 CFI s'arrêtera. 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 non-concordance.

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 consiste à essayer d'activer CFI dans du 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 natif pour chaque appel d'assembly et donnez 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 lors de l'exécution et ne présentent donc pas de risque pour la sécurité), cela résoudra le problème.

S'il y a trop de fonctions d'assemblage et qu'elles ne peuvent pas toutes être corrigées, vous pouvez également mettre sur liste noire toutes les fonctions contenant des appels indirects à l'assemblage. Ceci n'est pas recommandé car cela désactive les vérifications CFI de 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 nettoyage au moment de la compilation. La liste noire demande au compilateur de désactiver l'instrumentation CFI aux emplacements spécifiés.

Le système de construction Android prend en charge les listes noires par composant (vous permettant de choisir des fichiers source ou des fonctions individuelles qui ne recevront pas l'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'y a 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.