Écrire une règle SELinux

Le projet Android Open Source (AOSP) fournit une règle de base solide pour les applications et les services communs à tous les appareils Android. Les contributeurs à AOSP affinent régulièrement ce règlement. La stratégie de base devrait représenter environ 90 à 95% de la stratégie finale sur l'appareil, les personnalisations spécifiques à l'appareil représentant les 5 à 10 % restants. Cet article se concentre sur ces personnalisations spécifiques à l'appareil, sur la façon d'écrire des règles spécifiques à l'appareil et sur certains des écueils à éviter.

Mise en service de l'appareil

Lorsque vous rédigez une stratégie spécifique à un appareil, procédez comme suit.

Exécuter en mode permissif

Lorsqu'un appareil est en mode permissif, les refus sont consignés, mais pas appliqués. Le mode permissif est important pour deux raisons:

  • Le mode permissif garantit que la mise en service des règles ne retarde pas les autres tâches de mise en service précoce de l'appareil.
  • Un refus forcé peut masquer d'autres refus. Par exemple, l'accès à un fichier implique généralement une recherche dans un répertoire, l'ouverture d'un fichier, puis la lecture d'un fichier. En mode d'application, seul le refus de recherche dans le répertoire se produit. Le mode permissif garantit que tous les refus sont visibles.

Le moyen le plus simple de passer un appareil en mode permissif consiste à utiliser la ligne de commande du noyau. Cet élément peut être ajouté au fichier BoardConfig.mk de l'appareil : platform/device/<vendor>/<target>/BoardConfig.mk. Après avoir modifié la ligne de commande, exécutez make clean, puis make bootimage et flashez la nouvelle image de démarrage.

Confirmez ensuite le mode permissif avec:

adb shell getenforce

Deux semaines est un délai raisonnable pour utiliser le mode permissif global. Une fois que vous avez résolu la majorité des refus, revenez en mode d'application et corrigez les bugs au fur et à mesure. Les domaines qui continuent de générer des refus ou les services en cours de développement peuvent être temporairement mis en mode permissif, mais ils doivent être rétablis en mode d'application dès que possible.

Appliquer dès le départ

En mode "Enforcing", les refus sont à la fois consignés et appliqués. Il est recommandé de mettre votre appareil en mode d'application dès que possible. Attendre de créer et d'appliquer une règle spécifique à l'appareil entraîne souvent un produit buggé et une mauvaise expérience utilisateur. Commencez suffisamment tôt pour participer au dogfooding et assurer une couverture complète des tests des fonctionnalités dans un usage réel. Commencer tôt permet de s'assurer que les problèmes de sécurité éclairent les décisions de conception. À l'inverse, accorder des autorisations uniquement en fonction des refus observés est une approche dangereuse. Profitez de ce moment pour effectuer un audit de sécurité de l'appareil et signaler les bugs liés à un comportement qui ne devrait pas être autorisé.

Supprimer une règle existante

Il existe un certain nombre de bonnes raisons de créer une règle spécifique à l'appareil à partir de zéro sur un nouvel appareil. Par exemple:

Refus d'adresses pour les services principaux

Les refus générés par les services principaux sont généralement gérés par le libellé de fichier. Exemple :

avc: denied { open } for pid=1003 comm=”mediaserver” path="/dev/kgsl-3d0”
dev="tmpfs" scontext=u:r:mediaserver:s0 tcontext=u:object_r:device:s0
tclass=chr_file permissive=1
avc: denied { read write } for pid=1003 name="kgsl-3d0" dev="tmpfs"
scontext=u:r:mediaserver:s0
tcontext=u:object_r:device:s0 tclass=chr_file permissive=1

est complètement résolu en étiquetant /dev/kgsl-3d0 correctement. Dans cet exemple, tcontext est device. Il s'agit d'un contexte par défaut dans lequel tout ce qui se trouve dans /dev reçoit le libellé " device", sauf si un libellé plus spécifique est attribué. Le simple fait d'accepter la sortie de audit2allow ici entraînerait une règle incorrecte et trop permissive.

Pour résoudre ce type de problème, attribuez au fichier un libellé plus spécifique, qui est gpu_device dans ce cas. Aucune autre autorisation n'est nécessaire, car le serveur multimédia dispose déjà des autorisations nécessaires dans la stratégie de base pour accéder à gpu_device.

Autres fichiers spécifiques à l'appareil qui doivent être libellés avec des types prédéfinis dans la règle de base:

En général, il est incorrect d'accorder des autorisations aux libellés par défaut. De nombreuses autorisations ne sont pas autorisées par les règles neverallow, mais même si elles ne sont pas explicitement interdites, il est recommandé de fournir un libellé spécifique.

Étiqueter les nouveaux services et les refus d'adresse

Les services lancés au démarrage doivent s'exécuter dans leurs propres domaines SELinux. L'exemple suivant place le service "foo" dans son propre domaine SELinux et lui accorde des autorisations.

Le service est lancé dans le fichier init.device.rc de notre appareil en tant que:

