Intégrité du flux de contrôle

En 2016, environ 86 % de toutes les failles sur Android étaient liées à la sécurité de la mémoire. La plupart des failles sont exploitées par des pirates informatiques qui modifient 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 toute modification du graphique de flux de contrôle d'origine d'un binaire compilé, ce qui rend les attaques de ce type beaucoup plus difficiles à réaliser.

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é la CFI dans davantage de composants et dans le noyau. La CFI système est activée par défaut, mais vous devez activer la CFI du noyau.

La CFI de LLVM nécessite une compilation avec l'optimisation au moment de l'édition des liens (LTO). LTO préserve la représentation LLVM en bitcode des fichiers objets jusqu'à l'édition des liens, ce qui permet au compilateur de mieux comprendre les 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 de CFI entraîne une surcharge négligeable sur la taille et les performances du code. Dans certains cas, les deux ont été améliorés.

Pour en savoir plus sur le CFI et sur la façon dont les autres vérifications de contrôle direct sont gérées, consultez la documentation sur la conception de LLVM.

Exemples et source

La CFI est fournie par le compilateur et ajoute une instrumentation au fichier binaire lors de la compilation. Nous acceptons CFI dans la chaîne d'outils Clang et le système de compilation Android dans AOSP.

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

Implémenter le CFI du système

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

En fait, nous vous encourageons vivement à activer la CFI pour d'autres composants. 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 compilation Android, vous pouvez activer la CFI dans les nouveaux composants en ajoutant quelques lignes à vos fichiers makefile ou blueprint.

Prise en charge de CFI dans les fichiers makefile

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 outil de désinfection lors de la compilation.
  • LOCAL_SANITIZE_DIAG active le mode diagnostic pour CFI. Le mode diagnostic affiche des informations de débogage supplémentaires dans logcat en cas de plantage, ce qui est utile lors du développement et du test de vos builds. Veillez toutefois à supprimer le mode diagnostic dans les versions de production.
  • LOCAL_SANITIZE_BLACKLIST permet aux composants de désactiver sélectivement l'instrumentation CFI pour des fonctions ou des fichiers sources individuels. Vous pouvez utiliser une liste noire en dernier recours pour résoudre les problèmes rencontrés par les utilisateurs. Pour en savoir plus, consultez Désactiver CFI.

Prise en charge de CFI dans les fichiers de plan

Pour activer CFI dans un fichier de plan, 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 la CFI dans de nouveaux composants, vous pouvez rencontrer quelques problèmes liés aux erreurs d'incompatibilité de type de fonction et aux erreurs d'incompatibilité de type de code d'assemblage.

Les erreurs d'incohérence de type de fonction se produisent, car CFI limite les appels indirects aux fonctions qui ont le même type dynamique que le type statique utilisé dans l'appel. La CFI limite les appels de fonctions membres virtuels et non virtuels 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 enfreint l'une de ces hypothèses, l'instrumentation ajoutée par CFI s'arrête. Par exemple, la trace de pile affiche un SIGABRT et logcat contient une ligne indiquant que l'intégrité du flux de contrôle a trouvé une incohérence.

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 la tentative d'activation de la CFI dans un code contenant des appels indirects à l'assemblage. Comme 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'assemblage et donnez aux wrappers la même signature de fonction que le pointeur d'appel. Le wrapper peut ensuite appeler directement le code assembleur. Comme 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 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 qui contiennent des appels indirects à l'assemblage. Cette approche n'est pas recommandée, car elle désactive les vérifications CFI sur ces fonctions, ce qui ouvre une surface d'attaque.

Désactiver CFI

Nous n'avons observé aucune surcharge de performances. Vous ne devriez donc pas avoir besoin de désactiver la CFI. Toutefois, en cas d'impact sur l'utilisateur, vous pouvez désactiver sélectivement la CFI pour des fonctions ou des fichiers sources individuels en fournissant un fichier de liste noire de l'outil de désinfection au moment de la compilation. La liste noire indique au compilateur de désactiver l'instrumentation CFI à des emplacements spécifiques.

Le système de compilation Android est compatible avec les listes noires par composant (qui vous permettent de choisir les fichiers sources ou les fonctions individuelles qui ne recevront pas d'instrumentation CFI) pour Make et Soong. Pour en savoir plus sur le format d'un fichier de liste noire, consultez la documentation Clang en amont.

Validation

Actuellement, aucun test CTS n'est spécifiquement prévu pour CFI. Assurez-vous plutôt que les tests CTS réussissent avec ou sans CFI activé pour vérifier que CFI n'a pas d'impact sur l'appareil.