Daemon de bloqueo activo de Android (llkd)

Android 10 incluye el daemon de bloqueo activo de Android (llkd), que está diseñado para detectar y mitigar los interbloqueos del kernel. El componente llkd proporciona una implementación independiente predeterminada, pero también puedes integrar el código llkd en otro servicio, ya sea como parte del bucle principal o como un subproceso independiente.

Situaciones de detección

llkd tiene dos situaciones de detección: el estado D o Z persistente y la firma de pila persistente.

Estado D o Z persistente

Si un subproceso está en el estado D (suspensión ininterrumpida) o Z (zombificado) sin progreso durante más de ro.llk.timeout_ms or ro.llk.[D|Z].timeout_ms, llkd finaliza el proceso (o el proceso superior). Si un análisis posterior muestra que el mismo proceso sigue existiendo, llkd confirma una condición de bloqueo activo y genera un pánico en el kernel de una manera que proporciona el informe de errores más detallado para la condición.

llkd incluye un supervisor de eventos que genera una alarma si llkd se bloquea. El supervisor es el doble del tiempo esperado para fluir a través del bucle principal y el muestreo se realiza cada ro.llk_sample_ms.

Firma de pila persistente

En el caso de las versiones de userdebug, llkd puede detectar bloqueos en vivo del kernel con la verificación de firma de pila persistente. Si un subproceso en cualquier estado, excepto Z, tiene un símbolo del kernel ro.llk.stack enumerado de forma persistente que se informa durante más tiempo que ro.llk.timeout_ms o ro.llk.stack.timeout_ms, llkd finaliza el proceso (incluso si hay un progreso de programación hacia adelante). Si un análisis posterior muestra que el mismo proceso sigue existiendo, llkd confirma una condición de bloqueo activo y genera un pánico en el kernel de una manera que proporciona el informe de errores más detallado para la condición.

La verificación de lldk persiste de forma continua cuando existe la condición de bloqueo en tiempo real y busca las cadenas compuestas symbol+0x o symbol.cfi+0x en el archivo /proc/pid/stack de Linux. La lista de símbolos está en ro.llk.stack y, de forma predeterminada, es la lista separada por comas de cma_alloc,__get_user_pages,bit_wait_io,wait_on_page_bit_killable.

Los símbolos deben ser raros y de corta duración para que, en un sistema típico, la función se vea solo una vez en una muestra durante el período de tiempo de espera de ro.llk.stack.timeout_ms (las muestras se producen cada ro.llk.check_ms). Debido a la falta de protección de ABA, esta es la única forma de evitar un activador falso. La función de símbolo debe aparecer debajo de la función que llama al bloqueo que podría estar en conflicto. Si el bloqueo está debajo o en la función del símbolo, el símbolo aparece en todos los procesos afectados, no solo en el que causó el bloqueo.

Cobertura

La implementación predeterminada de llkd no supervisa los inicios de init, [kthreadd] ni [kthreadd]. Para que llkd cubra los subprocesos generados por [kthreadd], haz lo siguiente:

  • Los controladores no deben permanecer en un estado D persistente,

O

  • Los controladores deben tener mecanismos para recuperar el subproceso en caso de que se cancele de forma externa. Por ejemplo, usa wait_event_interruptible() en lugar de wait_event().

Si se cumple una de las condiciones anteriores, la lista de bloqueo llkd se puede ajustar para cubrir los componentes del kernel. La verificación de símbolos de pila implica una lista de entidades prohibidas de procesos adicional para evitar incumplimientos de la política de seguridad en los servicios que bloquean las operaciones de ptrace.

Propiedades de Android

El llkd responde a varias propiedades de Android (que se indican a continuación).

  • Las propiedades prop_ms se expresan en milisegundos.
  • Las propiedades que usan el separador de coma (,) para las listas usan un separador inicial para preservar la entrada predeterminada y, luego, agregar o quitar entradas con los prefijos opcionales de signo más (+) y signo menos (−), respectivamente. Para estas listas, la cadena false es sinónimo de una lista vacía, y las entradas en blanco o faltantes recurren al valor predeterminado especificado.

ro.config.low_ram

El dispositivo está configurado con memoria limitada.

ro.debuggable

El dispositivo está configurado para la compilación userdebug o eng.

ro.llk.sysrq_t

Si la propiedad es eng, el valor predeterminado no es ro.config.low_ram ni ro.debuggable. Si es true, volca todos los subprocesos (sysrq t).

ro.llk.enable

Permite que se habilite el daemon de bloqueo en vivo. El valor predeterminado es false.

llk.enable

Se evaluó para compilaciones de eng. El valor predeterminado es ro.llk.enable.

ro.khungtask.enable