service foo /system/bin/foo
    class core
  1. Créer le domaine "foo"

    Créez le fichier device/manufacturer/device-name/sepolicy/foo.te avec le contenu suivant:

    # foo service
    type foo, domain;
    type foo_exec, exec_type, file_type;
    
    init_daemon_domain(foo)
    

    Il s'agit du modèle initial du domaine SELinux foo, auquel vous pouvez ajouter des règles en fonction des opérations spécifiques effectuées par cet exécutable.

  2. Étiquette /system/bin/foo

    Ajoutez les éléments suivants à device/manufacturer/device-name/sepolicy/file_contexts:

    /system/bin/foo   u:object_r:foo_exec:s0
    

    Cela permet de s'assurer que l'exécutable est correctement étiqueté, de sorte que SELinux exécute le service dans le domaine approprié.

  3. Créez et flashez les images de démarrage et système.
  4. Affinez les règles SELinux pour le domaine.

    Utilisez les refus pour déterminer les autorisations requises. L'outil audit2allow fournit de bonnes consignes, mais ne l'utilisez que pour vous aider à rédiger des règles. Ne copiez pas simplement la sortie.

Revenir au mode d'application

Vous pouvez effectuer le dépannage en mode permissif, mais revenez au mode d'application dès que possible et essayez de le conserver.

Erreurs courantes

Voici quelques solutions aux erreurs courantes qui se produisent lors de la rédaction de règles spécifiques à l'appareil.

Utilisation excessive de la négation

L'exemple de règle suivant équivaut à verrouiller la porte d'entrée, mais à laisser les fenêtres ouvertes:

allow { domain -untrusted_app } scary_debug_device:chr_file rw_file_perms

L'intention est claire: tout le monde, sauf les applications tierces, peut avoir accès à l'appareil de débogage.

Cette règle présente plusieurs défauts. L'exclusion de untrusted_app est simple à contourner, car toutes les applications peuvent éventuellement exécuter des services dans le domaine isolated_app. De même, si de nouveaux domaines pour des applications tierces sont ajoutés à l'AOSP, ils ont également accès à scary_debug_device. La règle est trop permissive. La plupart des domaines n'ont pas besoin d'accéder à cet outil de débogage. La règle aurait dû être écrite de manière à n'autoriser que les domaines qui ont besoin d'un accès.

Déboguer des fonctionnalités en production

Les fonctionnalités de débogage ne doivent pas être présentes dans les builds de production, ni leur stratégie.

La solution la plus simple consiste à n'autoriser la fonctionnalité de débogage que lorsque SELinux est désactivé sur les builds eng/userdebug, tels que adb root et adb shell setenforce 0.

Une autre alternative sûre consiste à encapsuler les autorisations de débogage dans une instruction userdebug_or_eng.

Explosion de la taille des règles

Characterizing SEAndroid Policies in the Wild décrit une tendance préoccupante concernant la croissance des personnalisations des règles relatives aux appareils. Les règles spécifiques à l'appareil doivent représenter 5 à 10% de la règle globale exécutée sur un appareil. Les personnalisations de plus de 20%contiennent presque certainement des domaines sur-privilégiés et des règles obsolètes.

Règle inutilement volumineuse:

  • La mémoire est doublement sollicitée, car la stratégie se trouve dans le ramdisk et est également chargée dans la mémoire du noyau.
  • Gâche de l'espace disque en nécessitant une image de démarrage plus importante.
  • Affecte les temps de recherche des règles d'exécution.

L'exemple suivant montre deux appareils dont la stratégie spécifique au fabricant représentait 50% et 40% de la stratégie sur l'appareil. Une réécriture de la règle a apporté d'importantes améliorations de sécurité sans perte de fonctionnalité, comme indiqué ci-dessous. (Les appareils AOSP Shamu et Flounder sont inclus à des fins de comparaison.)

Figure 1: Comparaison de la taille des règles spécifiques à l&#39;appareil après un audit de sécurité.

Figure 1 : Comparaison de la taille des règles spécifiques à l'appareil après un audit de sécurité.

Dans les deux cas, la taille et le nombre d'autorisations de la règle ont été considérablement réduits. La diminution de la taille de la stratégie est presque entièrement due à la suppression d'autorisations inutiles, dont beaucoup étaient probablement des règles générées par audit2allow qui ont été ajoutées sans discernement à la stratégie. Les domaines morts étaient également un problème pour les deux appareils.

Accorder la capacité dac_override

Un refus dac_override signifie que le processus incriminé tente d'accéder à un fichier avec des autorisations utilisateur/groupe/monde Unix incorrectes. La bonne solution consiste presque jamais à accorder l'autorisation dac_override. À la place, modifiez les autorisations Unix sur le fichier ou le processus. Quelques domaines tels que init, vold et installd ont vraiment besoin de la possibilité de remplacer les autorisations de fichiers Unix pour accéder aux fichiers d'autres processus. Consultez le blog de Dan Walsh pour en savoir plus.