Valider SELinux

Android encourage vivement les OEM à tester minutieusement leurs implémentations SELinux. Lorsque les fabricants implémentent SELinux, ils doivent d'abord appliquer la nouvelle règle à un pool d'appareils de test.

Après avoir appliqué une nouvelle règle, assurez-vous que SELinux s'exécute dans le mode approprié sur l'appareil en émettant la commande getenforce.

Le mode SELinux global (Enforcing ou Permissive) s'affiche. Pour déterminer le mode SELinux de chaque domaine, vous devez examiner les fichiers correspondants ou exécuter la dernière version de sepolicy-analyze avec l'indicateur approprié (-p), présent dans /platform/system/sepolicy/tools/.

Lire les refus

Recherchez les erreurs, qui sont acheminées en tant que journaux des événements vers dmesg et logcat, et qui sont visibles en local sur l'appareil. Les fabricants doivent examiner la sortie SELinux vers dmesg sur ces appareils et affiner les paramètres avant la publication publique en mode permissif et le passage au mode application forcée. Les messages de journal SELinux contiennent avc: et peuvent donc être facilement trouvés avec grep. Vous pouvez capturer les journaux de refus en cours en exécutant cat /proc/kmsg ou les journaux de refus du démarrage précédent en exécutant cat /sys/fs/pstore/console-ramoops.

Les messages d'erreur SELinux sont limités en débit une fois le démarrage terminé afin d'éviter de submerger les journaux. Pour vous assurer que vous voyez tous les messages pertinents, vous pouvez désactiver cette fonctionnalité en exécutant adb shell auditctl -r 0.

Grâce à cette sortie, les fabricants peuvent facilement identifier les cas où des utilisateurs ou des composants du système ne respectent pas la stratégie SELinux. Les fabricants peuvent ensuite réparer ce comportement insatisfaisant en modifiant le logiciel, le règlement SELinux ou les deux.

Plus précisément, ces messages de journal indiquent quels processus échouent en mode application et pourquoi. Voici un exemple :

avc: denied  { connectto } for  pid=2671 comm="ping" path="/dev/socket/dnsproxyd"
scontext=u:r:shell:s0 tcontext=u:r:netd:s0 tclass=unix_stream_socket

Interprétez cette sortie comme suit :

  • { connectto } ci-dessus représente l'action effectuée. Avec le tclass à la fin (unix_stream_socket), il vous indique approximativement ce qui a été fait et quoi. Dans ce cas, un élément essayait de se connecter à un socket de flux Unix.
  • scontext (u:r:shell:s0) vous indique le contexte à l'origine de l'action. Dans ce cas, il s'agit d'un élément exécuté en tant que shell.
  • tcontext (u:r:netd:s0) vous indique le contexte de la cible de l'action. Dans ce cas, il s'agit d'un unix_stream_socket appartenant à netd.
  • Le comm="ping" en haut de la page fournit des informations supplémentaires sur ce qui était en cours d'exécution au moment où le refus a été généré. Dans ce cas, c'est une bonne indication.

Autre exemple :

adb shell su root dmesg | grep 'avc: '

Sortie :

<5> type=1400 audit: avc:  denied  { read write } for  pid=177
comm="rmt_storage" name="mem" dev="tmpfs" ino=6004 scontext=u:r:rmt:s0
tcontext=u:object_r:kmem_device:s0 tclass=chr_file

Voici les principaux éléments de ce refus :

  • Action : l'action tentée est mise en évidence entre crochets, read write ou setenforce.
  • Acteur : l'entrée scontext (contexte source) représente l'acteur, dans ce cas le daemon rmt_storage.
  • Objet : l'entrée tcontext (contexte cible) représente l'objet sur lequel l'action est effectuée, dans ce cas kmem.
  • Résultat : l'entrée tclass (classe cible) indique le type d'objet sur lequel l'action est effectuée, dans ce cas un chr_file (appareil à caractères).

Vider les piles utilisateur et noyau

Dans certains cas, les informations contenues dans le journal des événements ne suffisent pas à identifier l'origine du refus. Il est souvent utile de collecter la chaîne d'appels, y compris le noyau et l'espace utilisateur, pour mieux comprendre pourquoi le refus s'est produit.

Les noyaux récents définissent un point de trace nommé avc:selinux_audited. Utilisez Android simpleperf pour activer ce point de trace et capturer la chaîne d'appels.

