Daemon de bloqueio em tempo real do Android (llkd, na sigla em inglês)

O Android 10 inclui o daemon do Android Live-Lock (llkd), desenvolvido para captar e atenuar os impasses do kernel. O componente llkd fornece uma implementação independente padrão, mas você também pode integrar o código llkd a outro serviço, seja como parte do loop principal ou como uma linha de execução separada.

Cenários de detecção

O llkd tem dois cenários de detecção: estado D ou Z persistente e assinatura de pilha persistente.

Estado D ou Z persistente

Se uma linha de execução estiver no estado D (sono ininterruptível) ou Z (zumbi) sem progresso de encaminhamento por mais tempo que ro.llk.timeout_ms or ro.llk.[D|Z].timeout_ms, o llkd encerrará o processo (ou processo pai). Se uma verificação subsequente mostrar que o mesmo processo continua existindo, o llkd confirma uma condição de bloqueio ativo e causa pânico no kernel de uma maneira que fornece o relatório de bug mais detalhado para a condição.

O llkd inclui um watchdog automático que emite um alarme se o llkd travar. O watchdog é o dobro do tempo esperado para fluir pelo loop principal, e a amostragem é feita a cada ro.llk_sample_ms.

Assinatura de pilha persistente

Para versões de userdebug, o llkd pode detectar bloqueios ao vivo do kernel usando a verificação persistente de assinatura de pilha. Se uma linha de execução em qualquer estado, exceto Z, tiver um símbolo de kernel ro.llk.stack listado persistente que é informado por mais de ro.llk.timeout_ms ou ro.llk.stack.timeout_ms, o llkd vai encerrar o processo (mesmo que haja progresso de programação de encaminhamento). Se uma verificação subsequente mostrar que o mesmo processo continua existindo, o llkd confirma uma condição de bloqueio ativo e causa pânico no kernel de uma maneira que fornece o relatório de bug mais detalhado para a condição.

A verificação lldk persiste continuamente quando a condição de bloqueio em tempo real existe e procura as strings compostas symbol+0x ou symbol.cfi+0x no arquivo /proc/pid/stack no Linux. A lista de símbolos está em ro.llk.stack e é definida como a lista separada por vírgulas de cma_alloc,__get_user_pages,bit_wait_io,wait_on_page_bit_killable.

Os símbolos precisam ser raros e de curta duração o suficiente para que, em um sistema típico, a função seja detectada apenas uma vez em uma amostra durante o período de tempo limite de ro.llk.stack.timeout_ms (as amostras ocorrem a cada ro.llk.check_ms). Devido à falta de proteção ABA, essa é a única maneira de evitar um gatilho falso. A função do símbolo precisa aparecer abaixo da função que chama a trava que pode competir. Se a trava estiver abaixo ou na função do símbolo, o símbolo vai aparecer em todos os processos afetados, não apenas naquele que causou a trava.

Cobertura

A implementação padrão de llkd não monitora a criação de init, [kthreadd] ou [kthreadd]. Para que o llkd cubra linhas de execução geradas com [kthreadd]:

  • Os drivers não podem permanecer em um estado D persistente,

OU

  • Os drivers precisam ter mecanismos para recuperar a linha de execução caso ela seja encerrada externamente. Por exemplo, use wait_event_interruptible() em vez de wait_event().

Se uma das condições acima for atendida, a lista de negação llkd poderá ser ajustada para cobrir os componentes do kernel. A verificação de símbolos da pilha envolve uma lista de restrições de processo adicional para evitar violações de política de segurança em serviços que bloqueiam operações ptrace.

Propriedades do Android

O llkd responde a várias propriedades do Android (listadas abaixo).

  • As propriedades com o nome prop_ms estão em milissegundos.
  • As propriedades que usam o separador de vírgula (,) para listas usam um separador inicial para preservar a entrada padrão e, em seguida, adicionam ou subtraem entradas com prefixos de adição (+) e subtração (-) opcionais, respectivamente. Para essas listas, a string false é sinônimo de uma lista vazia, e as entradas em branco ou ausentes recorrem ao valor padrão especificado.

ro.config.low_ram

O dispositivo está configurado com memória limitada.

ro.debuggable

O dispositivo está configurado para userdebug ou eng build.

ro.llk.sysrq_t

Se a propriedade for eng, o padrão não será ro.config.low_ram nem ro.debuggable. Se true, despeje todas as linhas de execução (sysrq t).

ro.llk.enable

Permitir que o daemon de bloqueio em tempo real seja ativado. O padrão é false.

llk.ativar

