Daemon de verrouillage en direct Android (llkd)

Android 10 inclut le daemon de blocage en direct Android (llkd), conçu pour détecter et atténuer les interblocages du noyau. Le composant llkd fournit une implémentation autonome par défaut, mais vous pouvez également intégrer le code llkd dans un autre service, dans la boucle principale ou en tant que thread distinct.

Scénarios de détection

llkd propose deux scénarios de détection: l'état D ou Z persistant et la signature de pile persistante.

État D ou Z persistant

Si un thread est dans l'état D (sommeil ininterruptible) ou Z (zombie) sans progression vers l'avant pendant plus de ro.llk.timeout_ms or ro.llk.[D|Z].timeout_ms, llkd arrête le processus (ou le processus parent). Si une analyse ultérieure montre que le même processus continue d'exister, llkd confirme une condition de verrouillage et provoque une panique du noyau de manière à fournir le rapport de bug le plus détaillé pour la condition.

llkd inclut un chien de garde automatique qui déclenche une alarme si llkd se bloque. Le chien de garde est le double du temps prévu pour passer par la boucle principale, et l'échantillonnage est effectué toutes les ro.llk_sample_ms.

Signature de pile persistante

Pour les versions userdebug, llkd peut détecter les verrous actifs du noyau à l'aide d'une vérification de signature de pile persistante. Si un thread dans n'importe quel état, sauf Z, possède un symbole de kernel ro.llk.stack listé de manière persistante qui est signalé pendant plus de temps que ro.llk.timeout_ms ou ro.llk.stack.timeout_ms, llkd arrête le processus (même s'il y a une progression de la planification en avant). Si une analyse ultérieure montre que le même processus continue d'exister, llkd confirme une condition de verrouillage et provoque une panique du noyau de manière à fournir le rapport de bug le plus détaillé pour la condition.

La vérification lldk persiste en permanence lorsque la condition de verrouillage actif existe et recherche les chaînes composées symbol+0x ou symbol.cfi+0x dans le fichier /proc/pid/stack sous Linux. La liste des symboles se trouve dans ro.llk.stack et est par défaut la liste cma_alloc,__get_user_pages,bit_wait_io,wait_on_page_bit_killable séparée par une virgule.

Les symboles doivent être suffisamment rares et éphémères pour qu'un système typique ne voie la fonction qu'une seule fois dans un échantillon au cours de la période de délai avant expiration de ro.llk.stack.timeout_ms (les échantillons se produisent toutes les ro.llk.check_ms). En raison de l'absence de protection ABA, il s'agit du seul moyen d'éviter un déclenchement incorrect. La fonction de symbole doit apparaître sous la fonction qui appelle le verrouillage susceptible de se produire. Si le cadenas se trouve en dessous ou dans la fonction de symbole, le symbole apparaît dans tous les processus concernés, et pas seulement dans celui qui a causé le blocage.

Couverture

L'implémentation par défaut de llkd ne surveille pas les apparitions de init, [kthreadd] ou [kthreadd]. Pour que llkd couvre les threads créés par [kthreadd]:

  • Les pilotes ne doivent pas rester dans un état D persistant.

OU

  • Les pilotes doivent disposer de mécanismes permettant de récupérer le thread s'il est arrêté en externe. Par exemple, utilisez wait_event_interruptible() au lieu de wait_event().

Si l'une des conditions ci-dessus est remplie, la liste de blocage llkd peut être ajustée pour couvrir les composants du noyau. La vérification des symboles de la pile implique une liste de blocage de processus supplémentaire pour éviter les cas de non-respect de la stratégie de sécurité sur les services qui bloquent les opérations ptrace.

Propriétés Android

llkd répond à plusieurs propriétés Android (listées ci-dessous).

  • Les propriétés nommées prop_ms sont exprimées en millisecondes.
  • Les propriétés qui utilisent la virgule (,) comme séparateur pour les listes utilisent un séparateur initial pour conserver l'entrée par défaut, puis ajoutent ou soustraient des entrées avec des préfixes facultatifs plus (+) et moins (-), respectivement. Pour ces listes, la chaîne false est synonyme d'une liste vide, et les entrées vides ou manquantes reviennent à la valeur par défaut spécifiée.

ro.config.low_ram

La mémoire de l'appareil est limitée.

ro.debuggable

L'appareil est configuré pour la version userdebug ou eng.

ro.llk.sysrq_t

Si la propriété est eng, la valeur par défaut n'est pas ro.config.low_ram ni ro.debuggable. Si la valeur est true, videz tous les threads (sysrq t).

ro.llk.enable

Autorisez l'activation du daemon de verrouillage en direct. La valeur par défaut est false.

llk.enable

Évalué pour les builds en anglais. La valeur par défaut est ro.llk.enable.

ro.kskutask.enable

Autorisez l'activation du démon [khungtask]. La valeur par défaut est false.

kskutask.enable

