Intégrité du flux de contrôle

En 2016, environ 86% de toutes les failles sur Android sont 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 ces attaques beaucoup plus difficiles à effectuer.

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 également 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 la compilation avec l'optimisation au moment de l'édition de liens (LTO). LTO préserve la représentation du code binaire LLVM des fichiers d'objets jusqu'au moment de l'association, 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 de CFI entraîne une surcharge négligeable sur la taille et les performances du code. Dans certains cas, les deux sont améliorés.

Pour en savoir plus sur la CFI et la gestion des autres contrôles de transfert, consultez la documentation de conception LLVM.

Exemples et source

La CFI est fournie par le compilateur et ajoute une instrumentation au binaire au moment de la compilation. Nous acceptons la 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 activé directement dans un ensemble de fichiers de compilation/fichiers de modèle des composants multimédias, tels que /platform/frameworks/av/media/libmedia/Android.bp et /platform/frameworks/av/cmds/stagefright/Android.mk.

Implémenter la CFI du système

CFI est activé par défaut si vous utilisez Clang et le système de compilation Android. Étant donné que le CFI contribue à protéger les utilisateurs Android, vous ne devez pas le désactiver.

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 approuvées. 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 de compilation ou de modèle.

Prise en charge de la CFI dans les fichiers make

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 nettoyage 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 lors des plantages, ce qui est utile lors du développement et du test de vos builds. Veillez toutefois à supprimer le mode diagnostic dans les builds 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 visibles par les utilisateurs qui pourraient exister. Pour en savoir plus, consultez la section Désactiver CFI.

Prise en charge de la CFI dans les fichiers de plan

Pour activer la CFI dans un fichier de modèle, 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 de non-concordance de type de fonction et aux erreurs de non-concordance de type de code d'assemblage.

Les erreurs de non-concordance de type de fonction se produisent, car le CFI limite les appels indirects à ne sauter que vers des fonctions ayant le même type dynamique que le type statique utilisé dans l'appel. La CFI limite les appels de fonction membre virtuels et non virtuels à des sauts uniquement vers des 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 ne respecte pas l'une de ces hypothèses, l'instrumentation ajoutée par CFI s'arrête. Par exemple, la trace de la pile affiche un SIGABRT et logcat contient une ligne sur l'intégrité du flux de contrôle qui détecte 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é de manière statique. Voici deux exemples de CL:

Un autre problème possible consiste à essayer d'activer le CFI dans du code contenant des appels indirects à l'assemblage. Étant donné que le code d'assemblage 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 attribuez aux wrappers la même signature de fonction que le pointeur d'appel. Le wrapper peut ensuite appeler directement le code d'assemblage. É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 présentent donc pas de risque de sécurité), le problème est résolu.

Si le nombre de fonctions d'assemblage est trop élevé et qu'elles ne peuvent pas toutes être corrigées, vous pouvez également ajouter à la liste de blocage toutes les fonctions contenant des appels indirects à l'assemblage. Cette opération n'est pas recommandée, car elle désactive les vérifications CFI sur ces fonctions, ce qui ouvre la surface d'attaque.

Désactivation de la CFI

Nous n'avons observé aucun surcoût de performances. Vous n'avez donc pas besoin de désactiver CFI. Toutefois, en cas d'impact visible par 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 nettoyeur au moment de la compilation. La liste noire indique au compilateur de désactiver l'instrumentation CFI aux emplacements spécifiés.

Le système de compilation Android est compatible avec les listes noires par composant (ce qui vous permet de choisir des fichiers sources ou des 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

Pour le moment, il n'existe aucun test CTS spécifique à la CFI. Assurez-vous plutôt que les tests CTS réussissent avec ou sans CFI activé pour vérifier que CFI n'a aucun impact sur l'appareil.