Intégrité du flux de contrôle

Depuis 2016, environ 86% de toutes les failles de sécurité sur Android sont liées à la sécurité de la mémoire. La plupart des failles de sécurité sont exploitées par des pirates 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 la CFI par LLVM dans la pile multimédia. Dans Android 9, nous avons activé la CFI dans davantage de composants, ainsi que dans le noyau. La CFI du 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 la liaison (LTO). La LTO préserve la représentation de bitcode LLVM des fichiers objets jusqu'au moment de la liaison, ce qui permet au compilateur de mieux déterminer les optimisations à effectuer. L'activation de la 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 la LTO et de la CFI entraîne une surcharge négligeable de la taille et des performances du code. Dans certains cas, les deux ont été améliorés.

Pour obtenir plus d'informations techniques sur la CFI et sur la gestion des autres vérifications de contrôle direct gérées, consultez la documentation de 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 prenons en charge 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. Elle est également activée directement 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.

Implémenter la CFI du système

La CFI est activée par défaut si vous utilisez Clang et le système de compilation Android. Comme la CFI contribue à protéger les utilisateurs d'Android, vous ne devez pas la désactiver.

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

Prendre en charge la CFI dans les fichiers makefile

Pour activer la CFI dans un fichier make, tel que /platform/frameworks/av/cmds/stagefright/Android.mk, ajoutez les éléments suivants :

LOCAL_SANITIZE := cfi
# Optional features
LOCAL_SANITIZE_DIAG := cfi
LOCAL_SANITIZE_BLACKLIST := cfi_blacklist.txt
  • LOCAL_SANITIZE spécifie la CFI comme assainisseur lors de la compilation.
  • LOCAL_SANITIZE_DIAG active le mode de diagnostic pour la CFI. Le mode de 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 de diagnostic sur 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 rencontrés par les utilisateurs qui pourraient autrement exister. Pour en savoir plus, consultez Désactiver la CFI.

Prendre en charge la CFI dans les fichiers blueprint

Pour activer la CFI dans un fichier blueprint, tel que /platform/frameworks/av/media/libmedia/Android.bp, ajoutez les éléments suivants :

   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 avec des erreurs de type de fonction non concordant et des erreurs de type de code d'assemblage non concordant.

Les erreurs de type de fonction non concordant se produisent, car la 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 fonction membre 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 ne respecte pas l'une de ces hypothèses, l'instrumentation ajoutée par la CFI est abandonnée. Par exemple, la trace de la pile affiche un SIGABRT et logcat contient une ligne indiquant qu'un problème de concordance a été détecté au niveau de l'intégrité du flux de contrôle.

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

Un autre problème possible consiste à essayer d'activer la CFI dans du code contenant des appels indirects à l'assemblage. Comme le code d'assemblage n'est pas typé, cela entraîne une non-concordance de type.

Pour résoudre ce problème, créez des wrappers de code natif pour chaque appel d'assemblage et attribuez aux wrappers la même signature de fonction que le pointeur appelant. Le wrapper peut ensuite appeler directement le code d'assemblage. Comme les branches directes ne sont pas instrumentées par la CFI (elles ne peuvent pas être repointées au moment de l'exécution et ne présentent donc pas de risque de 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 en liste noire toutes les fonctions contenant des appels indirects à l'assemblage. Cette approche n'est pas recommandée, car elle désactive les vérifications de la CFI sur ces fonctions, ce qui ouvre une surface d'attaque.

Désactiver la CFI

Nous n'avons observé aucune surcharge de performances. Vous ne devriez donc pas avoir besoin de désactiver la CFI. Toutefois, si cela a un 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 d'assainisseur au temps de compilation. La liste noire indique au compilateur de désactiver l'instrumentation CFI à des emplacements spécifiés.

Le système de compilation Android prend en charge les listes noires par composant (ce qui vous permet 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, il n'existe aucun test CTS spécifique à la CFI. Assurez-vous plutôt que les tests CTS réussissent avec ou sans la CFI activée pour vérifier que la CFI n'a pas d'impact sur l'appareil.