Permite que se habilite el daemon [khungtask]. El valor predeterminado es false.

khungtask.enable

Se evaluó para compilaciones de eng. El valor predeterminado es ro.khungtask.enable.

ro.llk.mlockall

Habilitar llamada a mlockall(). El valor predeterminado es false.

ro.khungtask.timeout

[khungtask] límite de tiempo máximo. El valor predeterminado es de 12 minutos.

ro.llk.timeout_ms

Límite de tiempo máximo D o Z. El valor predeterminado es de 10 minutos. Duplica este valor para configurar el vigilante de alarma para llkd.

ro.llk.D.timeout_ms

D límite de tiempo máximo. El valor predeterminado es ro.llk.timeout_ms.

ro.llk.Z.timeout_ms

Límite de tiempo máximo de Z. El valor predeterminado es ro.llk.timeout_ms.

ro.llk.stack.timeout_ms

Verifica el límite de tiempo máximo de los símbolos de pila persistentes. El valor predeterminado es ro.llk.timeout_ms. Solo está activo en compilaciones userdebug o eng.

ro.llk.check_ms

Muestras de subprocesos para D o Z. El valor predeterminado es de dos minutos.

ro.llk.stack

Busca símbolos de pila de kernel que, si están presentes de forma persistente, pueden indicar que un subsistema está bloqueado. El valor predeterminado es una lista de símbolos del kernel separados por comas cma_alloc,__get_user_pages,bit_wait_io,wait_on_page_bit_killable. La verificación no realiza la programación anticipada de ABA, excepto mediante sondeos cada ro.llk_check_ms durante el período ro.llk.stack.timeout_ms, por lo que los símbolos de pila deben ser excepcionalmente raros y fugaces (es muy poco probable que un símbolo aparezca de forma persistente en todos los ejemplos de la pila). Busca una coincidencia para symbol+0x o symbol.cfi+0x en la expansión de pila. Disponible solo en compilaciones userdebug o eng. Los problemas de seguridad en las compilaciones de usuario generan privilegios limitados que impiden esta verificación.

ro.llk.blacklist.process

llkd no supervisa los procesos especificados. El valor predeterminado es 0,1,2 (kernel, init y [kthreadd]) más los nombres de los procesos init,[kthreadd],[khungtaskd],lmkd,llkd,watchdogd, [watchdogd],[watchdogd/0],...,[watchdogd/get_nprocs-1]. Un proceso puede ser una referencia comm, cmdline o pid. Un valor predeterminado automatizado puede ser mayor que el tamaño máximo actual de la propiedad de 92.

ro.llk.blacklist.parent

llkd no observa los procesos que tienen los elementos superiores especificados. El valor predeterminado es 0,2,adbd&[setsid] (kernel, [kthreadd] y adbd solo para setsid zombi). Un separador de signo & especifica que se ignora el elemento superior solo en combinación con el proceso secundario de destino. Se seleccionó el signo & porque nunca forma parte del nombre de un proceso. Sin embargo, un setprop en la shell requiere que se escape o entrecomille el signo &, aunque el archivo init rc en el que se especifica normalmente no tiene este problema. Un proceso superior o de destino puede ser una referencia comm, cmdline o pid.

ro.llk.get.uid.uid

llkd no supervisa los procesos que coinciden con los UIDs especificados. Es una lista de números o nombres de UIS separados por comas. El valor predeterminado es vacío o false.

ro.llk.blacklist.process.stack

llkd no supervisa el subconjunto especificado de procesos para las firmas de pila de bloqueo en vivo. El valor predeterminado es el nombre de los procesos init,lmkd.llkd,llkd,keystore,ueventd,apexd,logd. Evita la violación de sepolicy asociada con los procesos que bloquean ptrace (ya que no se pueden verificar). Solo está activo en compilaciones de userdebug y eng. Para obtener detalles sobre los tipos de compilación, consulta Cómo compilar Android.

Preocupaciones arquitectónicas

  • Las propiedades se limitan a 92 caracteres (sin embargo, esto se ignora para los valores predeterminados definidos en el archivo include/llkd.h de las fuentes).
  • El daemon [khungtask] integrado es demasiado genérico y pasa demasiado el código del controlador que está en estado D. Si se cambia a S, se podrían finalizar las tareas (y los controladores podrían resucitarlas si fuera necesario).

Interfaz de la biblioteca (opcional)

De manera opcional, puedes incorporar el llkd a otro daemon con privilegios mediante la siguiente interfaz de C del componente libllkd:

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

Si se proporciona un nombre de subproceso, se crea uno automáticamente. De lo contrario, el llamador debe llamar a llkCheckMilliseconds en su bucle principal. La función muestra el período de tiempo antes de la próxima llamada esperada a este controlador.