Avaliar para builds de eng. O padrão é ro.llk.enable.

ro.khungtask.enable

Permitir que o daemon [khungtask] seja ativado. O padrão é false.

khungtask.enable

Avaliar para builds de eng. O padrão é ro.khungtask.enable.

ro.llk.mlockall

Ative a chamada para mlockall(). O padrão é false.

ro.khungtask.timeout

[khungtask] limite de tempo máximo. O padrão é 12 minutos.

ro.llk.timeout_ms

Limite de tempo máximo D ou Z. O padrão é 10 minutos. Duplique esse valor para definir o servidor de controle de alarme para llkd.

ro.llk.D.timeout_ms

D limite de tempo máximo. O padrão é ro.llk.timeout_ms.

ro.llk.Z.timeout_ms

Z limite de tempo máximo. O padrão é ro.llk.timeout_ms.

ro.llk.stack.timeout_ms

Verifica o limite de tempo máximo dos símbolos da pilha persistente. O padrão é ro.llk.timeout_ms. Ativo apenas em builds userdebug ou eng.

ro.llk.check_ms

Exemplos de linhas de execução para D ou Z. O padrão é de dois minutos.

ro.llk.stack

Verifica se há símbolos de pilha do kernel que, se persistentemente presentes, podem indicar que um subsistema está bloqueado. O padrão é cma_alloc,__get_user_pages,bit_wait_io,wait_on_page_bit_killable, uma lista separada por vírgulas de símbolos do kernel. A verificação não faz programação avançada ABA, exceto por sondagem de cada ro.llk_check_ms no período ro.llk.stack.timeout_ms. Portanto, os símbolos da pilha devem ser excepcionalmente raros e transitórios. É muito improvável que um símbolo apareça de forma persistente em todas as amostras da pilha. Verifica se há uma correspondência para symbol+0x ou symbol.cfi+0x na expansão de pilha. Disponível apenas em builds de userdebug ou eng. As preocupações de segurança em builds de usuário resultam em privilégios limitados que impedem essa verificação.

ro.llk.blacklist.process

O llkd não monitora os processos especificados. O padrão é 0,1,2 (kernel, init e [kthreadd]) mais nomes de processos init,[kthreadd],[khungtaskd],lmkd,llkd,watchdogd, [watchdogd],[watchdogd/0],...,[watchdogd/get_nprocs-1]. Um processo pode ser uma referência comm, cmdline ou pid. Um padrão automatizado pode ser maior do que o tamanho máximo atual da propriedade, que é 92.

ro.llk.blacklist.parent

O llkd não monitora processos que têm os pais especificados. O padrão é 0,2,adbd&[setsid] (kernel, [kthreadd] e adbd apenas para setsid zumbificado). Um separador de "e comercial" (&) especifica que o processo pai é ignorado apenas em combinação com o processo filho de destino. O caractere & foi selecionado porque nunca faz parte de um nome de processo. No entanto, um setprop no shell exige que o caractere & seja escapado ou colocado entre aspas, embora o arquivo init rc em que ele é normalmente especificado não tenha esse problema. Um processo pai ou de destino pode ser uma referência comm, cmdline ou pid.

ro.llk.blacklist.uid

O llkd não monitora processos que correspondem aos UIDs especificados. Lista de números ou nomes de UIS separados por vírgulas. O padrão é vazio ou false.

ro.llk.blacklist.process.stack

O llkd não monitora o subconjunto especificado de processos para assinaturas de pilha de bloqueio em tempo real. O padrão é o nome do processo init,lmkd.llkd,llkd,keystore,ueventd,apexd,logd. Impede a violação de sepolicy associada a processos que bloqueiam ptrace (porque não podem ser verificados). Ativo apenas em builds userdebug e eng. Para saber mais sobre os tipos de build, consulte Como criar no Android.

Questões de arquitetura

  • As propriedades são limitadas a 92 caracteres. No entanto, isso é ignorado para padrões definidos no arquivo include/llkd.h nas origens.
  • O daemon [khungtask] integrado é muito genérico e falha em códigos de driver que ficam muito tempo no estado D. A mudança para S tornaria as tarefas mortais (e ressuscitáveis pelos drivers, se necessário).

Interface da biblioteca (opcional)

Você pode incorporar o llkd a outro daemon privilegiado usando a seguinte interface C do componente libllkd:

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

Se um nome de linha de execução for fornecido, uma linha de execução será gerada automaticamente. Caso contrário, o autor da chamada precisa chamar llkCheckMilliseconds no loop principal. A função retorna o período de tempo antes da próxima chamada esperada para esse gerenciador.