Validation de SELinux

Android encourage fortement les OEM à tester minutieusement leurs implémentations SELinux. À mesure que les fabricants implémentent SELinux, ils doivent d'abord appliquer la nouvelle politique à un pool de tests d'appareils.

Après avoir appliqué une nouvelle stratégie, assurez-vous que SELinux s'exécute dans le bon mode sur l'appareil en exécutant la commande getenforce .

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

Lecture des refus

Recherchez les erreurs, qui sont acheminées sous forme de journaux d'événements vers dmesg et logcat et sont visibles localement sur l'appareil. Les fabricants devraient examiner la sortie SELinux vers dmesg sur ces appareils et affiner les paramètres avant la publication publique en mode permissif et éventuellement passer en mode d'application. Les messages du journal SELinux contiennent avc: et peuvent donc être facilement trouvés avec grep . Il est possible de capturer les journaux de refus en cours en exécutant cat /proc/kmsg ou de capturer 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é pour éviter de submerger les journaux. Pour vous assurer de voir tous les messages pertinents, vous pouvez désactiver cela en exécutant adb shell auditctl -r 0 .

Grâce à ce résultat, les fabricants peuvent facilement identifier les utilisateurs ou les composants du système qui enfreignent la politique SELinux. Les fabricants peuvent ensuite réparer ce mauvais comportement, soit en modifiant le logiciel, soit en modifiant la politique SELinux, ou les deux.

Plus précisément, ces messages de journal indiquent quels processus échoueraient en mode d'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 ceci :

  • Le { connectto } ci-dessus représente l'action en cours. Avec la tclass à la fin ( unix_stream_socket ), il vous indique approximativement ce qui a été fait et quoi. Dans ce cas, quelque chose essayait de se connecter à un socket de flux Unix.
  • Le scontext (u:r:shell:s0) vous indique quel contexte a initié l'action. Dans ce cas, il s'agit de quelque chose qui s'exécute en tant que shell.
  • Le 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 vous donne une indication supplémentaire sur ce qui était en cours d'exécution au moment où le refus a été généré. Dans ce cas, c'est un très bon indice.

Un autre exemple:

adb shell su root dmesg | grep 'avc: '

Sortir:

<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 éléments clés de ce déni :

  • Action - l'action tentée est mise en évidence entre parenthèses, read write ou setenforce .
  • Acteur - L'entrée scontext (contexte source) représente l'acteur, dans ce cas le démon rmt_storage .
  • Objet - L'entrée tcontext (contexte cible) représente l'objet sur lequel on agit, dans ce cas kmem.
  • Résultat - L'entrée tclass (classe cible) indique le type d'objet sur lequel on agit, dans ce cas un chr_file (périphérique de caractère).

Vidage des piles d'utilisateurs et de 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 rassembler 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 prise en charge

  • Le noyau Linux >= 5.10, en particulier les branches Android Common Kernel mainline et android12-5.10 , sont pris en charge. 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 les autres versions du noyau, vous pouvez sélectionner les commits dd81662 et 30969bc .
  • Il devrait être possible de reproduire l'événement que vous déboguez. Les événements au moment du démarrage ne sont pas pris en charge avec simpleperf ; cependant, vous pourrez peut-être 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 simpleperf record :

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

Ensuite, l’événement qui a provoqué le refus devrait être déclenché. Après cela, l'enregistrement doit être arrêté. Dans cet exemple, en utilisant Ctrl-c , l'échantillon aurait dû être capturé :

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

Enfin, simpleperf report peut être utilisé pour inspecter la trace de pile capturée. Par 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 du noyau et de l'espace utilisateur. Il vous donne une meilleure vue du flux de code en démarrant la trace depuis l'espace utilisateur jusqu'au noyau où le refus se produit. Pour plus d'informations sur simpleperf , consultez la référence des commandes exécutables Simpleperf

Passer au permissif

L'application de SELinux peut être désactivée via ADB sur les versions userdebug ou eng. Pour ce faire, passez d’abord ADB à root en exécutant adb root . Ensuite, pour désactiver l'application de SELinux, exécutez :

adb shell setenforce 0

Ou sur la ligne de commande du noyau (lors de la première mise en service du périphérique) :

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 déclarations de stratégie SELinux correspondantes. En tant que tel, il peut considérablement accélérer le développement de SELinux.

Pour l'utiliser, exécutez :

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

Néanmoins, il faut veiller à examiner chaque ajout potentiel pour détecter des autorisations excessives. Par exemple, alimenter audit2allow le refus rmt_storage montré précédemment entraîne la déclaration de stratégie SELinux suggérée suivante :

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

Cela donnerait à rmt la possibilité d'écrire dans la mémoire du noyau, une faille de sécurité flagrante. Souvent, les instructions audit2allow ne sont qu'un point de départ. Après avoir utilisé ces instructions, vous devrez peut-être modifier le domaine source et l'étiquette de la cible, ainsi qu'incorporer les macros appropriées, pour parvenir à une bonne politique. Parfois, le refus examiné ne devrait entraîner aucun changement de politique ; l'application incriminée devrait plutôt être modifiée.