Configuration compatible

  • Le noyau Linux 5.10 ou version ultérieure, en particulier les branches du noyau commun Android mainline et android12-5.10, sont compatibles. La branche android12-5.4 est également prise en charge. Vous pouvez utiliser simpleperf pour déterminer si le point de trace est défini sur votre appareil : adb root && adb shell simpleperf list | grep avc:selinux_audited. Pour d'autres versions du kernel, vous pouvez sélectionner les commits dd81662 et 30969bc.
  • L'événement que vous déboguez doit pouvoir être reproduit. Les événements de temps de démarrage ne sont pas compatibles avec simpleperf. Toutefois, vous pouvez toujours redémarrer le service pour déclencher l'événement.

Capturer la chaîne d'appel

La première étape consiste à enregistrer l'événement à l'aide de simpleperf record:

adb shell -t "cd /data/local/tmp && su root simpleperf record -a -g -e avc:selinux_audited"

L'événement à l'origine du refus doit ensuite être déclenché. Passé ce délai, l'enregistrement doit être arrêté. Dans cet exemple, l'échantillon aurait dû être capturé à l'aide de Ctrl-c:

^Csimpleperf I cmd_record.cpp:751] Samples recorded: 1. Samples lost: 0.

Enfin, simpleperf report peut être utilisé pour inspecter la trace de la pile capturée. Exemple :

adb shell -t "cd /data/local/tmp && su root simpleperf report -g --full-callgraph"
[...]
Children  Self     Command  Pid   Tid   Shared Object                                   Symbol
100.00%   0.00%    dmesg    3318  3318  /apex/com.android.runtime/lib64/bionic/libc.so  __libc_init
       |
       -- __libc_init
          |
           -- main
              toybox_main
              toy_exec_which
              dmesg_main
              klogctl
              entry_SYSCALL_64_after_hwframe
              do_syscall_64
              __x64_sys_syslog
              do_syslog
              selinux_syslog
              slow_avc_audit
              common_lsm_audit
              avc_audit_post_callback
              avc_audit_post_callback

La chaîne d'appels ci-dessus est une chaîne d'appels unifiée pour le noyau et l'espace utilisateur. Elle vous offre une meilleure vue du flux de code en démarrant la trace de l'espace utilisateur jusqu'au noyau où le refus se produit. Pour en savoir plus sur simpleperf, consultez la documentation de référence sur les commandes exécutables Simpleperf.

Passer au mode permissif

L'application forcée de SELinux peut être désactivée avec adb sur les builds userdebug ou eng. Pour ce faire, commencez par faire passer ADB en mode root en exécutant adb root. Ensuite, pour désactiver l'application de SELinux, exécutez la commande suivante :

adb shell setenforce 0

Ou sur la ligne de commande du noyau (lors de l'utilisation initiale de l'appareil):

androidboot.selinux=permissive
androidboot.selinux=enforcing

Ou via bootconfig sous Android 12:

androidboot.selinux=permissive
androidboot.selinux=enforcing

Utiliser audit2allow

L'outil audit2allow prend les refus dmesg et les convertit en instructions de stratégie SELinux correspondantes. Ainsi, il peut accélérer considérablement le développement de SELinux.

Pour l'utiliser, exécutez la commande suivante :

adb pull /sys/fs/selinux/policy
adb logcat -b events -d | audit2allow -p policy

Néanmoins, il convient d'examiner chaque ajout potentiel afin de détecter un dépassement des autorisations. Par exemple, si vous alimentez audit2allow le refus rmt_storage affiché précédemment, vous obtenez l'instruction de règle SELinux suggérée suivante:

#============= shell ==============
allow shell kernel:security setenforce;
#============= rmt ==============
allow rmt kmem_device:chr_file { read write };

Cela permettrait à rmt d'écrire dans la mémoire du noyau, ce qui constitue un trou de sécurité flagrant. Les instructions audit2allow ne sont souvent qu'un point de départ. Après avoir utilisé ces instructions, vous devrez peut-être modifier le domaine source et le libellé de la cible, ainsi qu'intégrer les macros appropriées pour obtenir une bonne stratégie. Parfois, le refus examiné ne doit pas entraîner de modification du règlement. C'est plutôt l'application non conforme qui doit être modifiée.