Évalué pour les builds en anglais. La valeur par défaut est ro.khungtask.enable.

ro.llk.mlockall

Activez l'appel à mlockall(). La valeur par défaut est false.

ro.khungtask.timeout

Limite de temps maximale [khungtask]. La valeur par défaut est de 12 minutes.

ro.llk.timeout_ms

D ou Z : limite de temps maximale. La valeur par défaut est de 10 minutes. Doublez cette valeur pour définir le moniteur d'alarme pour llkd.

ro.llk.D.timeout_ms

D : durée maximale. La valeur par défaut est ro.llk.timeout_ms.

ro.llk.Z.timeout_ms

Limite de temps maximale Z. La valeur par défaut est ro.llk.timeout_ms.

ro.llk.stack.timeout_ms

Vérifie la limite de temps maximale pour les symboles de pile persistants. La valeur par défaut est ro.llk.timeout_ms. Active uniquement sur les builds userdebug ou eng.

ro.llk.check_ms

Exemples de threads pour D ou Z. La valeur par défaut est de deux minutes.

ro.llk.stack

Recherche des symboles de pile du noyau qui, s'ils sont présents de manière persistante, peuvent indiquer qu'un sous-système est verrouillé. La valeur par défaut est une liste de symboles de kernel séparés par des virgules cma_alloc,__get_user_pages,bit_wait_io,wait_on_page_bit_killable. La vérification n'effectue pas de planification ABA en avant, sauf en interrogeant tous les ro.llk_check_ms sur la période ro.llk.stack.timeout_ms. Par conséquent, les symboles de pile doivent être exceptionnellement rares et éphémères (il est très peu probable qu'un symbole s'affiche de manière persistante dans tous les échantillons de la pile). Recherche une correspondance pour symbol+0x ou symbol.cfi+0x lors du développement de la pile. Disponible uniquement sur les builds userdebug ou eng ; les problèmes de sécurité sur les builds utilisateur entraînent des droits limités qui empêchent cette vérification.

ro.llk.blacklist.process

llkd ne surveille pas les processus spécifiés. La valeur par défaut est 0,1,2 (kernel, init et [kthreadd]) plus les noms de processus init,[kthreadd],[khungtaskd],lmkd,llkd,watchdogd, [watchdogd],[watchdogd/0],...,[watchdogd/get_nprocs-1]. Un processus peut être une référence comm, cmdline ou pid. Une valeur par défaut automatique peut être supérieure à la taille maximale actuelle de la propriété (92).

ro.llk.noir.parent

llkd ne surveille pas les processus dont les parents sont spécifiés. La valeur par défaut est 0,2,adbd&[setsid] (kernel, [kthreadd] et adbd uniquement pour les setsid zombies). L'esperluette (&) indique que le parent n'est ignoré qu'en combinaison avec le processus enfant cible. L'esperluette a été sélectionnée, car elle ne fait jamais partie d'un nom de processus. Toutefois, un setprop dans le shell nécessite que l'esperluette soit échappée ou mise entre guillemets, bien que le fichier init rc où cela est normalement spécifié ne présente pas ce problème. Un processus parent ou cible peut être une référence comm, cmdline ou pid.

ro.llk.blacklist.uid

llkd ne surveille pas les processus correspondant aux UID spécifiés. Liste de numéros ou de noms UIS séparés par une virgule. La valeur par défaut est vide ou false.

ro.llk.black.process.stack

llkd ne surveille pas le sous-ensemble de processus spécifié pour les signatures de pile de verrouillage en direct. La valeur par défaut est les noms de processus init,lmkd.llkd,llkd,keystore,ueventd,apexd,logd. Empêche la violation de la stratégie de sécurité associée aux processus qui bloquent ptrace (car ils ne peuvent pas être vérifiés). Active uniquement sur les builds userdebug et eng. Pour en savoir plus sur les types de compilation, consultez Compilation d'Android.

Problèmes d'architecture

  • Les propriétés sont limitées à 92 caractères (cependant, cette limite est ignorée pour les valeurs par défaut définies dans le fichier include/llkd.h dans les sources).
  • Le daemon [khungtask] intégré est trop générique et se déclenche sur le code du pilote qui reste trop longtemps en état D. Le passage à S rendrait les tâches tuables (et ressuscitables par les pilotes si nécessaire).

Interface de la bibliothèque (facultatif)

Vous pouvez éventuellement intégrer llkd à un autre daemon privilégié à l'aide de l'interface C suivante du composant libllkd:

#include "llkd.h"
bool llkInit(const char* threadname) /* return true if enabled */
unsigned llkCheckMillseconds(void)   /* ms to sleep for next check */

Si un nom de thread est fourni, un thread est automatiquement créé. Sinon, l'appelant doit appeler llkCheckMilliseconds dans sa boucle principale. La fonction renvoie la période avant le prochain appel attendu à ce gestionnaire.